#!/usr/bin/env python3 """Verify the URL-fix evidence and publish a current index with explicit history.""" from datetime import datetime, timezone import hashlib import json from pathlib import Path import xml.etree.ElementTree as ET ROOT = Path(__file__).resolve().parents[2] RESULTS = ROOT / 'tests/results/portal' def sha(path): with path.open('rb') as stream: return hashlib.file_digest(stream, 'sha256').hexdigest() def read(name): return json.loads((RESULTS / name).read_text()) def main(): builds = {name: read(name + '-build-record.json') for name in ('arch', 'ubuntu')} current_sources = {str(path.relative_to(ROOT)): sha(path) for directory in ('src', 'qml', 'cmake', 'resources') for path in (ROOT / directory).rglob('*') if path.is_file() and '__pycache__' not in path.parts} current_sources.update({name: sha(ROOT / name) for name in ('CMakeLists.txt', 'resources.qrc')}) for name, value in builds.items(): assert all(value['sourceSha256'].get(path) == digest for path, digest in current_sources.items()) for path in ('tests/test_web_qml.cpp', 'tests/webqml/tst_reader.qml'): assert value['sourceSha256'][path] == sha(ROOT / path) ctest = RESULTS / (name + '-ctest') assert sha(ctest / 'tests.xml') == value['ctest']['junitSha256'] assert sha(ctest / 'LastTest.log') == value['ctest']['logSha256'] cases = ET.parse(ctest / 'tests.xml').findall('.//testcase') assert len(cases) == 22 and all(row.find('failure') is None and row.find('skipped') is None for row in cases) assert all(sha(ROOT / 'build' / name) == digest for name, digest in builds['arch']['binaries'].items()) package = read('ubuntu-package/report.json') repeated = read('ubuntu-package/repeat-report.json') verified = read('ubuntu-package/verification.json') archive = ROOT / 'build-ubuntu-vm/packages' / package['archive'] assert package['success'] and repeated['success'] and verified['success'] assert package['archiveSha256'] == repeated['archiveSha256'] == sha(archive) assert verified['archiveSha256'] == package['archiveSha256'] assert verified['allSizesModesAndHashesMatch'] and verified['rootOwnership'] assert verified['filesVerified'] == package['payloadFiles'] assert package['packagerSha256'] == sha(ROOT / 'tools/package_ubuntu.py') assert package['executableSha256'] == builds['ubuntu']['installedBinaries'] assert package['manifestSha256'] == sha(RESULTS / 'ubuntu-package/package-manifest.json') portal = {} for platform, directory in {'x11': 'portal-x11-final', 'wayland': 'portal-wayland-fixed-keymap'}.items(): report = read(directory + '/report.json') assert report['success'] and report['executablesUnchanged'] and not report['remainingTaggedChildren'] assert report['executables'] == package['executableSha256'] assert report['packageManifestSha256'] == package['manifestSha256'] assert report['runnerSha256'] == sha(RESULTS / directory / 'runner-at-start.py') assert report['portalResponses'] == ['2', '0', '0', '0', '0'] assert [row['case'] for row in report['cases']] == ['cancel', 'pdf', 'html', 'htmlzip', 'epub'] assert all(row['focusRestored'] for row in report['cases']) portal[platform] = {key: report[key] for key in ('success', 'platform', 'executables', 'portalResponses', 'cases', 'scope')} lifecycle = {phase: read('package-vm/' + phase + '/report.json') for phase in ('install', 'smoke', 'remove')} assert all(row['success'] and row['archiveSha256'] == package['archiveSha256'] for row in lifecycle.values()) assert lifecycle['install']['executables'] == package['executableSha256'] assert lifecycle['install']['installationMode'] == 'upgrade' assert lifecycle['install']['runnerSha256'] == sha(ROOT / 'tests/ubuntu_vm/clean_package.py') assert lifecycle['smoke']['smokeConditions'] == 12 assert lifecycle['remove']['payloadRemoved'] and lifecycle['remove']['kernelRestrictionUnchanged'] performance = read('performance-suite/summary.json') assert performance['success'] and len(performance['cases']) == 10 assert all(builds['arch']['binaries'][name] == digest for name, digest in performance['buildSha256'].items()) for row in performance['cases']: assert sha(RESULTS / 'performance-suite' / row['record']) == row['recordSha256'] assert performance['interactionRecordSha256'] == sha(RESULTS / 'performance-suite/interaction/record.json') assert performance['stressRecordSha256'] == sha(RESULTS / 'performance-suite/stress/report.json') prior = read('prior-validation-record.json') record = { 'schemaVersion': 2, 'recordedAtUtc': datetime.now(timezone.utc).isoformat(), 'scope': 'Current URL-fix build: Arch/Ubuntu full CTest, Ubuntu package and XDG/GTK file chooser integration; Arch ten-condition performance, interaction and 1 GiB PDF stress', 'ctestSkipScope': 'All 22 CTest groups pass. Each App group explicitly skips its opt-in performance case; Ubuntu Python 3.12 skips 5 zstd provenance cases. These are not counted as executed acceptance tests.', 'goalComplete': False, 'sourceSha256': current_sources, 'validationToolsSha256': {name: sha(ROOT / name) for name in ( 'tests/portal/record_build.py', 'tests/portal/record_validation.py', 'tests/portal/run.py', 'tests/portal/run_performance.py', 'tests/portal/summarize_performance.py', 'tests/ubuntu_vm/clean_package.py')}, 'executables': {name: builds['arch']['binaries'][name] for name in package['executableSha256']}, 'builds': builds, 'ubuntuPackage': package, 'portal': portal, 'packageLifecycle': lifecycle, 'performance': performance, 'historicalEvidence': { 'record': 'tests/results/portal/prior-validation-record.json', 'sha256': sha(RESULTS / 'prior-validation-record.json'), 'changedCompiledSources': [name for name, digest in current_sources.items() if prior['sourceSha256'].get(name) != digest], 'unchangedArchWorkers': [name for name in ('docview-pdf-worker', 'docview-archive-worker') if prior['executables'][name] == builds['arch']['binaries'][name]], 'scope': 'Prior full-app performance, GPU, IME, high-density PDF and package measurements are versioned history. Current ten-condition performance and 1 GiB stress are recorded separately above.', }, 'remainingAcceptance': [ 'Windows 11 native MSVC/Qt build, LPAC/pipe/PE execution and installer', 'Target-OS physical GPU/display/input, reference hardware and long-duration acceptance', 'Human G-RENDER reference-image approval', 'Complete notices/corresponding sources, public license and release distribution decision', ], 'evidenceSha256': {str(path.relative_to(ROOT)): sha(path) for path in sorted(RESULTS.rglob('*')) if path.is_file() and path.suffix != '.pyc' and path != RESULTS / 'record.json'}, } encoded = json.dumps(record, ensure_ascii=False, indent=2) + '\n' (RESULTS / 'record.json').write_text(encoded) (ROOT / 'docs/validation-record.json').write_text(encoded) print(json.dumps({'archCtestGroups': 22, 'ubuntuCtestGroups': 22, 'portalCases': 10, 'packageSmokeConditions': 12, 'performanceConditions': 10, 'goalComplete': False})) if __name__ == '__main__': main()