Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ jobs:
- name: Checkout
uses: actions/checkout@v7

- name: Check workflow definitions
shell: bash
run: |
actionlint_dir="$RUNNER_TEMP/actionlint"
mkdir -p "$actionlint_dir"
curl --fail --location --retry 3 --output "$actionlint_dir/actionlint.tar.gz" \
https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz
printf '8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 %s\n' "$actionlint_dir/actionlint.tar.gz" | sha256sum --check
tar -xzf "$actionlint_dir/actionlint.tar.gz" -C "$actionlint_dir" actionlint
"$actionlint_dir/actionlint" -color -shellcheck= -pyflakes=

- name: Setup Node.js
uses: actions/setup-node@v7
with:
Expand Down
43 changes: 43 additions & 0 deletions tests/workflow-gates.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import test from "node:test";
import { parse } from "yaml";

for (const name of ["ppvm", "rcmps", "tnqs"]) {
test(`${name} numerical CI preserves computation status through log capture`, { skip: process.platform === "win32" }, async t => {
const workflow = parse(await readFile(new URL(`../.github/workflows/${name}-sdk.yml`, import.meta.url), "utf8"));
const job = workflow.jobs["native-sdk"];
const step = job.steps.find(item => item.run?.includes("numerical.log"));
assert.ok(step, "The numerical validation step must remain covered");
for (const config of [job, step]) {
assert.ok(config["continue-on-error"] === undefined || config["continue-on-error"] === false,
"Numerical validation must fail the job on error");
}
const shell = step.shell ?? job.defaults?.run?.shell ?? workflow.defaults?.run?.shell;
assert.ok(shell === undefined || shell === "bash", `Unsupported shell in regression: ${shell}`);
// Match GitHub's runner templates: an implicit bash shell omits pipefail.
const args = shell === "bash" ? ["--noprofile", "--norc", "-eo", "pipefail"] : ["-e"];
for (const status of [0, 23]) {
await t.test(`computation exits ${status}`, async t => {
const directory = await mkdtemp(path.join(tmpdir(), "oq-workflow-gate-"));
t.after(() => rm(directory, { recursive: true, force: true }));
await mkdir(path.join(directory, "bin"));
await writeFile(path.join(directory, "bin/sandbox-exec"),
`#!/bin/bash\nprintf 'numerical fixture exit ${status}\\n' >&2\nexit ${status}\n`, { mode: 0o700 });
const script = path.join(directory, "run.sh");
await writeFile(script, step.run);
const result = spawnSync("bash", [...args, script], { encoding: "utf8", timeout: 5000,
env: { ...process.env, PATH: `${path.join(directory, "bin")}${path.delimiter}${process.env.PATH}`,
OPENQUANTUM_PYTHON_ENV_ROOT: path.join(directory, "python"),
OPENQUANTUM_SDK_CHECK_OUTPUT: path.join(directory, "evidence") } });
assert.ifError(result.error);
assert.equal(result.status, status, `Log capture changed the computation exit status: ${result.stderr}`);
assert.match(await readFile(path.join(directory, "evidence/numerical.log"), "utf8"),
new RegExp(`numerical fixture exit ${status}`));
});
}
});
}
Loading