文件
paintfcp-fxplug/tests/installer_identity_tests.py

105 行
5.2 KiB
Python

"""Run the generated installer migration in isolated volumes, never live paths."""
import os, pathlib, plistlib, shlex, subprocess, sys, tempfile, unittest
ROOT = pathlib.Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / 'scripts'))
from installer_identity import inject_migration, PRODUCT
class InstallerIdentityTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory(prefix='beautygirl-installer-test.')
self.root = pathlib.Path(self.tmp.name)
self.old = self.root / pathlib.Path(PRODUCT['legacyProducts'][0]['installationPath']).relative_to('/')
self.new = self.root / pathlib.Path(PRODUCT['installationPath']).relative_to('/')
for app, info in [(self.old, PRODUCT['legacyProducts'][0]), (self.new, PRODUCT)]:
(app / 'Contents/PlugIns' / info['pluginName'] / 'Contents').mkdir(parents=True)
(app / 'Contents/Info.plist').write_bytes(plistlib.dumps({'CFBundleIdentifier': info['bundleIdentifier']}))
(app / 'Contents/PlugIns' / info['pluginName'] / 'Contents/Info.plist').write_bytes(plistlib.dumps({'ProPlugPlugInList': [{'uuid': PRODUCT['filterUUID']}]}))
(app / 'payload-byte').write_bytes(b'original signed payload stand-in')
self.signer = self.root / 'codesign-fixture'
self.signer.write_text('#!/bin/zsh\napp="${argv[-1]}"\nif [[ -f "$app/.invalid" ]]; then exit 1; fi\n/bin/echo TeamIdentifier=B3W25FX5H5 >&2\n')
self.signer.chmod(0o755)
def tearDown(self):
self.tmp.cleanup()
def run_installer(self, fail_registration=False):
base = '''#!/bin/zsh
set -eu
TARGET_VOLUME=@@VOLUME@@
CONSOLE_UID=501
CONSOLE_USER=fixture
FX_FAILURE=0
fx_verify_app() {
/bin/echo "$1" >> @@LOG@@
if [[ "$1" == "$BG_NEW_APP" && @@FAIL@@ == 1 ]]; then return 1; fi
return 0
}
trap - EXIT
fx_verify_app "${TARGET_VOLUME}/Library/Plug-Ins/FxPlug/FCP-BeautyGirl.app" || FX_FAILURE=1
if (( FX_FAILURE )); then exit 1; fi
exit 0
'''
base = base.replace('@@VOLUME@@', shlex.quote(str(self.root))).replace('@@LOG@@', shlex.quote(str(self.root / 'registration-calls'))).replace('@@FAIL@@', '1' if fail_registration else '0')
code = inject_migration(base)
# Only signature and user-session registration are isolated. Identity
# parsing, file moves, deletes, traps and rollback run unchanged.
code = code.replace('/usr/bin/codesign', shlex.quote(str(self.signer)))
code = code.replace('/bin/launchctl asuser', '/usr/bin/true asuser')
script = self.root / 'postinstall'
script.write_text(code)
subprocess.run(['/bin/zsh', '-n', str(script)], check=True, capture_output=True)
return subprocess.run(['/bin/zsh', str(script)], text=True, capture_output=True)
def test_upgrade_keeps_new_and_removes_old_and_recovery_copy(self):
self.assertEqual(self.run_installer().returncode, 0)
self.assertTrue(self.new.exists())
self.assertFalse(self.old.exists())
self.assertFalse(list((self.root / 'Library/Application Support').rglob('*.app')))
def test_registration_failure_restores_old_and_removes_new(self):
original = (self.old / 'payload-byte').read_bytes()
result = self.run_installer(True)
self.assertNotEqual(result.returncode, 0)
self.assertFalse(self.new.exists())
self.assertEqual((self.old / 'payload-byte').read_bytes(), original)
self.assertIn(str(self.old), (self.root / 'registration-calls').read_text())
def test_invalid_new_signature_does_not_touch_old(self):
(self.new / '.invalid').touch()
self.assertNotEqual(self.run_installer().returncode, 0)
self.assertTrue(self.old.exists())
def test_foreign_old_bundle_id_is_preserved(self):
(self.old / 'Contents/Info.plist').write_bytes(plistlib.dumps({'CFBundleIdentifier': 'com.unrelated.product'}))
self.assertNotEqual(self.run_installer().returncode, 0)
self.assertTrue(self.old.exists())
def test_invalid_old_signature_is_preserved(self):
(self.old / '.invalid').touch()
self.assertNotEqual(self.run_installer().returncode, 0)
self.assertTrue(self.old.exists())
def test_fresh_install_and_repeat_are_idempotent(self):
import shutil
shutil.rmtree(self.old)
self.assertEqual(self.run_installer().returncode, 0)
self.assertEqual(self.run_installer().returncode, 0)
self.assertTrue(self.new.exists())
def test_unrelated_neighbor_is_preserved(self):
unrelated = self.old.parent / 'OtherProduct.app'
unrelated.mkdir()
self.assertEqual(self.run_installer().returncode, 0)
self.assertTrue(unrelated.exists())
@unittest.skipUnless(os.environ.get('FCP_BEAUTYGIRL_POSTINSTALL'), 'Final PKG is not built yet')
def test_final_package_contains_the_tested_migration_block(self):
actual = pathlib.Path(os.environ['FCP_BEAUTYGIRL_POSTINSTALL']).read_text()
generated = inject_migration('\ntrap - EXIT\nfx_verify_app candidate\nexit 0\n')
start = generated.index('# FCP-BeautyGirl identity migration.')
end = generated.rindex('\nfx_verify_app ')
self.assertIn(generated[start:end], actual)
if __name__ == '__main__':
unittest.main()