test: the tests that shell out to git bring their own config #49
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/test-repo-no-signing"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two test files shell out to real git —
packages/server/tests/external_modules_test.ts(seeds a repo so a real clone has something to find) andpackages/modules/docker/backend/commit_test.ts(runscommitLinesagainst real repositories). Both ran under whatever the developer's machine happened to say, and several of those settings decide whether a test passes.With
commit.gpgsign=true, the seed commit goes to pinentry:That is the two clone tests in
external_modules_test.tsfailing.commit_test.tshad already been bitten — itsrepo()setcommit.gpgsign=falseper repo, with a comment recording four tests and five minutes of pinentry timeouts. The same reach-in works throughcore.hooksPath,init.templateDirandurl.*.insteadOf, none of which either file had a line for. CI sets none of them, so the whole class only ever fires on a developer machine — the worse failure, because it reads as a bug in the branch under test. It has already cost review time on #48 twice, where both rounds had to say "not this branch, my git config".What this does
git takes its configuration from two places, so isolating it takes two:
The config files.
tools/test-gitconfigis now what those tests run under, passed asGIT_CONFIG_GLOBAL(plusGIT_CONFIG_NOSYSTEM=1) so it replaces the global and system files rather than layering on top of them. It carries the identity andgpgsign=falseboth callers were setting per repo, so those lines are gone — one file read by both, instead of a line per repo covering only the setting somebody already got bitten by.The environment, which outranks the files.
GIT_CONFIG_COUNTwith itsGIT_CONFIG_KEY_<n>/GIT_CONFIG_VALUE_<n>pairs sets any config key at all,GIT_AUTHOR_*/GIT_COMMITTER_*set the identity,GIT_DIR/GIT_WORK_TREEmove the repository out from under-C— andDeno.Command'senvoption merges into the parent environment instead of replacing it, so a config file alone closes half the hole.tools/test-git-env.tsowns the spawn options both call sites now spread:A strip rather than an allow-list of the variables that matter, because an allow-list written against today's git would not have
GIT_CONFIG_COUNTin it (2.31). This is what git's own test suite does —t/test-lib.shunsetsGIT_*wholesale before setting the few it wants.envandclearEnvship as one object becauseenvon its own is the merge that caused this, so a call site cannot take half of it. Everything that is notGIT_*is inherited on purpose: PATH, HOME, TMPDIR, SYSTEMROOT and whatever else a platform needs to start a process — naming those breaks on the first machine nobody tried.The two duplicated
TEST_GIT_ENVblocks are gone with it, and with them the../../../depth each was counting to the same file. Incommit_test.tsthe options go onsh(), so thecommitLinescode under test runs under them too; it shells out through that same helper and its commits were the ones stalling.tools/rather than beside either caller, and a file plus a module rather than something in a package, because both callers live in different workspace packages and neither should reach into the other's. Not the product: the real clone inmodules/external.tsstill inherits the environment whole, which is where an operator's credentials andinsteadOfrules live.Verification
deno task test→ 484 passed | 0 failed | 1 ignored, withcommit.gpgsign=trueglobally. No pinentry prompt. (external_modules_test.tsalone: 9 passed, was 7 passed / 2 failed.)commit.gpgsign,tag.gpgsign, apre-commithook viacore.hooksPaththat exits 1,url."https://evil.invalid/".insteadOf,init.defaultBranch=hostile— and a hostile environment:GIT_AUTHOR_NAME/GIT_AUTHOR_EMAIL,GIT_COMMITTER_*, and aGIT_CONFIG_COUNTpair settingcommit.gpgsign=trueandcore.hooksPath. Still 484 passed | 0 failed | 1 ignored. The same environment against the previous commit (config file only): 8 passed | 8 failed, which is the hole this second commit closes.tools/test-gitconfigaside and the git tests fail loudly (Author identity unknown) — proof the file is read rather than the suite passing for some other reason.--allow-env. The helper inherits nothing (nothing to inherit is also nothing that can bleed in), git gets the two config variables alone, andcommit_test.tsstill passes 7 | 0 — so a narrower single-file invocation thandeno task testdoes not break on the env read.deno task check,deno lint,deno fmt --checkclean.GIT_CONFIG_GLOBALneeds git ≥ 2.32 (June 2021); verified against 2.55.0.0f32049addd0f7df8f1ftest: a seeded temp repo turns commit signing offto test: the tests that shell out to git bring their own configThe config-file isolation is solid and everything the PR body claims checked out on verification: path depths are correct from both files, every git invocation in both test files goes through the env-wrapped helper, the removed per-repo lines are all covered by tools/test-gitconfig, and the product clone in external.ts is rightly untouched.
One hole remains, same disease one layer up:
Deno.Command'senvoption merges with the parent environment (noclearEnv), so environment-variable git configuration still bleeds through —GIT_AUTHOR_NAME/GIT_AUTHOR_EMAIL,GIT_COMMITTER_*, and especiallyGIT_CONFIG_COUNT/GIT_CONFIG_KEY_*/GIT_CONFIG_VALUE_*, all of which override the config file. A machine that exports any of these reproduces exactly the class of failure this PR closes for~/.gitconfig. Two inline comments, one per call site.Suggested fix: either
clearEnv: true(then check git still finds HOME/TEMP/PATH-dependent bits on all platforms), or explicitly unset the knownGIT_*overrides in TEST_GIT_ENV.@ -27,3 +41,4 @@async function sh(cmd: string, args: string[]) {const r = await new Deno.Command(cmd, {args,env: TEST_GIT_ENV,envhere merges with the parent environment — Deno.Command only replaces it withclearEnv: true. SoGIT_AUTHOR_NAME,GIT_COMMITTER_*, andGIT_CONFIG_COUNT/GIT_CONFIG_KEY_*from the developer's shell still reach every git run and override tools/test-gitconfig. Same machine-state bleed this PR fixes for the config file, one layer up. EitherclearEnv: trueplus whatever git needs re-added, or explicitly unset the knownGIT_*overrides in TEST_GIT_ENV.@ -222,3 +235,4 @@const run = async (...args: string[]) => {const out = await new Deno.Command("git", {args: ["-C", dir, ...args],env: TEST_GIT_ENV,Same env-merge hole as commit_test.ts
sh(): parentGIT_*environment variables (author/committer identity,GIT_CONFIG_COUNTfamily) merge in and override the config file. Whatever fix lands there belongs here too — the two TEST_GIT_ENV blocks should stay identical.Right on both counts, and thanks for chasing it one layer up —
envwithoutclearEnvis a merge, and the environment beats the config file, so the file alone closed half the hole. Fixed in6522527.Neither of the two suggested shapes on its own:
clearEnv: truewith a re-added allow-list is the portability bug (PATH, HOME, TMPDIR, SYSTEMROOT, and whatever the next platform needs), and explicitly unsetting the knownGIT_*names rots — an allow-list written against today's git would not haveGIT_CONFIG_COUNTin it, which only exists since 2.31. AlsoGIT_AUTHOR_NAME=""is not "unset": git dies withempty ident name (for <…>) not allowed.So:
clearEnv: truewith the inherited environment minus everyGIT_*, which is what git's ownt/test-lib.shdoes (unsetsGIT_*wholesale, then sets the few it wants). Case-insensitive strip, because Windows environment names are.tools/test-git-env.tsnow owns both, and the twoTEST_GIT_ENVblocks are gone rather than kept in sync — your "should stay identical" is a maintenance requirement I would rather not have:One object, because
envon its own merges — that being the bug, a call site cannot take half of it. Both call sites are now{ args, ...TEST_GIT_SPAWN, stdout, stderr }, and the../../../../tools/test-gitconfigdepth each file was counting is gone too; the helper resolves it from its ownimport.meta.dirname.Verification, with the hostile config file from the original round and a hostile environment on top (
GIT_AUTHOR_*,GIT_COMMITTER_*,GIT_CONFIG_COUNT=2settingcommit.gpgsign=trueandcore.hooksPath):d0f7df8: 8 passed | 8 failed — the hole, reproduced and then closed.Author identity unknown).--allow-envthe helper inherits nothing and git gets the two config variables alone;commit_test.tspasses 7 | 0. That path exists because the env read is at module load, and a single-filedeno testinvocation narrower thandeno task testwould otherwise die before the git gate.deno task check,deno lint,deno fmt --checkclean.PR body updated for both layers, including the "a file crosses no package boundary" line, which no longer describes what is there.
Both requested changes are closed by the rework, verified against the tree rather than the diff text:
tools/test-git-env.tsstrips everyGIT_*variable from the inherited environment (case-insensitive, which Windows needs), re-addsGIT_CONFIG_GLOBAL+GIT_CONFIG_NOSYSTEM, and ships the result as oneTEST_GIT_SPAWNobject withclearEnv: true— a call site cannot take the env without the clear, which was the failure mode of the previous shape.stdout/stderrafter the spread touch nothing it sets.test-gitconfigreference fromtools/itself all resolve.tools/sits outside thepackages/*workspace, so the shared module crosses no package boundary; lint covers it, and nothing in deno.json breaks.GIT_CONFIG_GLOBALreplaces the entire global scope including$XDG_CONFIG_HOME/git/config, so no lookup path is left uncovered.GIT_CONFIG_COUNTreason the comment gives; matches what git's own test suite does.The module-load-time env snapshot means a test mutating
Deno.envafter import won't reach spawned git — fine for what these tests do, just worth knowing it's frozen.