106 lines
6.2 KiB
Python
106 lines
6.2 KiB
Python
"""Read back the produced ZIP/stage/source without launching either executable."""
|
|
from datetime import datetime, timezone
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import stat
|
|
import sys
|
|
import zipfile
|
|
from prepare import BASE, SOURCE, VERSION, TARGET, COMMIT, SERVER_SHA, DLL_SHA, ordinary, sha, load, record, git, relative, historical_inputs, HISTORY
|
|
|
|
|
|
def main():
|
|
assert not (BASE / 'package-readback.json').exists()
|
|
preparation = load(BASE / 'preparation.json')
|
|
terminal = load(BASE / 'package2-result.json')
|
|
assert terminal['success'] is True and terminal['metadata_calls'] == 1
|
|
assert preparation['base_commit'] == COMMIT and preparation['version'] == VERSION and preparation['target'] == TARGET
|
|
name = f'ArcadiaStudio-{VERSION}-{TARGET}'
|
|
archive_path = BASE / 'packages' / (name + '.zip')
|
|
stage = BASE / 'packages' / name
|
|
assert Path(terminal['archive']).resolve() == archive_path.resolve()
|
|
assert terminal['archive_sha256'] == sha(archive_path) and terminal['archive_bytes'] == archive_path.stat().st_size
|
|
actual = {}
|
|
with zipfile.ZipFile(archive_path) as archive:
|
|
entries = archive.infolist()
|
|
assert entries and len({e.filename.casefold() for e in entries}) == len(entries)
|
|
for entry in entries:
|
|
assert entry.filename.startswith(name + '/') and not entry.is_dir()
|
|
path = relative(entry.filename[len(name) + 1:])
|
|
assert not (entry.flag_bits & 1) and not stat.S_ISLNK(entry.external_attr >> 16)
|
|
assert entry.date_time == (2000, 1, 1, 0, 0, 0)
|
|
with archive.open(entry) as stream:
|
|
checksum = hashlib.file_digest(stream, 'sha256').hexdigest()
|
|
assert sha(stage / path) == checksum and (stage / path).stat().st_size == entry.file_size
|
|
actual[path] = {'sha256': checksum, 'bytes': entry.file_size}
|
|
files = {ordinary(p).relative_to(stage).as_posix() for p in stage.rglob('*') if p.is_file()}
|
|
assert set(actual) == files
|
|
sums = {}
|
|
for line in (stage / 'SHA256SUMS.txt').read_text(encoding='utf-8').splitlines():
|
|
checksum, path = line.split(' ', 1)
|
|
assert relative(path) not in sums
|
|
sums[path] = checksum
|
|
assert set(sums) == set(actual) - {'SHA256SUMS.txt'}
|
|
assert all(actual[p]['sha256'] == h for p, h in sums.items())
|
|
companion = (archive_path.with_suffix('.zip.sha256')).read_text(encoding='utf-8').strip().split()
|
|
assert companion == [terminal['archive_sha256'], archive_path.name]
|
|
for item in preparation['artifacts']:
|
|
p = Path(item['destination']).name
|
|
assert actual[p]['sha256'] == sha(BASE / item['destination']) == item['sha256']
|
|
assert actual['arcadia-server.exe']['sha256'] == SERVER_SHA and actual['libunwind.dll']['sha256'] == DLL_SHA
|
|
assert git(SOURCE, 'rev-parse', 'HEAD').decode().strip() == preparation['reconstruction_commit']
|
|
assert not git(SOURCE, 'status', '--porcelain').strip()
|
|
for mapping in (preparation['gui_inputs'], preparation['shared_non_cargo_inputs']):
|
|
for path, checksum in mapping.items():
|
|
assert sha(SOURCE / path) == checksum
|
|
# This comparison deliberately uses the old server tar, never GUI source.
|
|
assert historical_inputs() == preparation['historical_server_inputs']
|
|
for item in load(BASE / 'evidence-manifest.json'):
|
|
assert sha(BASE / item['destination']) == item['sha256'] and (BASE / item['destination']).stat().st_size == item['bytes']
|
|
package = load(stage / 'package.json')
|
|
assert package['version'] == VERSION and package['target'] == TARGET
|
|
assert package['build_evidence']['evidence'] == load(BASE / 'build-evidence.json')
|
|
for path, checksum in package['input_sha256'].items():
|
|
assert actual[path]['sha256'] == checksum
|
|
notices = load(stage / 'dependency-notices.json')
|
|
previous = load(HISTORY / 'prior-dependency-notices.json')
|
|
def registry_key(item):
|
|
return item['name'], item['version'], item['source'], item['registry_checksum']
|
|
collected = {registry_key(x): x for x in notices['packages'] if x['source']}
|
|
covered = 0
|
|
for old in previous['packages']:
|
|
if not old['source']:
|
|
continue
|
|
current = collected[registry_key(old)]
|
|
assert old['license'] == current['license']
|
|
assert set(old['features']).issubset(current['features'])
|
|
# Exact recorded notice files must remain in the actual ZIP. Current
|
|
# metadata does not substitute for the historical server's provenance.
|
|
new_notices = {(n['path'], n['sha256']) for n in current['notices']}
|
|
for item in old['notices']:
|
|
assert (item['path'], item['sha256']) in new_notices
|
|
assert actual[item['path']]['sha256'] == item['sha256']
|
|
assert old['embedded_fonts'] == current['embedded_fonts']
|
|
for font in old['embedded_fonts']:
|
|
for item in font['notices']:
|
|
assert actual[item['path']]['sha256'] == item['sha256']
|
|
covered += 1
|
|
assert covered == preparation['historical_registry_count']
|
|
docs = load(stage / 'documentation-check.json')
|
|
result = {'atUtc': datetime.now(timezone.utc).isoformat(), 'status': 'PASS', 'archive_sha256': sha(archive_path),
|
|
'archive_bytes': archive_path.stat().st_size, 'file_count': len(actual), 'files': actual,
|
|
'manifest_entries': len(sums), 'source_commit': preparation['reconstruction_commit'],
|
|
'gui_inputs_verified': len(preparation['gui_inputs']), 'historical_server_inputs_verified': len(preparation['historical_server_inputs']),
|
|
'historical_server_comparison': preparation['historical_server_comparison'],
|
|
'historical_registry_packages_covered': covered, 'notice_roots': notices['root_packages'], 'notice_package_count': len(notices['packages']),
|
|
'documentation_check': docs, 'binary_imports': package['imports'],
|
|
'scope': 'Producer packaging readback only; no application launch or independent/native acceptance.'}
|
|
record(BASE / 'package-readback.json', result)
|
|
print(json.dumps({k: v for k, v in result.items() if k != 'files'}, ensure_ascii=False, indent=2))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
sys.stderr.reconfigure(encoding='utf-8')
|
|
main()
|