#!/usr/bin/env python3 """Offline regressions for PDFium staging and native package validation.""" import hashlib import importlib.util import io import os import pathlib import plistlib import shutil import struct import subprocess import sys import tarfile import tempfile import unittest import zipfile from unittest.mock import patch sys.dont_write_bytecode = True SCRIPTS = pathlib.Path(__file__).resolve().parent def module(name): spec = importlib.util.spec_from_file_location(name, SCRIPTS / (name + ".py")) result = importlib.util.module_from_spec(spec) spec.loader.exec_module(result) return result stage = module("stage-pdfium") native = module("verify-android-native") linux_elf = module("linux-elf") framework = module("write-pdfium-framework-plist") def archive(files): result = io.BytesIO() with tarfile.open(fileobj=result, mode="w:gz") as output: for name, data in files.items(): entry = tarfile.TarInfo(name) entry.size = len(data) output.addfile(entry, io.BytesIO(data)) return result.getvalue() def elf(alignment=16384): data = bytearray(120) data[:6] = b"\x7fELF\x02\x01" struct.pack_into(" "$output"\n') executable("iconutil", 'output="${@: -1}"\n: > "$output"\n') executable("plutil", 'test "$1" = -lint\ntest -f "$2"\n') executable( "codesign", 'target="${@: -1}"\nprintf "%s\\n" "$*" >> "$CODESIGN_LOG"\n' 'if [[ "$target" == *.dylib ]]; then : > "$target.signed"; ' 'elif [[ "$*" == *"--sign"* ]]; then test -f "$target/Contents/Frameworks/libpdfium.dylib.signed"; fi\n', ) executable( "ditto", 'if [[ "$1" == "-c" ]]; then : > "${@: -1}"; else mkdir -p "$2"; fi\n', ) executable( "hdiutil", 'if [[ "$1" == "create" ]]; then : > "${@: -1}"; else test -f "$2"; fi\n', ) for architecture, pdfium_target in (("arm64", "mac-arm64"), ("x64", "mac-x64")): with self.subTest(architecture=architecture): output_dir = root / f"target/macos-{architecture}" codesign_log = root / f"codesign-{architecture}.log" result = subprocess.run( [ "bash", str(root / "scripts/package-macos.sh"), str(root / "target/release/flectar-mail"), str(output_dir), architecture, ], cwd=root, env={ **os.environ, "PATH": f"{tools}:{os.environ['PATH']}", "CODESIGN_LOG": str(codesign_log), "EXPECTED_PDFIUM_TARGET": pdfium_target, }, capture_output=True, text=True, ) self.assertEqual(result.returncode, 0, result.stdout + result.stderr) app = output_dir / "Flectar Mail.app/Contents" self.assertTrue((app / "Frameworks/libpdfium.dylib").is_file()) self.assertTrue((app / "Resources/Licenses/PDFium/build.json").is_file()) self.assertFalse((app / "MacOS/libpdfium.dylib").exists()) self.assertTrue( (app / "Library/LaunchAgents/com.flectar.mail.login.plist").is_file() ) self.assertTrue((output_dir / f"flectar-mail-macos-{architecture}.zip").is_file()) self.assertTrue((output_dir / f"flectar-mail-macos-{architecture}.dmg").is_file()) signatures = codesign_log.read_text().splitlines() self.assertIn("Contents/Frameworks/libpdfium.dylib", signatures[0]) self.assertTrue(signatures[1].endswith("Flectar Mail.app")) self.assertIn("--verify --deep --strict", signatures[2]) def test_apk_requires_pdfium_notices_and_aligned_native_libraries(self): with tempfile.TemporaryDirectory() as directory: apk = pathlib.Path(directory) / "fixture.apk" files = { "lib/arm64-v8a/libflectar_mail_android.so": elf(), "lib/arm64-v8a/libpdfium.so": elf(), "assets/licenses/PDFium/arm64-v8a/pdfium-licenses/build.json": b"{}", "assets/licenses/PDFium/arm64-v8a/pdfium-licenses/LICENSE": b"license", } def write(values): with zipfile.ZipFile(apk, "w") as output: for name, data in values.items(): output.writestr(name, data) write(files) # This fixture only models load segments. JNI exports are exercised # with compiled shared libraries in test-android-jni.py. with patch.object(native, "verify_jni_exports"): native.verify_apk(apk) write({k: v for k, v in files.items() if not k.endswith("libpdfium.so")}) with self.assertRaisesRegex(ValueError, "missing bundled PDFium"): native.verify_apk(apk) write({**files, "lib/arm64-v8a/libpdfium.so": elf(4096)}) with self.assertRaisesRegex(ValueError, "not aligned"): native.verify_apk(apk) def test_ios_framework_metadata_distinguishes_device_and_simulator(self): with tempfile.TemporaryDirectory() as directory: path = pathlib.Path(directory) / "Info.plist" for simulator in (False, True): framework.write(path, simulator) info = plistlib.loads(path.read_bytes()) self.assertEqual(info["CFBundleExecutable"], "PDFium") self.assertEqual(info["CFBundlePackageType"], "FMWK") self.assertEqual(info["MinimumOSVersion"], "17.0") self.assertEqual(info["CFBundleSupportedPlatforms"], ["iPhoneSimulator" if simulator else "iPhoneOS"]) if __name__ == "__main__": unittest.main()