diff --git a/context/test-evidence/tui.md b/context/test-evidence/tui.md index 3565bcdd..a6189f38 100644 --- a/context/test-evidence/tui.md +++ b/context/test-evidence/tui.md @@ -490,7 +490,38 @@ in a scratch repository and asserts the walk lists it. All four were red against the `.gitignore` walk. The account, including the rivals, is under `every_shipped_document_loads` above. -### a_scratch_repo_ignores_an_inherited_git_dir +### a_hostile_git_environment_changes_nothing + +improvise-eahe. Each section below it once had a test of its own, and each of those tests re-ran this binary in child processes, one child per setting (a0i3 alone ran ten). By #276 that was 18 children, and each one spawns several `git` processes, so this binary took 2.1–3.3s against the 2s-per-binary tripwire (`context/design-principles.md` §7). + +**What changed.** The six tests are now one test, and its two children carry every hostile setting at once: +- **Children:** one re-runs `a_gzipped_document_is_walked` and one re-runs `every_shipped_document_loads`. +- **Settings:** each child gets the template dir, the global and system config, `XDG_CONFIG_HOME`, `GIT_DIR`/`GIT_INDEX_FILE`, the five pathspec and attribute settings, the six trace variables, and the config's trace2 targets. +- **Outer index:** the test then asserts the outer repository's index is still empty. That is the check that makes the inherited `GIT_DIR` visible, as in its section below. + +**Why one child still separates the settings.** The sections below show that each setting breaks the walk on its own. So a rival that disables the defence against one setting leaves that setting live in the child, and the child fails. Every rival was re-run against the combined test. The ones that were red against the per-setting tests are still red: +- dropping `--template=`; +- the `--local-env-vars` loop; +- the `core.excludesFile` or `core.attributesFile` null; +- each of `GIT_LITERAL_PATHSPECS`, `GIT_GLOB_PATHSPECS`, `GIT_NOGLOB_PATHSPECS` and `GIT_ATTR_SOURCE`; +- the trace prefix: no strip, the exact name only, the exact three names, and the prefix minus `GIT_TRACE`, `_SETUP` or `_PERFORMANCE`; +- the trace2 `0` override. + +**The ones green before are green now.** +- `GIT_ICASE_PATHSPECS` is green, as its section explains. +- The prefix minus `GIT_TRACE2_EVENT` is green, because the `0` override covers it. It was red against the per-setting test at ddcdf93a, before #276 added the override. +- **Dropping `GIT_CONFIG_GLOBAL=/dev/null` or `GIT_CONFIG_NOSYSTEM=1` is green, both at #276's head and here.** Their witness is an excludes line, and vwjj's `-c core.excludesFile=/dev/null` outranks every config file, so the witness cannot see them. Consolidation did not cause this. It is filed as improvise-3j70. + +**Measured** on an Apple M4 Max (16 cores), three `cargo test --workspace` runs each, before and after. The median counts only binaries that ran a test: + +| | this binary | load average | run median | ratio | next largest | +|---|---|---|---|---|---| +| before (#276, 18 children) | 2.10, 3.04, 3.31s | 37–39 (1 min) | 0.00–0.01s | ≈300× | 0.90s, one 2.66s spike | +| after (2 children) | 0.57, 0.59, 0.91s | 30–33 (1 min) | 0.00–0.02s | ≈30–90× | 1.02s, one 2.49s spike | + +The median is at the 0.01s resolution cargo prints, so the ratio is coarse. Before, this binary was the outlier by more than two orders of magnitude and over the tripwire in every run. After, it is below the next-largest binary in every run and well under 2s. The occasional 2.5s spike in "next largest" is some other binary, in both runs; that is load, not this file. + +### An inherited `GIT_DIR` (improvise-53zz) improvise-53zz. The scratch repositories inherited the caller's git environment, and two parts of it change what `git -C ` does. @@ -526,7 +557,7 @@ Rivals, each run against the fixed file: libtest exits 0 when a filter matches nothing, and both of these tests would then pass without running anything. -### a_scratch_repo_ignores_the_users_global_and_system_excludes +### The user's global and system excludes (improvise-53zz) improvise-53zz. The scratch repositories also read the user's own git config. A `*.gz` line in a global `core.excludesFile` made `git add` refuse @@ -545,7 +576,7 @@ Rivals: - Drop `GIT_CONFIG_NOSYSTEM=1`: red, in the system run. The system scope is there so that this half of the fix has a witness at all. -### a_scratch_repo_ignores_the_users_default_ignore_and_attributes_files +### The user's default ignore and attributes files (improvise-vwjj) improvise-vwjj, found reviewing PR #248 at `f5cfc7ed`. The test above sets `core.excludesFile` *inside* a config file, so emptying the config files looks @@ -582,7 +613,7 @@ Rivals, each run against the fixed file: - Drop `core.attributesFile=/dev/null`: red, `fails under a default git/attributes`. -### a_scratch_repo_ignores_the_users_template_dir +### The user's template directory (improvise-pr3i) improvise-pr3i, found reviewing PR #255 at `56ac72a2`. This is a third path to the 53zz symptom. `git init` copies a template directory into the new `.git`, and @@ -610,7 +641,7 @@ the `-c` arguments in `run_git` beat relying on the absence of a config file. Rival: drop `--template=`. Red, as above. -### the_walk_ignores_the_users_pathspec_and_attribute_settings +### Pathspec and attribute settings (improvise-a0i3) improvise-a0i3. `git(root)` strips what `git rev-parse --local-env-vars` names, and that list holds only the variables that point at a repository (`GIT_DIR`, @@ -651,7 +682,7 @@ Rivals, each dropping one name from `PATHSPEC_AND_ATTRIBUTE_SETTINGS`: variables, and a future spelling of the walk that depends on case would be exposed to it. The test covers it, but that coverage is vacuous today. -### the_walk_ignores_a_git_trace_sent_to_stdout +### A git trace sent to stdout (improvise-2iws, improvise-vav0) improvise-2iws, with improvise-vav0 folded in. The `GIT_TRACE*` family is process-wide, like improvise-a0i3's settings, so `--local-env-vars` lists none diff --git a/tests/shipped_documents_load.rs b/tests/shipped_documents_load.rs index 3f4dc62e..63d0a137 100644 --- a/tests/shipped_documents_load.rs +++ b/tests/shipped_documents_load.rs @@ -211,41 +211,70 @@ fn told(out: &std::process::Output) -> String { format!("\n--- stdout\n{stdout}\n--- stderr\n{stderr}") } -/// improvise-pr3i: `git init` copies `GIT_TEMPLATE_DIR`, which no config null -/// reaches. Evidence: `context/test-evidence/tui.md`. +/// Every hostile git setting these tests have met, at once, in one child per +/// test (improvise-eahe). Each setting's own witness is a rival: `context/test-evidence/tui.md`. #[test] -fn a_scratch_repo_ignores_the_users_template_dir() { +fn a_hostile_git_environment_changes_nothing() { + // improvise-pr3i: a template whose `info/exclude` refuses `*.gz`. let templates = tempfile::tempdir().expect("a temp dir"); write(templates.path(), "info/exclude", "*.gz\n"); - let out = rerun_with_env( - "a_gzipped_document_is_walked", - &[("GIT_TEMPLATE_DIR", templates.path())], + // improvise-53zz: a global and a system config whose excludes refuse `*.gz`. + let home = tempfile::tempdir().expect("a temp dir"); + let excludes = home.path().join("ignore"); + std::fs::write(&excludes, "*.gz\n").expect("writes"); + let config = home.path().join("gitconfig"); + // improvise-vav0: the same config also sends every trace2 target to stdout. + let text = format!( + "[core]\n\texcludesFile = {}\n[trace2]\n\tnormalTarget = /dev/stdout\n\teventTarget = /dev/stdout\n\tperfTarget = /dev/stdout\n", + excludes.display() ); - assert!( - out.status.success(), - "the gzip shape test fails under a template info/exclude: {}", - told(&out) + std::fs::write(&config, text).expect("writes"); + // improvise-vwjj: git's default ignore and attributes files, no config line. + let xdg = tempfile::tempdir().expect("a temp dir"); + write(xdg.path(), "git/ignore", "*.gz\n"); + write( + xdg.path(), + "git/attributes", + "*.gz working-tree-encoding=UTF-16\n", ); -} - -/// improvise-53zz: a hook in a linked worktree exports `GIT_DIR`, which beats -/// `-C`. Evidence: `context/test-evidence/tui.md`. -#[test] -fn a_scratch_repo_ignores_an_inherited_git_dir() { + // improvise-53zz: an inherited `GIT_DIR`, which beats `-C`. let outer = scratch_repo(); let dot_git = outer.path().join(".git"); - let out = rerun_with_env( - "a_single_file_un_ignore_is_walked", - &[ - ("GIT_DIR", &dot_git), - ("GIT_INDEX_FILE", &dot_git.join("index")), - ], - ); - assert!( - out.status.success(), - "the shape test fails under an inherited GIT_DIR: {}", - told(&out) - ); + let index = dot_git.join("index"); + let stdout = Path::new("/dev/stdout"); + let one = Path::new("1"); + let hostile: Vec<(&str, &Path)> = vec![ + ("GIT_TEMPLATE_DIR", templates.path()), + ("GIT_CONFIG_GLOBAL", &config), + ("GIT_CONFIG_SYSTEM", &config), + ("XDG_CONFIG_HOME", xdg.path()), + ("GIT_DIR", &dot_git), + ("GIT_INDEX_FILE", &index), + // improvise-a0i3: process-wide pathspec and attribute settings. + ("GIT_LITERAL_PATHSPECS", one), + ("GIT_GLOB_PATHSPECS", one), + ("GIT_NOGLOB_PATHSPECS", one), + ("GIT_ICASE_PATHSPECS", one), + ("GIT_ATTR_SOURCE", Path::new("HEAD")), + // improvise-2iws: every trace that writes to its target on git 2.55. + ("GIT_TRACE", stdout), + ("GIT_TRACE_SETUP", stdout), + ("GIT_TRACE_PERFORMANCE", stdout), + ("GIT_TRACE2", stdout), + ("GIT_TRACE2_EVENT", stdout), + ("GIT_TRACE2_PERF", stdout), + ]; + for test in [ + "a_gzipped_document_is_walked", + "every_shipped_document_loads", + ] { + let out = rerun_with_env(test, &hostile); + assert!( + out.status.success(), + "{test} fails in a hostile git environment: {}", + told(&out) + ); + } let staged = git(outer.path()) .args(["ls-files"]) .output() @@ -256,108 +285,3 @@ fn a_scratch_repo_ignores_an_inherited_git_dir() { String::from_utf8_lossy(&staged.stdout) ); } - -/// improvise-53zz: a user's `*.gz` exclude refused the gzip shape test's `add`. -/// Evidence: `context/test-evidence/tui.md`. -#[test] -fn a_scratch_repo_ignores_the_users_global_and_system_excludes() { - let home = tempfile::tempdir().expect("a temp dir"); - let excludes = home.path().join("ignore"); - std::fs::write(&excludes, "*.gz\n").expect("writes"); - let config = home.path().join("gitconfig"); - let text = format!("[core]\n\texcludesFile = {}\n", excludes.display()); - std::fs::write(&config, text).expect("writes"); - for scope in ["GIT_CONFIG_GLOBAL", "GIT_CONFIG_SYSTEM"] { - let out = rerun_with_env("a_gzipped_document_is_walked", &[(scope, &config)]); - assert!( - out.status.success(), - "the gzip shape test fails under a `*.gz` exclude in {scope}: {}", - told(&out) - ); - } -} - -/// improvise-vwjj: git's DEFAULT ignore and attributes files need no config line. -/// Evidence: `context/test-evidence/tui.md`. -#[test] -fn a_scratch_repo_ignores_the_users_default_ignore_and_attributes_files() { - let files = [ - ("ignore", "*.gz\n"), - ("attributes", "*.gz working-tree-encoding=UTF-16\n"), - ]; - for (name, text) in files { - let xdg = tempfile::tempdir().expect("a temp dir"); - write(xdg.path(), &format!("git/{name}"), text); - let out = rerun_with_env( - "a_gzipped_document_is_walked", - &[("XDG_CONFIG_HOME", xdg.path())], - ); - assert!( - out.status.success(), - "the gzip shape test fails under a default git/{name}: {}", - told(&out) - ); - } -} - -/// improvise-a0i3: pathspec magic and an attribute source are process-wide git -/// settings, and `--local-env-vars` lists none. Evidence: `context/test-evidence/tui.md`. -#[test] -fn the_walk_ignores_the_users_pathspec_and_attribute_settings() { - let settings = [ - ("GIT_LITERAL_PATHSPECS", "1"), - ("GIT_GLOB_PATHSPECS", "1"), - ("GIT_NOGLOB_PATHSPECS", "1"), - ("GIT_ICASE_PATHSPECS", "1"), - ("GIT_ATTR_SOURCE", "HEAD"), - ]; - let mut failed = Vec::new(); - for (name, value) in settings { - for test in [ - "a_gzipped_document_is_walked", - "every_shipped_document_loads", - ] { - let out = rerun_with_env(test, &[(name, Path::new(value))]); - if !out.status.success() { - failed.push(format!("{test} under {name}={value}: {}", told(&out))); - } - } - } - assert!(failed.is_empty(), "{}", failed.join("\n")); -} - -/// improvise-2iws: a git trace sent to stdout lands in `ls-files -z`'s output -/// and is read as paths. Evidence: `context/test-evidence/tui.md`. -#[test] -fn the_walk_ignores_a_git_trace_sent_to_stdout() { - // Every variable of the family that writes to its target on git 2.55, so a - // fixed list of names shorter than the family leaves one behind. - let stdout = Path::new("/dev/stdout"); - let mut traces: Vec<(&str, &Path)> = [ - "GIT_TRACE", - "GIT_TRACE_SETUP", - "GIT_TRACE_PERFORMANCE", - "GIT_TRACE2", - "GIT_TRACE2_EVENT", - "GIT_TRACE2_PERF", - ] - .map(|name| (name, stdout)) - .to_vec(); - // improvise-vav0: the same targets set in config, where no removal reaches. - let home = tempfile::tempdir().expect("a temp dir"); - let config = home.path().join("gitconfig"); - let text = "[trace2]\n\tnormalTarget = /dev/stdout\n\teventTarget = /dev/stdout\n\tperfTarget = /dev/stdout\n"; - std::fs::write(&config, text).expect("writes"); - traces.push(("GIT_CONFIG_GLOBAL", &config)); - for test in [ - "a_gzipped_document_is_walked", - "every_shipped_document_loads", - ] { - let out = rerun_with_env(test, &traces); - assert!( - out.status.success(), - "{test} under a trace to stdout: {}", - told(&out) - ); - } -}