cargo_darwin/
lib.rs

1//! `cargo-darwin` is a plugin over `cargo` tool.
2//!
3//! Darwin mutates your code, if your code still passes check tests, then your code isn't enough tested.
4//!
5//! ## Usage
6//! ```bash
7//! cargo darwin /path/to/project/to/test
8//! ```
9//! Will display something like
10//! ```bash
11//! [Missing] : Tests pass, the mutation hasn't been caught, suspicion of missing test
12//! [OK]      : Tests failed, the mutation has been caught
13//! [Timeout] : Mutation introduces infinite loop, inconclusive
14//! [Killed]  : Mutation introduces non buildable modification
15//!   ---
16//! [OK] : Mutation #0 replace - by + in function "sub" of file src\a\toto.rs at line 11:6
17//! [OK] : Mutation #1 replace - by * in function "sub" of file src\a\toto.rs at line 11:6
18//! [Killed] : Mutation #2 replace - by && in function "sub" of file src\a\toto.rs at line 11:6
19//! [Missing] : Mutation #3 replace + by - in function "add" of file src\lib.rs at line 5:6
20//! [Missing] : Mutation #4 replace + by * in function "add" of file src\lib.rs at line 5:6
21//! [Missing] : Mutation #5 replace + by - in function "add" of file src\lib.rs at line 5:10
22//! [Missing] : Mutation #6 replace + by * in function "add" of file src\lib.rs at line 5:10
23//! ```
24//!
25//! ## Details
26//!
27//! *Darwin* walks the provided path (if none provided get the current dir).
28//!
29//! For each file ending by **.rs** extension, **Darwin** analyze the file and try to found mutable
30//! function.
31//!
32//! A function is mutable if there is no `#[test]` or `#[tokio::test]` attribute over it.
33//!
34//! ```ignore
35//! fn mutable() {}
36//!
37//! #[test]
38//! fn non_mutable() {}
39//!
40//! #[tokio::test]
41//! async fn non_mutable_async() {}
42//! ```
43//!
44//! The project is in its really early stage, so the mutation are quite limited, actually just binary expressions like `a + b` or `a - b`.
45//!
46//! For example this mutable function
47//!
48//! ```
49//! fn add(x: u8, y:u8) -> u8 {
50//!     x + y
51//! }
52//! ```
53//! will become
54//! ```
55//! fn add(x: u8, y:u8) -> u8 {
56//!     x - y
57//! }
58//! ```
59//! Then Darwin create a copy of the actual project and apply the modification on the copied file
60//!
61//! Once the project mutated, Darwin runs a `cargo build`, if the project compile, then the mutation is sustainable
62//!
63//! If so, Darwin runs the `cargo test`, There are 3 possibilities:
64//! - project tests pass : the project is inefficiently tested as the mutation isn't catch
65//! - tests fail : the project has at least one test which catches the mutation
66//! - timeout : the mutation even if compiles, introduce a loop or something that makes the test run
67//! forever
68//!
69//! ### Reports
70//!
71//! All reports can be found in the *mutation path* in a **reports** folder which.
72//!
73//! For example if you have run *darwin* with
74//!
75//! ```bash
76//! cargo darwin --mutation-path /tmp/darwin /path/to/project/to/test
77//! ```
78//! You will get this tree
79//!
80//! ```bash
81//! tmp/
82//! ├─ darwin/
83//! │  ├─ reports/
84//! │  │  ├─ mutation_0.log
85//! │  │  ├─ mutation_1.log
86//! │  │  ├─ summary
87//! │  ├─ 0/
88//! │  ├─ 1/
89//! ```
90//!
91//! #### Mutated projects
92//!
93//! If the `--keep` flag is defined, after tests, you can walk to generated projects
94//!
95//! Each one has a mutation ID and the associated mutation ID can be found in summary file
96//!
97//! #### Summary
98//!
99//! Summarize the mutation applied and the result of each.
100//!
101//! ```bash
102//! [OK] : Mutation #0 replace - by + in function "sub" of file src\a\toto.rs at line 11:6
103//! [OK] : Mutation #1 replace - by * in function "sub" of file src\a\toto.rs at line 11:6
104//! [Killed] : Mutation #2 replace - by && in function "sub" of file src\a\toto.rs at line 11:6
105//! [Missing] : Mutation #3 replace + by - in function "add" of file src\lib.rs at line 5:6
106//! [Missing] : Mutation #4 replace + by * in function "add" of file src\lib.rs at line 5:6
107//! [Missing] : Mutation #5 replace + by - in function "add" of file src\lib.rs at line 5:10
108//! [Missing] : Mutation #6 replace + by * in function "add" of file src\lib.rs at line 5:10
109//! ```
110//!
111//! For more information about the mutation, check the associated mutation_ID.log file
112//!
113//! #### Mutation report
114//! `reports/mutation_X.log` files are the detailed view of the mutation.
115//!
116//! There are build with the following nomenclature
117//! - Mutated file
118//! - Mutation
119//! - Mutation status
120//! - Diff of the mutation
121//! - Test or build output
122//!
123//! Below an example of output
124//!
125//! ```log
126//! Mutation of file F:\Projets\Lab\Rust\darwin\playground\src\a\toto.rs
127//! Mutation reason: replace - by *
128//! Status : OK => Mutation Caught
129//! Mutation diff:
130//! @@ -8,7 +8,7 @@
131//!  //
132//!  fn sub(x: i8, y: i8) -> i8 {
133//!      let u = 8;
134//! -    x - y
135//! +    x * y
136//!  }
137//!
138//! Output:
139//!
140//!  #[test]
141//! stderr:
142//!    Compiling playground v0.1.0 (F:\Projets\Lab\Rust\darwin\tmp\1)
143//!     Finished test [unoptimized + debuginfo] target(s) in 0.18s
144//!      Running unittests src\lib.rs (target\debug\deps\playground-29148ab9d23d3c5d.exe)
145//! error: test failed, to rerun pass `--lib`
146//!
147//! stdout:
148//!
149//! running 2 tests
150//! test a::toto::test_sub ... FAILED
151//! test a::toto::async_test_sub ... FAILED
152//!
153//! failures:
154//!
155//! ---- a::toto::test_sub stdout ----
156//! thread 'a::toto::test_sub' panicked at src\a\toto.rs:16:5:
157//! assertion `left == right` failed
158//!   left: 10
159//!  right: 3
160//! note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
161//!
162//! ---- a::toto::async_test_sub stdout ----
163//! thread 'a::toto::async_test_sub' panicked at src\a\toto.rs:21:5:
164//! assertion `left == right` failed
165//!   left: 10
166//!  right: 3
167//!
168//!
169//! failures:
170//!     a::toto::async_test_sub
171//!     a::toto::test_sub
172//!
173//! test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
174//! ```
175//!
176//! As a test has failed, the mutation has been caught, so the code is enough tested for this particular mutation
177//!
178use std::fs;
179
180use clap::Parser;
181
182use actions::{analyze, generate, reporting};
183use cli::{Cli, Darwin};
184use mutation::Mutation;
185
186mod actions;
187mod cli;
188mod mutation;
189mod report;
190
191/// Display mutation but don't run tests
192fn display_mutations(mutations: &Vec<Mutation>) -> eyre::Result<()> {
193    for mutation in mutations {
194        println!("{}", mutation.display(true)?)
195    }
196    Ok(())
197}
198
199/// Main darwin function
200pub fn run() -> eyre::Result<()> {
201    let cli = Cli::parse();
202
203    let Cli::Darwin(Darwin {
204        mutation_path,
205        root_path,
206        dry_run,
207        keep,
208    }) = cli;
209
210    let root_path = fs::canonicalize(root_path)?;
211    let mut mutants = analyze::analyze(&root_path)?;
212
213    if !dry_run {
214        println!("{}---", cli::help());
215        generate::generate_and_verify_mutants(&mut mutants, &root_path, &mutation_path, keep)?;
216        reporting::generate_reports(&mutants, &mutation_path, &root_path)?;
217    } else {
218        log::info!("Run Darwin in dry run");
219        display_mutations(&mutants)?;
220    }
221
222    Ok(())
223}