Skip to content

Commit 2803f91

Browse files
committed
Auto merge of #389 - pietroalbini:minicrater-docs, r=pietroalbini
Add minicrater docs This PR adds some docs about minicrater, making easier to contributors to change it.
2 parents 5663636 + 5341794 commit 2803f91

File tree

4 files changed

+215
-162
lines changed

4 files changed

+215
-162
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,5 @@ guide](CONTRIBUTING.md).
4444

4545
**Technical documentation:**
4646

47-
* [Agent HTTP Api specification](docs/agent-http-api.md)
47+
* [minicrater docs](tests/minicrater/README.md)
48+
* [Agent HTTP API specification](docs/agent-http-api.md)

tests/minicrater/README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# minicrater
2+
3+
minicrater is the component of Crater's test suite that tests if the runs are
4+
actually executed and produce the correct result. It's inspired by rustc's
5+
compiletest.
6+
7+
## Executing minicrater
8+
9+
minicrater executions can take a few minutes to run, so it's ignored by default
10+
while running `cargo test`. You can run minicrater with:
11+
12+
```
13+
$ cargo test minicrater -- --ignored --test-threads 1
14+
```
15+
16+
The runs' output is hidden by default, but you can show it by setting the
17+
`MINICRATER_SHOW_OUTPUT` environment variable:
18+
19+
```
20+
$ MINICRATER_SHOW_OUTPUT=1 cargo test minicrater -- --ignored --test-threads 1
21+
```
22+
23+
## Adding tests to minicrater
24+
25+
There are two ways to add a test to minicrater:
26+
27+
* If your test requires different experiment configuration (like different
28+
flags or `config.toml` file entries) and no other minicrater run has the
29+
configuration you want you need to create a new minicrater run
30+
* Otherwise you can create a new local crate and have it tested by an existing
31+
minicrater run (usually the full ones)
32+
33+
minicrater runs are defined in `tests/minicrater/mod.rs`, and each run has
34+
additional configuration in the `tests/minicrater/<run>` directory: a
35+
`config.toml` with the configuration file used for the run, and
36+
`results.expected.json`, which contains the JSON output expected in the
37+
generated report.
38+
39+
### Adding new local crates
40+
41+
minicrater doesn't test public crates available on crates.io, but "local
42+
crates", which are dummy crates located in the `local-crates` directory at the
43+
top of the project. To add a new local crate you need to actually create the
44+
crate in that directory and then run:
45+
46+
```
47+
$ cargo run create-lists
48+
```
49+
50+
The `create-lists` command is only needed when you add or remove local crates,
51+
not when you edit one of them.
52+
53+
### Adding new minicrater runs
54+
55+
To add a new minicrater run, you need to add a new entry to
56+
`tests/minicrater/mod.rs` and a configuration file in
57+
`tests/minicrater/<run>/config.toml`. Then you run minicrater - expecting the
58+
experiment to be failing - and execute the command shown in the output to
59+
generate the expected JSON report.

