feat(binary-object): admit ordinary push_this #426
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Engine CI | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| schedule: | |
| - cron: "23 9 * * *" | |
| - cron: "47 18 * * *" | |
| workflow_dispatch: | |
| inputs: | |
| tier: | |
| description: Validation tier to run | |
| required: true | |
| default: fast | |
| type: choice | |
| options: | |
| - fast | |
| - quickjs-differential | |
| - test262-full | |
| - test262-receipt | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: "1" | |
| RUST_VERSION: "1.88.0" | |
| jobs: | |
| fast: | |
| if: >- | |
| github.event_name == 'pull_request' || | |
| github.event_name == 'push' || | |
| (github.event_name == 'workflow_dispatch' && inputs.tier == 'fast') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 300 | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Install pinned Rust toolchain | |
| run: | | |
| rustup toolchain install "${RUST_VERSION}" --profile minimal --component rustfmt,clippy | |
| rustup default "${RUST_VERSION}" | |
| - name: Install gate dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install --yes ripgrep | |
| - name: Cache Cargo inputs | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| key: ${{ runner.os }}-cargo-${{ env.RUST_VERSION }}-${{ hashFiles('Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-${{ env.RUST_VERSION }}- | |
| - name: Check formatting and architecture gates | |
| run: | | |
| cargo fmt --all -- --check | |
| ./scripts/check-binary-object-boundary.sh | |
| ./scripts/check-no-test262-special-casing.sh | |
| node scripts/check-test262-artifact-inventory.mjs | |
| node scripts/check-bc5-pinned-atoms.mjs --self-test | |
| node scripts/check-bc5-pinned-opcodes.mjs --self-test | |
| node --check scripts/audit-negative-diagnostics.mjs | |
| node --check scripts/test-negative-diagnostic-generator.mjs | |
| node --check scripts/generate-test262-dynamic-import-a.mjs | |
| node --check scripts/generate-test262-tla-core-a.mjs | |
| node --check scripts/generate-test262-tla-b.mjs | |
| node --check scripts/test-generate-test262-tla-b.mjs | |
| node --check scripts/generate-test262-module-var-lexical-shadow-a.mjs | |
| node --check scripts/generate-test262-module-local-binding-a.mjs | |
| node --check scripts/generate-test262-module-import-attributes-a.mjs | |
| node --check scripts/test-generate-test262-module-import-attributes-a.mjs | |
| node --check scripts/generate-test262-module-json-a.mjs | |
| node --check scripts/test-generate-test262-module-json-a.mjs | |
| ./scripts/check-oracle-registry.sh | |
| ./scripts/test-quickjs-fixtures.sh --validate | |
| ./scripts/test-quickjs-c-oracles.sh --validate | |
| ./scripts/test-quickjs-dynamic-import-trace.sh --validate | |
| ./scripts/check-test262-host-boundary.sh | |
| ./scripts/test-test262.sh --spec dev-support/test262/current.conf --check | |
| ./scripts/test-test262.sh --spec dev-support/test262/current.conf --runner-provenance | |
| node scripts/current-test262-metrics.mjs --check-docs | |
| ./scripts/check-rust-only.sh | |
| - name: Run fast engine tests | |
| run: | | |
| cargo test --locked --workspace --lib --bins | |
| cargo test --locked -p quickjs-oxide --features test262-host --lib --bins | |
| cargo test --locked --test cli | |
| cargo test --locked --test oracle | |
| cargo test --locked --features test262-host \ | |
| --test unsupported_diagnostics | |
| cargo test --locked --features test262-host \ | |
| --test oracle test262_ | |
| ./scripts/check-oracle-registry.sh --compiled | |
| - name: Lint production targets | |
| run: | | |
| cargo clippy --locked --workspace --lib --bins -- -D warnings | |
| cargo clippy --locked --test oracle -- -D warnings | |
| cargo clippy --locked -p quickjs-oxide --features test262-host \ | |
| --lib --bins -- -D warnings | |
| cargo clippy --locked -p quickjs-oxide --features test262-host \ | |
| --test unsupported_diagnostics --test oracle \ | |
| -- -D warnings | |
| quickjs-differential: | |
| if: >- | |
| (github.event_name == 'schedule' && github.event.schedule == '23 9 * * *') || | |
| (github.event_name == 'workflow_dispatch' && inputs.tier == 'quickjs-differential') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 180 | |
| env: | |
| QJS_ORACLE_CACHE: ${{ github.workspace }}-oracle-cache | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Install pinned Rust toolchain | |
| run: | | |
| rustup toolchain install "${RUST_VERSION}" --profile minimal | |
| rustup default "${RUST_VERSION}" | |
| - name: Install oracle dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install --yes build-essential curl git ripgrep xz-utils | |
| - name: Restore authenticated input caches | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| ${{ github.workspace }}-oracle-cache | |
| key: >- | |
| ${{ runner.os }}-differential-${{ env.RUST_VERSION }}-${{ | |
| hashFiles('Cargo.lock', 'compat/upstream.toml', | |
| 'scripts/build-quickjs-oracle.sh', 'scripts/prepare-test262.sh') }} | |
| restore-keys: | | |
| ${{ runner.os }}-differential-${{ env.RUST_VERSION }}- | |
| - name: Run the pinned QuickJS differential corpus | |
| run: | | |
| oracle_source=$(./scripts/build-quickjs-oracle.sh --test262-oracles) | |
| oracle="$oracle_source/qjs" | |
| node scripts/check-bc5-pinned-atoms.mjs \ | |
| --source "$oracle_source/quickjs-atom.h" | |
| node scripts/check-bc5-pinned-opcodes.mjs \ | |
| --source "$oracle_source/quickjs-opcode.h" | |
| QJS_ORACLE_ARCHIVE_FIXTURE="$QJS_ORACLE_CACHE/quickjs-2026-06-04.tar.xz" \ | |
| ./scripts/test-build-quickjs-oracle-cache.sh | |
| QJS_ORACLE_ARCHIVE="$QJS_ORACLE_CACHE/quickjs-2026-06-04.tar.xz" \ | |
| ./scripts/test-quickjs-dynamic-import-trace.sh | |
| suite=$(./scripts/prepare-test262.sh) | |
| node scripts/generate-test262-dynamic-import-a.mjs \ | |
| --suite "$suite" --check-current | |
| node scripts/generate-test262-tla-core-a.mjs \ | |
| --suite "$suite" --check-current | |
| node scripts/test-generate-test262-tla-b.mjs --suite "$suite" | |
| node scripts/generate-test262-tla-b.mjs \ | |
| --suite "$suite" --check-current | |
| node scripts/generate-test262-module-var-lexical-shadow-a.mjs \ | |
| --suite "$suite" \ | |
| --quickjs-runner "$oracle_source/run-test262" \ | |
| --quickjs-config "$oracle_source/test262.conf" \ | |
| --check-current | |
| node scripts/generate-test262-module-local-binding-a.mjs \ | |
| --suite "$suite" \ | |
| --quickjs-runner "$oracle_source/run-test262" \ | |
| --quickjs-config "$oracle_source/test262.conf" \ | |
| --check-current | |
| node scripts/audit-negative-diagnostics.mjs \ | |
| --contracts dev-support/test262/negative-diagnostics.tsv \ | |
| --rules dev-support/test262/negative-diagnostic-rules.tsv \ | |
| --suite "$suite" --qjs "$oracle" | |
| node scripts/test-generate-test262-module-import-attributes-a.mjs \ | |
| --suite "$suite" \ | |
| --quickjs-runner "$oracle_source/run-test262" \ | |
| --quickjs-config "$oracle_source/test262.conf" | |
| node scripts/generate-test262-module-import-attributes-a.mjs \ | |
| --suite "$suite" \ | |
| --quickjs-runner "$oracle_source/run-test262" \ | |
| --quickjs-config "$oracle_source/test262.conf" \ | |
| --check-current | |
| node scripts/test-generate-test262-module-json-a.mjs \ | |
| --suite "$suite" \ | |
| --quickjs-runner "$oracle_source/run-test262" \ | |
| --quickjs-config "$oracle_source/test262.conf" | |
| node scripts/generate-test262-module-json-a.mjs \ | |
| --suite "$suite" \ | |
| --quickjs-runner "$oracle_source/run-test262" \ | |
| --quickjs-config "$oracle_source/test262.conf" \ | |
| --check-current | |
| cargo build --locked --bin qjs | |
| node scripts/test-negative-diagnostic-generator.mjs \ | |
| --suite "$suite" --qjs "$oracle" --oxide ./target/debug/qjs | |
| ./scripts/test-quickjs-fixtures.sh --all --oxide ./target/debug/qjs | |
| ./scripts/test-quickjs-c-oracles.sh --check | |
| QJS_ORACLE="$oracle" cargo test --locked --workspace --all-targets \ | |
| --features test262-host | |
| test262-full: | |
| if: >- | |
| (github.event_name == 'schedule' && github.event.schedule == '47 18 * * *') || | |
| (github.event_name == 'workflow_dispatch' && | |
| (inputs.tier == 'test262-full' || inputs.tier == 'test262-receipt')) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 350 | |
| env: | |
| QJS_ORACLE_CACHE: ${{ github.workspace }}-oracle-cache | |
| TEST262_WORKERS: "2" | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Install pinned Rust toolchain | |
| run: | | |
| rustup toolchain install "${RUST_VERSION}" --profile minimal | |
| rustup default "${RUST_VERSION}" | |
| - name: Install Test262 dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install --yes build-essential curl git ripgrep xz-utils | |
| - name: Restore authenticated input caches | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| ${{ github.workspace }}-oracle-cache | |
| key: >- | |
| ${{ runner.os }}-test262-${{ env.RUST_VERSION }}-${{ | |
| hashFiles('Cargo.lock', 'compat/upstream.toml', | |
| 'scripts/build-quickjs-oracle.sh', 'scripts/prepare-test262.sh') }} | |
| restore-keys: | | |
| ${{ runner.os }}-test262-${{ env.RUST_VERSION }}- | |
| - name: Check the Test262 receipt boundary | |
| run: ./scripts/check-binary-object-boundary.sh --scan-only . | |
| - name: Run the complete frozen Test262 vector | |
| env: | |
| TEST262_RECEIPT_CAPTURE: >- | |
| ${{ github.event_name == 'workflow_dispatch' && | |
| inputs.tier == 'test262-receipt' }} | |
| run: | | |
| set -o pipefail | |
| set +e | |
| mkdir -p target | |
| ./scripts/test-test262.sh --spec dev-support/test262/current.conf --full \ | |
| 2>&1 | tee target/test262-full.log | |
| pipeline_status=("${PIPESTATUS[@]}") | |
| set -e | |
| test262_status=${pipeline_status[0]} | |
| tee_status=${pipeline_status[1]} | |
| if (( tee_status != 0 )); then | |
| exit "$tee_status" | |
| fi | |
| if (( test262_status == 0 )); then | |
| exit 0 | |
| fi | |
| if [[ "$TEST262_RECEIPT_CAPTURE" != true ]] || (( test262_status != 1 )); then | |
| exit "$test262_status" | |
| fi | |
| expected_error="error: full TSV receipt checksum drifted: $PWD/target/test262-full.tsv" | |
| error_count=$(grep -c '^error:' target/test262-full.log || true) | |
| expected_error_count=$(grep -Fxc "$expected_error" target/test262-full.log || true) | |
| if [[ "$error_count" != 1 || "$expected_error_count" != 1 ]]; then | |
| exit "$test262_status" | |
| fi | |
| node --input-type=module <<'NODE' | |
| import { execFileSync } from "node:child_process"; | |
| import { createHash } from "node:crypto"; | |
| import { lstatSync, readFileSync, realpathSync } from "node:fs"; | |
| import { isAbsolute, relative, resolve, sep } from "node:path"; | |
| function fail(message) { | |
| throw new Error(`receipt capture rejected: ${message}`); | |
| } | |
| function sha256(bytes) { | |
| return createHash("sha256").update(bytes).digest("hex"); | |
| } | |
| function lineCount(bytes, label) { | |
| if (bytes.length === 0 || bytes.at(-1) !== 0x0a) { | |
| fail(`${label} lacks a final newline`); | |
| } | |
| let count = 0; | |
| for (const byte of bytes) if (byte === 0x0a) count += 1; | |
| return count; | |
| } | |
| function utf8(bytes, label) { | |
| const text = bytes.toString("utf8"); | |
| if (!Buffer.from(text, "utf8").equals(bytes)) { | |
| fail(`${label} is not canonical UTF-8`); | |
| } | |
| return text; | |
| } | |
| function regular(path, label) { | |
| const stat = lstatSync(path, { throwIfNoEntry: false }); | |
| if ( | |
| stat === undefined || !stat.isFile() || stat.isSymbolicLink() || | |
| stat.nlink !== 1 | |
| ) { | |
| fail(`${label} is not a single-link regular file`); | |
| } | |
| if (realpathSync(path) !== path) fail(`${label} path is not canonical`); | |
| return readFileSync(path); | |
| } | |
| function parseSpec(bytes) { | |
| const text = utf8(bytes, "current.conf"); | |
| lineCount(bytes, "current.conf"); | |
| const values = new Map(); | |
| for (const line of text.slice(0, -1).split("\n")) { | |
| if (line === "" || line.startsWith("#")) continue; | |
| const match = /^([a-z][a-z0-9_]*)=(.*)$/.exec(line); | |
| if (match === null) { | |
| fail(`malformed current.conf line: ${JSON.stringify(line)}`); | |
| } | |
| if (values.has(match[1])) fail(`duplicate current.conf key: ${match[1]}`); | |
| values.set(match[1], match[2]); | |
| } | |
| return values; | |
| } | |
| function required(spec, key) { | |
| const value = spec.get(key); | |
| if (value === undefined || value === "") { | |
| fail(`missing current.conf key: ${key}`); | |
| } | |
| return value; | |
| } | |
| function countOccurrences(text, needle) { | |
| let count = 0; | |
| let offset = 0; | |
| while ((offset = text.indexOf(needle, offset)) !== -1) { | |
| count += 1; | |
| offset += needle.length; | |
| } | |
| return count; | |
| } | |
| function repositoryPath(root, path, label) { | |
| if ( | |
| path === "" || isAbsolute(path) || path.includes("\\") || | |
| path.split("/").some((part) => part === "" || part === "." || part === "..") | |
| ) { | |
| fail(`unsafe ${label} path`); | |
| } | |
| const absolute = resolve(root, ...path.split("/")); | |
| const inside = relative(root, absolute); | |
| if ( | |
| inside === "" || inside === ".." || inside.startsWith(`..${sep}`) || | |
| isAbsolute(inside) | |
| ) { | |
| fail(`${label} escaped repository root`); | |
| } | |
| return absolute; | |
| } | |
| const root = realpathSync(process.env.GITHUB_WORKSPACE); | |
| const specPath = resolve(root, "dev-support/test262/current.conf"); | |
| const tsvPath = resolve(root, "target/test262-full.tsv"); | |
| const jsonPath = resolve(root, "target/test262-full.jsonl"); | |
| const spec = parseSpec(regular(specPath, "current.conf")); | |
| const shaPattern = /^[0-9a-f]{64}$/; | |
| const countPattern = /^[1-9][0-9]*$/; | |
| const baseline = required(spec, "engine_semantics_sha256"); | |
| const expectedTsvHash = required(spec, "full_tsv_sha256"); | |
| const expectedJsonHash = required(spec, "full_jsonl_sha256"); | |
| const expectedTsvLines = required(spec, "full_tsv_lines"); | |
| const expectedJsonLines = required(spec, "full_jsonl_lines"); | |
| const expectedToolHash = required(spec, "engine_fingerprint_tool_sha256"); | |
| const expectedToolLines = required(spec, "engine_fingerprint_tool_lines"); | |
| for (const [label, value] of [ | |
| ["engine semantics", baseline], | |
| ["full TSV", expectedTsvHash], | |
| ["full JSONL", expectedJsonHash], | |
| ["fingerprint tool", expectedToolHash], | |
| ]) { | |
| if (!shaPattern.test(value)) fail(`invalid ${label} SHA-256`); | |
| } | |
| for (const [label, value] of [ | |
| ["full TSV", expectedTsvLines], | |
| ["full JSONL", expectedJsonLines], | |
| ["fingerprint tool", expectedToolLines], | |
| ]) { | |
| if (!countPattern.test(value)) fail(`invalid ${label} line count`); | |
| } | |
| const toolPath = repositoryPath( | |
| root, | |
| required(spec, "engine_fingerprint_tool"), | |
| "fingerprint tool", | |
| ); | |
| const toolBytes = regular(toolPath, "fingerprint tool"); | |
| if (sha256(toolBytes) !== expectedToolHash) { | |
| fail("fingerprint tool checksum drifted"); | |
| } | |
| if (lineCount(toolBytes, "fingerprint tool") !== Number(expectedToolLines)) { | |
| fail("fingerprint tool line count drifted"); | |
| } | |
| const fingerprintOutput = execFileSync(process.execPath, [ | |
| toolPath, | |
| "--root", root, | |
| "--worktree", | |
| "--files", required(spec, "engine_semantics_files"), | |
| "--trees", required(spec, "engine_semantics_trees"), | |
| ], { | |
| encoding: "utf8", | |
| stdio: ["ignore", "pipe", "inherit"], | |
| }); | |
| if (!/^[0-9a-f]{64}\n$/.test(fingerprintOutput)) { | |
| fail("fingerprint tool output drifted"); | |
| } | |
| const current = fingerprintOutput.trim(); | |
| if (current === baseline) { | |
| fail("receipt capture requires a source-stale baseline"); | |
| } | |
| const tsvBytes = regular(tsvPath, "full TSV receipt"); | |
| const jsonBytes = regular(jsonPath, "full JSONL receipt"); | |
| if ( | |
| lineCount(tsvBytes, "full TSV receipt") !== Number(expectedTsvLines) || | |
| lineCount(jsonBytes, "full JSONL receipt") !== Number(expectedJsonLines) | |
| ) { | |
| fail("full receipt line count drifted"); | |
| } | |
| const tsv = utf8(tsvBytes, "full TSV receipt"); | |
| const json = utf8(jsonBytes, "full JSONL receipt"); | |
| const tsvCurrent = | |
| `# quickjs-oxide Test262 outcome vector v5 ` + | |
| `engine_semantics_sha256=${current}\n`; | |
| const tsvBaseline = | |
| `# quickjs-oxide Test262 outcome vector v5 ` + | |
| `engine_semantics_sha256=${baseline}\n`; | |
| if ( | |
| !tsv.startsWith(tsvCurrent) || countOccurrences(tsv, current) !== 1 || | |
| countOccurrences(tsv, baseline) !== 0 | |
| ) { | |
| fail("full TSV fingerprint placement drifted"); | |
| } | |
| const normalizedTsv = Buffer.from( | |
| tsvBaseline + tsv.slice(tsvCurrent.length), | |
| "utf8", | |
| ); | |
| if ( | |
| sha256(tsvBytes) === expectedTsvHash || | |
| sha256(normalizedTsv) !== expectedTsvHash | |
| ) { | |
| fail("full TSV differs by more than its engine fingerprint"); | |
| } | |
| const firstNewline = json.indexOf("\n"); | |
| if (firstNewline === -1) fail("full JSONL metadata line is missing"); | |
| let metadata; | |
| try { | |
| metadata = JSON.parse(json.slice(0, firstNewline)); | |
| } catch { | |
| fail("full JSONL metadata is invalid JSON"); | |
| } | |
| if ( | |
| metadata.kind !== "metadata" || | |
| metadata.engine_semantics_sha256 !== current | |
| ) { | |
| fail("full JSONL metadata fingerprint drifted"); | |
| } | |
| const jsonCurrent = `\"engine_semantics_sha256\":\"${current}\"`; | |
| const jsonBaseline = `\"engine_semantics_sha256\":\"${baseline}\"`; | |
| if ( | |
| !json.slice(0, firstNewline).includes(jsonCurrent) || | |
| countOccurrences(json, current) !== 1 || countOccurrences(json, baseline) !== 0 | |
| ) { | |
| fail("full JSONL fingerprint placement drifted"); | |
| } | |
| const normalizedJson = Buffer.from( | |
| json.replace(jsonCurrent, jsonBaseline), | |
| "utf8", | |
| ); | |
| if ( | |
| sha256(jsonBytes) === expectedJsonHash || | |
| sha256(normalizedJson) !== expectedJsonHash | |
| ) { | |
| fail("full JSONL differs by more than its engine fingerprint"); | |
| } | |
| console.log( | |
| "Current-source Test262 receipts differ only by engine fingerprint: " + | |
| `${baseline} -> ${current}`, | |
| ); | |
| NODE | |
| - name: Upload complete Test262 receipts | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: test262-full-${{ github.sha }} | |
| path: | | |
| target/test262-full.tsv | |
| target/test262-full.jsonl | |
| target/test262-full.log | |
| dev-support/test262/current.conf | |
| dev-support/test262/negative-diagnostics.tsv | |
| compat/upstream.toml | |
| if-no-files-found: warn | |
| retention-days: 30 |