initial commit
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Boundaries of Ubuntu staging and preservation of launcher arguments."""
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / 'tools'))
|
||||
import package_ubuntu as package
|
||||
|
||||
|
||||
class UbuntuPackageTests(unittest.TestCase):
|
||||
def supplement_inputs(self, root):
|
||||
source = ROOT / 'tests/results/qt-notice-supplement/collection'
|
||||
directory = root / 'supplement'
|
||||
shutil.copytree(source, directory)
|
||||
base = root / 'base.json'
|
||||
shutil.copyfile(ROOT / 'tests/results/source-archives/qt-sdk-notices-final/report.json', base)
|
||||
report = json.loads((directory / 'report.json').read_text())
|
||||
known = {row['resolvedPath']: row['sha256'] for row in report['runtimeComparisons']}
|
||||
return directory, base, report, known
|
||||
|
||||
def test_fixed_supplement_preserves_all_six_original_files(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp); directory, base, report, known = self.supplement_inputs(root)
|
||||
output = root / 'output'
|
||||
package.copy_sdk_supplement(directory, base, known, output)
|
||||
self.assertEqual(len(list(p for p in output.rglob('*') if p.is_file())), 7)
|
||||
self.assertEqual(package.sha(output / 'report.json'), package.SDK_SUPPLEMENT_REPORT_SHA)
|
||||
for row in report['files']:
|
||||
self.assertEqual((output / row['file']).read_bytes(), (directory / row['file']).read_bytes())
|
||||
|
||||
def test_supplement_rejects_changed_source_report_base_runtime_and_symlink(self):
|
||||
for change in ('source', 'report', 'base', 'runtime', 'symlink'):
|
||||
with self.subTest(change=change), tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp); directory, base, report, known = self.supplement_inputs(root)
|
||||
source = directory / report['files'][0]['file']
|
||||
if change == 'source': source.write_bytes(b'changed')
|
||||
elif change == 'report': (directory / 'report.json').write_text('{}')
|
||||
elif change == 'base': base.write_text('{}')
|
||||
elif change == 'runtime': known[next(iter(known))] = '0' * 64
|
||||
else:
|
||||
outside = root / 'outside'; shutil.copyfile(source, outside)
|
||||
source.unlink(); source.symlink_to(outside)
|
||||
output = root / 'output'
|
||||
with self.assertRaises(ValueError):
|
||||
package.copy_sdk_supplement(directory, base, known, output)
|
||||
self.assertFalse(output.exists())
|
||||
|
||||
def test_destination_traversal_and_noncanonical_paths_rejected(self):
|
||||
for value in ('../x', '/x', 'a/../b', '.', '', 'a//b', './x', 'x\\y', 'x\0y'):
|
||||
with self.subTest(value=repr(value)), self.assertRaises(ValueError):
|
||||
package.canonical(value)
|
||||
self.assertEqual(str(package.canonical('qt/qml/QtQuick/qmldir')), 'qt/qml/QtQuick/qmldir')
|
||||
|
||||
def test_hash_mismatch_rejected_before_destination_created(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp); source = root / 'input'; source.write_bytes(b'actual')
|
||||
target = root / 'output'
|
||||
with self.assertRaisesRegex(ValueError, 'Input changed'):
|
||||
package.copy_verified(source, target, '0' * 64)
|
||||
self.assertFalse(target.exists())
|
||||
|
||||
def test_conflicting_alias_is_not_overwritten(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp); source = root / 'input'; source.write_bytes(b'first')
|
||||
target = root / 'output'
|
||||
package.copy_verified(source, target, executable=False)
|
||||
package.copy_verified(source, target, executable=False)
|
||||
source.write_bytes(b'different')
|
||||
with self.assertRaisesRegex(ValueError, 'Conflicting'):
|
||||
package.copy_verified(source, target)
|
||||
self.assertEqual(target.read_bytes(), b'first')
|
||||
|
||||
def test_launcher_preserves_literal_arguments_and_selects_private_runtime(self):
|
||||
with tempfile.TemporaryDirectory(prefix='docview launcher ') as tmp:
|
||||
root = Path(tmp); app = root / 'app'; (app / 'bin').mkdir(parents=True)
|
||||
binary = app / 'bin/docview'
|
||||
binary.write_text('#!' + sys.executable + '\nimport os,sys,json\nprint(json.dumps({"argv":sys.argv[1:],"lib":os.environ["LD_LIBRARY_PATH"],"plugins":os.environ["QT_PLUGIN_PATH"],"qml":os.environ["QML_IMPORT_PATH"],"helper":os.environ["QTWEBENGINEPROCESS_PATH"]}))\n')
|
||||
binary.chmod(0o755)
|
||||
source = (ROOT / 'resources/linux/docview-launcher').read_text()
|
||||
self.assertEqual(source.count('docview_root=/opt/docview'), 1)
|
||||
launcher = root / 'launcher'
|
||||
launcher.write_text(source.replace('docview_root=/opt/docview', 'docview_root=' + shlex.quote(str(app))))
|
||||
literal = ['日本語 空白.pdf', 'a$(touch unexpected)', "quote'\";", '--config', 'a b.toml']
|
||||
result = json.loads(subprocess.check_output(['sh', str(launcher), *literal], text=True,
|
||||
env={**os.environ, 'LD_LIBRARY_PATH':'/invalid', 'QT_PLUGIN_PATH':'/invalid'}))
|
||||
self.assertEqual(result['argv'], literal)
|
||||
self.assertEqual(result['lib'], str(app / 'lib') + ':' + str(app / 'qt/lib'))
|
||||
self.assertEqual(result['plugins'], str(app / 'qt/plugins'))
|
||||
self.assertEqual(result['qml'], str(app / 'qt/qml'))
|
||||
self.assertEqual(result['helper'], str(app / 'qt/libexec/QtWebEngineProcess'))
|
||||
|
||||
def test_maintainer_scripts_have_valid_shell_syntax(self):
|
||||
for name in ('docview-launcher', 'deb-postinst', 'deb-prerm'):
|
||||
subprocess.run(['sh', '-n', str(ROOT / 'resources/linux' / name)], check=True)
|
||||
|
||||
def test_staging_modes_do_not_depend_on_builder_umask(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp); regular = root / 'notice'; executable = root / 'launcher'
|
||||
regular.write_text('text'); executable.write_text('text')
|
||||
regular.chmod(0o666); executable.chmod(0o777); root.chmod(0o777)
|
||||
package.normalize_modes(root)
|
||||
self.assertEqual(regular.stat().st_mode & 0o7777, 0o644)
|
||||
self.assertEqual(executable.stat().st_mode & 0o7777, 0o755)
|
||||
self.assertEqual(root.stat().st_mode & 0o7777, 0o755)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user