test(ci): canonicalize the Lefthook fixture common directory

Reading Git's path text verbatim to keep trailing whitespace duplicated the
line-terminator helper that scripts/change-scope.ts already owns, which the
duplication gate rejects. Canonicalizing the fixture-side common directory
with realpathSync.native removes the text parsing entirely: short names,
junctions, and directory symlinks all collapse onto the physical directory
the installer resolves, and the injected path stays exact.

Drops the trailing-space case: with no text trimming left in the
derivation, that path already matched the installer before this change, so
the case could not fail and proved nothing.
This commit is contained in:
Tianyi Cui committed 2026-09-26 01:46:03 +08:00
1 parent 1829cdfec9
commit b73a702587
1 file changed
+6 -33
+6 -33
View File
@@ -7,6 +7,7 @@ import {
mkdtempSync,
lstatSync,
readFileSync,
realpathSync,
renameSync,
rmSync,
symlinkSync,
@@ -55,24 +56,6 @@ function git(fixture: Fixture, cwd: string, args: string[]): string {
return result.stdout.trim()
}
/** Remove the line terminator Git appends to a printed path, as the installer does. */
function stripGitLineTerminator(output: string): string {
const withoutLineFeed = output.endsWith('\n') ? output.slice(0, -1) : output
return process.platform === 'win32' && withoutLineFeed.endsWith('\r')
? withoutLineFeed.slice(0, -1)
: withoutLineFeed
}
// `git()` trims, which also drops a worktree directory's own trailing whitespace. Paths that must
// equal the installer's own reading keep only the line terminator, so a trailing space survives.
function gitPath(fixture: Fixture, cwd: string, args: string[]): string {
const result = gitResult(fixture, cwd, args)
if (result.status !== 0) {
throw new Error(`git ${args.join(' ')} failed: ${result.stderr}`)
}
return stripGitLineTerminator(result.stdout)
}
function write(path: string, content: string, mode?: number): void {
mkdirSync(dirname(path), { recursive: true })
writeFileSync(path, content, mode === undefined ? undefined : { mode })
@@ -179,13 +162,12 @@ function gitDirectory(fixture: Fixture, root: string): string {
// The installer resolves the common directory against Git's own top-level path, and the host
// spells that path differently from the fixture directory this file created: Git canonicalizes
// Windows 8.3 short names and the macOS `/var` symlink, while `mkdtempSync` returns the temp
// directory's own spelling. Reading both values the way the installer reads them (`gitPath`, not
// the trimming `git()`) keeps the relative common directory resolved to the same string the
// installer computes, so an injected failure on this path reaches the installer's own lock.
// directory's own spelling. Canonicalizing the fixture-side directory collapses both spellings
// onto the one directory the installer touches, so an injected failure on this path reaches the
// installer's own lock instead of a differently spelled name for it.
function commonDirectory(fixture: Fixture): string {
const root = gitPath(fixture, fixture.main, ['rev-parse', '--show-toplevel'])
const output = gitPath(fixture, fixture.main, ['rev-parse', '--git-common-dir'])
return isAbsolute(output) ? output : resolve(root, output)
const output = git(fixture, fixture.main, ['rev-parse', '--git-common-dir'])
return realpathSync.native(isAbsolute(output) ? output : resolve(fixture.main, output))
}
function hooksPath(fixture: Fixture, root: string): string {
@@ -415,15 +397,6 @@ syncBuiltinESMExports()
await expectInjectedLockAccessFailure(fixture, { operation: 'openSync', code: 'EPERM', expires: false })
})
// A worktree directory can end in a space on POSIX hosts; Git prints that path verbatim while a
// trimming read would drop the space and name a different directory, so the injection only fires
// when the expected path is read the installer's way. Windows cannot create such a directory.
it.skipIf(process.platform === 'win32')('handles an injected lock failure in a worktree whose path ends with a space', async () => {
await expectInjectedLockAccessFailure(createFixture({ main: 'main ' }), {
operation: 'openSync', code: 'EPERM', expires: false,
})
})
it('waits for a concurrent installer to finish publishing its lock record', async () => {
const fixture = createFixture()
const lockPath = installLockPath(fixture)