Record interrupted listing and integrated State picker test failure
This commit is contained in:
@@ -84,3 +84,4 @@ harness/experiments/canvas-repair-native-33e30c1-20260912/** -text -whitespace
|
||||
harness/experiments/canvas-repair-server-33e30c1-20260912/** -text -whitespace
|
||||
|
||||
harness/experiments/canvas-repair-publication-20260912/** -text -whitespace
|
||||
harness/prerelease37-windows-gates-20260924/attempts-8dd/** -text -whitespace
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Windows attempts on 8dd
|
||||
|
||||
Final-02 compiled all 61 native harnesses, then was intentionally stopped during read-only listing before actual tests. Final-03 used the separately reviewed v2 recipe, compiled and enumerated all 61 harnesses, then executed the literal workspace test command. It failed the State picker mounted Escape assertion. Source inputs remained unchanged in both attempts. Strict lint and product builds did not run.
|
||||
|
||||
The ZIP includes complete raw attempt records, both immutable recipes, format-02, the listing review, and source/actual ignored-test reconciliation. manifest.json binds every member and summarizes observed outcomes. The directly visible v2 files and review are exact copies for early review. This is failure evidence, not prerelease acceptance.
|
||||
Binary file not shown.
@@ -0,0 +1,57 @@
|
||||
# Independent review: phase-scoped native test listing provenance
|
||||
|
||||
Date: 2026-09-24. Reviewer: listing_efficiency_review, delegated by the Windows prerelease integration owner. Status: local-only review for parent synthesis/publication.
|
||||
|
||||
## Verdict
|
||||
|
||||
ACCEPT WITH SMALL REFINEMENTS. Replace repeated full source/toolchain/configuration hashing around every standard native harness listing with checks at the listing phase boundaries. Preserve the unchanged actual Cargo gates and their full before/after checks. This is a proportionate optimization under the existing exclusive source, configuration, recipe, toolchain, and target-writer ownership contract. It is not equivalent protection against an adversarial concurrent writer, and must not be described as continuous monitoring.
|
||||
|
||||
The inspected baseline is `.local/prerelease-gates-20260924/run.py`, `final.py`, and `README.md`. The active workspace-tests context identifies source commit `8ddfadf440b5c208b1adb6b211a97ba0c99cff04`, dev executable profile/default test profile, and the literal command `cargo test --locked --offline --workspace --no-fail-fast -- --test-threads=1`. This review inspected code and receipts only; it did not execute a harness, compiler, test, or source hash gate. The root checkout is a distinct working tree at `6ef6868f00e183620c3a5469c970ca55541665d3`, with pre-existing changes; review conclusions concern the identified candidate and recipe, not root cleanliness. Parent retains shared coordination and active-attempt ownership.
|
||||
|
||||
## Why the change preserves useful evidence
|
||||
|
||||
`final.py` obtains test executable identities from a successful same-pin Cargo no-run build, maps their source and manifests to the pinned source inventory, rejects explicit custom `harness=false` targets, and records their byte hashes. Standard libtest `--list` and `--list --ignored` enumerate compiled test metadata; this phase does not compile the source tree or execute test bodies. Re-hashing the complete source tree and compiler DLLs between these short processes adds little evidence under exclusive ownership. Keeping the exact invoked binary checks, raw outputs, parse/count checks, and ignored-subset checks preserves the link between compiled artifacts and the names observed.
|
||||
|
||||
The existing main runner already records a complete pre-command inventory and checks the frozen environment before calling `list_tests`. That snapshot can serve as the listing phase's initial boundary. An explicit matching full source/environment check after listing must succeed before the literal Cargo command is launched. Main's full post-command checks and the final assembler checks must remain unchanged. The existing all-artifact and no-run-inventory validation after the actual test command must also remain.
|
||||
|
||||
## Smallest safe change
|
||||
|
||||
1. Make a separate v2 recipe directory. Keep the old recipe bytes and attempt evidence intact; bind the v2 files in the fresh environment receipt. The new attempt reruns all gates, permitting ordinary Cargo cache reuse but no substitution of old pass receipts.
|
||||
2. Reuse the successful initial full source/environment check as the listing phase's before boundary. Remove only the full `inputs()` and `verify_environment()` calls inside each listing invocation.
|
||||
3. Move the binary-before check into the inner invocation loop, immediately before each process launch. The old code checked before the outer artifact loop and after each invocation; do not reproduce that small attribution gap when claiming a binary-before observation for each invocation. Hash after each invocation and compare both observations to the admitted executable record.
|
||||
4. Retain per-invocation argv, cwd, pin, exit code, raw stdout/stderr hashes, both observed binary hashes, exact parse/count validation, and ignored-subset validation. Do not retain a misleading per-invocation `source_unchanged: true`; use an explicit phase-scope reference or description instead.
|
||||
5. After successful enumeration and before literal Cargo execution, recompute full source inputs and verify the frozen environment. Compare source bytes to the admitted before snapshot. Recheck every test artifact and the no-run artifact inventory before the actual command, preserving the existing after-command checks. No full check failure may fall through into Cargo execution.
|
||||
6. Write a phase receipt that identifies its start/end, source pin, before/after inventory hashes, frozen-environment receipt, listing count, success/failure, and scope. A successful listing/name inventory must be bound to a successful boundary receipt; individual completed listing receipts alone are partial evidence. Preserve partial raw logs on any failure; use existing runner finalization or a narrow phase `finally` for failure evidence, without rerunning a test.
|
||||
7. Update v2 README to state the new phase-scoped checks. Keep literal workspace commands, target exclusivity, no-run attribution, real-test after-artifact validation, ignored-test handling, and failure stop behavior intact.
|
||||
|
||||
## Concrete false-pass and attribution risks
|
||||
|
||||
- Missing the new after-list/before-Cargo check would allow persistent source/configuration drift during a long listing phase to reach the real test command. Detecting drift only afterward prevents overall acceptance but still runs a command against unadmitted inputs. This is avoidable: make the new boundary a hard gate before launch.
|
||||
- A transient source/configuration edit introduced and restored between phase boundaries becomes less likely to be detected than under per-invocation checks. For standard listing of fixed binaries, source or Cargo compiler configuration changes do not alter compiled test names; the real Cargo command still sees admitted source/configuration after the boundary. Therefore this does not by itself yield a passing test claim on different source. A transient runtime/DLL replacement or nonstandard harness could change observed listing behavior while preserving executable bytes. Existing custom-harness rejection, standard-harness review, and exclusive toolchain/runtime/target ownership are necessary assumptions. Both old and new endpoint hashing have time-of-check/time-of-use limitations; neither proves absence of all transient edits.
|
||||
- Keeping old `source_unchanged` fields true would overstate observed evidence. Boundary-scoped wording avoids a provenance false claim even when the actual test result is valid.
|
||||
- Reusing old no-run/test success records in the fresh attempt, changing the active recipe in place, or treating an interrupted partial list as a passed gate would break attempt attribution. The proposed separate recipe/fresh attempt explicitly avoids these errors.
|
||||
- Removing final all-binary/no-run-inventory checks would permit the test command to use materially different executables from those listed. Those checks remain necessary even with exclusive ownership.
|
||||
|
||||
No blocker was found in the batching concept with these refinements. Review of the final v2 diff remains necessary to confirm that implementation matches this bounded recommendation. This document is not runtime gate evidence and does not authorize a release or assert test success.
|
||||
## Prepared v2 implementation recheck
|
||||
|
||||
Disposition: ACCEPT. Independently inspected the actual no-index diffs of v2 `run.py`, `final.py`, and `README.md` against v1. `final.py` is byte-identical. The runner change is limited to `list_tests`; the actual Cargo command vectors and main runner before/after/final artifact checks are unchanged. The README identifies the separate recipe path and accurately explains the verification scope and concurrent-writer limitation.
|
||||
|
||||
The prepared implementation checks full source/environment at phase entry and again after all listings. A source mismatch or environment exception prevents return to the Cargo launch. Every invocation freshly hashes its executable before launch and after completion. It retains raw outputs, exits, parsing/count checks and the ignored subset check, and removes the misleading per-listing source-unchanged claim. A successful name inventory links the phase receipt, which links the full source maps and admitted environment receipt.
|
||||
|
||||
Failure during enumeration or a throwing boundary verification may leave no phase receipt; this is acceptable here because no admitted name inventory is then written, the exception prevents Cargo launch, and unchanged main/final assembler failure handling preserves partial listing logs and attempts full source/environment/artifact checks. A phase receipt with `source_unchanged: false` is immediately followed by failure, never by name inventory admission or Cargo launch.
|
||||
|
||||
The extra initial full phase check duplicates the main pre-command check but is harmless and provides a self-contained listing function boundary. Keeping it is recommended for this narrow change; its fixed cost is minor compared with the removed repeated checks. The outer per-artifact hash is also redundant with the inner pre-invocation hash but harmless.
|
||||
|
||||
The no-run inventory is still hashed before reading and checked again in unchanged main finalization, as in v1. A further immediate before-Cargo inventory-file check suggested above would shorten detection latency only; omission does not yield a successful changed-inventory result because the final check fails the gate. It is not a blocker under exclusive ownership.
|
||||
|
||||
No concrete new false-pass bug found in the prepared diff. This acceptance is code inspection only, not a runtime or release pass. No build, test, recipe import, or active-attempt modification was performed by the reviewer. Parent remains responsible for orderly interruption/final checks of the old attempt, keeping its partial evidence, and a fresh v2 attempt with no substituted old gate passes.
|
||||
Reviewed SHA-256: 698ed7182726c77fa4a1c85fc6dd08187ed20689941f57e749e3f0bc845f3252 — C:\Users\User\Documents\Git\ARCADIA-Studio\.local\prerelease-gates-v2-20260924\run.py
|
||||
|
||||
Reviewed SHA-256: ce73a1680f8cd8bb5112cffa81929458dafaaefc282b8ff5391c7571cd34e666 — C:\Users\User\Documents\Git\ARCADIA-Studio\.local\prerelease-gates-v2-20260924\final.py
|
||||
|
||||
Reviewed SHA-256: 5f4aa965695f6e543e2108f4f0950dbf15a704955f6f70653c2c20811c91f76c — C:\Users\User\Documents\Git\ARCADIA-Studio\.local\prerelease-gates-v2-20260924\README.md
|
||||
|
||||
### Final concurrent-preparation delta checked
|
||||
|
||||
Parent reported two additions during the review. Re-read the complete current v1-to-v2 runner diff: the phase receipt now explicitly sets `passed=source_unchanged` after all listings and successful environment verification, and main now also compares the no-run inventory hash immediately before the literal Cargo launch. Both are correct; the earlier paragraph describing that extra before-Cargo check as absent is superseded. No other runner changes appeared. Final disposition remains ACCEPT. The current runner hash is `698ed7182726c77fa4a1c85fc6dd08187ed20689941f57e749e3f0bc845f3252` (same hash captured at the preceding addendum's end). No runtime work performed.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
# Prerelease Windows workspace gates — prepared, not executed
|
||||
|
||||
Issue37 preparation. No candidate is selected by these recipes. No compiler, test, evaluator, server or Studio was run to prepare them. Parent must review these files against the actual frozen integrated candidate and exclusively schedule its target directory before execution. No prior core/server evidence or old executable is substituted for a required gate.
|
||||
|
||||
`final.py` requires all four arguments: `--repo`, `--source-commit` (full40 lowercase hex), `--attempt` (fresh final-NN), and `--profile dev|release`. This explicit profile choice applies only to executable builds. Tests use Cargo's default test profile and Clippy its default dev profile. Commands build for the implicit native host; the actual rustc verbose output must contain exactly `host: x86_64-pc-windows-gnullvm`. No `--target` flag is added, so existing host-native caches remain usable. Outputs are copied from each matching Cargo JSON executable record, never inferred from debug/release directory names. Receipts distinguish the selected build profile, each command's profile and Cargo's emitted artifact profile metadata.
|
||||
|
||||
From this workspace, only after candidate review/freeze and scheduling, set actual values for `$candidateRepo`, `$candidateCommit`, `$gateAttempt`, `$buildProfile` and an absolute `$gateTargetDirectory`, then invoke:
|
||||
|
||||
```powershell
|
||||
. .local/toolchain/env.ps1
|
||||
$env:CARGO_TARGET_DIR = $gateTargetDirectory
|
||||
& 'C:/Users/User/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/python.exe' .local/prerelease-gates-v2-20260924/final.py --repo $candidateRepo --source-commit $candidateCommit --attempt $gateAttempt --profile $buildProfile
|
||||
```
|
||||
|
||||
There is no default/fabricated pin or candidate. A new invocation never overwrites an attempt or artifact. Source must remain clean and exact throughout. Jobs1 and incremental0 are enforced through `CARGO_BUILD_JOBS=1` and `CARGO_INCREMENTAL=0`; no redundant command flags change the literal gates. `CARGO_BUILD_TARGET` and Cargo configuration `build.target` must be absent. Compiler/wrapper overrides are refused so Cargo cannot bypass the toolchain whose host was verified. The admitted MinGW libunwind.dll hash remains `aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a`; another runtime/toolchain needs explicit recipe review.
|
||||
|
||||
## Required gates
|
||||
|
||||
After rustc/Cargo version capture, commands are literal workspace scope:
|
||||
|
||||
1. `cargo test --locked --offline --workspace --no-fail-fast -- --test-threads=1`
|
||||
2. `cargo clippy --locked --offline --workspace --all-targets -- -D warnings`
|
||||
3. `cargo build -p oa-desktop --bin arcadia-studio --locked --offline --message-format=json-render-diagnostics` for dev, or the same command with `--release` for a selected release build.
|
||||
4. Matching `cargo build -p oa-server --bin arcadia-server` with those build flags.
|
||||
|
||||
Optional examples use the same build-only profile rule. Selecting release does not silently convert the test or Clippy gates to a release profile. A dev choice can reuse the established host-native `target/debug` cache; reuse is not a clean-build claim. Every required command still executes against the frozen candidate, and emitted executable bytes are preserved and identified afresh.
|
||||
|
||||
Existing `#[ignore]` tests remain ignored under the literal test command; raw logs retain their actual names/counts. Do not relabel those as executed or hard-code a pre-candidate ignored count. Parent must review actual ignored cases and material warnings/failures. Windows gates do not establish Linux acceptance or native/package/server-launch behavior.
|
||||
|
||||
The frozen handoff identified during preparation is `6ae75fb2ccaae16c29004444815ddeaeab2e75ac`; the caller must still supply the actual reviewed repository and full pin. No implicit default has been added. The command vectors above follow the owner handoff exactly, including no-fail-fast and one test thread. There is no inherited-formatting gate in this runner; parent may run a separately attributed format check and classify any failure.
|
||||
|
||||
## Test-name and ignore inventory
|
||||
|
||||
Before executing the literal workspace test command once, a separate `workspace-test-build` runs `cargo test --locked --offline --workspace --no-run --message-format=json-render-diagnostics`. This compiles native test harnesses without running test bodies. `test-artifact-inventory.json` binds each Cargo-reported test executable to its target, package manifest, source, raw binary hash and no-run result. Source and package manifest must match entries in the frozen repository-path input map, including raw hashes and Git blob identities; merely residing inside the candidate is insufficient. It refuses artifacts outside the explicit target directory, sources/manifests outside the candidate, and declared custom `harness=false` targets whose `--list` behavior has not been reviewed.
|
||||
|
||||
The `workspace-tests` runner then invokes each identified native executable with `--list` and separately `--list --ignored`. Every invocation retains argv, source pin, raw stdout/stderr, exit code and binary hashes immediately before and after that invocation. Full source/environment checks bracket the complete read-only listing phase and must pass before the literal Cargo test command. `test-name-inventory.json` links complete native harness names and ignored subsets to those exact executable hashes. The explicit listing-phase-result.json receipt links full source inventories and the frozen environment; per-listing receipts make no individual source-unchanged claim. Exclusive source/configuration/runtime/target ownership is required. Listing does not execute ignored tests or any test body. Malformed listing output stops the attempt before tests instead of inventing an inventory.
|
||||
|
||||
The owner test command then executes exactly once. `test-artifacts-after.json` verifies that all listed executable hashes and the no-run inventory remained unchanged across the actual run, including when a test fails. Test executables are not copied into the evidence package merely for this inventory. Later lint/build steps may reuse the target, so their current files must not be mistaken for the earlier hashed test binaries.
|
||||
|
||||
`observed-ignores.json` records names and any reason actually emitted as ignored by the literal test stdout, with its raw log hash. A missing reason is explicitly absent: parent must document the rationale from the pinned source/owner contract before prerelease acceptance. The inventory provides evidence, not approval of every ignore. No test count or ignore count is presumed from earlier candidates.
|
||||
|
||||
Cargo `--no-run` does not supply persistent rustdoc-test executable artifacts. This native-harness inventory therefore makes no artifact/name completeness claim for doctests; their actual discovery, outcomes and any ignores remain in the literal workspace test logs. No extra doctest or ordinary test execution is added to obtain this inventory.
|
||||
|
||||
Optional matching helper builds require explicit arguments:
|
||||
|
||||
- `--snapshot-example attachment_snapshot`: candidate must contain that oa-core example; otherwise admission refuses. The accepted Allocation worktree contains it, while the current root inspected during preparation does not.
|
||||
- `--source-converter-example NAME`: parent supplies a reviewed converter example name present in the actual candidate. No checked-in source converter was identified in the root/accepted worktree inspected during preparation; no name, source or reused binary is invented. If absent, omit this argument. Native validation can later use a separately admitted candidate CLI/evaluator as appropriate.
|
||||
|
||||
The recipe checks the candidate manifests for `oa-desktop`/`arcadia-studio` and `oa-server`/`arcadia-server`. Example source is identified from explicit Cargo example declarations or conventional candidate example paths. Source files must lie within the exact clean candidate inventory. It does not assert that an arbitrarily named helper implements a correct oracle; helper purpose and runtime contract remain reviewer responsibilities.
|
||||
|
||||
## Provenance and failure preservation
|
||||
|
||||
Each command has immutable create-new context/result records, separate raw stdout/stderr, orchestration log, changes.patch, and before/after full tracked/nonignored inventories. The source mapping records committed Git blob IDs and exact raw Windows byte hashes. A batched Git hash-object check applies the path's Git clean/EOL rules and requires every working source blob to match the pin; raw hashes separately establish exact same-byte command inputs. Nonregular source entries and a mismatching index/file set refuse admission.
|
||||
|
||||
The frozen environment records profile/target overrides, env.ps1, actual toolchain executables/compiler DLLs, resolved cargo/rustc/linker, windres, runtime, recipes, and ancestor/CARGO_HOME config presence/bytes. These and clean HEAD/source inventories are rechecked before/after each command and at completion. Registry tokens and unrelated environment secrets are not captured. No source/config/script or target-writer concurrency is allowed.
|
||||
|
||||
Every successful executable build is preserved immediately from its single exact Cargo JSON artifact, checked before/after copying, then frozen again at final verification. JSON must identify a non-test executable under the explicit target directory. Relocation receipts retain original paths, emitted profile metadata and copied hashes. The runtime is copied after builds; no later shared-target content may replace preserved artifacts.
|
||||
|
||||
Failure stops later commands. A started attempt retains logs, any preserved artifacts, result/summary failure and final source/config checks, including failed command exit codes; no retries overwrite it. Final source, environment and preserved-artifact checks are attempted independently, so one failed check does not skip the others. Admission errors before output creation are refusals, not executed gate evidence. The assembler/runner does not stage or launch the resulting binaries. Packaging and GUI/server smoke acceptance require separate identified checks against these exact preserved outputs.
|
||||
|
||||
Validation performed while preparing these files: Python AST only. No production gate or helper executed. Parent must review the actual candidate's workspace membership, toolchain/configuration, examples, profile choice and package launch requirements before authorizing a run.
|
||||
|
||||
## V2 listing-boundary change
|
||||
|
||||
The original recipe measured about 19 seconds per read-only listing because it rehashed the complete source and compiler twice per invocation. With 61 harnesses this implied about 39 minutes before tests. V2 retains the required commands, per-command full proofs, every listed name/ignore, binary hashes and raw exits. It batches only repeated source/environment checks across the listing phase. Old recipes and interrupted evidence remain separate. This is not a security sandbox: transient source/config/runtime replacement within the phase is not excluded by endpoint checks, and concurrent writers remain forbidden. No v2 gate was executed during preparation.
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
"""Prepared Windows workspace gates. No default candidate; never launches artifacts."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tomllib
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
from run import clean, environment, inputs, now, read, record, save, verify_environment
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
TOOLCHAIN = ROOT.parent / 'toolchain'
|
||||
HOST = 'x86_64-pc-windows-gnullvm'
|
||||
TOOLCHAIN_NAME = 'stable-' + HOST
|
||||
RUNTIME = TOOLCHAIN / 'llvm-mingw-20260908-ucrt-x86_64/bin/libunwind.dll'
|
||||
RUNTIME_SHA = 'aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a'
|
||||
|
||||
|
||||
def manifest(path):
|
||||
with path.open('rb') as stream:
|
||||
return tomllib.load(stream)
|
||||
|
||||
|
||||
def example(repo, name):
|
||||
core = repo / 'crates/oa-core'
|
||||
cfg = manifest(core / 'Cargo.toml')
|
||||
declared = [x for x in cfg.get('example', []) if x.get('name') == name]
|
||||
if len(declared) > 1:
|
||||
raise RuntimeError('Duplicate example declaration')
|
||||
if declared:
|
||||
path = core / declared[0].get('path', 'examples/' + name + '.rs')
|
||||
elif cfg['package'].get('autoexamples', True):
|
||||
choices = [p for p in (core / ('examples/' + name + '.rs'), core / ('examples/' + name + '/main.rs')) if p.is_file()]
|
||||
if len(choices) != 1:
|
||||
return None
|
||||
path = choices[0]
|
||||
else:
|
||||
return None
|
||||
if not path.resolve().is_relative_to(repo) or not path.is_file():
|
||||
raise RuntimeError('Example source missing or outside candidate')
|
||||
return record(path)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--repo', required=True, type=Path)
|
||||
parser.add_argument('--source-commit', required=True)
|
||||
parser.add_argument('--attempt', required=True)
|
||||
parser.add_argument('--profile', required=True, choices=('dev', 'release'))
|
||||
parser.add_argument('--snapshot-example', choices=('attachment_snapshot',), help='Build only when explicitly requested and present in the candidate')
|
||||
parser.add_argument('--source-converter-example', help='Explicit reviewed oa-core example name; never reuse an older executable')
|
||||
args = parser.parse_args()
|
||||
repo, pin = args.repo.resolve(), args.source_commit
|
||||
if not re.fullmatch('[0-9a-f]{40}', pin) or not re.fullmatch('final-[0-9]{2}', args.attempt):
|
||||
raise RuntimeError('Full explicit source pin and fresh final-NN required')
|
||||
if args.source_converter_example and not re.fullmatch('[A-Za-z0-9_-]+', args.source_converter_example):
|
||||
raise RuntimeError('Invalid converter example name')
|
||||
out = ROOT / args.attempt
|
||||
if out.exists() or not repo.is_dir() or os.name != 'nt':
|
||||
raise RuntimeError('Fresh output and existing Windows candidate required')
|
||||
clean(repo, pin)
|
||||
for package, binary in (('oa-desktop', 'arcadia-studio'), ('oa-server', 'arcadia-server')):
|
||||
cfg = manifest(repo / ('crates/' + package + '/Cargo.toml'))
|
||||
if cfg['package']['name'] != package or not any(x.get('name') == binary for x in cfg.get('bin', [])):
|
||||
raise RuntimeError('Candidate executable contract differs: ' + binary)
|
||||
target_text = os.environ.get('CARGO_TARGET_DIR', '')
|
||||
if not target_text or not Path(target_text).is_absolute():
|
||||
raise RuntimeError('Explicit absolute CARGO_TARGET_DIR required')
|
||||
target = Path(target_text).resolve()
|
||||
if os.environ.get('CARGO_BUILD_TARGET'):
|
||||
raise RuntimeError('Host-native cache required; CARGO_BUILD_TARGET must be unset')
|
||||
compiler_keys = ('RUSTC', 'RUSTDOC', 'RUSTC_WRAPPER', 'RUSTC_WORKSPACE_WRAPPER',
|
||||
'CARGO_BUILD_RUSTC', 'CARGO_BUILD_RUSTDOC',
|
||||
'CARGO_BUILD_RUSTC_WRAPPER', 'CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER')
|
||||
if any(os.environ.get(key) for key in compiler_keys):
|
||||
raise RuntimeError('Unreviewed Cargo compiler override; use the verified pinned toolchain')
|
||||
required_env = {'CARGO_HOME': str((TOOLCHAIN / 'cargo').resolve()),
|
||||
'RUSTUP_HOME': str((TOOLCHAIN / 'rustup').resolve()),
|
||||
'RUSTUP_TOOLCHAIN': TOOLCHAIN_NAME, 'CARGO_BUILD_JOBS': '1', 'CARGO_INCREMENTAL': '0'}
|
||||
for key, value in required_env.items():
|
||||
actual = os.environ.get(key, '')
|
||||
if (Path(actual).resolve() != Path(value).resolve()) if key.endswith('_HOME') else actual != value:
|
||||
raise RuntimeError('Required environment differs: ' + key)
|
||||
if record(RUNTIME)['sha256'] != RUNTIME_SHA:
|
||||
raise RuntimeError('Admitted MinGW runtime differs')
|
||||
tools = [TOOLCHAIN / 'env.ps1', RUNTIME, ROOT / 'run.py', ROOT / 'final.py', ROOT / 'README.md']
|
||||
actual_bin = TOOLCHAIN / 'rustup/toolchains' / TOOLCHAIN_NAME / 'bin'
|
||||
for name in ('cargo.exe', 'rustc.exe', 'rustdoc.exe', 'cargo-clippy.exe', 'clippy-driver.exe'):
|
||||
tools.append(actual_bin / name)
|
||||
tools.extend(actual_bin.glob('rustc_driver-*.dll'))
|
||||
for name in ('cargo', 'rustc', 'clang.exe'):
|
||||
resolved = shutil.which(name)
|
||||
if not resolved:
|
||||
raise RuntimeError('Required command unavailable: ' + name)
|
||||
tools.append(Path(resolved))
|
||||
rc = os.environ.get('RC')
|
||||
if not rc:
|
||||
raise RuntimeError('Explicit windres path required')
|
||||
tools.append(Path(rc))
|
||||
configs = []
|
||||
cargo_home = TOOLCHAIN / 'cargo'
|
||||
for folder in dict.fromkeys([repo, *repo.parents, cargo_home.resolve()]):
|
||||
for name in ('config', 'config.toml'):
|
||||
path = folder / name if folder == cargo_home.resolve() else folder / '.cargo' / name
|
||||
configs.append(dict(path=str(path), present=path.is_file(), record=record(path) if path.is_file() else None))
|
||||
if path.is_file():
|
||||
build = manifest(path).get('build', {})
|
||||
if 'target' in build:
|
||||
raise RuntimeError('Host-native cache required; Cargo build.target must be absent: ' + str(path))
|
||||
if any(key in build for key in ('rustc', 'rustdoc', 'rustc-wrapper', 'rustc-workspace-wrapper')):
|
||||
raise RuntimeError('Unreviewed Cargo compiler configuration: ' + str(path))
|
||||
frozen = dict(environment=environment(), target=HOST, profile=args.profile,
|
||||
profile_scope='selected build profile only', target_mode='implicit verified rustc host',
|
||||
target_directory=str(target), cargo_configs=configs,
|
||||
bound_files=[record(p) for p in dict.fromkeys(p.resolve() for p in tools)])
|
||||
before = inputs(repo, pin)
|
||||
common = ['--locked', '--offline']
|
||||
build_flags = [*common, *(['--release'] if args.profile == 'release' else [])]
|
||||
commands = [('toolchain', ['rustc', '--version', '--verbose'], None),
|
||||
('cargo-version', ['cargo', '--version', '--verbose'], None),
|
||||
('workspace-test-build', ['cargo', 'test', '--locked', '--offline', '--workspace', '--no-run', '--message-format=json-render-diagnostics'], None),
|
||||
('workspace-tests', ['cargo', 'test', '--locked', '--offline', '--workspace', '--no-fail-fast', '--', '--test-threads=1'], None),
|
||||
('workspace-clippy', ['cargo', 'clippy', '--locked', '--offline', '--workspace', '--all-targets', '--', '-D', 'warnings'], None)]
|
||||
identified = {}
|
||||
for package, binary in (('oa-desktop', 'arcadia-studio'), ('oa-server', 'arcadia-server')):
|
||||
commands.append((binary + '-build', ['cargo', 'build', '-p', package, '--bin', binary, *build_flags, '--message-format=json-render-diagnostics'], binary))
|
||||
snapshot = example(repo, 'attachment_snapshot') if args.snapshot_example else None
|
||||
if args.snapshot_example and snapshot is None:
|
||||
raise RuntimeError('Requested attachment_snapshot example is absent')
|
||||
if snapshot:
|
||||
identified['attachment_snapshot'] = snapshot
|
||||
if args.source_converter_example:
|
||||
converter = example(repo, args.source_converter_example)
|
||||
if converter is None or args.source_converter_example in ('attachment_snapshot', 'arcadia-studio', 'arcadia-server'):
|
||||
raise RuntimeError('Explicit distinct converter example not present')
|
||||
identified[args.source_converter_example] = converter
|
||||
for name in identified:
|
||||
commands.append((name + '-build', ['cargo', 'build', '-p', 'oa-core', '--example', name, *build_flags, '--message-format=json-render-diagnostics'], name))
|
||||
out.mkdir()
|
||||
(out / 'artifacts').mkdir()
|
||||
save(out / 'inputs-before.json', before)
|
||||
save(out / 'environment.json', frozen)
|
||||
save(out / 'plan.json', dict(source_commit=pin, repo=str(repo), target=HOST, profile=args.profile,
|
||||
profile_scope='selected build profile only', target_mode='implicit verified rustc host',
|
||||
test_profile='default test', clippy_profile='default dev',
|
||||
commands=commands, examples=identified,
|
||||
attachment_snapshot_requested=args.snapshot_example,
|
||||
source_converter_requested=args.source_converter_example,
|
||||
no_prior_gate_or_binary_reuse=True))
|
||||
started = now()
|
||||
steps, outputs, relocations = [], [], []
|
||||
failure = None
|
||||
unchanged = False
|
||||
|
||||
def preserve(path, build_record=None):
|
||||
original = record(path)
|
||||
dest = out / 'artifacts' / path.name
|
||||
with path.open('rb') as src, dest.open('xb') as dst:
|
||||
shutil.copyfileobj(src, dst)
|
||||
copied = record(dest)
|
||||
if record(path) != original or any(original[k] != copied[k] for k in ('bytes', 'sha256')):
|
||||
raise RuntimeError('Artifact changed during preservation')
|
||||
outputs.append(copied)
|
||||
relocations.append(dict(original=original, preserved=copied, cargo_artifact=build_record))
|
||||
|
||||
try:
|
||||
for name, argv, executable in commands:
|
||||
command_profile = args.profile if executable else ('test' if name in ('workspace-test-build', 'workspace-tests') else 'dev' if name == 'workspace-clippy' else 'none')
|
||||
verify_environment(frozen)
|
||||
if inputs(repo, pin) != before:
|
||||
raise RuntimeError('Source changed before ' + name)
|
||||
wrapper = out / (name + '.orchestration.log')
|
||||
inventory_args = ['--test-inventory', str(out / 'test-artifact-inventory.json')] if name == 'workspace-tests' else []
|
||||
with wrapper.open('xb') as stream:
|
||||
proc = subprocess.run([sys.executable, str(ROOT / 'run.py'), '--repo', str(repo),
|
||||
'--source-commit', pin, '--profile', args.profile, '--command-profile', command_profile, '--output', str(out / name),
|
||||
'--environment', str(out / 'environment.json'), *inventory_args, '--', *argv], stdout=stream, stderr=subprocess.STDOUT)
|
||||
result_path = out / name / 'result.json'
|
||||
result = read(result_path) if result_path.is_file() else None
|
||||
steps.append(dict(name=name, argv=argv, command_profile=command_profile, runner_exit_code=proc.returncode,
|
||||
orchestration_log=record(wrapper), result=record(result_path) if result else None))
|
||||
if proc.returncode or not result or result['exit_code'] != 0 or result['failure'] or not result['inputs_unchanged']:
|
||||
raise RuntimeError('Gate failed: ' + name)
|
||||
if result['source_commit'] != pin or result['argv'] != argv or Path(result['cwd']) != repo or result['profile'] != args.profile or result['command_profile'] != command_profile or result['target'] != HOST:
|
||||
raise RuntimeError('Command attribution differs')
|
||||
for phase in ('inputs-before.json', 'inputs-after.json'):
|
||||
if read(out / name / phase) != before:
|
||||
raise RuntimeError('Source inventory differs in ' + name)
|
||||
verify_environment(frozen)
|
||||
if name == 'toolchain':
|
||||
host_lines = [line for line in (out / name / 'output.log').read_text().splitlines() if line.startswith('host:')]
|
||||
if host_lines != ['host: ' + HOST]:
|
||||
raise RuntimeError('Actual rustc host differs')
|
||||
if name == 'workspace-test-build':
|
||||
tests = {}
|
||||
source_by_repo_path = {item['repository_path']: item for item in before}
|
||||
for line in (out / name / 'output.log').read_text(encoding='utf-8').splitlines():
|
||||
artifact = json.loads(line)
|
||||
if artifact.get('reason') != 'compiler-artifact' or artifact.get('profile', {}).get('test') is not True or not artifact.get('executable'):
|
||||
continue
|
||||
path = Path(artifact['executable']).resolve()
|
||||
source = Path(artifact['target']['src_path']).resolve()
|
||||
if not path.is_relative_to(target) or path.suffix.lower() != '.exe' or not source.is_relative_to(repo):
|
||||
raise RuntimeError('Unexpected native test artifact/source')
|
||||
package_manifest = Path(artifact['manifest_path']).resolve()
|
||||
if not package_manifest.is_relative_to(repo) or not package_manifest.is_file():
|
||||
raise RuntimeError('Test package manifest unavailable')
|
||||
admitted_inputs = []
|
||||
for input_path in (source, package_manifest):
|
||||
relative_path = input_path.relative_to(repo).as_posix()
|
||||
admitted = source_by_repo_path.get(relative_path)
|
||||
if admitted is None or record(input_path) != {key: admitted[key] for key in ('path', 'bytes', 'sha256')}:
|
||||
raise RuntimeError('Test source/manifest absent from or changed since frozen input map: ' + relative_path)
|
||||
admitted_inputs.append(admitted)
|
||||
cfg = manifest(package_manifest)
|
||||
kind = artifact['target']['kind']
|
||||
declarations = [cfg.get('lib', {})] if 'lib' in kind else [item for section in ('bin', 'test', 'example', 'bench') for item in cfg.get(section, []) if item.get('name') == artifact['target']['name']]
|
||||
if any(item.get('harness') is False for item in declarations):
|
||||
raise RuntimeError('Custom test harness requires separate reviewed listing contract')
|
||||
item = dict(binary=record(path), cargo_artifact=artifact,
|
||||
source_input=admitted_inputs[0], package_manifest=admitted_inputs[1])
|
||||
if str(path) in tests and tests[str(path)] != item:
|
||||
raise RuntimeError('Conflicting test executable attribution')
|
||||
tests[str(path)] = item
|
||||
if not tests:
|
||||
raise RuntimeError('No native test harness artifacts identified')
|
||||
save(out / 'test-artifact-inventory.json', dict(source_commit=pin, target=HOST,
|
||||
build_result=record(result_path), artifacts=[tests[p] for p in sorted(tests)],
|
||||
scope='Native test harnesses from the no-run build; rustdoc tests remain in the literal Cargo test log. Binaries are hashed, not copied.'))
|
||||
if executable:
|
||||
matches = []
|
||||
for line in (out / name / 'output.log').read_text(encoding='utf-8').splitlines():
|
||||
item = json.loads(line)
|
||||
if item.get('reason') == 'compiler-artifact' and item.get('target', {}).get('name') == executable and item.get('executable'):
|
||||
matches.append(item)
|
||||
if len(matches) != 1:
|
||||
raise RuntimeError('One exact Cargo executable artifact required')
|
||||
artifact = matches[0]
|
||||
path = Path(artifact['executable']).resolve()
|
||||
if not path.is_relative_to(target) or path.suffix.lower() != '.exe' or artifact.get('profile', {}).get('test') is not False:
|
||||
raise RuntimeError('Unexpected executable location/profile')
|
||||
preserve(path, artifact)
|
||||
preserve(RUNTIME)
|
||||
except Exception as error:
|
||||
failure = str(error)
|
||||
finally:
|
||||
errors = []
|
||||
try:
|
||||
after = inputs(repo, pin)
|
||||
save(out / 'inputs-after.json', after)
|
||||
unchanged = after == before
|
||||
if not unchanged:
|
||||
errors.append('Final source differs')
|
||||
except Exception as error:
|
||||
errors.append('Final source verification failed: ' + str(error))
|
||||
try:
|
||||
verify_environment(frozen)
|
||||
except Exception as error:
|
||||
errors.append('Final environment verification failed: ' + str(error))
|
||||
for item in outputs:
|
||||
try:
|
||||
if record(item['path']) != item:
|
||||
errors.append('Preserved artifact changed')
|
||||
except Exception as error:
|
||||
errors.append('Final artifact verification failed: ' + str(error))
|
||||
failure = '; '.join(filter(None, [failure, *errors])) or None
|
||||
save(out / 'artifact-relocation.json', dict(source_commit=pin, target=HOST, profile=args.profile, artifacts=relocations))
|
||||
passed = failure is None and unchanged and len(steps) == len(commands) and len(outputs) == 3 + len(identified)
|
||||
save(out / 'summary.json', dict(source_commit=pin, repo=str(repo), target=HOST, profile=args.profile,
|
||||
profile_scope='selected build profile only', target_mode='implicit verified rustc host',
|
||||
passed=passed, failure=failure, inputs_unchanged=unchanged, started_utc=started, finished_utc=now(),
|
||||
steps=steps, outputs=outputs, source_manifest=record(out / 'inputs-before.json'),
|
||||
environment=record(out / 'environment.json'), plan=record(out / 'plan.json'),
|
||||
relocation=record(out / 'artifact-relocation.json'),
|
||||
test_artifact_inventory=record(out / 'test-artifact-inventory.json') if (out / 'test-artifact-inventory.json').exists() else None,
|
||||
test_name_inventory=record(out / 'workspace-tests/test-name-inventory.json') if (out / 'workspace-tests/test-name-inventory.json').exists() else None,
|
||||
observed_ignores=record(out / 'workspace-tests/observed-ignores.json') if (out / 'workspace-tests/observed-ignores.json').exists() else None,
|
||||
scope='Fresh literal workspace tests and strict all-target workspace lint; identified builds only. Existing ignored tests remain ignored; native/package/server-launch acceptance is separate.'))
|
||||
print(json.dumps(dict(summary=str(out / 'summary.json'), passed=passed, failure=failure)))
|
||||
if not passed:
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,256 @@
|
||||
"""Identified command runner and shared provenance helpers; no work on import."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ENV_KEYS = ('PATH', 'CARGO_HOME', 'RUSTUP_HOME', 'RUSTUP_TOOLCHAIN',
|
||||
'CARGO_TARGET_DIR', 'CARGO_BUILD_JOBS', 'CARGO_INCREMENTAL',
|
||||
'CARGO_TARGET_X86_64_PC_WINDOWS_GNULLVM_LINKER', 'RC',
|
||||
'RUSTFLAGS', 'CARGO_ENCODED_RUSTFLAGS', 'RUSTDOCFLAGS',
|
||||
'CARGO_BUILD_TARGET', 'CARGO_PROFILE_DEV_OPT_LEVEL',
|
||||
'CARGO_PROFILE_RELEASE_OPT_LEVEL', 'RUSTC', 'RUSTDOC',
|
||||
'RUSTC_WRAPPER', 'RUSTC_WORKSPACE_WRAPPER')
|
||||
|
||||
|
||||
def now():
|
||||
return datetime.datetime.now(datetime.timezone.utc).isoformat()
|
||||
|
||||
|
||||
def record(path):
|
||||
path = Path(path).resolve()
|
||||
raw = path.read_bytes()
|
||||
return dict(path=str(path), bytes=len(raw), sha256=hashlib.sha256(raw).hexdigest())
|
||||
|
||||
|
||||
def read(path):
|
||||
return json.loads(Path(path).read_text(encoding='utf-8-sig'))
|
||||
|
||||
|
||||
def save(path, value):
|
||||
with Path(path).open('x', encoding='utf-8', newline='\n') as stream:
|
||||
json.dump(value, stream, indent=2, ensure_ascii=False, allow_nan=False)
|
||||
stream.write('\n')
|
||||
|
||||
|
||||
def git(repo, *args, input=None):
|
||||
return subprocess.check_output(
|
||||
['git', '-c', 'core.longpaths=true', '-C', str(repo), *args], input=input)
|
||||
|
||||
|
||||
def clean(repo, pin):
|
||||
if git(repo, 'rev-parse', 'HEAD').decode().strip() != pin or git(repo, 'status', '--porcelain'):
|
||||
raise RuntimeError('Exact clean committed source required')
|
||||
|
||||
|
||||
def inputs(repo, pin):
|
||||
clean(repo, pin)
|
||||
tree = {}
|
||||
for entry in git(repo, 'ls-tree', '-r', '-z', pin).split(b'\0'):
|
||||
if not entry:
|
||||
continue
|
||||
header, name = entry.split(b'\t', 1)
|
||||
mode, kind, blob = header.split()
|
||||
if kind != b'blob' or mode not in (b'100644', b'100755'):
|
||||
raise RuntimeError('Unreviewed non-regular source entry')
|
||||
tree[name.decode('utf-8')] = blob.decode('ascii')
|
||||
paths = sorted(p.decode('utf-8') for p in git(
|
||||
repo, 'ls-files', '--cached', '--others', '--exclude-standard', '-z').split(b'\0') if p)
|
||||
if paths != sorted(tree) or any('\n' in p or '\r' in p for p in paths):
|
||||
raise RuntimeError('Source/index inventory differs from pinned tree')
|
||||
# Git applies the path's clean/EOL filters. Raw hashes below independently
|
||||
# preserve the exact Windows bytes, not a falsely claimed LF byte match.
|
||||
quoted = ''.join(json.dumps(p, ensure_ascii=False) + '\n' for p in paths).encode('utf-8')
|
||||
blobs = git(repo, 'hash-object', '--stdin-paths', input=quoted).decode().splitlines()
|
||||
if blobs != [tree[p] for p in paths]:
|
||||
raise RuntimeError('Working source Git blobs differ from pinned commit')
|
||||
result = [dict(record(repo / p), repository_path=p, git_blob=tree[p]) for p in paths]
|
||||
clean(repo, pin)
|
||||
return result
|
||||
|
||||
|
||||
def environment():
|
||||
# Include build/profile overrides, never registry credentials or unrelated
|
||||
# secrets that may also use the CARGO_ prefix.
|
||||
keys = set(ENV_KEYS) | {k for k in os.environ if k.startswith(('CARGO_PROFILE_', 'CARGO_BUILD_'))
|
||||
or (k.startswith('CARGO_TARGET_') and k.endswith(('_LINKER', '_RUSTFLAGS', '_RUNNER')))}
|
||||
return {k: os.environ.get(k) for k in sorted(keys)}
|
||||
|
||||
|
||||
def verify_environment(frozen):
|
||||
if environment() != frozen['environment']:
|
||||
raise RuntimeError('Command environment differs from admitted environment')
|
||||
for item in frozen['bound_files']:
|
||||
if record(item['path']) != item:
|
||||
raise RuntimeError('Bound recipe/toolchain/runtime input changed: ' + item['path'])
|
||||
for item in frozen['cargo_configs']:
|
||||
path = Path(item['path'])
|
||||
if path.is_file() != item['present'] or (item['present'] and record(path) != item['record']):
|
||||
raise RuntimeError('Cargo configuration changed: ' + str(path))
|
||||
|
||||
|
||||
def list_tests(inventory, out, repo, pin, before, frozen):
|
||||
"""List standard native harnesses, never invoke a test body here."""
|
||||
if inventory['source_commit'] != pin or not inventory['artifacts']:
|
||||
raise RuntimeError('Same-pin nonempty test artifact inventory required')
|
||||
verify_environment(frozen)
|
||||
if inputs(repo, pin) != before:
|
||||
raise RuntimeError('Source changed before listing phase')
|
||||
phase_started = now()
|
||||
rows = []
|
||||
for index, artifact in enumerate(inventory['artifacts']):
|
||||
binary = artifact['binary']
|
||||
if record(binary['path']) != binary:
|
||||
raise RuntimeError('Test executable changed before listing')
|
||||
names = {}
|
||||
for label, flags in [('all', ['--list']), ('ignored', ['--list', '--ignored'])]:
|
||||
if record(binary['path']) != binary:
|
||||
raise RuntimeError('Test executable changed before harness listing')
|
||||
folder = out / 'test-lists' / f'{index:03d}-{label}'
|
||||
folder.mkdir(parents=True, exist_ok=False)
|
||||
argv = [binary['path'], *flags]
|
||||
with (folder / 'stdout.log').open('xb') as stdout, (folder / 'stderr.log').open('xb') as stderr:
|
||||
code = subprocess.run(argv, cwd=repo, stdout=stdout, stderr=stderr).returncode
|
||||
binary_after = record(binary['path'])
|
||||
save(folder / 'result.json', dict(source_commit=pin, argv=argv, cwd=str(repo), exit_code=code,
|
||||
binary_before=binary, binary_after=binary_after,
|
||||
source_verification_scope='Listing phase boundaries; see listing-phase-result.json before treating this inventory as admitted',
|
||||
source_inventory=record(out / 'inputs-before.json'),
|
||||
stdout=record(folder / 'stdout.log'), stderr=record(folder / 'stderr.log')))
|
||||
if code or binary_after != binary:
|
||||
raise RuntimeError('Harness listing failed or inputs changed')
|
||||
text = (folder / 'stdout.log').read_text(encoding='utf-8')
|
||||
entries = re.findall(r'^(.+): (test|benchmark)$', text, re.MULTILINE)
|
||||
totals = re.findall(r'^(\d+) tests?, (\d+) benchmarks?$', text, re.MULTILINE)
|
||||
if len(totals) != 1 or tuple(map(int, totals[0])) != (sum(kind == 'test' for _, kind in entries), sum(kind == 'benchmark' for _, kind in entries)):
|
||||
raise RuntimeError('Unrecognized test-list output; preserve raw evidence')
|
||||
names[label] = [dict(name=name, kind=kind) for name, kind in entries]
|
||||
if any(item not in names['all'] for item in names['ignored']):
|
||||
raise RuntimeError('Ignored inventory is not a subset of listed tests')
|
||||
rows.append(dict(binary=binary, cargo_target=artifact['cargo_artifact']['target'], **names))
|
||||
after = inputs(repo, pin)
|
||||
save(out / 'listing-phase-inputs-after.json', after)
|
||||
source_unchanged = after == before
|
||||
verify_environment(frozen)
|
||||
save(out / 'listing-phase-result.json', dict(source_commit=pin,
|
||||
started_utc=phase_started, finished_utc=now(), source_unchanged=source_unchanged,
|
||||
environment_unchanged=True, passed=source_unchanged, invocations=2 * len(rows),
|
||||
source_before=record(out / 'inputs-before.json'),
|
||||
source_after=record(out / 'listing-phase-inputs-after.json'),
|
||||
frozen_environment=read(out / 'context.json')['environment_receipt'],
|
||||
scope='Read-only standard harness listing; exclusive source, runtime, configuration and target ownership required. Full source/environment checks cover phase boundaries, not each listing.'))
|
||||
if not source_unchanged:
|
||||
raise RuntimeError('Source changed during listing phase')
|
||||
save(out / 'test-name-inventory.json', dict(source_commit=pin, artifacts=rows,
|
||||
listing_phase=record(out / 'listing-phase-result.json'),
|
||||
scope='Native harness listing only. Ignored tests are listed, not executed. Rustdoc test discovery remains in the actual Cargo log.'))
|
||||
return rows
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--repo', required=True, type=Path)
|
||||
parser.add_argument('--source-commit', required=True)
|
||||
parser.add_argument('--profile', required=True, choices=('dev', 'release'))
|
||||
parser.add_argument('--command-profile', required=True, choices=('none', 'test', 'dev', 'release'))
|
||||
parser.add_argument('--output', required=True, type=Path)
|
||||
parser.add_argument('--environment', required=True, type=Path)
|
||||
parser.add_argument('--test-inventory', type=Path)
|
||||
parser.add_argument('command', nargs=argparse.REMAINDER)
|
||||
args = parser.parse_args()
|
||||
command = args.command[1:] if args.command[:1] == ['--'] else args.command
|
||||
if not command:
|
||||
raise RuntimeError('Explicit command required')
|
||||
repo, out = args.repo.resolve(), args.output.resolve()
|
||||
out.mkdir(exist_ok=False)
|
||||
frozen = read(args.environment)
|
||||
context = dict(source_commit=args.source_commit, profile=args.profile,
|
||||
profile_scope='selected build profile only', command_profile=args.command_profile,
|
||||
target='x86_64-pc-windows-gnullvm', cwd=str(repo), argv=command,
|
||||
started_utc=now(), environment=environment(), runner=record(__file__),
|
||||
environment_receipt=record(args.environment))
|
||||
save(out / 'context.json', context)
|
||||
before = after = None
|
||||
failure = None
|
||||
exit_code = None
|
||||
test_inventory = None
|
||||
inventory_record = None
|
||||
try:
|
||||
verify_environment(frozen)
|
||||
before = inputs(repo, args.source_commit)
|
||||
save(out / 'inputs-before.json', before)
|
||||
(out / 'changes.patch').write_bytes(git(repo, 'diff', '--binary', 'HEAD'))
|
||||
if args.test_inventory:
|
||||
inventory_record = record(args.test_inventory)
|
||||
test_inventory = read(args.test_inventory)
|
||||
list_tests(test_inventory, out, repo, args.source_commit, before, frozen)
|
||||
for item in test_inventory['artifacts']:
|
||||
if record(item['binary']['path']) != item['binary']:
|
||||
raise RuntimeError('Test executable changed before literal test command')
|
||||
if record(args.test_inventory) != inventory_record:
|
||||
raise RuntimeError('No-run artifact inventory changed before literal test command')
|
||||
with (out / 'output.log').open('xb') as stdout, (out / 'stderr.log').open('xb') as stderr:
|
||||
exit_code = subprocess.run(command, cwd=repo, stdout=stdout, stderr=stderr).returncode
|
||||
except Exception as error:
|
||||
failure = str(error)
|
||||
finally:
|
||||
if test_inventory is not None:
|
||||
try:
|
||||
artifacts_after = []
|
||||
artifact_errors = []
|
||||
for item in test_inventory['artifacts']:
|
||||
try:
|
||||
observed = record(item['binary']['path'])
|
||||
artifacts_after.append(observed)
|
||||
if observed != item['binary']:
|
||||
artifact_errors.append('Test executable changed: ' + item['binary']['path'])
|
||||
except Exception as error:
|
||||
artifact_errors.append(str(error))
|
||||
try:
|
||||
if record(args.test_inventory) != inventory_record:
|
||||
artifact_errors.append('No-run artifact inventory changed')
|
||||
except Exception as error:
|
||||
artifact_errors.append(str(error))
|
||||
if artifact_errors:
|
||||
failure = '; '.join(filter(None, (failure, *artifact_errors)))
|
||||
save(out / 'test-artifacts-after.json', dict(source_commit=args.source_commit,
|
||||
inventory=inventory_record, artifacts=artifacts_after, errors=artifact_errors,
|
||||
unchanged=not artifact_errors))
|
||||
except Exception as error:
|
||||
failure = '; '.join(filter(None, (failure, 'Final test artifact verification failed: ' + str(error))))
|
||||
try:
|
||||
log = out / 'output.log'
|
||||
if log.exists():
|
||||
ignored = [dict(name=name, emitted_reason=reason or None) for name, reason in re.findall(
|
||||
r'^test (.+?) \.\.\. ignored(?:, (.*))?$', log.read_text(encoding='utf-8', errors='replace'), re.MULTILINE)]
|
||||
save(out / 'observed-ignores.json', dict(source_commit=args.source_commit, test_log=record(log),
|
||||
ignores=ignored, scope='Actual literal test stdout. Missing reason requires parent rationale review; no ignore is converted into a pass. Native per-artifact names remain in test-name-inventory.json.'))
|
||||
except Exception as error:
|
||||
failure = '; '.join(filter(None, (failure, 'Final ignore inventory failed: ' + str(error))))
|
||||
try:
|
||||
after = inputs(repo, args.source_commit)
|
||||
save(out / 'inputs-after.json', after)
|
||||
if before != after:
|
||||
raise RuntimeError('Source inventory changed during command')
|
||||
except Exception as error:
|
||||
failure = '; '.join(filter(None, (failure, str(error))))
|
||||
try:
|
||||
verify_environment(frozen)
|
||||
except Exception as error:
|
||||
failure = '; '.join(filter(None, (failure, str(error))))
|
||||
context.update(exit_code=exit_code, inputs_unchanged=before is not None and before == after,
|
||||
failure=failure, finished_utc=now(),
|
||||
logs=[record(out / name) for name in ('output.log', 'stderr.log') if (out / name).exists()])
|
||||
save(out / 'result.json', context)
|
||||
print(json.dumps(dict(result=str(out / 'result.json'), exit_code=exit_code, failure=failure)))
|
||||
if exit_code != 0 or failure or before is None or before != after:
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user