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

90 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""Remove/purge the tested local package in the dedicated validation VM."""
import argparse
import hashlib
import json
import os
from pathlib import Path
import subprocess
def sha(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--smoke', type=Path, required=True)
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
assert os.getuid() != 0 and Path.home() == Path('/home/docview')
smoke = json.loads((args.smoke / 'report.json').read_text())
assert smoke['success'] and smoke['smokeConditions'] == 12
for name, digest in smoke['executables'].items():
assert sha(Path('/opt/docview/bin') / name) == digest
args.output.mkdir(parents=True, exist_ok=False)
record = {'success': False, 'runnerSha256': sha(Path(__file__)),
'smokeReportSha256': sha(args.smoke / 'report.json'), 'commands': []}
def run(name, command, allowed=(0,)):
result = subprocess.run(command, capture_output=True, text=True, timeout=180)
log = args.output / (name + '.log')
log.write_text(result.stdout + result.stderr)
record['commands'].append({'name': name, 'command': command, 'exitCode': result.returncode,
'logSha256': sha(log)})
assert result.returncode in allowed, name
return result.stdout
def profiles(label):
return run(label, ['sudo', 'cat', '/sys/kernel/security/apparmor/profiles'])
bundled = 'docview-bundled-qtwebengine '
sentinels = []
try:
before = profiles('profiles-installed')
assert bundled in before and 'docview-qtwebengine ' in before
record['installedPackage'] = run('package-installed', ['dpkg-query', '-W',
'-f=${Package} ${Version} ${db:Status-Status} ${Installed-Size}\n', 'docview']).strip()
assert ' installed ' in record['installedPackage']
assert run('userns-before', ['sysctl', '-n', 'kernel.apparmor_restrict_unprivileged_userns']).strip() == '1'
for relative in ['.config/docview', '.local/state/docview', '.local/share/docview',
'validation/package-user-document']:
directory = Path.home() / relative
directory.mkdir(parents=True, exist_ok=True)
path = directory / 'deb-preservation-probe.txt'
with path.open('x') as stream:
stream.write('DocView package removal preservation probe — 日本語\n')
sentinels.append({'path': str(path), 'sha256': sha(path)})
run('apt-remove', ['sudo', 'env', 'DEBIAN_FRONTEND=noninteractive', 'apt-get', '-y', 'remove', 'docview'])
after = profiles('profiles-removed')
assert bundled not in after and 'docview-qtwebengine ' in after
for name in ['/opt/docview', '/usr/bin/docview', '/usr/share/applications/docview.desktop']:
assert not Path(name).exists(), name
assert Path('/etc/apparmor.d/docview').is_file()
assert all(sha(Path(row['path'])) == row['sha256'] for row in sentinels)
record['remove'] = {'payloadRemoved': True, 'profileUnloaded': True,
'conffilePreserved': True, 'userProbesPreserved': True}
run('apt-purge', ['sudo', 'env', 'DEBIAN_FRONTEND=noninteractive', 'apt-get', '-y', 'purge', 'docview'])
assert not Path('/etc/apparmor.d/docview').exists()
assert all(sha(Path(row['path'])) == row['sha256'] for row in sentinels)
assert run('userns-after', ['sysctl', '-n', 'kernel.apparmor_restrict_unprivileged_userns']).strip() == '1'
assert Path('/opt/docview-qt/6.11.2/gcc_64/bin/qtpaths').is_file()
assert Path('/opt/docview-deps/lib/libqpdf.so.30').is_file()
assert Path('/home/docview/docview-install/bin/docview').is_file()
assert Path('/home/docview/docview-build/docview').is_file()
record['purge'] = {'conffileRemoved': True, 'userProbesPreserved': True}
record.update(success=True, userProbes=sentinels, originalDevelopmentPrefixesRestored=True,
kernelUserNamespaceRestrictionUnchanged=True)
finally:
# Delete only these newly-created probes after preserving their hashes.
for row in sentinels:
path = Path(row['path'])
if path.is_file() and sha(path) == row['sha256']:
path.unlink()
(args.output / 'report.json').write_text(json.dumps(record, indent=2, sort_keys=True) + '\n')
print(json.dumps({key: record[key] for key in ('success', 'remove', 'purge')}))
if __name__ == '__main__':
main()