From 56e653d966537969064253f87a55b30e49251f91 Mon Sep 17 00:00:00 2001 From: grumbach Date: Tue, 6 Oct 2026 12:02:28 +0900 Subject: [PATCH] ci(browser): restore the Chromium job on Rust 1.99 and prove it ran The "Real Chromium and local nodes" job builds the pinned ant-node with the workflow's `RUSTFLAGS: -D warnings` on the latest stable toolchain. Since stable moved to Rust 1.99 on 2026-10-01, the pin (c092f225) no longer compiles: 1.99 deprecates `Atomic::fetch_update`, which its `web_rtc` byte budget calls twice. The devnet never starts and no browser test runs. - Pin 26129a26, the ant-node main commit that merges the Rust 1.99 update into the 0.21.0 release. Its tree is the v0.21.0 tag plus that one change, whose only non-test source edit renames the two calls to `try_update`, the same method. The lockfile is unchanged, so the nodes run the code of the current release, with the pointer rules this suite already exercised. No release contains the rename, and the release archives ship `ant-node` without the `ant-devnet` binary the suite runs, so a release binary cannot replace the build. - Build the pin without `-D warnings`. It is old code kept to test this client against older nodes. Each new stable toolchain can add warnings to it, and those are not this repository's to fix. The browser bindings built from this repository earlier in the job still deny warnings. - Without `ANT_NODE_DIR`, refuse to start the devnet unless the sibling checkout is the pinned revision without tracked edits, and build it with `--locked`, so a run cannot test a different node or re-resolve its dependencies. An explicit `ANT_NODE_DIR` builds as before. - Fail an otherwise passing run unless every test ran and passed, and reject `.only` in CI. Playwright exits 0 when tests are skipped. --- .github/workflows/ci.yml | 7 ++++++ ant-core/browser-tests/README.md | 17 ++++++++++--- ant-core/browser-tests/node-revision | 2 +- ant-core/browser-tests/playwright.config.js | 3 ++- ant-core/browser-tests/run-guard.js | 19 +++++++++++++++ ant-core/browser-tests/start-devnet.mjs | 27 ++++++++++++++++++--- 6 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 ant-core/browser-tests/run-guard.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf57deb1..3385394a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -159,6 +159,13 @@ jobs: run: npx playwright install --with-deps chromium - name: Exercise real paid browser storage and recovery working-directory: ant-core/browser-tests + # This step compiles only the pinned node, which runs the code of a + # node release so this client is tested against older nodes. Each new + # stable toolchain can add warnings to that old code (Rust 1.99 + # deprecated `fetch_update`), which are not this repository's to fix, + # so they must not fail the build here. + env: + RUSTFLAGS: "" run: npm test test-unit: diff --git a/ant-core/browser-tests/README.md b/ant-core/browser-tests/README.md index 3a619ec0..32a1ebd2 100644 --- a/ant-core/browser-tests/README.md +++ b/ant-core/browser-tests/README.md @@ -53,10 +53,19 @@ The test starts seven real ant-nodes and an isolated Anvil chain, then uses Chromium's real WebRTC implementation. `ANT_NODE_DIR` overrides the default sibling `ant-node-web-support` checkout. CI checks out the exact browser-enabled node revision in [`node-revision`](node-revision); use that revision locally to -reproduce CI. This integration pin is separate from the native `ant-node` -registry dependency, which has no git `rev`. Update the pin only with a passing -paid browser integration run against the replacement. Anvil must be installed -and available on PATH. +reproduce CI. Without `ANT_NODE_DIR`, the devnet refuses to start unless the +sibling checkout is that revision without tracked edits, and builds it with +`--locked`. The pin runs the same code as a node release already on the +network, so the suite checks this client against older nodes. It is currently +`26129a26`, the ant-node `main` commit that adds only the Rust 1.99 fixes to +the 0.21.0 release: its one runtime change renames `fetch_update` to +`try_update`, the same method. CI builds the pin without `-D warnings`, because +each new toolchain can add warnings to an old release. This integration pin is +separate from the native `ant-node` registry dependency, which has no git +`rev`. Update the pin only with a passing paid browser integration run against +the replacement. In CI, the run fails unless every test ran and passed: +`run-guard.js` fails a run with a skipped or unfinished test, and `.only` is +rejected. Anvil must be installed and available on PATH. The harness authenticates through `BrowserNetworkClient.connect()` and retains that pool for the upload, makes one batched storage payment, diff --git a/ant-core/browser-tests/node-revision b/ant-core/browser-tests/node-revision index 5b821f16..8c6eba29 100644 --- a/ant-core/browser-tests/node-revision +++ b/ant-core/browser-tests/node-revision @@ -1 +1 @@ -c092f225877b3058eccbc47e2082fc44750ed2b5 +26129a2611cac08f9f53a557f91fb8806b40a1cc diff --git a/ant-core/browser-tests/playwright.config.js b/ant-core/browser-tests/playwright.config.js index 7d201af7..e67ad957 100644 --- a/ant-core/browser-tests/playwright.config.js +++ b/ant-core/browser-tests/playwright.config.js @@ -1,6 +1,7 @@ import { defineConfig } from "@playwright/test"; export default defineConfig({ - testDir: ".", testMatch: "*.spec.js", workers: 1, timeout: 300_000, reporter: "line", + testDir: ".", testMatch: "*.spec.js", workers: 1, timeout: 300_000, forbidOnly: !!process.env.CI, + reporter: [["line"], ["./run-guard.js"]], use: { browserName: "chromium", headless: true, baseURL: "http://127.0.0.1:35173", launchOptions: { args: ["--force-fieldtrials=WebRTC-NoSdpMangleUfrag/Enabled/"] } }, webServer: [ diff --git a/ant-core/browser-tests/run-guard.js b/ant-core/browser-tests/run-guard.js new file mode 100644 index 00000000..d603b97e --- /dev/null +++ b/ant-core/browser-tests/run-guard.js @@ -0,0 +1,19 @@ +// Fails an otherwise passing run unless every discovered test ran in Chromium and passed. +// Playwright alone exits 0 when tests are skipped, so a skipped test could leave the suite green +// without exercising the pinned nodes. +export default class RunGuard { + onBegin(_, suite) { this.tests = suite.allTests(); } + onEnd(result) { + // `--list` reports the discovered tests without running them, so there is nothing to check. + if (result.status !== "passed" || process.argv.includes("--list")) return; + const tests = this.tests ?? []; + const missed = tests.filter(test => test.results.at(-1)?.status !== "passed"); + if (tests.length > 0 && missed.length === 0) { + console.log(`Run guard: all ${tests.length} tests ran and passed`); + return; + } + console.error(tests.length === 0 ? "Run guard: no tests ran" + : `Run guard: ${missed.length} of ${tests.length} tests did not pass: ${missed.map(test => test.title).join("; ")}`); + return { status: "failed" }; + } +} diff --git a/ant-core/browser-tests/start-devnet.mjs b/ant-core/browser-tests/start-devnet.mjs index 5d712a5d..57ab4c89 100644 --- a/ant-core/browser-tests/start-devnet.mjs +++ b/ant-core/browser-tests/start-devnet.mjs @@ -1,10 +1,29 @@ -import { spawn } from "node:child_process"; -import { mkdtemp, rm } from "node:fs/promises"; +import { execFileSync, spawn } from "node:child_process"; +import { mkdtemp, readFile, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { resolve, join } from "node:path"; -const directory = await mkdtemp(join(tmpdir(), "ant-core-browser-test-")); const node = resolve(process.env.ANT_NODE_DIR ?? "../../../ant-node-web-support"); -const child = spawn("cargo", ["run", "--manifest-path", join(node, "Cargo.toml"), "--bin", "ant-devnet", "--", +const cargo = ["run", "--manifest-path", join(node, "Cargo.toml"), "--bin", "ant-devnet"]; +// Without an explicit ANT_NODE_DIR the suite runs exactly the pinned node: that revision, without tracked edits, +// on its own lockfile. +if (!process.env.ANT_NODE_DIR) { + const pinned = (await readFile("node-revision", "utf8")).trim(); + const git = (...args) => execFileSync("git", ["-C", node, ...args], { encoding: "utf8", stdio: "pipe" }).trim(); + let problem; + try { + const revision = git("rev-parse", "HEAD"); + if (revision !== pinned) problem = `is at ${revision}, not the pinned ${pinned}`; + else if (git("status", "--porcelain", "--untracked-files=no") !== "") problem = `has tracked edits to the pinned ${pinned}`; + } catch { problem = `is not a git checkout of the pinned ${pinned}`; } + if (problem) { + console.error(`${node} ${problem}: check out node-revision there or set ANT_NODE_DIR`); + process.exit(1); + } + console.log(`Starting ant-devnet from the pinned node ${pinned}`); + cargo.push("--locked"); +} +const directory = await mkdtemp(join(tmpdir(), "ant-core-browser-test-")); +const child = spawn("cargo", [...cargo, "--", "--nodes", "7", "--data-dir", directory, "--base-port", "33000", "--webrtc-direct", "--webrtc-direct-base-port", "34000", "--serve-port", "35000", "--enable-evm"], { stdio: "inherit" }); for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => child.kill("SIGINT"));