Files
improvise/tests/shipped_documents_load.rs
T
Edward Langley 56ac72a291 Null the default excludes and attributes files for scratch repos
improvise-vwjj, found reviewing #248. GIT_CONFIG_GLOBAL=/dev/null empties
the config files. It does not stop git falling back to
$XDG_CONFIG_HOME/git/ignore (or ~/.config/git/ignore) when
core.excludesFile is unset, and git/attributes has the same kind of
default. So a *.gz line in the user's default ignore file still failed
a_gzipped_document_is_walked.

run_git now passes -c core.excludesFile=/dev/null and
-c core.attributesFile=/dev/null. A -c argument outranks every config
file and every default path.

The new test re-runs the gzip shape test under an XDG_CONFIG_HOME whose
git/ignore holds *.gz, and again with a git/attributes line that breaks
git add. Each half was red on its own.

Stacked on #248 (fix/53zz-scratch-repo-env).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 11:29:01 -07:00

244 lines
8.2 KiB
Rust

//! Every `.improv` document this repository ships must load (improvise-odbc).
//! "Shipped" is "tracked", read off git's index: `context/test-evidence/tui.md`.
use std::path::{Path, PathBuf};
/// Every tracked `.improv` and `.improv.gz` under `root`. Outside a checkout this
/// panics rather than answering empty (improvise-c0aj).
fn shipped_documents(root: &Path) -> Vec<PathBuf> {
let out = git(root)
.args(["ls-files", "-z", "--", "*.improv", "*.improv.gz"])
.output()
.expect("git runs: this test reads the index, so it needs a checkout");
assert!(
out.status.success(),
"git ls-files failed in {}: {}",
root.display(),
String::from_utf8_lossy(&out.stderr)
);
let mut found: Vec<PathBuf> = String::from_utf8(out.stdout)
.expect("UTF-8 paths")
.split('\0')
.filter(|p| !p.is_empty())
.map(|p| root.join(p))
.collect();
found.sort();
found
}
#[test]
fn every_shipped_document_loads() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let documents = shipped_documents(root);
assert!(
!documents.is_empty(),
"precondition: the index must list at least one shipped document"
);
for path in documents {
let workbook = improvise::persistence::load(&path)
.unwrap_or_else(|e| panic!("{} does not load: {e}", path.display()));
assert!(
workbook.views.contains_key(&workbook.active_view),
"{} loads but its initial view {:?} is not one of {:?}",
path.display(),
workbook.active_view,
workbook.views.keys().collect::<Vec<_>>()
);
}
}
/// `git -C root`, with every repo-local variable a caller exported removed,
/// since `GIT_DIR` and friends override `-C` (improvise-53zz).
fn git(root: &Path) -> std::process::Command {
let names = std::process::Command::new("git")
.args(["rev-parse", "--local-env-vars"])
.output()
.expect("git runs");
let mut git = std::process::Command::new("git");
for name in String::from_utf8(names.stdout).expect("UTF-8").lines() {
git.env_remove(name);
}
git.arg("-C").arg(root);
git
}
/// A scratch repository, so each way of shipping a document is set up for real.
fn scratch_repo() -> tempfile::TempDir {
let dir = tempfile::tempdir().expect("a temp dir");
run_git(dir.path(), &["init", "-q"]);
dir
}
fn run_git(root: &Path, args: &[&str]) {
let status = git(root)
.env("GIT_CONFIG_GLOBAL", "/dev/null")
.env("GIT_CONFIG_NOSYSTEM", "1")
.args(["-c", "core.excludesFile=/dev/null"])
.args(["-c", "core.attributesFile=/dev/null"])
.args(args)
.status()
.expect("git runs");
assert!(status.success(), "git {args:?} failed");
}
fn write(root: &Path, rel: &str, text: &str) {
let path = root.join(rel);
std::fs::create_dir_all(path.parent().expect("a parent")).expect("dirs");
std::fs::write(path, text).expect("writes");
}
fn walked(root: &Path, rel: &str) -> bool {
shipped_documents(root).contains(&root.join(rel))
}
/// improvise-c0aj: one test per way of shipping a document that reading the
/// root `.gitignore` missed. Evidence: `context/test-evidence/tui.md`.
#[test]
fn a_single_file_un_ignore_is_walked() {
let repo = scratch_repo();
let root = repo.path();
write(root, ".gitignore", "*.improv\n!web/demo.improv\n");
write(root, "web/demo.improv", "x");
run_git(root, &["add", ".gitignore", "web/demo.improv"]);
assert!(walked(root, "web/demo.improv"));
}
#[test]
fn a_gzipped_document_is_walked() {
let repo = scratch_repo();
let root = repo.path();
write(root, ".gitignore", "*.improv\n");
write(root, "docs/model.improv.gz", "x");
run_git(root, &["add", ".gitignore", "docs/model.improv.gz"]);
assert!(walked(root, "docs/model.improv.gz"));
}
#[test]
fn a_document_un_ignored_by_a_nested_gitignore_is_walked() {
let repo = scratch_repo();
let root = repo.path();
write(root, ".gitignore", "*.improv\n");
write(root, "harness/.gitignore", "!keep.improv\n");
write(root, "harness/keep.improv", "x");
run_git(
root,
&[
"add",
".gitignore",
"harness/.gitignore",
"harness/keep.improv",
],
);
assert!(walked(root, "harness/keep.improv"));
}
/// An empty walk would pass every per-document assertion vacuously, so a
/// directory git cannot read must stop the test instead.
#[test]
#[should_panic(expected = "git ls-files failed")]
fn a_directory_that_is_not_a_checkout_fails_loudly() {
let not_a_repo = tempfile::tempdir().expect("a temp dir");
shipped_documents(not_a_repo.path());
}
#[test]
fn a_force_added_document_is_walked() {
let repo = scratch_repo();
let root = repo.path();
write(root, ".gitignore", "*.improv\n");
write(root, "secret/forced.improv", "x");
run_git(root, &["add", ".gitignore"]);
run_git(root, &["add", "-f", "secret/forced.improv"]);
assert!(walked(root, "secret/forced.improv"));
}
/// Runs one of this binary's own tests in a child process with `env` added,
/// since the environment is what is under test and threads share ours.
fn rerun_with_env(test: &str, env: &[(&str, &Path)]) -> std::process::Output {
let mut child = std::process::Command::new(std::env::current_exe().expect("test binary"));
child.args([test, "--exact", "--nocapture"]);
for (name, value) in env {
child.env(name, value);
}
let out = child.output().expect("the test binary runs");
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("running 1 test"),
"{test} did not run: {stdout}"
);
out
}
/// 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() {
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: {}",
String::from_utf8_lossy(&out.stdout)
);
let staged = git(outer.path())
.args(["ls-files"])
.output()
.expect("git runs");
assert!(
staged.stdout.is_empty(),
"the outer index gained: {}",
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}: {}",
String::from_utf8_lossy(&out.stdout)
);
}
}
/// 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}: {}",
String::from_utf8_lossy(&out.stdout)
);
}
}