-
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
1481f41
fbb2871
b016c49
e3b9c27
386656e
a7b4d60
9bd6fbb
7feb5a5
c050ff1
0627bf4
93ed353
ffdcd63
11d99af
cad55e5
e2404fc
b1bec75
3243095
1bb9b36
604c359
79a5a93
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,130 @@ | ||||||||||
| 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)), "..") | ||||||||||
| const wasmDir = path.join(srcDir, "node_modules", "tree-sitter-wasms", "out") | ||||||||||
| const distDir = path.join(srcDir, "dist") | ||||||||||
| const wasmPattern = /^tree-sitter-.*\.wasm$/ | ||||||||||
| const temporaryPattern = /^tree-sitter-.*\.wasm\.\d+\.tmp$/ | ||||||||||
|
|
||||||||||
| export function cleanPublishedTreeSitterWasms(destinationDir, filesystem = fs) { | ||||||||||
| if (!filesystem.existsSync(destinationDir)) return | ||||||||||
| for (const filename of filesystem.readdirSync(destinationDir)) { | ||||||||||
| if (wasmPattern.test(filename) || temporaryPattern.test(filename)) { | ||||||||||
| filesystem.rmSync(path.join(destinationDir, filename), { force: true }) | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| export async function publishTreeSitterWasms( | ||||||||||
| sourceDir, | ||||||||||
| destinationDir, | ||||||||||
| { filesystem = fs.promises, onStep = async () => {}, signalState = { requested: undefined } } = {}, | ||||||||||
| ) { | ||||||||||
| const transactionDir = `${destinationDir}.tree-sitter-wasms-transaction` | ||||||||||
| const stagedDir = path.join(transactionDir, "staged") | ||||||||||
| const backupDir = path.join(transactionDir, "backup") | ||||||||||
| const quarantineDir = path.join(transactionDir, "quarantine") | ||||||||||
| const sourceFiles = (await filesystem.readdir(sourceDir)).filter((filename) => wasmPattern.test(filename)).sort() | ||||||||||
| if (sourceFiles.length === 0) throw new Error("WASM source set is empty") | ||||||||||
| const step = async (name, filename) => { | ||||||||||
| await onStep(name, filename) | ||||||||||
| if (signalState.requested) throw Object.assign(new Error("WASM publication cancelled"), { code: "CANCELLED" }) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| let commitStarted = false | ||||||||||
| let committed = false | ||||||||||
| let ownsTransaction = false | ||||||||||
| const publishedFiles = [] | ||||||||||
| try { | ||||||||||
| await filesystem.mkdir(destinationDir, { recursive: true }) | ||||||||||
| await step("initialized", destinationDir) | ||||||||||
| await filesystem.mkdir(transactionDir) | ||||||||||
| ownsTransaction = true | ||||||||||
| await step("initialized", transactionDir) | ||||||||||
| await filesystem.mkdir(stagedDir) | ||||||||||
| await step("initialized", stagedDir) | ||||||||||
| await filesystem.mkdir(backupDir) | ||||||||||
| await step("initialized", backupDir) | ||||||||||
| await filesystem.mkdir(quarantineDir) | ||||||||||
| await step("initialized", quarantineDir) | ||||||||||
|
|
||||||||||
| for (const filename of sourceFiles) { | ||||||||||
| await filesystem.copyFile(path.join(sourceDir, filename), path.join(stagedDir, filename)) | ||||||||||
| await step("staged", filename) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const previousFiles = (await filesystem.readdir(destinationDir)).filter((filename) => | ||||||||||
| wasmPattern.test(filename), | ||||||||||
| ) | ||||||||||
| commitStarted = true | ||||||||||
| for (const filename of previousFiles) { | ||||||||||
| await filesystem.rename(path.join(destinationDir, filename), path.join(backupDir, filename)) | ||||||||||
| await step("backed-up", filename) | ||||||||||
| } | ||||||||||
| for (const filename of sourceFiles) { | ||||||||||
| await filesystem.rename(path.join(stagedDir, filename), path.join(destinationDir, filename)) | ||||||||||
| publishedFiles.push(filename) | ||||||||||
| await step("published", filename) | ||||||||||
| } | ||||||||||
| for (const filename of await filesystem.readdir(destinationDir)) { | ||||||||||
| if (temporaryPattern.test(filename)) { | ||||||||||
| await filesystem.rm(path.join(destinationDir, filename), { force: true }) | ||||||||||
| await step("removed-temporary", filename) | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| committed = true | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Check cancellation before the commit boundary. A signal can set Check Proposed fix+ if (signalState.requested)
+ throw Object.assign(new Error("WASM publication cancelled"), { code: "CANCELLED" })
committed = true📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| await filesystem.rm(transactionDir, { recursive: true, force: true }) | ||||||||||
|
zoomote[bot] marked this conversation as resolved.
|
||||||||||
| return { sourceFiles, cleanup: () => cleanPublishedTreeSitterWasms(destinationDir) } | ||||||||||
| } catch (error) { | ||||||||||
| if (committed) throw error | ||||||||||
| if (!commitStarted && ownsTransaction) { | ||||||||||
| await filesystem.rm(transactionDir, { recursive: true, force: true }) | ||||||||||
| } | ||||||||||
| if (!commitStarted) throw error | ||||||||||
|
|
||||||||||
| const failures = [] | ||||||||||
| for (const filename of publishedFiles) { | ||||||||||
| try { | ||||||||||
| await filesystem.rename(path.join(destinationDir, filename), path.join(quarantineDir, filename)) | ||||||||||
| } catch (rollbackError) { | ||||||||||
| if (rollbackError.code !== "ENOENT") failures.push(rollbackError) | ||||||||||
| } | ||||||||||
| } | ||||||||||
| for (const filename of await filesystem.readdir(backupDir)) { | ||||||||||
| try { | ||||||||||
| await filesystem.rename(path.join(backupDir, filename), path.join(destinationDir, filename)) | ||||||||||
| await onStep("restored", filename) | ||||||||||
| } catch (rollbackError) { | ||||||||||
| failures.push(rollbackError) | ||||||||||
| } | ||||||||||
| } | ||||||||||
| if (failures.length > 0) { | ||||||||||
| throw new AggregateError( | ||||||||||
| [error, ...failures], | ||||||||||
| `WASM rollback incomplete; recovery retained at ${transactionDir}`, | ||||||||||
| ) | ||||||||||
| } | ||||||||||
| await filesystem.rm(transactionDir, { recursive: true, force: true }) | ||||||||||
| throw error | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (process.argv[1] === fileURLToPath(import.meta.url)) { | ||||||||||
| const signalState = { requested: undefined } | ||||||||||
| for (const signal of ["SIGINT", "SIGTERM"]) { | ||||||||||
| process.on(signal, () => { | ||||||||||
| signalState.requested ??= signal | ||||||||||
| }) | ||||||||||
| } | ||||||||||
| try { | ||||||||||
| await publishTreeSitterWasms(wasmDir, distDir, { signalState }) | ||||||||||
| if (signalState.requested) process.exitCode = signalState.requested === "SIGINT" ? 130 : 143 | ||||||||||
| } catch (error) { | ||||||||||
| if (!signalState.requested) throw error | ||||||||||
| process.exitCode = signalState.requested === "SIGINT" ? 130 : 143 | ||||||||||
| } | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| import fs from "node:fs" | ||
| import os from "node:os" | ||
| import path from "node:path" | ||
| import { afterEach, beforeEach, describe, expect, it } from "vitest" | ||
|
|
||
| import { publishTreeSitterWasms } from "./copy-tree-sitter-wasms.mjs" | ||
|
|
||
| describe("publishTreeSitterWasms", () => { | ||
| let root | ||
|
|
||
| beforeEach(() => { | ||
| root = fs.mkdtempSync(path.join(os.tmpdir(), "tree-sitter-wasms-")) | ||
| }) | ||
|
|
||
| afterEach(() => fs.rmSync(root, { recursive: true, force: true })) | ||
|
|
||
| it("publishes the exact WASM set and removes stale outputs", async () => { | ||
| const source = path.join(root, "source") | ||
| const destination = path.join(root, "dist") | ||
| fs.mkdirSync(source) | ||
| fs.mkdirSync(destination) | ||
| fs.writeFileSync(path.join(source, "tree-sitter-a.wasm"), "a") | ||
| fs.writeFileSync(path.join(source, "ignored.txt"), "ignored") | ||
| fs.writeFileSync(path.join(destination, "tree-sitter-stale.wasm"), "stale") | ||
| fs.writeFileSync(path.join(destination, "tree-sitter-a.wasm.999.tmp"), "partial") | ||
|
|
||
| await publishTreeSitterWasms(source, destination) | ||
|
|
||
| expect(fs.readdirSync(destination)).toEqual(["tree-sitter-a.wasm"]) | ||
| expect(fs.readFileSync(path.join(destination, "tree-sitter-a.wasm"), "utf8")).toBe("a") | ||
| }) | ||
|
|
||
| it("rejects an empty source set before touching published outputs", async () => { | ||
| const source = path.join(root, "source") | ||
| const destination = path.join(root, "dist") | ||
| fs.mkdirSync(source) | ||
| fs.mkdirSync(destination) | ||
| fs.writeFileSync(path.join(destination, "tree-sitter-a.wasm"), "existing") | ||
|
|
||
| await expect(publishTreeSitterWasms(source, destination)).rejects.toThrow("WASM source set is empty") | ||
| expect(fs.readFileSync(path.join(destination, "tree-sitter-a.wasm"), "utf8")).toBe("existing") | ||
| expect(fs.existsSync(`${destination}.tree-sitter-wasms-transaction`)).toBe(false) | ||
| }) | ||
|
|
||
| it("restores published outputs when publication fails", async () => { | ||
| const source = path.join(root, "source") | ||
| const destination = path.join(root, "dist") | ||
| fs.mkdirSync(source) | ||
| fs.mkdirSync(destination) | ||
| fs.writeFileSync(path.join(source, "tree-sitter-a.wasm"), "a") | ||
| fs.writeFileSync(path.join(source, "tree-sitter-b.wasm"), "b") | ||
| fs.writeFileSync(path.join(destination, "tree-sitter-existing.wasm"), "existing") | ||
| let copies = 0 | ||
| const filesystem = { | ||
| ...fs.promises, | ||
| copyFile(...args) { | ||
| if (++copies === 2) throw new Error("copy failed") | ||
| return fs.promises.copyFile(...args) | ||
| }, | ||
| } | ||
|
|
||
| await expect(publishTreeSitterWasms(source, destination, { filesystem })).rejects.toThrow("copy failed") | ||
| expect(fs.readdirSync(destination)).toEqual(["tree-sitter-existing.wasm"]) | ||
| expect(fs.readFileSync(path.join(destination, "tree-sitter-existing.wasm"), "utf8")).toBe("existing") | ||
| }) | ||
|
|
||
| it("restores published outputs when an atomic rename fails", async () => { | ||
| const source = path.join(root, "source") | ||
| const destination = path.join(root, "dist") | ||
| fs.mkdirSync(source) | ||
| fs.mkdirSync(destination) | ||
| fs.writeFileSync(path.join(source, "tree-sitter-a.wasm"), "a") | ||
| fs.writeFileSync(path.join(source, "tree-sitter-b.wasm"), "b") | ||
| fs.writeFileSync(path.join(destination, "tree-sitter-a.wasm"), "previous-a") | ||
| fs.writeFileSync(path.join(destination, "tree-sitter-b.wasm"), "previous-b") | ||
| let renames = 0 | ||
| const filesystem = { | ||
| ...fs.promises, | ||
| rename(...args) { | ||
| if (args[0].includes(`${path.sep}staged${path.sep}`) && ++renames === 2) | ||
| throw new Error("rename failed") | ||
| return fs.promises.rename(...args) | ||
| }, | ||
| } | ||
|
|
||
| await expect(publishTreeSitterWasms(source, destination, { filesystem })).rejects.toThrow("rename failed") | ||
| expect(fs.readdirSync(destination)).toEqual(["tree-sitter-a.wasm", "tree-sitter-b.wasm"]) | ||
| expect(fs.readFileSync(path.join(destination, "tree-sitter-a.wasm"), "utf8")).toBe("previous-a") | ||
| expect(fs.readFileSync(path.join(destination, "tree-sitter-b.wasm"), "utf8")).toBe("previous-b") | ||
| }) | ||
|
|
||
| it("restores published outputs when signalled during commit", async () => { | ||
| const source = path.join(root, "source") | ||
| const destination = path.join(root, "dist") | ||
| fs.mkdirSync(source) | ||
| fs.mkdirSync(destination) | ||
| fs.writeFileSync(path.join(source, "tree-sitter-a.wasm"), "new-a") | ||
| fs.writeFileSync(path.join(destination, "tree-sitter-a.wasm"), "previous-a") | ||
| const signalState = { requested: undefined } | ||
|
|
||
| await expect( | ||
| publishTreeSitterWasms(source, destination, { | ||
| signalState, | ||
| onStep(name) { | ||
| if (name === "published") signalState.requested = "SIGTERM" | ||
| if (name === "restored") signalState.requested ??= "SIGINT" | ||
| }, | ||
| }), | ||
| ).rejects.toThrow("WASM publication cancelled") | ||
| expect(signalState.requested).toBe("SIGTERM") | ||
| expect(fs.readFileSync(path.join(destination, "tree-sitter-a.wasm"), "utf8")).toBe("previous-a") | ||
| expect(fs.existsSync(`${destination}.tree-sitter-wasms-transaction`)).toBe(false) | ||
| }) | ||
|
|
||
| it("retains the backup when restoration fails", async () => { | ||
| const source = path.join(root, "source") | ||
| const destination = path.join(root, "dist") | ||
| const transaction = `${destination}.tree-sitter-wasms-transaction` | ||
| fs.mkdirSync(source) | ||
| fs.mkdirSync(destination) | ||
| fs.writeFileSync(path.join(source, "tree-sitter-a.wasm"), "new-a") | ||
| fs.writeFileSync(path.join(destination, "tree-sitter-a.wasm"), "previous-a") | ||
| const signalState = { requested: undefined } | ||
| const filesystem = { | ||
| ...fs.promises, | ||
| rename(sourcePath, destinationPath) { | ||
| if (sourcePath.includes(`${path.sep}backup${path.sep}`)) throw new Error("restore failed") | ||
| return fs.promises.rename(sourcePath, destinationPath) | ||
| }, | ||
| } | ||
|
|
||
| await expect( | ||
| publishTreeSitterWasms(source, destination, { | ||
| filesystem, | ||
| signalState, | ||
| onStep(name) { | ||
| if (name === "published") signalState.requested = "SIGTERM" | ||
| }, | ||
| }), | ||
| ).rejects.toThrow(`WASM rollback incomplete; recovery retained at ${transaction}`) | ||
| expect(fs.readFileSync(path.join(transaction, "backup", "tree-sitter-a.wasm"), "utf8")).toBe("previous-a") | ||
| await expect(publishTreeSitterWasms(source, destination)).rejects.toMatchObject({ code: "EEXIST" }) | ||
| expect(fs.readFileSync(path.join(transaction, "backup", "tree-sitter-a.wasm"), "utf8")).toBe("previous-a") | ||
| await expect(publishTreeSitterWasms(source, destination)).rejects.toMatchObject({ code: "EEXIST" }) | ||
| }) | ||
|
|
||
| it("cleans an incomplete transaction setup", async () => { | ||
| const source = path.join(root, "source") | ||
| const destination = path.join(root, "dist") | ||
| const transaction = `${destination}.tree-sitter-wasms-transaction` | ||
| fs.mkdirSync(source) | ||
| fs.writeFileSync(path.join(source, "tree-sitter-a.wasm"), "a") | ||
| const filesystem = { | ||
| ...fs.promises, | ||
| mkdir(directory, options) { | ||
| if (directory.endsWith(`${path.sep}backup`)) throw new Error("setup failed") | ||
| return fs.promises.mkdir(directory, options) | ||
| }, | ||
| } | ||
|
|
||
| await expect(publishTreeSitterWasms(source, destination, { filesystem })).rejects.toThrow("setup failed") | ||
| expect(fs.existsSync(transaction)).toBe(false) | ||
| }) | ||
|
|
||
| it("observes a signal during temporary cleanup", async () => { | ||
| const source = path.join(root, "source") | ||
| const destination = path.join(root, "dist") | ||
| fs.mkdirSync(source) | ||
| fs.mkdirSync(destination) | ||
| fs.writeFileSync(path.join(source, "tree-sitter-a.wasm"), "new-a") | ||
| fs.writeFileSync(path.join(destination, "tree-sitter-a.wasm"), "previous-a") | ||
| fs.writeFileSync(path.join(destination, "tree-sitter-a.wasm.999.tmp"), "partial") | ||
| const signalState = { requested: undefined } | ||
|
|
||
| await expect( | ||
| publishTreeSitterWasms(source, destination, { | ||
| signalState, | ||
| onStep(name) { | ||
| if (name === "removed-temporary") signalState.requested = "SIGTERM" | ||
| }, | ||
| }), | ||
| ).rejects.toThrow("WASM publication cancelled") | ||
| expect(fs.readFileSync(path.join(destination, "tree-sitter-a.wasm"), "utf8")).toBe("previous-a") | ||
| }) | ||
|
|
||
| it("keeps committed outputs when transaction cleanup fails", async () => { | ||
| const source = path.join(root, "source") | ||
| const destination = path.join(root, "dist") | ||
| const transaction = `${destination}.tree-sitter-wasms-transaction` | ||
| fs.mkdirSync(source) | ||
| fs.mkdirSync(destination) | ||
| fs.writeFileSync(path.join(source, "tree-sitter-a.wasm"), "new-a") | ||
| fs.writeFileSync(path.join(destination, "tree-sitter-a.wasm"), "previous-a") | ||
| const filesystem = { | ||
| ...fs.promises, | ||
| rm(directory, options) { | ||
| if (directory === transaction) throw new Error("cleanup failed") | ||
| return fs.promises.rm(directory, options) | ||
| }, | ||
| } | ||
|
|
||
| await expect(publishTreeSitterWasms(source, destination, { filesystem })).rejects.toThrow("cleanup failed") | ||
| expect(fs.readFileSync(path.join(destination, "tree-sitter-a.wasm"), "utf8")).toBe("new-a") | ||
| expect(fs.readFileSync(path.join(transaction, "backup", "tree-sitter-a.wasm"), "utf8")).toBe("previous-a") | ||
| }) | ||
| }) |
Uh oh!
There was an error while loading. Please reload this page.