#!/usr/bin/env python3 """Bind a notice-only Ubuntu package update to the existing tested executables.""" from datetime import datetime, timezone import hashlib import json from pathlib import Path ROOT = Path(__file__).resolve().parents[2] RESULTS = ROOT / 'tests/results/qt-notice-supplement' 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(): prior = read('prior-validation-record.json') assert sha(RESULTS / 'prior-validation-record.json') == '25bc879e35e02b70f7bd1cf2afca293896afb247163fa5b1f81020ec5bd3359b' for name, digest in prior['evidenceSha256'].items(): assert sha(ROOT / name) == digest, name assert sha(RESULTS / 'prior-packager.py') == prior['ubuntuPackage']['packagerSha256'] assert sha(RESULTS / 'prior-record-validation.py') == prior['validationToolsSha256']['tests/portal/record_validation.py'] 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')}) assert current_sources == prior['sourceSha256'] for name, digest in prior['builds']['arch']['binaries'].items(): assert sha(ROOT / 'build' / name) == digest supplement = read('collection/report.json') repeat = read('collection-repeat/report.json') assert supplement == repeat and supplement['success'] assert len(supplement['files']) == 6 and len(supplement['notices']) == 2 assert supplement['collectorSha256'] == sha(ROOT / 'tests/source_archives/collect_qt_sdk_supplement.py') assert supplement['baseNoticeReportSha256'] == sha(ROOT / 'tests/results/source-archives/qt-sdk-notices-final/report.json') for row in supplement['files']: assert sha(RESULTS / 'collection' / row['file']) == row['sha256'] assert sha(RESULTS / 'collection-repeat' / row['file']) == row['sha256'] package = read('package/report.json') repeated = read('package/repeat-report.json') verified = read('package/verification.json') manifest = read('package/package-manifest.json') assert package['success'] and repeated['success'] and verified['success'] assert package['archiveSha256'] == repeated['archiveSha256'] == verified['archiveSha256'] assert package['archiveSha256'] == sha(ROOT / 'build-ubuntu-vm/packages' / package['archive']) assert package['manifestSha256'] == verified['packageManifestSha256'] == sha(RESULTS / 'package/package-manifest.json') assert package['packagerSha256'] == sha(ROOT / 'tools/package_ubuntu.py') assert package['collectorSha256'] == sha(ROOT / 'tools/collect_ubuntu_validation_runtime.py') assert package['runtimeRecordSha256'] == sha(RESULTS / 'package/runtime/runtime.json') assert package['executableSha256'] == prior['ubuntuPackage']['executableSha256'] assert package['executableSha256'] == prior['builds']['ubuntu']['installedBinaries'] assert verified['filesVerified'] == package['payloadFiles'] == len(manifest['files']) + 1 assert verified['allSizesModesAndHashesMatch'] and verified['rootOwnership'] assert package['sdkSupplementReportSha256'] == sha(RESULTS / 'collection/report.json') assert manifest['sdkSupplementReportSha256'] == package['sdkSupplementReportSha256'] old_manifest = json.loads((ROOT / 'tests/results/portal/ubuntu-package/package-manifest.json').read_text()) old_rows = {r['path']: r for r in old_manifest['files']} new_rows = {r['path']: r for r in manifest['files']} added = sorted(new_rows.keys() - old_rows.keys()) removed = sorted(old_rows.keys() - new_rows.keys()) changed = sorted(name for name in old_rows.keys() & new_rows.keys() if old_rows[name] != new_rows[name]) assert not removed and len(added) == 7 assert all(name.startswith('opt/docview/share/doc/docview/third-party/qt-sdk-supplement/') for name in added) assert changed == ['DEBIAN/control', 'opt/docview/share/doc/docview/UBUNTU-PACKAGE.txt'] for row in supplement['files']: name = 'opt/docview/share/doc/docview/third-party/qt-sdk-supplement/' + row['file'] assert new_rows[name]['sha256'] == row['sha256'] and new_rows[name]['size'] == row['bytes'] 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()) for phase, report in lifecycle.items(): assert report['runnerSha256'] == sha(ROOT / 'tests/ubuntu_vm/clean_package.py') for command in report['commands']: assert command['exitCode'] == 0 assert sha(RESULTS / 'package-vm' / phase / (command['name'] + '.log')) == command['logSha256'] assert lifecycle['install']['executables'] == package['executableSha256'] assert lifecycle['install']['installationMode'] == 'fresh-install' assert lifecycle['install']['installedFilesVerified'] == len(manifest['files']) - 4 assert lifecycle['smoke']['smokeConditions'] == 12 and lifecycle['smoke']['executableBytesUnchanged'] assert lifecycle['remove']['payloadRemoved'] and lifecycle['remove']['kernelRestrictionUnchanged'] assert len(lifecycle['remove']['userProbesPreserved']) == 4 for os_name in ('arch', 'ubuntu'): log = (RESULTS / ('package-tests-' + os_name + '.log')).read_text() assert 'Ran 8 tests' in log and log.rstrip().endswith('OK') assert read('shutdown.json')['allGuestsStopped'] record = {**prior, 'schemaVersion': 3, 'recordedAtUtc': datetime.now(timezone.utc).isoformat(), 'scope': 'Notice-only Ubuntu validation3 package; all compiled product sources and executables unchanged ' 'from the URL-fix record. Eight packaging tests on Arch and Ubuntu, two identical deb builds, ' 'archive verification and 12 X11/Wayland launches plus install/remove/purge on the existing SDK-free VM.', 'ubuntuPackage': package, 'packageLifecycle': lifecycle, 'noticeSupplement': supplement, 'packageChanges': {'added': added, 'removed': removed, 'changed': changed, 'selfManifestAlsoChanged': True, 'allOtherPayloadIdentical': True}, 'previousValidation': {'record': 'tests/results/qt-notice-supplement/prior-validation-record.json', 'sha256': sha(RESULTS / 'prior-validation-record.json'), 'scope': 'All 22 CTest groups per OS, native portal cases and performance stay bound ' 'to their original runs. Product bytes are unchanged. Portal ran on package ' 'validation2; its OS integration was not rerun for notice-only validation3.'}, 'packageVmScope': 'Existing dedicated Ubuntu 24.04 VM with no SDK/build tree. Test helpers already ' 'installed; this is not a newly created OS. Prior validation1 fresh-OS evidence is historical.', 'validationToolsSha256': {**prior['validationToolsSha256'], **{name: sha(ROOT / name) for name in ( 'tools/package_ubuntu.py', 'tools/verify_ubuntu_package.py', 'tests/test_ubuntu_package.py', 'tests/source_archives/collect_qt_sdk_supplement.py', 'tests/source_archives/record_qt_supplement.py')}}, 'evidenceSha256': {**prior['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'}}, 'goalComplete': False} 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({'additionalNoticeFiles': 2, 'packageFiles': package['payloadFiles'], 'smokeConditions': 12, 'compiledSourcesUnchanged': True, 'goalComplete': False})) if __name__ == '__main__': main()