tests/minicrater/driver.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use crate::common::CommandCraterExt;
2+
use assert_cmd::prelude::*;
3+
use difference::Changeset;
4+
use rand::{self, distributions::Alphanumeric, Rng};
5+
use serde_json::{self, Value};
6+
use std::env;
7+
use std::path::PathBuf;
8+
use std::process::Command;
9+
10+
trait CommandMinicraterExt {
11+
fn minicrater_exec(&mut self);
12+
}
13+
14+
impl CommandMinicraterExt for Command {
15+
fn minicrater_exec(&mut self) {
16+
if env::var_os("MINICRATER_SHOW_OUTPUT").is_some() {
17+
assert!(self.status().unwrap().success());
18+
} else {
19+
self.assert().success();
20+
}
21+
}
22+
}
23+
24+
pub(super) struct MinicraterRun {
25+
pub(super) ex: &'static str,
26+
pub(super) crate_select: &'static str,
27+
pub(super) multithread: bool,
28+
pub(super) ignore_blacklist: bool,
29+
}
30+
31+
impl MinicraterRun {
32+
pub(super) fn execute(&self) {
33+
let ex_dir = PathBuf::from("tests").join("minicrater").join(self.ex);
34+
let config_file = ex_dir.join("config.toml");
35+
let expected_file = ex_dir.join("results.expected.json");
36+
let actual_file = ex_dir.join("results.actual.json");
37+
38+
let threads_count = if self.multithread { num_cpus::get() } else { 1 };
39+
40+
let report_dir = tempfile::tempdir().expect("failed to create report dir");
41+
let ex_arg = format!(
42+
"--ex=minicrater-{}-{}",
43+
self.ex,
44+
rand::thread_rng()
45+
.sample_iter(&Alphanumeric)
46+
.take(10)
47+
.collect::<String>()
48+
);
49+
50+
// Create local list in the temp work dir
51+
Command::crater()
52+
.args(&["create-lists", "local"])
53+
.env("CRATER_CONFIG", &config_file)
54+
.minicrater_exec();
55+
56+
// Define the experiment
57+
let crate_select = format!("--crate-select={}", self.crate_select);
58+
let mut define_args = vec!["define-ex", &ex_arg, "stable", "beta", &crate_select];
59+
if self.ignore_blacklist {
60+
define_args.push("--ignore-blacklist");
61+
}
62+
Command::crater()
63+
.args(&define_args)
64+
.env("CRATER_CONFIG", &config_file)
65+
.minicrater_exec();
66+
67+
// Execute the experiment
68+
Command::crater()
69+
.args(&[
70+
"run-graph",
71+
&ex_arg,
72+
"--threads",
73+
&threads_count.to_string(),
74+
])
75+
.env("CRATER_CONFIG", &config_file)
76+
.minicrater_exec();
77+
78+
// Generate the report
79+
Command::crater()
80+
.args(&["gen-report", &ex_arg])
81+
.env("CRATER_CONFIG", &config_file)
82+
.arg(report_dir.path())
83+
.minicrater_exec();
84+
85+
// Read the JSON report
86+
let json_report = ::std::fs::read(report_dir.path().join("results.json"))
87+
.expect("failed to read json report");
88+
89+
// Delete the experiment
90+
Command::crater()
91+
.args(&["delete-ex", &ex_arg])
92+
.env("CRATER_CONFIG", &config_file)
93+
.minicrater_exec();
94+
95+
// Load the generated JSON report
96+
let parsed_report: Value =
97+
serde_json::from_slice(&json_report).expect("invalid json report");
98+
let mut actual_report = serde_json::to_vec_pretty(&parsed_report).unwrap();
99+
actual_report.push(b'\n');
100+
101+
// Load the expected JSON report
102+
let expected_report = ::std::fs::read(&expected_file).unwrap_or(Vec::new());
103+
104+
// Write the actual JSON report
105+
::std::fs::write(&actual_file, &actual_report)
106+
.expect("failed to write copy of the json report");
107+
108+
let changeset = Changeset::new(
109+
&String::from_utf8(expected_report).expect("invalid utf-8 in the expected report"),
110+
&String::from_utf8(actual_report.clone()).expect("invalid utf-8 in the actual report"),
111+
"\n",
112+
);
113+
if changeset.distance != 0 {
114+
eprintln!(
115+
"Difference between expected and actual reports:\n{}",
116+
changeset
117+
);
118+
eprintln!("To expect the new report in the future run:");
119+
eprintln!(
120+
"$ cp {} {}\n",
121+
actual_file.to_string_lossy(),
122+
expected_file.to_string_lossy()
123+
);
124+
panic!("invalid report generated by Crater");
125+
}
126+
}
127+
}
128+
129+
#[macro_export]
130+
macro_rules! minicrater {
131+
($($name:ident $opts:tt,)*) => {
132+
$(
133+
#[test]
134+
#[ignore]
135+
fn $name() {
136+
use $crate::minicrater::driver::MinicraterRun;
137+
MinicraterRun $opts.execute();
138+
}
139+
)*
140+
}
141+
}

0 commit comments

Comments
 (0)