Skip to content

Commit 8a0fedb

Browse files
committed
fix: assure archives are unique if their generator-scripts are called with arguments. (#1440)
Previously there was a race condition that would cause archives to be created either with or without arguments, depending on which test was run first. After its creation, they wouldn't be looked at again as on disk they would already be available in their usable form.
1 parent e2e8cf9 commit 8a0fedb

File tree

1 file changed

+101
-18
lines changed

1 file changed

+101
-18
lines changed

tests/tools/src/lib.rs

+101-18
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,15 @@ enum DirectoryRoot {
224224
StandaloneTest,
225225
}
226226

227+
/// Don't add a suffix to the archive name as `args` are platform dependent, none-deterministic,
228+
/// or otherwise don't influence the content of the archive.
229+
/// Note that this also means that `args` won't be used to control the hash of the archive itself.
230+
#[derive(Copy, Clone)]
231+
enum ArgsInHash {
232+
Yes,
233+
No,
234+
}
235+
227236
/// Return the path to the `<crate-root>/tests/fixtures/<path>` directory.
228237
pub fn fixture_path(path: impl AsRef<Path>) -> PathBuf {
229238
fixture_path_inner(path, DirectoryRoot::IntegrationTest)
@@ -309,7 +318,19 @@ pub fn scripted_fixture_writable_with_args(
309318
args: impl IntoIterator<Item = impl Into<String>>,
310319
mode: Creation,
311320
) -> Result<tempfile::TempDir> {
312-
scripted_fixture_writable_with_args_inner(script_name, args, mode, DirectoryRoot::IntegrationTest)
321+
scripted_fixture_writable_with_args_inner(script_name, args, mode, DirectoryRoot::IntegrationTest, ArgsInHash::Yes)
322+
}
323+
324+
/// Like [`scripted_fixture_writable()`], but passes `args` to `script_name` while providing control over
325+
/// the way files are created with `mode`.
326+
///
327+
/// See [`scripted_fixture_read_only_with_args_single_archive()`] for important details on what `single_archive` means.
328+
pub fn scripted_fixture_writable_with_args_single_archive(
329+
script_name: impl AsRef<Path>,
330+
args: impl IntoIterator<Item = impl Into<String>>,
331+
mode: Creation,
332+
) -> Result<tempfile::TempDir> {
333+
scripted_fixture_writable_with_args_inner(script_name, args, mode, DirectoryRoot::IntegrationTest, ArgsInHash::No)
313334
}
314335

315336
/// Like [`scripted_fixture_writable_with_args`], but does not prefix the fixture directory with `tests`
@@ -318,24 +339,36 @@ pub fn scripted_fixture_writable_with_args_standalone(
318339
args: impl IntoIterator<Item = impl Into<String>>,
319340
mode: Creation,
320341
) -> Result<tempfile::TempDir> {
321-
scripted_fixture_writable_with_args_inner(script_name, args, mode, DirectoryRoot::StandaloneTest)
342+
scripted_fixture_writable_with_args_inner(script_name, args, mode, DirectoryRoot::StandaloneTest, ArgsInHash::Yes)
343+
}
344+
345+
/// Like [`scripted_fixture_writable_with_args`], but does not prefix the fixture directory with `tests`
346+
///
347+
/// See [`scripted_fixture_read_only_with_args_single_archive()`] for important details on what `single_archive` means.
348+
pub fn scripted_fixture_writable_with_args_standalone_single_archive(
349+
script_name: &str,
350+
args: impl IntoIterator<Item = impl Into<String>>,
351+
mode: Creation,
352+
) -> Result<tempfile::TempDir> {
353+
scripted_fixture_writable_with_args_inner(script_name, args, mode, DirectoryRoot::StandaloneTest, ArgsInHash::No)
322354
}
323355

324356
fn scripted_fixture_writable_with_args_inner(
325357
script_name: impl AsRef<Path>,
326358
args: impl IntoIterator<Item = impl Into<String>>,
327359
mode: Creation,
328360
root: DirectoryRoot,
361+
args_in_hash: ArgsInHash,
329362
) -> Result<tempfile::TempDir> {
330363
let dst = tempfile::TempDir::new()?;
331364
Ok(match mode {
332365
Creation::CopyFromReadOnly => {
333-
let ro_dir = scripted_fixture_read_only_with_args_inner(script_name, args, None, root)?;
366+
let ro_dir = scripted_fixture_read_only_with_args_inner(script_name, args, None, root, args_in_hash)?;
334367
copy_recursively_into_existing_dir(ro_dir, dst.path())?;
335368
dst
336369
}
337370
Creation::ExecuteScript => {
338-
scripted_fixture_read_only_with_args_inner(script_name, args, dst.path().into(), root)?;
371+
scripted_fixture_read_only_with_args_inner(script_name, args, dst.path().into(), root, args_in_hash)?;
339372
dst
340373
}
341374
})
@@ -365,22 +398,49 @@ pub fn scripted_fixture_read_only_with_args(
365398
script_name: impl AsRef<Path>,
366399
args: impl IntoIterator<Item = impl Into<String>>,
367400
) -> Result<PathBuf> {
368-
scripted_fixture_read_only_with_args_inner(script_name, args, None, DirectoryRoot::IntegrationTest)
401+
scripted_fixture_read_only_with_args_inner(script_name, args, None, DirectoryRoot::IntegrationTest, ArgsInHash::Yes)
402+
}
403+
404+
/// Like `scripted_fixture_read_only()`], but passes `args` to `script_name`.
405+
///
406+
/// Also, don't add a suffix to the archive name as `args` are platform dependent, none-deterministic,
407+
/// or otherwise don't influence the content of the archive.
408+
/// Note that this also means that `args` won't be used to control the hash of the archive itself.
409+
///
410+
/// Sometimes, this should be combined with adding the archive name to `.gitignore` to prevent its creation
411+
/// in the first place.
412+
///
413+
/// Note that suffixing archives by default helps to learn what calls are made, and forces the author to
414+
/// think about what should be done to get it right.
415+
pub fn scripted_fixture_read_only_with_args_single_archive(
416+
script_name: impl AsRef<Path>,
417+
args: impl IntoIterator<Item = impl Into<String>>,
418+
) -> Result<PathBuf> {
419+
scripted_fixture_read_only_with_args_inner(script_name, args, None, DirectoryRoot::IntegrationTest, ArgsInHash::No)
369420
}
370421

371422
/// Like [`scripted_fixture_read_only_with_args()`], but does not prefix the fixture directory with `tests`
372423
pub fn scripted_fixture_read_only_with_args_standalone(
373424
script_name: impl AsRef<Path>,
374425
args: impl IntoIterator<Item = impl Into<String>>,
375426
) -> Result<PathBuf> {
376-
scripted_fixture_read_only_with_args_inner(script_name, args, None, DirectoryRoot::StandaloneTest)
427+
scripted_fixture_read_only_with_args_inner(script_name, args, None, DirectoryRoot::StandaloneTest, ArgsInHash::Yes)
428+
}
429+
430+
/// Like [`scripted_fixture_read_only_with_args_standalone()`], only has a single archive.
431+
pub fn scripted_fixture_read_only_with_args_standalone_single_archive(
432+
script_name: impl AsRef<Path>,
433+
args: impl IntoIterator<Item = impl Into<String>>,
434+
) -> Result<PathBuf> {
435+
scripted_fixture_read_only_with_args_inner(script_name, args, None, DirectoryRoot::StandaloneTest, ArgsInHash::No)
377436
}
378437

379438
fn scripted_fixture_read_only_with_args_inner(
380439
script_name: impl AsRef<Path>,
381440
args: impl IntoIterator<Item = impl Into<String>>,
382441
destination_dir: Option<&Path>,
383442
root: DirectoryRoot,
443+
args_in_hash: ArgsInHash,
384444
) -> Result<PathBuf> {
385445
// Assure tempfiles get removed when aborting the test.
386446
gix_tempfile::signal::setup(
@@ -414,11 +474,23 @@ fn scripted_fixture_read_only_with_args_inner(
414474

415475
let script_basename = script_location.file_stem().unwrap_or(script_location.as_os_str());
416476
let archive_file_path = fixture_path_inner(
417-
Path::new("generated-archives").join(format!(
418-
"{}.tar{}",
419-
script_basename.to_str().expect("valid UTF-8"),
420-
if cfg!(feature = "xz") { ".xz" } else { "" }
421-
)),
477+
{
478+
let suffix = match args_in_hash {
479+
ArgsInHash::Yes => {
480+
let mut suffix = args.join("_");
481+
if !suffix.is_empty() {
482+
suffix.insert(0, '_');
483+
}
484+
suffix.replace(['\\', '/', ' ', '.'], "_")
485+
}
486+
ArgsInHash::No => "".into(),
487+
};
488+
Path::new("generated-archives").join(format!(
489+
"{}{suffix}.tar{}",
490+
script_basename.to_str().expect("valid UTF-8"),
491+
if cfg!(feature = "xz") { ".xz" } else { "" }
492+
))
493+
},
422494
root,
423495
);
424496
let (force_run, script_result_directory) = destination_dir.map_or_else(
@@ -447,7 +519,15 @@ fn scripted_fixture_read_only_with_args_inner(
447519
std::fs::remove_dir_all(&script_result_directory).map_err(|err| format!("Failed to remove '{script_result_directory:?}', please try to do that by hand. Original error: {err}"))?
448520
}
449521
std::fs::create_dir_all(&script_result_directory)?;
450-
match extract_archive(&archive_file_path, &script_result_directory, script_identity) {
522+
let script_identity_for_archive = match args_in_hash {
523+
ArgsInHash::Yes => script_identity,
524+
ArgsInHash::No => 0,
525+
};
526+
match extract_archive(
527+
&archive_file_path,
528+
&script_result_directory,
529+
script_identity_for_archive,
530+
) {
451531
Ok((archive_id, platform)) => {
452532
eprintln!(
453533
"Extracted fixture from archive '{}' ({}, {:?})",
@@ -489,12 +569,15 @@ fn scripted_fixture_read_only_with_args_inner(
489569
output.stdout.as_bstr(),
490570
output.stderr.as_bstr()
491571
);
492-
create_archive_if_not_on_ci(&script_result_directory, &archive_file_path, script_identity).map_err(
493-
|err| {
494-
write_failure_marker(&failure_marker);
495-
err
496-
},
497-
)?;
572+
create_archive_if_not_on_ci(
573+
&script_result_directory,
574+
&archive_file_path,
575+
script_identity_for_archive,
576+
)
577+
.map_err(|err| {
578+
write_failure_marker(&failure_marker);
579+
err
580+
})?;
498581
}
499582
}
500583
}

0 commit comments

Comments
 (0)