diff --git a/scripts/lib/channel-contract-test-plan.mjs b/scripts/lib/channel-contract-test-plan.mjs index 1aca18246f3..69c2454ba3a 100644 --- a/scripts/lib/channel-contract-test-plan.mjs +++ b/scripts/lib/channel-contract-test-plan.mjs @@ -1,7 +1,20 @@ +import { spawnSync } from "node:child_process"; import { existsSync, readdirSync } from "node:fs"; import { join, relative } from "node:path"; function listContractTestFiles(rootDir = "src/channels/plugins/contracts") { + const result = spawnSync("git", ["ls-files", "--", rootDir], { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }); + if (result.status === 0) { + return result.stdout + .split("\n") + .map((line) => line.trim().replaceAll("\\", "/")) + .filter((line) => line.endsWith(".test.ts")) + .toSorted((a, b) => a.localeCompare(b)); + } + if (!existsSync(rootDir)) { return []; } diff --git a/scripts/lib/plugin-contract-test-plan.mjs b/scripts/lib/plugin-contract-test-plan.mjs index 6cca550e53b..82e94ecfc11 100644 --- a/scripts/lib/plugin-contract-test-plan.mjs +++ b/scripts/lib/plugin-contract-test-plan.mjs @@ -1,7 +1,20 @@ +import { spawnSync } from "node:child_process"; import { existsSync, readdirSync } from "node:fs"; import { join } from "node:path"; function listContractTestFiles(rootDir = "src/plugins/contracts") { + const result = spawnSync("git", ["ls-files", "--", rootDir], { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }); + if (result.status === 0) { + return result.stdout + .split("\n") + .map((line) => line.trim().replaceAll("\\", "/")) + .filter((line) => line.endsWith(".test.ts")) + .toSorted((a, b) => a.localeCompare(b)); + } + if (!existsSync(rootDir)) { return []; } diff --git a/test/scripts/channel-contract-test-plan.test.ts b/test/scripts/channel-contract-test-plan.test.ts index 01927129031..cbe8c8f2172 100644 --- a/test/scripts/channel-contract-test-plan.test.ts +++ b/test/scripts/channel-contract-test-plan.test.ts @@ -1,16 +1,18 @@ -import { existsSync, readdirSync } from "node:fs"; -import { join } from "node:path"; +import { spawnSync } from "node:child_process"; import { describe, expect, it } from "vitest"; import { createChannelContractTestShards } from "../../scripts/lib/channel-contract-test-plan.mjs"; function listContractTests(rootDir = "src/channels/plugins/contracts"): string[] { - if (!existsSync(rootDir)) { - return []; - } - - return readdirSync(rootDir, { withFileTypes: true }) - .filter((entry) => entry.isFile() && entry.name.endsWith(".test.ts")) - .map((entry) => join(rootDir, entry.name).replaceAll("\\", "/")) + const result = spawnSync("git", ["ls-files", "--", rootDir], { + cwd: process.cwd(), + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }); + expect(result.status).toBe(0); + return result.stdout + .split("\n") + .map((line) => line.trim().replaceAll("\\", "/")) + .filter((line) => line.endsWith(".test.ts")) .toSorted((a, b) => a.localeCompare(b)); } @@ -42,6 +44,54 @@ describe("scripts/lib/channel-contract-test-plan.mjs", () => { expect(new Set(actual).size).toBe(actual.length); }); + it("uses git-tracked files without walking contract directories", () => { + const result = spawnSync( + process.execPath, + [ + "--input-type=module", + "--eval", + ` + import fs from "node:fs"; + import { syncBuiltinESMExports } from "node:module"; + const counts = { existsSync: 0, readdirSync: 0 }; + const originalExistsSync = fs.existsSync; + const originalReaddirSync = fs.readdirSync; + fs.existsSync = (...args) => { + counts.existsSync += 1; + return originalExistsSync(...args); + }; + fs.readdirSync = (...args) => { + counts.readdirSync += 1; + return originalReaddirSync(...args); + }; + syncBuiltinESMExports(); + const { createChannelContractTestShards } = await import("./scripts/lib/channel-contract-test-plan.mjs"); + const shards = createChannelContractTestShards(); + console.log(JSON.stringify({ + counts, + files: shards.reduce((total, shard) => total + shard.includePatterns.length, 0), + shards: shards.length, + })); + `, + ], + { + cwd: process.cwd(), + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }, + ); + + expect(result.status, result.stderr).toBe(0); + const payload = JSON.parse(result.stdout) as { + counts: { existsSync: number; readdirSync: number }; + files: number; + shards: number; + }; + expect(payload.shards).toBe(3); + expect(payload.files).toBeGreaterThan(0); + expect(payload.counts).toEqual({ existsSync: 0, readdirSync: 0 }); + }); + it("keeps registry-backed surface shards spread across checks", () => { for (const shard of createChannelContractTestShards()) { const surfaceRegistryFiles = shard.includePatterns.filter((pattern) => diff --git a/test/scripts/plugin-contract-test-plan.test.ts b/test/scripts/plugin-contract-test-plan.test.ts index 2827b8b8fed..8447e12e1af 100644 --- a/test/scripts/plugin-contract-test-plan.test.ts +++ b/test/scripts/plugin-contract-test-plan.test.ts @@ -1,29 +1,20 @@ -import { existsSync, readFileSync, readdirSync } from "node:fs"; -import { join } from "node:path"; +import { spawnSync } from "node:child_process"; +import { readFileSync } from "node:fs"; import { describe, expect, it } from "vitest"; import { createPluginContractTestShards } from "../../scripts/lib/plugin-contract-test-plan.mjs"; function listContractTests(rootDir = "src/plugins/contracts"): string[] { - if (!existsSync(rootDir)) { - return []; - } - - const files: string[] = []; - const visit = (dir: string) => { - for (const entry of readdirSync(dir, { withFileTypes: true })) { - const path = join(dir, entry.name); - if (entry.isDirectory()) { - visit(path); - continue; - } - if (entry.isFile() && entry.name.endsWith(".test.ts")) { - files.push(path.replaceAll("\\", "/")); - } - } - }; - - visit(rootDir); - return files.toSorted((a, b) => a.localeCompare(b)); + const result = spawnSync("git", ["ls-files", "--", rootDir], { + cwd: process.cwd(), + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }); + expect(result.status).toBe(0); + return result.stdout + .split("\n") + .map((line) => line.trim().replaceAll("\\", "/")) + .filter((line) => line.endsWith(".test.ts")) + .toSorted((a, b) => a.localeCompare(b)); } describe("scripts/lib/plugin-contract-test-plan.mjs", () => { @@ -66,6 +57,54 @@ describe("scripts/lib/plugin-contract-test-plan.mjs", () => { expect(new Set(actual).size).toBe(actual.length); }); + it("uses git-tracked files without walking contract directories", () => { + const result = spawnSync( + process.execPath, + [ + "--input-type=module", + "--eval", + ` + import fs from "node:fs"; + import { syncBuiltinESMExports } from "node:module"; + const counts = { existsSync: 0, readdirSync: 0 }; + const originalExistsSync = fs.existsSync; + const originalReaddirSync = fs.readdirSync; + fs.existsSync = (...args) => { + counts.existsSync += 1; + return originalExistsSync(...args); + }; + fs.readdirSync = (...args) => { + counts.readdirSync += 1; + return originalReaddirSync(...args); + }; + syncBuiltinESMExports(); + const { createPluginContractTestShards } = await import("./scripts/lib/plugin-contract-test-plan.mjs"); + const shards = createPluginContractTestShards(); + console.log(JSON.stringify({ + counts, + files: shards.reduce((total, shard) => total + shard.includePatterns.length, 0), + shards: shards.length, + })); + `, + ], + { + cwd: process.cwd(), + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }, + ); + + expect(result.status, result.stderr).toBe(0); + const payload = JSON.parse(result.stdout) as { + counts: { existsSync: number; readdirSync: number }; + files: number; + shards: number; + }; + expect(payload.shards).toBe(4); + expect(payload.files).toBeGreaterThan(0); + expect(payload.counts).toEqual({ existsSync: 0, readdirSync: 0 }); + }); + it("keeps plugin registration contract files spread across checks", () => { for (const shard of createPluginContractTestShards()) { const registrationFiles = shard.includePatterns.filter((pattern) =>