#!/usr/bin/env python3 """Run the opt-in production GUI T20 interaction workload and pin its evidence.""" import argparse import hashlib import json import os from pathlib import Path import platform import subprocess import sys import time def hashed(path, label): with path.open("rb") as stream: digest = hashlib.file_digest(stream, "sha256").hexdigest() return {"path": label, "size": path.stat().st_size, "sha256": digest} def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--binary", type=Path, required=True) parser.add_argument("--output", type=Path, required=True) args = parser.parse_args() source = Path(__file__).resolve().parents[1] binary = args.binary.resolve() output = args.output.resolve() if not binary.is_file(): parser.error("--binary must be an existing test_app executable") if output.exists(): parser.error("--output must be a new directory to preserve earlier evidence") if os.environ.get("QTWEBENGINE_DISABLE_SANDBOX") or any(x in os.environ.get("QTWEBENGINE_CHROMIUM_FLAGS", "") for x in ("--no-sandbox", "--disable-setuid-sandbox")): parser.error("Sandbox disabling is not allowed for this measurement") output.mkdir(parents=True) suffix = ".exe" if platform.system() == "Windows" else "" binaries = [binary, binary.parent / ("docview-pdf-worker" + suffix), binary.parent / ("docview-archive-worker" + suffix)] app = binary.parent / ("docview" + suffix) if app.exists(): binaries.append(app) # Recorded for comparison; the tested shell is test_app. pdfium = source / ".deps/pdfium/lib/libpdfium.so" if pdfium.exists(): binaries.append(pdfium) source_files = [source / "CMakeLists.txt", source / "resources.qrc", source / "tests/test_app.cpp", source / "tests/interaction_measurement.h", Path(__file__).resolve()] for directory in ("src", "qml", "resources"): source_files.extend(p for p in (source / directory).rglob("*") if p.is_file() and p.suffix in (".cpp", ".h", ".qml", ".js", ".toml")) source_files = sorted(set(source_files)) before_binaries = [hashed(p, p.name) for p in binaries] before_sources = [hashed(p, str(p.relative_to(source))) for p in source_files] environment = os.environ.copy() environment["DOCVIEW_INTERACTION_OUTPUT"] = str(output) command = [str(binary), "measureInteractionWorkload"] start = time.monotonic() try: run = subprocess.run(command, env=environment, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=180, check=False) log, code = run.stdout, run.returncode except subprocess.TimeoutExpired as error: log, code = error.stdout or b"", 124 (output / "test.log").write_bytes(log) after_binaries = [hashed(p, p.name) for p in binaries] after_sources = [hashed(p, str(p.relative_to(source))) for p in source_files] measurement_path = output / "measurement.json" measurement = json.loads(measurement_path.read_text()) if measurement_path.exists() else {} cpu = "" if Path("/proc/cpuinfo").exists(): cpu = next((line.partition(":")[2].strip() for line in Path("/proc/cpuinfo").read_text().splitlines() if line.startswith("model name")), "") mem = "" if Path("/proc/meminfo").exists(): mem = next((line.partition(":")[2].strip() for line in Path("/proc/meminfo").read_text().splitlines() if line.startswith("MemTotal:")), "") stable = before_binaries == after_binaries and before_sources == after_sources record = {"schemaVersion": 1, "command": [binary.name, "measureInteractionWorkload"], "testedExecutable": binary.name, "buildDirectory": binary.parent.name, "exitCode": code, "elapsedSeconds": time.monotonic() - start, "success": code == 0 and measurement.get("success") is True and stable, "binaryAndSourceHashesStableDuringRun": stable, "binaries": before_binaries, "sources": before_sources, "host": {"platform": platform.platform(), "cpu": cpu, "logicalCpuCount": os.cpu_count(), "memTotal": mem}, "measurement": hashed(measurement_path, "measurement.json") if measurement_path.exists() else None, "limitations": ["Development-host synthetic workload, not the provisional reference machine.", "Xvfb/software rendering when selected by the caller; no native GPU or physical scanout acceptance.", "frameSwapped is a presentation-queue notification; Web is not correlated to Chromium frame IDs.", "GUI timer gaps include host scheduling and instrumentation; they are not a direct measurement of exclusively application-caused stalls.", "No OS cache flush. No Windows/Ubuntu acceptance inferred.", "Normal app CTest deliberately skips this opt-in slot; this dedicated run executes it."]} (output / "record.json").write_text(json.dumps(record, indent=2, ensure_ascii=False) + "\n") print(json.dumps({"success": record["success"], "output": str(output), "exitCode": code}, ensure_ascii=False)) return 0 if record["success"] else 1 if __name__ == "__main__": sys.exit(main())