-
Notifications
You must be signed in to change notification settings - Fork 273
[Chore] Use cacheable extension test lanes in CI #1620
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f2ad040
5129761
54581c8
795e8c0
af97e9d
d6a67e9
89a338e
75d19fa
f69e1fc
55c60fc
dd42be5
a857936
e38cb20
6e39677
e20f66d
ec7fd78
3c65568
d6d9c21
4378384
c493be8
79b0de8
74c2bbc
dc59ffe
edb92d0
17aaaef
eaed70a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ mock/ | |
| # Builds | ||
| bin/ | ||
| *.vsix | ||
| /src/generated/ | ||
|
|
||
| # Local prompts and rules | ||
| /local-prompts | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import fs from "node:fs" | ||
| import path from "node:path" | ||
| import process from "node:process" | ||
| import { fileURLToPath } from "node:url" | ||
|
|
||
| const srcDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..") | ||
|
Check warning on line 6 in src/scripts/copy-tree-sitter-wasms.mjs
|
||
| const wasmDir = path.join(srcDir, "node_modules", "tree-sitter-wasms", "out") | ||
|
Check warning on line 7 in src/scripts/copy-tree-sitter-wasms.mjs
|
||
| const generatedDir = path.join(srcDir, "generated", "tree-sitter-wasms") | ||
|
Check warning on line 8 in src/scripts/copy-tree-sitter-wasms.mjs
|
||
| const wasmPattern = /^tree-sitter-.*\.wasm$/ | ||
|
Check warning on line 9 in src/scripts/copy-tree-sitter-wasms.mjs
|
||
|
|
||
| export async function prepareTreeSitterWasms(sourceDir, destinationDir, { filesystem = fs.promises } = {}) { | ||
| const sourceFiles = (await filesystem.readdir(sourceDir)).filter((filename) => wasmPattern.test(filename)).sort() | ||
|
Check warning on line 12 in src/scripts/copy-tree-sitter-wasms.mjs
|
||
| if (sourceFiles.length === 0) throw new Error("WASM source set is empty") | ||
|
Check warning on line 13 in src/scripts/copy-tree-sitter-wasms.mjs
|
||
|
|
||
| await filesystem.rm(destinationDir, { recursive: true, force: true }) | ||
| await filesystem.mkdir(destinationDir, { recursive: true }) | ||
| try { | ||
| for (const filename of sourceFiles) { | ||
| await filesystem.copyFile(path.join(sourceDir, filename), path.join(destinationDir, filename)) | ||
| } | ||
| } catch (error) { | ||
| await filesystem.rm(destinationDir, { recursive: true, force: true }) | ||
|
Check warning on line 22 in src/scripts/copy-tree-sitter-wasms.mjs
|
||
| throw error | ||
| } | ||
| return { sourceFiles } | ||
| } | ||
|
|
||
| if (process.argv[1] === fileURLToPath(import.meta.url)) { | ||
| await prepareTreeSitterWasms(wasmDir, generatedDir) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import fs from "node:fs" | ||
| import os from "node:os" | ||
| import path from "node:path" | ||
| import { afterEach, beforeEach, describe, expect, it } from "vitest" | ||
|
|
||
| import { prepareTreeSitterWasms } from "./copy-tree-sitter-wasms.mjs" | ||
|
|
||
| describe("prepareTreeSitterWasms", () => { | ||
| let root | ||
| let source | ||
| let destination | ||
|
|
||
| beforeEach(() => { | ||
| root = fs.mkdtempSync(path.join(os.tmpdir(), "tree-sitter-wasms-")) | ||
| source = path.join(root, "source") | ||
| destination = path.join(root, "generated", "tree-sitter-wasms") | ||
| fs.mkdirSync(source) | ||
| fs.writeFileSync(path.join(source, "tree-sitter-a.wasm"), "a") | ||
| }) | ||
|
|
||
| afterEach(() => fs.rmSync(root, { recursive: true, force: true })) | ||
|
|
||
| it("replaces pre-existing generated output without filesystem rename", async () => { | ||
| fs.writeFileSync(path.join(source, "ignored.txt"), "ignored") | ||
| fs.mkdirSync(destination, { recursive: true }) | ||
| fs.writeFileSync(path.join(destination, "tree-sitter-stale.wasm"), "stale") | ||
| const filesystem = { | ||
| ...fs.promises, | ||
| rename: async () => { | ||
| throw new Error("rename must not be used") | ||
| }, | ||
| } | ||
|
|
||
| await prepareTreeSitterWasms(source, destination, { filesystem }) | ||
|
|
||
| expect(fs.readdirSync(destination)).toEqual(["tree-sitter-a.wasm"]) | ||
| expect(fs.readFileSync(path.join(destination, "tree-sitter-a.wasm"), "utf8")).toBe("a") | ||
| }) | ||
|
|
||
| it("removes partial task output after a copy failure and rebuilds cleanly", async () => { | ||
| fs.writeFileSync(path.join(source, "tree-sitter-b.wasm"), "b") | ||
| let copies = 0 | ||
| const filesystem = { | ||
| ...fs.promises, | ||
| copyFile: async (...args) => { | ||
| if (++copies === 2) throw new Error("copy failed") | ||
| return fs.promises.copyFile(...args) | ||
| }, | ||
| } | ||
|
|
||
| await expect(prepareTreeSitterWasms(source, destination, { filesystem })).rejects.toThrow("copy failed") | ||
| expect(fs.existsSync(destination)).toBe(false) | ||
|
|
||
| await prepareTreeSitterWasms(source, destination) | ||
| expect(fs.readdirSync(destination)).toEqual(["tree-sitter-a.wasm", "tree-sitter-b.wasm"]) | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| import { spawnSync } from "node:child_process" | ||
| import fs from "node:fs" | ||
| import path from "node:path" | ||
| import process from "node:process" | ||
| import { fileURLToPath } from "node:url" | ||
|
|
||
| import { assertMatchingFiles } from "./verify-wasm-files.mjs" | ||
|
|
||
| const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..") | ||
|
Check warning on line 9 in src/scripts/verify-coverage-contract.mjs
|
||
| const pnpm = process.platform === "win32" ? process.env.npm_execpath : "pnpm" | ||
|
Check warning on line 10 in src/scripts/verify-coverage-contract.mjs
|
||
| if (!pnpm) throw new Error("pnpm executable path is unavailable") | ||
|
Check warning on line 11 in src/scripts/verify-coverage-contract.mjs
|
||
| const pnpmPrefix = process.platform === "win32" ? [pnpm] : [] | ||
| const run = (args, options = {}) => { | ||
| const { includeStderr = true, ...spawnOptions } = options | ||
| const command = process.platform === "win32" ? process.execPath : pnpm | ||
| const result = spawnSync(command, [...pnpmPrefix, ...args], { cwd: root, encoding: "utf8", ...spawnOptions }) | ||
| if (result.status !== 0) { | ||
| const details = [ | ||
| result.error?.message, | ||
| result.signal ? `terminated by ${result.signal}` : undefined, | ||
| result.stderr, | ||
| result.stdout, | ||
| ] | ||
| .filter(Boolean) | ||
| .join("\n") | ||
| throw new Error(details || `pnpm exited with status ${result.status ?? "unknown"}`) | ||
| } | ||
| return `${result.stdout || ""}${includeStderr ? result.stderr || "" : ""}` | ||
| } | ||
|
|
||
| const graph = JSON.parse( | ||
| run(["turbo", "run", "test:coverage:unit", "--filter=zoo-code", "--dry=json"], { includeStderr: false }), | ||
| ) | ||
| const coverageTask = graph.tasks.find(({ taskId }) => taskId === "zoo-code#test:coverage:unit") | ||
| const preparationTask = graph.tasks.find(({ taskId }) => taskId === "zoo-code#prepare:tree-sitter-wasms") | ||
| if (!coverageTask?.dependencies.includes("zoo-code#prepare:tree-sitter-wasms")) | ||
| throw new Error("WASM prerequisite missing") | ||
| if (coverageTask.dependencies.includes("zoo-code#bundle")) throw new Error("Unit coverage must not depend on bundle") | ||
| if (JSON.stringify(preparationTask?.outputs) !== JSON.stringify(["generated/tree-sitter-wasms/**"])) | ||
| throw new Error("WASM prerequisite outputs changed") | ||
|
|
||
| const generated = path.join(root, "src", "generated", "tree-sitter-wasms") | ||
| const cacheDir = path.join(root, ".turbo", "coverage-contract") | ||
| fs.rmSync(cacheDir, { recursive: true, force: true }) | ||
|
|
||
| try { | ||
| run(["turbo", "run", "prepare:tree-sitter-wasms", "--filter=zoo-code", "--cache-dir=.turbo/coverage-contract"]) | ||
| run(["--dir", "src", "exec", "vitest", "run", "services/tree-sitter/__tests__"], { stdio: "inherit" }) | ||
|
|
||
| const source = fs | ||
| .readdirSync(path.join(root, "src", "node_modules", "tree-sitter-wasms", "out")) | ||
| .filter((filename) => /^tree-sitter-.*\.wasm$/.test(filename)) | ||
| .sort() | ||
| const prepared = fs | ||
| .readdirSync(generated) | ||
| .filter((filename) => /^tree-sitter-.*\.wasm$/.test(filename)) | ||
| .sort() | ||
| if (source.length === 0) throw new Error("Dependency contains no tree-sitter WASMs") | ||
| if (JSON.stringify(source) !== JSON.stringify(prepared)) | ||
| throw new Error("Prepared WASM set does not match dependency") | ||
| assertMatchingFiles( | ||
| path.join(root, "src", "node_modules", "tree-sitter-wasms", "out"), | ||
| generated, | ||
| source, | ||
| "Prepared WASM content does not match dependency", | ||
| ) | ||
|
|
||
| fs.rmSync(generated, { recursive: true, force: true }) | ||
| const warmGraph = JSON.parse( | ||
| run( | ||
| [ | ||
| "turbo", | ||
| "run", | ||
| "prepare:tree-sitter-wasms", | ||
| "--filter=zoo-code", | ||
| "--cache-dir=.turbo/coverage-contract", | ||
| "--dry=json", | ||
| ], | ||
| { includeStderr: false }, | ||
| ), | ||
| ) | ||
| const warmTask = warmGraph.tasks.find(({ taskId }) => taskId === "zoo-code#prepare:tree-sitter-wasms") | ||
| if (warmTask?.cache.status !== "HIT") throw new Error("WASM prerequisite is not available in the isolated cache") | ||
| run(["turbo", "run", "prepare:tree-sitter-wasms", "--filter=zoo-code", "--cache-dir=.turbo/coverage-contract"]) | ||
| const restored = fs | ||
| .readdirSync(generated) | ||
| .filter((filename) => /^tree-sitter-.*\.wasm$/.test(filename)) | ||
| .sort() | ||
| if (JSON.stringify(source) !== JSON.stringify(restored)) throw new Error("WASM cache did not restore exact outputs") | ||
| assertMatchingFiles( | ||
| path.join(root, "src", "node_modules", "tree-sitter-wasms", "out"), | ||
| generated, | ||
| source, | ||
| "WASM cache restored corrupted output", | ||
| ) | ||
| } finally { | ||
| fs.rmSync(cacheDir, { recursive: true, force: true }) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import fs from "node:fs" | ||
| import process from "node:process" | ||
| import { fileURLToPath } from "node:url" | ||
|
|
||
| export function verifyLcov(content) { | ||
| let inRecord = false | ||
| let anyCovered = false | ||
| let linesFound | ||
| let linesHit | ||
|
|
||
| for (const line of content.split(/\r?\n/)) { | ||
| if (line.startsWith("SF:")) { | ||
|
zoomote[bot] marked this conversation as resolved.
|
||
| if (inRecord) throw new Error("LCOV source record is not terminated") | ||
| if (!line.slice(3)) throw new Error("LCOV source path is empty") | ||
| inRecord = true | ||
| linesFound = undefined | ||
| linesHit = undefined | ||
| } else if (line.startsWith("LF:")) { | ||
| if (!inRecord) throw new Error("LCOV line count is outside a source record") | ||
| if (linesFound !== undefined) throw new Error("LCOV source record has duplicate line counts") | ||
| const found = line.slice(3) | ||
| if (!/^\d+$/.test(found)) throw new Error("LCOV line count is not a decimal integer") | ||
| linesFound = BigInt(found) | ||
|
zoomote[bot] marked this conversation as resolved.
|
||
| } else if (line.startsWith("LH:")) { | ||
| if (!inRecord) throw new Error("LCOV hit count is outside a source record") | ||
| if (linesHit !== undefined) throw new Error("LCOV source record has duplicate hit counts") | ||
| const hits = line.slice(3) | ||
| if (!/^\d+$/.test(hits)) throw new Error("LCOV hit count is not a decimal integer") | ||
| linesHit = BigInt(hits) | ||
| } else if (line === "end_of_record") { | ||
| if (!inRecord) throw new Error("LCOV terminator is outside a source record") | ||
| if (linesFound === undefined || linesHit === undefined) | ||
| throw new Error("LCOV source record has incomplete line summaries") | ||
| if (linesHit > linesFound) throw new Error("LCOV hit count exceeds lines found") | ||
| if (linesHit > 0n) anyCovered = true | ||
| inRecord = false | ||
| } | ||
| } | ||
|
|
||
| if (inRecord) throw new Error("LCOV source record is not terminated") | ||
| if (!anyCovered) throw new Error("LCOV report has no covered lines") | ||
| } | ||
|
|
||
| if (process.argv[1] === fileURLToPath(import.meta.url)) { | ||
| verifyLcov(fs.readFileSync(process.argv[2], "utf8")) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { describe, expect, it } from "vitest" | ||
|
|
||
| import { verifyLcov } from "./verify-lcov.mjs" | ||
|
|
||
| describe("verifyLcov", () => { | ||
| it("accepts complete records with covered lines", () => { | ||
| expect(() => verifyLcov("SF:file.ts\nLF:1\nLH:1\nend_of_record\n")).not.toThrow() | ||
| }) | ||
|
|
||
| it.each([ | ||
| ["an empty source path", "SF:\nLF:1\nLH:1\nend_of_record\n"], | ||
| ["an unterminated record", "SF:file.ts\nLH:1\n"], | ||
| ["a zero-hit report", "SF:file.ts\nLF:1\nLH:0\nend_of_record\n"], | ||
| ["a line count outside a record", "LF:1\n"], | ||
| ["a hit count outside a record", "LH:1\n"], | ||
| ["a terminator outside a record", "end_of_record\n"], | ||
| ["consecutive source records", "SF:first.ts\nSF:second.ts\nLF:1\nLH:1\nend_of_record\n"], | ||
| ["a record without lines found", "SF:file.ts\nLH:1\nend_of_record\n"], | ||
| ["an infinite line count", "SF:file.ts\nLF:Infinity\nLH:1\nend_of_record\n"], | ||
| ["a fractional line count", "SF:file.ts\nLF:1.5\nLH:1\nend_of_record\n"], | ||
| ["an exponential line count", "SF:file.ts\nLF:1e3\nLH:1\nend_of_record\n"], | ||
| ["an infinite hit count", "SF:file.ts\nLH:Infinity\nend_of_record\n"], | ||
| ["a fractional hit count", "SF:file.ts\nLH:1.5\nend_of_record\n"], | ||
| ["an exponential hit count", "SF:file.ts\nLH:1e3\nend_of_record\n"], | ||
| ["duplicate line counts", "SF:file.ts\nLF:1\nLF:0\nLH:0\nend_of_record\n"], | ||
| ["duplicate hit counts", "SF:file.ts\nLF:1\nLH:1\nLH:0\nend_of_record\n"], | ||
| ["more hit lines than found lines", "SF:file.ts\nLF:0\nLH:1\nend_of_record\n"], | ||
| ])("rejects %s", (_, content) => { | ||
| expect(() => verifyLcov(content)).toThrow() | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import fs from "node:fs" | ||
| import path from "node:path" | ||
|
|
||
| export function assertMatchingFiles(expectedDir, actualDir, filenames, message, filesystem = fs) { | ||
| for (const filename of filenames) { | ||
| if ( | ||
| !filesystem | ||
| .readFileSync(path.join(expectedDir, filename)) | ||
| .equals(filesystem.readFileSync(path.join(actualDir, filename))) | ||
| ) { | ||
| throw new Error(`${message}: ${filename}`) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import fs from "node:fs" | ||
| import os from "node:os" | ||
| import path from "node:path" | ||
| import { afterEach, beforeEach, describe, expect, it } from "vitest" | ||
|
|
||
| import { assertMatchingFiles } from "./verify-wasm-files.mjs" | ||
|
|
||
| describe("assertMatchingFiles", () => { | ||
| let root | ||
|
|
||
| beforeEach(() => { | ||
| root = fs.mkdtempSync(path.join(os.tmpdir(), "verify-wasm-files-")) | ||
| }) | ||
|
|
||
| afterEach(() => fs.rmSync(root, { recursive: true, force: true })) | ||
|
|
||
| it("rejects a restored WASM with corrupted content", () => { | ||
| const expected = path.join(root, "expected") | ||
| const actual = path.join(root, "actual") | ||
| fs.mkdirSync(expected) | ||
| fs.mkdirSync(actual) | ||
| fs.writeFileSync(path.join(expected, "tree-sitter-a.wasm"), "expected") | ||
| fs.writeFileSync(path.join(actual, "tree-sitter-a.wasm"), "corrupted") | ||
|
|
||
| expect(() => assertMatchingFiles(expected, actual, ["tree-sitter-a.wasm"], "WASM mismatch")).toThrow( | ||
| "WASM mismatch: tree-sitter-a.wasm", | ||
| ) | ||
| }) | ||
| }) |
Uh oh!
There was an error while loading. Please reload this page.