-
Notifications
You must be signed in to change notification settings - Fork 294
[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
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
a58036a
9e5c92c
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 |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import { spawnSync } from "node:child_process" | ||
| import process from "node:process" | ||
|
|
||
| const pnpm = process.platform === "win32" ? process.env.npm_execpath : "pnpm" | ||
|
Check warning on line 4 in src/scripts/verify-coverage-contract.mjs
|
||
| if (!pnpm) throw new Error("pnpm executable path is unavailable") | ||
|
Check warning on line 5 in src/scripts/verify-coverage-contract.mjs
|
||
| const command = process.platform === "win32" ? process.execPath : pnpm | ||
|
Check warning on line 6 in src/scripts/verify-coverage-contract.mjs
|
||
| const args = process.platform === "win32" ? [pnpm] : [] | ||
|
Check warning on line 7 in src/scripts/verify-coverage-contract.mjs
|
||
| const result = spawnSync( | ||
| command, | ||
| [...args, "turbo", "run", "test:coverage:unit", "test:dist", "--filter=zoo-code", "--dry=json"], | ||
|
Check warning on line 10 in src/scripts/verify-coverage-contract.mjs
|
||
| { encoding: "utf8" }, | ||
|
Check warning on line 11 in src/scripts/verify-coverage-contract.mjs
|
||
| ) | ||
| if (result.status !== 0) { | ||
|
Check warning on line 13 in src/scripts/verify-coverage-contract.mjs
|
||
| const details = [result.error?.message, result.signal, result.stderr, result.stdout].filter(Boolean).join("\n") | ||
| throw new Error(details || `pnpm exited with status ${result.status ?? "unknown"}`) | ||
| } | ||
|
|
||
| const graph = JSON.parse(result.stdout) | ||
| const coverageTask = graph.tasks.find(({ taskId }) => taskId === "zoo-code#test:coverage:unit") | ||
| const distTask = graph.tasks.find(({ taskId }) => taskId === "zoo-code#test:dist") | ||
| if (!coverageTask) throw new Error("Unit coverage task missing") | ||
| if (graph.tasks.some(({ taskId }) => taskId === "zoo-code#prepare:tree-sitter-wasms")) | ||
| throw new Error("Removed WASM preparation task remains in the graph") | ||
| if (coverageTask.dependencies.includes("zoo-code#bundle")) throw new Error("Unit coverage must not depend on bundle") | ||
| if (!coverageTask.dependencies.includes("@roo-code/types#build")) | ||
| throw new Error("Unit coverage must depend on the types build") | ||
| if (!Object.hasOwn(coverageTask.inputs, "package.json")) throw new Error("Unit coverage must hash package.json") | ||
| if (!coverageTask.hashOfExternalDependencies) throw new Error("Unit coverage must hash external dependencies") | ||
| if (!distTask?.dependencies.includes("zoo-code#bundle")) throw new Error("Dist smoke test must depend on bundle") | ||
| 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") | ||
|
Check warning on line 13 in src/scripts/verify-lcov.mjs
|
||
| if (!line.slice(3)) throw new Error("LCOV source path is empty") | ||
|
Check warning on line 14 in src/scripts/verify-lcov.mjs
|
||
| inRecord = true | ||
| linesFound = undefined | ||
| linesHit = undefined | ||
| } else if (line.startsWith("LF:")) { | ||
| if (!inRecord) throw new Error("LCOV line count is outside a source record") | ||
|
Check warning on line 19 in src/scripts/verify-lcov.mjs
|
||
| 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,87 @@ | ||
| import fs from "fs" | ||
| import os from "os" | ||
| import path from "path" | ||
| import { afterEach, beforeAll, describe, expect, it } from "vitest" | ||
| import { Parser } from "web-tree-sitter" | ||
|
|
||
| import { loadTestGrammar } from "./wasm" | ||
|
|
||
| const requiredGrammars = [ | ||
| "c", | ||
| "cpp", | ||
| "c_sharp", | ||
| "css", | ||
| "dart", | ||
| "elisp", | ||
| "elixir", | ||
| "embedded_template", | ||
| "go", | ||
| "html", | ||
| "java", | ||
| "javascript", | ||
| "json", | ||
| "kotlin", | ||
| "lua", | ||
| "ocaml", | ||
| "php", | ||
| "python", | ||
| "ruby", | ||
| "rust", | ||
| "scala", | ||
| "solidity", | ||
| "swift", | ||
| "systemrdl", | ||
| "tlaplus", | ||
| "toml", | ||
| "tsx", | ||
| "typescript", | ||
| "vue", | ||
| "zig", | ||
| ] | ||
|
|
||
| async function captureLoadFailure(filename: string, directory: string) { | ||
| let error: unknown | ||
| try { | ||
| await loadTestGrammar(filename, directory) | ||
| } catch (caught) { | ||
| error = caught | ||
| } | ||
| if (!(error instanceof Error) || !(error.cause instanceof Error)) { | ||
| throw new Error("Expected a contextual grammar load error with an Error cause") | ||
| } | ||
| expect(error.message).toContain(error.cause.message) | ||
| return error | ||
| } | ||
|
|
||
| describe("dependency-owned Tree-sitter grammars", () => { | ||
| const temporaryDirectories: string[] = [] | ||
|
|
||
| beforeAll(() => Parser.init()) | ||
| afterEach(() => temporaryDirectories.splice(0).forEach((directory) => fs.rmSync(directory, { recursive: true }))) | ||
|
|
||
| it.each(requiredGrammars)("loads tree-sitter-%s.wasm", async (grammar) => { | ||
| const language = await loadTestGrammar(`tree-sitter-${grammar}.wasm`) | ||
| expect(() => new Parser().setLanguage(language)).not.toThrow() | ||
| }) | ||
|
|
||
| it("reports a missing dependency artifact with its filename and resolved path", async () => { | ||
| const directory = fs.mkdtempSync(path.join(os.tmpdir(), "tree-sitter-wasm-missing-")) | ||
| temporaryDirectories.push(directory) | ||
|
|
||
| const error = await captureLoadFailure("tree-sitter-missing.wasm", directory) | ||
| expect(error.message).toContain( | ||
| `Failed to load Tree-sitter grammar tree-sitter-missing.wasm from ${path.join(directory, "tree-sitter-missing.wasm")}`, | ||
| ) | ||
| }) | ||
|
|
||
| it("reports a malformed dependency artifact with its filename and resolved path", async () => { | ||
| const directory = fs.mkdtempSync(path.join(os.tmpdir(), "tree-sitter-wasm-malformed-")) | ||
| temporaryDirectories.push(directory) | ||
| fs.writeFileSync(path.join(directory, "tree-sitter-malformed.wasm"), "not wasm") | ||
|
|
||
| const error = await captureLoadFailure("tree-sitter-malformed.wasm", directory) | ||
| expect(error.message).toContain( | ||
| `Failed to load Tree-sitter grammar tree-sitter-malformed.wasm from ${path.join(directory, "tree-sitter-malformed.wasm")}`, | ||
| ) | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import path from "path" | ||
| import { Language } from "web-tree-sitter" | ||
|
|
||
| export const TEST_WASM_DIR = path.join(__dirname, "../../../node_modules/tree-sitter-wasms/out") | ||
|
|
||
| export async function loadTestGrammar(filename: string, directory = TEST_WASM_DIR) { | ||
| const wasmPath = path.join(directory, filename) | ||
| try { | ||
| return await Language.load(wasmPath) | ||
| } catch (error) { | ||
| const detail = error instanceof Error ? error.message : String(error) | ||
| throw new Error(`Failed to load Tree-sitter grammar ${filename} from ${wasmPath}: ${detail}`, { cause: error }) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.