Skip to content

Commit 1aec924

Browse files
committed
protect test generation from multi-process races (#384)
cargo nextest runs tests in (even more) parallel which can cause races when creating directories. Usually we could lock with an in-process mutex, but on top of that for these we also have to lock on the file system.
1 parent a981517 commit 1aec924

File tree

3 files changed

+13
-6
lines changed

3 files changed

+13
-6
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/tools/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ path = "src/main.rs"
1414

1515
[dependencies]
1616
git-hash = { version = "^0.9.3", path = "../../git-hash" }
17+
git-lock = { version = "^2.0.0", path = "../../git-lock" }
18+
1719
nom = { version = "7", default-features = false, features = ["std"]}
1820
bstr = "0.2.15"
1921
crc = "2.0.0"

tests/tools/src/lib.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::io::Read;
2+
use std::time::Duration;
23
use std::{
34
collections::BTreeMap,
45
path::{Path, PathBuf},
@@ -87,8 +88,8 @@ pub fn scripted_fixture_repo_read_only_with_args(
8788
script_name: impl AsRef<Path>,
8889
args: impl IntoIterator<Item = &'static str>,
8990
) -> Result<PathBuf> {
90-
let script_name = script_name.as_ref();
91-
let script_path = fixture_path(script_name);
91+
let script_location = script_name.as_ref();
92+
let script_path = fixture_path(script_location);
9293

9394
// keep this lock to assure we don't return unfinished directories for threaded callers
9495
let args: Vec<String> = args.into_iter().map(Into::into).collect();
@@ -106,7 +107,7 @@ pub fn scripted_fixture_repo_read_only_with_args(
106107
})
107108
.to_owned();
108109

109-
let script_basename = script_name.file_stem().unwrap_or(script_name.as_os_str());
110+
let script_basename = script_location.file_stem().unwrap_or(script_location.as_os_str());
110111
let archive_file_path = fixture_path(
111112
Path::new("generated-archives")
112113
.join(crate_under_test())
@@ -118,14 +119,19 @@ pub fn scripted_fixture_repo_read_only_with_args(
118119
.join(format!("{}", script_identity)),
119120
);
120121

122+
let _marker = git_lock::Marker::acquire_to_hold_resource(
123+
script_basename,
124+
git_lock::acquire::Fail::AfterDurationWithBackoff(Duration::from_secs(5)),
125+
None,
126+
)?;
121127
if !script_result_directory.is_dir() {
128+
std::fs::create_dir_all(&script_result_directory)?;
122129
match extract_archive(&archive_file_path, &script_result_directory, script_identity) {
123130
Ok(_) => {}
124131
Err(err) => {
125132
if err.kind() != std::io::ErrorKind::NotFound {
126133
eprintln!("{}", err);
127134
}
128-
std::fs::create_dir_all(&script_result_directory)?;
129135
let script_absolute_path = std::env::current_dir()?.join(script_path);
130136
let output = std::process::Command::new("bash")
131137
.arg(script_absolute_path)
@@ -206,8 +212,6 @@ fn populate_meta_dir(destination_dir: &Path, script_identity: u32) -> std::io::R
206212
/// `required_script_identity` is the identity of the script that generated the state that is contained in `archive`.
207213
/// If this is not the case, the arvhive will be ignored.
208214
fn extract_archive(archive: &Path, destination_dir: &Path, required_script_identity: u32) -> std::io::Result<()> {
209-
std::fs::create_dir_all(destination_dir)?;
210-
211215
let mut archive_buf = Vec::<u8>::new();
212216
let mut decoder = xz2::bufread::XzDecoder::new(std::io::BufReader::new(std::fs::File::open(archive)?));
213217
std::io::copy(&mut decoder, &mut archive_buf)?;

0 commit comments

Comments
 (0)