diff --git a/Cargo.lock b/Cargo.lock index 6e70ea0c..f7319bd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -76,9 +76,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "assert_cmd" @@ -108,9 +108,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "bitflags" @@ -120,9 +120,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bstr" @@ -137,9 +137,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.3" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bytecount" @@ -181,7 +181,7 @@ dependencies = [ "lazy_static", "mutants 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "nextest-metadata", - "nix 0.28.0", + "nix", "nutmeg", "patch", "path-slash", @@ -190,12 +190,13 @@ dependencies = [ "proc-macro2", "quote", "regex", + "rusty-fork", "serde", "serde_json", "similar", "strum", "subprocess", - "syn 2.0.52", + "syn 2.0.58", "tempfile", "time", "toml", @@ -208,9 +209,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" dependencies = [ "serde", ] @@ -231,9 +232,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.88" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" +checksum = "1fd97381a8cc6493395a5afc4c691c1084b3768db713b73aa215217aa245d153" [[package]] name = "cfg-expr" @@ -259,9 +260,9 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -273,9 +274,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.1" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -283,9 +284,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -305,14 +306,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -412,11 +413,11 @@ checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "ctrlc" -version = "3.4.2" +version = "3.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" dependencies = [ - "nix 0.27.1", + "nix", "windows-sys 0.52.0", ] @@ -477,9 +478,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "filetime" @@ -502,6 +503,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fs2" version = "0.4.3" @@ -543,6 +550,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -605,9 +618,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", @@ -615,21 +628,20 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "insta" -version = "1.35.1" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c985c1bef99cf13c58fade470483d81a2bfe846ebde60ed28cc2dddec2df9e2" +checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc" dependencies = [ "console", "lazy_static", "linked-hash-map", "similar", - "yaml-rust", ] [[package]] @@ -654,15 +666,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -715,9 +727,9 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "minimal-lexical" @@ -755,24 +767,13 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d906846a98739ed9d73d66e62c2641eef8321f1734b7a1156ab045a0248fb2b3" -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.4.2", - "cfg-if", - "libc", -] - [[package]] name = "nix" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "cfg_aliases", "libc", @@ -896,9 +897,9 @@ checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "powerfmt" @@ -948,13 +949,19 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.35" @@ -975,9 +982,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -987,9 +994,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -998,9 +1005,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustix" @@ -1018,11 +1025,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys 0.4.13", @@ -1031,9 +1038,21 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] [[package]] name = "ryu" @@ -1082,14 +1101,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -1116,15 +1135,15 @@ dependencies = [ [[package]] name = "similar" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smol_str" @@ -1137,30 +1156,30 @@ dependencies = [ [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -1186,9 +1205,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -1223,7 +1242,7 @@ checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "rustix 0.38.31", + "rustix 0.38.32", "windows-sys 0.52.0", ] @@ -1243,7 +1262,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.31", + "rustix 0.38.32", "windows-sys 0.48.0", ] @@ -1255,22 +1274,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -1316,9 +1335,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.10" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", @@ -1337,9 +1356,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ "indexmap", "serde", @@ -1379,7 +1398,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -1468,9 +1487,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1478,24 +1497,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1503,28 +1522,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -1532,9 +1551,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fec781d48b41f8163426ed18e8fc2864c12937df9ce54c88ede7bd47270893e" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" dependencies = [ "redox_syscall", "wasite", @@ -1722,15 +1741,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 749ac839..1b8443c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,6 +98,7 @@ lazy_static = "1.4" predicates = "3" pretty_assertions = "1" regex = "1.5" +rusty-fork = "0.3" walkdir = "2.3" [workspace] diff --git a/NEWS.md b/NEWS.md index f34fa1f5..ae4ebdc0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,6 +14,8 @@ - Changed: Minimum Rust version (to build cargo-mutants, not to use it) increased to 1.74. +- Changed: Removed the count of `failure` from `mutants.out/outcomes.json`: it was already the case that every outcome received some other classification, so the count was always zero. + ## 24.2.1 - New: `--features`, `--no-default-features` and `--all-features` options are passed through to Cargo. diff --git a/src/cargo.rs b/src/cargo.rs index 85a856cc..1d07ff6f 100644 --- a/src/cargo.rs +++ b/src/cargo.rs @@ -37,15 +37,11 @@ pub fn run_cargo( let process_status = Process::run(&argv, &env, build_dir.path(), timeout, log_file, console)?; check_interrupted()?; debug!(?process_status, elapsed = ?start.elapsed()); - if options.test_tool == TestTool::Nextest && phase == Phase::Test { - // Nextest returns detailed exit codes. I think we should still treat any non-zero result as just an - // error, but we can at least warn if it's unexpected. - if let ProcessStatus::Failure(code) = process_status { - // TODO: When we build with `nextest test --no-test` then we should also check build - // processes. - if code != NextestExitCode::TEST_RUN_FAILED as u32 { - warn!(%code, "nextest process exited with unexpected code (not TEST_RUN_FAILED)"); - } + if let ProcessStatus::Failure(code) = process_status { + if argv[1] == "nextest" && code != NextestExitCode::TEST_RUN_FAILED as u32 { + // Nextest returns detailed exit codes. I think we should still treat any non-zero result as just an + // error, but we can at least warn if it's unexpected. + warn!(%code, "nextest process exited with unexpected code (not TEST_RUN_FAILED)"); } } Ok(PhaseResult { @@ -158,6 +154,7 @@ mod test { use std::sync::Arc; use pretty_assertions::assert_eq; + use rusty_fork::rusty_fork_test; use super::*; @@ -294,4 +291,27 @@ mod test { ] ); } + + rusty_fork_test! { + #[test] + fn rustflags_with_no_environment_variables() { + env::remove_var("RUSTFLAGS"); + env::remove_var("CARGO_ENCODED_RUSTFLAGS"); + assert_eq!(rustflags(), "--cap-lints=allow"); + } + + #[test] + fn rustflags_added_to_existing_encoded_rustflags() { + env::set_var("RUSTFLAGS", "--something\x1f--else"); + env::remove_var("CARGO_ENCODED_RUSTFLAGS"); + assert_eq!(rustflags(), "--something\x1f--else\x1f--cap-lints=allow"); + } + + #[test] + fn rustflags_added_to_existing_rustflags() { + env::set_var("RUSTFLAGS", "-Dwarnings"); + env::remove_var("CARGO_ENCODED_RUSTFLAGS"); + assert_eq!(rustflags(), "-Dwarnings\x1f--cap-lints=allow"); + } + } } diff --git a/src/config.rs b/src/config.rs index e81dc228..6b2d25f6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,6 +11,7 @@ use std::default::Default; use std::fs::read_to_string; use std::path::Path; +use std::str::FromStr; use anyhow::Context; use camino::Utf8Path; @@ -65,3 +66,11 @@ impl Config { } } } + +impl FromStr for Config { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + toml::de::from_str(s).with_context(|| "parse toml") + } +} diff --git a/src/console.rs b/src/console.rs index 7cf3e2d2..c8db13a5 100644 --- a/src/console.rs +++ b/src/console.rs @@ -142,12 +142,6 @@ impl Console { self.message(&s); } - pub fn build_dirs_start(&self, _n: usize) { - // self.message(&format!("Make {n} more build directories...\n")); - } - - pub fn build_dirs_finished(&self) {} - pub fn start_copy(&self) { self.view.update(|model| { assert!(model.copy_model.is_none()); diff --git a/src/fnvalue.rs b/src/fnvalue.rs index 1e8a0b73..112afe92 100644 --- a/src/fnvalue.rs +++ b/src/fnvalue.rs @@ -1,4 +1,4 @@ -// Copyright 2021-2023 Martin Pool +// Copyright 2021-2024 Martin Pool //! Mutations of replacing a function body with a value of a (hopefully) appropriate type. @@ -242,16 +242,19 @@ fn path_ends_with(path: &Path, ident: &str) -> bool { fn match_impl_iterator(TypeImplTrait { bounds, .. }: &TypeImplTrait) -> Option<&Type> { for bound in bounds { if let TypeParamBound::Trait(TraitBound { path, .. }) = bound { - if path.segments.len() == 1 && path.segments[0].ident == "Iterator" { - if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { - args, .. - }) = &path.segments[0].arguments - { - if let Some(GenericArgument::AssocType(AssocType { ident, ty, .. })) = - args.first() + if let Some(last_segment) = path.segments.last() { + if last_segment.ident == "Iterator" { + if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { + args, + .. + }) = &last_segment.arguments { - if ident == "Item" { - return Some(ty); + if let Some(GenericArgument::AssocType(AssocType { ident, ty, .. })) = + args.first() + { + if ident == "Item" { + return Some(ty); + } } } } @@ -288,6 +291,8 @@ fn known_container(path: &Path) -> Option<(&Ident, &Type)> { /// Match known simple collections that can be empty or constructed from an /// iterator. +/// +/// Returns the short name (like "VecDeque") and the inner type. fn known_collection(path: &Path) -> Option<(&Ident, &Type)> { let last = path.segments.last()?; if ![ @@ -432,6 +437,7 @@ mod test { use pretty_assertions::assert_eq; use syn::{parse_quote, Expr, ReturnType}; + use crate::fnvalue::match_impl_iterator; use crate::pretty::ToPrettyString; use super::{known_map, return_type_replacements}; @@ -580,6 +586,69 @@ mod test { ); } + #[test] + fn match_known_collection() { + assert_eq!( + super::known_collection(&parse_quote! { std::collections::VecDeque }), + Some((&parse_quote! { VecDeque }, &parse_quote! { String })) + ); + + assert_eq!( + super::known_collection(&parse_quote! { std::collections::BinaryHeap<(u32, u32)> }), + Some((&parse_quote! { BinaryHeap }, &parse_quote! { (u32, u32) })) + ); + + assert_eq!( + super::known_collection(&parse_quote! { LinkedList<[u8; 256]> }), + Some((&parse_quote! { LinkedList }, &parse_quote! { [u8; 256] })) + ); + + assert_eq!(super::known_collection(&parse_quote! { Arc }), None); + + // This might be a collection, and is handled generically, but it's not a specifically known + // collection type. (Maybe we shouldn't bother knowing specific types?) + assert_eq!( + super::known_collection(&parse_quote! { Wibble<&str> }), + None + ); + } + + #[test] + fn match_known_map() { + assert_eq!( + super::known_map(&parse_quote! { std::collections::BTreeMap }), + Some(( + &parse_quote! { BTreeMap }, + &parse_quote! { String }, + &parse_quote! { usize } + )) + ); + + assert_eq!( + super::known_map(&parse_quote! { std::collections::HashMap<(usize, usize), bool> }), + Some(( + &parse_quote! { HashMap }, + &parse_quote! { (usize, usize) }, + &parse_quote! { bool } + )) + ); + + assert_eq!( + super::known_map(&parse_quote! { Option<(usize, usize)> }), + None + ); + + assert_eq!( + super::known_map(&parse_quote! { MyMap }), + None, + ); + + assert_eq!( + super::known_map(&parse_quote! { Pair }), + None, + ); + } + #[test] fn btreeset_replacement() { check_replacements( @@ -664,6 +733,25 @@ mod test { ); } + #[test] + fn impl_matches_iterator() { + assert_eq!( + match_impl_iterator(&parse_quote! { impl std::iter::Iterator }), + Some(&parse_quote! { String }) + ); + assert_eq!( + match_impl_iterator(&parse_quote! { impl Iterator }), + Some(&parse_quote! { String }) + ); + // Strange, maybe it's a type defined in this crate, but we don't know what to + // do with it. + assert_eq!(match_impl_iterator(&parse_quote! { impl Iterator }), None); + assert_eq!( + match_impl_iterator(&parse_quote! { impl Borrow }), + None + ); + } + #[test] fn slice_replacement() { check_replacements( diff --git a/src/in_diff.rs b/src/in_diff.rs index 480642bc..b7be4e98 100644 --- a/src/in_diff.rs +++ b/src/in_diff.rs @@ -117,7 +117,7 @@ fn strip_patch_path(path: &str) -> &Utf8Path { /// /// If a line is deleted then the range will span from the line before to the line after. fn affected_lines(patch: &Patch) -> Vec { - let mut r = Vec::new(); + let mut affected_lines = Vec::new(); for hunk in &patch.hunks { let mut lineno: usize = hunk.new_range.start.try_into().unwrap(); // True if the previous line was deleted. If set, then the next line that exists in the @@ -131,11 +131,11 @@ fn affected_lines(patch: &Patch) -> Vec { Line::Add(_) | Line::Context(_) => { if prev_removed { debug_assert!( - r.last().map_or(true, |last| *last < lineno), - "{lineno} {r:?}" + affected_lines.last().map_or(true, |last| *last < lineno), + "{lineno} {affected_lines:?}" ); debug_assert!(lineno >= 1, "{lineno}"); - r.push(lineno); + affected_lines.push(lineno); prev_removed = false; } } @@ -145,24 +145,28 @@ fn affected_lines(patch: &Patch) -> Vec { lineno += 1; } Line::Add(_) => { - if r.last().map_or(true, |last| *last < lineno) { - r.push(lineno); + if affected_lines.last().map_or(true, |last| *last != lineno) { + affected_lines.push(lineno); } lineno += 1; } Line::Remove(_) => { - if lineno > 1 && r.last().map_or(true, |last| *last < (lineno - 1)) { - r.push(lineno - 1); + if lineno > 1 + && affected_lines + .last() + .map_or(true, |last| *last != (lineno - 1)) + { + affected_lines.push(lineno - 1); } } } } } debug_assert!( - r.iter().tuple_windows().all(|(a, b)| a < b), - "remove_context: line numbers not sorted and unique: {r:?}" + affected_lines.iter().tuple_windows().all(|(a, b)| a < b), + "remove_context: line numbers not sorted and unique: {affected_lines:?}" ); - r + affected_lines } /// Recreate a partial view of the new file from a Patch. diff --git a/src/lab.rs b/src/lab.rs index 689acdf0..fd0fd64a 100644 --- a/src/lab.rs +++ b/src/lab.rs @@ -85,10 +85,14 @@ pub fn test_mutants( BaselineStrategy::Skip => None, }; let mut build_dirs = vec![build_dir]; - let test_timeout = test_timeout(&baseline_outcome, &options); + let baseline_test_duration = baseline_outcome + .as_ref() + .and_then(|so| so.phase_result(Phase::Test)) + .map(|pr| pr.duration); + + let test_timeout = test_timeout(baseline_test_duration, &options); let jobs = max(1, min(options.jobs.unwrap_or(1), mutants.len())); - console.build_dirs_start(jobs - 1); for i in 1..jobs { debug!("copy build dir {i}"); build_dirs.push(BuildDir::copy_from( @@ -98,7 +102,6 @@ pub fn test_mutants( console, )?); } - console.build_dirs_finished(); debug!(build_dirs = ?build_dirs); // Create n threads, each dedicated to one build directory. Each of them tries to take a @@ -156,23 +159,16 @@ pub fn test_mutants( Ok(lab_outcome) } -fn test_timeout(baseline_outcome: &Option, options: &Options) -> Duration { +fn test_timeout(baseline_test_duration: Option, options: &Options) -> Duration { if let Some(timeout) = options.test_timeout { timeout } else if options.check_only { Duration::ZERO - } else if options.baseline == BaselineStrategy::Skip { - warn!("An explicit timeout is recommended when using --baseline=skip; using 300 seconds by default"); - Duration::from_secs(300) - } else { + } else if let Some(baseline_test_duration) = baseline_test_duration { let timeout = max( options.minimum_test_timeout, Duration::from_secs( - (baseline_outcome - .as_ref() - .expect("Baseline tests should have run") - .total_phase_duration(Phase::Test) - .as_secs_f64() + (baseline_test_duration.as_secs_f64() * options.test_timeout_multiplier.unwrap_or(5.0)) .round() as u64, ), @@ -184,6 +180,9 @@ fn test_timeout(baseline_outcome: &Option, options: &Options) - ); } timeout + } else { + warn!("An explicit timeout is recommended when using --baseline=skip; using 300 seconds by default"); + Duration::from_secs(300) } } @@ -240,7 +239,7 @@ fn test_scenario( let success = phase_result.is_success(); // so we can move it away outcome.add_phase_result(phase_result); console.scenario_phase_finished(scenario, phase); - if (phase == Phase::Check && options.check_only) || !success { + if !success { break; } } @@ -257,14 +256,61 @@ fn test_scenario( #[cfg(test)] mod test { + use std::str::FromStr; + + use indoc::indoc; + use super::*; use crate::config::Config; #[test] - fn test_timeout_multiplier_correct_parsing() { + fn timeout_multiplier_from_option() { let args = Args::parse_from(["mutants", "--timeout-multiplier", "1.5"]); let options = Options::new(&args, &Config::default()).unwrap(); - assert_eq!(options.test_timeout_multiplier, Some(1.5)) + assert_eq!(options.test_timeout_multiplier, Some(1.5)); + assert_eq!( + test_timeout(Some(Duration::from_secs(40)), &options), + Duration::from_secs(60), + ); + } + + #[test] + fn timeout_multiplier_from_config() { + let args = Args::parse_from(["mutants"]); + let config = Config::from_str(indoc! {r#" + timeout_multiplier = 2.0 + "#}) + .unwrap(); + let options = Options::new(&args, &config).unwrap(); + + assert_eq!(options.test_timeout_multiplier, Some(2.0)); + assert_eq!( + test_timeout(Some(Duration::from_secs(42)), &options), + Duration::from_secs(42 * 2), + ); + } + + #[test] + fn timeout_multiplier_default() { + let args = Args::parse_from(["mutants"]); + let options = Options::new(&args, &Config::default()).unwrap(); + + assert_eq!(options.test_timeout_multiplier, None); + assert_eq!( + test_timeout(Some(Duration::from_secs(42)), &options), + Duration::from_secs(42 * 5), + ); + } + + #[test] + fn timeout_multiplier_default_with_baseline_skip() { + // The --baseline option is not used to set the timeout but it's + // indicative of the realistic situation. + let args = Args::parse_from(["mutants", "--baseline", "skip"]); + let options = Options::new(&args, &Config::default()).unwrap(); + + assert_eq!(options.test_timeout_multiplier, None); + assert_eq!(test_timeout(None, &options), Duration::from_secs(300),); } } diff --git a/src/options.rs b/src/options.rs index 9dab6024..d9a4d4cb 100644 --- a/src/options.rs +++ b/src/options.rs @@ -124,10 +124,7 @@ pub enum TestTool { /// Join two slices into a new vector. fn join_slices(a: &[String], b: &[String]) -> Vec { - let mut v = Vec::with_capacity(a.len() + b.len()); - v.extend_from_slice(a); - v.extend_from_slice(b); - v + a.iter().chain(b).cloned().collect() } /// Should ANSI colors be drawn? @@ -162,6 +159,7 @@ impl Colors { } } + #[mutants::skip] // depends on a real tty etc, hard to test pub fn active_stdout(&self) -> bool { self.forced_value() .unwrap_or_else(::console::colors_enabled) @@ -247,6 +245,7 @@ mod test { use std::io::Write; use indoc::indoc; + use rusty_fork::rusty_fork_test; use tempfile::NamedTempFile; use super::*; @@ -350,4 +349,60 @@ mod test { assert!(!options.features.no_default_features); assert!(options.features.all_features); } + + rusty_fork_test! { + #[test] + fn color_control_from_cargo_env() { + use std::env::{set_var,remove_var}; + + set_var("CARGO_TERM_COLOR", "always"); + remove_var("CLICOLOR_FORCE"); + remove_var("NO_COLOR"); + let args = Args::parse_from(["mutants"]); + let options = Options::new(&args, &Config::default()).unwrap(); + assert_eq!(options.colors.forced_value(), Some(true)); + + set_var("CARGO_TERM_COLOR", "never"); + let args = Args::parse_from(["mutants"]); + let options = Options::new(&args, &Config::default()).unwrap(); + assert_eq!(options.colors.forced_value(), Some(false)); + + set_var("CARGO_TERM_COLOR", "auto"); + let args = Args::parse_from(["mutants"]); + let options = Options::new(&args, &Config::default()).unwrap(); + assert_eq!(options.colors.forced_value(), None); + + remove_var("CARGO_TERM_COLOR"); + let args = Args::parse_from(["mutants"]); + let options = Options::new(&args, &Config::default()).unwrap(); + assert_eq!(options.colors.forced_value(), None); + } + + #[test] + fn color_control_from_env() { + use std::env::{set_var,remove_var}; + + remove_var("CARGO_TERM_COLOR"); + remove_var("CLICOLOR_FORCE"); + remove_var("NO_COLOR"); + let args = Args::parse_from(["mutants"]); + let options = Options::new(&args, &Config::default()).unwrap(); + assert_eq!(options.colors.forced_value(), None); + + remove_var("CLICOLOR_FORCE"); + set_var("NO_COLOR", "1"); + let options = Options::new(&args, &Config::default()).unwrap(); + assert_eq!(options.colors.forced_value(), Some(false)); + + remove_var("NO_COLOR"); + set_var("CLICOLOR_FORCE", "1"); + let options = Options::new(&args, &Config::default()).unwrap(); + assert_eq!(options.colors.forced_value(), Some(true)); + + remove_var("CLICOLOR_FORCE"); + remove_var("NO_COLOR"); + let options = Options::new(&args, &Config::default()).unwrap(); + assert_eq!(options.colors.forced_value(), None); + } + } } diff --git a/src/outcome.rs b/src/outcome.rs index ac805427..4c8840fd 100644 --- a/src/outcome.rs +++ b/src/outcome.rs @@ -11,6 +11,7 @@ use humantime::format_duration; use serde::ser::SerializeStruct; use serde::Serialize; use serde::Serializer; +use tracing::warn; use crate::console::plural; use crate::process::ProcessStatus; @@ -61,7 +62,6 @@ pub struct LabOutcome { pub timeout: usize, pub unviable: usize, pub success: usize, - pub failure: usize, } impl LabOutcome { @@ -79,7 +79,10 @@ impl LabOutcome { SummaryOutcome::Timeout => self.timeout += 1, SummaryOutcome::Unviable => self.unviable += 1, SummaryOutcome::Success => self.success += 1, - SummaryOutcome::Failure => self.failure += 1, + SummaryOutcome::Failure => { + // We don't expect to see failures that don't fit into the other categories. + warn!("Unclassified failure for mutant {:?}", outcome.scenario); + } } } self.outcomes.push(outcome); @@ -115,24 +118,21 @@ impl LabOutcome { } s.push(": ".into()); let mut by_outcome: Vec = Vec::new(); - if self.missed > 0 { + if self.missed != 0 { by_outcome.push(format!("{} missed", self.missed)); } - if self.caught > 0 { + if self.caught != 0 { by_outcome.push(format!("{} caught", self.caught)); } - if self.unviable > 0 { + if self.unviable != 0 { by_outcome.push(format!("{} unviable", self.unviable)); } - if self.timeout > 0 { + if self.timeout != 0 { by_outcome.push(format!("{} timeouts", self.timeout)); } - if self.success > 0 { + if self.success != 0 { by_outcome.push(format!("{} succeeded", self.success)); } - if self.failure > 0 { - by_outcome.push(format!("{} failed", self.failure)); - } s.push(by_outcome.join(", ")); s.join("") } @@ -191,19 +191,14 @@ impl ScenarioOutcome { self.phase_results.last().unwrap().process_status } + /// Return the results of all phases. pub fn phase_results(&self) -> &[PhaseResult] { &self.phase_results } - /// Return the total time spent in commands for one phase. - /// - /// If the phase was not run, returns zero. - pub fn total_phase_duration(&self, phase: Phase) -> Duration { - self.phase_results - .iter() - .filter(|pr| pr.phase == phase) - .map(|pr| pr.duration) - .sum() + /// Return the result of the given phase, if it was run. + pub fn phase_result(&self, phase: Phase) -> Option<&PhaseResult> { + self.phase_results.iter().find(|pr| pr.phase == phase) } /// True if this status indicates the user definitely needs to see the logs, because a task @@ -243,6 +238,8 @@ impl ScenarioOutcome { } pub fn summary(&self) -> SummaryOutcome { + // Caution: this function is called when rendering progress + // and so should not log; see https://github.com/sourcefrog/nutmeg/issues/16. match self.scenario { Scenario::Baseline => { if self.has_timeout() { @@ -265,6 +262,7 @@ impl ScenarioOutcome { } else if self.success() { SummaryOutcome::Success } else { + // Some unattributed failure; should be rare or impossible? SummaryOutcome::Failure } } @@ -315,3 +313,60 @@ pub enum SummaryOutcome { Failure, Timeout, } + +#[cfg(test)] +mod test { + use std::time::Duration; + + use crate::process::ProcessStatus; + + use super::{Phase, PhaseResult, Scenario, ScenarioOutcome}; + + #[test] + fn find_phase_result() { + let outcome = ScenarioOutcome { + log_path: "log".into(), + scenario: Scenario::Baseline, + phase_results: vec![ + PhaseResult { + phase: Phase::Build, + duration: Duration::from_secs(2), + process_status: ProcessStatus::Success, + argv: vec!["cargo".into(), "build".into()], + }, + PhaseResult { + phase: Phase::Test, + duration: Duration::from_secs(3), + process_status: ProcessStatus::Success, + argv: vec!["cargo".into(), "test".into()], + }, + ], + }; + assert_eq!( + outcome.phase_result(Phase::Build), + Some(&PhaseResult { + phase: Phase::Build, + duration: Duration::from_secs(2), + process_status: ProcessStatus::Success, + argv: vec!["cargo".into(), "build".into()], + }) + ); + assert_eq!( + outcome + .phase_result(Phase::Build) + .unwrap() + .duration + .as_secs(), + 2 + ); + assert_eq!( + outcome + .phase_result(Phase::Test) + .unwrap() + .duration + .as_secs(), + 3 + ); + assert_eq!(outcome.phase_result(Phase::Check), None); + } +} diff --git a/src/output.rs b/src/output.rs index b3578826..722ceb7d 100644 --- a/src/output.rs +++ b/src/output.rs @@ -59,21 +59,17 @@ impl LockFile { .write(true) .open(&lock_path) .context("open or create lock.json in existing directory")?; - if lock_file.try_lock_exclusive().is_err() { - info!("Waiting for lock on {} ...", lock_path.to_slash_lossy()); - let contended_kind = fs2::lock_contended_error().kind(); - loop { - check_interrupted()?; - if let Err(err) = lock_file.try_lock_exclusive() { - if err.kind() == contended_kind { - sleep(LOCK_POLL) - } else { - return Err(err).context("wait for lock"); - } - } else { - break; - } + let mut first = true; + while let Err(err) = lock_file.try_lock_exclusive() { + if first { + info!( + "Waiting for lock on {} ...: {err}", + lock_path.to_slash_lossy() + ); + first = false; } + check_interrupted()?; + sleep(LOCK_POLL); } lock_file.set_len(0)?; lock_file diff --git a/src/process.rs b/src/process.rs index c228d870..df94433f 100644 --- a/src/process.rs +++ b/src/process.rs @@ -158,6 +158,7 @@ impl Process { #[cfg(unix)] #[allow(unknown_lints, clippy::needless_pass_by_ref_mut)] // To match Windows +#[mutants::skip] // hard to exercise the ESRCH edge case fn terminate_child_impl(child: &mut Popen) -> Result<()> { use nix::errno::Errno; use nix::sys::signal::{killpg, Signal}; diff --git a/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap b/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap deleted file mode 100644 index ab6148b5..00000000 --- a/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap +++ /dev/null @@ -1,550 +0,0 @@ ---- -source: src/visit.rs -expression: list_output ---- -src/main.rs: replace main -> Result<()> with Ok(()) -src/main.rs: replace main -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/build_dir.rs: replace BuildDir::copy_from -> Result with Ok(Default::default()) -src/build_dir.rs: replace BuildDir::copy_from -> Result with Err(::anyhow::anyhow!("mutated!")) -src/build_dir.rs: replace BuildDir::in_place -> Result with Ok(Default::default()) -src/build_dir.rs: replace BuildDir::in_place -> Result with Err(::anyhow::anyhow!("mutated!")) -src/build_dir.rs: replace BuildDir::path -> &Utf8Path with &Default::default() -src/cargo.rs: replace run_cargo -> Result with Ok(Default::default()) -src/cargo.rs: replace run_cargo -> Result with Err(::anyhow::anyhow!("mutated!")) -src/cargo.rs: replace && with || in run_cargo -src/cargo.rs: replace == with != in run_cargo -src/cargo.rs: replace == with != in run_cargo -src/cargo.rs: replace != with == in run_cargo -src/cargo.rs: replace cargo_bin -> String with String::new() -src/cargo.rs: replace cargo_bin -> String with "xyzzy".into() -src/cargo.rs: replace cargo_argv -> Vec with vec![] -src/cargo.rs: replace cargo_argv -> Vec with vec![String::new()] -src/cargo.rs: replace cargo_argv -> Vec with vec!["xyzzy".into()] -src/cargo.rs: replace == with != in cargo_argv -src/cargo.rs: replace == with != in cargo_argv -src/cargo.rs: replace rustflags -> String with String::new() -src/cargo.rs: replace rustflags -> String with "xyzzy".into() -src/cargo.rs: replace == with != in rustflags -src/config.rs: replace Config::read_file -> Result with Ok(Default::default()) -src/config.rs: replace Config::read_file -> Result with Err(::anyhow::anyhow!("mutated!")) -src/config.rs: replace Config::read_tree_config -> Result with Ok(Default::default()) -src/config.rs: replace Config::read_tree_config -> Result with Err(::anyhow::anyhow!("mutated!")) -src/copy_tree.rs: replace copy_tree -> Result with Ok(Default::default()) -src/copy_tree.rs: replace copy_tree -> Result with Err(::anyhow::anyhow!("mutated!")) -src/copy_tree.rs: replace += with -= in copy_tree -src/copy_tree.rs: replace += with *= in copy_tree -src/copy_tree.rs: replace += with -= in copy_tree -src/copy_tree.rs: replace += with *= in copy_tree -src/copy_tree.rs: replace copy_symlink -> Result<()> with Ok(()) -src/copy_tree.rs: replace copy_symlink -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/fnvalue.rs: replace return_type_replacements -> Vec with vec![] -src/fnvalue.rs: replace return_type_replacements -> Vec with vec![Default::default()] -src/fnvalue.rs: replace type_replacements -> impl Iterator with ::std::iter::empty() -src/fnvalue.rs: replace type_replacements -> impl Iterator with ::std::iter::once(Default::default()) -src/fnvalue.rs: replace path_ends_with -> bool with true -src/fnvalue.rs: replace path_ends_with -> bool with false -src/fnvalue.rs: replace == with != in path_ends_with -src/fnvalue.rs: replace match_impl_iterator -> Option<&Type> with None -src/fnvalue.rs: replace match_impl_iterator -> Option<&Type> with Some(&Default::default()) -src/fnvalue.rs: replace && with || in match_impl_iterator -src/fnvalue.rs: replace == with != in match_impl_iterator -src/fnvalue.rs: replace == with != in match_impl_iterator -src/fnvalue.rs: replace == with != in match_impl_iterator -src/fnvalue.rs: replace known_container -> Option<(&Ident, &Type)> with None -src/fnvalue.rs: replace known_container -> Option<(&Ident, &Type)> with Some((&Default::default(), &Default::default())) -src/fnvalue.rs: replace == with != in known_container -src/fnvalue.rs: replace == with != in known_container -src/fnvalue.rs: replace known_collection -> Option<(&Ident, &Type)> with None -src/fnvalue.rs: replace known_collection -> Option<(&Ident, &Type)> with Some((&Default::default(), &Default::default())) -src/fnvalue.rs: replace == with != in known_collection -src/fnvalue.rs: replace == with != in known_collection -src/fnvalue.rs: replace known_map -> Option<(&Ident, &Type, &Type)> with None -src/fnvalue.rs: replace known_map -> Option<(&Ident, &Type, &Type)> with Some((&Default::default(), &Default::default(), &Default::default())) -src/fnvalue.rs: replace == with != in known_map -src/fnvalue.rs: replace maybe_collection_or_container -> Option<(&Ident, &Type)> with None -src/fnvalue.rs: replace maybe_collection_or_container -> Option<(&Ident, &Type)> with Some((&Default::default(), &Default::default())) -src/fnvalue.rs: replace == with != in maybe_collection_or_container -src/fnvalue.rs: replace path_is_float -> bool with true -src/fnvalue.rs: replace path_is_float -> bool with false -src/fnvalue.rs: replace path_is_unsigned -> bool with true -src/fnvalue.rs: replace path_is_unsigned -> bool with false -src/fnvalue.rs: replace path_is_signed -> bool with true -src/fnvalue.rs: replace path_is_signed -> bool with false -src/fnvalue.rs: replace path_is_nonzero_signed -> bool with true -src/fnvalue.rs: replace path_is_nonzero_signed -> bool with false -src/fnvalue.rs: replace path_is_nonzero_unsigned -> bool with true -src/fnvalue.rs: replace path_is_nonzero_unsigned -> bool with false -src/fnvalue.rs: replace match_first_type_arg -> Option<&'p Type> with None -src/fnvalue.rs: replace match_first_type_arg -> Option<&'p Type> with Some(&Default::default()) -src/fnvalue.rs: replace == with != in match_first_type_arg -src/glob.rs: replace build_glob_set -> Result> with Ok(None) -src/glob.rs: replace build_glob_set -> Result> with Ok(Some(Default::default())) -src/glob.rs: replace build_glob_set -> Result> with Err(::anyhow::anyhow!("mutated!")) -src/in_diff.rs: replace diff_filter -> Result> with Ok(vec![]) -src/in_diff.rs: replace diff_filter -> Result> with Ok(vec![Default::default()]) -src/in_diff.rs: replace diff_filter -> Result> with Err(::anyhow::anyhow!("mutated!")) -src/in_diff.rs: replace == with != in diff_filter -src/in_diff.rs: replace check_diff_new_text_matches -> Result<()> with Ok(()) -src/in_diff.rs: replace check_diff_new_text_matches -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/in_diff.rs: replace - with + in check_diff_new_text_matches -src/in_diff.rs: replace - with / in check_diff_new_text_matches -src/in_diff.rs: replace != with == in check_diff_new_text_matches -src/in_diff.rs: replace strip_patch_path -> &Utf8Path with &Default::default() -src/in_diff.rs: replace affected_lines -> Vec with vec![] -src/in_diff.rs: replace affected_lines -> Vec with vec![0] -src/in_diff.rs: replace affected_lines -> Vec with vec![1] -src/in_diff.rs: replace += with -= in affected_lines -src/in_diff.rs: replace += with *= in affected_lines -src/in_diff.rs: replace < with == in affected_lines -src/in_diff.rs: replace < with > in affected_lines -src/in_diff.rs: replace += with -= in affected_lines -src/in_diff.rs: replace += with *= in affected_lines -src/in_diff.rs: replace && with || in affected_lines -src/in_diff.rs: replace > with == in affected_lines -src/in_diff.rs: replace > with < in affected_lines -src/in_diff.rs: replace < with == in affected_lines -src/in_diff.rs: replace < with > in affected_lines -src/in_diff.rs: replace - with + in affected_lines -src/in_diff.rs: replace - with / in affected_lines -src/in_diff.rs: replace - with + in affected_lines -src/in_diff.rs: replace - with / in affected_lines -src/in_diff.rs: replace partial_new_file -> Vec<(usize, &'d str)> with vec![] -src/in_diff.rs: replace partial_new_file -> Vec<(usize, &'d str)> with vec![(0, "")] -src/in_diff.rs: replace partial_new_file -> Vec<(usize, &'d str)> with vec![(0, "xyzzy")] -src/in_diff.rs: replace partial_new_file -> Vec<(usize, &'d str)> with vec![(1, "")] -src/in_diff.rs: replace partial_new_file -> Vec<(usize, &'d str)> with vec![(1, "xyzzy")] -src/in_diff.rs: replace += with -= in partial_new_file -src/in_diff.rs: replace += with *= in partial_new_file -src/interrupt.rs: replace install_handler with () -src/lab.rs: replace test_mutants -> Result with Ok(Default::default()) -src/lab.rs: replace test_mutants -> Result with Err(::anyhow::anyhow!("mutated!")) -src/lab.rs: replace - with + in test_mutants -src/lab.rs: replace - with / in test_mutants -src/lab.rs: replace == with != in test_mutants -src/lab.rs: replace == with != in test_mutants -src/lab.rs: replace test_timeout -> Duration with Default::default() -src/lab.rs: replace == with != in test_timeout -src/lab.rs: replace * with + in test_timeout -src/lab.rs: replace * with / in test_timeout -src/lab.rs: replace test_scenario -> Result with Ok(Default::default()) -src/lab.rs: replace test_scenario -> Result with Err(::anyhow::anyhow!("mutated!")) -src/lab.rs: replace || with && in test_scenario -src/lab.rs: replace && with || in test_scenario -src/lab.rs: replace == with != in test_scenario -src/list.rs: replace >::write_str -> Result<(), fmt::Error> with Ok(()) -src/list.rs: replace >::write_str -> Result<(), fmt::Error> with Err(::anyhow::anyhow!("mutated!")) -src/list.rs: replace list_mutants -> Result<()> with Ok(()) -src/list.rs: replace list_mutants -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/list.rs: replace list_files -> Result<()> with Ok(()) -src/list.rs: replace list_files -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/log_file.rs: replace LogFile::create_in -> Result with Ok(Default::default()) -src/log_file.rs: replace LogFile::create_in -> Result with Err(::anyhow::anyhow!("mutated!")) -src/log_file.rs: replace == with != in LogFile::create_in -src/log_file.rs: replace == with != in LogFile::create_in -src/log_file.rs: replace LogFile::open_append -> Result with Ok(Default::default()) -src/log_file.rs: replace LogFile::open_append -> Result with Err(::anyhow::anyhow!("mutated!")) -src/log_file.rs: replace LogFile::message with () -src/log_file.rs: replace LogFile::path -> &Utf8Path with &Default::default() -src/log_file.rs: replace clean_filename -> String with String::new() -src/log_file.rs: replace clean_filename -> String with "xyzzy".into() -src/manifest.rs: replace fix_manifest -> Result<()> with Ok(()) -src/manifest.rs: replace fix_manifest -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/manifest.rs: replace fix_manifest_toml -> Result> with Ok(None) -src/manifest.rs: replace fix_manifest_toml -> Result> with Ok(Some(Default::default())) -src/manifest.rs: replace fix_manifest_toml -> Result> with Err(::anyhow::anyhow!("mutated!")) -src/manifest.rs: replace == with != in fix_manifest_toml -src/manifest.rs: replace fix_dependency_table with () -src/manifest.rs: replace fix_cargo_config -> Result<()> with Ok(()) -src/manifest.rs: replace fix_cargo_config -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/manifest.rs: replace fix_cargo_config_toml -> Result> with Ok(None) -src/manifest.rs: replace fix_cargo_config_toml -> Result> with Ok(Some(String::new())) -src/manifest.rs: replace fix_cargo_config_toml -> Result> with Ok(Some("xyzzy".into())) -src/manifest.rs: replace fix_cargo_config_toml -> Result> with Err(::anyhow::anyhow!("mutated!")) -src/manifest.rs: replace fix_path -> Option with None -src/manifest.rs: replace fix_path -> Option with Some(String::new()) -src/manifest.rs: replace fix_path -> Option with Some("xyzzy".into()) -src/manifest.rs: replace || with && in fix_path -src/manifest.rs: replace == with != in fix_path -src/mutate.rs: replace Mutant::mutated_code -> String with String::new() -src/mutate.rs: replace Mutant::mutated_code -> String with "xyzzy".into() -src/mutate.rs: replace Mutant::describe_change -> String with String::new() -src/mutate.rs: replace Mutant::describe_change -> String with "xyzzy".into() -src/mutate.rs: replace Mutant::name -> String with String::new() -src/mutate.rs: replace Mutant::name -> String with "xyzzy".into() -src/mutate.rs: replace Mutant::styled_parts -> Vec> with vec![] -src/mutate.rs: replace Mutant::styled_parts -> Vec> with vec![StyledObject::new()] -src/mutate.rs: replace Mutant::styled_parts -> Vec> with vec![StyledObject::from_iter([String::new()])] -src/mutate.rs: replace Mutant::styled_parts -> Vec> with vec![StyledObject::new(String::new())] -src/mutate.rs: replace Mutant::styled_parts -> Vec> with vec![StyledObject::from(String::new())] -src/mutate.rs: replace Mutant::styled_parts -> Vec> with vec![StyledObject::from_iter(["xyzzy".into()])] -src/mutate.rs: replace Mutant::styled_parts -> Vec> with vec![StyledObject::new("xyzzy".into())] -src/mutate.rs: replace Mutant::styled_parts -> Vec> with vec![StyledObject::from("xyzzy".into())] -src/mutate.rs: replace Mutant::styled_parts::s -> StyledObject with StyledObject::new() -src/mutate.rs: replace Mutant::styled_parts::s -> StyledObject with StyledObject::from_iter([String::new()]) -src/mutate.rs: replace Mutant::styled_parts::s -> StyledObject with StyledObject::new(String::new()) -src/mutate.rs: replace Mutant::styled_parts::s -> StyledObject with StyledObject::from(String::new()) -src/mutate.rs: replace Mutant::styled_parts::s -> StyledObject with StyledObject::from_iter(["xyzzy".into()]) -src/mutate.rs: replace Mutant::styled_parts::s -> StyledObject with StyledObject::new("xyzzy".into()) -src/mutate.rs: replace Mutant::styled_parts::s -> StyledObject with StyledObject::from("xyzzy".into()) -src/mutate.rs: replace == with != in Mutant::styled_parts -src/mutate.rs: replace Mutant::original_text -> String with String::new() -src/mutate.rs: replace Mutant::original_text -> String with "xyzzy".into() -src/mutate.rs: replace Mutant::replacement_text -> &str with "" -src/mutate.rs: replace Mutant::replacement_text -> &str with "xyzzy" -src/mutate.rs: replace Mutant::package_name -> &str with "" -src/mutate.rs: replace Mutant::package_name -> &str with "xyzzy" -src/mutate.rs: replace Mutant::package -> &Package with &Default::default() -src/mutate.rs: replace Mutant::diff -> String with String::new() -src/mutate.rs: replace Mutant::diff -> String with "xyzzy".into() -src/mutate.rs: replace Mutant::apply -> Result with Ok(Default::default()) -src/mutate.rs: replace Mutant::apply -> Result with Err(::anyhow::anyhow!("mutated!")) -src/mutate.rs: replace Mutant::unapply -> Result<()> with Ok(()) -src/mutate.rs: replace Mutant::unapply -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/mutate.rs: replace Mutant::write_in_dir -> Result<()> with Ok(()) -src/mutate.rs: replace Mutant::write_in_dir -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/mutate.rs: replace Mutant::log_file_name_base -> String with String::new() -src/mutate.rs: replace Mutant::log_file_name_base -> String with "xyzzy".into() -src/mutate.rs: replace ::fmt -> fmt::Result with Ok(Default::default()) -src/mutate.rs: replace ::fmt -> fmt::Result with Err(::anyhow::anyhow!("mutated!")) -src/mutate.rs: replace ::serialize -> Result with Ok(Default::default()) -src/mutate.rs: replace ::serialize -> Result with Err(::anyhow::anyhow!("mutated!")) -src/mutate.rs: replace >::drop with () -src/options.rs: replace join_slices -> Vec with vec![] -src/options.rs: replace join_slices -> Vec with vec![String::new()] -src/options.rs: replace join_slices -> Vec with vec!["xyzzy".into()] -src/options.rs: replace + with - in join_slices -src/options.rs: replace + with * in join_slices -src/options.rs: replace Colors::forced_value -> Option with None -src/options.rs: replace Colors::forced_value -> Option with Some(true) -src/options.rs: replace Colors::forced_value -> Option with Some(false) -src/options.rs: replace != with == in Colors::forced_value -src/options.rs: replace != with == in Colors::forced_value -src/options.rs: replace Colors::active_stdout -> bool with true -src/options.rs: replace Colors::active_stdout -> bool with false -src/options.rs: replace or_slices -> &'c[T] with Vec::leak(Vec::new()) -src/options.rs: replace or_slices -> &'c[T] with Vec::leak(vec![Default::default()]) -src/outcome.rs: replace Phase::name -> &'static str with "" -src/outcome.rs: replace Phase::name -> &'static str with "xyzzy" -src/outcome.rs: replace ::fmt -> fmt::Result with Ok(Default::default()) -src/outcome.rs: replace ::fmt -> fmt::Result with Err(::anyhow::anyhow!("mutated!")) -src/outcome.rs: replace LabOutcome::add with () -src/outcome.rs: replace += with -= in LabOutcome::add -src/outcome.rs: replace += with *= in LabOutcome::add -src/outcome.rs: replace += with -= in LabOutcome::add -src/outcome.rs: replace += with *= in LabOutcome::add -src/outcome.rs: replace += with -= in LabOutcome::add -src/outcome.rs: replace += with *= in LabOutcome::add -src/outcome.rs: replace += with -= in LabOutcome::add -src/outcome.rs: replace += with *= in LabOutcome::add -src/outcome.rs: replace += with -= in LabOutcome::add -src/outcome.rs: replace += with *= in LabOutcome::add -src/outcome.rs: replace += with -= in LabOutcome::add -src/outcome.rs: replace += with *= in LabOutcome::add -src/outcome.rs: replace += with -= in LabOutcome::add -src/outcome.rs: replace += with *= in LabOutcome::add -src/outcome.rs: replace LabOutcome::exit_code -> i32 with 0 -src/outcome.rs: replace LabOutcome::exit_code -> i32 with 1 -src/outcome.rs: replace LabOutcome::exit_code -> i32 with -1 -src/outcome.rs: replace && with || in LabOutcome::exit_code -src/outcome.rs: replace > with == in LabOutcome::exit_code -src/outcome.rs: replace > with < in LabOutcome::exit_code -src/outcome.rs: replace > with == in LabOutcome::exit_code -src/outcome.rs: replace > with < in LabOutcome::exit_code -src/outcome.rs: replace LabOutcome::summary_string -> String with String::new() -src/outcome.rs: replace LabOutcome::summary_string -> String with "xyzzy".into() -src/outcome.rs: replace > with == in LabOutcome::summary_string -src/outcome.rs: replace > with < in LabOutcome::summary_string -src/outcome.rs: replace > with == in LabOutcome::summary_string -src/outcome.rs: replace > with < in LabOutcome::summary_string -src/outcome.rs: replace > with == in LabOutcome::summary_string -src/outcome.rs: replace > with < in LabOutcome::summary_string -src/outcome.rs: replace > with == in LabOutcome::summary_string -src/outcome.rs: replace > with < in LabOutcome::summary_string -src/outcome.rs: replace > with == in LabOutcome::summary_string -src/outcome.rs: replace > with < in LabOutcome::summary_string -src/outcome.rs: replace > with == in LabOutcome::summary_string -src/outcome.rs: replace > with < in LabOutcome::summary_string -src/outcome.rs: replace ::serialize -> Result with Ok(Default::default()) -src/outcome.rs: replace ::serialize -> Result with Err(::anyhow::anyhow!("mutated!")) -src/outcome.rs: replace ScenarioOutcome::add_phase_result with () -src/outcome.rs: replace ScenarioOutcome::get_log_content -> Result with Ok(String::new()) -src/outcome.rs: replace ScenarioOutcome::get_log_content -> Result with Ok("xyzzy".into()) -src/outcome.rs: replace ScenarioOutcome::get_log_content -> Result with Err(::anyhow::anyhow!("mutated!")) -src/outcome.rs: replace ScenarioOutcome::last_phase -> Phase with Default::default() -src/outcome.rs: replace ScenarioOutcome::last_phase_result -> ProcessStatus with Default::default() -src/outcome.rs: replace ScenarioOutcome::phase_results -> &[PhaseResult] with Vec::leak(Vec::new()) -src/outcome.rs: replace ScenarioOutcome::phase_results -> &[PhaseResult] with Vec::leak(vec![Default::default()]) -src/outcome.rs: replace ScenarioOutcome::total_phase_duration -> Duration with Default::default() -src/outcome.rs: replace == with != in ScenarioOutcome::total_phase_duration -src/outcome.rs: replace ScenarioOutcome::should_show_logs -> bool with true -src/outcome.rs: replace ScenarioOutcome::should_show_logs -> bool with false -src/outcome.rs: replace && with || in ScenarioOutcome::should_show_logs -src/outcome.rs: replace ScenarioOutcome::success -> bool with true -src/outcome.rs: replace ScenarioOutcome::success -> bool with false -src/outcome.rs: replace ScenarioOutcome::has_timeout -> bool with true -src/outcome.rs: replace ScenarioOutcome::has_timeout -> bool with false -src/outcome.rs: replace ScenarioOutcome::check_or_build_failed -> bool with true -src/outcome.rs: replace ScenarioOutcome::check_or_build_failed -> bool with false -src/outcome.rs: replace && with || in ScenarioOutcome::check_or_build_failed -src/outcome.rs: replace != with == in ScenarioOutcome::check_or_build_failed -src/outcome.rs: replace ScenarioOutcome::mutant_caught -> bool with true -src/outcome.rs: replace ScenarioOutcome::mutant_caught -> bool with false -src/outcome.rs: replace && with || in ScenarioOutcome::mutant_caught -src/outcome.rs: replace && with || in ScenarioOutcome::mutant_caught -src/outcome.rs: replace == with != in ScenarioOutcome::mutant_caught -src/outcome.rs: replace ScenarioOutcome::mutant_missed -> bool with true -src/outcome.rs: replace ScenarioOutcome::mutant_missed -> bool with false -src/outcome.rs: replace && with || in ScenarioOutcome::mutant_missed -src/outcome.rs: replace && with || in ScenarioOutcome::mutant_missed -src/outcome.rs: replace == with != in ScenarioOutcome::mutant_missed -src/outcome.rs: replace ScenarioOutcome::summary -> SummaryOutcome with Default::default() -src/outcome.rs: replace PhaseResult::is_success -> bool with true -src/outcome.rs: replace PhaseResult::is_success -> bool with false -src/outcome.rs: replace ::serialize -> Result with Ok(Default::default()) -src/outcome.rs: replace ::serialize -> Result with Err(::anyhow::anyhow!("mutated!")) -src/output.rs: replace LockFile::acquire_lock -> Result with Ok(Default::default()) -src/output.rs: replace LockFile::acquire_lock -> Result with Err(::anyhow::anyhow!("mutated!")) -src/output.rs: replace == with != in LockFile::acquire_lock -src/output.rs: replace OutputDir::create_log -> Result with Ok(Default::default()) -src/output.rs: replace OutputDir::create_log -> Result with Err(::anyhow::anyhow!("mutated!")) -src/output.rs: replace OutputDir::path -> &Utf8Path with &Default::default() -src/output.rs: replace OutputDir::write_lab_outcome -> Result<()> with Ok(()) -src/output.rs: replace OutputDir::write_lab_outcome -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/output.rs: replace OutputDir::add_scenario_outcome -> Result<()> with Ok(()) -src/output.rs: replace OutputDir::add_scenario_outcome -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/output.rs: replace OutputDir::open_debug_log -> Result with Ok(Default::default()) -src/output.rs: replace OutputDir::open_debug_log -> Result with Err(::anyhow::anyhow!("mutated!")) -src/output.rs: replace OutputDir::write_mutants_list -> Result<()> with Ok(()) -src/output.rs: replace OutputDir::write_mutants_list -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/output.rs: replace OutputDir::take_lab_outcome -> LabOutcome with Default::default() -src/path.rs: replace ascent -> isize with 0 -src/path.rs: replace ascent -> isize with 1 -src/path.rs: replace ascent -> isize with -1 -src/path.rs: replace == with != in ascent -src/path.rs: replace += with -= in ascent -src/path.rs: replace += with *= in ascent -src/path.rs: replace != with == in ascent -src/path.rs: replace -= with += in ascent -src/path.rs: replace -= with /= in ascent -src/path.rs: replace > with == in ascent -src/path.rs: replace > with < in ascent -src/path.rs: replace ::to_slash_path -> String with String::new() -src/path.rs: replace ::to_slash_path -> String with "xyzzy".into() -src/path.rs: replace || with && in ::to_slash_path -src/path.rs: replace == with != in ::to_slash_path -src/path.rs: replace == with != in ::to_slash_path -src/pretty.rs: replace ::to_pretty_string -> String with String::new() -src/pretty.rs: replace ::to_pretty_string -> String with "xyzzy".into() -src/pretty.rs: replace && with || in ::to_pretty_string -src/pretty.rs: replace || with && in ::to_pretty_string -src/pretty.rs: replace || with && in ::to_pretty_string -src/pretty.rs: replace == with != in ::to_pretty_string -src/pretty.rs: replace == with != in ::to_pretty_string -src/pretty.rs: replace || with && in ::to_pretty_string -src/pretty.rs: replace += with -= in ::to_pretty_string -src/pretty.rs: replace += with *= in ::to_pretty_string -src/process.rs: replace Process::run -> Result with Ok(Default::default()) -src/process.rs: replace Process::run -> Result with Err(::anyhow::anyhow!("mutated!")) -src/process.rs: replace Process::start -> Result with Ok(Default::default()) -src/process.rs: replace Process::start -> Result with Err(::anyhow::anyhow!("mutated!")) -src/process.rs: replace Process::terminate -> Result<()> with Ok(()) -src/process.rs: replace Process::terminate -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/process.rs: replace terminate_child_impl -> Result<()> with Ok(()) -src/process.rs: replace terminate_child_impl -> Result<()> with Err(::anyhow::anyhow!("mutated!")) -src/process.rs: replace != with == in terminate_child_impl -src/process.rs: replace ProcessStatus::is_success -> bool with true -src/process.rs: replace ProcessStatus::is_success -> bool with false -src/process.rs: replace == with != in ProcessStatus::is_success -src/process.rs: replace ProcessStatus::is_timeout -> bool with true -src/process.rs: replace ProcessStatus::is_timeout -> bool with false -src/process.rs: replace == with != in ProcessStatus::is_timeout -src/process.rs: replace ProcessStatus::is_failure -> bool with true -src/process.rs: replace ProcessStatus::is_failure -> bool with false -src/process.rs: replace setpgid_on_unix -> PopenConfig with Default::default() -src/process.rs: replace get_command_output -> Result with Ok(String::new()) -src/process.rs: replace get_command_output -> Result with Ok("xyzzy".into()) -src/process.rs: replace get_command_output -> Result with Err(::anyhow::anyhow!("mutated!")) -src/process.rs: replace cheap_shell_quote -> String with String::new() -src/process.rs: replace cheap_shell_quote -> String with "xyzzy".into() -src/scenario.rs: replace ::fmt -> fmt::Result with Ok(Default::default()) -src/scenario.rs: replace ::fmt -> fmt::Result with Err(::anyhow::anyhow!("mutated!")) -src/scenario.rs: replace Scenario::is_mutant -> bool with true -src/scenario.rs: replace Scenario::is_mutant -> bool with false -src/scenario.rs: replace Scenario::mutant -> Option<&Mutant> with None -src/scenario.rs: replace Scenario::mutant -> Option<&Mutant> with Some(&Default::default()) -src/scenario.rs: replace Scenario::log_file_name_base -> String with String::new() -src/scenario.rs: replace Scenario::log_file_name_base -> String with "xyzzy".into() -src/shard.rs: replace Shard::select -> Vec with vec![] -src/shard.rs: replace Shard::select -> Vec with vec![Default::default()] -src/shard.rs: replace == with != in Shard::select -src/shard.rs: replace % with / in Shard::select -src/shard.rs: replace % with + in Shard::select -src/shard.rs: replace ::from_str -> Result with Ok(Default::default()) -src/shard.rs: replace ::from_str -> Result with Err(::anyhow::anyhow!("mutated!")) -src/source.rs: replace SourceFile::tree_relative_slashes -> String with String::new() -src/source.rs: replace SourceFile::tree_relative_slashes -> String with "xyzzy".into() -src/source.rs: replace SourceFile::path -> &Utf8Path with &Default::default() -src/source.rs: replace SourceFile::code -> &str with "" -src/source.rs: replace SourceFile::code -> &str with "xyzzy" -src/span.rs: replace ::from -> Self with Default::default() -src/span.rs: replace + with - in ::from -src/span.rs: replace + with * in ::from -src/span.rs: replace ::fmt -> fmt::Result with Ok(Default::default()) -src/span.rs: replace ::fmt -> fmt::Result with Err(::anyhow::anyhow!("mutated!")) -src/span.rs: replace Span::quad -> Self with Default::default() -src/span.rs: replace Span::extract -> String with String::new() -src/span.rs: replace Span::extract -> String with "xyzzy".into() -src/span.rs: replace && with || in Span::extract -src/span.rs: replace || with && in Span::extract -src/span.rs: replace && with || in Span::extract -src/span.rs: replace == with != in Span::extract -src/span.rs: replace >= with < in Span::extract -src/span.rs: replace > with == in Span::extract -src/span.rs: replace > with < in Span::extract -src/span.rs: replace || with && in Span::extract -src/span.rs: replace < with == in Span::extract -src/span.rs: replace < with > in Span::extract -src/span.rs: replace && with || in Span::extract -src/span.rs: replace == with != in Span::extract -src/span.rs: replace < with == in Span::extract -src/span.rs: replace < with > in Span::extract -src/span.rs: replace == with != in Span::extract -src/span.rs: replace += with -= in Span::extract -src/span.rs: replace += with *= in Span::extract -src/span.rs: replace > with == in Span::extract -src/span.rs: replace > with < in Span::extract -src/span.rs: replace == with != in Span::extract -src/span.rs: replace += with -= in Span::extract -src/span.rs: replace += with *= in Span::extract -src/span.rs: replace && with || in Span::extract -src/span.rs: replace == with != in Span::extract -src/span.rs: replace >= with < in Span::extract -src/span.rs: replace Span::replace -> String with String::new() -src/span.rs: replace Span::replace -> String with "xyzzy".into() -src/span.rs: replace + with - in Span::replace -src/span.rs: replace + with * in Span::replace -src/span.rs: replace && with || in Span::replace -src/span.rs: replace == with != in Span::replace -src/span.rs: replace == with != in Span::replace -src/span.rs: replace || with && in Span::replace -src/span.rs: replace || with && in Span::replace -src/span.rs: replace || with && in Span::replace -src/span.rs: replace < with == in Span::replace -src/span.rs: replace < with > in Span::replace -src/span.rs: replace > with == in Span::replace -src/span.rs: replace > with < in Span::replace -src/span.rs: replace && with || in Span::replace -src/span.rs: replace == with != in Span::replace -src/span.rs: replace < with == in Span::replace -src/span.rs: replace < with > in Span::replace -src/span.rs: replace && with || in Span::replace -src/span.rs: replace == with != in Span::replace -src/span.rs: replace >= with < in Span::replace -src/span.rs: replace == with != in Span::replace -src/span.rs: replace += with -= in Span::replace -src/span.rs: replace += with *= in Span::replace -src/span.rs: replace == with != in Span::replace -src/span.rs: replace += with -= in Span::replace -src/span.rs: replace += with *= in Span::replace -src/span.rs: replace && with || in Span::replace -src/span.rs: replace == with != in Span::replace -src/span.rs: replace == with != in Span::replace -src/span.rs: replace ::from -> Self with Default::default() -src/span.rs: replace ::from -> Self with Default::default() -src/span.rs: replace ::from -> Self with Default::default() -src/span.rs: replace ::fmt -> fmt::Result with Ok(Default::default()) -src/span.rs: replace ::fmt -> fmt::Result with Err(::anyhow::anyhow!("mutated!")) -src/tail_file.rs: replace TailFile::last_line -> Result<&str> with Ok("") -src/tail_file.rs: replace TailFile::last_line -> Result<&str> with Ok("xyzzy") -src/tail_file.rs: replace TailFile::last_line -> Result<&str> with Err(::anyhow::anyhow!("mutated!")) -src/tail_file.rs: replace > with == in TailFile::last_line -src/tail_file.rs: replace > with < in TailFile::last_line -src/visit.rs: replace walk_tree -> Result with Ok(Default::default()) -src/visit.rs: replace walk_tree -> Result with Err(::anyhow::anyhow!("mutated!")) -src/visit.rs: replace && with || in walk_tree -src/visit.rs: replace || with && in walk_tree -src/visit.rs: replace || with && in walk_tree -src/visit.rs: replace walk_file -> Result<(Vec, Vec)> with Ok((vec![], vec![])) -src/visit.rs: replace walk_file -> Result<(Vec, Vec)> with Ok((vec![], vec![String::new()])) -src/visit.rs: replace walk_file -> Result<(Vec, Vec)> with Ok((vec![], vec!["xyzzy".into()])) -src/visit.rs: replace walk_file -> Result<(Vec, Vec)> with Ok((vec![Default::default()], vec![])) -src/visit.rs: replace walk_file -> Result<(Vec, Vec)> with Ok((vec![Default::default()], vec![String::new()])) -src/visit.rs: replace walk_file -> Result<(Vec, Vec)> with Ok((vec![Default::default()], vec!["xyzzy".into()])) -src/visit.rs: replace walk_file -> Result<(Vec, Vec)> with Err(::anyhow::anyhow!("mutated!")) -src/visit.rs: replace DiscoveryVisitor<'o>::enter_function -> Arc with Arc::new(Default::default()) -src/visit.rs: replace DiscoveryVisitor<'o>::leave_function with () -src/visit.rs: replace DiscoveryVisitor<'o>::collect_mutant with () -src/visit.rs: replace DiscoveryVisitor<'o>::collect_fn_mutants with () -src/visit.rs: replace == with != in DiscoveryVisitor<'o>::collect_fn_mutants -src/visit.rs: replace DiscoveryVisitor<'o>::in_namespace -> T with Default::default() -src/visit.rs: replace >::visit_item_fn with () -src/visit.rs: replace || with && in >::visit_item_fn -src/visit.rs: replace || with && in >::visit_item_fn -src/visit.rs: replace >::visit_impl_item_fn with () -src/visit.rs: replace || with && in >::visit_impl_item_fn -src/visit.rs: replace || with && in >::visit_impl_item_fn -src/visit.rs: replace || with && in >::visit_impl_item_fn -src/visit.rs: replace == with != in >::visit_impl_item_fn -src/visit.rs: replace >::visit_trait_item_fn with () -src/visit.rs: replace || with && in >::visit_trait_item_fn -src/visit.rs: replace || with && in >::visit_trait_item_fn -src/visit.rs: replace == with != in >::visit_trait_item_fn -src/visit.rs: replace >::visit_item_impl with () -src/visit.rs: replace == with != in >::visit_item_impl -src/visit.rs: replace >::visit_item_trait with () -src/visit.rs: replace >::visit_item_mod with () -src/visit.rs: replace >::visit_expr_binary with () -src/visit.rs: replace function_body_span -> Option with None -src/visit.rs: replace function_body_span -> Option with Some(Default::default()) -src/visit.rs: replace find_mod_source -> Result> with Ok(None) -src/visit.rs: replace find_mod_source -> Result> with Ok(Some(Default::default())) -src/visit.rs: replace find_mod_source -> Result> with Err(::anyhow::anyhow!("mutated!")) -src/visit.rs: replace || with && in find_mod_source -src/visit.rs: replace + with - in find_mod_source -src/visit.rs: replace + with * in find_mod_source -src/visit.rs: replace fn_sig_excluded -> bool with true -src/visit.rs: replace fn_sig_excluded -> bool with false -src/visit.rs: replace attrs_excluded -> bool with true -src/visit.rs: replace attrs_excluded -> bool with false -src/visit.rs: replace || with && in attrs_excluded -src/visit.rs: replace || with && in attrs_excluded -src/visit.rs: replace block_is_empty -> bool with true -src/visit.rs: replace block_is_empty -> bool with false -src/visit.rs: replace attr_is_cfg_test -> bool with true -src/visit.rs: replace attr_is_cfg_test -> bool with false -src/visit.rs: replace attr_is_test -> bool with true -src/visit.rs: replace attr_is_test -> bool with false -src/visit.rs: replace path_is -> bool with true -src/visit.rs: replace path_is -> bool with false -src/visit.rs: replace attr_is_mutants_skip -> bool with true -src/visit.rs: replace attr_is_mutants_skip -> bool with false -src/workspace.rs: replace PackageFilter::explicit -> PackageFilter with Default::default() -src/workspace.rs: replace PackageFilter::resolve_auto -> Result with Ok(Default::default()) -src/workspace.rs: replace PackageFilter::resolve_auto -> Result with Err(::anyhow::anyhow!("mutated!")) -src/workspace.rs: replace == with != in PackageFilter::resolve_auto -src/workspace.rs: replace Workspace::open -> Result with Ok(Default::default()) -src/workspace.rs: replace Workspace::open -> Result with Err(::anyhow::anyhow!("mutated!")) -src/workspace.rs: replace Workspace::packages -> Result>> with Ok(vec![]) -src/workspace.rs: replace Workspace::packages -> Result>> with Ok(vec![Arc::new(Default::default())]) -src/workspace.rs: replace Workspace::packages -> Result>> with Err(::anyhow::anyhow!("mutated!")) -src/workspace.rs: replace Workspace::package_tops -> Result> with Ok(vec![]) -src/workspace.rs: replace Workspace::package_tops -> Result> with Ok(vec![Default::default()]) -src/workspace.rs: replace Workspace::package_tops -> Result> with Err(::anyhow::anyhow!("mutated!")) -src/workspace.rs: replace == with != in Workspace::package_tops -src/workspace.rs: replace Workspace::top_sources -> Result> with Ok(vec![]) -src/workspace.rs: replace Workspace::top_sources -> Result> with Ok(vec![Default::default()]) -src/workspace.rs: replace Workspace::top_sources -> Result> with Err(::anyhow::anyhow!("mutated!")) -src/workspace.rs: replace Workspace::discover -> Result with Ok(Default::default()) -src/workspace.rs: replace Workspace::discover -> Result with Err(::anyhow::anyhow!("mutated!")) -src/workspace.rs: replace Workspace::mutants -> Result> with Ok(vec![]) -src/workspace.rs: replace Workspace::mutants -> Result> with Ok(vec![Default::default()]) -src/workspace.rs: replace Workspace::mutants -> Result> with Err(::anyhow::anyhow!("mutated!")) -src/workspace.rs: replace direct_package_sources -> Result> with Ok(vec![]) -src/workspace.rs: replace direct_package_sources -> Result> with Ok(vec![Default::default()]) -src/workspace.rs: replace direct_package_sources -> Result> with Err(::anyhow::anyhow!("mutated!")) -src/workspace.rs: replace should_mutate_target -> bool with true -src/workspace.rs: replace should_mutate_target -> bool with false -src/workspace.rs: replace || with && in should_mutate_target -src/workspace.rs: replace == with != in should_mutate_target -src/workspace.rs: replace locate_project -> Result with Ok(Default::default()) -src/workspace.rs: replace locate_project -> Result with Err(::anyhow::anyhow!("mutated!")) diff --git a/src/span.rs b/src/span.rs index 5dd0b817..c9616de4 100644 --- a/src/span.rs +++ b/src/span.rs @@ -101,6 +101,7 @@ impl Span { /// `replacement`. pub fn replace(&self, s: &str, replacement: &str) -> String { let mut r = String::with_capacity(s.len() + replacement.len()); + debug_assert_eq!(r.capacity(), s.len() + replacement.len()); // cheesy way to stop mutants complaining the previous line is untested, see #301 let mut line_no = 1; let mut col_no = 1; let start = self.start; diff --git a/src/visit.rs b/src/visit.rs index 55c1e5b3..73add006 100644 --- a/src/visit.rs +++ b/src/visit.rs @@ -542,13 +542,10 @@ fn attr_is_mutants_skip(attr: &Attribute) -> bool { #[cfg(test)] mod test { - use std::path::Path; - use indoc::indoc; use itertools::Itertools; use super::*; - use crate::config::Config; use crate::package::Package; /// We should not generate mutants that produce the same tokens as the @@ -576,34 +573,4 @@ mod test { ["src/lib.rs: replace always_true -> bool with false"] ); } - - /// As a generic protection against regressions in discovery, the the mutants - /// generated from `cargo-mutants` own tree against a checked-in list. - /// - /// The snapshot will need to be updated when functions are added or removed, - /// as well as when new mutation patterns are added. - /// - /// To stop it being too noisy, we use a custom format with no line numbers. - #[test] - fn expected_mutants_for_own_source_tree() { - let config = Config::read_file(Path::new("./.cargo/mutants.toml")).expect("Read config"); - let args = - Args::try_parse_from(["mutants", "--list", "--line-col=false", "--colors=never"]) - .expect("Parse args"); - let options = Options::new(&args, &config).expect("Build options"); - let mut list_output = String::new(); - let console = Console::new(); - let workspace = Workspace::open( - Utf8Path::new(".") - .canonicalize_utf8() - .expect("Canonicalize source path"), - ) - .unwrap(); - let discovered = workspace - .discover(&PackageFilter::All, &options, &console) - .expect("Discover mutants"); - crate::list_mutants(&mut list_output, &discovered.mutants, &options) - .expect("Discover mutants in own source tree"); - insta::assert_snapshot!(list_output); - } } diff --git a/testdata/everything_skipped/README.md b/testdata/everything_skipped/README.md new file mode 100644 index 00000000..0bc9840d --- /dev/null +++ b/testdata/everything_skipped/README.md @@ -0,0 +1,5 @@ +# `everything_skipped` + +This tree generates no mutants, either because things are explicitly skipped +with annotations, or because they match built-in patterns for things that cannot +or should not be mutated. diff --git a/testdata/everything_skipped/src/bin/everything-skipped.rs b/testdata/everything_skipped/src/bin/everything-skipped.rs index 6690b8f2..49e17b16 100644 --- a/testdata/everything_skipped/src/bin/everything-skipped.rs +++ b/testdata/everything_skipped/src/bin/everything-skipped.rs @@ -21,3 +21,42 @@ fn test_factorial() { println!("factorial({}) = {}", 6, factorial(6)); // This line is here so we can see it in --nocapture assert_eq!(factorial(6), 720); } + +struct Thing(u32); + +impl From for Thing { + #[mutants::skip] + fn from(x: u32) -> Self { + Thing(x) + } +} + +impl Thing { + #[mutants::skip] + fn value(&self) -> u32 { + self.0 + } + + // impl fns called "new" are implicitly skipped, because it seems unlikely that + // we can create the type without them, and it might cause infinite recursions + // between `default` and `new`. + fn new() -> Thing { + Thing(42) + } + + fn nothing() { + // Has an empty body (and therefore returns unit), so it's skipped + } +} + +trait Pet { + #[mutants::skip] + fn name(&self) -> String { + "Pixel".to_string() + } + + fn new() -> Self { + // This is skipped because it's an impl fn called "new" + unimplemented!() + } +} diff --git a/tests/build_dir.rs b/tests/build_dir.rs index 0fe75fcc..b9df1bcd 100644 --- a/tests/build_dir.rs +++ b/tests/build_dir.rs @@ -33,13 +33,14 @@ fn gitignore_can_be_turned_off() { } /// A tree containing a symlink that must exist for the tests to pass works properly. -/// -/// This runs in-place to avoid any complications from copying the testdata. #[test] -fn tree_with_symlink() { +#[cfg(unix)] +fn symlink_in_source_tree_is_copied() { + let tmp = copy_of_testdata("symlink"); + assert!(tmp.path().join("testdata").join("symlink").is_symlink()); run() .args(["mutants", "-d"]) - .arg("testdata/symlink") + .arg(tmp.path()) .assert() .success(); } diff --git a/tests/check_build.rs b/tests/check_build.rs new file mode 100644 index 00000000..6401c5d8 --- /dev/null +++ b/tests/check_build.rs @@ -0,0 +1,211 @@ +// Copyright 2024 Martin Pool + +//! Tests for `--check` + +use predicates::prelude::*; +use pretty_assertions::assert_eq; + +mod util; +use util::{copy_of_testdata, outcome_json_counts, run}; + +#[test] +fn small_well_tested_tree_check_only() { + let tmp_src_dir = copy_of_testdata("small_well_tested"); + run() + .args(["mutants", "--check", "--no-shuffle", "--no-times"]) + .current_dir(tmp_src_dir.path()) + .assert() + .success() + .stdout(predicate::function(|stdout: &str| { + insta::assert_snapshot!(stdout, @r###" + Found 4 mutants to test + ok Unmutated baseline + ok src/lib.rs:5:5: replace factorial -> u32 with 0 + ok src/lib.rs:5:5: replace factorial -> u32 with 1 + ok src/lib.rs:7:11: replace *= with += in factorial + ok src/lib.rs:7:11: replace *= with /= in factorial + 4 mutants tested: 4 succeeded + "###); + true + })); + let outcomes = outcome_json_counts(&tmp_src_dir); + assert_eq!( + outcomes, + serde_json::json!({ + "success": 4, // They did all build + "caught": 0, // They weren't actually tested + "unviable": 0, + "missed": 0, + "timeout": 0, + "total_mutants": 4, + }) + ); +} + +#[test] +fn small_well_tested_tree_check_only_shuffled() { + let tmp_src_dir = copy_of_testdata("small_well_tested"); + run() + .args(["mutants", "--check", "--no-times", "--shuffle"]) + .current_dir(tmp_src_dir.path()) + .assert() + .success() + .stdout(predicate::str::contains("4 mutants tested: 4 succeeded")); + assert_eq!( + outcome_json_counts(&tmp_src_dir), + serde_json::json!({ + "success": 4, // They did all build + "caught": 0, // They weren't actually tested + "unviable": 0, + "missed": 0, + "timeout": 0, + "total_mutants": 4, + }) + ); +} + +#[test] +fn warning_when_no_mutants_found() { + let tmp_src_dir = copy_of_testdata("everything_skipped"); + run() + .args(["mutants", "--check", "--no-times", "--no-shuffle"]) + .current_dir(tmp_src_dir.path()) + .assert() + .stderr(predicate::str::contains( + "No mutants found under the active filters", + )) + .stdout(predicate::str::contains("Found 0 mutants to test")) + .success(); // It's arguable, but better if CI doesn't fail in this case. + // There is no outcomes.json? Arguably a bug. +} + +#[test] +fn check_succeeds_in_tree_that_builds_but_fails_tests() { + // --check doesn't actually run the tests so won't discover that they fail. + let tmp_src_dir = copy_of_testdata("already_failing_tests"); + run() + .args(["mutants", "--check", "--no-times", "--no-shuffle"]) + .current_dir(tmp_src_dir.path()) + .env_remove("RUST_BACKTRACE") + .assert() + .success() + .stdout(predicate::function(|stdout: &str| { + insta::assert_snapshot!(stdout, @r###" + Found 4 mutants to test + ok Unmutated baseline + ok src/lib.rs:2:5: replace factorial -> u32 with 0 + ok src/lib.rs:2:5: replace factorial -> u32 with 1 + ok src/lib.rs:4:11: replace *= with += in factorial + ok src/lib.rs:4:11: replace *= with /= in factorial + 4 mutants tested: 4 succeeded + "###); + true + })); + assert_eq!( + outcome_json_counts(&tmp_src_dir), + serde_json::json!({ + "caught": 0, + "missed": 0, + "success": 4, + "timeout": 0, + "unviable": 0, + "total_mutants": 4, + }) + ); +} + +#[test] +fn check_tree_with_mutants_skip() { + let tmp_src_dir = copy_of_testdata("hang_avoided_by_attr"); + run() + .arg("mutants") + .args(["--check", "--no-times", "--no-shuffle"]) + .current_dir(tmp_src_dir.path()) + .env_remove("RUST_BACKTRACE") + .assert() + .success() + .stdout(predicate::function(|stdout: &str| { + insta::assert_snapshot!(stdout, @r###" + Found 5 mutants to test + ok Unmutated baseline + ok src/lib.rs:15:5: replace controlled_loop with () + ok src/lib.rs:21:28: replace > with == in controlled_loop + ok src/lib.rs:21:28: replace > with < in controlled_loop + ok src/lib.rs:21:53: replace * with + in controlled_loop + ok src/lib.rs:21:53: replace * with / in controlled_loop + 5 mutants tested: 5 succeeded + "###); + true + })); + assert_eq!( + outcome_json_counts(&tmp_src_dir), + serde_json::json!({ + "caught": 0, + "missed": 0, + "success": 5, + "timeout": 0, + "unviable": 0, + "total_mutants": 5, + }) + ); +} + +#[test] +fn check_tree_where_build_fails() { + let tmp_src_dir = copy_of_testdata("typecheck_fails"); + run() + .arg("mutants") + .args(["--check", "--no-times", "--no-shuffle"]) + .current_dir(tmp_src_dir.path()) + .env_remove("RUST_BACKTRACE") + .assert() + .code(4) // clean tests failed + .stdout(predicate::str::contains("FAILED Unmutated baseline")); + assert_eq!( + outcome_json_counts(&tmp_src_dir), + serde_json::json!({ + "caught": 0, + "missed": 0, + "success": 0, + "timeout": 0, + "unviable": 0, + "total_mutants": 0, + }) + ); +} + +#[test] +fn unviable_mutation_of_struct_with_no_default() { + let tmp_src_dir = copy_of_testdata("struct_with_no_default"); + run() + .args([ + "mutants", + "--line-col=false", + "--check", + "--no-times", + "--no-shuffle", + "-v", + "-V", + ]) + .arg("-d") + .arg(tmp_src_dir.path()) + .assert() + .success() + .stdout( + predicate::str::is_match( + r"unviable *src/lib.rs:\d+:\d+: replace make_an_s -> S with Default::default\(\)", + ) + .unwrap(), + ); + assert_eq!( + outcome_json_counts(&tmp_src_dir), + serde_json::json!({ + "success": 0, + "caught": 0, + "unviable": 1, + "missed": 0, + "timeout": 0, + "total_mutants": 1, + }) + ); +} diff --git a/tests/list.rs b/tests/list.rs index 59e3389e..63e79b0a 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -304,3 +304,16 @@ fn list_files_json_well_tested() { .current_dir("testdata/well_tested") .assert_insta("list_files_json_well_tested"); } + +#[test] +fn no_mutants_in_tree_everything_skipped() { + let tmp_src_dir = copy_of_testdata("everything_skipped"); + run() + .args(["mutants", "--list"]) + .arg("--dir") + .arg(tmp_src_dir.path()) + .assert() + .stderr(predicate::str::is_empty()) // not an error or warning + .stdout(predicate::str::is_empty()) + .success(); +} diff --git a/tests/main.rs b/tests/main.rs index 4df8c859..846b7a1c 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -14,6 +14,7 @@ use itertools::Itertools; use predicate::str::{contains, is_match}; use predicates::prelude::*; use pretty_assertions::assert_eq; + use subprocess::{Popen, PopenConfig, Redirection}; use tempfile::TempDir; @@ -77,8 +78,15 @@ fn tree_with_child_directories_is_well_tested() { .arg("mutants") .arg("-d") .arg(tmp_src_dir.path()) + .arg("-Ldebug") .assert() - .success(); + .success() + .stderr( + predicate::str::is_match( + r#"DEBUG Copied source tree total_bytes=\d{3,} total_files=1[34]"#, + ) + .unwrap(), + ); } #[test] @@ -279,19 +287,6 @@ fn integration_test_source_is_not_mutated() { .success(); check_text_list_output(tmp_src_dir.path(), "integration_test_source_is_not_mutated"); } -#[test] -fn warning_when_no_mutants_found() { - let tmp_src_dir = copy_of_testdata("everything_skipped"); - run() - .args(["mutants", "--check", "--no-times", "--no-shuffle"]) - .current_dir(tmp_src_dir.path()) - .assert() - .stderr(predicate::str::contains( - "No mutants found under the active filters", - )) - .stdout(predicate::str::contains("Found 0 mutants to test")) - .success(); // It's arguable, but better if CI doesn't fail in this case. -} #[test] fn uncaught_mutant_in_factorial() { diff --git a/tests/nextest.rs b/tests/nextest.rs index ed680116..0a0dc778 100644 --- a/tests/nextest.rs +++ b/tests/nextest.rs @@ -2,6 +2,8 @@ //! Integration tests for cargo mutants calling nextest. +use predicates::prelude::*; + mod util; use util::{copy_of_testdata, run}; @@ -13,6 +15,12 @@ fn test_with_nextest_on_small_tree() { .arg("-d") .arg(tmp_src_dir.path()) .assert() + .stderr(predicates::str::contains("WARN").not()) + .stdout( + predicates::str::contains("4 mutants tested") + .and(predicates::str::contains("Found 4 mutants to test")) + .and(predicates::str::contains("4 caught")), + ) .success(); println!( "stdout:\n{}", diff --git a/tests/util/mod.rs b/tests/util/mod.rs index 24ac2473..734f31d0 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -6,7 +6,7 @@ use std::borrow::Borrow; use std::env; -use std::fs::read_dir; +use std::fs::{read_dir, read_to_string}; use std::path::{Path, PathBuf}; use std::time::Duration; @@ -102,3 +102,17 @@ pub fn all_testdata_tree_paths() -> Vec { paths.sort(); paths } + +pub fn outcome_json(tmp_src_dir: &TempDir) -> serde_json::Value { + read_to_string(tmp_src_dir.path().join("mutants.out/outcomes.json")) + .expect("read outcomes.json") + .parse() + .expect("parse outcomes.json") +} + +pub fn outcome_json_counts(tmp_src_dir: &TempDir) -> serde_json::Value { + let mut outcomes = outcome_json(tmp_src_dir); + // We don't want to compare the detailed outcomes + outcomes.as_object_mut().unwrap().remove("outcomes"); + outcomes +}