Skip to content

Commit 627db36

Browse files
committed
test: normalize Windows junction semantics
1 parent 9786693 commit 627db36

4 files changed

Lines changed: 37 additions & 19 deletions

File tree

electron/analyzer.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,17 @@ export async function summarizeDirectory(
8787
}
8888

8989
const entryPath = path.join(current, entry.name);
90-
if (entry.isSymbolicLink()) {
90+
let stats;
91+
try {
92+
stats = await lstat(entryPath);
93+
} catch (error) {
94+
if (scanErrors.length < 20) {
95+
scanErrors.push(`${entryPath}: ${error instanceof Error ? error.message : String(error)}`);
96+
}
97+
continue;
98+
}
99+
100+
if (entry.isSymbolicLink() || stats.isSymbolicLink()) {
91101
reparsePointCount += 1;
92102
if (options.includeReparsePoints) {
93103
try {
@@ -104,16 +114,6 @@ export async function summarizeDirectory(
104114
continue;
105115
}
106116

107-
let stats;
108-
try {
109-
stats = await lstat(entryPath);
110-
} catch (error) {
111-
if (scanErrors.length < 20) {
112-
scanErrors.push(`${entryPath}: ${error instanceof Error ? error.message : String(error)}`);
113-
}
114-
continue;
115-
}
116-
117117
newestMtime = Math.max(newestMtime, stats.mtimeMs);
118118
const childPath = firstChild(root, entryPath);
119119
const aggregate = childMap.get(childPath) ?? {

electron/migration.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,22 @@ export function runRobocopy(source: string, destination: string): Promise<void>
8989
});
9090
}
9191

92+
export function normalizeReparseTarget(target: string): string {
93+
let normalized = target.replaceAll("/", "\\");
94+
if (/^\\\\\?\\[a-z]:\\/iu.test(normalized)) normalized = normalized.slice(4);
95+
if (/^\\\?\?\\[a-z]:\\/iu.test(normalized)) normalized = normalized.slice(4);
96+
normalized = path.win32.normalize(normalized);
97+
const root = path.win32.parse(normalized).root;
98+
while (normalized.length > root.length && normalized.endsWith("\\")) {
99+
normalized = normalized.slice(0, -1);
100+
}
101+
return normalized.toLocaleLowerCase();
102+
}
103+
92104
function normalizedReparsePoints(summary: DirectorySummary): string[] {
93105
return (summary.reparsePoints ?? [])
94106
.map(({ relativePath, target }) =>
95-
`${relativePath.replaceAll("/", "\\").toLocaleLowerCase()}\u0000${target
96-
.replaceAll("/", "\\")
97-
.toLocaleLowerCase()}`
107+
`${path.win32.normalize(relativePath).toLocaleLowerCase()}\u0000${normalizeReparseTarget(target)}`
98108
)
99109
.sort();
100110
}

tests/analyzer.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { describe, expect, it } from "vitest";
55
import { portableCandidateScores, summarizeDirectory } from "../electron/analyzer";
66
import { inferKnownDirectory } from "../electron/directory-knowledge";
77
import { hasSignificantAnalysisChange } from "../electron/analysis-freshness";
8+
import { normalizeReparseTarget } from "../electron/migration";
89

910
describe("directory ownership evidence", () => {
1011
it("uses portable executables from another drive as first-party ownership evidence", () => {
@@ -39,9 +40,10 @@ describe("directory ownership evidence", () => {
3940
expect(summary.scanErrors).toEqual([]);
4041
expect(summary.fileCount).toBe(1);
4142
expect(summary.reparsePointCount).toBe(1);
42-
expect(summary.reparsePoints).toEqual([
43-
{ relativePath: "latest", target: versionedDirectory }
44-
]);
43+
expect(summary.reparsePoints?.[0]?.relativePath).toBe("latest");
44+
expect(normalizeReparseTarget(summary.reparsePoints?.[0]?.target ?? "")).toBe(
45+
normalizeReparseTarget(versionedDirectory)
46+
);
4547
} finally {
4648
await rm(temporaryRoot, { recursive: true, force: true });
4749
}

tests/migration.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { lstat, mkdir, mkdtemp, readlink, rm, symlink, writeFile } from "node:fs
22
import os from "node:os";
33
import path from "node:path";
44
import { describe, expect, it } from "vitest";
5-
import { migrationRobocopyArguments, runRobocopy } from "../electron/migration";
5+
import {
6+
migrationRobocopyArguments,
7+
normalizeReparseTarget,
8+
runRobocopy
9+
} from "../electron/migration";
610

711
describe("migration copy options", () => {
812
it("copies junctions and symbolic links as links instead of expanding or excluding them", () => {
@@ -28,7 +32,9 @@ describe("migration copy options", () => {
2832

2933
const copiedLink = path.join(destination, "latest");
3034
expect((await lstat(copiedLink)).isSymbolicLink()).toBe(true);
31-
expect(await readlink(copiedLink)).toBe(target);
35+
expect(normalizeReparseTarget(await readlink(copiedLink))).toBe(
36+
normalizeReparseTarget(target)
37+
);
3238
} finally {
3339
await rm(temporaryRoot, { recursive: true, force: true });
3440
}

0 commit comments

Comments
 (0)