Skip to content

Commit ffb165c

Browse files
committed
Auto merge of #140 - cjpearce:fix/test-race-condition, r=komaeda
Fix intermittent test failure caused by race condition First public pull request 😬 There's an intermittent integration test failure when you use multiple test threads (at least for me on a mac). I narrowed it down to two tests each spawning a process using `Command` which then try to compile the same file at the same time. If the timing doesn't work out, they both try to compile, and then one process runs `clean` before the other can run the executable - causing a panic. ![Screenshot 2019-04-07 at 19 54 55](https://user-images.githubusercontent.com/3453268/55688324-20520980-596f-11e9-8474-5215d61a4387.png) You can prevent it from happening by running with a single thread (`cargo test -- --test-threads=1`), because the `Command` blocks. That's not a particularly good solution though because it's not something you can configure in `Cargo.toml`. I considered making the affected tests just run serially, but it occurred to me that this could also happen if someone accidentally runs rustlings in watch mode in two terminals without realising it. I wound't consider this that unlikely given it's a tool for learning. I fixed it by ensuring that the executables made from separate processes don't conflict by appending a process id to the output executable name. I also extracted the commands into a single file next to `clean` so that we don't have to repeat the generated file name everywhere and risk missing something.
2 parents 78552eb + 65cb09e commit ffb165c

File tree

4 files changed

+49
-30
lines changed

4 files changed

+49
-30
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ rust:
33
- stable
44
- beta
55
- nightly
6-
script: cargo test --verbose -- --test-threads=1
6+
script: cargo test --verbose
77
matrix:
88
allow_failures:
99
- rust: nightly

src/run.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
use crate::util::clean;
1+
use crate::util;
22
use crate::verify::test;
33
use console::{style, Emoji};
44
use indicatif::ProgressBar;
55
use std::fs;
6-
use std::process::Command;
76
use toml::Value;
87

98
pub fn run(matches: clap::ArgMatches) -> Result<(), ()> {
@@ -33,28 +32,26 @@ pub fn compile_and_run(filename: &str) -> Result<(), ()> {
3332
let progress_bar = ProgressBar::new_spinner();
3433
progress_bar.set_message(format!("Compiling {}...", filename).as_str());
3534
progress_bar.enable_steady_tick(100);
36-
let compilecmd = Command::new("rustc")
37-
.args(&[filename, "-o", "temp", "--color", "always"])
38-
.output()
39-
.expect("fail");
35+
36+
let compilecmd = util::compile_cmd(filename);
4037
progress_bar.set_message(format!("Running {}...", filename).as_str());
4138
if compilecmd.status.success() {
42-
let runcmd = Command::new("./temp").output().expect("fail");
39+
let runcmd = util::run_cmd();
4340
progress_bar.finish_and_clear();
4441

4542
if runcmd.status.success() {
4643
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
4744
let formatstr = format!("{} Successfully ran {}", Emoji("✅", "✓"), filename);
4845
println!("{}", style(formatstr).green());
49-
clean();
46+
util::clean();
5047
Ok(())
5148
} else {
5249
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
5350
println!("{}", String::from_utf8_lossy(&runcmd.stderr));
5451

5552
let formatstr = format!("{} Ran {} with errors", Emoji("⚠️ ", "!"), filename);
5653
println!("{}", style(formatstr).red());
57-
clean();
54+
util::clean();
5855
Err(())
5956
}
6057
} else {
@@ -66,7 +63,7 @@ pub fn compile_and_run(filename: &str) -> Result<(), ()> {
6663
);
6764
println!("{}", style(formatstr).red());
6865
println!("{}", String::from_utf8_lossy(&compilecmd.stderr));
69-
clean();
66+
util::clean();
7067
Err(())
7168
}
7269
}

src/util.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,41 @@
11
use std::fs::remove_file;
2+
use std::process::{self, Command, Output};
3+
4+
const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"];
5+
6+
fn temp_file() -> String {
7+
format!("./temp_{}", process::id())
8+
}
9+
10+
pub fn compile_test_cmd(filename: &str) -> Output {
11+
Command::new("rustc")
12+
.args(&["--test", filename, "-o", &temp_file()])
13+
.args(RUSTC_COLOR_ARGS)
14+
.output()
15+
.expect("failed to compile exercise")
16+
}
17+
18+
pub fn compile_cmd(filename: &str) -> Output {
19+
Command::new("rustc")
20+
.args(&[filename, "-o", &temp_file()])
21+
.args(RUSTC_COLOR_ARGS)
22+
.output()
23+
.expect("failed to compile exercise")
24+
}
25+
26+
pub fn run_cmd() -> Output {
27+
Command::new(&temp_file())
28+
.output()
29+
.expect("failed to run exercise")
30+
}
231

332
pub fn clean() {
4-
let _ignored = remove_file("temp");
33+
let _ignored = remove_file(&temp_file());
534
}
635

736
#[test]
837
fn test_clean() {
9-
std::fs::File::create("temp").unwrap();
38+
std::fs::File::create(&temp_file()).unwrap();
1039
clean();
11-
assert!(!std::path::Path::new("temp").exists());
40+
assert!(!std::path::Path::new(&temp_file()).exists());
1241
}

src/verify.rs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
use crate::util::clean;
1+
use crate::util;
22
use console::{style, Emoji};
33
use indicatif::ProgressBar;
44
use std::fs;
5-
use std::process::Command;
65
use toml::Value;
76

87
pub fn verify(start_at: Option<&str>) -> Result<(), ()> {
@@ -34,10 +33,7 @@ fn compile_only(filename: &str) -> Result<(), ()> {
3433
let progress_bar = ProgressBar::new_spinner();
3534
progress_bar.set_message(format!("Compiling {}...", filename).as_str());
3635
progress_bar.enable_steady_tick(100);
37-
let compilecmd = Command::new("rustc")
38-
.args(&[filename, "-o", "temp", "--color", "always"])
39-
.output()
40-
.expect("fail");
36+
let compilecmd = util::compile_cmd(filename);
4137
progress_bar.finish_and_clear();
4238
if compilecmd.status.success() {
4339
let formatstr = format!(
@@ -46,7 +42,7 @@ fn compile_only(filename: &str) -> Result<(), ()> {
4642
filename
4743
);
4844
println!("{}", style(formatstr).green());
49-
clean();
45+
util::clean();
5046
Ok(())
5147
} else {
5248
let formatstr = format!(
@@ -56,7 +52,7 @@ fn compile_only(filename: &str) -> Result<(), ()> {
5652
);
5753
println!("{}", style(formatstr).red());
5854
println!("{}", String::from_utf8_lossy(&compilecmd.stderr));
59-
clean();
55+
util::clean();
6056
Err(())
6157
}
6258
}
@@ -65,19 +61,16 @@ pub fn test(filename: &str) -> Result<(), ()> {
6561
let progress_bar = ProgressBar::new_spinner();
6662
progress_bar.set_message(format!("Testing {}...", filename).as_str());
6763
progress_bar.enable_steady_tick(100);
68-
let testcmd = Command::new("rustc")
69-
.args(&["--test", filename, "-o", "temp", "--color", "always"])
70-
.output()
71-
.expect("fail");
64+
let testcmd = util::compile_test_cmd(filename);
7265
if testcmd.status.success() {
7366
progress_bar.set_message(format!("Running {}...", filename).as_str());
74-
let runcmd = Command::new("./temp").output().expect("fail");
67+
let runcmd = util::run_cmd();
7568
progress_bar.finish_and_clear();
7669

7770
if runcmd.status.success() {
7871
let formatstr = format!("{} Successfully tested {}!", Emoji("✅", "✓"), filename);
7972
println!("{}", style(formatstr).green());
80-
clean();
73+
util::clean();
8174
Ok(())
8275
} else {
8376
let formatstr = format!(
@@ -87,7 +80,7 @@ pub fn test(filename: &str) -> Result<(), ()> {
8780
);
8881
println!("{}", style(formatstr).red());
8982
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
90-
clean();
83+
util::clean();
9184
Err(())
9285
}
9386
} else {
@@ -99,7 +92,7 @@ pub fn test(filename: &str) -> Result<(), ()> {
9992
);
10093
println!("{}", style(formatstr).red());
10194
println!("{}", String::from_utf8_lossy(&testcmd.stderr));
102-
clean();
95+
util::clean();
10396
Err(())
10497
}
10598
}

0 commit comments

Comments
 (0)