From 261ca867a2950c48d22a8b050e16d2601c4850bb Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Thu, 24 Sep 2026 08:39:26 +0000 Subject: [PATCH 1/4] ci: remove duplicate packaged Rewatch integration suite Signed-off-by: Christoph Knittel --- .github/workflows/ci.yml | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index baa311be88..c8c116f976 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -592,37 +592,3 @@ jobs: node src/BeltTest.mjs shell: bash working-directory: ${{ steps.tmp-dir.outputs.path }} - - test-integration-rewatch: - needs: - - pkg-pr-new - strategy: - fail-fast: false - matrix: - include: - - os: macos-15-intel - - os: macos-15 - - os: ubuntu-24.04 - - os: ubuntu-24.04-arm - - os: windows-2025 - runs-on: ${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Use Node.js - uses: actions/setup-node@v7 - with: - # Run integration tests with the oldest supported node version. - node-version: 20 - - - name: Install ReScript package in rewatch/testrepo - run: | - COMMIT_SHA="${{ needs.pkg-pr-new.outputs.commit_sha }}" - yarn add "rescript@https://pkg.pr.new/rescript-lang/rescript@${COMMIT_SHA}" - shell: bash - working-directory: rewatch/testrepo - - - name: Run rewatch integration tests - run: ./rewatch/tests/suite.sh rewatch/testrepo/node_modules/.bin/rescript - shell: bash From bb1a80b4b71b9e0b02eb09f3cd56be998e31ee83 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Thu, 24 Sep 2026 18:43:31 +0000 Subject: [PATCH 2/4] ci: smoke test packaged Rewatch CLI subcommands Signed-off-by: Christoph Knittel --- .github/workflows/ci.yml | 5 + .../installation_test/packaged-cli-smoke.mjs | 95 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 tests/package_tests/installation_test/packaged-cli-smoke.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8c116f976..8f77433d0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -526,6 +526,11 @@ jobs: shell: bash working-directory: ${{ steps.tmp-dir.outputs.path }} + - name: Test packaged CLI subcommands and watch shutdown + run: node packaged-cli-smoke.mjs + shell: bash + working-directory: ${{ steps.tmp-dir.outputs.path }} + test-installation-pnpm: needs: - pkg-pr-new diff --git a/tests/package_tests/installation_test/packaged-cli-smoke.mjs b/tests/package_tests/installation_test/packaged-cli-smoke.mjs new file mode 100644 index 0000000000..dc7d50d9da --- /dev/null +++ b/tests/package_tests/installation_test/packaged-cli-smoke.mjs @@ -0,0 +1,95 @@ +import assert from "node:assert/strict"; +import { spawn, spawnSync } from "node:child_process"; +import { existsSync } from "node:fs"; +import { rm } from "node:fs/promises"; +import path from "node:path"; + +const launcher = path.resolve("node_modules/rescript/cli/rescript.js"); +const output = path.resolve("src/Test.mjs"); +const watchLock = path.resolve("lib/watch.lock"); +const timeoutMs = process.platform === "win32" ? 120000 : 30000; + +function run(args, input) { + const result = spawnSync(process.execPath, [launcher, ...args], { + input, + encoding: "utf8", + timeout: timeoutMs, + }); + if (result.error) throw result.error; + assert.equal(result.status, 0, `${args.join(" ")} failed: ${result.stderr}`); + return result.stdout; +} + +async function waitForExit(promise) { + let timer; + try { + return await Promise.race([ + promise, + new Promise((_, reject) => { + timer = setTimeout( + () => reject(new Error("watch did not exit after shutdown request")), + timeoutMs, + ); + }), + ]); + } finally { + clearTimeout(timer); + } +} + +async function waitUntil(predicate, message) { + for (let attempt = 0; attempt < timeoutMs / 250; attempt++) { + if (predicate()) return; + await new Promise(resolve => setTimeout(resolve, 250)); + } + throw new Error(message); +} + +const formatted = run(["format", "--stdin", ".res"], "let x=1\n"); +assert.match(formatted, /let x = 1/); + +assert.ok(existsSync(output), "installation test should have built Test.mjs"); +run(["clean"]); +assert.ok(!existsSync(output), "clean should remove Test.mjs"); + +const watcher = spawn(process.execPath, [launcher, "watch"], { + stdio: ["ignore", "pipe", "pipe"], +}); +let watchOutput = ""; +watcher.stdout.on("data", chunk => { + watchOutput += chunk; +}); +watcher.stderr.on("data", chunk => { + watchOutput += chunk; +}); +const watcherExit = new Promise((resolve, reject) => { + watcher.once("error", reject); + watcher.once("exit", (code, signal) => resolve({ code, signal })); +}); + +try { + await waitUntil( + () => existsSync(watchLock) && existsSync(output), + `watch did not build the project: ${watchOutput}`, + ); + if (process.platform === "win32") { + // Windows cannot deliver POSIX SIGINT to a child process through kill(). + await rm(watchLock); + } else { + assert.ok(watcher.kill("SIGINT"), "could not signal packaged CLI"); + } + await waitForExit(watcherExit); + await waitUntil( + () => !existsSync(watchLock), + `watch lock remained after shutdown: ${watchOutput}`, + ); + assert.match( + watchOutput, + /Exiting\.\.\./, + "watcher did not report a clean shutdown", + ); +} finally { + await rm(watchLock, { force: true }); + if (watcher.exitCode === null && watcher.signalCode === null) + watcher.kill("SIGTERM"); +} From 2ce08d843f869cd3d0e6b961ff7c4101cbf3aa8b Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sat, 26 Sep 2026 09:37:46 +0200 Subject: [PATCH 3/4] ci: fix packaged watcher shutdown smoke test Signed-off-by: Christoph Knittel --- .../installation_test/packaged-cli-smoke.mjs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/package_tests/installation_test/packaged-cli-smoke.mjs b/tests/package_tests/installation_test/packaged-cli-smoke.mjs index dc7d50d9da..485a66846f 100644 --- a/tests/package_tests/installation_test/packaged-cli-smoke.mjs +++ b/tests/package_tests/installation_test/packaged-cli-smoke.mjs @@ -78,11 +78,9 @@ try { } else { assert.ok(watcher.kill("SIGINT"), "could not signal packaged CLI"); } - await waitForExit(watcherExit); - await waitUntil( - () => !existsSync(watchLock), - `watch lock remained after shutdown: ${watchOutput}`, - ); + const { code, signal } = await waitForExit(watcherExit); + assert.equal(signal, null, `watcher was killed: ${watchOutput}`); + assert.equal(code, 0, `watcher exited with code ${code}: ${watchOutput}`); assert.match( watchOutput, /Exiting\.\.\./, From a8f9487b0eab87a89f7543d7c2124184f70f4482 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sat, 26 Sep 2026 14:18:27 +0200 Subject: [PATCH 4/4] ci: wait for packaged watcher streams to close Signed-off-by: Christoph Knittel --- tests/package_tests/installation_test/packaged-cli-smoke.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/package_tests/installation_test/packaged-cli-smoke.mjs b/tests/package_tests/installation_test/packaged-cli-smoke.mjs index 485a66846f..8f88533f96 100644 --- a/tests/package_tests/installation_test/packaged-cli-smoke.mjs +++ b/tests/package_tests/installation_test/packaged-cli-smoke.mjs @@ -64,7 +64,7 @@ watcher.stderr.on("data", chunk => { }); const watcherExit = new Promise((resolve, reject) => { watcher.once("error", reject); - watcher.once("exit", (code, signal) => resolve({ code, signal })); + watcher.once("close", (code, signal) => resolve({ code, signal })); }); try {