diff --git a/.github/workflows/typescript.yml b/.github/workflows/typescript.yml new file mode 100644 index 0000000..5ffffa7 --- /dev/null +++ b/.github/workflows/typescript.yml @@ -0,0 +1,35 @@ +name: typescript +on: + push: + branches: [main] + pull_request: +jobs: + check: + runs-on: ubuntu-latest + env: + FSC_SKIP_DOCKER_TESTS: "1" + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # the co-change coupling tests read the repository's own git history + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - name: git identity (tests create commits) + run: | + git config --global user.name ci + git config --global user.email ci@example.com + git config --global init.defaultBranch main + # The tree-sitter native build (node-gyp) fails on current Node, locally and on the runner, + # and nothing under src/ or test/ imports it. + - run: bun install --frozen-lockfile --ignore-scripts + - name: typecheck + run: bun run typecheck + - name: tests + working-directory: packages/core + run: bun run test + - name: golden fixtures are in sync with the TypeScript reference + run: | + bun script/gen-golden.ts + bun script/gen-merge-golden.ts + git diff --exit-code -- crates/*/tests/golden diff --git a/packages/core/test/codeCommitterReportVisualization.test.ts b/packages/core/test/codeCommitterReportVisualization.test.ts index cec7220..eaec92b 100644 --- a/packages/core/test/codeCommitterReportVisualization.test.ts +++ b/packages/core/test/codeCommitterReportVisualization.test.ts @@ -1,8 +1,16 @@ -import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test"; +import { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun:test"; +import * as realChildProcess from "child_process"; import { CodeCommitter } from "../src/codeCommitter"; import type { TaskResult } from "../src/task"; import { TaskStatus } from "../src/task"; +// mock.module() replaces the module for the whole process and is not undone by mock.restore(). +// Without this, every test file that runs after this one sees execSync() === 'abc123def456'. +const originalChildProcess = { ...realChildProcess }; +afterAll(() => { + mock.module('child_process', () => originalChildProcess); +}); + describe('CodeCommitter Beautiful Report Visualization Demo', () => { let mockTaskResults: TaskResult[]; diff --git a/packages/core/test/copyFilesToContainer.test.ts b/packages/core/test/copyFilesToContainer.test.ts index 6fe8386..deeffb9 100644 --- a/packages/core/test/copyFilesToContainer.test.ts +++ b/packages/core/test/copyFilesToContainer.test.ts @@ -3,12 +3,7 @@ import { spawnSync } from "bun"; import { DockerInstance } from "../src/dockerInstance"; import { RunStatus } from "../src/taskExecutor"; -const dockerAvailable = (() => { - try { - const r = spawnSync({ cmd: ["docker", "info"], timeout: 5000 }); - return r.exitCode === 0; - } catch { return false; } -})(); +import { dockerAvailable } from "./helpers/docker"; const testDocker = test.skipIf(!dockerAvailable); diff --git a/packages/core/test/dockerInstance.test.ts b/packages/core/test/dockerInstance.test.ts index a3ebcee..87288b8 100644 --- a/packages/core/test/dockerInstance.test.ts +++ b/packages/core/test/dockerInstance.test.ts @@ -2,12 +2,7 @@ import { expect, test, describe } from "bun:test"; import { spawnSync } from "bun"; import { DockerInstance, DockerRunStatus } from "../src/dockerInstance"; -const dockerAvailable = (() => { - try { - const r = spawnSync({ cmd: ["docker", "info"], timeout: 5000 }); - return r.exitCode === 0; - } catch { return false; } -})(); +import { dockerAvailable } from "./helpers/docker"; const testDocker = test.skipIf(!dockerAvailable); diff --git a/packages/core/test/dockerInstanceFileRead.test.ts b/packages/core/test/dockerInstanceFileRead.test.ts index cebe925..6017f2a 100644 --- a/packages/core/test/dockerInstanceFileRead.test.ts +++ b/packages/core/test/dockerInstanceFileRead.test.ts @@ -2,12 +2,7 @@ import { expect, test, describe } from "bun:test"; import { spawnSync } from "bun"; import { DockerInstance } from "../src/dockerInstance"; -const dockerAvailable = (() => { - try { - const r = spawnSync({ cmd: ["docker", "info"], timeout: 5000 }); - return r.exitCode === 0; - } catch { return false; } -})(); +import { dockerAvailable } from "./helpers/docker"; const testDocker = test.skipIf(!dockerAvailable); diff --git a/packages/core/test/helpers/docker.ts b/packages/core/test/helpers/docker.ts new file mode 100644 index 0000000..f02216a --- /dev/null +++ b/packages/core/test/helpers/docker.ts @@ -0,0 +1,14 @@ +import { spawnSync } from "bun"; + +/** + * True when Docker integration tests can run. Set FSC_SKIP_DOCKER_TESTS=1 to skip them even if + * Docker is installed (CI does this: the native pipeline does not need Docker). + */ +export const dockerAvailable: boolean = (() => { + if (process.env.FSC_SKIP_DOCKER_TESTS === "1") return false; + try { + return spawnSync({ cmd: ["docker", "info"], timeout: 5000 }).exitCode === 0; + } catch { + return false; + } +})(); diff --git a/packages/core/test/runCommandAsync.test.ts b/packages/core/test/runCommandAsync.test.ts index 8dd153f..17dc9d7 100644 --- a/packages/core/test/runCommandAsync.test.ts +++ b/packages/core/test/runCommandAsync.test.ts @@ -2,12 +2,7 @@ import { expect, test, describe, beforeAll, afterAll, jest } from 'bun:test'; import { spawnSync } from "bun"; import { DockerInstance, DockerRunStatus } from "../src/dockerInstance"; -const dockerAvailable = (() => { - try { - const r = spawnSync({ cmd: ["docker", "info"], timeout: 5000 }); - return r.exitCode === 0; - } catch { return false; } -})(); +import { dockerAvailable } from "./helpers/docker"; const describeDocker = dockerAvailable ? describe : describe.skip; diff --git a/packages/core/test/salacia/coupling.test.ts b/packages/core/test/salacia/coupling.test.ts index 34909c5..e180580 100644 --- a/packages/core/test/salacia/coupling.test.ts +++ b/packages/core/test/salacia/coupling.test.ts @@ -1,7 +1,10 @@ import { describe, test, expect } from "bun:test"; import { getCoChangedFiles, expandWithCoupling } from "../../src/salacia/coupling"; -const REPO = "D:/projects/full-self-coding"; +import { resolve } from "path"; + +// The repository this test file lives in (test/salacia -> packages/core -> packages -> repo root). +const REPO = resolve(import.meta.dir, "..", "..", "..", ".."); describe("getCoChangedFiles", () => { test("returns co-changed files for scheduler.ts", () => { diff --git a/packages/core/test/salacia/replay-real-commits.test.ts b/packages/core/test/salacia/replay-real-commits.test.ts index 5e1f3c7..f40c909 100644 --- a/packages/core/test/salacia/replay-real-commits.test.ts +++ b/packages/core/test/salacia/replay-real-commits.test.ts @@ -1,10 +1,12 @@ import { describe, test, expect } from "bun:test"; +import { resolve } from "path"; import { gitExec } from "../../src/execution/gitExec"; import { generateContract } from "../../src/salacia/contract"; import { detectDrift, isFatalDrift, exceedsThreshold } from "../../src/salacia/drift"; import type { Task } from "../../src/task"; -const REPO = "D:/projects/full-self-coding"; +// The repository this test file lives in (test/salacia -> packages/core -> packages -> repo root). +const REPO = resolve(import.meta.dir, "..", "..", "..", ".."); function getRecentCommits(n: number): Array<{ hash: string; message: string; files: string[] }> { const log = gitExec(["log", "--oneline", `-${n}`], REPO); diff --git a/packages/core/test/taskSolverClaudeCode.test.ts b/packages/core/test/taskSolverClaudeCode.test.ts index 78a6d27..3d002cf 100644 --- a/packages/core/test/taskSolverClaudeCode.test.ts +++ b/packages/core/test/taskSolverClaudeCode.test.ts @@ -8,12 +8,7 @@ import { WorkStyle } from "../src/workStyle"; import {CLAUDE_CODE_API_KEY, CLAUDE_CODE_API_URL} from "./apiKeySetup"; import { spawnSync } from "bun"; -const dockerAvailable = (() => { - try { - const r = spawnSync({ cmd: ["docker", "info"], timeout: 5000 }); - return r.exitCode === 0; - } catch { return false; } -})(); +import { dockerAvailable } from "./helpers/docker"; const testDocker = test.skipIf(!dockerAvailable || !process.env.ANTHROPIC_API_KEY); diff --git a/packages/core/test/taskSolverGemini.test.ts b/packages/core/test/taskSolverGemini.test.ts index b4ec37f..c66aa97 100644 --- a/packages/core/test/taskSolverGemini.test.ts +++ b/packages/core/test/taskSolverGemini.test.ts @@ -8,12 +8,7 @@ import { WorkStyle } from "../src/workStyle"; import {GEMINI_API_KEY} from "./apiKeySetup"; import { spawnSync } from "bun"; -const dockerAvailable = (() => { - try { - const r = spawnSync({ cmd: ["docker", "info"], timeout: 5000 }); - return r.exitCode === 0; - } catch { return false; } -})(); +import { dockerAvailable } from "./helpers/docker"; const testDocker = test.skipIf(!dockerAvailable || !process.env.GEMINI_API_KEY);