Files
docview/tests/test_dependency_provenance_qt.py
T
2026-09-21 13:41:40 +09:00

137 lines
6.9 KiB
Python

#!/usr/bin/env python3
"""Offline provenance boundary tests for the partial Qt resource notice set."""
import json
import importlib.util
from pathlib import Path
import shutil
import sys
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'tools'))
import record_dependency_provenance as provenance
class QtNoticeProvenanceTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory(prefix='docview-qt-provenance-')
self.addCleanup(self.temporary.cleanup)
self.root = Path(self.temporary.name)
self.project, self.payload = self.root / 'project', self.root / 'payload'
self.original = self.project / 'resources/licenses/qt-embedded-notices'
shutil.copytree(ROOT / 'resources/licenses/qt-embedded-notices', self.original)
self.relative = 'licenses/qt6-webengine/embedded-resource-notices'
self.directory = self.payload / 'share/doc/docview/third-party' / self.relative
shutil.copytree(self.original, self.directory)
raw = (self.original / 'sources.json').read_bytes()
self.metadata = json.loads(raw)
rows = [{'path': self.relative + '/' + row['name'], 'size': row['size'], 'sha256': row['sha256'],
'origin': 'reviewed-installed-DataPack-resource-comment',
'sourceResources': row['sourceResources'], 'collectionScope': self.metadata['scope']}
for row in self.metadata['files']]
self.component = {'version': '6.11.2-1', 'source': {'status': 'not-collected'}, 'licenseTexts': rows,
'embeddedResourceNotices': {'status': 'collected-reviewed-resource-subset',
'metadataPath': self.relative + '/sources.json', 'metadataSha256': provenance.digest(raw),
'noticeCount': 7, 'sourceResourceCount': 13, 'runtimeDistribution': 'system-not-bundled',
'completeChromiumNotices': False, 'scope': self.metadata['scope']}}
self.manifest = {'host': {'qtVersion': '6.11.2'}, 'components': {'qt6-webengine': self.component},
'systemFiles': self.metadata['dataPacks']}
def record(self):
licenses = [provenance.license_evidence(self.payload, row) for row in self.component['licenseTexts']]
return provenance.qt_embedded_notice_record(self.payload, self.manifest, licenses, self.project)
def test_new_notices_metadata_inventory_and_repo_are_bound(self):
result = self.record()
self.assertEqual(result['status'], 'repository-original-and-package-matched')
self.assertEqual((result['noticeCount'], result['sourceResourceCount']), (7, 13))
self.assertIs(result['completeChromiumNotices'], False)
self.assertEqual(result['sourceCollectionStatus'], 'not-collected')
self.assertEqual(result['runtimeDistribution'], 'system-not-bundled')
self.assertEqual(result['metadata']['sha256'], result['repositoryMetadata']['sha256'])
self.assertEqual(len(result['files']), 7)
self.assertEqual(len(result['dataPacks']), 2)
def test_legacy_without_any_embedded_payload_remains_supported(self):
shutil.rmtree(self.directory)
self.component['licenseTexts'] = []
del self.component['embeddedResourceNotices']
result = self.record()
self.assertEqual(result['status'], 'not-in-this-package')
self.assertIs(result['completeChromiumNotices'], False)
def test_payload_without_manifest_metadata_is_not_legacy(self):
del self.component['embeddedResourceNotices']
with self.assertRaisesRegex(ValueError, 'lacks manifest metadata'):
self.record()
def test_changed_packaged_metadata_rejected(self):
path = self.directory / 'sources.json'
path.write_bytes(path.read_bytes() + b'\n')
with self.assertRaisesRegex(ValueError, 'metadata differs from repository'):
self.record()
def test_changed_notice_with_updated_outer_hash_still_rejected(self):
row = self.component['licenseTexts'][0]
data = b'replacement with self-consistent package inventory'
(self.payload / 'share/doc/docview/third-party' / row['path']).write_bytes(data)
row.update(size=len(data), sha256=provenance.digest(data))
with self.assertRaisesRegex(ValueError, 'differs from original'):
self.record()
def test_changed_original_notice_rejected(self):
(self.original / self.metadata['files'][0]['name']).write_bytes(b'changed repository original')
with self.assertRaisesRegex(ValueError, 'digest/size mismatch'):
self.record()
def test_changed_datapack_identity_rejected(self):
self.manifest['systemFiles'][0]['sha256'] = '0' * 64
with self.assertRaisesRegex(ValueError, 'DataPack inventory mismatch'):
self.record()
def test_omitted_or_duplicate_license_inventory_rejected(self):
original = self.component['licenseTexts'][:]
for rows in (original[:-1], original + [original[0]]):
self.component['licenseTexts'] = rows
with self.subTest(count=len(rows)), self.assertRaisesRegex(ValueError, 'license inventory differs'):
self.record()
def test_forged_resource_source_not_lost_in_generic_license_hash(self):
self.component['licenseTexts'][0]['sourceResources'][0]['resourceId'] += 1
with self.assertRaisesRegex(ValueError, 'resource provenance differs'):
self.record()
def test_manifest_cannot_claim_full_chromium_or_other_pin(self):
original = dict(self.component['embeddedResourceNotices'])
for key, value in [('completeChromiumNotices', True), ('metadataSha256', '0' * 64),
('noticeCount', 8), ('runtimeDistribution', 'bundled')]:
self.component['embeddedResourceNotices'] = {**original, key: value}
with self.subTest(key=key), self.assertRaisesRegex(ValueError, 'manifest metadata mismatch'):
self.record()
def test_partial_notices_cannot_change_source_collection_status(self):
self.component['source']['status'] = 'complete'
with self.assertRaisesRegex(ValueError, 'cannot claim complete source'):
self.record()
def test_unlisted_file_rejected(self):
(self.directory / 'extra.txt').write_bytes(b'unreviewed notice')
with self.assertRaisesRegex(ValueError, 'payload contents'):
self.record()
def load_tests(loader, tests, pattern):
# Keep the historical fixture tests intact while exposing one portable
# suite for current Qt notices and the existing provenance boundaries.
path = ROOT / 'tests/test_dependency_provenance.py'
spec = importlib.util.spec_from_file_location('docview_legacy_provenance_tests', path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
tests.addTests(loader.loadTestsFromModule(module))
return tests
if __name__ == '__main__':
unittest.main(verbosity=2)