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}