cargo-mutants-23.10.0/.cargo/audit.toml000064400000000000000000000002111046102023000157210ustar 00000000000000[advisories] ignore = [ # Intractable Unix TZ issues; not obviously important in Conserve "RUSTSEC-2020-0071", "RUSTSEC-2020-0159" ] cargo-mutants-23.10.0/.cargo/mutants.toml000064400000000000000000000001641046102023000163150ustar 00000000000000# cargo-mutants configuration error_values = ["::anyhow::anyhow!(\"mutated\")"] exclude_globs = ["src/console.rs"] cargo-mutants-23.10.0/.cargo_vcs_info.json0000644000000001360000000000100137630ustar { "git": { "sha1": "f52f438b07e0fca5dc5b9396bfbe1de0f8a462c0" }, "path_in_vcs": "" }cargo-mutants-23.10.0/Cargo.lock0000644000001107460000000000100117470ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] [[package]] name = "anstream" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" [[package]] name = "anstyle-parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ "windows-sys 0.48.0", ] [[package]] name = "anstyle-wincon" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", "windows-sys 0.48.0", ] [[package]] name = "anyhow" version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "assert_cmd" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6" dependencies = [ "anstyle", "bstr", "doc-comment", "predicates", "predicates-core", "predicates-tree", "wait-timeout", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi 0.1.19", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "bstr" version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" dependencies = [ "memchr", "regex-automata", "serde", ] [[package]] name = "bumpalo" version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "camino" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" dependencies = [ "serde", ] [[package]] name = "cargo-mutants" version = "23.10.0" dependencies = [ "anyhow", "assert_cmd", "camino", "cargo_metadata", "clap", "clap_complete", "console", "cp_r", "ctrlc", "fastrand", "fs2", "globset", "indoc", "insta", "itertools 0.11.0", "lazy_static", "mutants", "nix", "nutmeg", "path-slash", "predicates", "pretty_assertions", "proc-macro2", "quote", "regex", "serde", "serde_json", "similar", "subprocess", "syn", "tempfile", "time", "toml", "tracing", "tracing-appender", "tracing-subscriber", "walkdir", "whoami", ] [[package]] name = "cargo-platform" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb9ac64500cc83ce4b9f8dafa78186aa008c8dea77a09b94cd307fd0cd5022a8" dependencies = [ "camino", "cargo-platform", "semver", "serde", "serde_json", "thiserror", ] [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", "terminal_size", ] [[package]] name = "clap_complete" version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4110a1e6af615a9e6d0a36f805d5c99099f8bab9b8042f5bc1fa220a4a89e36f" dependencies = [ "clap", ] [[package]] name = "clap_derive" version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "console" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", "windows-sys 0.45.0", ] [[package]] name = "cp_r" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f21d305efe161d2d3179950f5746837215197e774bf7424c12eafc191b63e88a" dependencies = [ "filetime", ] [[package]] name = "crossbeam-channel" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] [[package]] name = "ctrlc" version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" dependencies = [ "nix", "windows-sys 0.48.0", ] [[package]] name = "deranged" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "difflib" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", "windows-sys 0.48.0", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "fastrand" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "filetime" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", "redox_syscall", "windows-sys 0.48.0", ] [[package]] name = "float-cmp" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" dependencies = [ "libc", "winapi", ] [[package]] name = "globset" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" dependencies = [ "aho-corasick", "bstr", "fnv", "log", "regex", ] [[package]] name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hermit-abi" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "indexmap" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "indoc" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4" [[package]] name = "insta" version = "1.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a" dependencies = [ "console", "lazy_static", "linked-hash-map", "similar", "yaml-rust", ] [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.2", "libc", "windows-sys 0.48.0", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "lock_api" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "mutants" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc0287524726960e07b119cebd01678f852f147742ae0d925e6a520dca956126" [[package]] name = "nix" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ "bitflags 2.4.0", "cfg-if", "libc", ] [[package]] name = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", "winapi", ] [[package]] name = "num-traits" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", ] [[package]] name = "nutmeg" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "210b363fa6901c372f264fa32ef3710c0e86328901deaed31294fecfd51e848b" dependencies = [ "atty", "parking_lot", "terminal_size", "yansi", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.48.5", ] [[package]] name = "path-slash" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "predicates" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" dependencies = [ "anstyle", "difflib", "float-cmp", "itertools 0.10.5", "normalize-line-endings", "predicates-core", "regex", ] [[package]] name = "predicates-core" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" [[package]] name = "predicates-tree" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" dependencies = [ "predicates-core", "termtree", ] [[package]] name = "pretty_assertions" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ "diff", "yansi", ] [[package]] name = "proc-macro2" version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rustix" version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ "bitflags 1.3.2", "errno", "io-lifetimes", "libc", "linux-raw-sys 0.3.8", "windows-sys 0.48.0", ] [[package]] name = "rustix" version = "0.38.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" dependencies = [ "bitflags 2.4.0", "errno", "libc", "linux-raw-sys 0.4.7", "windows-sys 0.48.0", ] [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" dependencies = [ "serde", ] [[package]] name = "serde" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ "serde", ] [[package]] name = "sharded-slab" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ "lazy_static", ] [[package]] name = "similar" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" [[package]] name = "smallvec" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subprocess" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086" dependencies = [ "libc", "winapi", ] [[package]] name = "syn" version = "2.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix 0.38.13", "windows-sys 0.48.0", ] [[package]] name = "terminal_size" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ "rustix 0.37.23", "windows-sys 0.48.0", ] [[package]] name = "termtree" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thread_local" version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ "cfg-if", "once_cell", ] [[package]] name = "time" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" dependencies = [ "deranged", "itoa", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" dependencies = [ "time-core", ] [[package]] name = "toml" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c226a7bba6d859b63c92c4b4fe69c5b6b72d0cb897dbc8e6012298e6154cb56e" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ff63e60a958cefbb518ae1fd6566af80d9d4be430a33f3723dfc47d1d411d95" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "tracing" version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-appender" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" dependencies = [ "crossbeam-channel", "time", "tracing-subscriber", ] [[package]] name = "tracing-attributes" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing-core" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-log" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static", "log", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "nu-ansi-term", "sharded-slab", "smallvec", "thread_local", "tracing-core", "tracing-log", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "walkdir" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasm-bindgen" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "whoami" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" dependencies = [ "wasm-bindgen", "web-sys", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets 0.42.2", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" cargo-mutants-23.10.0/Cargo.toml0000644000000056570000000000100117760ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.70" name = "cargo-mutants" version = "23.10.0" authors = ["Martin Pool"] exclude = [ ".codespell*", ".markdownlint*", "*.md", ".devcontainer", ".gitattributes", ".gitignore", ".github", "mutants.out*", ".vscode", "book", "testdata", ] description = "Find inadequately-tested code that can be removed without any tests failing." homepage = "https://mutants.rs/" readme = "README.md" keywords = [ "testing", "mutants", "cargo", "mutation-testing", "coverage", ] categories = ["development-tools::testing"] license = "MIT" repository = "https://github.com/sourcefrog/cargo-mutants" [dependencies.anyhow] version = "1.0" [dependencies.camino] version = "1.0" [dependencies.cargo_metadata] version = "0.18" [dependencies.clap] version = "4" features = [ "deprecated", "derive", "env", "wrap_help", ] [dependencies.clap_complete] version = "4" [dependencies.console] version = "0.15" [dependencies.cp_r] version = "0.5.1" [dependencies.ctrlc] version = "3.2.1" features = ["termination"] [dependencies.fastrand] version = "2" [dependencies.fs2] version = "0.4" [dependencies.globset] version = "0.4.8" [dependencies.indoc] version = "2.0.0" [dependencies.itertools] version = "0.11" [dependencies.mutants] version = "0.0.3" [dependencies.nix] version = "0.27" [dependencies.nutmeg] version = "0.1.4" [dependencies.path-slash] version = "0.2" [dependencies.proc-macro2] version = "1.0.29" features = ["span-locations"] [dependencies.quote] version = "1.0" [dependencies.regex] version = "1.5" [dependencies.serde] version = "1" features = ["derive"] [dependencies.serde_json] version = "1" [dependencies.similar] version = "2.0" [dependencies.subprocess] version = "0.2.8" [dependencies.syn] version = "2" features = [ "full", "extra-traits", "visit", ] [dependencies.tempfile] version = "3.2" [dependencies.time] version = "0.3" [dependencies.toml] version = "0.8" [dependencies.tracing] version = "0.1" [dependencies.tracing-appender] version = "0.2" [dependencies.tracing-subscriber] version = "0.3" [dependencies.whoami] version = "1.2" [dev-dependencies.assert_cmd] version = "2.0" [dev-dependencies.insta] version = "1.12" [dev-dependencies.lazy_static] version = "1.4" [dev-dependencies.predicates] version = "3" [dev-dependencies.pretty_assertions] version = "1" [dev-dependencies.regex] version = "1.5" [dev-dependencies.walkdir] version = "2.3" cargo-mutants-23.10.0/Cargo.toml.orig000064400000000000000000000061631046102023000154500ustar 00000000000000[package] name = "cargo-mutants" version = "23.10.0" edition = "2021" authors = ["Martin Pool"] license = "MIT" description = "Find inadequately-tested code that can be removed without any tests failing." repository = "https://github.com/sourcefrog/cargo-mutants" homepage = "https://mutants.rs/" categories = ["development-tools::testing"] keywords = ["testing", "mutants", "cargo", "mutation-testing", "coverage"] rust-version = "1.70" exclude = [ ".codespell*", ".markdownlint*", "*.md", ".devcontainer", ".gitattributes", ".gitignore", ".github", "mutants.out*", ".vscode", "book", "testdata", ] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] anyhow = "1.0" camino = "1.0" cargo_metadata = "0.18" clap = { version = "4", features = [ "deprecated", "derive", "env", "wrap_help", ] } clap_complete = "4" console = "0.15" ctrlc = { version = "3.2.1", features = ["termination"] } fastrand = "2" fs2 = "0.4" globset = "0.4.8" indoc = "2.0.0" itertools = "0.11" mutants = "0.0.3" nix = "0.27" path-slash = "0.2" quote = "1.0" serde_json = "1" similar = "2.0" subprocess = "0.2.8" tempfile = "3.2" time = "0.3" toml = "0.8" tracing = "0.1" tracing-appender = "0.2" tracing-subscriber = "0.3" whoami = "1.2" [dependencies.regex] version = "1.5" [dependencies.cp_r] version = "0.5.1" [dependencies.nutmeg] version = "0.1.4" # git = "https://github.com/sourcefrog/nutmeg.git" # branch = "const-new" [dependencies.proc-macro2] features = ["span-locations"] version = "1.0.29" [dependencies.serde] version = "1" features = ["derive"] [dependencies.syn] version = "2" features = ["full", "extra-traits", "visit"] [dev-dependencies] assert_cmd = "2.0" insta = "1.12" lazy_static = "1.4" predicates = "3" pretty_assertions = "1" regex = "1.5" walkdir = "2.3" [workspace] members = ["mutants_attrs"] resolver = "2" # Exclude all testdata, so that they're more isolated from the real tree, and # so that support for testing workspaces does not try to test the whole # cargo-mutants tree. exclude = [ "testdata/tree/already_failing_tests", "testdata/tree/already_hangs", "testdata/tree/cdylib", "testdata/tree/cfg_attr_mutants_skip", "testdata/tree/cfg_attr_test_skip", "testdata/tree/dependency", "testdata/tree/error_value", "testdata/tree/everything_skipped", "testdata/tree/factorial", "testdata/tree/fails_without_feature", "testdata/tree/hang_avoided_by_attr/", "testdata/tree/hang_when_mutated", "testdata/tree/insta", "testdata/tree/integration_tests", "testdata/tree/missing_test", "testdata/tree/mut_ref", "testdata/tree/never_type", "testdata/tree/override_dependency", "testdata/tree/package-fails/", "testdata/tree/patch_dependency", "testdata/tree/relative_dependency", "testdata/tree/replace_dependency", "testdata/tree/small_well_tested", "testdata/tree/strict_warnings", "testdata/tree/struct_with_no_default", "testdata/tree/unapply", "testdata/tree/unsafe", "testdata/tree/well_tested", "testdata/tree/with_child_directories", ] cargo-mutants-23.10.0/LICENSE000064400000000000000000000020541046102023000135610ustar 00000000000000MIT License Copyright (c) 2021 Martin Pool Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cargo-mutants-23.10.0/README.md000064400000000000000000000040471046102023000140370ustar 00000000000000# cargo-mutants [![Tests](https://github.com/sourcefrog/cargo-mutants/actions/workflows/tests.yml/badge.svg?branch=main&event=push)](https://github.com/sourcefrog/cargo-mutants/actions/workflows/tests.yml?query=branch%3Amain) [![crates.io](https://img.shields.io/crates/v/cargo-mutants.svg)](https://crates.io/crates/cargo-mutants) [![libs.rs](https://img.shields.io/badge/libs.rs-cargo--mutants-blue)](https://lib.rs/crates/cargo-mutants) cargo-mutants is a mutation testing tool for Rust. It helps you improve your program's quality by finding functions whose body could be replaced without causing any tests to fail. Coverage measurements can be helpful, but they really tell you what code is _reached_ by a test, and not whether the test really _checks_ anything about the behavior of the code. Mutation tests give different information, about whether the tests really check the code's behavior. The goal of cargo-mutants is to be _easy_ to run on any Rust source tree, and to tell you something _interesting_ about areas where bugs might be lurking or the tests might be insufficient. **The main documentation is the user guide at .** ## Install ```sh cargo install --locked cargo-mutants ``` ## Quick start From within a Rust source directory, just run ```sh cargo mutants ``` ## Project status As of April 2023 this is an actively-maintained spare time project. It is very usable as it is and there is room for future improvements, especially in adding new types of mutation. I expect to make releases about every one or two months, depending on how much time and energy I have available. Constructive feedback is welcome but there is absolutely no warranty or guarantee of support. ## Further reading **The main documentation is the user guide at .** See also: - [How cargo-mutants compares to other techniques and tools](https://github.com/sourcefrog/cargo-mutants/wiki/Compared). - [Design notes](DESIGN.md) - [Contributing](CONTRIBUTING.md) - [Release notes](NEWS.md) cargo-mutants-23.10.0/src/build_dir.rs000064400000000000000000000116061046102023000156510ustar 00000000000000// Copyright 2021-2023 Martin Pool //! A temporary directory containing mutated source to run cargo builds and tests. use std::convert::TryInto; use std::fmt; use anyhow::Context; use camino::{Utf8Path, Utf8PathBuf}; use tempfile::TempDir; use tracing::{debug, error, info, trace}; use crate::manifest::fix_cargo_config; use crate::*; /// Filenames excluded from being copied with the source. const SOURCE_EXCLUDE: &[&str] = &[ ".git", ".hg", ".bzr", ".svn", "_darcs", ".pijul", "mutants.out", "mutants.out.old", "target", ]; /// A temporary directory initialized with a copy of the source, where mutations can be tested. pub struct BuildDir { /// The path of the root of the temporary directory. path: Utf8PathBuf, /// A prefix for tempdir names, based on the name of the source directory. name_base: String, /// Holds a reference to the temporary directory, so that it will be deleted when this /// object is dropped. #[allow(dead_code)] strategy: TempDirStrategy, } enum TempDirStrategy { Collect(TempDir), Leak, } impl BuildDir { /// Make a new build dir, copying from a source directory. /// /// [SOURCE_EXCLUDE] is excluded. pub fn new(source: &Utf8Path, options: &Options, console: &Console) -> Result { let name_base = format!("cargo-mutants-{}-", source.file_name().unwrap_or("")); let source_abs = source .canonicalize_utf8() .expect("canonicalize source path"); // TODO: Only exclude `target` in directories containing Cargo.toml? let temp_dir = copy_tree(source, &name_base, SOURCE_EXCLUDE, console)?; let path: Utf8PathBuf = temp_dir.path().to_owned().try_into().unwrap(); fix_manifest(&path.join("Cargo.toml"), &source_abs)?; fix_cargo_config(&path, &source_abs)?; let strategy = if options.leak_dirs { let _ = temp_dir.into_path(); info!(?path, "Build directory will be leaked for inspection"); TempDirStrategy::Leak } else { TempDirStrategy::Collect(temp_dir) }; let build_dir = BuildDir { strategy, name_base, path, }; Ok(build_dir) } pub fn path(&self) -> &Utf8Path { self.path.as_path() } /// Make a copy of this build dir, including its target directory. pub fn copy(&self, console: &Console) -> Result { let temp_dir = copy_tree(&self.path, &self.name_base, &[], console)?; Ok(BuildDir { path: temp_dir.path().to_owned().try_into().unwrap(), strategy: TempDirStrategy::Collect(temp_dir), name_base: self.name_base.clone(), }) } } impl fmt::Debug for BuildDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("BuildDir") .field("path", &self.path) .finish() } } fn copy_tree( from_path: &Utf8Path, name_base: &str, exclude: &[&str], console: &Console, ) -> Result { let temp_dir = tempfile::Builder::new() .prefix(name_base) .suffix(".tmp") .tempdir() .context("create temp dir")?; console.start_copy(); let copy_options = cp_r::CopyOptions::new() .after_entry_copied(|path, _ft, stats| { console.copy_progress(stats.file_bytes); check_interrupted().map_err(|_| cp_r::Error::new(cp_r::ErrorKind::Interrupted, path)) }) .filter(|path, _dir_entry| { let excluded = exclude.iter().any(|ex| path.ends_with(ex)); if excluded { trace!("Skip {path:?}"); } else { trace!("Copy {path:?}"); } Ok(!excluded) }); match copy_options .copy_tree(from_path, temp_dir.path()) .context("copy tree") { Ok(stats) => { debug!(files = stats.files, file_bytes = stats.file_bytes,); } Err(err) => { error!( "error copying {} to {}: {:?}", &from_path.to_slash_path(), &temp_dir.path().to_slash_lossy(), err ); return Err(err); } } console.finish_copy(); Ok(temp_dir) } #[cfg(test)] mod test { use regex::Regex; use super::*; #[test] fn build_dir_debug_form() { let options = Options::default(); let root = CargoTool::new() .find_root("testdata/tree/factorial".into()) .unwrap(); let build_dir = BuildDir::new(&root, &options, &Console::new()).unwrap(); let debug_form = format!("{build_dir:?}"); assert!( Regex::new(r#"^BuildDir \{ path: "[^"]*[/\\]cargo-mutants-factorial[^"]*" \}$"#) .unwrap() .is_match(&debug_form), "debug form is {debug_form:?}", ); } } cargo-mutants-23.10.0/src/cargo.rs000064400000000000000000000347411046102023000150140ustar 00000000000000// Copyright 2021-2023 Martin Pool //! Run Cargo as a subprocess, including timeouts and propagating signals. use std::env; use std::sync::Arc; use anyhow::{anyhow, ensure, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use itertools::Itertools; use serde_json::Value; use tracing::debug_span; #[allow(unused_imports)] use tracing::{debug, error, info, span, trace, warn, Level}; use crate::process::get_command_output; use crate::source::Package; use crate::tool::Tool; use crate::*; #[derive(Debug)] pub struct CargoTool { // environment is currently constant across all invocations. env: Vec<(String, String)>, } impl CargoTool { pub fn new() -> CargoTool { let env = vec![ ("CARGO_ENCODED_RUSTFLAGS".to_owned(), rustflags()), // The tests might use Insta , and we don't want it to write // updates to the source tree, and we *certainly* don't want it to write // updates and then let the test pass. ("INSTA_UPDATE".to_owned(), "no".to_owned()), ]; CargoTool { env } } } impl Tool for CargoTool { fn name(&self) -> &str { "cargo" } fn find_root(&self, path: &Utf8Path) -> Result { ensure!(path.is_dir(), "{path:?} is not a directory"); let cargo_bin = cargo_bin(); // needed for lifetime let argv: Vec<&str> = vec![&cargo_bin, "locate-project", "--workspace"]; let stdout = get_command_output(&argv, path) .with_context(|| format!("run cargo locate-project in {path:?}"))?; let val: Value = serde_json::from_str(&stdout).context("parse cargo locate-project output")?; let cargo_toml_path: Utf8PathBuf = val["root"] .as_str() .with_context(|| format!("cargo locate-project output has no root: {stdout:?}"))? .to_owned() .into(); debug!(?cargo_toml_path, "Found workspace root manifest"); ensure!( cargo_toml_path.is_file(), "cargo locate-project root {cargo_toml_path:?} is not a file" ); let root = cargo_toml_path .parent() .ok_or_else(|| anyhow!("cargo locate-project root {cargo_toml_path:?} has no parent"))? .to_owned(); ensure!( root.is_dir(), "apparent project root directory {root:?} is not a directory" ); Ok(root) } /// Find the root files for each relevant package in the source tree. /// /// A source tree might include multiple packages (e.g. in a Cargo workspace), /// and each package might have multiple targets (e.g. a bin and lib). Test targets /// are excluded here: we run them, but we don't mutate them. /// /// Each target has one root file, typically but not necessarily called `src/lib.rs` /// or `src/main.rs`. This function returns a list of all those files. /// /// After this, there is one more level of discovery, by walking those root files /// to find `mod` statements, and then recursively walking those files to find /// all source files. fn top_source_files(&self, source_root_path: &Utf8Path) -> Result>> { let cargo_toml_path = source_root_path.join("Cargo.toml"); debug!(?cargo_toml_path, ?source_root_path, "Find root files"); check_interrupted()?; let metadata = cargo_metadata::MetadataCommand::new() .manifest_path(&cargo_toml_path) .exec() .context("run cargo metadata")?; let mut r = Vec::new(); // cargo-metadata output is not obviously ordered so make it deterministic. for package_metadata in metadata .workspace_packages() .iter() .sorted_by_key(|p| &p.name) { check_interrupted()?; let _span = debug_span!("package", name = %package_metadata.name).entered(); let manifest_path = &package_metadata.manifest_path; debug!(%manifest_path, "walk package"); let relative_manifest_path = manifest_path .strip_prefix(source_root_path) .map_err(|_| { anyhow!( "manifest path {manifest_path:?} for package {name:?} is not within the detected source root path {source_root_path:?}", name = package_metadata.name ) })? .to_owned(); let package = Arc::new(Package { name: package_metadata.name.clone(), relative_manifest_path, }); for source_path in direct_package_sources(source_root_path, package_metadata)? { check_interrupted()?; r.push(Arc::new(SourceFile::new( source_root_path, source_path, &package, )?)); } } Ok(r) } fn compose_argv( &self, build_dir: &BuildDir, packages: Option<&[&Package]>, phase: Phase, options: &Options, ) -> Result> { Ok(cargo_argv(build_dir.path(), packages, phase, options)) } fn compose_env(&self) -> Result> { Ok(self.env.clone()) } } /// Return the name of the cargo binary. fn cargo_bin() -> String { // When run as a Cargo subcommand, which is the usual/intended case, // $CARGO tells us the right way to call back into it, so that we get // the matching toolchain etc. env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()) } /// Make up the argv for a cargo check/build/test invocation, including argv[0] as the /// cargo binary itself. // (This is split out so it's easier to test.) fn cargo_argv( build_dir: &Utf8Path, packages: Option<&[&Package]>, phase: Phase, options: &Options, ) -> Vec { let mut cargo_args = vec![cargo_bin(), phase.name().to_string()]; if phase == Phase::Check || phase == Phase::Build { cargo_args.push("--tests".to_string()); } if let Some([package]) = packages { // Use the unambiguous form for this case; it works better when the same // package occurs multiple times in the tree with different versions? cargo_args.push("--manifest-path".to_owned()); cargo_args.push(build_dir.join(&package.relative_manifest_path).to_string()); } else if let Some(packages) = packages { for package in packages.iter().map(|p| p.name.to_owned()).sorted() { cargo_args.push("--package".to_owned()); cargo_args.push(package); } } else { cargo_args.push("--workspace".to_string()); } cargo_args.extend(options.additional_cargo_args.iter().cloned()); if phase == Phase::Test { cargo_args.extend(options.additional_cargo_test_args.iter().cloned()); } cargo_args } /// Return adjusted CARGO_ENCODED_RUSTFLAGS, including any changes to cap-lints. /// /// This does not currently read config files; it's too complicated. /// /// See /// fn rustflags() -> String { let mut rustflags: Vec = if let Some(rustflags) = env::var_os("CARGO_ENCODED_RUSTFLAGS") { rustflags .to_str() .expect("CARGO_ENCODED_RUSTFLAGS is not valid UTF-8") .split(|c| c == '\x1f') .map(|s| s.to_owned()) .collect() } else if let Some(rustflags) = env::var_os("RUSTFLAGS") { rustflags .to_str() .expect("RUSTFLAGS is not valid UTF-8") .split(' ') .map(|s| s.to_owned()) .collect() } else { // TODO: We could read the config files, but working out the right target and config seems complicated // given the information available here. // TODO: All matching target..rustflags and target..rustflags config entries joined together. // TODO: build.rustflags config value. Vec::new() }; rustflags.push("--cap-lints=allow".to_owned()); // debug!("adjusted rustflags: {:?}", rustflags); rustflags.join("\x1f") } /// Find all the files that are named in the `path` of targets in a Cargo manifest that should be tested. /// /// These are the starting points for discovering source files. fn direct_package_sources( workspace_root: &Utf8Path, package_metadata: &cargo_metadata::Package, ) -> Result> { let mut found = Vec::new(); let pkg_dir = package_metadata.manifest_path.parent().unwrap(); for target in &package_metadata.targets { if should_mutate_target(target) { if let Ok(relpath) = target .src_path .strip_prefix(workspace_root) .map(ToOwned::to_owned) { debug!( "found mutation target {} of kind {:?}", relpath, target.kind ); found.push(relpath); } else { warn!("{:?} is not in {:?}", target.src_path, pkg_dir); } } else { debug!( "skipping target {:?} of kinds {:?}", target.name, target.kind ); } } found.sort(); found.dedup(); Ok(found) } fn should_mutate_target(target: &cargo_metadata::Target) -> bool { target.kind.iter().any(|k| k.ends_with("lib") || k == "bin") } #[cfg(test)] mod test { use std::ffi::OsStr; use itertools::Itertools; use pretty_assertions::assert_eq; use crate::{Options, Phase}; use super::*; #[test] fn generate_cargo_args_for_baseline_with_default_options() { let options = Options::default(); let build_dir = Utf8Path::new("/tmp/buildXYZ"); assert_eq!( cargo_argv(build_dir, None, Phase::Check, &options)[1..], ["check", "--tests", "--workspace"] ); assert_eq!( cargo_argv(build_dir, None, Phase::Build, &options)[1..], ["build", "--tests", "--workspace"] ); assert_eq!( cargo_argv(build_dir, None, Phase::Test, &options)[1..], ["test", "--workspace"] ); } #[test] fn generate_cargo_args_with_additional_cargo_test_args_and_package() { let mut options = Options::default(); let package_name = "cargo-mutants-testdata-something"; let build_dir = Utf8Path::new("/tmp/buildXYZ"); let relative_manifest_path = Utf8PathBuf::from("testdata/something/Cargo.toml"); options .additional_cargo_test_args .extend(["--lib", "--no-fail-fast"].iter().map(|s| s.to_string())); let package = Arc::new(Package { name: package_name.to_owned(), relative_manifest_path: relative_manifest_path.clone(), }); let build_manifest_path = build_dir.join(relative_manifest_path); assert_eq!( cargo_argv(build_dir, Some(&[&package]), Phase::Check, &options)[1..], [ "check", "--tests", "--manifest-path", build_manifest_path.as_str(), ] ); assert_eq!( cargo_argv(build_dir, Some(&[&package]), Phase::Build, &options)[1..], [ "build", "--tests", "--manifest-path", build_manifest_path.as_str(), ] ); assert_eq!( cargo_argv(build_dir, Some(&[&package]), Phase::Test, &options)[1..], [ "test", "--manifest-path", build_manifest_path.as_str(), "--lib", "--no-fail-fast" ] ); } #[test] fn generate_cargo_args_with_additional_cargo_args_and_test_args() { let mut options = Options::default(); let build_dir = Utf8Path::new("/tmp/buildXYZ"); options .additional_cargo_test_args .extend(["--lib", "--no-fail-fast"].iter().map(|s| s.to_string())); options .additional_cargo_args .extend(["--release".to_owned()]); assert_eq!( cargo_argv(build_dir, None, Phase::Check, &options)[1..], ["check", "--tests", "--workspace", "--release"] ); assert_eq!( cargo_argv(build_dir, None, Phase::Build, &options)[1..], ["build", "--tests", "--workspace", "--release"] ); assert_eq!( cargo_argv(build_dir, None, Phase::Test, &options)[1..], [ "test", "--workspace", "--release", "--lib", "--no-fail-fast" ] ); } #[test] fn error_opening_outside_of_crate() { CargoTool::new().find_root(Utf8Path::new("/")).unwrap_err(); } #[test] fn open_subdirectory_of_crate_opens_the_crate() { let root = CargoTool::new() .find_root(Utf8Path::new("testdata/tree/factorial/src")) .expect("open source tree from subdirectory"); assert!(root.is_dir()); assert!(root.join("Cargo.toml").is_file()); assert!(root.join("src/bin/factorial.rs").is_file()); assert_eq!(root.file_name().unwrap(), OsStr::new("factorial")); } #[test] fn find_root_from_subdirectory_of_workspace_finds_the_workspace_root() { let root = CargoTool::new() .find_root(Utf8Path::new("testdata/tree/workspace/main")) .expect("Find root from within workspace/main"); assert_eq!(root.file_name(), Some("workspace"), "Wrong root: {root:?}"); } #[test] fn find_top_source_files_from_subdirectory_of_workspace() { let tool = CargoTool::new(); let root_dir = tool .find_root(Utf8Path::new("testdata/tree/workspace/main")) .expect("Find workspace root"); let top_source_files = tool.top_source_files(&root_dir).expect("Find root files"); println!("{top_source_files:#?}"); let paths = top_source_files .iter() .map(|sf| sf.tree_relative_path.to_slash_path()) .collect_vec(); // The order here might look strange, but they're actually deterministically // sorted by the package name, not the path name. assert_eq!( paths, ["utils/src/lib.rs", "main/src/main.rs", "main2/src/main.rs"] ); } } cargo-mutants-23.10.0/src/config.rs000064400000000000000000000042051046102023000151560ustar 00000000000000// Copyright 2022-2023 Martin Pool. //! `.cargo/mutants.toml` configuration file. //! //! The config file is read after parsing command line arguments, //! and after finding the source tree, because these together //! determine its location. //! //! The config file is then merged in to the [Options]. use std::default::Default; use std::fs::read_to_string; use anyhow::Context; use camino::Utf8Path; use serde::Deserialize; use crate::Result; /// Configuration read from a config file. /// /// This is similar to [Options], and eventually merged into it, but separate because it /// can be deserialized. #[derive(Debug, Default, Clone, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct Config { /// Generate these error values from functions returning Result. pub error_values: Vec, /// Generate mutants from source files matching these globs. pub examine_globs: Vec, /// Exclude mutants from source files matching these globs. pub exclude_globs: Vec, /// Exclude mutants from source files matches these regexps. pub exclude_re: Vec, /// Examine only mutants matching these regexps. pub examine_re: Vec, /// Pass extra args to every cargo invocation. pub additional_cargo_args: Vec, /// Pass extra args to cargo test. pub additional_cargo_test_args: Vec, /// Minimum test timeout, in seconds, as a floor on the autoset value. pub minimum_test_timeout: Option, } impl Config { pub fn read_file(path: &Utf8Path) -> Result { let toml = read_to_string(path).with_context(|| format!("read config {path:?}"))?; toml::de::from_str(&toml).with_context(|| format!("parse toml from {path:?}")) } /// Read the config from a tree's `.cargo/mutants.toml`, and return a default (empty) /// Config is the file does not exist. pub fn read_tree_config(source_tree_root: &Utf8Path) -> Result { let path = source_tree_root.join(".cargo").join("mutants.toml"); if path.exists() { Config::read_file(&path) } else { Ok(Config::default()) } } } cargo-mutants-23.10.0/src/console.rs000064400000000000000000000472631046102023000153660ustar 00000000000000// Copyright 2021, 2022 Martin Pool //! Print messages and progress bars on the terminal. use std::borrow::Cow; use std::fmt::Write; use std::fs::File; use std::io; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use camino::{Utf8Path, Utf8PathBuf}; use console::{style, StyledObject}; use tracing::Level; use tracing_subscriber::fmt::MakeWriter; use tracing_subscriber::prelude::*; use crate::outcome::{LabOutcome, SummaryOutcome}; use crate::scenario::Scenario; use crate::{last_line, Mutant, Options, Phase, Result, ScenarioOutcome}; static COPY_MESSAGE: &str = "Copy source to scratch directory"; /// An interface to the console for the rest of cargo-mutants. /// /// This wraps the Nutmeg view and model. pub struct Console { /// The inner view through which progress bars and messages are drawn. view: Arc>, /// The `mutants.out/debug.log` file, if it's open yet. debug_log: Arc>>, } impl Console { pub fn new() -> Console { Console { view: Arc::new(nutmeg::View::new(LabModel::default(), nutmeg_options())), debug_log: Arc::new(Mutex::new(None)), } } pub fn walk_tree_start(&self) { self.view .update(|model| model.walk_tree = Some(WalkModel::default())); } pub fn walk_tree_update(&self, files_done: usize, mutants_found: usize) { self.view.update(|model| { *model.walk_tree.as_mut().expect("walk tree started") = WalkModel { files_done, mutants_found, } }); } pub fn walk_tree_done(&self) { self.view.update(|model| model.walk_tree = None); } /// Update that a cargo task is starting. pub fn scenario_started(&self, scenario: &Scenario, log_file: &Utf8Path) { let start = Instant::now(); let scenario_model = ScenarioModel::new(scenario, start, log_file.to_owned()); self.view.update(|model| { model.scenario_models.push(scenario_model); }); } /// Update that cargo finished. pub fn scenario_finished( &self, scenario: &Scenario, outcome: &ScenarioOutcome, options: &Options, ) { self.view.update(|model| { model.mutants_done += scenario.is_mutant() as usize; match outcome.summary() { SummaryOutcome::CaughtMutant => model.mutants_caught += 1, SummaryOutcome::MissedMutant => model.mutants_missed += 1, SummaryOutcome::Timeout => model.timeouts += 1, SummaryOutcome::Unviable => model.unviable += 1, SummaryOutcome::Success => model.successes += 1, SummaryOutcome::Failure => model.failures += 1, } model.remove_scenario(scenario); }); if (outcome.mutant_caught() && !options.print_caught) || (outcome.scenario.is_mutant() && outcome.check_or_build_failed() && !options.print_unviable) { return; } let mut s = String::with_capacity(100); write!( s, "{} ... {}", style_scenario(scenario), style_outcome(outcome) ) .unwrap(); if options.show_times { let prs: Vec = outcome .phase_results() .iter() .map(|pr| { format!( "{secs} {phase}", secs = style_secs(pr.duration), phase = style(pr.phase.to_string()).dim() ) }) .collect(); let _ = write!(s, " in {}", prs.join(" + ")); } if outcome.should_show_logs() || options.show_all_logs { s.push('\n'); s.push_str( outcome .get_log_content() .expect("read log content") .as_str(), ); } s.push('\n'); self.view.message(&s); } /// Update that a test timeout was auto-set. pub fn autoset_timeout(&self, timeout: Duration) { self.message(&format!( "Auto-set test timeout to {}\n", style_secs(timeout) )); } 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()); model.copy_model = Some(CopyModel::new()); }); } pub fn finish_copy(&self) { self.view.update(|model| { model.copy_model = None; }); } pub fn copy_progress(&self, total_bytes: u64) { self.view.update(|model| { model .copy_model .as_mut() .expect("copy in progress") .bytes_copied(total_bytes) }); } /// Update that we discovered some mutants to test. pub fn discovered_mutants(&self, mutants: &[Mutant]) { self.message(&format!( "Found {} to test\n", plural(mutants.len(), "mutant") )); let n_mutants = mutants.len(); self.view.update(|model| { model.n_mutants = n_mutants; model.lab_start_time = Some(Instant::now()); }) } /// Update that work is starting on testing a given number of mutants. pub fn start_testing_mutants(&self, _n_mutants: usize) { self.view .update(|model| model.mutants_start_time = Some(Instant::now())); } /// A new phase of this scenario started. pub fn scenario_phase_started(&self, scenario: &Scenario, phase: Phase) { self.view.update(|model| { model.find_scenario_mut(scenario).phase_started(phase); }) } pub fn scenario_phase_finished(&self, scenario: &Scenario, phase: Phase) { self.view.update(|model| { model.find_scenario_mut(scenario).phase_finished(phase); }) } pub fn lab_finished(&self, lab_outcome: &LabOutcome, start_time: Instant, options: &Options) { self.view.update(|model| { model.scenario_models.clear(); }); self.message(&format!( "{}\n", lab_outcome.summary_string(start_time, options) )); } pub fn clear(&self) { self.view.clear() } pub fn message(&self, message: &str) { self.view.message(message) } pub fn tick(&self) { self.view.update(|_| ()) } /// Return a tracing `MakeWriter` that will send messages via nutmeg to the console. pub fn make_terminal_writer(&self) -> TerminalWriter { TerminalWriter { view: Arc::clone(&self.view), } } /// Return a tracing `MakeWriter` that will send messages to the debug log file if /// it's open. pub fn make_debug_log_writer(&self) -> DebugLogWriter { DebugLogWriter(Arc::clone(&self.debug_log)) } /// Set the debug log file. pub fn set_debug_log(&self, file: File) { *self.debug_log.lock().unwrap() = Some(file); } /// Configure tracing to send messages to the console and debug log. /// /// The debug log is opened later and provided by [Console::set_debug_log]. pub fn setup_global_trace(&self, console_trace_level: Level) -> Result<()> { // Show time relative to the start of the program. let uptime = tracing_subscriber::fmt::time::uptime(); let debug_log_layer = tracing_subscriber::fmt::layer() .with_ansi(false) .with_file(true) // source file name .with_line_number(true) .with_timer(uptime) .with_writer(self.make_debug_log_writer()); let level_filter = tracing_subscriber::filter::LevelFilter::from_level(console_trace_level); let console_layer = tracing_subscriber::fmt::layer() .with_ansi(true) .with_writer(self.make_terminal_writer()) .with_target(false) .with_timer(uptime) .with_filter(level_filter); tracing_subscriber::registry() .with(debug_log_layer) .with(console_layer) .init(); Ok(()) } } /// Write trace output to the terminal via the console. pub struct TerminalWriter { view: Arc>, } impl<'w> MakeWriter<'w> for TerminalWriter { type Writer = Self; fn make_writer(&self) -> Self::Writer { TerminalWriter { view: Arc::clone(&self.view), } } } impl std::io::Write for TerminalWriter { fn write(&mut self, buf: &[u8]) -> std::io::Result { // This calls `message` rather than `View::write` because the latter // only requires a &View and it handles locking internally, without // requiring exclusive use of the Arc. self.view.message(std::str::from_utf8(buf).unwrap()); Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } /// Write trace output to the debug log file if it's open. pub struct DebugLogWriter(Arc>>); impl<'w> MakeWriter<'w> for DebugLogWriter { type Writer = Self; fn make_writer(&self) -> Self::Writer { DebugLogWriter(self.0.clone()) } } impl io::Write for DebugLogWriter { fn write(&mut self, buf: &[u8]) -> io::Result { if let Some(file) = self.0.lock().unwrap().as_mut() { file.write(buf) } else { Ok(buf.len()) } } fn flush(&mut self) -> io::Result<()> { if let Some(file) = self.0.lock().unwrap().as_mut() { file.flush() } else { Ok(()) } } } /// Description of all current activities in the lab. /// /// At the moment there is either a copy, cargo runs, or nothing. Later, there /// might be concurrent activities. #[derive(Default)] struct LabModel { walk_tree: Option, copy_model: Option, scenario_models: Vec, lab_start_time: Option, // The instant when we started trying mutation scenarios, after running the baseline. mutants_start_time: Option, mutants_done: usize, n_mutants: usize, mutants_caught: usize, mutants_missed: usize, unviable: usize, timeouts: usize, successes: usize, failures: usize, } impl nutmeg::Model for LabModel { fn render(&mut self, width: usize) -> String { let mut s = String::with_capacity(1024); if let Some(walk_tree) = &mut self.walk_tree { s += &walk_tree.render(width); } if let Some(copy) = self.copy_model.as_mut() { s.push_str(©.render(width)); } if !s.is_empty() { s.push('\n') } if let Some(lab_start_time) = self.lab_start_time { let elapsed = lab_start_time.elapsed(); let percent = if self.n_mutants > 0 { ((self.mutants_done as f64) / (self.n_mutants as f64) * 100.0).round() } else { 0.0 }; write!( s, "{}/{} mutants tested, {}% done", style(self.mutants_done).cyan(), style(self.n_mutants).cyan(), style(percent).cyan(), ) .unwrap(); if self.mutants_missed > 0 { write!( s, ", {} {}", style(self.mutants_missed).cyan(), style("missed").red() ) .unwrap(); } if self.timeouts > 0 { write!( s, ", {} {}", style(self.timeouts).cyan(), style("timeout").red() ) .unwrap(); } if self.mutants_caught > 0 { write!(s, ", {} caught", style(self.mutants_caught).cyan()).unwrap(); } if self.unviable > 0 { write!(s, ", {} unviable", style(self.unviable).cyan()).unwrap(); } // Maybe don't report these, because they're uninteresting? // if self.successes > 0 { // write!(s, ", {} successes", self.successes).unwrap(); // } // if self.failures > 0 { // write!(s, ", {} failures", self.failures).unwrap(); // } write!(s, ", {} elapsed", style_minutes_seconds(elapsed)).unwrap(); if self.mutants_done > 2 { write!( s, ", about {} remaining", style(nutmeg::estimate_remaining( &self.mutants_start_time.unwrap(), self.mutants_done, self.n_mutants )) .cyan() ) .unwrap(); } writeln!(s).unwrap(); } for sm in self.scenario_models.iter_mut() { s.push_str(&sm.render(width)); s.push('\n'); } while s.ends_with('\n') { s.pop(); } s } } impl LabModel { fn find_scenario_mut(&mut self, scenario: &Scenario) -> &mut ScenarioModel { self.scenario_models .iter_mut() .find(|sm| sm.scenario == *scenario) .expect("scenario is in progress") } fn remove_scenario(&mut self, scenario: &Scenario) { self.scenario_models.retain(|sm| sm.scenario != *scenario); } } /// A Nutmeg progress model for walking the tree. #[derive(Default)] struct WalkModel { files_done: usize, mutants_found: usize, } impl nutmeg::Model for WalkModel { fn render(&mut self, _width: usize) -> String { if self.files_done == 0 { "Scanning tree metadata...\n".to_owned() } else { format!( "Finding mutation opportunities: {} files done, {} mutants found\n", self.files_done, self.mutants_found ) } } } /// A Nutmeg progress model for running a single scenario. /// /// It draws the command and some description of what scenario is being tested. struct ScenarioModel { scenario: Scenario, name: Cow<'static, str>, phase_start: Instant, phase: Option, /// Previously-executed phases and durations. previous_phase_durations: Vec<(Phase, Duration)>, log_file: Utf8PathBuf, } impl ScenarioModel { fn new(scenario: &Scenario, start: Instant, log_file: Utf8PathBuf) -> ScenarioModel { ScenarioModel { scenario: scenario.clone(), name: style_scenario(scenario), phase: None, phase_start: start, log_file, previous_phase_durations: Vec::new(), } } fn phase_started(&mut self, phase: Phase) { self.phase = Some(phase); self.phase_start = Instant::now(); } fn phase_finished(&mut self, phase: Phase) { debug_assert_eq!(self.phase, Some(phase)); self.previous_phase_durations .push((phase, self.phase_start.elapsed())); self.phase = None; } } impl nutmeg::Model for ScenarioModel { fn render(&mut self, _width: usize) -> String { let mut s = String::with_capacity(100); write!(s, "{} ... ", self.name).unwrap(); let mut prs = self .previous_phase_durations .iter() .map(|(phase, duration)| format!("{} {}", style_secs(*duration), style(phase).dim())) .collect::>(); if let Some(phase) = self.phase { prs.push(format!( "{} {}", style_secs(self.phase_start.elapsed()), style(phase).dim() )); } write!(s, "{}", prs.join(" + ")).unwrap(); if let Ok(last_line) = last_line(&self.log_file) { write!(s, "\n {}", style(last_line).dim()).unwrap(); } s } } /// A Nutmeg model for progress in copying a tree. struct CopyModel { bytes_copied: u64, start: Instant, } impl CopyModel { #[allow(dead_code)] fn new() -> CopyModel { CopyModel { start: Instant::now(), bytes_copied: 0, } } /// Update that some bytes have been copied. /// /// `bytes_copied` is the total bytes copied so far. #[allow(dead_code)] fn bytes_copied(&mut self, bytes_copied: u64) { self.bytes_copied = bytes_copied } } impl nutmeg::Model for CopyModel { fn render(&mut self, _width: usize) -> String { format!( "{} ... {} in {}", COPY_MESSAGE, style_mb(self.bytes_copied), style_elapsed_secs(self.start), ) } } fn nutmeg_options() -> nutmeg::Options { nutmeg::Options::default().print_holdoff(Duration::from_millis(50)) } /// Return a styled string reflecting the moral value of this outcome. pub fn style_outcome(outcome: &ScenarioOutcome) -> StyledObject<&'static str> { match outcome.summary() { SummaryOutcome::CaughtMutant => style("caught").green(), SummaryOutcome::MissedMutant => style("NOT CAUGHT").red().bold(), SummaryOutcome::Failure => style("FAILED").red().bold(), SummaryOutcome::Success => style("ok").green(), SummaryOutcome::Unviable => style("unviable").blue(), SummaryOutcome::Timeout => style("TIMEOUT").red().bold(), } } pub(crate) fn style_mutant(mutant: &Mutant) -> String { // This is like `impl Display for Mutant`, but with colors. // The text content should be the same. let mut s = String::with_capacity(200); write!( &mut s, "{}:{}", mutant.source_file.tree_relative_slashes(), mutant.span.start.line, ) .unwrap(); s.push_str(": replace "); s.push_str(&style(mutant.function_name()).bright().magenta().to_string()); if !mutant.return_type().is_empty() { s.push(' '); s.push_str(&style(mutant.return_type()).magenta().to_string()); } s.push_str(" with "); s.push_str(&style(mutant.replacement_text()).yellow().to_string()); s } fn style_elapsed_secs(since: Instant) -> String { style_secs(since.elapsed()) } fn style_secs(duration: Duration) -> String { style(format!("{:.1}s", duration.as_secs_f32())) .cyan() .to_string() } fn style_minutes_seconds(duration: Duration) -> String { style(duration_minutes_seconds(duration)).cyan().to_string() } pub fn duration_minutes_seconds(duration: Duration) -> String { let secs = duration.as_secs(); format!("{}:{:02}", secs / 60, secs % 60) } fn format_mb(bytes: u64) -> String { format!("{} MB", bytes / 1_000_000) } fn style_mb(bytes: u64) -> StyledObject { style(format_mb(bytes)).cyan() } pub fn style_scenario(scenario: &Scenario) -> Cow<'static, str> { match scenario { Scenario::Baseline => "Unmutated baseline".into(), Scenario::Mutant(mutant) => style_mutant(mutant).into(), } } pub fn plural(n: usize, noun: &str) -> String { if n == 1 { format!("{n} {noun}") } else { format!("{n} {noun}s") } } #[cfg(test)] mod test { use super::*; use std::time::Duration; #[test] fn test_duration_minutes_seconds() { assert_eq!(duration_minutes_seconds(Duration::ZERO), "0:00"); assert_eq!(duration_minutes_seconds(Duration::from_secs(3)), "0:03"); assert_eq!(duration_minutes_seconds(Duration::from_secs(73)), "1:13"); assert_eq!( duration_minutes_seconds(Duration::from_secs(6003)), "100:03" ); } } cargo-mutants-23.10.0/src/exit_code.rs000064400000000000000000000014771046102023000156640ustar 00000000000000// Copyright 2021 Martin Pool //! Exit codes from cargo-mutants. //! //! These are assigned so that different cases that CI or other automation (or //! cargo-mutants' own test suite) might want to distinguish are distinct. //! //! These are also described in README.md. // TODO: Maybe merge this with outcome::Status? /// Everything worked and all the mutants were caught. pub const SUCCESS: i32 = 0; /// The wrong arguments, etc. /// /// (1 is also the value returned by Clap.) pub const USAGE: i32 = 1; /// Found one or mutants that were not caught by tests. pub const FOUND_PROBLEMS: i32 = 2; /// One or more tests timed out: probably the mutant caused an infinite loop, or the timeout is too low. pub const TIMEOUT: i32 = 3; /// The tests are already failing in an unmutated tree. pub const CLEAN_TESTS_FAILED: i32 = 4; cargo-mutants-23.10.0/src/fnvalue.rs000064400000000000000000000542741046102023000153640ustar 00000000000000// Copyright 2021-2023 Martin Pool //! Mutations of replacing a function body with a value of a (hopefully) appropriate type. use std::iter; use itertools::Itertools; use proc_macro2::TokenStream; use quote::quote; use syn::{ AngleBracketedGenericArguments, AssocType, Expr, GenericArgument, Ident, Path, PathArguments, ReturnType, TraitBound, Type, TypeArray, TypeImplTrait, TypeParamBound, TypeSlice, TypeTuple, }; use tracing::trace; /// Generate replacement text for a function based on its return type. pub(crate) fn return_type_replacements( return_type: &ReturnType, error_exprs: &[Expr], ) -> impl Iterator { match return_type { ReturnType::Default => vec![quote! { () }], ReturnType::Type(_rarrow, type_) => type_replacements(type_, error_exprs).collect_vec(), } .into_iter() } /// Generate some values that we hope are reasonable replacements for a type. /// /// This is really the heart of cargo-mutants. fn type_replacements(type_: &Type, error_exprs: &[Expr]) -> impl Iterator { // This could probably change to run from some configuration rather than // hardcoding various types, which would make it easier to support tree-specific // mutation values, and perhaps reduce duplication. However, it seems better // to support all the core cases with direct code first to learn what generalizations // are needed. match type_ { Type::Path(syn::TypePath { path, .. }) => { // dbg!(&path); if path.is_ident("bool") { vec![quote! { true }, quote! { false }] } else if path.is_ident("String") { vec![quote! { String::new() }, quote! { "xyzzy".into() }] } else if path.is_ident("str") { vec![quote! { "" }, quote! { "xyzzy" }] } else if path_is_unsigned(path) { vec![quote! { 0 }, quote! { 1 }] } else if path_is_signed(path) { vec![quote! { 0 }, quote! { 1 }, quote! { -1 }] } else if path_is_nonzero_signed(path) { vec![quote! { 1 }, quote! { -1 }] } else if path_is_nonzero_unsigned(path) { vec![quote! { 1 }] } else if path_is_float(path) { vec![quote! { 0.0 }, quote! { 1.0 }, quote! { -1.0 }] } else if path_ends_with(path, "Result") { if let Some(ok_type) = match_first_type_arg(path, "Result") { type_replacements(ok_type, error_exprs) .map(|rep| { quote! { Ok(#rep) } }) .collect_vec() } else { // A result with no type arguments, like `fmt::Result`; hopefully // the Ok value can be constructed with Default. vec![quote! { Ok(Default::default()) }] } .into_iter() .chain(error_exprs.iter().map(|error_expr| { quote! { Err(#error_expr) } })) .collect_vec() } else if path_ends_with(path, "HttpResponse") { vec![quote! { HttpResponse::Ok().finish() }] } else if let Some(some_type) = match_first_type_arg(path, "Option") { iter::once(quote! { None }) .chain(type_replacements(some_type, error_exprs).map(|rep| { quote! { Some(#rep) } })) .collect_vec() } else if let Some(element_type) = match_first_type_arg(path, "Vec") { // Generate an empty Vec, and then a one-element vec for every recursive // value. iter::once(quote! { vec![] }) .chain(type_replacements(element_type, error_exprs).map(|rep| { quote! { vec![#rep] } })) .collect_vec() } else if let Some(borrowed_type) = match_first_type_arg(path, "Cow") { // TODO: We could specialize Cows for cases like Vec and Box where // we would have to leak to make the reference; perhaps it would only // look better... type_replacements(borrowed_type, error_exprs) .flat_map(|rep| { [ quote! { Cow::Borrowed(#rep) }, quote! { Cow::Owned(#rep.to_owned()) }, ] }) .collect_vec() } else if let Some((container_type, inner_type)) = known_container(path) { // Something like Arc, Mutex, etc. // TODO: Ideally we should use the path without relying on it being // imported, but we must strip or rewrite the arguments, so that // `std::sync::Arc` becomes either `std::sync::Arc::::new` // or at least `std::sync::Arc::new`. Similarly for other types. type_replacements(inner_type, error_exprs) .map(|rep| { quote! { #container_type::new(#rep) } }) .collect_vec() } else if let Some((collection_type, inner_type)) = known_collection(path) { iter::once(quote! { #collection_type::new() }) .chain(type_replacements(inner_type, error_exprs).map(|rep| { quote! { #collection_type::from_iter([#rep]) } })) .collect_vec() } else if let Some((collection_type, inner_type)) = maybe_collection_or_container(path) { // Something like `T` or `T<'a, A>`, when we don't know exactly how // to call it, but we strongly suspect that you could construct it from // an `A`. iter::once(quote! { #collection_type::new() }) .chain(type_replacements(inner_type, error_exprs).flat_map(|rep| { [ quote! { #collection_type::from_iter([#rep]) }, quote! { #collection_type::new(#rep) }, quote! { #collection_type::from(#rep) }, ] })) .collect_vec() } else { trace!(?type_, "Return type is not recognized, trying Default"); vec![quote! { Default::default() }] } } Type::Array(TypeArray { elem, len, .. }) => // Generate arrays that repeat each replacement value however many times. // In principle we could generate combinations, but that might get very // large, and values like "all zeros" and "all ones" seem likely to catch // lots of things. { type_replacements(elem, error_exprs) .map(|r| quote! { [ #r; #len ] }) .collect_vec() } Type::Slice(TypeSlice { elem, .. }) => iter::once(quote! { Vec::leak(Vec::new()) }) .chain(type_replacements(elem, error_exprs).map(|r| quote! { Vec::leak(vec![ #r ]) })) .collect_vec(), Type::Reference(syn::TypeReference { mutability: None, elem, .. }) => match &**elem { // You can't currently match box patterns in Rust Type::Path(path) if path.path.is_ident("str") => { vec![quote! { "" }, quote! { "xyzzy" }] } Type::Slice(TypeSlice { elem, .. }) => iter::once(quote! { Vec::leak(Vec::new()) }) .chain( type_replacements(elem, error_exprs).map(|r| quote! { Vec::leak(vec![ #r ]) }), ) .collect_vec(), _ => type_replacements(elem, error_exprs) .map(|rep| { quote! { &#rep } }) .collect_vec(), }, Type::Reference(syn::TypeReference { mutability: Some(_), elem, .. }) => match &**elem { Type::Slice(TypeSlice { elem, .. }) => iter::once(quote! { Vec::leak(Vec::new()) }) .chain( type_replacements(elem, error_exprs).map(|r| quote! { Vec::leak(vec![ #r ]) }), ) .collect_vec(), _ => { // Make &mut with static lifetime by leaking them on the heap. type_replacements(elem, error_exprs) .map(|rep| { quote! { Box::leak(Box::new(#rep)) } }) .collect_vec() } }, Type::Tuple(TypeTuple { elems, .. }) if elems.is_empty() => { vec![quote! { () }] } Type::Tuple(TypeTuple { elems, .. }) => { // Generate the cartesian product of replacements of every type within the tuple. elems .iter() .map(|elem| type_replacements(elem, error_exprs).collect_vec()) .multi_cartesian_product() .map(|reps| { quote! { ( #( #reps ),* ) } }) .collect_vec() } // -> impl Iterator Type::ImplTrait(impl_trait) => { if let Some(item_type) = match_impl_iterator(impl_trait) { iter::once(quote! { ::std::iter::empty() }) .chain( type_replacements(item_type, error_exprs) .map(|r| quote! { ::std::iter::once(#r) }), ) .collect_vec() } else { // TODO: Can we do anything with other impl traits? vec![] } } Type::Never(_) => { vec![] } _ => { trace!(?type_, "Return type is not recognized, trying Default"); vec![quote! { Default::default() }] } } .into_iter() } fn path_ends_with(path: &Path, ident: &str) -> bool { path.segments.last().map_or(false, |s| s.ident == ident) } 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 ident == "Item" { return Some(ty); } } } } } } None } /// If the type has a single type argument then, perhaps it's a simple container /// like Box, Cell, Mutex, etc, that can be constructed with `T::new(inner_val)`. /// /// If so, return the short name (like "Box") and the inner type. fn known_container(path: &Path) -> Option<(&Ident, &Type)> { let last = path.segments.last()?; if ["Box", "Cell", "RefCell", "Arc", "Rc", "Mutex"] .iter() .any(|v| last.ident == v) { if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = &last.arguments { // TODO: Skip lifetime args. // TODO: Return the path with args stripped out. if args.len() == 1 { if let Some(GenericArgument::Type(inner_type)) = args.first() { return Some((&last.ident, inner_type)); } } } } None } /// Match known simple collections that can be empty or constructed from an /// iterator. fn known_collection(path: &Path) -> Option<(&Ident, &Type)> { let last = path.segments.last()?; if ![ "BinaryHeap", "BTreeSet", "HashSet", "LinkedList", "VecDeque", ] .iter() .any(|v| last.ident == v) { return None; } if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = &last.arguments { // TODO: Skip lifetime args. // TODO: Return the path with args stripped out. if args.len() == 1 { if let Some(GenericArgument::Type(inner_type)) = args.first() { return Some((&last.ident, inner_type)); } } } None } /// Match a type with one type argument, which might be a container or collection. fn maybe_collection_or_container(path: &Path) -> Option<(&Ident, &Type)> { let last = path.segments.last()?; if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = &last.arguments { let type_args: Vec<_> = args .iter() .filter_map(|a| match a { GenericArgument::Type(t) => Some(t), _ => None, }) .collect(); // TODO: Return the path with args stripped out. if type_args.len() == 1 { return Some((&last.ident, type_args.first().unwrap())); } } None } fn path_is_float(path: &Path) -> bool { ["f32", "f64"].iter().any(|s| path.is_ident(s)) } fn path_is_unsigned(path: &Path) -> bool { ["u8", "u16", "u32", "u64", "u128", "usize"] .iter() .any(|s| path.is_ident(s)) } fn path_is_signed(path: &Path) -> bool { ["i8", "i16", "i32", "i64", "i128", "isize"] .iter() .any(|s| path.is_ident(s)) } fn path_is_nonzero_signed(path: &Path) -> bool { if let Some(l) = path.segments.last().map(|p| p.ident.to_string()) { matches!( l.as_str(), "NonZeroIsize" | "NonZeroI8" | "NonZeroI16" | "NonZeroI32" | "NonZeroI64" | "NonZeroI128", ) } else { false } } fn path_is_nonzero_unsigned(path: &Path) -> bool { if let Some(l) = path.segments.last().map(|p| p.ident.to_string()) { matches!( l.as_str(), "NonZeroUsize" | "NonZeroU8" | "NonZeroU16" | "NonZeroU32" | "NonZeroU64" | "NonZeroU128", ) } else { false } } /// If this is a path ending in `expected_ident`, return the first type argument, ignoring /// lifetimes. fn match_first_type_arg<'p>(path: &'p Path, expected_ident: &str) -> Option<&'p Type> { // TODO: Maybe match only things witn one arg? let last = path.segments.last()?; if last.ident == expected_ident { if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = &last.arguments { for arg in args { match arg { GenericArgument::Type(arg_type) => return Some(arg_type), GenericArgument::Lifetime(_) => (), _ => return None, } } } } None } #[cfg(test)] mod test { use itertools::Itertools; use pretty_assertions::assert_eq; use syn::{parse_quote, Expr, ReturnType}; use crate::pretty::ToPrettyString; use super::return_type_replacements; #[test] fn recurse_into_result_bool() { check_replacements( parse_quote! {-> std::result::Result }, &[], &["Ok(true)", "Ok(false)"], ); } #[test] fn recurse_into_result_result_bool_with_error_values() { check_replacements( parse_quote! {-> std::result::Result> }, &[parse_quote! { anyhow!("mutated") }], &[ "Ok(Ok(true))", "Ok(Ok(false))", r#"Ok(Err(anyhow!("mutated")))"#, r#"Err(anyhow!("mutated"))"#, ], ); } #[test] fn u16_replacements() { check_replacements(parse_quote! { -> u16 }, &[], &["0", "1"]); } #[test] fn isize_replacements() { check_replacements(parse_quote! { -> isize }, &[], &["0", "1", "-1"]); } #[test] fn nonzero_integer_replacements() { check_replacements( parse_quote! { -> std::num::NonZeroIsize }, &[], &["1", "-1"], ); check_replacements(parse_quote! { -> std::num::NonZeroUsize }, &[], &["1"]); check_replacements(parse_quote! { -> std::num::NonZeroU32 }, &[], &["1"]); } #[test] fn unit_replacement() { check_replacements(parse_quote! { -> () }, &[], &["()"]); } #[test] fn result_unit_replacement() { check_replacements(parse_quote! { -> Result<(), Error> }, &[], &["Ok(())"]); check_replacements(parse_quote! { -> Result<()> }, &[], &["Ok(())"]); } #[test] fn http_response_replacement() { check_replacements( parse_quote! { -> HttpResponse }, &[], &["HttpResponse::Ok().finish()"], ); } #[test] fn option_usize_replacement() { check_replacements( parse_quote! { -> Option }, &[], &["None", "Some(0)", "Some(1)"], ); } #[test] fn box_usize_replacement() { check_replacements( parse_quote! { -> Box }, &[], &["Box::new(0)", "Box::new(1)"], ); } #[test] fn box_unrecognized_type_replacement() { check_replacements( parse_quote! { -> Box }, &[], &["Box::new(Default::default())"], ); } #[test] fn vec_string_replacement() { check_replacements( parse_quote! { -> std::vec::Vec }, &[], &["vec![]", "vec![String::new()]", r#"vec!["xyzzy".into()]"#], ); } #[test] fn float_replacement() { check_replacements(parse_quote! { -> f32 }, &[], &["0.0", "1.0", "-1.0"]); } #[test] fn ref_replacement_recurses() { check_replacements(parse_quote! { -> &bool }, &[], &["&true", "&false"]); } #[test] fn array_replacement() { check_replacements( parse_quote! { -> [u8; 256] }, &[], &["[0; 256]", "[1; 256]"], ); } #[test] fn arc_replacement() { // Also checks that it matches the path, even using an atypical path. // TODO: Ideally this would be fully qualified like `alloc::sync::Arc::new(String::new())`. check_replacements( parse_quote! { -> alloc::sync::Arc }, &[], &["Arc::new(String::new())", r#"Arc::new("xyzzy".into())"#], ); } #[test] fn rc_replacement() { // Also checks that it matches the path, even using an atypical path. // TODO: Ideally this would be fully qualified like `alloc::sync::Rc::new(String::new())`. check_replacements( parse_quote! { -> alloc::sync::Rc }, &[], &["Rc::new(String::new())", r#"Rc::new("xyzzy".into())"#], ); } #[test] fn btreeset_replacement() { check_replacements( parse_quote! { -> std::collections::BTreeSet }, &[], &[ "BTreeSet::new()", "BTreeSet::from_iter([String::new()])", r#"BTreeSet::from_iter(["xyzzy".into()])"#, ], ); } #[test] fn cow_generates_borrowed_and_owned() { check_replacements( parse_quote! { -> Cow<'static, str> }, &[], &[ r#"Cow::Borrowed("")"#, r#"Cow::Owned("".to_owned())"#, r#"Cow::Borrowed("xyzzy")"#, r#"Cow::Owned("xyzzy".to_owned())"#, ], ); } #[test] fn unknown_container_replacement() { // This looks like something that holds a &str, and maybe can be constructed // from a &str, but we don't know anythig else about it, so we just guess. check_replacements( parse_quote! { -> UnknownContainer<'static, str> }, &[], &[ "UnknownContainer::new()", r#"UnknownContainer::from_iter([""])"#, r#"UnknownContainer::new("")"#, r#"UnknownContainer::from("")"#, r#"UnknownContainer::from_iter(["xyzzy"])"#, r#"UnknownContainer::new("xyzzy")"#, r#"UnknownContainer::from("xyzzy")"#, ], ); } #[test] fn tuple_combinations() { check_replacements( parse_quote! { -> (bool, usize) }, &[], &["(true, 0)", "(true, 1)", "(false, 0)", "(false, 1)"], ) } #[test] fn tuple_combination_longer() { check_replacements( parse_quote! { -> (bool, Option) }, &[], &[ "(true, None)", "(true, Some(String::new()))", r#"(true, Some("xyzzy".into()))"#, "(false, None)", "(false, Some(String::new()))", r#"(false, Some("xyzzy".into()))"#, ], ) } #[test] fn iter_replacement() { check_replacements( parse_quote! { -> impl Iterator }, &[], &[ "::std::iter::empty()", "::std::iter::once(String::new())", r#"::std::iter::once("xyzzy".into())"#, ], ); } #[test] fn slice_replacement() { check_replacements( parse_quote! { -> [u8] }, &[], &[ "Vec::leak(Vec::new())", "Vec::leak(vec![0])", "Vec::leak(vec![1])", ], ); } fn check_replacements(return_type: ReturnType, error_exprs: &[Expr], expected: &[&str]) { assert_eq!( return_type_replacements(&return_type, error_exprs) .map(|t| t.to_pretty_string()) .collect_vec(), expected ); } } cargo-mutants-23.10.0/src/interrupt.rs000064400000000000000000000013751046102023000157520ustar 00000000000000// Copyright 2022 Martin Pool //! Handle ctrl-c by setting a global atomic and checking it from long-running //! operations. use std::sync::atomic::{AtomicBool, Ordering}; use anyhow::anyhow; use tracing::error; use crate::Result; static INTERRUPTED: AtomicBool = AtomicBool::new(false); pub fn install_handler() { ctrlc::set_handler(|| INTERRUPTED.store(true, Ordering::SeqCst)) .expect("install ctrl-c handler"); } /// Return an error if the program was interrupted and should exit. #[mutants::skip] // With this mutated too many of the tests will hang. pub fn check_interrupted() -> Result<()> { if INTERRUPTED.load(Ordering::SeqCst) { error!("interrupted"); Err(anyhow!("interrupted")) } else { Ok(()) } } cargo-mutants-23.10.0/src/lab.rs000064400000000000000000000206251046102023000144530ustar 00000000000000// Copyright 2021-2023 Martin Pool //! Successively apply mutations to the source code and run cargo to check, build, and test them. use std::cmp::max; use std::sync::Mutex; use std::thread; use std::time::{Duration, Instant}; use anyhow::{anyhow, Context, Result}; use itertools::Itertools; use tracing::warn; #[allow(unused)] use tracing::{debug, debug_span, error, info, trace}; use crate::console::Console; use crate::outcome::{LabOutcome, Phase, PhaseResult, ScenarioOutcome}; use crate::output::OutputDir; use crate::process::Process; use crate::source::Package; use crate::*; /// Run all possible mutation experiments. /// /// Before testing the mutants, the lab checks that the source tree passes its tests with no /// mutations applied. pub fn test_unmutated_then_all_mutants( tool: &dyn Tool, source_tree: &Utf8Path, options: Options, console: &Console, ) -> Result { let start_time = Instant::now(); let output_in_dir: &Utf8Path = options .output_in_dir .as_ref() .map_or(source_tree, |p| p.as_path()); let output_dir = OutputDir::new(output_in_dir)?; console.set_debug_log(output_dir.open_debug_log()?); let mut mutants = walk_tree(tool, source_tree, &options, console)?.mutants; if options.shuffle { fastrand::shuffle(&mut mutants); } output_dir.write_mutants_list(&mutants)?; console.discovered_mutants(&mutants); if mutants.is_empty() { return Err(anyhow!("No mutants found")); } let all_packages = mutants.iter().map(|m| m.package()).unique().collect_vec(); let output_mutex = Mutex::new(output_dir); let mut build_dirs = vec![BuildDir::new(source_tree, &options, console)?]; let baseline_outcome = { let _span = debug_span!("baseline").entered(); test_scenario( tool, &mut build_dirs[0], &output_mutex, &options, &Scenario::Baseline, &all_packages, options.test_timeout.unwrap_or(Duration::MAX), console, )? }; if !baseline_outcome.success() { error!( "{} {} failed in an unmutated tree, so no mutants were tested", tool.name(), baseline_outcome.last_phase(), ); // TODO: Maybe should be Err, but it would need to be an error that can map to the right // exit code. return Ok(output_mutex .into_inner() .expect("lock output_dir") .take_lab_outcome()); } let mutated_test_timeout = if let Some(timeout) = options.test_timeout { timeout } else if let Some(baseline_test_duration) = baseline_outcome .phase_results() .iter() .find(|r| r.phase == Phase::Test) .map(|r| r.duration) { let auto_timeout = max( options.minimum_test_timeout, baseline_test_duration.mul_f32(5.0), ); if options.show_times { console.autoset_timeout(auto_timeout); } auto_timeout } else { Duration::MAX }; let jobs = std::cmp::max(1, std::cmp::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(build_dirs[0].copy(console).context("copy build dir")?); } 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 // scenario to test off the queue, and then exits when there are no more left. console.start_testing_mutants(mutants.len()); let numbered_mutants = Mutex::new(mutants.into_iter().enumerate()); thread::scope(|scope| { let mut threads = Vec::new(); for build_dir in build_dirs { threads.push(scope.spawn(|| { let mut build_dir = build_dir; // move it into this thread let _thread_span = debug_span!("test thread", thread = ?thread::current().id()).entered(); trace!("start thread in {build_dir:?}"); loop { // Not a while loop so that it only holds the lock briefly. let next = numbered_mutants.lock().expect("lock mutants queue").next(); if let Some((mutant_id, mutant)) = next { let _span = debug_span!("mutant", id = mutant_id).entered(); let package = mutant.package().clone(); // We don't care about the outcome; it's been collected into the output_dir. let _outcome = test_scenario( tool, &mut build_dir, &output_mutex, &options, &Scenario::Mutant(mutant), &[&package], mutated_test_timeout, console, ) .expect("scenario test"); } else { trace!("no more work"); break; } } })); } for thread in threads { thread.join().expect("join thread"); } }); let output_dir = output_mutex .into_inner() .expect("final unlock mutants queue"); console.lab_finished(&output_dir.lab_outcome, start_time, &options); let lab_outcome = output_dir.take_lab_outcome(); if lab_outcome.total_mutants == 0 { // This should be unreachable as we also bail out before copying // the tree if no mutants are generated. warn!("No mutants were generated"); } else if lab_outcome.unviable == lab_outcome.total_mutants { warn!("No mutants were viable; perhaps there is a problem with building in a scratch directory"); } Ok(lab_outcome) } /// Test various phases of one scenario in a build dir. /// /// The [BuildDir] is passed as mutable because it's for the exclusive use of this function for the /// duration of the test. #[allow(unknown_lints, clippy::needless_pass_by_ref_mut)] fn test_scenario( tool: &dyn Tool, build_dir: &mut BuildDir, output_mutex: &Mutex, options: &Options, scenario: &Scenario, packages: &[&Package], test_timeout: Duration, console: &Console, ) -> Result { let mut log_file = output_mutex .lock() .expect("lock output_dir to create log") .create_log(scenario)?; log_file.message(&scenario.to_string()); if let Scenario::Mutant(mutant) = scenario { log_file.message(&format!("mutation diff:\n{}", mutant.diff())); mutant.apply(build_dir)?; } console.scenario_started(scenario, log_file.path()); let mut outcome = ScenarioOutcome::new(&log_file, scenario.clone()); let phases: &[Phase] = if options.check_only { &[Phase::Check] } else { &[Phase::Build, Phase::Test] }; for &phase in phases { let _span = debug_span!("run", ?phase).entered(); let start = Instant::now(); console.scenario_phase_started(scenario, phase); let timeout = match phase { Phase::Test => test_timeout, _ => Duration::MAX, }; let argv = tool.compose_argv(build_dir, Some(packages), phase, options)?; let env = tool.compose_env()?; let process_status = Process::run( &argv, &env, build_dir.path(), timeout, &mut log_file, console, )?; check_interrupted()?; debug!(?process_status, elapsed = ?start.elapsed()); let phase_result = PhaseResult { phase, duration: start.elapsed(), process_status, argv, }; outcome.add_phase_result(phase_result); console.scenario_phase_finished(scenario, phase); if (phase == Phase::Check && options.check_only) || !process_status.success() { break; } } if let Scenario::Mutant(mutant) = scenario { mutant.unapply(build_dir)?; } output_mutex .lock() .expect("lock output dir to add outcome") .add_scenario_outcome(&outcome)?; debug!(outcome = ?outcome.summary()); console.scenario_finished(scenario, &outcome, options); Ok(outcome) } cargo-mutants-23.10.0/src/list.rs000064400000000000000000000051041046102023000146630ustar 00000000000000// Copyright 2023 Martin Pool //! List mutants and files as text. use std::fmt; use std::io; use camino::Utf8Path; use serde_json::{json, Value}; use crate::console::style_mutant; use crate::console::Console; use crate::path::Utf8PathSlashes; use crate::{walk_tree, Options, Result, Tool}; /// Convert `fmt::Write` to `io::Write`. pub(crate) struct FmtToIoWrite(W); impl FmtToIoWrite { pub(crate) fn new(w: W) -> Self { Self(w) } } impl fmt::Write for FmtToIoWrite { fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { self.0.write_all(s.as_bytes()).map_err(|_| fmt::Error) } } pub(crate) fn list_mutants( mut out: W, tool: &dyn Tool, source_tree_root: &Utf8Path, options: &Options, console: &Console, ) -> Result<()> { let discovered = walk_tree(tool, source_tree_root, options, console)?; console.clear(); if options.emit_json { let mut list: Vec = Vec::new(); for mutant in discovered.mutants { let mut obj = serde_json::to_value(&mutant)?; if options.emit_diffs { obj.as_object_mut() .unwrap() .insert("diff".to_owned(), json!(mutant.diff())); } list.push(obj); } out.write_str(&serde_json::to_string_pretty(&list)?)?; } else { for mutant in &discovered.mutants { if options.colors { writeln!(out, "{}", style_mutant(mutant))?; } else { writeln!(out, "{}", mutant)?; } if options.emit_diffs { writeln!(out, "{}", mutant.diff())?; } } } Ok(()) } pub(crate) fn list_files( mut out: W, tool: &dyn Tool, source: &Utf8Path, options: &Options, console: &Console, ) -> Result<()> { let files = walk_tree(tool, source, options, console)?.files; if options.emit_json { let json_list = Value::Array( files .iter() .map(|source_file| { json!({ "path": source_file.tree_relative_path.to_slash_path(), "package": source_file.package.name, }) }) .collect(), ); writeln!(out, "{}", serde_json::to_string_pretty(&json_list)?)?; } else { for file in files { writeln!(out, "{}", file.tree_relative_path.to_slash_path())?; } } Ok(()) } cargo-mutants-23.10.0/src/log_file.rs000064400000000000000000000101051046102023000154650ustar 00000000000000// Copyright 2021, 2022 Martin Pool //! Manage per-scenario log files, which contain the output from cargo //! and test cases, mixed with commentary from cargo-mutants. use std::fs::{self, File, OpenOptions}; use std::io::{self, Write}; use anyhow::Context; use camino::{Utf8Path, Utf8PathBuf}; use crate::Result; /// Text inserted in log files to make important sections more visible. pub const LOG_MARKER: &str = "***"; /// A log file for execution of a single scenario. #[derive(Debug)] pub struct LogFile { path: Utf8PathBuf, write_to: File, } impl LogFile { pub fn create_in(log_dir: &Utf8Path, scenario_name: &str) -> Result { let basename = clean_filename(scenario_name); for i in 0..1000 { let t = if i == 0 { format!("{basename}.log") } else { format!("{basename}_{i:03}.log") }; let path = log_dir.join(t); match OpenOptions::new() .write(true) .read(true) .append(true) .create_new(true) .open(&path) { Ok(write_to) => return Ok(LogFile { path, write_to }), Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue, Err(e) => return Err(anyhow::Error::from(e).context("create test log file")), } } unreachable!( "couldn't create any test log in {:?} for {:?}", log_dir, scenario_name, ); } /// Open the log file to append more content. pub fn open_append(&self) -> Result { OpenOptions::new() .append(true) .open(&self.path) .with_context(|| format!("open {} for append", self.path)) } /// Write a message, with a marker. Ignore errors. pub fn message(&mut self, message: &str) { write!(self.write_to, "\n{LOG_MARKER} {message}\n").expect("write message to log"); } pub fn path(&self) -> &Utf8Path { &self.path } } /// Return the last non-empty line from a file, if it has any content. pub fn last_line(path: &Utf8Path) -> Result { // This is somewhat inefficient: we could potentially remember how long // the file was last time, seek to that point, and deal with incomplete // lines. However, probably these files will never get so colossal that // reading them is a big problem; they are almost certainly in cache; // and this should only be called a few times per second... Ok(fs::read_to_string(path)? .lines() .filter(|s| !s.trim().is_empty()) .last() .unwrap_or_default() .to_owned()) } fn clean_filename(s: &str) -> String { let s = s.replace('/', "__"); s.chars() .map(|c| match c { '\\' | ' ' | ':' | '<' | '>' | '?' | '*' | '|' | '"' => '_', c => c, }) .collect::() } #[cfg(test)] mod test { use super::*; #[test] fn clean_filename_removes_special_characters() { assert_eq!( clean_filename("1/2\\3:4<5>6?7*8|9\"0"), "1__2_3_4_5_6_7_8_9_0" ); } #[test] fn last_line_of_file() { let mut tempfile = tempfile::NamedTempFile::new().unwrap(); let path: Utf8PathBuf = tempfile.path().to_owned().try_into().unwrap(); assert_eq!( last_line(&path).unwrap(), "", "empty file has an empty last line" ); tempfile.write_all(b"hello").unwrap(); assert_eq!( last_line(&path).unwrap(), "hello", "single line file with no terminator has that line as last line" ); tempfile.write_all(b"\n\n\n").unwrap(); assert_eq!( last_line(&path).unwrap(), "hello", "trailing blank lines are ignored" ); tempfile.write_all(b"that's all folks!\n").unwrap(); assert_eq!( last_line(&path).unwrap(), "that's all folks!", "newline terminated last line is returned" ); } } cargo-mutants-23.10.0/src/main.rs000064400000000000000000000160031046102023000146340ustar 00000000000000// Copyright 2021-2023 Martin Pool //! `cargo-mutants`: Find inadequately-tested code that can be removed without any tests failing. mod build_dir; mod cargo; mod config; mod console; mod exit_code; mod fnvalue; mod interrupt; mod lab; mod list; mod log_file; mod manifest; mod mutate; mod options; mod outcome; mod output; mod path; mod pretty; mod process; mod scenario; mod source; mod textedit; mod tool; mod visit; use std::env; use std::io; use std::process::exit; use anyhow::Result; use camino::Utf8Path; use camino::Utf8PathBuf; use clap::CommandFactory; use clap::Parser; use clap_complete::{generate, Shell}; use path_slash::PathExt; use tracing::debug; // Imports of public names from this crate. use crate::build_dir::BuildDir; use crate::cargo::CargoTool; use crate::console::Console; use crate::interrupt::check_interrupted; use crate::list::{list_files, list_mutants, FmtToIoWrite}; use crate::log_file::{last_line, LogFile}; use crate::manifest::fix_manifest; use crate::mutate::{Genre, Mutant}; use crate::options::Options; use crate::outcome::{Phase, ScenarioOutcome}; use crate::path::Utf8PathSlashes; use crate::scenario::Scenario; use crate::source::SourceFile; use crate::tool::Tool; use crate::visit::walk_tree; const VERSION: &str = env!("CARGO_PKG_VERSION"); const NAME: &str = env!("CARGO_PKG_NAME"); #[derive(Parser)] #[command(name = "cargo", bin_name = "cargo")] enum Cargo { #[command(name = "mutants")] Mutants(Args), } /// Find inadequately-tested code that can be removed without any tests failing. /// /// See for more information. #[derive(Parser, PartialEq, Debug)] #[command(author, about)] struct Args { /// show cargo output for all invocations (very verbose). #[arg(long)] all_logs: bool, /// print mutants that were caught by tests. #[arg(long, short = 'v')] caught: bool, /// cargo check generated mutants, but don't run tests. #[arg(long)] check: bool, /// generate autocompletions for the given shell. #[arg(long)] completions: Option, /// show the mutation diffs. #[arg(long)] diff: bool, /// rust crate directory to examine. #[arg(long, short = 'd')] dir: Option, /// return this error values from functions returning Result: /// for example, `::anyhow::anyhow!("mutated")`. #[arg(long)] error: Vec, /// regex for mutations to examine, matched against the names shown by `--list`. #[arg(long = "re", short = 'F')] examine_re: Vec, /// glob for files to exclude; with no glob, all files are included; globs containing /// slash match the entire path. If used together with `--file` argument, then the files to be examined are matched before the files to be excluded. #[arg(long, short = 'e')] exclude: Vec, /// regex for mutations to exclude, matched against the names shown by `--list`. #[arg(long, short = 'E')] exclude_re: Vec, /// glob for files to examine; with no glob, all files are examined; globs containing /// slash match the entire path. If used together with `--exclude` argument, then the files to be examined are matched before the files to be excluded. #[arg(long, short = 'f')] file: Vec, /// run this many cargo build/test jobs in parallel. #[arg(long, short = 'j', env = "CARGO_MUTANTS_JOBS")] jobs: Option, /// output json (only for --list). #[arg(long)] json: bool, /// don't delete the scratch directories, for debugging. #[arg(long)] leak_dirs: bool, /// log level for stdout (trace, debug, info, warn, error). #[arg( long, short = 'L', default_value = "info", env = "CARGO_MUTANTS_TRACE_LEVEL" )] level: tracing::Level, /// just list possible mutants, don't run them. #[arg(long)] list: bool, /// list source files, don't run anything. #[arg(long)] list_files: bool, /// don't read .cargo/mutants.toml. #[arg(long)] no_config: bool, /// don't copy the /target directory, and don't build the source tree first. #[arg(long)] no_copy_target: bool, /// don't print times or tree sizes, to make output deterministic. #[arg(long)] no_times: bool, /// create mutants.out within this directory. #[arg(long, short = 'o')] output: Option, /// run mutants in random order. #[arg(long)] shuffle: bool, /// run mutants in the fixed order they occur in the source tree. #[arg(long)] no_shuffle: bool, /// maximum run time for all cargo commands, in seconds. #[arg(long, short = 't')] timeout: Option, /// minimum timeout for tests, in seconds, as a lower bound on the auto-set time. #[arg(long, env = "CARGO_MUTANTS_MINIMUM_TEST_TIMEOUT")] minimum_test_timeout: Option, /// print mutations that failed to check or build. #[arg(long, short = 'V')] unviable: bool, /// show version and quit. #[arg(long, action = clap::ArgAction::SetTrue)] version: bool, /// additional args for all cargo invocations. #[arg(long, short = 'C', allow_hyphen_values = true)] cargo_arg: Vec, // The following option captures all the remaining non-option args, to // send to cargo. /// pass remaining arguments to cargo test after all options and after `--`. #[arg(last = true)] cargo_test_args: Vec, } fn main() -> Result<()> { let args = match Cargo::try_parse() { Ok(Cargo::Mutants(args)) => args, Err(e) => { eprintln!("{e}"); exit(exit_code::USAGE); } }; let console = Console::new(); console.setup_global_trace(args.level)?; interrupt::install_handler(); if args.version { println!("{NAME} {VERSION}"); return Ok(()); } else if let Some(shell) = args.completions { generate(shell, &mut Cargo::command(), "cargo", &mut io::stdout()); return Ok(()); } let source_path: &Utf8Path = if let Some(p) = &args.dir { p } else { Utf8Path::new(".") }; let tool = CargoTool::new(); let source_tree_root = tool.find_root(source_path)?; let config; if args.no_config { config = config::Config::default(); } else { config = config::Config::read_tree_config(&source_tree_root)?; debug!(?config); } let options = Options::new(&args, &config)?; debug!(?options); if args.list_files { list_files( FmtToIoWrite::new(io::stdout()), &tool, &source_tree_root, &options, &console, )?; } else if args.list { list_mutants( FmtToIoWrite::new(io::stdout()), &tool, &source_tree_root, &options, &console, )?; } else { let lab_outcome = lab::test_unmutated_then_all_mutants(&tool, &source_tree_root, options, &console)?; exit(lab_outcome.exit_code()); } Ok(()) } cargo-mutants-23.10.0/src/manifest.rs000064400000000000000000000274131046102023000155250ustar 00000000000000// Copyright 2022-2023 Martin Pool. //! Manipulate Cargo manifest and config files. //! //! In particular, when the tree is copied we have to fix up relative paths, so //! that they still work from the new location of the scratch directory. use std::fs; use anyhow::Context; use camino::Utf8Path; use tracing::debug; use crate::path::ascent; use crate::Result; /// Rewrite the scratch copy of a manifest to have absolute paths. /// /// `manifest_source_dir` is the directory originally containing the manifest, from /// which the absolute paths are calculated. pub fn fix_manifest(manifest_scratch_path: &Utf8Path, source_dir: &Utf8Path) -> Result<()> { let toml_str = fs::read_to_string(manifest_scratch_path).context("read manifest")?; if let Some(changed_toml) = fix_manifest_toml(&toml_str, source_dir)? { let toml_str = toml::to_string_pretty(&changed_toml).context("serialize changed manifest")?; fs::write(manifest_scratch_path, toml_str.as_bytes()).context("write manifest")?; } Ok(()) } /// Fix any relative paths within a Cargo.toml manifest. /// /// Returns the new manifest, or None if no changes were made. fn fix_manifest_toml( manifest_toml: &str, manifest_source_dir: &Utf8Path, ) -> Result> { let mut value: toml::Value = manifest_toml.parse().context("parse manifest")?; let orig_value = value.clone(); if let Some(top_table) = value.as_table_mut() { if let Some(dependencies) = top_table.get_mut("dependencies") { fix_dependency_table(dependencies, manifest_source_dir); } if let Some(replace) = top_table.get_mut("replace") { // The replace section is a table from package name/version to a // table which might include a `path` key. (The keys are not exactly // package names but it doesn't matter.) // fix_dependency_table(replace, manifest_source_dir); } if let Some(patch_table) = top_table.get_mut("patch").and_then(|p| p.as_table_mut()) { // The keys of the patch table are registry names or source URLs; // the values are like dependency tables. // for (_name, dependencies) in patch_table { fix_dependency_table(dependencies, manifest_source_dir); } } } if value == orig_value { Ok(None) } else { Ok(Some(value)) } } /// Fix up paths in a manifest "dependency table". /// /// This is a pattern that can occur at various places in the manifest. It's a /// map from a string (such as a package name) to a table which may contain a "path" field. /// /// For example: /// /// ```yaml /// mutants = { version = "1.0", path = "../mutants" } /// ``` /// /// The table is mutated if necessary. /// /// `dependencies` is a TOML Value that should normally be a table; /// other values are left unchanged. /// /// Entries that have no `path` are left unchanged too. fn fix_dependency_table(dependencies: &mut toml::Value, manifest_source_dir: &Utf8Path) { if let Some(dependencies_table) = dependencies.as_table_mut() { for (_, value) in dependencies_table.iter_mut() { if let Some(dependency_table) = value.as_table_mut() { if let Some(path_value) = dependency_table.get_mut("path") { if let Some(path_str) = path_value.as_str() { if let Some(new_path) = fix_path(path_str, manifest_source_dir) { *path_value = toml::Value::String(new_path); } } } } } } } /// Rewrite relative paths within `.cargo/config.toml` to be absolute paths. pub fn fix_cargo_config(build_path: &Utf8Path, source_path: &Utf8Path) -> Result<()> { let config_path = build_path.join(".cargo/config.toml"); if config_path.exists() { let toml_str = fs::read_to_string(&config_path).context("read .cargo/config.toml")?; if let Some(changed_toml) = fix_cargo_config_toml(&toml_str, source_path)? { fs::write(build_path.join(&config_path), changed_toml.as_bytes()) .context("write .cargo/config.toml")?; } } Ok(()) } /// Replace any relative paths in a config file with absolute paths. /// /// Returns None if no changes are needed. /// /// See . fn fix_cargo_config_toml(config_toml: &str, source_dir: &Utf8Path) -> Result> { let mut value: toml::Value = config_toml.parse().context("parse config.toml")?; let mut changed = false; if let Some(paths) = value.get_mut("paths").and_then(|p| p.as_array_mut()) { for path_value in paths { if let Some(path_str) = path_value.as_str() { if let Some(new_path) = fix_path(path_str, source_dir) { *path_value = toml::Value::String(new_path); changed = true; } } } } if changed { Ok(Some(toml::to_string_pretty(&value)?)) } else { Ok(None) } } /// Fix one path, from inside a scratch tree, to be absolute as interpreted /// relative to the source tree. /// /// Paths pointing into a subdirectory of the source tree are left unchanged. /// /// Returns None if the path does not need to be changed. fn fix_path(path_str: &str, source_dir: &Utf8Path) -> Option { let path = Utf8Path::new(path_str); if path.is_absolute() || ascent(path) == 0 { None } else { let mut new_path = source_dir.to_owned(); new_path.push(path); let new_path_str = new_path.to_string(); debug!("fix path {path_str} -> {new_path_str}"); Some(new_path_str) } } #[cfg(test)] mod test { use camino::{Utf8Path, Utf8PathBuf}; use indoc::indoc; use pretty_assertions::assert_eq; use toml::Table; use super::fix_manifest_toml; #[test] fn fix_path_absolute_unchanged() { let dependency_abspath = Utf8Path::new("testdata/tree/dependency") .canonicalize_utf8() .unwrap(); assert_eq!( super::fix_path( dependency_abspath.as_str(), Utf8Path::new("/home/user/src/foo") ), None ); } #[test] fn fix_path_relative() { let fixed_path: Utf8PathBuf = super::fix_path( "../dependency", Utf8Path::new("testdata/tree/relative_dependency"), ) .expect("path was adjusted") .into(); assert_eq!( &fixed_path, Utf8Path::new("testdata/tree/relative_dependency/../dependency"), ); } #[test] fn fix_relative_path_in_manifest() { let manifest_toml = indoc! { r#" # A comment, which will be dropped. author = "A Smithee" [dependencies] wibble = { path = "../wibble" } # Use the relative path to the dependency. "# }; let orig_path = Utf8Path::new("/home/user/src/foo"); let fixed = fix_manifest_toml(manifest_toml, orig_path) .unwrap() .expect("toml was modified"); println!("{fixed:#?}"); assert_eq!(fixed["author"].as_str().unwrap(), "A Smithee"); assert_eq!( fixed["dependencies"]["wibble"]["path"].as_str().unwrap(), Utf8Path::new("/home/user/src/foo/../wibble") ); } #[test] fn fix_replace_section() { let manifest_toml = indoc! { r#" [dependencies] wibble = "1.2.3" [replace] "wibble:1.2.3" = { path = "../wibble" } # Use the relative path to the dependency. "# }; let orig_path = Utf8Path::new("/home/user/src/foo"); let fixed = fix_manifest_toml(manifest_toml, orig_path) .unwrap() .expect("toml was modified"); println!("fixed toml:\n{}", toml::to_string_pretty(&fixed).unwrap()); assert_eq!(fixed["dependencies"]["wibble"].as_str().unwrap(), "1.2.3"); assert_eq!( fixed["replace"]["wibble:1.2.3"]["path"].as_str().unwrap(), orig_path.join("../wibble") ); } #[test] fn absolute_path_in_manifest_is_unchanged() { #[cfg(unix)] let manifest_toml = indoc! { r#" [dependencies] wibble = { path = "/home/asmithee/src/wibble" } "# }; #[cfg(windows)] let manifest_toml = indoc! { r#" [dependencies] wibble = { path = "c:/home/asmithee/src/wibble" } "# }; let orig_path = Utf8Path::new("/home/user/src/foo"); let fixed_toml = fix_manifest_toml(manifest_toml, orig_path).unwrap(); assert_eq!( fixed_toml, None, "manifest containing only an absolute path should not be modified" ); } #[test] fn subdir_path_in_manifest_is_unchanged() { let manifest_toml = indoc! { r#" [dependencies] wibble = { path = "wibble" } "# }; let orig_path = Utf8Path::new("/home/user/src/foo"); let fixed_toml = fix_manifest_toml(manifest_toml, orig_path).unwrap(); assert_eq!( fixed_toml, None, "manifest with a relative path to a subdirectory should not be modified", ); } #[test] fn fix_patch_section() { let manifest_toml = indoc! { r#" [dependencies] wibble = "1.2.3" [patch.crates-io] wibble = { path = "../wibble" } # Use the relative path to the dependency. "# }; let orig_path = Utf8Path::new("/home/user/src/foo"); let fixed = fix_manifest_toml(manifest_toml, orig_path) .unwrap() .expect("toml was modified"); println!("{fixed:#?}"); assert_eq!(fixed["dependencies"]["wibble"].as_str(), Some("1.2.3")); assert_eq!( fixed["patch"]["crates-io"]["wibble"]["path"] .as_str() .unwrap(), orig_path.join("../wibble") ); } #[test] fn cargo_config_toml_paths_outside_tree_are_made_absolute() { // To avoid test flakiness due to TOML stylistic changes, we compare the // TOML values. // // And, to avoid headaches about forward and backslashes on Windows, // compare path objects. let cargo_config_toml = indoc! { r#" paths = [ "sub_dependency", "../sibling_dependency", "../../parent_dependency", "/Users/jane/src/absolute_dependency", "/src/other", ]"# }; let source_dir = Utf8Path::new("/Users/jane/src/foo"); let fixed_toml = super::fix_cargo_config_toml(cargo_config_toml, source_dir) .unwrap() .expect("toml was modified"); println!("fixed toml:\n{fixed_toml}"); // TODO: Maybe fix_cargo_config_toml should return the Value. let fixed_table: Table = fixed_toml.parse::().unwrap(); let fixed_paths = fixed_table["paths"] .as_array() .unwrap() .iter() .map(|val| val.as_str().unwrap().into()) .collect::>(); assert_eq!( fixed_paths, [ Utf8Path::new("sub_dependency"), &source_dir.join("../sibling_dependency"), &source_dir.join("../../parent_dependency"), &source_dir.parent().unwrap().join("absolute_dependency"), Utf8Path::new("/src/other"), ] ); } } cargo-mutants-23.10.0/src/mutate.rs000064400000000000000000000254101046102023000152110ustar 00000000000000// Copyright 2021-2023 Martin Pool //! Mutations to source files, and inference of interesting mutations to apply. use std::fmt; use std::fs; use std::sync::Arc; use anyhow::ensure; use anyhow::Context; use anyhow::Result; use serde::ser::{SerializeStruct, Serializer}; use serde::Serialize; use similar::TextDiff; use crate::build_dir::BuildDir; use crate::source::Package; use crate::source::SourceFile; use crate::textedit::{replace_region, Span}; /// A comment marker inserted next to changes, so they can be easily found. const MUTATION_MARKER_COMMENT: &str = "/* ~ changed by cargo-mutants ~ */"; /// Various broad categories of mutants. #[derive(Clone, Eq, PartialEq, Debug, Serialize)] pub enum Genre { /// Replace the body of a function with a fixed value. FnValue, } /// A mutation applied to source code. #[derive(Clone, Eq, PartialEq)] pub struct Mutant { /// Which file is being mutated. pub source_file: Arc, /// The function that's being mutated. pub function_name: Arc, /// The return type of the function, as a fragment of Rust syntax. pub return_type: Arc, /// The mutated textual region. pub span: Span, /// The replacement text. pub replacement: String, /// What general category of mutant this is. pub genre: Genre, } impl Mutant { /// Return text of the whole file with the mutation applied. pub fn mutated_code(&self) -> String { replace_region( &self.source_file.code, &self.span.start, &self.span.end, &format!("{{\n{} {}\n}}\n", self.replacement, MUTATION_MARKER_COMMENT), ) } /// Return the original code for the entire file affected by this mutation. pub fn original_code(&self) -> &str { &self.source_file.code } pub fn return_type(&self) -> &str { &self.return_type } /// Describe the mutant briefly, not including the location. /// /// The result is like `replace factorial -> u32 with Default::default()`. pub fn describe_change(&self) -> String { format!( "replace {name}{space}{type} with {replacement}", name = self.function_name(), space = if self.return_type.is_empty() { "" } else { " " }, type = self.return_type(), replacement = self.replacement ) } /// Return the text inserted for this mutation. pub fn replacement_text(&self) -> &str { self.replacement.as_str() } /// Return the name of the function to be mutated. /// /// Note that this will often not be unique: the same name can be reused /// in different modules, under different cfg guards, etc. pub fn function_name(&self) -> &str { &self.function_name } /// Return the cargo package name. pub fn package_name(&self) -> &str { &self.source_file.package.name } pub fn package(&self) -> &Package { &self.source_file.package } /// Return a unified diff for the mutant. pub fn diff(&self) -> String { let old_label = self.source_file.tree_relative_slashes(); // There shouldn't be any newlines, but just in case... let new_label = self.describe_change().replace('\n', " "); TextDiff::from_lines(self.original_code(), &self.mutated_code()) .unified_diff() .context_radius(8) .header(&old_label, &new_label) .to_string() } pub fn apply(&self, build_dir: &BuildDir) -> Result<()> { self.write_in_dir(build_dir, &self.mutated_code()) } pub fn unapply(&self, build_dir: &BuildDir) -> Result<()> { self.write_in_dir(build_dir, self.original_code()) } fn write_in_dir(&self, build_dir: &BuildDir, code: &str) -> Result<()> { let path = build_dir.path().join(&self.source_file.tree_relative_path); // for safety, don't follow symlinks ensure!(path.is_file(), "{path:?} is not a file"); fs::write(&path, code.as_bytes()) .with_context(|| format!("failed to write mutated code to {path:?}")) } pub fn log_file_name_base(&self) -> String { format!( "{}_line_{}", self.source_file.tree_relative_slashes(), self.span.start.line ) } } impl fmt::Debug for Mutant { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Custom implementation to show spans more concisely f.debug_struct("Mutant") .field("function_name", &self.function_name()) .field("return_type", &self.return_type) .field("replacement", &self.replacement) .field("genre", &self.genre) .field("start", &(self.span.start.line, self.span.start.column)) .field("end", &(self.span.end.line, self.span.end.column)) .field("package_name", &self.package_name()) .finish() } } impl fmt::Display for Mutant { /// Describe this mutant like a compiler error message, starting with the file and line. /// /// The result is like `src/source.rs:123: replace source::SourceFile::new with Default::default()`. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // This is like `style_mutant`, but without colors. // The text content should be the same. write!( f, "{file}:{line}: {change}", file = self.source_file.tree_relative_slashes(), line = self.span.start.line, change = self.describe_change() ) } } impl Serialize for Mutant { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // custom serialize to omit inessential info let mut ss = serializer.serialize_struct("Mutant", 7)?; ss.serialize_field("package", &self.package_name())?; ss.serialize_field("file", &self.source_file.tree_relative_slashes())?; ss.serialize_field("line", &self.span.start.line)?; ss.serialize_field("function", &self.function_name.as_ref())?; ss.serialize_field("return_type", &self.return_type.as_ref())?; ss.serialize_field("replacement", &self.replacement)?; ss.serialize_field("genre", &self.genre)?; ss.end() } } #[cfg(test)] mod test { use camino::Utf8Path; use indoc::indoc; use itertools::Itertools; use pretty_assertions::assert_eq; use crate::*; #[test] fn discover_factorial_mutants() { let tree_path = Utf8Path::new("testdata/tree/factorial"); let tool = CargoTool::new(); let source_tree = tool.find_root(tree_path).unwrap(); let options = Options::default(); let mutants = walk_tree(&tool, &source_tree, &options, &Console::new()) .unwrap() .mutants; assert_eq!(mutants.len(), 3); assert_eq!( format!("{:?}", mutants[0]), "Mutant { \ function_name: \"main\", \ return_type: \"\", \ replacement: \"()\", \ genre: FnValue, \ start: (1, 11), end: (5, 2), \ package_name: \"cargo-mutants-testdata-factorial\" \ }" ); assert_eq!( mutants[0].to_string(), "src/bin/factorial.rs:1: replace main with ()" ); assert_eq!( format!("{:#?}", mutants[1]), indoc! { r#" Mutant { function_name: "factorial", return_type: "-> u32", replacement: "0", genre: FnValue, start: ( 7, 29, ), end: ( 13, 2, ), package_name: "cargo-mutants-testdata-factorial", }"# } ); assert_eq!( mutants[1].to_string(), "src/bin/factorial.rs:7: replace factorial -> u32 with 0" ); assert_eq!( mutants[2].to_string(), "src/bin/factorial.rs:7: replace factorial -> u32 with 1" ); } #[test] fn filter_by_attributes() { let tree_path = Utf8Path::new("testdata/tree/hang_avoided_by_attr"); let tool = CargoTool::new(); let source_tree = tool.find_root(tree_path).unwrap(); let mutants = walk_tree(&tool, &source_tree, &Options::default(), &Console::new()) .unwrap() .mutants; let descriptions = mutants.iter().map(Mutant::describe_change).collect_vec(); insta::assert_snapshot!( descriptions.join("\n"), @"replace controlled_loop with ()" ); } #[test] fn mutate_factorial() { let tree_path = Utf8Path::new("testdata/tree/factorial"); let tool = CargoTool::new(); let source_tree = tool.find_root(tree_path).unwrap(); let mutants = walk_tree(&tool, &source_tree, &Options::default(), &Console::new()) .unwrap() .mutants; assert_eq!(mutants.len(), 3); let mut mutated_code = mutants[0].mutated_code(); assert_eq!(mutants[0].function_name(), "main"); mutated_code.retain(|c| c != '\r'); assert_eq!( mutated_code, indoc! { r#" fn main() { () /* ~ changed by cargo-mutants ~ */ } fn factorial(n: u32) -> u32 { let mut a = 1; for i in 2..=n { a *= i; } a } #[test] fn test_factorial() { println!("factorial({}) = {}", 6, factorial(6)); // This line is here so we can see it in --nocapture assert_eq!(factorial(6), 720); } "# } ); let mut mutated_code = mutants[1].mutated_code(); assert_eq!(mutants[1].function_name(), "factorial"); mutated_code.retain(|c| c != '\r'); assert_eq!( mutated_code, indoc! { r#" fn main() { for i in 1..=6 { println!("{}! = {}", i, factorial(i)); } } fn factorial(n: u32) -> u32 { 0 /* ~ changed by cargo-mutants ~ */ } #[test] fn test_factorial() { println!("factorial({}) = {}", 6, factorial(6)); // This line is here so we can see it in --nocapture assert_eq!(factorial(6), 720); } "# } ); } } cargo-mutants-23.10.0/src/options.rs000064400000000000000000000133521046102023000154070ustar 00000000000000// Copyright 2021-2023 Martin Pool //! Global in-process options for experimenting on mutants. //! //! The [Options] structure is built from command-line options and then widely passed around. //! Options are also merged from the [config] after reading the command line arguments. use std::time::Duration; use anyhow::Context; use camino::Utf8PathBuf; use globset::{Glob, GlobSet, GlobSetBuilder}; use regex::RegexSet; use tracing::warn; use crate::{config::Config, *}; /// Options for mutation testing, based on both command-line arguments and the /// config file. #[derive(Default, Debug, Clone)] pub struct Options { /// Don't run the tests, just see if each mutant builds. pub check_only: bool, /// Don't delete scratch directories. pub leak_dirs: bool, /// The time limit for test tasks, if set. /// /// If this is not set by the user it's None, in which case there is no time limit /// on the baseline test, and then the mutated tests get a multiple of the time /// taken by the baseline test. pub test_timeout: Option, /// The minimum test timeout, as a floor on the autoset value. pub minimum_test_timeout: Duration, pub print_caught: bool, pub print_unviable: bool, pub show_times: bool, /// Show logs even from mutants that were caught, or source/unmutated builds. pub show_all_logs: bool, /// Test mutants in random order. /// /// This is now the default, so that repeated partial runs are more likely to find /// interesting results. pub shuffle: bool, /// Additional arguments for every cargo invocation. pub additional_cargo_args: Vec, /// Additional arguments to `cargo test`. pub additional_cargo_test_args: Vec, /// Files to examine. pub examine_globset: Option, /// Files to exclude. pub exclude_globset: Option, /// Mutants to examine, as a regexp matched against the full name. pub examine_names: Option, /// Mutants to skip, as a regexp matched against the full name. pub exclude_names: Option, /// Create `mutants.out` within this directory (by default, the source directory). pub output_in_dir: Option, /// Run this many `cargo build` or `cargo test` tasks in parallel. pub jobs: Option, /// Insert these values as errors from functions returning `Result`. pub error_values: Vec, /// Show ANSI colors. pub colors: bool, /// List mutants in json, etc. pub emit_json: bool, /// Emit diffs showing just what changed. pub emit_diffs: bool, } 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 } impl Options { /// Build options by merging command-line args and config file. pub(crate) fn new(args: &Args, config: &Config) -> Result { if args.no_copy_target { warn!("--no-copy-target is deprecated and has no effect; target/ is never copied"); } let minimum_test_timeout = Duration::from_secs_f64( args.minimum_test_timeout .or(config.minimum_test_timeout) .unwrap_or(20f64), ); let options = Options { additional_cargo_args: join_slices(&args.cargo_arg, &config.additional_cargo_args), additional_cargo_test_args: join_slices( &args.cargo_test_args, &config.additional_cargo_test_args, ), check_only: args.check, error_values: join_slices(&args.error, &config.error_values), examine_names: Some( RegexSet::new(args.examine_re.iter().chain(config.examine_re.iter())) .context("Compiling examine_re regex")?, ), examine_globset: build_glob_set(args.file.iter().chain(config.examine_globs.iter()))?, exclude_names: Some( RegexSet::new(args.exclude_re.iter().chain(config.exclude_re.iter())) .context("Compiling exclude_re regex")?, ), exclude_globset: build_glob_set( args.exclude.iter().chain(config.exclude_globs.iter()), )?, jobs: args.jobs, leak_dirs: args.leak_dirs, output_in_dir: args.output.clone(), print_caught: args.caught, print_unviable: args.unviable, shuffle: !args.no_shuffle, show_times: !args.no_times, show_all_logs: args.all_logs, test_timeout: args.timeout.map(Duration::from_secs_f64), emit_json: args.json, colors: true, // TODO: An option for this and use CLICOLORS. emit_diffs: args.diff, minimum_test_timeout, }; options.error_values.iter().for_each(|e| { if e.starts_with("Err(") { warn!( "error_value option gives the value of the error, and probably should not start with Err(: got {}", e ); } }); Ok(options) } } fn build_glob_set, I: IntoIterator>( glob_set: I, ) -> Result> { let mut glob_set = glob_set.into_iter().peekable(); if glob_set.peek().is_none() { return Ok(None); } let mut builder = GlobSetBuilder::new(); for glob_str in glob_set { let glob_str = glob_str.as_ref(); if glob_str.contains('/') || glob_str.contains(std::path::MAIN_SEPARATOR) { builder.add(Glob::new(glob_str)?); } else { builder.add(Glob::new(&format!("**/{glob_str}"))?); } } Ok(Some(builder.build()?)) } cargo-mutants-23.10.0/src/outcome.rs000064400000000000000000000221351046102023000153660ustar 00000000000000// Copyright 2022-2023 Martin Pool //! The outcome of running a single mutation scenario, or a whole lab. use std::fmt; use std::fs; use std::time::Duration; use std::time::Instant; use anyhow::Context; use serde::ser::SerializeStruct; use serde::Serialize; use serde::Serializer; use crate::console::{duration_minutes_seconds, plural}; use crate::exit_code; use crate::log_file::LogFile; use crate::process::ProcessStatus; use crate::*; /// What phase of running a scenario. /// /// Every scenario proceed through up to three phases in order. They are: /// /// 1. `cargo check` -- is the tree basically buildable? This is skipped /// during normal testing, but used with `--check`, in which case the /// other phases are skipped. /// 2. `cargo build` -- actually build it. /// 3. `cargo tests` -- do the tests pass? /// /// Some scenarios such as freshening the tree don't run the tests. #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize)] pub enum Phase { Check, Build, Test, } impl Phase { pub fn name(&self) -> &'static str { match self { Phase::Check => "check", Phase::Build => "build", Phase::Test => "test", } } } impl fmt::Display for Phase { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.name()) } } /// The outcome from a whole lab run containing multiple mutants. #[derive(Debug, Default, Serialize)] pub struct LabOutcome { /// All the scenario outcomes, including baseline builds. pub outcomes: Vec, pub total_mutants: usize, pub missed: usize, pub caught: usize, pub timeout: usize, pub unviable: usize, pub success: usize, pub failure: usize, } impl LabOutcome { pub fn new() -> LabOutcome { LabOutcome::default() } /// Record the event of one test. pub fn add(&mut self, outcome: ScenarioOutcome) { if outcome.scenario.is_mutant() { self.total_mutants += 1; match outcome.summary() { SummaryOutcome::CaughtMutant => self.caught += 1, SummaryOutcome::MissedMutant => self.missed += 1, SummaryOutcome::Timeout => self.timeout += 1, SummaryOutcome::Unviable => self.unviable += 1, SummaryOutcome::Success => self.success += 1, SummaryOutcome::Failure => self.failure += 1, } } self.outcomes.push(outcome); } /// Return the overall program exit code reflecting this outcome. pub fn exit_code(&self) -> i32 { // TODO: Maybe move this into an error returned from experiment()? if self .outcomes .iter() .any(|o| !o.scenario.is_mutant() && !o.success()) { exit_code::CLEAN_TESTS_FAILED } else if self.timeout > 0 { exit_code::TIMEOUT } else if self.missed > 0 { exit_code::FOUND_PROBLEMS } else { exit_code::SUCCESS } } /// Return an overall summary, to show at the end of the program. pub fn summary_string(&self, start_time: Instant, options: &Options) -> String { let mut s = format!("{} tested", plural(self.total_mutants, "mutant"),); if options.show_times { s.push_str(" in "); s.push_str(&duration_minutes_seconds(start_time.elapsed())); } s.push_str(": "); let mut parts: Vec = Vec::new(); if self.missed > 0 { parts.push(format!("{} missed", self.missed)); } if self.caught > 0 { parts.push(format!("{} caught", self.caught)); } if self.unviable > 0 { parts.push(format!("{} unviable", self.unviable)); } if self.timeout > 0 { parts.push(format!("{} timeouts", self.timeout)); } if self.success > 0 { parts.push(format!("{} succeeded", self.success)); } if self.failure > 0 { parts.push(format!("{} failed", self.failure)); } s.push_str(&parts.join(", ")); s } } /// The result of running one mutation scenario. #[derive(Debug, Clone, Eq, PartialEq)] #[must_use] pub struct ScenarioOutcome { /// A file holding the text output from running this test. // TODO: Maybe this should be a log object? log_path: Utf8PathBuf, /// What kind of scenario was being built? pub scenario: Scenario, /// For each phase, the duration and the cargo result. phase_results: Vec, } impl Serialize for ScenarioOutcome { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // custom serialize to omit inessential info let mut ss = serializer.serialize_struct("Outcome", 4)?; ss.serialize_field("scenario", &self.scenario)?; ss.serialize_field("log_path", &self.log_path)?; ss.serialize_field("summary", &self.summary())?; ss.serialize_field("phase_results", &self.phase_results)?; ss.end() } } impl ScenarioOutcome { pub fn new(log_file: &LogFile, scenario: Scenario) -> ScenarioOutcome { ScenarioOutcome { log_path: log_file.path().to_owned(), scenario, phase_results: Vec::new(), } } pub fn add_phase_result(&mut self, phase_result: PhaseResult) { self.phase_results.push(phase_result); } pub fn get_log_content(&self) -> Result { fs::read_to_string(&self.log_path).context("read log file") } pub fn last_phase(&self) -> Phase { self.phase_results.last().unwrap().phase } pub fn last_phase_result(&self) -> ProcessStatus { self.phase_results.last().unwrap().process_status } pub fn phase_results(&self) -> &[PhaseResult] { &self.phase_results } /// True if this status indicates the user definitely needs to see the logs, because a task /// failed that should not have failed. pub fn should_show_logs(&self) -> bool { !self.scenario.is_mutant() && !self.success() } pub fn success(&self) -> bool { self.last_phase_result().success() } pub fn has_timeout(&self) -> bool { self.phase_results .iter() .any(|pr| pr.process_status.timeout()) } pub fn check_or_build_failed(&self) -> bool { self.phase_results .iter() .any(|pr| pr.phase != Phase::Test && pr.process_status == ProcessStatus::Failure) } /// True if this outcome is a caught mutant: it's a mutant and the tests failed. pub fn mutant_caught(&self) -> bool { self.scenario.is_mutant() && self.last_phase() == Phase::Test && self.last_phase_result() == ProcessStatus::Failure } /// True if this outcome is a missed mutant: it's a mutant and the tests succeeded. pub fn mutant_missed(&self) -> bool { self.scenario.is_mutant() && self.last_phase() == Phase::Test && self.last_phase_result().success() } pub fn summary(&self) -> SummaryOutcome { match self.scenario { Scenario::Baseline => { if self.has_timeout() { SummaryOutcome::Timeout } else if self.success() { SummaryOutcome::Success } else { SummaryOutcome::Failure } } Scenario::Mutant(_) => { if self.check_or_build_failed() { SummaryOutcome::Unviable } else if self.has_timeout() { SummaryOutcome::Timeout } else if self.mutant_caught() { SummaryOutcome::CaughtMutant } else if self.mutant_missed() { SummaryOutcome::MissedMutant } else if self.success() { SummaryOutcome::Success } else { SummaryOutcome::Failure } } } } } /// The result of running one phase of a mutation scenario, i.e. a single cargo check/build/test command. #[derive(Debug, Clone, Eq, PartialEq)] pub struct PhaseResult { /// What phase was this? pub phase: Phase, /// How long did it take? pub duration: Duration, /// Did it succeed? pub process_status: ProcessStatus, /// What command was run, as an argv list. pub argv: Vec, } impl Serialize for PhaseResult { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut ss = serializer.serialize_struct("PhaseResult", 4)?; ss.serialize_field("phase", &self.phase)?; ss.serialize_field("duration", &self.duration.as_secs_f64())?; ss.serialize_field("process_status", &self.process_status)?; ss.serialize_field("argv", &self.argv)?; ss.end() } } /// Overall summary outcome for one mutant. #[derive(Debug, Clone, Eq, PartialEq, Serialize, Hash)] pub enum SummaryOutcome { Success, CaughtMutant, MissedMutant, Unviable, Failure, Timeout, } cargo-mutants-23.10.0/src/output.rs000064400000000000000000000275321046102023000152610ustar 00000000000000// Copyright 2021-2023 Martin Pool //! A `mutants.out` directory holding logs and other output. use std::fs::{self, File, OpenOptions}; use std::io::{BufWriter, Write}; use std::path::Path; use std::thread::sleep; use std::time::Duration; use anyhow::{Context, Result}; use camino::Utf8Path; use fs2::FileExt; use path_slash::PathExt; use serde::Serialize; use time::format_description::well_known::Rfc3339; use time::OffsetDateTime; use tracing::info; use crate::outcome::{LabOutcome, SummaryOutcome}; use crate::*; const OUTDIR_NAME: &str = "mutants.out"; const ROTATED_NAME: &str = "mutants.out.old"; const LOCK_JSON: &str = "lock.json"; const LOCK_POLL: Duration = Duration::from_millis(100); /// The contents of a `lock.json` written into the output directory and used as /// a lock file to ensure that two cargo-mutants invocations don't try to write /// to the same `mutants.out` simultneously. #[derive(Debug, Serialize)] struct LockFile { cargo_mutants_version: String, start_time: String, hostname: String, username: String, } impl LockFile { fn new() -> LockFile { let start_time = OffsetDateTime::now_utc() .format(&Rfc3339) .expect("format current time"); LockFile { cargo_mutants_version: crate::VERSION.to_string(), start_time, hostname: whoami::hostname(), username: whoami::username(), } } /// Block until acquiring a file lock on `lock.json` in the given `mutants.out` /// directory. /// /// Return the `File` whose lifetime controls the file lock. pub fn acquire_lock(output_dir: &Path) -> Result { let lock_path = output_dir.join(LOCK_JSON); let mut lock_file = File::options() .create(true) .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; } } } lock_file.set_len(0)?; lock_file .write_all(serde_json::to_string_pretty(&LockFile::new())?.as_bytes()) .context("write lock.json")?; Ok(lock_file) } } /// A `mutants.out` directory holding logs and other output information. #[derive(Debug)] pub struct OutputDir { path: Utf8PathBuf, log_dir: Utf8PathBuf, #[allow(unused)] // Lifetime controls the file lock lock_file: File, /// A file holding a list of missed mutants as text, one per line. missed_list: File, /// A file holding a list of caught mutants as text, one per line. caught_list: File, /// A file holding a list of mutants where testing timed out, as text, one per line. timeout_list: File, unviable_list: File, /// The accumulated overall lab outcome. pub lab_outcome: LabOutcome, } impl OutputDir { /// Create a new `mutants.out` output directory, within the given directory. /// /// If `in_dir` does not exist, it's created too, so that users can name a new directory /// with `--output`. /// /// If the directory already exists, it's rotated to `mutants.out.old`. If that directory /// exists, it's deleted. /// /// If the directory already exists and `lock.json` exists and is locked, this waits for /// the lock to be released. The returned `OutputDir` holds a lock for its lifetime. pub fn new(in_dir: &Utf8Path) -> Result { if !in_dir.exists() { fs::create_dir(in_dir).context("create output parent directory {in_dir:?}")?; } let output_dir = in_dir.join(OUTDIR_NAME); if output_dir.exists() { LockFile::acquire_lock(output_dir.as_ref())?; // Now release the lock for a bit while we move the directory. This might be // slightly racy. let rotated = in_dir.join(ROTATED_NAME); if rotated.exists() { fs::remove_dir_all(&rotated).with_context(|| format!("remove {:?}", &rotated))?; } fs::rename(&output_dir, &rotated) .with_context(|| format!("move {:?} to {:?}", &output_dir, &rotated))?; } fs::create_dir(&output_dir) .with_context(|| format!("create output directory {:?}", &output_dir))?; let lock_file = LockFile::acquire_lock(output_dir.as_std_path()) .context("create lock.json lock file")?; let log_dir = output_dir.join("log"); fs::create_dir(&log_dir).with_context(|| format!("create log directory {:?}", &log_dir))?; // Create text list files. let mut list_file_options = OpenOptions::new(); list_file_options.create(true).append(true); let missed_list = list_file_options .open(output_dir.join("missed.txt")) .context("create missed.txt")?; let caught_list = list_file_options .open(output_dir.join("caught.txt")) .context("create caught.txt")?; let unviable_list = list_file_options .open(output_dir.join("unviable.txt")) .context("create unviable.txt")?; let timeout_list = list_file_options .open(output_dir.join("timeout.txt")) .context("create timeout.txt")?; Ok(OutputDir { path: output_dir, lab_outcome: LabOutcome::new(), log_dir, lock_file, missed_list, caught_list, timeout_list, unviable_list, }) } /// Create a new log for a given scenario. /// /// Returns the [File] to which subprocess output should be sent, and a LogFile to read it /// later. pub fn create_log(&self, scenario: &Scenario) -> Result { LogFile::create_in(&self.log_dir, &scenario.log_file_name_base()) } #[allow(dead_code)] /// Return the path of the `mutants.out` directory. pub fn path(&self) -> &Utf8Path { &self.path } /// Update the state of the overall lab. /// /// Called multiple times as the lab runs. pub fn write_lab_outcome(&self) -> Result<()> { serde_json::to_writer_pretty( BufWriter::new(File::create(self.path.join("outcomes.json"))?), &self.lab_outcome, ) .context("write outcomes.json") } /// Add the result of testing one scenario. pub fn add_scenario_outcome(&mut self, scenario_outcome: &ScenarioOutcome) -> Result<()> { self.lab_outcome.add(scenario_outcome.to_owned()); self.write_lab_outcome()?; let scenario = &scenario_outcome.scenario; if let Scenario::Mutant(mutant) = scenario { let file = match scenario_outcome.summary() { SummaryOutcome::MissedMutant => &mut self.missed_list, SummaryOutcome::CaughtMutant => &mut self.caught_list, SummaryOutcome::Timeout => &mut self.timeout_list, SummaryOutcome::Unviable => &mut self.unviable_list, _ => return Ok(()), }; writeln!(file, "{mutant}").context("write to list file")?; } Ok(()) } pub fn open_debug_log(&self) -> Result { let debug_log_path = self.path.join("debug.log"); OpenOptions::new() .create(true) .append(true) .open(&debug_log_path) .with_context(|| format!("open {debug_log_path}")) } pub fn write_mutants_list(&self, mutants: &[Mutant]) -> Result<()> { serde_json::to_writer_pretty( BufWriter::new(File::create(self.path.join("mutants.json"))?), mutants, ) .context("write mutants.json") } pub fn take_lab_outcome(self) -> LabOutcome { self.lab_outcome } } #[cfg(test)] mod test { use std::convert::TryInto; use indoc::indoc; use itertools::Itertools; use path_slash::PathExt; use pretty_assertions::assert_eq; use tempfile::TempDir; use super::*; fn minimal_source_tree() -> TempDir { let tmp = tempfile::tempdir().unwrap(); let path = tmp.path(); fs::write( path.join("Cargo.toml"), indoc! { br#" # enough for a test [package] name = "cargo-mutants-minimal-test-tree" version = "0.0.0" "# }, ) .unwrap(); fs::create_dir(path.join("src")).unwrap(); fs::write(path.join("src/lib.rs"), b"fn foo() {}").unwrap(); tmp } fn list_recursive(path: &Path) -> Vec { walkdir::WalkDir::new(path) .sort_by_file_name() .into_iter() .map(|entry| { entry .unwrap() .path() .strip_prefix(path) .unwrap() .to_slash_lossy() .to_string() }) .collect_vec() } #[test] fn create_output_dir() { let tmp = minimal_source_tree(); let tmp_path = tmp.path().try_into().unwrap(); let root = CargoTool::new().find_root(tmp_path).unwrap(); let output_dir = OutputDir::new(&root).unwrap(); assert_eq!( list_recursive(tmp.path()), &[ "", "Cargo.toml", "mutants.out", "mutants.out/caught.txt", "mutants.out/lock.json", "mutants.out/log", "mutants.out/missed.txt", "mutants.out/timeout.txt", "mutants.out/unviable.txt", "src", "src/lib.rs", ] ); assert_eq!(output_dir.path(), root.join("mutants.out")); assert_eq!(output_dir.log_dir, root.join("mutants.out/log")); assert!(output_dir.path().join("lock.json").is_file()); } #[test] fn rotate() { let temp_dir = tempfile::TempDir::new().unwrap(); let temp_dir_path = Utf8Path::from_path(temp_dir.path()).unwrap(); // Create an initial output dir with one log. let output_dir = OutputDir::new(temp_dir_path).unwrap(); output_dir.create_log(&Scenario::Baseline).unwrap(); assert!(temp_dir .path() .join("mutants.out/log/baseline.log") .is_file()); drop(output_dir); // release the lock. // The second time we create it in the same directory, the old one is moved away. let output_dir = OutputDir::new(temp_dir_path).unwrap(); output_dir.create_log(&Scenario::Baseline).unwrap(); assert!(temp_dir .path() .join("mutants.out.old/log/baseline.log") .is_file()); assert!(temp_dir .path() .join("mutants.out/log/baseline.log") .is_file()); drop(output_dir); // The third time (and later), the .old directory is removed. let output_dir = OutputDir::new(temp_dir_path).unwrap(); output_dir.create_log(&Scenario::Baseline).unwrap(); assert!(temp_dir .path() .join("mutants.out/log/baseline.log") .is_file()); assert!(temp_dir .path() .join("mutants.out.old/log/baseline.log") .is_file()); assert!(temp_dir .path() .join("mutants.out.old/log/baseline.log") .is_file()); } } cargo-mutants-23.10.0/src/path.rs000064400000000000000000000043021046102023000146430ustar 00000000000000// Copyright 2022 Martin Pool. //! Utilities for file paths. use camino::Utf8Path; /// Measures how far above its starting point a path ascends. /// /// If in following this path you would ever ascend above the starting point, /// this returns a whole number indicating the number of steps above the /// starting point. /// /// This only considers the textual content of the path, and does not look at /// symlinks or whether the directories actually exist. pub fn ascent(path: &Utf8Path) -> isize { let mut max_ascent: isize = 0; let mut ascent = 0; for component in path.components().map(|c| c.as_str()) { if component == ".." { ascent += 1; } else if component != "." { ascent -= 1; } if ascent > max_ascent { max_ascent = ascent; } } max_ascent } /// An extension trait that helps Utf8Path print with forward slashes, /// even on Windows. /// /// This makes the output more consistent across platforms and so easier /// to test. pub trait Utf8PathSlashes { fn to_slash_path(&self) -> String; } impl Utf8PathSlashes for Utf8Path { fn to_slash_path(&self) -> String { self.components() .map(|c| c.as_str()) .filter(|c| !c.is_empty()) .map(|c| if c == "/" || c == "\\" { "" } else { c }) .collect::>() .join("/") } } #[cfg(test)] mod test { use camino::{Utf8Path, Utf8PathBuf}; use super::{ascent, Utf8PathSlashes}; #[test] fn path_slashes_drops_empty_parts() { let mut path = Utf8PathBuf::from("/a/b/c/"); path.push("d/e/f"); assert_eq!(path.to_slash_path(), "/a/b/c/d/e/f"); } #[test] fn path_ascent() { assert_eq!(ascent(Utf8Path::new(".")), 0); assert_eq!(ascent(Utf8Path::new("..")), 1); assert_eq!(ascent(Utf8Path::new("sub/dir")), 0); assert_eq!(ascent(Utf8Path::new("sub/dir/../..")), 0); assert_eq!(ascent(Utf8Path::new("sub/../sub/./..")), 0); assert_eq!(ascent(Utf8Path::new("../back")), 1); assert_eq!(ascent(Utf8Path::new("../back/../back")), 1); assert_eq!(ascent(Utf8Path::new("../back/../../back/down")), 2); } } cargo-mutants-23.10.0/src/pretty.rs000064400000000000000000000077241046102023000152510ustar 00000000000000// Copyright 2021-2023 Martin Pool //! Convert a token stream back to (reasonably) pretty Rust code in a string. use proc_macro2::{Delimiter, TokenTree}; use quote::ToTokens; /// Convert something to a pretty-printed string. pub(crate) trait ToPrettyString { fn to_pretty_string(&self) -> String; } /// Convert a TokenStream representing some code to a reasonably formatted /// string of Rust code. /// /// [TokenStream] has a `to_string`, but it adds spaces in places that don't /// look idiomatic, so this reimplements it in a way that looks better. /// /// This is probably not correctly formatted for all Rust syntax, and only tries /// to cover cases that can emerge from the code we generate. impl ToPrettyString for T where T: ToTokens, { fn to_pretty_string(&self) -> String { use TokenTree::*; let mut b = String::with_capacity(200); let mut ts = self.to_token_stream().into_iter().peekable(); while let Some(tt) = ts.next() { match tt { Punct(p) => { let pc = p.as_char(); b.push(pc); if ts.peek().is_some() && (b.ends_with("->") || pc == ',' || pc == ';') { b.push(' '); } } Ident(_) | Literal(_) => { if b.ends_with('=') || b.ends_with("=>") { b.push(' '); } match tt { Literal(l) => b.push_str(&l.to_string()), Ident(i) => b.push_str(&i.to_string()), _ => unreachable!(), }; if let Some(next) = ts.peek() { match next { Ident(_) | Literal(_) => b.push(' '), Punct(p) => match p.as_char() { ',' | ';' | '<' | '>' | ':' | '.' | '!' => (), _ => b.push(' '), }, Group(_) => (), } } } Group(g) => { match g.delimiter() { Delimiter::Brace => b.push('{'), Delimiter::Bracket => b.push('['), Delimiter::Parenthesis => b.push('('), Delimiter::None => (), } b += &g.stream().to_pretty_string(); match g.delimiter() { Delimiter::Brace => b.push('}'), Delimiter::Bracket => b.push(']'), Delimiter::Parenthesis => b.push(')'), Delimiter::None => (), } } } } debug_assert!( !b.ends_with(' '), "generated a trailing space: ts={ts:?}, b={b:?}", ts = self.to_token_stream(), ); b } } #[cfg(test)] mod test { use pretty_assertions::assert_eq; use quote::quote; use super::ToPrettyString; #[test] fn pretty_format_examples() { assert_eq!( quote! { // Nonsense rust but a big salad of syntax > :: next -> Option < Self :: Item > } .to_pretty_string(), ">::next -> Option" ); assert_eq!( quote! { Lex < 'buf >::take }.to_pretty_string(), "Lex<'buf>::take" ); } #[test] fn format_trait_with_assoc_type() { assert_eq!( quote! { impl Iterator < Item = String > }.to_pretty_string(), "impl Iterator" ); } #[test] fn format_thick_arrow() { assert_eq!(quote! { a => b }.to_pretty_string(), "a => b"); } } cargo-mutants-23.10.0/src/process.rs000064400000000000000000000216621046102023000153750ustar 00000000000000// Copyright 2021-2023 Martin Pool //! Manage a subprocess, with polling, timeouts, termination, and so on. //! //! This module is above the external `subprocess` crate, but has no //! knowledge of whether it's running Cargo or potentially something else. //! //! On Unix, the subprocess runs as its own process group, so that any //! grandchild processses are also signalled if it's interrupted. use std::ffi::OsString; use std::io::Read; use std::thread::sleep; use std::time::{Duration, Instant}; use anyhow::{anyhow, Context}; use camino::Utf8Path; use serde::Serialize; use subprocess::{Popen, PopenConfig, Redirection}; #[allow(unused_imports)] use tracing::{debug, debug_span, error, info, span, trace, warn, Level}; use crate::console::Console; use crate::interrupt::check_interrupted; use crate::log_file::LogFile; use crate::Result; /// How long to wait for metadata-only Cargo commands. const METADATA_TIMEOUT: Duration = Duration::from_secs(20); /// How frequently to check if a subprocess finished. const WAIT_POLL_INTERVAL: Duration = Duration::from_millis(50); pub struct Process { child: Popen, start: Instant, timeout: Duration, } impl Process { /// Run a subprocess to completion, watching for interupts, with a timeout, while /// ticking the progress bar. pub fn run( argv: &[String], env: &[(String, String)], cwd: &Utf8Path, timeout: Duration, log_file: &mut LogFile, console: &Console, ) -> Result { let mut child = Process::start(argv, env, cwd, timeout, log_file)?; let process_status = loop { if let Some(exit_status) = child.poll()? { break exit_status; } else { console.tick(); sleep(WAIT_POLL_INTERVAL); } }; log_file.message(&format!("result: {process_status:?}")); Ok(process_status) } /// Launch a process, and return an object representing the child. pub fn start( argv: &[String], env: &[(String, String)], cwd: &Utf8Path, timeout: Duration, log_file: &mut LogFile, ) -> Result { let start = Instant::now(); log_file.message(&format!("run {}", argv.join(" "))); debug!("start {argv:?}"); let mut os_env = PopenConfig::current_env(); os_env.extend( env.iter() .map(|(k, v)| (OsString::from(k), OsString::from(v))), ); let child = Popen::create( argv, PopenConfig { stdin: Redirection::None, stdout: Redirection::File(log_file.open_append()?), stderr: Redirection::Merge, cwd: Some(cwd.as_os_str().to_owned()), env: Some(os_env), ..setpgid_on_unix() }, ) .with_context(|| format!("failed to spawn {}", argv.join(" ")))?; Ok(Process { child, start, timeout, }) } #[mutants::skip] // It's hard to avoid timeouts if this never works... pub fn poll(&mut self) -> Result> { let elapsed = self.start.elapsed(); if elapsed > self.timeout { info!( "timeout after {:.1}s, terminating child process...", elapsed.as_secs_f32() ); self.terminate()?; Ok(Some(ProcessStatus::Timeout)) } else if let Err(e) = check_interrupted() { debug!("interrupted, terminating child process..."); self.terminate()?; Err(e) } else if let Some(status) = self.child.poll() { if status.success() { Ok(Some(ProcessStatus::Success)) } else { Ok(Some(ProcessStatus::Failure)) } } else { Ok(None) } } /// Terminate the subprocess, initially gently and then harshly. /// /// Blocks until the subprocess is terminated and then returns the exit status. /// /// The status might not be Timeout if this raced with a normal exit. fn terminate(&mut self) -> Result<()> { let _span = span!(Level::DEBUG, "terminate_child", pid = self.child.pid()).entered(); debug!("terminating child process"); terminate_child_impl(&mut self.child)?; trace!("wait for child after termination"); if let Some(exit_status) = self .child .wait_timeout(Duration::from_secs(10)) .context("wait for child after terminating pgroup")? { debug!("terminated child exit status {exit_status:?}"); } else { warn!("child did not exit after termination"); let kill_result = self.child.kill(); warn!("force kill child: {:?}", kill_result); if kill_result.is_ok() { if let Ok(Some(exit_status)) = self .child .wait_timeout(Duration::from_secs(10)) .context("wait for child after force kill") { debug!("force kill child exit status {exit_status:?}"); } else { warn!("child did not exit after force kill"); } } } Ok(()) } } #[cfg(unix)] #[allow(unknown_lints, clippy::needless_pass_by_ref_mut)] // To match Windows fn terminate_child_impl(child: &mut Popen) -> Result<()> { use nix::errno::Errno; use nix::sys::signal::{killpg, Signal}; let pid = nix::unistd::Pid::from_raw(child.pid().expect("child has a pid").try_into().unwrap()); if let Err(errno) = killpg(pid, Signal::SIGTERM) { // It might have already exited, in which case we can proceed to wait for it. if errno != Errno::ESRCH { let message = format!("failed to terminate child: {errno}"); warn!("{}", message); return Err(anyhow!(message)); } } Ok(()) } // We do not yet have a way to mutate this only on Windows, and I mostly test on Unix, so it's just skipped for now. #[mutants::skip] #[cfg(not(unix))] fn terminate_child_impl(child: &mut Popen) -> Result<()> { if let Err(e) = child.terminate() { // most likely we raced and it's already gone let message = format!("failed to terminate child: {}", e); warn!("{}", message); return Err(anyhow!(message)); } Ok(()) } /// The result of running a single child process. #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)] pub enum ProcessStatus { Success, Failure, Timeout, } impl ProcessStatus { pub fn success(&self) -> bool { *self == ProcessStatus::Success } pub fn timeout(&self) -> bool { *self == ProcessStatus::Timeout } } #[cfg(unix)] fn setpgid_on_unix() -> PopenConfig { PopenConfig { setpgid: true, ..Default::default() } } #[mutants::skip] // Has no effect, so can't be tested. #[cfg(not(unix))] fn setpgid_on_unix() -> PopenConfig { Default::default() } /// Run a command and return its stdout output as a string. /// /// If the command exits non-zero, the error includes any messages it wrote to stderr. /// /// The runtime is capped by [METADATA_TIMEOUT]. pub fn get_command_output(argv: &[&str], cwd: &Utf8Path) -> Result { // TODO: Perhaps redirect to files so this doesn't jam if there's a lot of output. // For the commands we use this for today, which only produce small output, it's OK. let _span = debug_span!("get_command_output", argv = ?argv).entered(); let mut child = Popen::create( argv, PopenConfig { stdin: Redirection::None, stdout: Redirection::Pipe, stderr: Redirection::Pipe, cwd: Some(cwd.as_os_str().to_owned()), ..Default::default() }, ) .with_context(|| format!("failed to spawn {argv:?}"))?; match child.wait_timeout(METADATA_TIMEOUT) { Err(e) => { let message = format!("failed to wait for {argv:?}: {e}"); return Err(anyhow!(message)); } Ok(None) => { let message = format!("{argv:?} timed out",); return Err(anyhow!(message)); } Ok(Some(status)) if status.success() => {} Ok(Some(status)) => { let mut stderr = String::new(); let _ = child .stderr .take() .expect("child has stderr") .read_to_string(&mut stderr); error!("child failed with status {status:?}: {stderr}"); let message = format!("{argv:?} failed with status {status:?}: {stderr}"); return Err(anyhow!(message)); } } let mut stdout = String::new(); child .stdout .take() .expect("child has stdout") .read_to_string(&mut stdout) .context("failed to read child stdout")?; debug!("output: {}", stdout.trim()); Ok(stdout) } cargo-mutants-23.10.0/src/scenario.rs000064400000000000000000000017511046102023000155170ustar 00000000000000// Copyright 2021-2023 Martin Pool use serde::Serialize; use std::fmt; use crate::Mutant; /// A scenario is either a freshening build in the source tree, a baseline test with no mutations, or a mutation test. #[derive(Clone, Eq, PartialEq, Debug, Serialize)] pub enum Scenario { /// Build in a copy of the source tree but with no mutations applied. Baseline, /// Build with a mutation applied. Mutant(Mutant), } impl fmt::Display for Scenario { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Scenario::Baseline => f.write_str("baseline"), Scenario::Mutant(mutant) => mutant.fmt(f), } } } impl Scenario { pub fn is_mutant(&self) -> bool { matches!(self, Scenario::Mutant { .. }) } pub fn log_file_name_base(&self) -> String { match self { Scenario::Baseline => "baseline".into(), Scenario::Mutant(mutant) => mutant.log_file_name_base(), } } } ././@LongLink00006440000000000000000000000152000000000000007771Lustar cargo-mutants-23.10.0/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snapcargo-mutants-23.10.0/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree000064400000000000000000000701151046102023000326570ustar 00000000000000--- 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::path -> &Utf8Path with &Default::default() src/build_dir.rs: replace BuildDir::copy -> Result with Ok(Default::default()) src/build_dir.rs: replace BuildDir::copy -> Result with Err(::anyhow::anyhow!("mutated!")) src/build_dir.rs: replace ::fmt -> fmt::Result with Ok(Default::default()) src/build_dir.rs: replace ::fmt -> fmt::Result with Err(::anyhow::anyhow!("mutated!")) src/build_dir.rs: replace copy_tree -> Result with Ok(Default::default()) src/build_dir.rs: replace copy_tree -> Result with Err(::anyhow::anyhow!("mutated!")) src/cargo.rs: replace ::name -> &str with "" src/cargo.rs: replace ::name -> &str with "xyzzy" src/cargo.rs: replace ::find_root -> Result with Ok(Default::default()) src/cargo.rs: replace ::find_root -> Result with Err(::anyhow::anyhow!("mutated!")) src/cargo.rs: replace ::top_source_files -> Result>> with Ok(vec![]) src/cargo.rs: replace ::top_source_files -> Result>> with Ok(vec![Arc::new(Default::default())]) src/cargo.rs: replace ::top_source_files -> Result>> with Err(::anyhow::anyhow!("mutated!")) src/cargo.rs: replace ::compose_argv -> Result> with Ok(vec![]) src/cargo.rs: replace ::compose_argv -> Result> with Ok(vec![String::new()]) src/cargo.rs: replace ::compose_argv -> Result> with Ok(vec!["xyzzy".into()]) src/cargo.rs: replace ::compose_argv -> Result> with Err(::anyhow::anyhow!("mutated!")) src/cargo.rs: replace ::compose_env -> Result> with Ok(vec![]) src/cargo.rs: replace ::compose_env -> Result> with Ok(vec![(String::new(), String::new())]) src/cargo.rs: replace ::compose_env -> Result> with Ok(vec![(String::new(), "xyzzy".into())]) src/cargo.rs: replace ::compose_env -> Result> with Ok(vec![("xyzzy".into(), String::new())]) src/cargo.rs: replace ::compose_env -> Result> with Ok(vec![("xyzzy".into(), "xyzzy".into())]) src/cargo.rs: replace ::compose_env -> Result> with Err(::anyhow::anyhow!("mutated!")) 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 rustflags -> String with String::new() src/cargo.rs: replace rustflags -> String with "xyzzy".into() src/cargo.rs: replace direct_package_sources -> Result> with Ok(vec![]) src/cargo.rs: replace direct_package_sources -> Result> with Ok(vec![Default::default()]) src/cargo.rs: replace direct_package_sources -> Result> with Err(::anyhow::anyhow!("mutated!")) src/cargo.rs: replace should_mutate_target -> bool with true src/cargo.rs: replace should_mutate_target -> bool with false 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/console.rs: replace Console::walk_tree_start with () src/console.rs: replace Console::walk_tree_update with () src/console.rs: replace Console::walk_tree_done with () src/console.rs: replace Console::scenario_started with () src/console.rs: replace Console::scenario_finished with () src/console.rs: replace Console::autoset_timeout with () src/console.rs: replace Console::start_copy with () src/console.rs: replace Console::finish_copy with () src/console.rs: replace Console::copy_progress with () src/console.rs: replace Console::discovered_mutants with () src/console.rs: replace Console::start_testing_mutants with () src/console.rs: replace Console::scenario_phase_started with () src/console.rs: replace Console::scenario_phase_finished with () src/console.rs: replace Console::lab_finished with () src/console.rs: replace Console::clear with () src/console.rs: replace Console::message with () src/console.rs: replace Console::tick with () src/console.rs: replace Console::make_terminal_writer -> TerminalWriter with Default::default() src/console.rs: replace Console::make_debug_log_writer -> DebugLogWriter with Default::default() src/console.rs: replace Console::set_debug_log with () src/console.rs: replace Console::setup_global_trace -> Result<()> with Ok(()) src/console.rs: replace Console::setup_global_trace -> Result<()> with Err(::anyhow::anyhow!("mutated!")) src/console.rs: replace ::make_writer -> Self::Writer with Default::default() src/console.rs: replace ::write -> std::io::Result with Ok(0) src/console.rs: replace ::write -> std::io::Result with Ok(1) src/console.rs: replace ::write -> std::io::Result with Err(::anyhow::anyhow!("mutated!")) src/console.rs: replace ::flush -> std::io::Result<()> with Ok(()) src/console.rs: replace ::flush -> std::io::Result<()> with Err(::anyhow::anyhow!("mutated!")) src/console.rs: replace ::make_writer -> Self::Writer with Default::default() src/console.rs: replace ::write -> io::Result with Ok(0) src/console.rs: replace ::write -> io::Result with Ok(1) src/console.rs: replace ::write -> io::Result with Err(::anyhow::anyhow!("mutated!")) src/console.rs: replace ::flush -> io::Result<()> with Ok(()) src/console.rs: replace ::flush -> io::Result<()> with Err(::anyhow::anyhow!("mutated!")) src/console.rs: replace ::render -> String with String::new() src/console.rs: replace ::render -> String with "xyzzy".into() src/console.rs: replace LabModel::find_scenario_mut -> &mut ScenarioModel with Box::leak(Box::new(Default::default())) src/console.rs: replace LabModel::remove_scenario with () src/console.rs: replace ::render -> String with String::new() src/console.rs: replace ::render -> String with "xyzzy".into() src/console.rs: replace ScenarioModel::phase_started with () src/console.rs: replace ScenarioModel::phase_finished with () src/console.rs: replace ::render -> String with String::new() src/console.rs: replace ::render -> String with "xyzzy".into() src/console.rs: replace CopyModel::bytes_copied with () src/console.rs: replace ::render -> String with String::new() src/console.rs: replace ::render -> String with "xyzzy".into() src/console.rs: replace nutmeg_options -> nutmeg::Options with Default::default() src/console.rs: replace style_outcome -> StyledObject<&'static str> with StyledObject::new() src/console.rs: replace style_outcome -> StyledObject<&'static str> with StyledObject::from_iter([""]) src/console.rs: replace style_outcome -> StyledObject<&'static str> with StyledObject::new("") src/console.rs: replace style_outcome -> StyledObject<&'static str> with StyledObject::from("") src/console.rs: replace style_outcome -> StyledObject<&'static str> with StyledObject::from_iter(["xyzzy"]) src/console.rs: replace style_outcome -> StyledObject<&'static str> with StyledObject::new("xyzzy") src/console.rs: replace style_outcome -> StyledObject<&'static str> with StyledObject::from("xyzzy") src/console.rs: replace style_mutant -> String with String::new() src/console.rs: replace style_mutant -> String with "xyzzy".into() src/console.rs: replace style_elapsed_secs -> String with String::new() src/console.rs: replace style_elapsed_secs -> String with "xyzzy".into() src/console.rs: replace style_secs -> String with String::new() src/console.rs: replace style_secs -> String with "xyzzy".into() src/console.rs: replace style_minutes_seconds -> String with String::new() src/console.rs: replace style_minutes_seconds -> String with "xyzzy".into() src/console.rs: replace duration_minutes_seconds -> String with String::new() src/console.rs: replace duration_minutes_seconds -> String with "xyzzy".into() src/console.rs: replace format_mb -> String with String::new() src/console.rs: replace format_mb -> String with "xyzzy".into() src/console.rs: replace style_mb -> StyledObject with StyledObject::new() src/console.rs: replace style_mb -> StyledObject with StyledObject::from_iter([String::new()]) src/console.rs: replace style_mb -> StyledObject with StyledObject::new(String::new()) src/console.rs: replace style_mb -> StyledObject with StyledObject::from(String::new()) src/console.rs: replace style_mb -> StyledObject with StyledObject::from_iter(["xyzzy".into()]) src/console.rs: replace style_mb -> StyledObject with StyledObject::new("xyzzy".into()) src/console.rs: replace style_mb -> StyledObject with StyledObject::from("xyzzy".into()) src/console.rs: replace style_scenario -> Cow<'static, str> with Cow::Borrowed("") src/console.rs: replace style_scenario -> Cow<'static, str> with Cow::Owned("".to_owned()) src/console.rs: replace style_scenario -> Cow<'static, str> with Cow::Borrowed("xyzzy") src/console.rs: replace style_scenario -> Cow<'static, str> with Cow::Owned("xyzzy".to_owned()) src/console.rs: replace plural -> String with String::new() src/console.rs: replace plural -> String with "xyzzy".into() src/fnvalue.rs: replace return_type_replacements -> impl Iterator with ::std::iter::empty() src/fnvalue.rs: replace return_type_replacements -> impl Iterator with ::std::iter::once(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 match_impl_iterator -> Option<&Type> with None src/fnvalue.rs: replace match_impl_iterator -> Option<&Type> with Some(&Default::default()) 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 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 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 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/interrupt.rs: replace install_handler with () src/lab.rs: replace test_unmutated_then_all_mutants -> Result with Ok(Default::default()) src/lab.rs: replace test_unmutated_then_all_mutants -> Result with Err(::anyhow::anyhow!("mutated!")) src/lab.rs: replace test_scenario -> Result with Ok(Default::default()) src/lab.rs: replace test_scenario -> Result with Err(::anyhow::anyhow!("mutated!")) 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 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 last_line -> Result with Ok(String::new()) src/log_file.rs: replace last_line -> Result with Ok("xyzzy".into()) src/log_file.rs: replace last_line -> Result with Err(::anyhow::anyhow!("mutated!")) 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 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/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::original_code -> &str with "" src/mutate.rs: replace Mutant::original_code -> &str with "xyzzy" src/mutate.rs: replace Mutant::return_type -> &str with "" src/mutate.rs: replace Mutant::return_type -> &str with "xyzzy" 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::replacement_text -> &str with "" src/mutate.rs: replace Mutant::replacement_text -> &str with "xyzzy" src/mutate.rs: replace Mutant::function_name -> &str with "" src/mutate.rs: replace Mutant::function_name -> &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(()) 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 ::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/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 build_glob_set -> Result> with Ok(None) src/options.rs: replace build_glob_set -> Result> with Ok(Some(Default::default())) src/options.rs: replace build_glob_set -> Result> with Err(::anyhow::anyhow!("mutated!")) 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 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 LabOutcome::summary_string -> String with String::new() src/outcome.rs: replace LabOutcome::summary_string -> String with "xyzzy".into() 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::should_show_logs -> bool with true src/outcome.rs: replace ScenarioOutcome::should_show_logs -> bool with false 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 ScenarioOutcome::mutant_caught -> bool with true src/outcome.rs: replace ScenarioOutcome::mutant_caught -> bool with false src/outcome.rs: replace ScenarioOutcome::mutant_missed -> bool with true src/outcome.rs: replace ScenarioOutcome::mutant_missed -> bool with false src/outcome.rs: replace ScenarioOutcome::summary -> SummaryOutcome with Default::default() 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 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 ::to_slash_path -> String with String::new() src/path.rs: replace ::to_slash_path -> String with "xyzzy".into() src/pretty.rs: replace ::to_pretty_string -> String with String::new() src/pretty.rs: replace ::to_pretty_string -> String with "xyzzy".into() 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 ProcessStatus::success -> bool with true src/process.rs: replace ProcessStatus::success -> bool with false src/process.rs: replace ProcessStatus::timeout -> bool with true src/process.rs: replace ProcessStatus::timeout -> 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/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::log_file_name_base -> String with String::new() src/scenario.rs: replace Scenario::log_file_name_base -> String with "xyzzy".into() 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/textedit.rs: replace ::from -> Self with Default::default() src/textedit.rs: replace ::from -> Self with Default::default() src/textedit.rs: replace ::from -> Self with Default::default() src/textedit.rs: replace replace_region -> String with String::new() src/textedit.rs: replace replace_region -> String with "xyzzy".into() 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 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>::collect_fn_mutants with () src/visit.rs: replace DiscoveryVisitor<'o>::in_namespace -> T with Default::default() src/visit.rs: replace >::visit_item_fn with () src/visit.rs: replace >::visit_impl_item_fn with () src/visit.rs: replace >::visit_item_impl with () src/visit.rs: replace >::visit_item_mod with () 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 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 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 cargo-mutants-23.10.0/src/source.rs000064400000000000000000000056111046102023000152130ustar 00000000000000// Copyright 2021-2023 Martin Pool //! Access to a Rust source tree and files. use std::sync::Arc; use anyhow::{Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; #[allow(unused_imports)] use tracing::{debug, info, warn}; use crate::path::Utf8PathSlashes; /// A Rust source file within a source tree. /// /// It can be viewed either relative to the source tree (for display) /// or as a path that can be opened (relative to cwd or absolute.) /// /// Code is normalized to Unix line endings as it's read in, and modified /// files are written with Unix line endings. #[derive(Debug, Eq, PartialEq)] pub struct SourceFile { /// Package within the workspace. pub package: Arc, /// Path relative to the root of the tree. pub tree_relative_path: Utf8PathBuf, /// Full copy of the source. pub code: Arc, } impl SourceFile { /// Construct a SourceFile representing a file within a tree. /// /// This eagerly loads the text of the file. pub fn new( tree_path: &Utf8Path, tree_relative_path: Utf8PathBuf, package: &Arc, ) -> Result { let full_path = tree_path.join(&tree_relative_path); let code = std::fs::read_to_string(&full_path) .with_context(|| format!("failed to read source of {full_path:?}"))? .replace("\r\n", "\n"); Ok(SourceFile { tree_relative_path, code: Arc::new(code), package: Arc::clone(package), }) } /// Return the path of this file relative to the tree root, with forward slashes. pub fn tree_relative_slashes(&self) -> String { self.tree_relative_path.to_slash_path() } } /// A package built and tested as a unit. #[derive(Debug, Eq, PartialEq, Hash, Clone)] pub struct Package { /// The short name of the package, like "mutants". pub name: String, /// For Cargo, the path of the `Cargo.toml` manifest file, relative to the top of the tree. pub relative_manifest_path: Utf8PathBuf, } #[cfg(test)] mod test { use std::fs::File; use std::io::Write; use pretty_assertions::assert_eq; use super::*; #[test] fn source_file_normalizes_crlf() { let temp_dir = tempfile::tempdir().unwrap(); let temp_dir_path = Utf8Path::from_path(temp_dir.path()).unwrap(); let file_name = "lib.rs"; File::create(temp_dir.path().join(file_name)) .unwrap() .write_all(b"fn main() {\r\n 640 << 10;\r\n}\r\n") .unwrap(); let source_file = SourceFile::new( temp_dir_path, file_name.parse().unwrap(), &Arc::new(Package { name: "imaginary-package".to_owned(), relative_manifest_path: "whatever/Cargo.toml".into(), }), ) .unwrap(); assert_eq!(*source_file.code, "fn main() {\n 640 << 10;\n}\n"); } } cargo-mutants-23.10.0/src/textedit.rs000064400000000000000000000077751046102023000155620ustar 00000000000000// Copyright 2021 Martin Pool //! Edit source code. use serde::Serialize; /// A (line, column) position in a source file. #[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize)] pub struct LineColumn { /// 1-based line number. pub line: usize, /// 1-based column, measured in chars. pub column: usize, } impl From for LineColumn { fn from(l: proc_macro2::LineColumn) -> Self { LineColumn { line: l.line, column: l.column + 1, } } } /// A contiguous text span in a file. /// /// TODO: Perhaps a semi-open range that can represent an empty span would be more general? #[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize)] pub struct Span { /// The inclusive position where the span starts. pub start: LineColumn, /// The inclusive position where the span ends. pub end: LineColumn, } impl From for Span { fn from(s: proc_macro2::Span) -> Self { Span { start: s.start().into(), end: s.end().into(), } } } impl From<&proc_macro2::Span> for Span { fn from(s: &proc_macro2::Span) -> Self { Span { start: s.start().into(), end: s.end().into(), } } } /// Replace a subregion of text. /// /// Returns a copy of `s` with the region between `start` and `end` inclusive replaced by /// `replacement`. pub(crate) fn replace_region( s: &str, start: &LineColumn, end: &LineColumn, replacement: &str, ) -> String { // dbg!(start, end); let mut r = String::with_capacity(s.len() + replacement.len()); let mut line_no = 1; let mut col_no = 1; for c in s.chars() { if line_no < start.line || line_no > end.line || (line_no == start.line && col_no < start.column) || (line_no == end.line && col_no > end.column) { r.push(c); } else if line_no == start.line && col_no == start.column { r.push_str(replacement); } if c == '\n' { line_no += 1; col_no = 1; } else if c == '\r' { // counts as part of the last column, not a separate column } else { col_no += 1; } } r } #[cfg(test)] mod test { use indoc::indoc; use pretty_assertions::assert_eq; use super::*; #[test] fn replace_treats_crlf_as_part_of_last_column() { let source = "fn foo() {\r\n wibble();\r\n}\r\n//hey!\r\n"; assert_eq!( replace_region( source, &LineColumn { line: 1, column: 10 }, &LineColumn { line: 3, column: 2 }, "{}\r\n" ), "fn foo() {}\r\n//hey!\r\n" ); } #[test] fn test_replace_region() { let source = indoc! { r#" fn foo() { some(); stuff(); } const BAR: u32 = 32; "# }; // typical multi-line case let replaced = replace_region( source, &LineColumn { line: 2, column: 10, }, &LineColumn { line: 5, column: 1 }, "{ /* body deleted */ }", ); assert_eq!( replaced, indoc! { r#" fn foo() { /* body deleted */ } const BAR: u32 = 32; "# } ); // single-line case let replaced = replace_region( source, &LineColumn { line: 7, column: 18, }, &LineColumn { line: 7, column: 19, }, "69", ); assert_eq!( replaced, indoc! { r#" fn foo() { some(); stuff(); } const BAR: u32 = 69; "# } ); } } cargo-mutants-23.10.0/src/tool.rs000064400000000000000000000036421046102023000146720ustar 00000000000000// Copyright 2023 Martin Pool. //! A generic interface for running a tool such as Cargo that can determine the tree //! shape, build it, and run tests. //! //! At present only Cargo is supported, but this interface aims to leave a place to //! support for example Bazel in future. use std::fmt::Debug; use std::marker::{Send, Sync}; use std::sync::Arc; use camino::{Utf8Path, Utf8PathBuf}; #[allow(unused_imports)] use tracing::{debug, debug_span, trace}; use crate::options::Options; use crate::outcome::Phase; use crate::source::Package; use crate::SourceFile; use crate::{build_dir, Result}; pub trait Tool: Debug + Send + Sync { /// A short name for this tool, like "cargo". fn name(&self) -> &str; /// Find the root of the source tree enclosing a given path. /// /// The root is the enclosing directory that needs to be copied to make a self-contained /// scratch directory, and from where source discovery begins. /// /// This may include more directories than will actually be tested, sufficient to allow /// the build to work. For Cargo, we copy the whole workspace. fn find_root(&self, path: &Utf8Path) -> Result; /// Find the top-level files for each package within a tree. /// /// The path is the root returned by [find_root]. /// /// For Cargo, this is files like `src/bin/*.rs`, `src/lib.rs` identified by targets /// in the manifest for each package. /// /// From each of these top files, we can discover more source by following `mod` /// statements. fn top_source_files(&self, path: &Utf8Path) -> Result>>; /// Compose argv to run one phase in this tool. fn compose_argv( &self, build_dir: &build_dir::BuildDir, packages: Option<&[&Package]>, phase: Phase, options: &Options, ) -> Result>; fn compose_env(&self) -> Result>; } cargo-mutants-23.10.0/src/visit.rs000064400000000000000000000353001046102023000150470ustar 00000000000000// Copyright 2021-2023 Martin Pool //! Visit all the files in a source tree, and then the AST of each file, //! to discover mutation opportunities. //! //! Walking the tree starts with some root files known to the build tool: //! e.g. for cargo they are identified from the targets. The tree walker then //! follows `mod` statements to recursively visit other referenced files. use std::collections::VecDeque; use std::sync::Arc; use anyhow::Context; use itertools::Itertools; use syn::ext::IdentExt; use syn::visit::Visit; use syn::{Attribute, Expr, ItemFn, ReturnType}; use tracing::{debug, debug_span, trace, trace_span, warn}; use crate::fnvalue::return_type_replacements; use crate::pretty::ToPrettyString; use crate::source::SourceFile; use crate::*; /// Mutants and files discovered in a source tree. /// /// Files are listed separately so that we can represent files that /// were visited but that produced no mutants. pub struct Discovered { pub mutants: Vec, pub files: Vec>, } /// Discover all mutants and all source files. /// /// The list of source files includes even those with no mutants. pub fn walk_tree( tool: &dyn Tool, root: &Utf8Path, options: &Options, console: &Console, ) -> Result { let error_exprs = options .error_values .iter() .map(|e| syn::parse_str(e).with_context(|| format!("Failed to parse error value {e:?}"))) .collect::>>()?; console.walk_tree_start(); let mut mutants = Vec::new(); let mut files: Vec> = Vec::new(); let mut file_queue: VecDeque> = tool.top_source_files(root)?.into(); while let Some(source_file) = file_queue.pop_front() { console.walk_tree_update(files.len(), mutants.len()); check_interrupted()?; let (mut file_mutants, external_mods) = walk_file(Arc::clone(&source_file), &error_exprs)?; // We'll still walk down through files that don't match globs, so that // we have a chance to find modules underneath them. However, we won't // collect any mutants from them, and they don't count as "seen" for // `--list-files`. for mod_name in &external_mods { if let Some(mod_path) = find_mod_source(root, &source_file, mod_name)? { file_queue.push_back(Arc::new(SourceFile::new( root, mod_path, &source_file.package, )?)) } } let path = &source_file.tree_relative_path; if let Some(examine_globset) = &options.examine_globset { if !examine_globset.is_match(path) { trace!("{path:?} does not match examine globset"); continue; } } if let Some(exclude_globset) = &options.exclude_globset { if exclude_globset.is_match(path) { trace!("{path:?} excluded by globset"); continue; } } if let Some(examine_names) = &options.examine_names { if !examine_names.is_empty() { file_mutants.retain(|m| examine_names.is_match(&m.to_string())); } } if let Some(exclude_names) = &options.exclude_names { if !exclude_names.is_empty() { file_mutants.retain(|m| !exclude_names.is_match(&m.to_string())); } } mutants.append(&mut file_mutants); files.push(source_file); } console.walk_tree_done(); Ok(Discovered { mutants, files }) } /// Find all possible mutants in a source file. /// /// Returns the mutants found, and the names of modules referenced by `mod` statements /// that should be visited later. fn walk_file( source_file: Arc, error_exprs: &[Expr], ) -> Result<(Vec, Vec)> { let _span = debug_span!("source_file", path = source_file.tree_relative_slashes()).entered(); debug!("visit source file"); let syn_file = syn::parse_str::(&source_file.code) .with_context(|| format!("failed to parse {}", source_file.tree_relative_slashes()))?; let mut visitor = DiscoveryVisitor { error_exprs, external_mods: Vec::new(), mutants: Vec::new(), namespace_stack: Vec::new(), source_file: source_file.clone(), }; visitor.visit_file(&syn_file); Ok((visitor.mutants, visitor.external_mods)) } /// `syn` visitor that recursively traverses the syntax tree, accumulating places /// that could be mutated. /// /// As it walks the tree, it accumulates within itself a list of mutation opportunities, /// and other files referenced by `mod` statements that should be visited later. struct DiscoveryVisitor<'o> { /// All the mutants generated by visiting the file. mutants: Vec, /// The file being visited. source_file: Arc, /// The stack of namespaces we're currently inside. namespace_stack: Vec, /// The names from `mod foo;` statements that should be visited later. external_mods: Vec, /// Parsed error expressions, from the config file or command line. error_exprs: &'o [Expr], } impl<'o> DiscoveryVisitor<'o> { fn collect_fn_mutants(&mut self, return_type: &ReturnType, span: &proc_macro2::Span) { let full_function_name = Arc::new(self.namespace_stack.join("::")); let return_type_str = Arc::new(return_type.to_pretty_string()); let mut new_mutants = return_type_replacements(return_type, self.error_exprs) .map(|rep| Mutant { source_file: Arc::clone(&self.source_file), function_name: Arc::clone(&full_function_name), return_type: Arc::clone(&return_type_str), replacement: rep.to_pretty_string(), span: span.into(), genre: Genre::FnValue, }) .collect_vec(); if new_mutants.is_empty() { debug!( ?full_function_name, ?return_type_str, "No mutants generated for this return type" ); } else { self.mutants.append(&mut new_mutants); } } /// Call a function with a namespace pushed onto the stack. /// /// This is used when recursively descending into a namespace. fn in_namespace(&mut self, name: &str, f: F) -> T where F: FnOnce(&mut Self) -> T, { self.namespace_stack.push(name.to_owned()); let r = f(self); assert_eq!(self.namespace_stack.pop().unwrap(), name); r } } impl<'ast> Visit<'ast> for DiscoveryVisitor<'_> { /// Visit top-level `fn foo()`. fn visit_item_fn(&mut self, i: &'ast ItemFn) { let function_name = i.sig.ident.to_pretty_string(); let _span = trace_span!( "fn", line = i.sig.fn_token.span.start().line, name = function_name ) .entered(); if fn_sig_excluded(&i.sig) || attrs_excluded(&i.attrs) || block_is_empty(&i.block) { return; } self.in_namespace(&function_name, |self_| { self_.collect_fn_mutants(&i.sig.output, &i.block.brace_token.span.join()); syn::visit::visit_item_fn(self_, i); }); } /// Visit `fn foo()` within an `impl`. fn visit_impl_item_fn(&mut self, i: &'ast syn::ImplItemFn) { // Don't look inside constructors (called "new") because there's often no good // alternative. let function_name = i.sig.ident.to_pretty_string(); let _span = trace_span!( "fn", line = i.sig.fn_token.span.start().line, name = function_name ) .entered(); if fn_sig_excluded(&i.sig) || attrs_excluded(&i.attrs) || i.sig.ident == "new" || block_is_empty(&i.block) { return; } self.in_namespace(&function_name, |self_| { self_.collect_fn_mutants(&i.sig.output, &i.block.brace_token.span.join()); syn::visit::visit_impl_item_fn(self_, i) }); } /// Visit `impl Foo { ...}` or `impl Debug for Foo { ... }`. fn visit_item_impl(&mut self, i: &'ast syn::ItemImpl) { if attrs_excluded(&i.attrs) { return; } let type_name = i.self_ty.to_pretty_string(); let name = if let Some((_, trait_path, _)) = &i.trait_ { let trait_name = &trait_path.segments.last().unwrap().ident; if trait_name == "Default" { // Can't think of how to generate a viable different default. return; } format!("") } else { type_name }; self.in_namespace(&name, |v| syn::visit::visit_item_impl(v, i)); } /// Visit `mod foo { ... }` or `mod foo;`. fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) { let mod_name = &node.ident.unraw().to_string(); let _span = trace_span!("mod", line = node.mod_token.span.start().line, mod_name).entered(); if attrs_excluded(&node.attrs) { trace!("mod excluded by attrs"); return; } // If there's no content in braces, then this is a `mod foo;` // statement referring to an external file. We remember the module // name and then later look for the file. if node.content.is_none() { self.external_mods.push(mod_name.to_owned()); } self.in_namespace(mod_name, |v| syn::visit::visit_item_mod(v, node)); } } /// Find a new source file referenced by a `mod` statement. /// /// Possibly, our heuristics just won't be able to find which file it is, /// in which case we return `Ok(None)`. fn find_mod_source( tree_root: &Utf8Path, parent: &SourceFile, mod_name: &str, ) -> Result> { // Both the current module and the included sub-module can be in // either style: `.../foo.rs` or `.../foo/mod.rs`. // // If the current file ends with `/mod.rs`, then sub-modules // will be in the same directory as this file. Otherwise, this is // `/foo.rs` and sub-modules will be in `foo/`. // // Having determined the directory then we can look for either // `foo.rs` or `foo/mod.rs`. let parent_path = &parent.tree_relative_path; // TODO: Maybe matching on the name here is not the right approach and // we should instead remember how this file was found? This might go wrong // with unusually-named files. let dir = if parent_path.ends_with("mod.rs") || parent_path.ends_with("lib.rs") || parent_path.ends_with("main.rs") { parent_path .parent() .expect("mod path has no parent") .to_owned() } else { parent_path.with_extension("") }; let mut tried_paths = Vec::new(); for &tail in &[".rs", "/mod.rs"] { let relative_path = dir.join(mod_name.to_owned() + tail); let full_path = tree_root.join(&relative_path); if full_path.is_file() { trace!("found submodule in {full_path}"); return Ok(Some(relative_path)); } else { tried_paths.push(full_path); } } warn!(?parent_path, %mod_name, ?tried_paths, "referent of mod not found"); Ok(None) } /// True if the signature of a function is such that it should be excluded. fn fn_sig_excluded(sig: &syn::Signature) -> bool { if sig.unsafety.is_some() { trace!("Skip unsafe fn"); true } else { false } } /// True if any of the attrs indicate that we should skip this node and everything inside it. fn attrs_excluded(attrs: &[Attribute]) -> bool { attrs .iter() .any(|attr| attr_is_cfg_test(attr) || attr_is_test(attr) || attr_is_mutants_skip(attr)) } /// True if the block (e.g. the contents of a function) is empty. fn block_is_empty(block: &syn::Block) -> bool { block.stmts.is_empty() } /// True if the attribute looks like `#[cfg(test)]`, or has "test" /// anywhere in it. fn attr_is_cfg_test(attr: &Attribute) -> bool { if !path_is(attr.path(), &["cfg"]) { return false; } let mut contains_test = false; if let Err(err) = attr.parse_nested_meta(|meta| { if meta.path.is_ident("test") { contains_test = true; } Ok(()) }) { debug!( ?err, ?attr, "Attribute is not in conventional form; skipped" ); return false; } contains_test } /// True if the attribute is `#[test]`. fn attr_is_test(attr: &Attribute) -> bool { attr.path().is_ident("test") } fn path_is(path: &syn::Path, idents: &[&str]) -> bool { path.segments.iter().map(|ps| &ps.ident).eq(idents.iter()) } /// True if the attribute contains `mutants::skip`. /// /// This for example returns true for `#[mutants::skip] or `#[cfg_attr(test, mutants::skip)]`. fn attr_is_mutants_skip(attr: &Attribute) -> bool { if path_is(attr.path(), &["mutants", "skip"]) { return true; } if !path_is(attr.path(), &["cfg_attr"]) { return false; } let mut skip = false; if let Err(err) = attr.parse_nested_meta(|meta| { if path_is(&meta.path, &["mutants", "skip"]) { skip = true } Ok(()) }) { debug!( ?attr, ?err, "Attribute is not a path with attributes; skipping" ); return false; } skip } #[cfg(test)] mod test { use regex::Regex; use crate::cargo::CargoTool; use super::*; /// 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 options = Options { error_values: vec!["::anyhow::anyhow!(\"mutated!\")".to_owned()], ..Default::default() }; let mut list_output = String::new(); crate::list_mutants( &mut list_output, &CargoTool::new(), &Utf8Path::new(".") .canonicalize_utf8() .expect("Canonicalize source path"), &options, &Console::new(), ) .expect("Discover mutants in own source tree"); // Strip line numbers so this is not too brittle. let line_re = Regex::new(r"(?m)^([^:]+:)\d+:( .*)$").unwrap(); let list_output = line_re.replace_all(&list_output, "$1$2"); insta::assert_snapshot!(list_output); } } cargo-mutants-23.10.0/tests/cli/config.rs000064400000000000000000000111151046102023000162760ustar 00000000000000// Copyright 2022 Martin Pool. //! Test handling of `mutants.toml` configuration. use std::fs::{create_dir, write}; use predicates::prelude::*; use tempfile::TempDir; use super::{copy_of_testdata, run}; fn write_config_file(tempdir: &TempDir, config: &str) { let path = tempdir.path(); // This will error if it exists, which today it never will, // but perhaps later we should ignore that. create_dir(path.join(".cargo")).unwrap(); write(path.join(".cargo/mutants.toml"), config.as_bytes()).unwrap(); } #[test] fn invalid_toml_rejected() { let testdata = copy_of_testdata("well_tested"); write_config_file( &testdata, r#"what even is this? "#, ); run() .args(["mutants", "--list-files", "-d"]) .arg(testdata.path()) .assert() .failure() .stderr(predicates::str::contains("Error: parse toml from ")); } #[test] fn invalid_field_rejected() { let testdata = copy_of_testdata("well_tested"); write_config_file( &testdata, r#"wobble = false "#, ); run() .args(["mutants", "--list-files", "-d"]) .arg(testdata.path()) .assert() .failure() .stderr( predicates::str::contains("Error: parse toml from ") .and(predicates::str::contains("unknown field `wobble`")), ); } #[test] fn list_with_config_file_exclusion() { let testdata = copy_of_testdata("well_tested"); write_config_file( &testdata, r#"exclude_globs = ["src/*_mod.rs"] "#, ); run() .args(["mutants", "--list-files", "-d"]) .arg(testdata.path()) .assert() .success() .stdout(predicates::str::contains("_mod.rs").not()); run() .args(["mutants", "--list", "-d"]) .arg(testdata.path()) .assert() .success() .stdout(predicates::str::contains("_mod.rs").not()); } #[test] fn list_with_config_file_inclusion() { let testdata = copy_of_testdata("well_tested"); write_config_file( &testdata, r#"examine_globs = ["src/*_mod.rs"] "#, ); run() .args(["mutants", "--list-files", "-d"]) .arg(testdata.path()) .assert() .success() .stdout(predicates::str::diff( "src/inside_mod.rs src/item_mod.rs\n", )); run() .args(["mutants", "--list", "-d"]) .arg(testdata.path()) .assert() .success() .stdout(predicates::str::contains("simple_fns.rs").not()); } #[test] fn list_with_config_file_regexps() { let testdata = copy_of_testdata("well_tested"); write_config_file( &testdata, r#" # comments are ok examine_re = ["divisible"] exclude_re = ["-> bool with true"] "#, ); run() .args(["mutants", "--list", "-d"]) .arg(testdata.path()) .assert() .success() .stdout(predicates::str::diff( "src/simple_fns.rs:17: replace divisible_by_three -> bool with false\n", )); } #[test] fn tree_fails_without_needed_feature() { // The point of this tree is to check that Cargo features can be turned on, // but let's make sure it does fail as intended if they're not. let testdata = copy_of_testdata("fails_without_feature"); run() .args(["mutants", "-d"]) .arg(testdata.path()) .assert() .failure() .stdout(predicates::str::contains( "test failed in an unmutated tree", )); } #[test] fn additional_cargo_args() { // The point of this tree is to check that Cargo features can be turned on, // but let's make sure it does fail as intended if they're not. let testdata = copy_of_testdata("fails_without_feature"); write_config_file( &testdata, r#" additional_cargo_args = ["--features", "needed"] "#, ); run() .args(["mutants", "-d"]) .arg(testdata.path()) .assert() .success() .stdout(predicates::str::contains("2 caught")); } #[test] fn additional_cargo_test_args() { // The point of this tree is to check that Cargo features can be turned on, // but let's make sure it does fail as intended if they're not. let testdata = copy_of_testdata("fails_without_feature"); write_config_file( &testdata, r#" additional_cargo_test_args = ["--all-features", ] "#, ); run() .args(["mutants", "-d"]) .arg(testdata.path()) .assert() .success() .stdout(predicates::str::contains("2 caught")); } cargo-mutants-23.10.0/tests/cli/error_value.rs000064400000000000000000000067261046102023000173720ustar 00000000000000// Copyright 2023 Martin Pool //! Tests for error value mutations, from `--error-value` etc. use std::env; use indoc::indoc; use predicates::prelude::*; use super::{copy_of_testdata, run}; #[test] fn error_value_catches_untested_ok_case() { // By default this tree should fail because it's configured to // generate an error value, and the tests forgot to check that // the code under test does return Ok. let tmp_src_dir = copy_of_testdata("error_value"); run() .arg("mutants") .args(["-v", "-V", "--no-times", "--no-shuffle"]) .arg("-d") .arg(tmp_src_dir.path()) .assert() .code(2) .stderr("") .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); } #[test] fn no_config_option_disables_config_file_so_error_value_is_not_generated() { // In this case, the config file is not loaded. Error values are not // generated by default (because we don't know what a good value for // this tree would be), so no mutants are caught. let tmp_src_dir = copy_of_testdata("error_value"); run() .arg("mutants") .args(["-v", "-V", "--no-times", "--no-shuffle", "--no-config"]) .arg("-d") .arg(tmp_src_dir.path()) .assert() .code(0) .stderr("") .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); } #[test] fn list_mutants_with_error_value_from_command_line_list() { // This is not a good error mutant for this tree, which uses // anyhow, but it's a good test of the command line option. let tmp_src_dir = copy_of_testdata("error_value"); run() .arg("mutants") .args([ "--no-times", "--no-shuffle", "--no-config", "--list", "--error=::eyre::eyre!(\"mutant\")", ]) .arg("-d") .arg(tmp_src_dir.path()) .assert() .code(0) .stderr("") .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); } #[test] fn warn_if_error_value_starts_with_err() { // Users might misunderstand what should be passed to --error, // so give a warning. let tmp_src_dir = copy_of_testdata("error_value"); run() .arg("mutants") .args([ "--no-times", "--no-shuffle", "--no-config", "--list", "--error=Err(anyhow!(\"mutant\"))", ]) .arg("-d") .arg(tmp_src_dir.path()) .assert() .code(0) .stderr(predicate::str::is_empty()) .stdout(predicate::str::contains( "error_value option gives the value of the error, and probably should not start with Err(: got Err(anyhow!(\"mutant\"))" )); } #[test] fn fail_when_error_value_does_not_parse() { let tmp_src_dir = copy_of_testdata("error_value"); run() .arg("mutants") .args([ "--no-times", "--no-shuffle", "--no-config", "--list", "--error=shouldn't work", ]) .arg("-d") .arg(tmp_src_dir.path()) .assert() .code(1) .stderr(predicate::str::contains(indoc! { " Error: Failed to parse error value \"shouldn\'t work\" Caused by: unexpected token "})) .stdout(predicate::str::is_empty()); } cargo-mutants-23.10.0/tests/cli/jobs.rs000064400000000000000000000010611046102023000157650ustar 00000000000000// Copyright 2022-2023 Martin Pool. //! Test handling of `--jobs` concurrency option. use super::{copy_of_testdata, run}; /// It's a bit hard to assess that multiple jobs really ran in parallel, /// but we can at least check that the option is accepted. #[test] fn jobs_option_accepted() { let testdata = copy_of_testdata("well_tested"); run() .arg("mutants") .arg("-d") .arg(testdata.path()) .arg("-j3") .arg("--minimum-test-timeout=120") // to avoid flakes on slow CI .assert() .success(); } cargo-mutants-23.10.0/tests/cli/main.rs000064400000000000000000001346421046102023000157700ustar 00000000000000// Copyright 2021-2023 Martin Pool //! Tests for cargo-mutants CLI layer. use std::env; use std::fmt::Write; use std::fs::{self, read_dir, read_to_string}; use std::io::Read; use std::path::{Path, PathBuf}; use std::thread::sleep; use std::time::Duration; use indoc::indoc; use itertools::Itertools; use lazy_static::lazy_static; use path_slash::PathBufExt; use predicate::str::{contains, is_match}; use predicates::prelude::*; use pretty_assertions::assert_eq; use regex::Regex; use subprocess::{Popen, PopenConfig, Redirection}; use tempfile::{tempdir, TempDir}; mod config; mod error_value; mod jobs; mod trace; #[cfg(windows)] mod windows; /// A timeout for a `cargo mutants` invocation from the test suite. Needs to be /// long enough that even commands that do a lot of work can pass even on slow /// CI VMs and even on Windows, but short enough that the test does not hang /// forever. const OUTER_TIMEOUT: Duration = Duration::from_secs(60); lazy_static! { static ref MAIN_BINARY: PathBuf = assert_cmd::cargo::cargo_bin("cargo-mutants"); static ref DURATION_RE: Regex = Regex::new(r"(\d+\.\d{1,3}s|\d+:\d{2})").unwrap(); static ref SIZE_RE: Regex = Regex::new(r"\d+ MB").unwrap(); } fn run() -> assert_cmd::Command { let mut cmd = assert_cmd::Command::new(MAIN_BINARY.as_os_str()); // Strip any options configured in the environment running these tests, // so that they don't cause unexpected behavior in the code under test. // // For example, without this, // `env CARGO_MUTANTS_JOBS=4 cargo mutants` // // would end up with tests running 4 jobs by default, which would cause // the tests to fail. // // Even more generally than that example, we want the tests to be as hermetic // as reasonably possible. env::vars() .map(|(k, _v)| k) .filter(|k| k.starts_with("CARGO_MUTANTS_")) .for_each(|k| { cmd.env_remove(k); }); cmd } trait CommandInstaExt { fn assert_insta(&mut self, snapshot_name: &str); } impl CommandInstaExt for assert_cmd::Command { fn assert_insta(&mut self, snapshot_name: &str) { let output = self.output().expect("command completes"); assert!(output.status.success()); insta::assert_snapshot!(snapshot_name, String::from_utf8_lossy(&output.stdout)); assert_eq!(&String::from_utf8_lossy(&output.stderr), ""); } } // Copy the source because output is written into mutants.out. fn copy_of_testdata(tree_name: &str) -> TempDir { let tmp_src_dir = tempdir().unwrap(); cp_r::CopyOptions::new() .filter(|path, _stat| { Ok(["target", "mutants.out", "mutants.out.old"] .iter() .all(|p| !path.starts_with(p))) }) .copy_tree(Path::new("testdata/tree").join(tree_name), &tmp_src_dir) .unwrap(); tmp_src_dir } /// Remove anything that looks like a duration or tree size, since they'll be unpredictable. fn redact_timestamps_sizes(s: &str) -> String { // TODO: Maybe match the number of digits? let s = DURATION_RE.replace_all(s, "x.xxxs"); SIZE_RE.replace_all(&s, "xxx MB").to_string() } #[test] fn incorrect_cargo_subcommand() { // argv[1] "mutants" is missing here. run().arg("wibble").assert().code(1); } #[test] fn missing_cargo_subcommand() { // argv[1] "mutants" is missing here. run().assert().code(1); } #[test] fn option_in_place_of_cargo_subcommand() { // argv[1] "mutants" is missing here. run().args(["--list"]).assert().code(1); } #[test] fn show_version() { run() .args(["mutants", "--version"]) .assert() .success() .stdout(predicates::str::is_match(r"^cargo-mutants \d+\.\d+\.\d+(-.*)?\n$").unwrap()); } #[test] fn uses_cargo_env_var_to_run_cargo_so_invalid_value_fails() { let tmp_src_dir = copy_of_testdata("well_tested"); let bogus_cargo = "NOTHING_NONEXISTENT_VOID"; run() .env("CARGO", bogus_cargo) .args(["mutants", "-d"]) .arg(tmp_src_dir.path()) .assert() .stderr( predicates::str::contains("No such file or directory") .or(predicates::str::contains( "The system cannot find the file specified", )) .or( predicates::str::contains("program not found"), /* Windows */ ), ) .code(1); // TODO: Preferably there would be a more specific exit code for the // clean build failing. } #[test] fn list_diff_json_contains_diffs() { let cmd = run() .args([ "mutants", "--list", "--json", "--diff", "-d", "testdata/tree/factorial", ]) .assert() .success(); // needed for lifetime let out = cmd.get_output(); assert_eq!(String::from_utf8_lossy(&out.stderr), ""); println!("{}", String::from_utf8_lossy(&out.stdout)); let out_json = serde_json::from_slice::(&out.stdout).unwrap(); let mutants_json = out_json.as_array().expect("json output is array"); assert_eq!(mutants_json.len(), 3); assert!(mutants_json.iter().all(|e| e.as_object().unwrap()["diff"] .as_str() .unwrap() .contains("--- src/bin/factorial.rs"))); } /// Return paths to all testdata trees, in order, excluding leftover git /// detritus with no Cargo.toml. fn all_testdata_tree_paths() -> Vec { let mut paths: Vec = fs::read_dir("testdata/tree") .unwrap() .map(|r| r.unwrap()) .filter(|dir_entry| dir_entry.file_type().unwrap().is_dir()) .filter(|dir_entry| dir_entry.file_name() != "parse_fails") .map(|dir_entry| dir_entry.path()) .filter(|dir_path| dir_path.join("Cargo.toml").exists()) .collect(); paths.sort(); paths } #[test] fn list_mutants_in_all_trees_as_json() { // The snapshot accumulated here is actually a big text file // containing JSON fragments. This might seem a bit weird for easier // review I want just a single snapshot, and json-inside-json has quoting // that makes it harder to review. let mut buf = String::new(); for dir_path in all_testdata_tree_paths() { writeln!(buf, "## {}\n", dir_path.to_slash_lossy()).unwrap(); let cmd_assert = run() .arg("mutants") .arg("--list") .arg("--json") .current_dir(&dir_path) .timeout(OUTER_TIMEOUT) .assert() .success(); let json_str = String::from_utf8_lossy(&cmd_assert.get_output().stdout); writeln!(buf, "```json\n{json_str}\n```\n").unwrap(); } insta::assert_snapshot!(buf); } #[test] fn list_mutants_in_all_trees_as_text() { let mut buf = String::new(); for dir_path in all_testdata_tree_paths() { writeln!(buf, "## {}\n\n```", dir_path.to_slash_lossy()).unwrap(); let cmd_assert = run() .arg("mutants") .arg("--list") .current_dir(&dir_path) .timeout(OUTER_TIMEOUT) .assert() .success(); buf.push_str(&String::from_utf8_lossy(&cmd_assert.get_output().stdout)); buf.push_str("```\n\n"); } insta::assert_snapshot!(buf); } #[test] fn list_mutants_in_factorial() { run() .arg("mutants") .arg("--list") .current_dir("testdata/tree/factorial") .assert_insta("list_mutants_in_factorial"); } #[test] fn list_mutants_in_factorial_json() { run() .arg("mutants") .arg("--list") .arg("--json") .current_dir("testdata/tree/factorial") .assert_insta("list_mutants_in_factorial_json"); } #[test] fn list_mutants_in_cfg_attr_mutants_skip() { let tmp_src_dir = copy_of_testdata("cfg_attr_mutants_skip"); run() .arg("mutants") .arg("--list") .current_dir(tmp_src_dir.path()) .assert_insta("list_mutants_in_cfg_attr_mutants_skip"); } #[test] fn list_mutants_in_cfg_attr_mutants_skip_json() { let tmp_src_dir = copy_of_testdata("cfg_attr_mutants_skip"); run() .arg("mutants") .arg("--list") .arg("--json") .current_dir(tmp_src_dir.path()) .assert_insta("list_mutants_in_cfg_attr_mutants_skip_json"); } #[test] fn list_mutants_in_cfg_attr_test_skip() { let tmp_src_dir = copy_of_testdata("cfg_attr_test_skip"); run() .arg("mutants") .arg("--list") .current_dir(tmp_src_dir.path()) .assert_insta("list_mutants_in_cfg_attr_test_skip"); } #[test] fn list_mutants_in_cfg_attr_test_skip_json() { let tmp_src_dir = copy_of_testdata("cfg_attr_test_skip"); run() .arg("mutants") .arg("--list") .arg("--json") .current_dir(tmp_src_dir.path()) .assert_insta("list_mutants_in_cfg_attr_test_skip_json"); } #[test] fn list_mutants_with_dir_option() { run() .arg("mutants") .arg("--list") .arg("--dir") .arg("testdata/tree/factorial") .assert_insta("list_mutants_with_dir_option"); } #[test] fn list_mutants_with_diffs_in_factorial() { run() .arg("mutants") .arg("--list") .arg("--diff") .current_dir("testdata/tree/factorial") .assert_insta("list_mutants_with_diffs_in_factorial"); } #[test] fn list_mutants_well_tested() { run() .arg("mutants") .arg("--list") .current_dir("testdata/tree/well_tested") .assert_insta("list_mutants_well_tested"); } #[test] fn list_mutants_well_tested_examine_name_filter() { run() .arg("mutants") .args(["--list", "--file", "nested_function.rs"]) .current_dir("testdata/tree/well_tested") .assert_insta("list_mutants_well_tested_examine_name_filter"); } #[test] fn list_mutants_well_tested_exclude_name_filter() { run() .arg("mutants") .args(["--list", "--exclude", "simple_fns.rs"]) .current_dir("testdata/tree/well_tested") .assert_insta("list_mutants_well_tested_exclude_name_filter"); } #[test] fn list_mutants_well_tested_exclude_folder_filter() { run() .arg("mutants") .args(["--list", "--exclude", "*/module/*"]) .current_dir("testdata/tree/with_child_directories") .assert_insta("list_mutants_well_tested_exclude_folder_filter"); } #[test] fn list_mutants_well_tested_examine_and_exclude_name_filter_combined() { run() .arg("mutants") .args([ "--list", "--file", "src/module/utils/*.rs", "--exclude", "nested_function.rs", ]) .current_dir("testdata/tree/with_child_directories") .assert_insta("list_mutants_well_tested_examine_and_exclude_name_filter_combined"); } #[test] fn list_mutants_regex_filters() { run() .arg("mutants") .args(["--list", "--re", "divisible"]) .arg("-d") .arg("testdata/tree/well_tested") .assert_insta("list_mutants_regex_filters"); } #[test] fn list_mutants_regex_anchored_matches_full_line() { run() .arg("mutants") .args([ "--list", "--re", r"^src/simple_fns.rs:\d+: replace returns_unit with \(\)$", ]) .arg("-d") .arg("testdata/tree/well_tested") .assert_insta("list_mutants_regex_anchored_matches_full_line"); } #[test] fn list_mutants_regex_filters_json() { run() .arg("mutants") .args([ "--list", "--re", "divisible", "--exclude-re", "false", "--json", ]) .arg("-d") .arg("testdata/tree/well_tested") .assert_insta("list_mutants_regex_filters_json"); } #[test] fn tree_with_child_directories_is_well_tested() { let tmp_src_dir = copy_of_testdata("with_child_directories"); run() .arg("mutants") .arg("-d") .arg(tmp_src_dir.path()) .assert() .success(); } #[test] fn list_mutants_well_tested_multiple_examine_and_exclude_name_filter_with_files_and_folders() { run() .arg("mutants") .args(["--list", "--file", "module_methods.rs", "--file", "*/utils/*", "--exclude", "*/sub_utils/*", "--exclude", "nested_function.rs"]) .current_dir("testdata/tree/with_child_directories") .assert_insta("list_mutants_well_tested_multiple_examine_and_exclude_name_filter_with_files_and_folders"); } #[test] fn list_mutants_json_well_tested() { run() .arg("mutants") .arg("--list") .arg("--json") .current_dir("testdata/tree/well_tested") .assert_insta("list_mutants_json_well_tested"); } #[test] fn list_files_text_well_tested() { run() .arg("mutants") .arg("--list-files") .current_dir("testdata/tree/well_tested") .assert_insta("list_files_text_well_tested"); } #[test] fn list_files_respects_file_filters() { // Files matching excludes *are* visited to find references to other modules, // but they're not included in --list-files. run() .arg("mutants") .args(["--list-files", "--exclude", "lib.rs"]) .current_dir("testdata/tree/well_tested") .assert() .success() .stdout(predicate::str::contains("methods.rs")) .stdout(predicate::str::contains("lib.rs").not()); } #[test] fn list_files_json_well_tested() { run() .arg("mutants") .arg("--list-files") .arg("--json") .current_dir("testdata/tree/well_tested") .assert_insta("list_files_json_well_tested"); } #[test] fn list_files_json_workspace() { // Demonstrates that we get package names in the json listing. run() .args(["mutants", "--list-files", "--json"]) .current_dir("testdata/tree/workspace") .assert_insta("list_files_json_workspace"); } #[test] fn list_files_as_json_in_workspace_subdir() { run() .args(["mutants", "--list-files", "--json"]) .current_dir("testdata/tree/workspace/main2") .assert() .stdout(indoc! {r#" [ { "package": "cargo_mutants_testdata_workspace_utils", "path": "utils/src/lib.rs" }, { "package": "main", "path": "main/src/main.rs" }, { "package": "main2", "path": "main2/src/main.rs" } ] "#}); } #[test] fn workspace_tree_is_well_tested() { let tmp_src_dir = copy_of_testdata("workspace"); run() .args(["mutants", "-d"]) .arg(tmp_src_dir.path()) .assert() .success(); // The outcomes.json has some summary data let json_str = fs::read_to_string(tmp_src_dir.path().join("mutants.out/outcomes.json")).unwrap(); println!("outcomes.json:\n{json_str}"); let json: serde_json::Value = json_str.parse().unwrap(); assert_eq!(json["total_mutants"].as_u64().unwrap(), 8); assert_eq!(json["caught"].as_u64().unwrap(), 8); assert_eq!(json["missed"].as_u64().unwrap(), 0); assert_eq!(json["timeout"].as_u64().unwrap(), 0); let outcomes = json["outcomes"].as_array().unwrap(); { let baseline = outcomes[0].as_object().unwrap(); assert_eq!(baseline["scenario"].as_str().unwrap(), "Baseline"); assert_eq!(baseline["summary"], "Success"); let baseline_phases = baseline["phase_results"].as_array().unwrap(); assert_eq!(baseline_phases.len(), 2); assert_eq!(baseline_phases[0]["process_status"], "Success"); assert_eq!( baseline_phases[0]["argv"].as_array().unwrap().into_iter().map(|v| v.as_str().unwrap()).skip(1).collect_vec().join(" "), "build --tests --package cargo_mutants_testdata_workspace_utils --package main --package main2" ); assert_eq!(baseline_phases[1]["process_status"], "Success"); assert_eq!( baseline_phases[1]["argv"] .as_array() .unwrap() .into_iter() .map(|v| v.as_str().unwrap()) .skip(1) .collect_vec() .join(" "), "test --package cargo_mutants_testdata_workspace_utils --package main --package main2" ); } assert_eq!(outcomes.len(), 9); for outcome in &outcomes[1..] { let mutant = &outcome["scenario"]["Mutant"]; let package_name = mutant["package"].as_str().unwrap(); assert!(!package_name.is_empty()); assert_eq!(outcome["summary"], "CaughtMutant"); let mutant_phases = outcome["phase_results"].as_array().unwrap(); assert_eq!(mutant_phases.len(), 2); assert_eq!(mutant_phases[0]["process_status"], "Success"); assert_eq!( mutant_phases[0]["argv"].as_array().unwrap()[1..=3], ["build", "--tests", "--manifest-path"] ); assert_eq!(mutant_phases[1]["process_status"], "Failure"); assert_eq!( mutant_phases[1]["argv"].as_array().unwrap()[1..=2], ["test", "--manifest-path"], ); } { let baseline = json["outcomes"][0].as_object().unwrap(); assert_eq!(baseline["scenario"].as_str().unwrap(), "Baseline"); assert_eq!(baseline["summary"], "Success"); let baseline_phases = baseline["phase_results"].as_array().unwrap(); assert_eq!(baseline_phases.len(), 2); assert_eq!(baseline_phases[0]["process_status"], "Success"); assert_eq!( baseline_phases[0]["argv"].as_array().unwrap()[1..].into_iter().map(|v| v.as_str().unwrap()).join(" "), "build --tests --package cargo_mutants_testdata_workspace_utils --package main --package main2", ); assert_eq!(baseline_phases[1]["process_status"], "Success"); assert_eq!( baseline_phases[1]["argv"].as_array().unwrap()[1..] .into_iter() .map(|v| v.as_str().unwrap()) .join(" "), "test --package cargo_mutants_testdata_workspace_utils --package main --package main2", ); } } #[test] /// Baseline tests in a workspace only test the packages that will later /// be mutated. /// See fn in_workspace_only_relevant_packages_included_in_baseline_tests() { let tmp = copy_of_testdata("package_fails"); run() .args(["mutants", "-f", "passing/src/lib.rs", "--no-shuffle", "-d"]) .arg(tmp.path()) .assert() .success(); assert_eq!( read_to_string(tmp.path().join("mutants.out/caught.txt")).unwrap(), indoc! { "\ passing/src/lib.rs:1: replace triple -> usize with 0 passing/src/lib.rs:1: replace triple -> usize with 1 "} ); assert_eq!( read_to_string(tmp.path().join("mutants.out/timeout.txt")).unwrap(), "" ); assert_eq!( read_to_string(tmp.path().join("mutants.out/missed.txt")).unwrap(), "" ); assert_eq!( read_to_string(tmp.path().join("mutants.out/unviable.txt")).unwrap(), "" ); } #[test] fn copy_testdata_doesnt_include_build_artifacts() { // If there is a target or mutants.out in the source directory, we don't want it in the copy, // so that the tests are (more) hermetic. let tmp_src_dir = copy_of_testdata("factorial"); assert!(!tmp_src_dir.path().join("mutants.out").exists()); assert!(!tmp_src_dir.path().join("target").exists()); assert!(!tmp_src_dir.path().join("mutants.out.old").exists()); assert!(tmp_src_dir.path().join("Cargo.toml").exists()); } #[test] fn small_well_tested_tree_is_clean() { let tmp_src_dir = copy_of_testdata("small_well_tested"); run() .arg("mutants") .args(["--no-times", "--no-shuffle", "-v", "-V"]) .current_dir(tmp_src_dir.path()) .assert() .success() .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); // The log file should exist and include something that looks like a diff. let log_content = fs::read_to_string( tmp_src_dir .path() .join("mutants.out/log/src__lib.rs_line_4.log"), ) .unwrap() .replace('\r', ""); println!("log content:\n{log_content}"); assert!(log_content.contains("*** mutation diff")); assert!(log_content.contains(indoc! { r#" *** mutation diff: --- src/lib.rs +++ replace factorial -> u32 with 0 @@ -1,17 +1,13 @@ "# })); assert!(log_content.contains(indoc! { r#" pub fn factorial(n: u32) -> u32 { - let mut a = 1; - for i in 2..=n { - a *= i; - } - a +0 /* ~ changed by cargo-mutants ~ */ } "# })); // Also, it should contain output from the failed tests with mutations applied. assert!(log_content.contains("test test::test_factorial ... FAILED")); assert!(log_content.contains("---- test::test_factorial stdout ----")); assert!(log_content.contains("factorial(6) = 0")); } #[test] fn cdylib_tree_is_well_tested() { let tmp_src_dir = copy_of_testdata("cdylib"); run() .arg("mutants") .args(["--no-times", "--no-shuffle", "-v", "-V"]) .current_dir(tmp_src_dir.path()) .assert() .success() .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); } #[test] fn well_tested_tree_quiet() { let tmp_src_dir = copy_of_testdata("well_tested"); run() .arg("mutants") .arg("--no-times") .arg("--no-shuffle") .current_dir(tmp_src_dir.path()) .assert() .success() .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); let outcomes_json = fs::read_to_string(tmp_src_dir.path().join("mutants.out/outcomes.json")).unwrap(); println!("outcomes.json:\n{outcomes_json}"); let outcomes: serde_json::Value = outcomes_json.parse().unwrap(); assert_eq!(outcomes["total_mutants"], 38); assert_eq!(outcomes["caught"], 38); assert_eq!(outcomes["unviable"], 0); assert_eq!(outcomes["missed"], 0); } #[test] fn well_tested_tree_finds_no_problems() { let tmp_src_dir = copy_of_testdata("well_tested"); run() .arg("mutants") .arg("--no-times") .arg("--caught") .arg("--no-shuffle") .current_dir(tmp_src_dir.path()) .assert() .success() .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); assert!(tmp_src_dir .path() .join("mutants.out/outcomes.json") .exists()); check_text_list_output(tmp_src_dir.path(), "well_tested_tree_finds_no_problems"); } #[test] fn well_tested_tree_check_only() { let tmp_src_dir = copy_of_testdata("well_tested"); run() .args(["mutants", "--check", "--no-shuffle", "--no-times"]) .current_dir(tmp_src_dir.path()) .assert() .success() .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); } #[test] fn well_tested_tree_check_only_shuffled() { let tmp_src_dir = copy_of_testdata("well_tested"); run() .args(["mutants", "--check", "--no-times", "--shuffle"]) .current_dir(tmp_src_dir.path()) .assert() .success(); // Caution: No assertions about output here, we just check that it runs. } #[test] fn unviable_mutation_of_struct_with_no_default() { let tmp_src_dir = copy_of_testdata("struct_with_no_default"); run() .args(["mutants", "--no-times", "--no-shuffle", "-v", "-V"]) .arg("-d") .arg(tmp_src_dir.path()) .assert() .success() .stdout( predicate::str::is_match( r"src/lib.rs:\d+: replace make_an_s -> S with Default::default\(\) \.\.\. unviable", ) .unwrap(), ); check_text_list_output( tmp_src_dir.path(), "unviable_mutation_of_struct_with_no_default", ); } #[test] fn integration_test_source_is_not_mutated() { let tmp_src_dir = copy_of_testdata("integration_tests"); run() .args(["mutants", "--no-times", "--no-shuffle", "--list-files"]) .current_dir(tmp_src_dir.path()) .assert() .success() .stdout("src/lib.rs\n"); run() .args(["mutants", "--no-times", "--no-shuffle"]) .current_dir(tmp_src_dir.path()) .assert() .success(); check_text_list_output(tmp_src_dir.path(), "integration_test_source_is_not_mutated"); } #[test] fn error_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("Error: No mutants found")) .stdout(predicate::str::contains("Found 0 mutants to test")) .failure(); } #[test] fn uncaught_mutant_in_factorial() { let tmp_src_dir = copy_of_testdata("factorial"); run() .arg("mutants") .arg("--no-shuffle") .arg("-d") .arg(tmp_src_dir.path()) .assert() .code(2) .stderr("") .stdout(predicate::function(|stdout| { insta::assert_snapshot!(redact_timestamps_sizes(stdout)); true })); // Some log files should have been created let log_dir = tmp_src_dir.path().join("mutants.out/log"); assert!(log_dir.is_dir()); let mut names = fs::read_dir(log_dir) .unwrap() .map(Result::unwrap) .map(|e| e.file_name().into_string().unwrap()) .collect_vec(); names.sort_unstable(); insta::assert_debug_snapshot!("factorial__log_names", &names); // A mutants.json is in the mutants.out directory. let mutants_json = fs::read_to_string(tmp_src_dir.path().join("mutants.out/mutants.json")).unwrap(); insta::assert_snapshot!("mutants.json", mutants_json); check_text_list_output(tmp_src_dir.path(), "uncaught_mutant_in_factorial"); } #[test] fn factorial_mutants_with_all_logs() { // The log contains a lot of build output, which is hard to deal with, but let's check that // some key lines are there. let tmp_src_dir = copy_of_testdata("factorial"); run() .arg("mutants") .arg("--all-logs") .arg("-v") .arg("-V") .arg("-d") .arg(tmp_src_dir.path()) .assert() .code(2) .stderr("") .stdout(is_match( r"Unmutated baseline \.\.\. ok in \d+\.\ds" ).unwrap()) .stdout(is_match( r"src/bin/factorial\.rs:1: replace main with \(\) \.\.\. NOT CAUGHT in \d+\.\ds" ).unwrap()) .stdout(is_match( r"src/bin/factorial\.rs:7: replace factorial -> u32 with 0 \.\.\. caught in \d+\.\ds" ).unwrap()); } #[test] fn factorial_mutants_with_all_logs_and_nocapture() { let tmp_src_dir = copy_of_testdata("factorial"); run() .arg("mutants") .args(["--all-logs", "-v", "-V"]) .arg("-d") .arg(tmp_src_dir.path()) .args(["--", "--", "--nocapture"]) .assert() .code(2) .stderr("") .stdout(contains("factorial(6) = 720")) // println from the test .stdout(contains("factorial(6) = 0")) // The mutated result ; } #[test] fn factorial_mutants_no_copy_target() { let tmp_src_dir = copy_of_testdata("factorial"); run() .arg("mutants") .args(["--no-times"]) .arg("-d") .arg(tmp_src_dir.path()) .assert() .code(2) .stderr("") .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); } #[test] fn small_well_tested_mutants_with_cargo_arg_release() { let tmp_src_dir = copy_of_testdata("small_well_tested"); run() .arg("mutants") .args(["--no-times", "--cargo-arg", "--release"]) .arg("-d") .arg(tmp_src_dir.path()) .assert() .success() .stderr("") .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); // Check that it was actually passed. let baseline_log_path = &tmp_src_dir.path().join("mutants.out/log/baseline.log"); println!("{}", baseline_log_path.display()); let log_content = fs::read_to_string(baseline_log_path).unwrap(); println!("{log_content}"); regex::Regex::new(r"cargo.* build --tests --manifest-path .* --release") .unwrap() .captures(&log_content) .unwrap(); regex::Regex::new(r"cargo.* test --manifest-path .* --release") .unwrap() .captures(&log_content) .unwrap(); } #[test] /// The `--output` directory creates the named directory if necessary, and then /// creates `mutants.out` within it. `mutants.out` is not created in the /// source directory in this case. fn output_option() { let tmp_src_dir = copy_of_testdata("factorial"); let output_tmpdir = TempDir::new().unwrap(); let output_parent = output_tmpdir.path().join("output_parent"); assert!( !tmp_src_dir.path().join("mutants.out").exists(), "mutants.out should not be in a clean copy of the test data" ); run() .arg("mutants") .arg("--output") .arg(&output_parent) .args(["--check", "--no-times"]) .arg("-d") .arg(tmp_src_dir.path()) .assert() .success(); assert!( !tmp_src_dir.path().join("mutants.out").exists(), "mutants.out should not be in the source directory after --output was given" ); let mutants_out = output_parent.join("mutants.out"); assert!(mutants_out.exists(), "mutants.out is in --output directory"); for name in [ "mutants.json", "debug.log", "outcomes.json", "missed.txt", "caught.txt", "timeout.txt", "unviable.txt", ] { assert!(mutants_out.join(name).is_file(), "{name} is in mutants.out",); } } #[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| { insta::assert_snapshot!(stdout); true })); } #[test] fn check_tree_with_mutants_skip() { let tmp_src_dir = copy_of_testdata("hang_avoided_by_attr"); run() .arg("mutants") .arg("--check") .arg("--no-times") .current_dir(tmp_src_dir.path()) .env_remove("RUST_BACKTRACE") .assert() .success() .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); } #[test] fn already_failing_tests_are_detected_before_running_mutants() { let tmp_src_dir = copy_of_testdata("already_failing_tests"); run() .arg("mutants") .current_dir(tmp_src_dir.path()) .env_remove("RUST_BACKTRACE") .assert() .code(4) .stdout( predicate::str::contains("running 1 test\ntest test_factorial ... FAILED") .normalize() .and(predicate::str::contains("thread 'test_factorial' panicked")) .and(predicate::str::contains("72")) // the failing value should be in the output .and(predicate::str::contains("lib.rs:11:5")) .and(predicate::str::contains( "cargo test failed in an unmutated tree, so no mutants were tested", )) .and( predicate::str::contains("test result: FAILED. 0 passed; 1 failed;") .normalize(), ), ); } #[test] fn already_failing_doctests_are_detected() { let tmp_src_dir = copy_of_testdata("already_failing_doctests"); run() .arg("mutants") .current_dir(tmp_src_dir.path()) .env_remove("RUST_BACKTRACE") .assert() .code(4) // CLEAN_TESTS_FAILED .stdout(contains("lib.rs - takes_one_arg (line 5) ... FAILED")) .stdout(contains( "this function takes 1 argument but 3 arguments were supplied", )) .stdout(predicate::str::contains( "cargo test failed in an unmutated tree, so no mutants were tested", )); } #[test] fn already_failing_doctests_can_be_skipped_with_cargo_arg() { let tmp_src_dir = copy_of_testdata("already_failing_doctests"); run() .arg("mutants") .args(["--", "--all-targets"]) .current_dir(tmp_src_dir.path()) .env_remove("RUST_BACKTRACE") .assert() .code(0) .stdout(contains("Found 2 mutants to test")); } #[test] fn source_tree_parse_fails() { let tmp_src_dir = copy_of_testdata("parse_fails"); run() .arg("mutants") .current_dir(tmp_src_dir.path()) .env_remove("RUST_BACKTRACE") .assert() .failure() // TODO: This should be a distinct error code .stderr(contains("Error: failed to parse src/lib.rs")); } #[test] fn source_tree_typecheck_fails() { let tmp_src_dir = copy_of_testdata("typecheck_fails"); run() .arg("mutants") .current_dir(tmp_src_dir.path()) .env_remove("RUST_BACKTRACE") .assert() .failure() // TODO: This should be a distinct error code .stdout(is_match(r"Unmutated baseline \.\.\. FAILED in \d+\.\ds").unwrap()) .stdout( contains(r#""1" + 2 // Doesn't work in Rust: just as well!"#) .name("The problem source line"), ) .stdout(contains("*** baseline")) .stdout(contains("build --tests")) // Caught at the check phase .stdout(contains("lib.rs:6")) .stdout(contains("*** result: ")) .stdout(contains( "build failed in an unmutated tree, so no mutants were tested", )); } /// `CARGO_MUTANTS_MINIMUM_TEST_TIMEOUT` overrides the detected minimum timeout. #[test] fn minimum_test_timeout_from_env() { let tmp_src_dir = copy_of_testdata("small_well_tested"); run() .arg("mutants") .env("CARGO_MUTANTS_MINIMUM_TEST_TIMEOUT", "1234") .current_dir(tmp_src_dir.path()) .timeout(OUTER_TIMEOUT) .assert() .success() .stdout(predicate::str::contains("Auto-set test timeout to 1234.0s")); } /// In this tree, as the name suggests, tests will hang in a clean tree. /// /// cargo-mutants should notice this when doing baseline tests and return a clean result. /// /// We run the cargo-mutants subprocess with an enclosing timeout, so that the outer test will /// fail rather than hang if cargo-mutants own timeout doesn't work as intended. /// /// All these timeouts are a little brittle if the test machine is very slow. #[test] fn timeout_when_unmutated_tree_test_hangs() { let tmp_src_dir = copy_of_testdata("already_hangs"); run() .arg("mutants") .args(["--timeout", "2.9"]) .current_dir(tmp_src_dir.path()) .env_remove("RUST_BACKTRACE") .timeout(OUTER_TIMEOUT) .assert() .code(4) // exit_code::CLEAN_TESTS_FAILED .stdout(is_match(r"Unmutated baseline \.\.\. TIMEOUT in \d+\.\ds").unwrap()) .stdout(contains("timeout")) .stdout(contains( "cargo test failed in an unmutated tree, so no mutants were tested", )); } /// If the test hangs and the user (in this case the test suite) interrupts it, then /// the `cargo test` child should be killed. /// /// This is a bit hard to directly observe: the property that we really most care /// about is that _all_ grandchild processes are also killed and nothing is left /// behind. (On Unix, this is accomplished by use of a pgroup.) However that's a bit /// hard to mechanically check without reading and interpreting the process tree, which /// seems likely to be a bit annoying to do portably and without flakes. /// (But maybe we still should?) /// /// An easier thing to test is that the cargo-mutants process _thinks_ it has killed /// the children, and we can observe this in the debug log. /// /// In this test cargo-mutants has a very long timeout, but the test driver has a /// short timeout, so it should kill cargo-mutants. #[test] #[cfg(unix)] // Should in principle work on Windows, but does not at the moment. fn interrupt_caught_and_kills_children() { let tmp_src_dir = copy_of_testdata("already_hangs"); // We can't use `assert_cmd` `timeout` here because that sends the child a `SIGKILL`, // which doesn't give it a chance to clean up. And, `std::process::Command` only // has an abrupt kill. But `subprocess` has a gentle `terminate` method. let config = PopenConfig { stdout: Redirection::Pipe, stderr: Redirection::Pipe, cwd: Some(tmp_src_dir.path().as_os_str().to_owned()), ..Default::default() }; let args = [ MAIN_BINARY.to_str().unwrap(), "mutants", "--timeout=300", "--level=trace", ]; println!("Running: {args:?}"); let mut child = Popen::create(&args, config).expect("spawn child"); // TODO: Watch the output, maybe using `subprocess`, rather than just guessing how long it needs. sleep(Duration::from_secs(4)); // Let it get started assert!(child.poll().is_none(), "child exited early"); println!("Sending terminate to cargo-mutants..."); child.terminate().expect("terminate child"); println!("Wait for cargo-mutants to exit..."); match child.wait_timeout(Duration::from_secs(4)) { Err(e) => panic!("failed to wait for child: {e}"), Ok(None) => { println!("child did not exit after interrupt"); child.kill().expect("kill child"); child.wait().expect("wait for child after kill"); } Ok(Some(status)) => { println!("cargo-mutants exited with status: {status:?}"); } } let mut stdout = String::new(); child .stdout .as_mut() .unwrap() .read_to_string(&mut stdout) .expect("read stdout"); println!("stdout:\n{stdout}"); let mut stderr = String::new(); child .stderr .as_mut() .unwrap() .read_to_string(&mut stderr) .expect("read stderr"); println!("stderr:\n{stderr}"); assert!(stdout.contains("interrupted")); assert!(stdout.contains("terminating child process")); assert!(stdout.contains("terminated child exit status")); } /// A tree that hangs when some functions are mutated does not hang cargo-mutants /// overall, because we impose a timeout. The timeout can be specified on the /// command line, with decimal seconds. /// /// This test is a bit at risk of being flaky, because it depends on the progress /// of real time and tests can be unexpectedly slow on CI. /// /// The `hang_when_mutated` tree generates three mutants: /// /// * `controlled_loop` could be replaced to return 0 and this will be /// detected, because it should normally return at least one. /// /// * `should_stop` could change to always return `true`, in which case /// the test will fail and the mutant will be caught because the loop /// does only one pass. /// /// * `should_stop` could change to always return `false`, in which case /// the loop will never stop, but the test should eventually be killed /// by a timeout. #[test] fn mutants_causing_tests_to_hang_are_stopped_by_manual_timeout() { let tmp_src_dir = copy_of_testdata("hang_when_mutated"); // Also test that it accepts decimal seconds run() .arg("mutants") .args(["-t", "8.1", "-v", "--", "--", "--nocapture"]) .current_dir(tmp_src_dir.path()) .env_remove("RUST_BACKTRACE") .timeout(OUTER_TIMEOUT) .assert() .code(3) // exit_code::TIMEOUT .stdout(contains( "replace should_stop -> bool with false ... TIMEOUT", )) .stdout(contains("replace should_stop -> bool with true ... caught")) .stdout(contains( "replace controlled_loop -> usize with 0 ... caught", )); // TODO: Inspect outcomes.json. } #[test] fn log_file_names_are_short_and_dont_collide() { // The "well_tested" tree can generate multiple mutants from single lines. They get distinct file names. let tmp_src_dir = copy_of_testdata("well_tested"); let cmd_assert = run() .arg("mutants") .args(["--check", "-v", "-V"]) .current_dir(tmp_src_dir.path()) .env_remove("RUST_BACKTRACE") .assert() .success(); println!( "{}", String::from_utf8_lossy(&cmd_assert.get_output().stdout) ); let log_dir = tmp_src_dir.path().join("mutants.out").join("log"); let all_log_names = read_dir(log_dir) .unwrap() .map(|e| e.unwrap().file_name().to_str().unwrap().to_string()) .inspect(|filename| println!("{filename}")) .collect::>(); assert!(all_log_names.len() > 10); assert!( all_log_names.iter().all(|filename| filename.len() < 80), "log file names are too long" ); assert!( all_log_names .iter() .any(|filename| filename.ends_with("_001.log")), "log file names are not disambiguated" ); } #[test] fn cargo_mutants_in_override_dependency_tree_passes() { // Run against the testdata directory directly, without copying it, so that the // relative dependency `../dependency` is still used. run() .arg("mutants") .arg("--no-times") .arg("--no-shuffle") .arg("-d") .arg("testdata/tree/override_dependency") .assert() .success() .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); } #[test] fn cargo_mutants_in_relative_dependency_tree_passes() { // Run against the testdata directory directly, without copying it, so that the // relative dependency `../dependency` is still used. run() .arg("mutants") .arg("--no-times") .arg("--no-shuffle") .arg("-d") .arg("testdata/tree/relative_dependency") .assert() .success() .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); } #[test] fn cargo_mutants_in_replace_dependency_tree_passes() { // Run against the testdata directory directly, without copying it, so that the // relative dependency `../dependency` is still used. run() .arg("mutants") .arg("--no-times") .arg("--no-shuffle") .arg("-d") .arg("testdata/tree/replace_dependency") .assert() .success() .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); } #[test] fn cargo_mutants_in_patch_dependency_tree_passes() { // Run against the testdata directory directly, without copying it, so that the // relative dependency `../dependency` is still used. run() .arg("mutants") .arg("--no-times") .arg("--no-shuffle") .arg("-d") .arg("testdata/tree/patch_dependency") .assert() .success() .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); } /// This test would fail if mutants aren't correctly removed from the tree after /// testing, which would cause all later mutants to be incorrectly marked as /// caught. /// /// This was suggested by `Mutant::unapply` being marked as missed. #[test] fn mutants_are_unapplied_after_testing_so_later_missed_mutants_are_found() { // This needs --no-shuffle because failure to unapply will show up when the // uncaught mutant is not the first file tested. let tmp_src_dir = copy_of_testdata("unapply"); run() .args(["mutants", "--no-times", "--no-shuffle"]) .arg("-d") .arg(tmp_src_dir.path()) .assert() .code(2) // some were missed .stdout(predicate::function(|stdout| { insta::assert_snapshot!(stdout); true })); } #[test] fn strict_warnings_about_unused_variables_are_disabled_so_mutants_compile() { let tmp_src_dir = copy_of_testdata("strict_warnings"); run() .arg("mutants") .arg("--check") .arg("--no-times") .current_dir(tmp_src_dir.path()) .env_remove("RUST_BACKTRACE") .assert() .success() .stdout(contains("2 mutants tested: 2 succeeded")); run() .arg("mutants") .arg("--no-times") .current_dir(tmp_src_dir.path()) .env_remove("RUST_BACKTRACE") .assert() .success() .stdout(contains("2 mutants tested: 2 caught")); } /// `INSTA_UPDATE=always` in the environment will cause Insta to update /// the snaphots, so the tests will pass, so mutants will not be caught. /// This test checks that cargo-mutants sets the environment variable /// so that mutants are caught properly. #[test] fn insta_test_failures_are_detected() { for insta_update in ["auto", "always"] { println!("INSTA_UPDATE={insta_update}"); let tmp_src_dir = copy_of_testdata("insta"); run() .arg("mutants") .args(["--no-times", "--no-shuffle", "--caught"]) .env("INSTA_UPDATE", insta_update) .current_dir(tmp_src_dir.path()) .assert() .success(); } } fn check_text_list_output(dir: &Path, test_name: &str) { // There is a `missed.txt` file with the right content, etc. for name in ["missed", "caught", "timeout", "unviable"] { let path = dir.join(format!("mutants.out/{name}.txt")); let content = fs::read_to_string(&path).unwrap(); insta::assert_snapshot!(format!("{test_name}__{name}.txt"), content); } } /// `cargo mutants --completions SHELL` produces a shell script for some /// well-known shells. /// /// We won't check the content but let's just make sure that it succeeds /// and produces some non-empty output. #[test] fn completions_option_generates_something() { for shell in ["bash", "fish", "zsh", "powershell"] { println!("completions for {shell}"); run() .arg("mutants") .arg("--completions") .arg(shell) .assert() .success() .stdout(predicate::str::is_empty().not()); } } cargo-mutants-23.10.0/tests/cli/snapshots/cli__cargo_mutants_in_override_dependency_tree_passes.snap000064400000000000000000000001761046102023000324110ustar 00000000000000--- source: tests/cli.rs expression: stdout --- Found 2 mutants to test Unmutated baseline ... ok 2 mutants tested: 2 caught cargo-mutants-23.10.0/tests/cli/snapshots/cli__cargo_mutants_in_patch_dependency_tree_passes.snap000064400000000000000000000001761046102023000316710ustar 00000000000000--- source: tests/cli.rs expression: stdout --- Found 2 mutants to test Unmutated baseline ... ok 2 mutants tested: 2 caught cargo-mutants-23.10.0/tests/cli/snapshots/cli__cargo_mutants_in_relative_dependency_tree_passes.snap000064400000000000000000000002031046102023000323740ustar 00000000000000--- source: tests/cli/main.rs expression: stdout --- Found 2 mutants to test Unmutated baseline ... ok 2 mutants tested: 2 caught cargo-mutants-23.10.0/tests/cli/snapshots/cli__cargo_mutants_in_replace_dependency_tree_passes.snap000064400000000000000000000001761046102023000322050ustar 00000000000000--- source: tests/cli.rs expression: stdout --- Found 2 mutants to test Unmutated baseline ... ok 2 mutants tested: 2 caught cargo-mutants-23.10.0/tests/cli/snapshots/cli__cdylib_tree_is_well_tested.snap000064400000000000000000000003711046102023000257330ustar 00000000000000--- source: tests/cli/main.rs expression: stdout --- Found 2 mutants to test Unmutated baseline ... ok src/entry.rs:1: replace factorial -> u32 with 0 ... caught src/entry.rs:1: replace factorial -> u32 with 1 ... caught 2 mutants tested: 2 caught ././@LongLink00006440000000000000000000000147000000000000007775Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__check_succeeds_in_tree_that_builds_but_fails_tests.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__check_succeeds_in_tree_that_builds_but_fails_tests.sn000064400000000000000000000003601046102023000323310ustar 00000000000000--- source: tests/cli/main.rs expression: stdout --- Found 2 mutants to test Unmutated baseline ... ok src/lib.rs:1: replace factorial -> u32 with 0 ... ok src/lib.rs:1: replace factorial -> u32 with 1 ... ok 2 mutants tested: 2 succeeded cargo-mutants-23.10.0/tests/cli/snapshots/cli__check_tree_with_mutants_skip.snap000064400000000000000000000002651046102023000263120ustar 00000000000000--- source: tests/cli.rs expression: stdout --- Found 1 mutant to test Unmutated baseline ... ok src/lib.rs:14: replace controlled_loop with () ... ok 1 mutant tested: 1 succeeded ././@LongLink00006440000000000000000000000146000000000000007774Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__error_value__error_value_catches_untested_ok_case.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__error_value__error_value_catches_untested_ok_case.sna000064400000000000000000000006361046102023000323660ustar 00000000000000--- source: tests/cli/error_value.rs expression: stdout --- Found 3 mutants to test Unmutated baseline ... ok src/lib.rs:3: replace even_is_ok -> Result with Ok(0) ... caught src/lib.rs:3: replace even_is_ok -> Result with Ok(1) ... caught src/lib.rs:3: replace even_is_ok -> Result with Err("injected") ... NOT CAUGHT 3 mutants tested: 1 missed, 2 caught ././@LongLink00006440000000000000000000000166000000000000007776Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__error_value__list_mutants_with_error_value_from_command_line_list.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__error_value__list_mutants_with_error_value_from_comma000064400000000000000000000004571046102023000325560ustar 00000000000000--- source: tests/cli/error_value.rs expression: stdout --- src/lib.rs:3: replace even_is_ok -> Result with Ok(0) src/lib.rs:3: replace even_is_ok -> Result with Ok(1) src/lib.rs:3: replace even_is_ok -> Result with Err(::eyre::eyre!("mutant")) ././@LongLink00006440000000000000000000000207000000000000007772Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__error_value__no_config_option_disables_config_file_so_error_value_is_not_generated.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__error_value__no_config_option_disables_config_file_so000064400000000000000000000004621046102023000324110ustar 00000000000000--- source: tests/cli/error_value.rs expression: stdout --- Found 2 mutants to test Unmutated baseline ... ok src/lib.rs:3: replace even_is_ok -> Result with Ok(0) ... caught src/lib.rs:3: replace even_is_ok -> Result with Ok(1) ... caught 2 mutants tested: 2 caught cargo-mutants-23.10.0/tests/cli/snapshots/cli__factorial__log_names.snap000064400000000000000000000003161046102023000245060ustar 00000000000000--- source: tests/cli/main.rs expression: "&names" --- [ "baseline.log", "src__bin__factorial.rs_line_1.log", "src__bin__factorial.rs_line_7.log", "src__bin__factorial.rs_line_7_001.log", ] cargo-mutants-23.10.0/tests/cli/snapshots/cli__factorial_mutants_no_copy_target.snap000064400000000000000000000003111046102023000271650ustar 00000000000000--- source: tests/cli/main.rs expression: stdout --- Found 3 mutants to test Unmutated baseline ... ok src/bin/factorial.rs:1: replace main with () ... NOT CAUGHT 3 mutants tested: 1 missed, 2 caught ././@LongLink00006440000000000000000000000147000000000000007775Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__integration_test_source_is_not_mutated__caught.txt.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__integration_test_source_is_not_mutated__caught.txt.sn000064400000000000000000000002151046102023000324040ustar 00000000000000--- source: tests/cli/main.rs expression: content --- src/lib.rs:1: replace double -> u32 with 0 src/lib.rs:1: replace double -> u32 with 1 ././@LongLink00006440000000000000000000000147000000000000007775Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__integration_test_source_is_not_mutated__missed.txt.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__integration_test_source_is_not_mutated__missed.txt.sn000064400000000000000000000000621046102023000324150ustar 00000000000000--- source: tests/cli.rs expression: content --- ././@LongLink00006440000000000000000000000150000000000000007767Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__integration_test_source_is_not_mutated__timeout.txt.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__integration_test_source_is_not_mutated__timeout.txt.s000064400000000000000000000000621046102023000324410ustar 00000000000000--- source: tests/cli.rs expression: content --- ././@LongLink00006440000000000000000000000151000000000000007770Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__integration_test_source_is_not_mutated__unviable.txt.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__integration_test_source_is_not_mutated__unviable.txt.000064400000000000000000000000621046102023000323750ustar 00000000000000--- source: tests/cli.rs expression: content --- cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_files_json_well_tested.snap000064400000000000000000000024141046102023000261410ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- [ { "package": "cargo-mutants-testdata-well-tested", "path": "src/lib.rs" }, { "package": "cargo-mutants-testdata-well-tested", "path": "src/arc.rs" }, { "package": "cargo-mutants-testdata-well-tested", "path": "src/empty_fns.rs" }, { "package": "cargo-mutants-testdata-well-tested", "path": "src/inside_mod.rs" }, { "package": "cargo-mutants-testdata-well-tested", "path": "src/item_mod.rs" }, { "package": "cargo-mutants-testdata-well-tested", "path": "src/methods.rs" }, { "package": "cargo-mutants-testdata-well-tested", "path": "src/nested_function.rs" }, { "package": "cargo-mutants-testdata-well-tested", "path": "src/numbers.rs" }, { "package": "cargo-mutants-testdata-well-tested", "path": "src/result.rs" }, { "package": "cargo-mutants-testdata-well-tested", "path": "src/sets.rs" }, { "package": "cargo-mutants-testdata-well-tested", "path": "src/simple_fns.rs" }, { "package": "cargo-mutants-testdata-well-tested", "path": "src/slices.rs" }, { "package": "cargo-mutants-testdata-well-tested", "path": "src/struct_with_lifetime.rs" } ] cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_files_json_workspace.snap000064400000000000000000000004671046102023000256320ustar 00000000000000--- source: tests/cli.rs expression: "String::from_utf8_lossy(&output.stdout)" --- [ { "package": "cargo_mutants_testdata_workspace_utils", "path": "utils/src/lib.rs" }, { "package": "main", "path": "main/src/main.rs" }, { "package": "main2", "path": "main2/src/main.rs" } ] cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_files_text_well_tested.snap000064400000000000000000000004551046102023000261570ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- src/lib.rs src/arc.rs src/empty_fns.rs src/inside_mod.rs src/item_mod.rs src/methods.rs src/nested_function.rs src/numbers.rs src/result.rs src/sets.rs src/simple_fns.rs src/slices.rs src/struct_with_lifetime.rs cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_in_all_trees_as_json.snap000064400000000000000000000714251046102023000273520ustar 00000000000000--- source: tests/cli/main.rs expression: buf --- ## testdata/tree/already_failing_doctests ```json [ { "file": "src/lib.rs", "function": "takes_one_arg", "genre": "FnValue", "line": 9, "package": "mutants-testdata-already-failing-doctests", "replacement": "0", "return_type": "-> usize" }, { "file": "src/lib.rs", "function": "takes_one_arg", "genre": "FnValue", "line": 9, "package": "mutants-testdata-already-failing-doctests", "replacement": "1", "return_type": "-> usize" } ] ``` ## testdata/tree/already_failing_tests ```json [ { "file": "src/lib.rs", "function": "factorial", "genre": "FnValue", "line": 1, "package": "mutants-testdata-already-failing-tests", "replacement": "0", "return_type": "-> u32" }, { "file": "src/lib.rs", "function": "factorial", "genre": "FnValue", "line": 1, "package": "mutants-testdata-already-failing-tests", "replacement": "1", "return_type": "-> u32" } ] ``` ## testdata/tree/already_hangs ```json [ { "file": "src/lib.rs", "function": "infinite_loop", "genre": "FnValue", "line": 8, "package": "cargo-mutants-testdata-already-hangs", "replacement": "()", "return_type": "" } ] ``` ## testdata/tree/cdylib ```json [ { "file": "src/entry.rs", "function": "factorial", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-cdylib", "replacement": "0", "return_type": "-> u32" }, { "file": "src/entry.rs", "function": "factorial", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-cdylib", "replacement": "1", "return_type": "-> u32" } ] ``` ## testdata/tree/cfg_attr_mutants_skip ```json [] ``` ## testdata/tree/cfg_attr_test_skip ```json [ { "file": "src/lib.rs", "function": "double", "genre": "FnValue", "line": 17, "package": "cargo-mutants-testdata-cfg-attr-test-skip", "replacement": "0", "return_type": "-> usize" }, { "file": "src/lib.rs", "function": "double", "genre": "FnValue", "line": 17, "package": "cargo-mutants-testdata-cfg-attr-test-skip", "replacement": "1", "return_type": "-> usize" } ] ``` ## testdata/tree/dependency ```json [ { "file": "src/lib.rs", "function": "factorial", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-dependency", "replacement": "0", "return_type": "-> u32" }, { "file": "src/lib.rs", "function": "factorial", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-dependency", "replacement": "1", "return_type": "-> u32" } ] ``` ## testdata/tree/error_value ```json [ { "file": "src/lib.rs", "function": "even_is_ok", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-error-value", "replacement": "Ok(0)", "return_type": "-> Result" }, { "file": "src/lib.rs", "function": "even_is_ok", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-error-value", "replacement": "Ok(1)", "return_type": "-> Result" }, { "file": "src/lib.rs", "function": "even_is_ok", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-error-value", "replacement": "Err(\"injected\")", "return_type": "-> Result" } ] ``` ## testdata/tree/everything_skipped ```json [] ``` ## testdata/tree/factorial ```json [ { "file": "src/bin/factorial.rs", "function": "main", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-factorial", "replacement": "()", "return_type": "" }, { "file": "src/bin/factorial.rs", "function": "factorial", "genre": "FnValue", "line": 7, "package": "cargo-mutants-testdata-factorial", "replacement": "0", "return_type": "-> u32" }, { "file": "src/bin/factorial.rs", "function": "factorial", "genre": "FnValue", "line": 7, "package": "cargo-mutants-testdata-factorial", "replacement": "1", "return_type": "-> u32" } ] ``` ## testdata/tree/fails_without_feature ```json [ { "file": "src/bin/factorial.rs", "function": "factorial", "genre": "FnValue", "line": 9, "package": "cargo-mutants-testdata-fails-without-feature", "replacement": "0", "return_type": "-> u32" }, { "file": "src/bin/factorial.rs", "function": "factorial", "genre": "FnValue", "line": 9, "package": "cargo-mutants-testdata-fails-without-feature", "replacement": "1", "return_type": "-> u32" } ] ``` ## testdata/tree/hang_avoided_by_attr ```json [ { "file": "src/lib.rs", "function": "controlled_loop", "genre": "FnValue", "line": 14, "package": "cargo-mutants-testdata-hang-avoided-by-attr", "replacement": "()", "return_type": "" } ] ``` ## testdata/tree/hang_when_mutated ```json [ { "file": "src/lib.rs", "function": "should_stop", "genre": "FnValue", "line": 12, "package": "cargo-mutants-testdata-hang-when-mutated", "replacement": "true", "return_type": "-> bool" }, { "file": "src/lib.rs", "function": "should_stop", "genre": "FnValue", "line": 12, "package": "cargo-mutants-testdata-hang-when-mutated", "replacement": "false", "return_type": "-> bool" }, { "file": "src/lib.rs", "function": "controlled_loop", "genre": "FnValue", "line": 25, "package": "cargo-mutants-testdata-hang-when-mutated", "replacement": "0", "return_type": "-> usize" }, { "file": "src/lib.rs", "function": "controlled_loop", "genre": "FnValue", "line": 25, "package": "cargo-mutants-testdata-hang-when-mutated", "replacement": "1", "return_type": "-> usize" } ] ``` ## testdata/tree/insta ```json [ { "file": "src/lib.rs", "function": "say_hello", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-insta", "replacement": "String::new()", "return_type": "-> String" }, { "file": "src/lib.rs", "function": "say_hello", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-insta", "replacement": "\"xyzzy\".into()", "return_type": "-> String" } ] ``` ## testdata/tree/integration_tests ```json [ { "file": "src/lib.rs", "function": "double", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-integration-tests", "replacement": "0", "return_type": "-> u32" }, { "file": "src/lib.rs", "function": "double", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-integration-tests", "replacement": "1", "return_type": "-> u32" } ] ``` ## testdata/tree/missing_test ```json [ { "file": "src/lib.rs", "function": "is_symlink", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-missing-test", "replacement": "true", "return_type": "-> bool" }, { "file": "src/lib.rs", "function": "is_symlink", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-missing-test", "replacement": "false", "return_type": "-> bool" } ] ``` ## testdata/tree/mut_ref ```json [ { "file": "src/lib.rs", "function": "returns_mut_ref", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-mut-ref", "replacement": "Box::leak(Box::new(0))", "return_type": "-> &mut u32" }, { "file": "src/lib.rs", "function": "returns_mut_ref", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-mut-ref", "replacement": "Box::leak(Box::new(1))", "return_type": "-> &mut u32" } ] ``` ## testdata/tree/never_type ```json [] ``` ## testdata/tree/nightly_only ```json [ { "file": "src/lib.rs", "function": "box_an_int", "genre": "FnValue", "line": 2, "package": "nightly_only", "replacement": "Box::new(0)", "return_type": "-> Box" }, { "file": "src/lib.rs", "function": "box_an_int", "genre": "FnValue", "line": 2, "package": "nightly_only", "replacement": "Box::new(1)", "return_type": "-> Box" }, { "file": "src/lib.rs", "function": "box_an_int", "genre": "FnValue", "line": 2, "package": "nightly_only", "replacement": "Box::new(-1)", "return_type": "-> Box" } ] ``` ## testdata/tree/override_dependency ```json [ { "file": "src/lib.rs", "function": "is_even", "genre": "FnValue", "line": 6, "package": "cargo-mutants-testdata-override-dependency", "replacement": "true", "return_type": "-> bool" }, { "file": "src/lib.rs", "function": "is_even", "genre": "FnValue", "line": 6, "package": "cargo-mutants-testdata-override-dependency", "replacement": "false", "return_type": "-> bool" } ] ``` ## testdata/tree/package_fails ```json [ { "file": "failing/src/lib.rs", "function": "triple", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-package-fails-failing", "replacement": "0", "return_type": "-> usize" }, { "file": "failing/src/lib.rs", "function": "triple", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-package-fails-failing", "replacement": "1", "return_type": "-> usize" }, { "file": "passing/src/lib.rs", "function": "triple", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-package-fails-passing", "replacement": "0", "return_type": "-> usize" }, { "file": "passing/src/lib.rs", "function": "triple", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-package-fails-passing", "replacement": "1", "return_type": "-> usize" } ] ``` ## testdata/tree/patch_dependency ```json [ { "file": "src/lib.rs", "function": "is_even", "genre": "FnValue", "line": 6, "package": "cargo-mutants-testdata-patch-dependency", "replacement": "true", "return_type": "-> bool" }, { "file": "src/lib.rs", "function": "is_even", "genre": "FnValue", "line": 6, "package": "cargo-mutants-testdata-patch-dependency", "replacement": "false", "return_type": "-> bool" } ] ``` ## testdata/tree/relative_dependency ```json [ { "file": "src/lib.rs", "function": "double_factorial", "genre": "FnValue", "line": 5, "package": "cargo-mutants-testdata-relative-dependency", "replacement": "0", "return_type": "-> u32" }, { "file": "src/lib.rs", "function": "double_factorial", "genre": "FnValue", "line": 5, "package": "cargo-mutants-testdata-relative-dependency", "replacement": "1", "return_type": "-> u32" } ] ``` ## testdata/tree/replace_dependency ```json [ { "file": "src/lib.rs", "function": "is_even", "genre": "FnValue", "line": 6, "package": "cargo-mutants-testdata-replace-dependency", "replacement": "true", "return_type": "-> bool" }, { "file": "src/lib.rs", "function": "is_even", "genre": "FnValue", "line": 6, "package": "cargo-mutants-testdata-replace-dependency", "replacement": "false", "return_type": "-> bool" } ] ``` ## testdata/tree/small_well_tested ```json [ { "file": "src/lib.rs", "function": "factorial", "genre": "FnValue", "line": 4, "package": "cargo-mutants-testdata-small-well-tested", "replacement": "0", "return_type": "-> u32" }, { "file": "src/lib.rs", "function": "factorial", "genre": "FnValue", "line": 4, "package": "cargo-mutants-testdata-small-well-tested", "replacement": "1", "return_type": "-> u32" } ] ``` ## testdata/tree/strict_warnings ```json [ { "file": "src/lib.rs", "function": "some_fn", "genre": "FnValue", "line": 5, "package": "cargo-mutants-testdata-strict-warnings", "replacement": "0", "return_type": "-> usize" }, { "file": "src/lib.rs", "function": "some_fn", "genre": "FnValue", "line": 5, "package": "cargo-mutants-testdata-strict-warnings", "replacement": "1", "return_type": "-> usize" } ] ``` ## testdata/tree/struct_with_no_default ```json [ { "file": "src/lib.rs", "function": "make_an_s", "genre": "FnValue", "line": 11, "package": "cargo-mutants-testdata-struct-with-no-default", "replacement": "Default::default()", "return_type": "-> S" } ] ``` ## testdata/tree/typecheck_fails ```json [ { "file": "src/lib.rs", "function": "try_value_coercion", "genre": "FnValue", "line": 5, "package": "mutants-testdata-typecheck-fails", "replacement": "String::new()", "return_type": "-> String" }, { "file": "src/lib.rs", "function": "try_value_coercion", "genre": "FnValue", "line": 5, "package": "mutants-testdata-typecheck-fails", "replacement": "\"xyzzy\".into()", "return_type": "-> String" } ] ``` ## testdata/tree/unapply ```json [ { "file": "src/a.rs", "function": "one", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-unapply", "replacement": "0", "return_type": "-> i32" }, { "file": "src/a.rs", "function": "one", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-unapply", "replacement": "1", "return_type": "-> i32" }, { "file": "src/a.rs", "function": "one", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-unapply", "replacement": "-1", "return_type": "-> i32" }, { "file": "src/b.rs", "function": "one_untested", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-unapply", "replacement": "0", "return_type": "-> i32" }, { "file": "src/b.rs", "function": "one_untested", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-unapply", "replacement": "1", "return_type": "-> i32" }, { "file": "src/b.rs", "function": "one_untested", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-unapply", "replacement": "-1", "return_type": "-> i32" }, { "file": "src/c.rs", "function": "one", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-unapply", "replacement": "0", "return_type": "-> i32" }, { "file": "src/c.rs", "function": "one", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-unapply", "replacement": "1", "return_type": "-> i32" }, { "file": "src/c.rs", "function": "one", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-unapply", "replacement": "-1", "return_type": "-> i32" } ] ``` ## testdata/tree/unsafe ```json [] ``` ## testdata/tree/well_tested ```json [ { "file": "src/arc.rs", "function": "return_arc", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Arc::new(String::new())", "return_type": "-> Arc" }, { "file": "src/arc.rs", "function": "return_arc", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Arc::new(\"xyzzy\".into())", "return_type": "-> Arc" }, { "file": "src/inside_mod.rs", "function": "outer::inner::name", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "\"\"", "return_type": "-> &'static str" }, { "file": "src/inside_mod.rs", "function": "outer::inner::name", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "\"xyzzy\"", "return_type": "-> &'static str" }, { "file": "src/methods.rs", "function": "Foo::double", "genre": "FnValue", "line": 16, "package": "cargo-mutants-testdata-well-tested", "replacement": "()", "return_type": "" }, { "file": "src/methods.rs", "function": "::fmt", "genre": "FnValue", "line": 22, "package": "cargo-mutants-testdata-well-tested", "replacement": "Ok(Default::default())", "return_type": "-> fmt::Result" }, { "file": "src/methods.rs", "function": "::fmt", "genre": "FnValue", "line": 28, "package": "cargo-mutants-testdata-well-tested", "replacement": "Ok(Default::default())", "return_type": "-> fmt::Result" }, { "file": "src/nested_function.rs", "function": "has_nested", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-well-tested", "replacement": "0", "return_type": "-> u32" }, { "file": "src/nested_function.rs", "function": "has_nested", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-well-tested", "replacement": "1", "return_type": "-> u32" }, { "file": "src/nested_function.rs", "function": "has_nested::inner", "genre": "FnValue", "line": 2, "package": "cargo-mutants-testdata-well-tested", "replacement": "0", "return_type": "-> u32" }, { "file": "src/nested_function.rs", "function": "has_nested::inner", "genre": "FnValue", "line": 2, "package": "cargo-mutants-testdata-well-tested", "replacement": "1", "return_type": "-> u32" }, { "file": "src/numbers.rs", "function": "double_float", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-well-tested", "replacement": "0.0", "return_type": "-> f32" }, { "file": "src/numbers.rs", "function": "double_float", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-well-tested", "replacement": "1.0", "return_type": "-> f32" }, { "file": "src/numbers.rs", "function": "double_float", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-well-tested", "replacement": "-1.0", "return_type": "-> f32" }, { "file": "src/result.rs", "function": "simple_result", "genre": "FnValue", "line": 5, "package": "cargo-mutants-testdata-well-tested", "replacement": "Ok(\"\")", "return_type": "-> Result<&'static str, ()>" }, { "file": "src/result.rs", "function": "simple_result", "genre": "FnValue", "line": 5, "package": "cargo-mutants-testdata-well-tested", "replacement": "Ok(\"xyzzy\")", "return_type": "-> Result<&'static str, ()>" }, { "file": "src/result.rs", "function": "error_if_negative", "genre": "FnValue", "line": 9, "package": "cargo-mutants-testdata-well-tested", "replacement": "Ok(())", "return_type": "-> Result<(), ()>" }, { "file": "src/result.rs", "function": "result_with_no_apparent_type_args", "genre": "FnValue", "line": 17, "package": "cargo-mutants-testdata-well-tested", "replacement": "Ok(Default::default())", "return_type": "-> std::fmt::Result" }, { "file": "src/sets.rs", "function": "make_a_set", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "BTreeSet::new()", "return_type": "-> BTreeSet" }, { "file": "src/sets.rs", "function": "make_a_set", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "BTreeSet::from_iter([String::new()])", "return_type": "-> BTreeSet" }, { "file": "src/sets.rs", "function": "make_a_set", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "BTreeSet::from_iter([\"xyzzy\".into()])", "return_type": "-> BTreeSet" }, { "file": "src/simple_fns.rs", "function": "returns_unit", "genre": "FnValue", "line": 7, "package": "cargo-mutants-testdata-well-tested", "replacement": "()", "return_type": "" }, { "file": "src/simple_fns.rs", "function": "returns_42u32", "genre": "FnValue", "line": 12, "package": "cargo-mutants-testdata-well-tested", "replacement": "0", "return_type": "-> u32" }, { "file": "src/simple_fns.rs", "function": "returns_42u32", "genre": "FnValue", "line": 12, "package": "cargo-mutants-testdata-well-tested", "replacement": "1", "return_type": "-> u32" }, { "file": "src/simple_fns.rs", "function": "divisible_by_three", "genre": "FnValue", "line": 17, "package": "cargo-mutants-testdata-well-tested", "replacement": "true", "return_type": "-> bool" }, { "file": "src/simple_fns.rs", "function": "divisible_by_three", "genre": "FnValue", "line": 17, "package": "cargo-mutants-testdata-well-tested", "replacement": "false", "return_type": "-> bool" }, { "file": "src/simple_fns.rs", "function": "double_string", "genre": "FnValue", "line": 26, "package": "cargo-mutants-testdata-well-tested", "replacement": "String::new()", "return_type": "-> String" }, { "file": "src/simple_fns.rs", "function": "double_string", "genre": "FnValue", "line": 26, "package": "cargo-mutants-testdata-well-tested", "replacement": "\"xyzzy\".into()", "return_type": "-> String" }, { "file": "src/slices.rs", "function": "pad", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(Vec::new())", "return_type": "-> &'a[Cow<'static, str>]" }, { "file": "src/slices.rs", "function": "pad", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(vec![Cow::Borrowed(\"\")])", "return_type": "-> &'a[Cow<'static, str>]" }, { "file": "src/slices.rs", "function": "pad", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(vec![Cow::Owned(\"\".to_owned())])", "return_type": "-> &'a[Cow<'static, str>]" }, { "file": "src/slices.rs", "function": "pad", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(vec![Cow::Borrowed(\"xyzzy\")])", "return_type": "-> &'a[Cow<'static, str>]" }, { "file": "src/slices.rs", "function": "pad", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(vec![Cow::Owned(\"xyzzy\".to_owned())])", "return_type": "-> &'a[Cow<'static, str>]" }, { "file": "src/slices.rs", "function": "return_mut_slice", "genre": "FnValue", "line": 12, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(Vec::new())", "return_type": "-> &mut[usize]" }, { "file": "src/slices.rs", "function": "return_mut_slice", "genre": "FnValue", "line": 12, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(vec![0])", "return_type": "-> &mut[usize]" }, { "file": "src/slices.rs", "function": "return_mut_slice", "genre": "FnValue", "line": 12, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(vec![1])", "return_type": "-> &mut[usize]" }, { "file": "src/struct_with_lifetime.rs", "function": "Lex<'buf>::buf_len", "genre": "FnValue", "line": 14, "package": "cargo-mutants-testdata-well-tested", "replacement": "0", "return_type": "-> usize" }, { "file": "src/struct_with_lifetime.rs", "function": "Lex<'buf>::buf_len", "genre": "FnValue", "line": 14, "package": "cargo-mutants-testdata-well-tested", "replacement": "1", "return_type": "-> usize" } ] ``` ## testdata/tree/with_child_directories ```json [ { "file": "src/methods.rs", "function": "double", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "0", "return_type": "-> usize" }, { "file": "src/methods.rs", "function": "double", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "1", "return_type": "-> usize" }, { "file": "src/module/module_methods.rs", "function": "double", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "0", "return_type": "-> usize" }, { "file": "src/module/module_methods.rs", "function": "double", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "1", "return_type": "-> usize" }, { "file": "src/module/utils/inside_mod.rs", "function": "outer::inner::name", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "\"\"", "return_type": "-> &'static str" }, { "file": "src/module/utils/inside_mod.rs", "function": "outer::inner::name", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "\"xyzzy\"", "return_type": "-> &'static str" }, { "file": "src/module/utils/nested_function.rs", "function": "has_nested", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "0", "return_type": "-> u32" }, { "file": "src/module/utils/nested_function.rs", "function": "has_nested", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "1", "return_type": "-> u32" }, { "file": "src/module/utils/nested_function.rs", "function": "has_nested::inner", "genre": "FnValue", "line": 2, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "0", "return_type": "-> u32" }, { "file": "src/module/utils/nested_function.rs", "function": "has_nested::inner", "genre": "FnValue", "line": 2, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "1", "return_type": "-> u32" }, { "file": "src/module/utils/sub_utils/subutils_nested_function.rs", "function": "has_nested", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "0", "return_type": "-> u32" }, { "file": "src/module/utils/sub_utils/subutils_nested_function.rs", "function": "has_nested", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "1", "return_type": "-> u32" }, { "file": "src/module/utils/sub_utils/subutils_nested_function.rs", "function": "has_nested::inner", "genre": "FnValue", "line": 2, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "0", "return_type": "-> u32" }, { "file": "src/module/utils/sub_utils/subutils_nested_function.rs", "function": "has_nested::inner", "genre": "FnValue", "line": 2, "package": "cargo-mutants-testdata-with-child-directories", "replacement": "1", "return_type": "-> u32" } ] ``` ## testdata/tree/workspace ```json [ { "file": "utils/src/lib.rs", "function": "triple", "genre": "FnValue", "line": 1, "package": "cargo_mutants_testdata_workspace_utils", "replacement": "0", "return_type": "-> i32" }, { "file": "utils/src/lib.rs", "function": "triple", "genre": "FnValue", "line": 1, "package": "cargo_mutants_testdata_workspace_utils", "replacement": "1", "return_type": "-> i32" }, { "file": "utils/src/lib.rs", "function": "triple", "genre": "FnValue", "line": 1, "package": "cargo_mutants_testdata_workspace_utils", "replacement": "-1", "return_type": "-> i32" }, { "file": "main/src/main.rs", "function": "factorial", "genre": "FnValue", "line": 11, "package": "main", "replacement": "0", "return_type": "-> u32" }, { "file": "main/src/main.rs", "function": "factorial", "genre": "FnValue", "line": 11, "package": "main", "replacement": "1", "return_type": "-> u32" }, { "file": "main2/src/main.rs", "function": "triple_3", "genre": "FnValue", "line": 9, "package": "main2", "replacement": "0", "return_type": "-> i32" }, { "file": "main2/src/main.rs", "function": "triple_3", "genre": "FnValue", "line": 9, "package": "main2", "replacement": "1", "return_type": "-> i32" }, { "file": "main2/src/main.rs", "function": "triple_3", "genre": "FnValue", "line": 9, "package": "main2", "replacement": "-1", "return_type": "-> i32" } ] ``` cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_in_all_trees_as_text.snap000064400000000000000000000217101046102023000273550ustar 00000000000000--- source: tests/cli/main.rs expression: buf --- ## testdata/tree/already_failing_doctests ``` src/lib.rs:9: replace takes_one_arg -> usize with 0 src/lib.rs:9: replace takes_one_arg -> usize with 1 ``` ## testdata/tree/already_failing_tests ``` src/lib.rs:1: replace factorial -> u32 with 0 src/lib.rs:1: replace factorial -> u32 with 1 ``` ## testdata/tree/already_hangs ``` src/lib.rs:8: replace infinite_loop with () ``` ## testdata/tree/cdylib ``` src/entry.rs:1: replace factorial -> u32 with 0 src/entry.rs:1: replace factorial -> u32 with 1 ``` ## testdata/tree/cfg_attr_mutants_skip ``` ``` ## testdata/tree/cfg_attr_test_skip ``` src/lib.rs:17: replace double -> usize with 0 src/lib.rs:17: replace double -> usize with 1 ``` ## testdata/tree/dependency ``` src/lib.rs:1: replace factorial -> u32 with 0 src/lib.rs:1: replace factorial -> u32 with 1 ``` ## testdata/tree/error_value ``` src/lib.rs:3: replace even_is_ok -> Result with Ok(0) src/lib.rs:3: replace even_is_ok -> Result with Ok(1) src/lib.rs:3: replace even_is_ok -> Result with Err("injected") ``` ## testdata/tree/everything_skipped ``` ``` ## testdata/tree/factorial ``` src/bin/factorial.rs:1: replace main with () src/bin/factorial.rs:7: replace factorial -> u32 with 0 src/bin/factorial.rs:7: replace factorial -> u32 with 1 ``` ## testdata/tree/fails_without_feature ``` src/bin/factorial.rs:9: replace factorial -> u32 with 0 src/bin/factorial.rs:9: replace factorial -> u32 with 1 ``` ## testdata/tree/hang_avoided_by_attr ``` src/lib.rs:14: replace controlled_loop with () ``` ## testdata/tree/hang_when_mutated ``` src/lib.rs:12: replace should_stop -> bool with true src/lib.rs:12: replace should_stop -> bool with false src/lib.rs:25: replace controlled_loop -> usize with 0 src/lib.rs:25: replace controlled_loop -> usize with 1 ``` ## testdata/tree/insta ``` src/lib.rs:1: replace say_hello -> String with String::new() src/lib.rs:1: replace say_hello -> String with "xyzzy".into() ``` ## testdata/tree/integration_tests ``` src/lib.rs:1: replace double -> u32 with 0 src/lib.rs:1: replace double -> u32 with 1 ``` ## testdata/tree/missing_test ``` src/lib.rs:1: replace is_symlink -> bool with true src/lib.rs:1: replace is_symlink -> bool with false ``` ## testdata/tree/mut_ref ``` src/lib.rs:1: replace returns_mut_ref -> &mut u32 with Box::leak(Box::new(0)) src/lib.rs:1: replace returns_mut_ref -> &mut u32 with Box::leak(Box::new(1)) ``` ## testdata/tree/never_type ``` ``` ## testdata/tree/nightly_only ``` src/lib.rs:2: replace box_an_int -> Box with Box::new(0) src/lib.rs:2: replace box_an_int -> Box with Box::new(1) src/lib.rs:2: replace box_an_int -> Box with Box::new(-1) ``` ## testdata/tree/override_dependency ``` src/lib.rs:6: replace is_even -> bool with true src/lib.rs:6: replace is_even -> bool with false ``` ## testdata/tree/package_fails ``` failing/src/lib.rs:1: replace triple -> usize with 0 failing/src/lib.rs:1: replace triple -> usize with 1 passing/src/lib.rs:1: replace triple -> usize with 0 passing/src/lib.rs:1: replace triple -> usize with 1 ``` ## testdata/tree/patch_dependency ``` src/lib.rs:6: replace is_even -> bool with true src/lib.rs:6: replace is_even -> bool with false ``` ## testdata/tree/relative_dependency ``` src/lib.rs:5: replace double_factorial -> u32 with 0 src/lib.rs:5: replace double_factorial -> u32 with 1 ``` ## testdata/tree/replace_dependency ``` src/lib.rs:6: replace is_even -> bool with true src/lib.rs:6: replace is_even -> bool with false ``` ## testdata/tree/small_well_tested ``` src/lib.rs:4: replace factorial -> u32 with 0 src/lib.rs:4: replace factorial -> u32 with 1 ``` ## testdata/tree/strict_warnings ``` src/lib.rs:5: replace some_fn -> usize with 0 src/lib.rs:5: replace some_fn -> usize with 1 ``` ## testdata/tree/struct_with_no_default ``` src/lib.rs:11: replace make_an_s -> S with Default::default() ``` ## testdata/tree/typecheck_fails ``` src/lib.rs:5: replace try_value_coercion -> String with String::new() src/lib.rs:5: replace try_value_coercion -> String with "xyzzy".into() ``` ## testdata/tree/unapply ``` src/a.rs:1: replace one -> i32 with 0 src/a.rs:1: replace one -> i32 with 1 src/a.rs:1: replace one -> i32 with -1 src/b.rs:1: replace one_untested -> i32 with 0 src/b.rs:1: replace one_untested -> i32 with 1 src/b.rs:1: replace one_untested -> i32 with -1 src/c.rs:1: replace one -> i32 with 0 src/c.rs:1: replace one -> i32 with 1 src/c.rs:1: replace one -> i32 with -1 ``` ## testdata/tree/unsafe ``` ``` ## testdata/tree/well_tested ``` src/arc.rs:3: replace return_arc -> Arc with Arc::new(String::new()) src/arc.rs:3: replace return_arc -> Arc with Arc::new("xyzzy".into()) src/inside_mod.rs:3: replace outer::inner::name -> &'static str with "" src/inside_mod.rs:3: replace outer::inner::name -> &'static str with "xyzzy" src/methods.rs:16: replace Foo::double with () src/methods.rs:22: replace ::fmt -> fmt::Result with Ok(Default::default()) src/methods.rs:28: replace ::fmt -> fmt::Result with Ok(Default::default()) src/nested_function.rs:1: replace has_nested -> u32 with 0 src/nested_function.rs:1: replace has_nested -> u32 with 1 src/nested_function.rs:2: replace has_nested::inner -> u32 with 0 src/nested_function.rs:2: replace has_nested::inner -> u32 with 1 src/numbers.rs:1: replace double_float -> f32 with 0.0 src/numbers.rs:1: replace double_float -> f32 with 1.0 src/numbers.rs:1: replace double_float -> f32 with -1.0 src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("") src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("xyzzy") src/result.rs:9: replace error_if_negative -> Result<(), ()> with Ok(()) src/result.rs:17: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default()) src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::new() src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::from_iter([String::new()]) src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::from_iter(["xyzzy".into()]) src/simple_fns.rs:7: replace returns_unit with () src/simple_fns.rs:12: replace returns_42u32 -> u32 with 0 src/simple_fns.rs:12: replace returns_42u32 -> u32 with 1 src/simple_fns.rs:17: replace divisible_by_three -> bool with true src/simple_fns.rs:17: replace divisible_by_three -> bool with false src/simple_fns.rs:26: replace double_string -> String with String::new() src/simple_fns.rs:26: replace double_string -> String with "xyzzy".into() src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(Vec::new()) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Borrowed("")]) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Owned("".to_owned())]) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Borrowed("xyzzy")]) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Owned("xyzzy".to_owned())]) src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(Vec::new()) src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![0]) src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![1]) src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 0 src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 1 ``` ## testdata/tree/with_child_directories ``` src/methods.rs:1: replace double -> usize with 0 src/methods.rs:1: replace double -> usize with 1 src/module/module_methods.rs:1: replace double -> usize with 0 src/module/module_methods.rs:1: replace double -> usize with 1 src/module/utils/inside_mod.rs:3: replace outer::inner::name -> &'static str with "" src/module/utils/inside_mod.rs:3: replace outer::inner::name -> &'static str with "xyzzy" src/module/utils/nested_function.rs:1: replace has_nested -> u32 with 0 src/module/utils/nested_function.rs:1: replace has_nested -> u32 with 1 src/module/utils/nested_function.rs:2: replace has_nested::inner -> u32 with 0 src/module/utils/nested_function.rs:2: replace has_nested::inner -> u32 with 1 src/module/utils/sub_utils/subutils_nested_function.rs:1: replace has_nested -> u32 with 0 src/module/utils/sub_utils/subutils_nested_function.rs:1: replace has_nested -> u32 with 1 src/module/utils/sub_utils/subutils_nested_function.rs:2: replace has_nested::inner -> u32 with 0 src/module/utils/sub_utils/subutils_nested_function.rs:2: replace has_nested::inner -> u32 with 1 ``` ## testdata/tree/workspace ``` utils/src/lib.rs:1: replace triple -> i32 with 0 utils/src/lib.rs:1: replace triple -> i32 with 1 utils/src/lib.rs:1: replace triple -> i32 with -1 main/src/main.rs:11: replace factorial -> u32 with 0 main/src/main.rs:11: replace factorial -> u32 with 1 main2/src/main.rs:9: replace triple_3 -> i32 with 0 main2/src/main.rs:9: replace triple_3 -> i32 with 1 main2/src/main.rs:9: replace triple_3 -> i32 with -1 ``` cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_in_cfg_attr_mutants_skip.snap000064400000000000000000000001241046102023000302420ustar 00000000000000--- source: tests/cli.rs expression: "String::from_utf8_lossy(&output.stdout)" --- cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_in_cfg_attr_mutants_skip_json.snap000064400000000000000000000001261046102023000312750ustar 00000000000000--- source: tests/cli.rs expression: "String::from_utf8_lossy(&output.stdout)" --- [] cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_in_cfg_attr_test_skip.snap000064400000000000000000000002651046102023000275340ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- src/lib.rs:17: replace double -> usize with 0 src/lib.rs:17: replace double -> usize with 1 cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_in_cfg_attr_test_skip_json.snap000064400000000000000000000010111046102023000305530ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- [ { "file": "src/lib.rs", "function": "double", "genre": "FnValue", "line": 17, "package": "cargo-mutants-testdata-cfg-attr-test-skip", "replacement": "0", "return_type": "-> usize" }, { "file": "src/lib.rs", "function": "double", "genre": "FnValue", "line": 17, "package": "cargo-mutants-testdata-cfg-attr-test-skip", "replacement": "1", "return_type": "-> usize" } ] cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_in_factorial.snap000064400000000000000000000003661046102023000256240ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- src/bin/factorial.rs:1: replace main with () src/bin/factorial.rs:7: replace factorial -> u32 with 0 src/bin/factorial.rs:7: replace factorial -> u32 with 1 cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_in_factorial_json.snap000064400000000000000000000013311046102023000266460ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- [ { "file": "src/bin/factorial.rs", "function": "main", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-factorial", "replacement": "()", "return_type": "" }, { "file": "src/bin/factorial.rs", "function": "factorial", "genre": "FnValue", "line": 7, "package": "cargo-mutants-testdata-factorial", "replacement": "0", "return_type": "-> u32" }, { "file": "src/bin/factorial.rs", "function": "factorial", "genre": "FnValue", "line": 7, "package": "cargo-mutants-testdata-factorial", "replacement": "1", "return_type": "-> u32" } ] cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_json_well_tested.snap000064400000000000000000000217341046102023000265400ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- [ { "file": "src/arc.rs", "function": "return_arc", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Arc::new(String::new())", "return_type": "-> Arc" }, { "file": "src/arc.rs", "function": "return_arc", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Arc::new(\"xyzzy\".into())", "return_type": "-> Arc" }, { "file": "src/inside_mod.rs", "function": "outer::inner::name", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "\"\"", "return_type": "-> &'static str" }, { "file": "src/inside_mod.rs", "function": "outer::inner::name", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "\"xyzzy\"", "return_type": "-> &'static str" }, { "file": "src/methods.rs", "function": "Foo::double", "genre": "FnValue", "line": 16, "package": "cargo-mutants-testdata-well-tested", "replacement": "()", "return_type": "" }, { "file": "src/methods.rs", "function": "::fmt", "genre": "FnValue", "line": 22, "package": "cargo-mutants-testdata-well-tested", "replacement": "Ok(Default::default())", "return_type": "-> fmt::Result" }, { "file": "src/methods.rs", "function": "::fmt", "genre": "FnValue", "line": 28, "package": "cargo-mutants-testdata-well-tested", "replacement": "Ok(Default::default())", "return_type": "-> fmt::Result" }, { "file": "src/nested_function.rs", "function": "has_nested", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-well-tested", "replacement": "0", "return_type": "-> u32" }, { "file": "src/nested_function.rs", "function": "has_nested", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-well-tested", "replacement": "1", "return_type": "-> u32" }, { "file": "src/nested_function.rs", "function": "has_nested::inner", "genre": "FnValue", "line": 2, "package": "cargo-mutants-testdata-well-tested", "replacement": "0", "return_type": "-> u32" }, { "file": "src/nested_function.rs", "function": "has_nested::inner", "genre": "FnValue", "line": 2, "package": "cargo-mutants-testdata-well-tested", "replacement": "1", "return_type": "-> u32" }, { "file": "src/numbers.rs", "function": "double_float", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-well-tested", "replacement": "0.0", "return_type": "-> f32" }, { "file": "src/numbers.rs", "function": "double_float", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-well-tested", "replacement": "1.0", "return_type": "-> f32" }, { "file": "src/numbers.rs", "function": "double_float", "genre": "FnValue", "line": 1, "package": "cargo-mutants-testdata-well-tested", "replacement": "-1.0", "return_type": "-> f32" }, { "file": "src/result.rs", "function": "simple_result", "genre": "FnValue", "line": 5, "package": "cargo-mutants-testdata-well-tested", "replacement": "Ok(\"\")", "return_type": "-> Result<&'static str, ()>" }, { "file": "src/result.rs", "function": "simple_result", "genre": "FnValue", "line": 5, "package": "cargo-mutants-testdata-well-tested", "replacement": "Ok(\"xyzzy\")", "return_type": "-> Result<&'static str, ()>" }, { "file": "src/result.rs", "function": "error_if_negative", "genre": "FnValue", "line": 9, "package": "cargo-mutants-testdata-well-tested", "replacement": "Ok(())", "return_type": "-> Result<(), ()>" }, { "file": "src/result.rs", "function": "result_with_no_apparent_type_args", "genre": "FnValue", "line": 17, "package": "cargo-mutants-testdata-well-tested", "replacement": "Ok(Default::default())", "return_type": "-> std::fmt::Result" }, { "file": "src/sets.rs", "function": "make_a_set", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "BTreeSet::new()", "return_type": "-> BTreeSet" }, { "file": "src/sets.rs", "function": "make_a_set", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "BTreeSet::from_iter([String::new()])", "return_type": "-> BTreeSet" }, { "file": "src/sets.rs", "function": "make_a_set", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "BTreeSet::from_iter([\"xyzzy\".into()])", "return_type": "-> BTreeSet" }, { "file": "src/simple_fns.rs", "function": "returns_unit", "genre": "FnValue", "line": 7, "package": "cargo-mutants-testdata-well-tested", "replacement": "()", "return_type": "" }, { "file": "src/simple_fns.rs", "function": "returns_42u32", "genre": "FnValue", "line": 12, "package": "cargo-mutants-testdata-well-tested", "replacement": "0", "return_type": "-> u32" }, { "file": "src/simple_fns.rs", "function": "returns_42u32", "genre": "FnValue", "line": 12, "package": "cargo-mutants-testdata-well-tested", "replacement": "1", "return_type": "-> u32" }, { "file": "src/simple_fns.rs", "function": "divisible_by_three", "genre": "FnValue", "line": 17, "package": "cargo-mutants-testdata-well-tested", "replacement": "true", "return_type": "-> bool" }, { "file": "src/simple_fns.rs", "function": "divisible_by_three", "genre": "FnValue", "line": 17, "package": "cargo-mutants-testdata-well-tested", "replacement": "false", "return_type": "-> bool" }, { "file": "src/simple_fns.rs", "function": "double_string", "genre": "FnValue", "line": 26, "package": "cargo-mutants-testdata-well-tested", "replacement": "String::new()", "return_type": "-> String" }, { "file": "src/simple_fns.rs", "function": "double_string", "genre": "FnValue", "line": 26, "package": "cargo-mutants-testdata-well-tested", "replacement": "\"xyzzy\".into()", "return_type": "-> String" }, { "file": "src/slices.rs", "function": "pad", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(Vec::new())", "return_type": "-> &'a[Cow<'static, str>]" }, { "file": "src/slices.rs", "function": "pad", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(vec![Cow::Borrowed(\"\")])", "return_type": "-> &'a[Cow<'static, str>]" }, { "file": "src/slices.rs", "function": "pad", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(vec![Cow::Owned(\"\".to_owned())])", "return_type": "-> &'a[Cow<'static, str>]" }, { "file": "src/slices.rs", "function": "pad", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(vec![Cow::Borrowed(\"xyzzy\")])", "return_type": "-> &'a[Cow<'static, str>]" }, { "file": "src/slices.rs", "function": "pad", "genre": "FnValue", "line": 3, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(vec![Cow::Owned(\"xyzzy\".to_owned())])", "return_type": "-> &'a[Cow<'static, str>]" }, { "file": "src/slices.rs", "function": "return_mut_slice", "genre": "FnValue", "line": 12, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(Vec::new())", "return_type": "-> &mut[usize]" }, { "file": "src/slices.rs", "function": "return_mut_slice", "genre": "FnValue", "line": 12, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(vec![0])", "return_type": "-> &mut[usize]" }, { "file": "src/slices.rs", "function": "return_mut_slice", "genre": "FnValue", "line": 12, "package": "cargo-mutants-testdata-well-tested", "replacement": "Vec::leak(vec![1])", "return_type": "-> &mut[usize]" }, { "file": "src/struct_with_lifetime.rs", "function": "Lex<'buf>::buf_len", "genre": "FnValue", "line": 14, "package": "cargo-mutants-testdata-well-tested", "replacement": "0", "return_type": "-> usize" }, { "file": "src/struct_with_lifetime.rs", "function": "Lex<'buf>::buf_len", "genre": "FnValue", "line": 14, "package": "cargo-mutants-testdata-well-tested", "replacement": "1", "return_type": "-> usize" } ] cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_regex_anchored_matches_full_line.snap000064400000000000000000000002061046102023000316750ustar 00000000000000--- source: tests/cli.rs expression: "String::from_utf8_lossy(&output.stdout)" --- src/simple_fns.rs:7: replace returns_unit with () cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_regex_filters.snap000064400000000000000000000003331046102023000260260ustar 00000000000000--- source: tests/cli.rs expression: "String::from_utf8_lossy(&output.stdout)" --- src/simple_fns.rs:17: replace divisible_by_three -> bool with true src/simple_fns.rs:17: replace divisible_by_three -> bool with false cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_regex_filters_json.snap000064400000000000000000000005001046102023000270530ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- [ { "file": "src/simple_fns.rs", "function": "divisible_by_three", "genre": "FnValue", "line": 17, "package": "cargo-mutants-testdata-well-tested", "replacement": "true", "return_type": "-> bool" } ] cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_well_tested.snap000064400000000000000000000056661046102023000255150ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- src/arc.rs:3: replace return_arc -> Arc with Arc::new(String::new()) src/arc.rs:3: replace return_arc -> Arc with Arc::new("xyzzy".into()) src/inside_mod.rs:3: replace outer::inner::name -> &'static str with "" src/inside_mod.rs:3: replace outer::inner::name -> &'static str with "xyzzy" src/methods.rs:16: replace Foo::double with () src/methods.rs:22: replace ::fmt -> fmt::Result with Ok(Default::default()) src/methods.rs:28: replace ::fmt -> fmt::Result with Ok(Default::default()) src/nested_function.rs:1: replace has_nested -> u32 with 0 src/nested_function.rs:1: replace has_nested -> u32 with 1 src/nested_function.rs:2: replace has_nested::inner -> u32 with 0 src/nested_function.rs:2: replace has_nested::inner -> u32 with 1 src/numbers.rs:1: replace double_float -> f32 with 0.0 src/numbers.rs:1: replace double_float -> f32 with 1.0 src/numbers.rs:1: replace double_float -> f32 with -1.0 src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("") src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("xyzzy") src/result.rs:9: replace error_if_negative -> Result<(), ()> with Ok(()) src/result.rs:17: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default()) src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::new() src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::from_iter([String::new()]) src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::from_iter(["xyzzy".into()]) src/simple_fns.rs:7: replace returns_unit with () src/simple_fns.rs:12: replace returns_42u32 -> u32 with 0 src/simple_fns.rs:12: replace returns_42u32 -> u32 with 1 src/simple_fns.rs:17: replace divisible_by_three -> bool with true src/simple_fns.rs:17: replace divisible_by_three -> bool with false src/simple_fns.rs:26: replace double_string -> String with String::new() src/simple_fns.rs:26: replace double_string -> String with "xyzzy".into() src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(Vec::new()) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Borrowed("")]) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Owned("".to_owned())]) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Borrowed("xyzzy")]) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Owned("xyzzy".to_owned())]) src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(Vec::new()) src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![0]) src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![1]) src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 0 src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 1 ././@LongLink00006440000000000000000000000166000000000000007776Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_well_tested_examine_and_exclude_name_filter_combined.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_well_tested_examine_and_exclude_name_fil000064400000000000000000000012021046102023000324260ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- src/module/utils/inside_mod.rs:3: replace outer::inner::name -> &'static str with "" src/module/utils/inside_mod.rs:3: replace outer::inner::name -> &'static str with "xyzzy" src/module/utils/sub_utils/subutils_nested_function.rs:1: replace has_nested -> u32 with 0 src/module/utils/sub_utils/subutils_nested_function.rs:1: replace has_nested -> u32 with 1 src/module/utils/sub_utils/subutils_nested_function.rs:2: replace has_nested::inner -> u32 with 0 src/module/utils/sub_utils/subutils_nested_function.rs:2: replace has_nested::inner -> u32 with 1 cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_well_tested_examine_name_filter.snap000064400000000000000000000005231046102023000315530ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- src/nested_function.rs:1: replace has_nested -> u32 with 0 src/nested_function.rs:1: replace has_nested -> u32 with 1 src/nested_function.rs:2: replace has_nested::inner -> u32 with 0 src/nested_function.rs:2: replace has_nested::inner -> u32 with 1 cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_well_tested_exclude_folder_filter.snap000064400000000000000000000002731046102023000321130ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- src/methods.rs:1: replace double -> usize with 0 src/methods.rs:1: replace double -> usize with 1 cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_well_tested_exclude_name_filter.snap000064400000000000000000000047661046102023000315730ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- src/arc.rs:3: replace return_arc -> Arc with Arc::new(String::new()) src/arc.rs:3: replace return_arc -> Arc with Arc::new("xyzzy".into()) src/inside_mod.rs:3: replace outer::inner::name -> &'static str with "" src/inside_mod.rs:3: replace outer::inner::name -> &'static str with "xyzzy" src/methods.rs:16: replace Foo::double with () src/methods.rs:22: replace ::fmt -> fmt::Result with Ok(Default::default()) src/methods.rs:28: replace ::fmt -> fmt::Result with Ok(Default::default()) src/nested_function.rs:1: replace has_nested -> u32 with 0 src/nested_function.rs:1: replace has_nested -> u32 with 1 src/nested_function.rs:2: replace has_nested::inner -> u32 with 0 src/nested_function.rs:2: replace has_nested::inner -> u32 with 1 src/numbers.rs:1: replace double_float -> f32 with 0.0 src/numbers.rs:1: replace double_float -> f32 with 1.0 src/numbers.rs:1: replace double_float -> f32 with -1.0 src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("") src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("xyzzy") src/result.rs:9: replace error_if_negative -> Result<(), ()> with Ok(()) src/result.rs:17: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default()) src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::new() src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::from_iter([String::new()]) src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::from_iter(["xyzzy".into()]) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(Vec::new()) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Borrowed("")]) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Owned("".to_owned())]) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Borrowed("xyzzy")]) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Owned("xyzzy".to_owned())]) src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(Vec::new()) src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![0]) src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![1]) src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 0 src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 1 ././@LongLink00006440000000000000000000000215000000000000007771Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_well_tested_multiple_examine_and_exclude_name_filter_with_files_and_folders.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_well_tested_multiple_examine_and_exclude000064400000000000000000000006061046102023000325160ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- src/module/module_methods.rs:1: replace double -> usize with 0 src/module/module_methods.rs:1: replace double -> usize with 1 src/module/utils/inside_mod.rs:3: replace outer::inner::name -> &'static str with "" src/module/utils/inside_mod.rs:3: replace outer::inner::name -> &'static str with "xyzzy" cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_with_diffs_in_factorial.snap000064400000000000000000000030211046102023000300210ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- src/bin/factorial.rs:1: replace main with () --- src/bin/factorial.rs +++ replace main with () @@ -1,12 +1,10 @@ fn main() { - for i in 1..=6 { - println!("{}! = {}", i, factorial(i)); - } +() /* ~ changed by cargo-mutants ~ */ } fn factorial(n: u32) -> u32 { let mut a = 1; for i in 2..=n { a *= i; } a src/bin/factorial.rs:7: replace factorial -> u32 with 0 --- src/bin/factorial.rs +++ replace factorial -> u32 with 0 @@ -1,19 +1,15 @@ fn main() { for i in 1..=6 { println!("{}! = {}", i, factorial(i)); } } fn factorial(n: u32) -> u32 { - let mut a = 1; - for i in 2..=n { - a *= i; - } - a +0 /* ~ changed by cargo-mutants ~ */ } #[test] fn test_factorial() { println!("factorial({}) = {}", 6, factorial(6)); // This line is here so we can see it in --nocapture assert_eq!(factorial(6), 720); } src/bin/factorial.rs:7: replace factorial -> u32 with 1 --- src/bin/factorial.rs +++ replace factorial -> u32 with 1 @@ -1,19 +1,15 @@ fn main() { for i in 1..=6 { println!("{}! = {}", i, factorial(i)); } } fn factorial(n: u32) -> u32 { - let mut a = 1; - for i in 2..=n { - a *= i; - } - a +1 /* ~ changed by cargo-mutants ~ */ } #[test] fn test_factorial() { println!("factorial({}) = {}", 6, factorial(6)); // This line is here so we can see it in --nocapture assert_eq!(factorial(6), 720); } cargo-mutants-23.10.0/tests/cli/snapshots/cli__list_mutants_with_dir_option.snap000064400000000000000000000003661046102023000263730ustar 00000000000000--- source: tests/cli/main.rs expression: "String::from_utf8_lossy(&output.stdout)" --- src/bin/factorial.rs:1: replace main with () src/bin/factorial.rs:7: replace factorial -> u32 with 0 src/bin/factorial.rs:7: replace factorial -> u32 with 1 cargo-mutants-23.10.0/tests/cli/snapshots/cli__mutants.json.snap000064400000000000000000000012741046102023000230260ustar 00000000000000--- source: tests/cli/main.rs expression: mutants_json --- [ { "package": "cargo-mutants-testdata-factorial", "file": "src/bin/factorial.rs", "line": 1, "function": "main", "return_type": "", "replacement": "()", "genre": "FnValue" }, { "package": "cargo-mutants-testdata-factorial", "file": "src/bin/factorial.rs", "line": 7, "function": "factorial", "return_type": "-> u32", "replacement": "0", "genre": "FnValue" }, { "package": "cargo-mutants-testdata-factorial", "file": "src/bin/factorial.rs", "line": 7, "function": "factorial", "return_type": "-> u32", "replacement": "1", "genre": "FnValue" } ] ././@LongLink00006440000000000000000000000172000000000000007773Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__mutants_are_unapplied_after_testing_so_later_missed_mutants_are_found.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__mutants_are_unapplied_after_testing_so_later_missed_m000064400000000000000000000006621046102023000324740ustar 00000000000000--- source: tests/cli/main.rs expression: stdout --- Found 9 mutants to test Unmutated baseline ... ok src/a.rs:1: replace one -> i32 with 1 ... NOT CAUGHT src/b.rs:1: replace one_untested -> i32 with 0 ... NOT CAUGHT src/b.rs:1: replace one_untested -> i32 with 1 ... NOT CAUGHT src/b.rs:1: replace one_untested -> i32 with -1 ... NOT CAUGHT src/c.rs:1: replace one -> i32 with 1 ... NOT CAUGHT 9 mutants tested: 5 missed, 4 caught cargo-mutants-23.10.0/tests/cli/snapshots/cli__small_well_tested_mutants_with_cargo_arg_release.snap000064400000000000000000000002031046102023000323670ustar 00000000000000--- source: tests/cli/main.rs expression: stdout --- Found 2 mutants to test Unmutated baseline ... ok 2 mutants tested: 2 caught cargo-mutants-23.10.0/tests/cli/snapshots/cli__small_well_tested_tree_is_clean.snap000064400000000000000000000003651046102023000267420ustar 00000000000000--- source: tests/cli/main.rs expression: stdout --- Found 2 mutants to test Unmutated baseline ... ok src/lib.rs:4: replace factorial -> u32 with 0 ... caught src/lib.rs:4: replace factorial -> u32 with 1 ... caught 2 mutants tested: 2 caught cargo-mutants-23.10.0/tests/cli/snapshots/cli__uncaught_mutant_in_factorial.snap000064400000000000000000000005101046102023000262730ustar 00000000000000--- source: tests/cli/main.rs expression: redact_timestamps_sizes(stdout) --- Found 3 mutants to test Unmutated baseline ... ok in x.xxxs build + x.xxxs test Auto-set test timeout to x.xxxs src/bin/factorial.rs:1: replace main with () ... NOT CAUGHT in x.xxxs build + x.xxxs test 3 mutants tested in x.xxxs: 1 missed, 2 caught cargo-mutants-23.10.0/tests/cli/snapshots/cli__uncaught_mutant_in_factorial__caught.txt.snap000064400000000000000000000002471046102023000306120ustar 00000000000000--- source: tests/cli/main.rs expression: content --- src/bin/factorial.rs:7: replace factorial -> u32 with 0 src/bin/factorial.rs:7: replace factorial -> u32 with 1 cargo-mutants-23.10.0/tests/cli/snapshots/cli__uncaught_mutant_in_factorial__missed.txt.snap000064400000000000000000000001421046102023000306150ustar 00000000000000--- source: tests/cli.rs expression: missed_txt --- src/bin/factorial.rs:1: replace main with () cargo-mutants-23.10.0/tests/cli/snapshots/cli__uncaught_mutant_in_factorial__timeout.txt.snap000064400000000000000000000000621046102023000310200ustar 00000000000000--- source: tests/cli.rs expression: content --- cargo-mutants-23.10.0/tests/cli/snapshots/cli__uncaught_mutant_in_factorial__unviable.txt.snap000064400000000000000000000000621046102023000311370ustar 00000000000000--- source: tests/cli.rs expression: content --- ././@LongLink00006440000000000000000000000154000000000000007773Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__unviable_mutation_of_struct_with_no_default__caught.txt.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__unviable_mutation_of_struct_with_no_default__caught.t000064400000000000000000000000621046102023000324210ustar 00000000000000--- source: tests/cli.rs expression: content --- ././@LongLink00006440000000000000000000000154000000000000007773Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__unviable_mutation_of_struct_with_no_default__missed.txt.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__unviable_mutation_of_struct_with_no_default__missed.t000064400000000000000000000000621046102023000324320ustar 00000000000000--- source: tests/cli.rs expression: content --- ././@LongLink00006440000000000000000000000155000000000000007774Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__unviable_mutation_of_struct_with_no_default__timeout.txt.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__unviable_mutation_of_struct_with_no_default__timeout.000064400000000000000000000000621046102023000324500ustar 00000000000000--- source: tests/cli.rs expression: content --- ././@LongLink00006440000000000000000000000156000000000000007775Lustar cargo-mutants-23.10.0/tests/cli/snapshots/cli__unviable_mutation_of_struct_with_no_default__unviable.txt.snapcargo-mutants-23.10.0/tests/cli/snapshots/cli__unviable_mutation_of_struct_with_no_default__unviable000064400000000000000000000001651046102023000325150ustar 00000000000000--- source: tests/cli/main.rs expression: content --- src/lib.rs:11: replace make_an_s -> S with Default::default() cargo-mutants-23.10.0/tests/cli/snapshots/cli__well_tested_tree_check_only.snap000064400000000000000000000063601046102023000261140ustar 00000000000000--- source: tests/cli/main.rs expression: stdout --- Found 38 mutants to test Unmutated baseline ... ok src/arc.rs:3: replace return_arc -> Arc with Arc::new(String::new()) ... ok src/arc.rs:3: replace return_arc -> Arc with Arc::new("xyzzy".into()) ... ok src/inside_mod.rs:3: replace outer::inner::name -> &'static str with "" ... ok src/inside_mod.rs:3: replace outer::inner::name -> &'static str with "xyzzy" ... ok src/methods.rs:16: replace Foo::double with () ... ok src/methods.rs:22: replace ::fmt -> fmt::Result with Ok(Default::default()) ... ok src/methods.rs:28: replace ::fmt -> fmt::Result with Ok(Default::default()) ... ok src/nested_function.rs:1: replace has_nested -> u32 with 0 ... ok src/nested_function.rs:1: replace has_nested -> u32 with 1 ... ok src/nested_function.rs:2: replace has_nested::inner -> u32 with 0 ... ok src/nested_function.rs:2: replace has_nested::inner -> u32 with 1 ... ok src/numbers.rs:1: replace double_float -> f32 with 0.0 ... ok src/numbers.rs:1: replace double_float -> f32 with 1.0 ... ok src/numbers.rs:1: replace double_float -> f32 with -1.0 ... ok src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("") ... ok src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("xyzzy") ... ok src/result.rs:9: replace error_if_negative -> Result<(), ()> with Ok(()) ... ok src/result.rs:17: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default()) ... ok src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::new() ... ok src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::from_iter([String::new()]) ... ok src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::from_iter(["xyzzy".into()]) ... ok src/simple_fns.rs:7: replace returns_unit with () ... ok src/simple_fns.rs:12: replace returns_42u32 -> u32 with 0 ... ok src/simple_fns.rs:12: replace returns_42u32 -> u32 with 1 ... ok src/simple_fns.rs:17: replace divisible_by_three -> bool with true ... ok src/simple_fns.rs:17: replace divisible_by_three -> bool with false ... ok src/simple_fns.rs:26: replace double_string -> String with String::new() ... ok src/simple_fns.rs:26: replace double_string -> String with "xyzzy".into() ... ok src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(Vec::new()) ... ok src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Borrowed("")]) ... ok src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Owned("".to_owned())]) ... ok src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Borrowed("xyzzy")]) ... ok src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Owned("xyzzy".to_owned())]) ... ok src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(Vec::new()) ... ok src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![0]) ... ok src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![1]) ... ok src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 0 ... ok src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 1 ... ok 38 mutants tested: 38 succeeded cargo-mutants-23.10.0/tests/cli/snapshots/cli__well_tested_tree_finds_no_problems.snap000064400000000000000000000066051046102023000275020ustar 00000000000000--- source: tests/cli/main.rs expression: stdout --- Found 38 mutants to test Unmutated baseline ... ok src/arc.rs:3: replace return_arc -> Arc with Arc::new(String::new()) ... caught src/arc.rs:3: replace return_arc -> Arc with Arc::new("xyzzy".into()) ... caught src/inside_mod.rs:3: replace outer::inner::name -> &'static str with "" ... caught src/inside_mod.rs:3: replace outer::inner::name -> &'static str with "xyzzy" ... caught src/methods.rs:16: replace Foo::double with () ... caught src/methods.rs:22: replace ::fmt -> fmt::Result with Ok(Default::default()) ... caught src/methods.rs:28: replace ::fmt -> fmt::Result with Ok(Default::default()) ... caught src/nested_function.rs:1: replace has_nested -> u32 with 0 ... caught src/nested_function.rs:1: replace has_nested -> u32 with 1 ... caught src/nested_function.rs:2: replace has_nested::inner -> u32 with 0 ... caught src/nested_function.rs:2: replace has_nested::inner -> u32 with 1 ... caught src/numbers.rs:1: replace double_float -> f32 with 0.0 ... caught src/numbers.rs:1: replace double_float -> f32 with 1.0 ... caught src/numbers.rs:1: replace double_float -> f32 with -1.0 ... caught src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("") ... caught src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("xyzzy") ... caught src/result.rs:9: replace error_if_negative -> Result<(), ()> with Ok(()) ... caught src/result.rs:17: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default()) ... caught src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::new() ... caught src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::from_iter([String::new()]) ... caught src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::from_iter(["xyzzy".into()]) ... caught src/simple_fns.rs:7: replace returns_unit with () ... caught src/simple_fns.rs:12: replace returns_42u32 -> u32 with 0 ... caught src/simple_fns.rs:12: replace returns_42u32 -> u32 with 1 ... caught src/simple_fns.rs:17: replace divisible_by_three -> bool with true ... caught src/simple_fns.rs:17: replace divisible_by_three -> bool with false ... caught src/simple_fns.rs:26: replace double_string -> String with String::new() ... caught src/simple_fns.rs:26: replace double_string -> String with "xyzzy".into() ... caught src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(Vec::new()) ... caught src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Borrowed("")]) ... caught src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Owned("".to_owned())]) ... caught src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Borrowed("xyzzy")]) ... caught src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Owned("xyzzy".to_owned())]) ... caught src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(Vec::new()) ... caught src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![0]) ... caught src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![1]) ... caught src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 0 ... caught src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 1 ... caught 38 mutants tested: 38 caught cargo-mutants-23.10.0/tests/cli/snapshots/cli__well_tested_tree_finds_no_problems__caught.txt.snap000064400000000000000000000056241046102023000320120ustar 00000000000000--- source: tests/cli/main.rs expression: content --- src/arc.rs:3: replace return_arc -> Arc with Arc::new(String::new()) src/arc.rs:3: replace return_arc -> Arc with Arc::new("xyzzy".into()) src/inside_mod.rs:3: replace outer::inner::name -> &'static str with "" src/inside_mod.rs:3: replace outer::inner::name -> &'static str with "xyzzy" src/methods.rs:16: replace Foo::double with () src/methods.rs:22: replace ::fmt -> fmt::Result with Ok(Default::default()) src/methods.rs:28: replace ::fmt -> fmt::Result with Ok(Default::default()) src/nested_function.rs:1: replace has_nested -> u32 with 0 src/nested_function.rs:1: replace has_nested -> u32 with 1 src/nested_function.rs:2: replace has_nested::inner -> u32 with 0 src/nested_function.rs:2: replace has_nested::inner -> u32 with 1 src/numbers.rs:1: replace double_float -> f32 with 0.0 src/numbers.rs:1: replace double_float -> f32 with 1.0 src/numbers.rs:1: replace double_float -> f32 with -1.0 src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("") src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("xyzzy") src/result.rs:9: replace error_if_negative -> Result<(), ()> with Ok(()) src/result.rs:17: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default()) src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::new() src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::from_iter([String::new()]) src/sets.rs:3: replace make_a_set -> BTreeSet with BTreeSet::from_iter(["xyzzy".into()]) src/simple_fns.rs:7: replace returns_unit with () src/simple_fns.rs:12: replace returns_42u32 -> u32 with 0 src/simple_fns.rs:12: replace returns_42u32 -> u32 with 1 src/simple_fns.rs:17: replace divisible_by_three -> bool with true src/simple_fns.rs:17: replace divisible_by_three -> bool with false src/simple_fns.rs:26: replace double_string -> String with String::new() src/simple_fns.rs:26: replace double_string -> String with "xyzzy".into() src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(Vec::new()) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Borrowed("")]) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Owned("".to_owned())]) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Borrowed("xyzzy")]) src/slices.rs:3: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow::Owned("xyzzy".to_owned())]) src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(Vec::new()) src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![0]) src/slices.rs:12: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![1]) src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 0 src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 1 cargo-mutants-23.10.0/tests/cli/snapshots/cli__well_tested_tree_finds_no_problems__missed.txt.snap000064400000000000000000000000621046102023000320120ustar 00000000000000--- source: tests/cli.rs expression: content --- cargo-mutants-23.10.0/tests/cli/snapshots/cli__well_tested_tree_finds_no_problems__timeout.txt.snap000064400000000000000000000000621046102023000322140ustar 00000000000000--- source: tests/cli.rs expression: content --- cargo-mutants-23.10.0/tests/cli/snapshots/cli__well_tested_tree_finds_no_problems__unviable.txt.snap000064400000000000000000000000621046102023000323330ustar 00000000000000--- source: tests/cli.rs expression: content --- cargo-mutants-23.10.0/tests/cli/snapshots/cli__well_tested_tree_quiet.snap000064400000000000000000000002061046102023000251160ustar 00000000000000--- source: tests/cli/main.rs expression: stdout --- Found 38 mutants to test Unmutated baseline ... ok 38 mutants tested: 38 caught cargo-mutants-23.10.0/tests/cli/trace.rs000064400000000000000000000010641046102023000161310ustar 00000000000000// Copyright 2023 Martin Pool //! Tests for trace from the cargo-mutants CLI. use predicates::prelude::*; use super::run; #[test] fn env_var_controls_trace() { run() .env("CARGO_MUTANTS_TRACE_LEVEL", "trace") .args(["mutants", "--list"]) .arg("-d") .arg("testdata/tree/never_type") .assert() // This is a debug!() message; it should only be seen if the trace var // was wired correctly. .stdout(predicate::str::contains( "No mutants generated for this return type", )); } cargo-mutants-23.10.0/tests/cli/windows.rs000064400000000000000000000006351046102023000165300ustar 00000000000000// Copyright 2021-2023 Martin Pool //! Windows-only CLI tests. use super::{run, CommandInstaExt}; #[test] fn list_mutants_well_tested_exclude_folder_containing_backslash_on_windows() { run() .arg("mutants") .args(["--list", "--exclude", "*\\module\\*"]) .current_dir("testdata/tree/with_child_directories") .assert_insta("list_mutants_well_tested_exclude_folder_filter"); }