Skip to content

Commit 3617213

Browse files
committed
add unit test for the copy-examples job, which had a pathing issue on win32.
1 parent 436119b commit 3617213

File tree

4 files changed

+155
-3
lines changed

4 files changed

+155
-3
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"clean": "tsc --build --clean",
1414
"dev": "tsc --build --watch",
1515
"lint": "eslint .",
16-
"test": "npm run test --workspace react-native-node-api-modules --workspace react-native-node-api-cmake --workspace gyp-to-cmake --workspace node-addon-examples"
16+
"test": "npm run test --workspace react-native-node-api-modules --workspace react-native-node-api-cmake --workspace gyp-to-cmake --workspace react-native-node-addon-examples"
1717
},
1818
"author": {
1919
"name": "Callstack",

packages/node-addon-examples/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"build": "tsx scripts/build-examples.mts",
1414
"copy-and-build": "npm run copy-examples && npm run gyp-to-cmake && npm run build",
1515
"verify": "tsx scripts/verify-prebuilds.mts",
16-
"test": "npm run copy-and-build && npm run verify"
16+
"test": "tsx --test scripts/*.test.mts scripts/**/*.test.mts",
17+
"test:integration": "npm run copy-and-build && npm run verify"
1718
},
1819
"devDependencies": {
1920
"node-addon-examples": "github:nodejs/node-addon-examples#4213d4c9d07996ae68629c67926251e117f8e52a",

packages/node-addon-examples/scripts/cmake-projects.mts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { readdirSync, statSync } from "node:fs";
22
import { join } from "node:path";
3+
import { fileURLToPath } from "node:url";
34

4-
export const EXAMPLES_DIR = new URL("../examples", import.meta.url).pathname;
5+
export const EXAMPLES_DIR = fileURLToPath(new URL("../examples", import.meta.url));
56

67
export function findCMakeProjects(dir = EXAMPLES_DIR): string[] {
78
let results: string[] = [];
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import assert from "node:assert/strict";
2+
import { describe, it, TestContext } from "node:test";
3+
import path from "node:path";
4+
import fs from "node:fs";
5+
import os from "node:os";
6+
7+
import { EXAMPLES_DIR, findCMakeProjects } from "./cmake-projects.mjs";
8+
9+
function setupTempDirectory(context: TestContext, files: Record<string, string>) {
10+
const tempDirectoryPath = fs.realpathSync(
11+
fs.mkdtempSync(path.join(os.tmpdir(), "cmake-projects-test-"))
12+
);
13+
14+
context.after(() => {
15+
fs.rmSync(tempDirectoryPath, { recursive: true, force: true });
16+
});
17+
18+
for (const [filePath, content] of Object.entries(files)) {
19+
const fullPath = path.join(tempDirectoryPath, filePath);
20+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
21+
fs.writeFileSync(fullPath, content, "utf8");
22+
}
23+
24+
return tempDirectoryPath;
25+
}
26+
27+
describe("EXAMPLES_DIR", () => {
28+
it("should resolve to a valid platform-specific path", () => {
29+
// Check that EXAMPLES_DIR is an absolute path
30+
assert(path.isAbsolute(EXAMPLES_DIR), "EXAMPLES_DIR should be absolute");
31+
32+
// On Windows, should not start with /
33+
if (process.platform === "win32") {
34+
assert(
35+
!EXAMPLES_DIR.startsWith("/"),
36+
"Windows path should not start with /"
37+
);
38+
// Should match Windows path pattern (e.g., C:\... or D:\...)
39+
assert(
40+
/^[A-Za-z]:[\\/]/.test(EXAMPLES_DIR),
41+
"Windows path should start with drive letter"
42+
);
43+
} else {
44+
// On Unix-like systems, should start with /
45+
assert(
46+
EXAMPLES_DIR.startsWith("/"),
47+
"Unix path should start with /"
48+
);
49+
}
50+
});
51+
52+
it("should work correctly with path.join operations", (context) => {
53+
const tempDir = setupTempDirectory(context, {
54+
"test/subdir/file.txt": "test content",
55+
});
56+
57+
// Simulate what happens in copy-examples.mts
58+
const relativePath = "test/subdir";
59+
const joinedPath = path.join(tempDir, relativePath);
60+
61+
// The joined path should exist and be accessible
62+
assert(fs.existsSync(joinedPath), "Joined path should exist");
63+
assert(
64+
fs.statSync(joinedPath).isDirectory(),
65+
"Joined path should be a directory"
66+
);
67+
});
68+
69+
it("should handle URL to path conversion correctly on all platforms", () => {
70+
// Create a test URL similar to how EXAMPLES_DIR is created
71+
const testUrl = new URL("../examples", import.meta.url);
72+
const convertedPath = testUrl.pathname;
73+
74+
// The converted path should work with fs operations
75+
// We can't test the actual EXAMPLES_DIR since it might not exist,
76+
// but we can verify the conversion produces valid paths
77+
if (process.platform === "win32") {
78+
// On Windows, URL.pathname returns /C:/... which is invalid
79+
// Our fix uses fileURLToPath which returns C:\...
80+
assert(
81+
!path.isAbsolute(convertedPath) || convertedPath.startsWith("/"),
82+
"Direct URL.pathname on Windows produces invalid absolute paths"
83+
);
84+
}
85+
});
86+
});
87+
88+
describe("findCMakeProjects", () => {
89+
it("should find CMakeLists.txt files recursively", (context) => {
90+
const tempDir = setupTempDirectory(context, {
91+
"project1/CMakeLists.txt": "# CMake file 1",
92+
"project2/subdir/CMakeLists.txt": "# CMake file 2",
93+
"project3/CMakeLists.txt": "# CMake file 3",
94+
"not-a-project/other.txt": "not cmake",
95+
});
96+
97+
const projects = findCMakeProjects(tempDir);
98+
99+
assert.equal(projects.length, 3, "Should find 3 CMake projects");
100+
101+
// Sort for consistent comparison
102+
const sortedProjects = projects.sort();
103+
const expectedProjects = [
104+
path.join(tempDir, "project1"),
105+
path.join(tempDir, "project2", "subdir"),
106+
path.join(tempDir, "project3"),
107+
].sort();
108+
109+
assert.deepEqual(sortedProjects, expectedProjects);
110+
});
111+
112+
it("should handle empty directories", (context) => {
113+
const tempDir = setupTempDirectory(context, {});
114+
const projects = findCMakeProjects(tempDir);
115+
assert.equal(projects.length, 0, "Should find no projects in empty dir");
116+
});
117+
118+
it("should handle nested CMake projects", (context) => {
119+
const tempDir = setupTempDirectory(context, {
120+
"parent/CMakeLists.txt": "# Parent CMake",
121+
"parent/child/CMakeLists.txt": "# Child CMake",
122+
"parent/child/grandchild/CMakeLists.txt": "# Grandchild CMake",
123+
});
124+
125+
const projects = findCMakeProjects(tempDir);
126+
127+
assert.equal(projects.length, 3, "Should find all nested projects");
128+
assert(
129+
projects.includes(path.join(tempDir, "parent")),
130+
"Should include parent project"
131+
);
132+
assert(
133+
projects.includes(path.join(tempDir, "parent", "child")),
134+
"Should include child project"
135+
);
136+
assert(
137+
projects.includes(path.join(tempDir, "parent", "child", "grandchild")),
138+
"Should include grandchild project"
139+
);
140+
});
141+
142+
it("should work with Windows-style paths", { skip: process.platform !== "win32" }, (context) => {
143+
const tempDir = setupTempDirectory(context, {
144+
"windows\\style\\path\\CMakeLists.txt": "# CMake file",
145+
});
146+
147+
const projects = findCMakeProjects(tempDir);
148+
assert.equal(projects.length, 1, "Should find project with Windows path");
149+
});
150+
});

0 commit comments

Comments
 (0)