`examples/<name>/` was a tested surface in name only. The source-quality job had no examples check at all, and scripts/smoke_examples.sh accepted any process that had not printed one of its error markers: a project that died during startup, exited after a single frame, or crashed without a marker was reported as PASS. Nothing stopped a malformed or unregistered example from reaching dev, which is how 14 examples arrived without READMEs and 20 were never registered in the overview table. Source side - scripts/check_examples.py classifies every directory under examples/ as either runnable (main.nut plus a config.nut that assigns the engine `config` table) or as a reviewed non-runnable sample listed in NON_RUNNABLE_EXAMPLES with a reason. Both kinds must own a README.md and appear in examples/README.md. Stale exemptions and overview links to missing directories fail as well. - `make check/examples` and a new source-quality CI step run the gate. - scripts/tests/test_check_examples.py covers it and pins each failure mode against fixtures. Runtime side - scripts/smoke_examples.sh now enforces liveness, not just marker absence: every runnable example must still be running after MIN_RUN_SECONDS (default 2s; CI passes 2s), and a process that dies inside that window fails whatever its exit code. A clean self-exit after the window passes with an explicit note instead of being silently accepted as a pass. - The Linux CI step sets MIN_RUN_SECONDS=2 and keeps RUN_SECONDS=6, so the job's wall clock is unchanged (13.7 min of its 20 min budget on dev). Windows / GBK - scripts/utf8_stdio.py re-encodes stdout/stderr as UTF-8 and every scripts/*.py program calls it as the first statement of its __main__ block. On a Chinese Windows a redirected stream falls back to the ANSI code page (cp936), and printing a character that code page cannot encode (arrows, superscripts, bullet separators, emoji - all common in this repository) raised UnicodeEncodeError and turned a checker into a traceback. - The mirror image is input: read_text() without encoding= decoded UTF-8 repository text as cp936 and raised UnicodeDecodeError. - scripts/tests/test_utf8_stdio.py enforces both conventions for every script and test module; text file I/O and child-process decoding now pass encoding="utf-8" explicitly. - Unit-test invocations run with `python3 -X utf8` so the runner's own output never follows the host code page. Violations the new gates found on top of dev, fixed here: 14 examples shipped without a README (fluid3d, mesh-impact-lab, mesh-modifier-lab, spline-tube-lab on the earlier dev tip, plus pcg-depth-of-field, pcg-detail-overwrite, pcg-layer-culling, pcg-location-system, pcg-runtime-orchestration, pcg-scene-player-culling, pcg-snow-wind, pcg-thunder-strike, pcg-ui-scaler and pcg-underwater added by #397), 20 were missing from the overview table (those 14 plus archspace-editor, geometry-stroke-lab, interior-mapping, layered-animation, triplanar-decal, terrain-stamping), and scripts/export_meshret_onnx.py had no UTF-8 stdio guard. Verified locally: `python -m ruff check scripts` (All checks passed); scripts/check_test_manifest.py; scripts/check_quality_metadata.py; scripts/check_architecture_contracts.py --base HEAD; scripts/check_nodiscard.py; `make -n check/examples`; scripts/check_examples.py (125 examples: 118 runnable, 7 reviewed non-runnable); and `python -X utf8 -m unittest discover -s scripts/tests -p "test_*.py"` (155 tests, OK). The runtime half of the contract needs a Linux headless build, so it is verified by the CI examples smoke step rather than locally.
49 lines
2.1 KiB
Python
49 lines
2.1 KiB
Python
"""Run the real snow example with deterministic interaction regression checks."""
|
|
import argparse
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import utf8_stdio
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--eve", type=Path, required=True)
|
|
parser.add_argument("--output", type=Path, default=Path("build/snow-regression"))
|
|
parser.add_argument("--validation", action="store_true")
|
|
args = parser.parse_args()
|
|
root = Path(__file__).resolve().parents[1]
|
|
output = args.output.resolve()
|
|
output.mkdir(parents=True, exist_ok=True)
|
|
for helper in (root / "examples/snow").glob("*.nut"):
|
|
if helper.name not in ("main.nut", "config.nut"):
|
|
(output / helper.name).write_text(helper.read_text(encoding="utf-8"), encoding="utf-8")
|
|
source = (root / "examples/snow/main.nut").read_text(encoding="utf-8")
|
|
fixture = (root / "test/snow_example.nut").read_text(encoding="utf-8")
|
|
(output / "main.nut").write_text(source + "\n" + fixture, encoding="utf-8")
|
|
(output / "config.nut").write_text(
|
|
'config = { width=960 height=640 title="snow regression" hotReload=false };\n',
|
|
encoding="utf-8",
|
|
)
|
|
env = os.environ.copy()
|
|
env["EVENGINE_VULKAN_VALIDATION"] = "1" if args.validation else "0"
|
|
with (output / "run.log").open("w", encoding="utf-8") as log:
|
|
result = subprocess.run(
|
|
[str(args.eve.resolve()), "run", str(output)], cwd=root, env=env,
|
|
stdout=log, stderr=subprocess.STDOUT, timeout=120,
|
|
)
|
|
text = (output / "run.log").read_text(encoding="utf-8", errors="replace")
|
|
failures = [line for line in text.splitlines() if any(
|
|
marker in line for marker in ("SNOW_FAIL", "frame error:", "[ERROR: Validation]"))]
|
|
if result.returncode or "SNOW_PASS:" not in text or failures:
|
|
print("Snow regression failed:", result.returncode, *failures[:12], sep="\n")
|
|
print("Log:", output / "run.log")
|
|
return 1
|
|
print("Snow regression passed; captures and log:", output)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
utf8_stdio.enable_utf8_stdio()
|
|
raise SystemExit(main())
|