This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Source-level invariants, not a substitute for Android lint, Gradle or device testing."""
|
||||
from pathlib import Path
|
||||
import xml.etree.ElementTree as ET
|
||||
root = Path(__file__).resolve().parent.parent
|
||||
android = '{http://schemas.android.com/apk/res/android}'
|
||||
for xml in (root / 'androidApp/src').rglob('*.xml'):
|
||||
ET.parse(xml)
|
||||
manifest = ET.parse(root / 'androidApp/src/main/AndroidManifest.xml').getroot()
|
||||
assert not manifest.findall('uses-permission'), 'Unexpected permission in app manifest'
|
||||
application = manifest.find('application')
|
||||
assert application.get(android+'allowBackup') == 'false'
|
||||
service = application.find('service')
|
||||
assert service.get(android+'isolatedProcess') == 'true'
|
||||
assert service.get(android+'exported') == 'false'
|
||||
provider = application.find('provider')
|
||||
assert provider.get(android+'exported') == 'false'
|
||||
for source in (root / 'core/src/commonMain').rglob('*.kt'):
|
||||
content = source.read_text()
|
||||
assert 'import android.' not in content and 'import java.' not in content, source
|
||||
all_android = '\n'.join(p.read_text() for p in (root / 'androidApp/src/main/kotlin').rglob('*.kt'))
|
||||
assert 'WebView(' not in all_android
|
||||
assert 'Log.' not in all_android
|
||||
print('PASS project invariants: XML, permission-free manifest, isolated PDF service, narrow FileProvider, portable core, no WebView/log calls.')
|
||||
|
||||
# Browser/picker entry points must not impersonate the system document chooser.
|
||||
activities = {item.get(android+'name'): item for item in application.findall('activity')}
|
||||
main = activities['.MainActivity']
|
||||
picker = activities['.FilePickerActivity']
|
||||
assert picker.get(android+'exported') == 'true'
|
||||
assert picker.get(android+'launchMode', 'standard') == 'standard'
|
||||
all_actions = {item.get(android+'name') for item in application.findall('.//action')}
|
||||
assert 'android.intent.action.GET_CONTENT' in all_actions
|
||||
assert 'android.intent.action.OPEN_DOCUMENT' not in all_actions
|
||||
assert 'android.intent.action.OPEN_DOCUMENT_TREE' not in all_actions
|
||||
assert any(item.get(android+'name') == 'android.intent.category.APP_FILES' for item in main.findall('.//category'))
|
||||
assert any(item.get(android+'name') == 'android.intent.category.OPENABLE' for item in picker.findall('.//category'))
|
||||
paths = ET.parse(root / 'androidApp/src/main/res/xml/file_paths.xml').getroot()
|
||||
assert {(item.tag, item.get('path')) for item in paths} == {('cache-path', 'shared/'), ('cache-path', 'picked/')}
|
||||
assert all('FixtureDocumentsProvider' not in item.get(android+'name', '') for item in application.findall('provider'))
|
||||
for file in ['MainActivity.kt', 'FilePickerActivity.kt']:
|
||||
content = (root / 'androidApp/src/main/kotlin/dev/fileworkbench/android' / file).read_text()
|
||||
assert 'sanitized' in content
|
||||
assert content.index('sanitized') < content.index('super.onCreate(savedInstanceState)')
|
||||
copy_code = (root / 'androidApp/src/main/kotlin/dev/fileworkbench/android/browser/PickedFileStore.kt').read_text()
|
||||
assert 'SelectionCopyBudget()' in copy_code
|
||||
assert 'flags = Intent.FLAG_GRANT_READ_URI_PERMISSION' in copy_code
|
||||
assert 'FLAG_GRANT_PREFIX_URI_PERMISSION' not in copy_code
|
||||
assert 'FLAG_GRANT_WRITE_URI_PERMISSION' not in copy_code
|
||||
assert 'FLAG_GRANT_PERSISTABLE_URI_PERMISSION' not in copy_code
|
||||
print('PASS browser source invariants: APP_FILES/GET_CONTENT, standard picker activity, no system-picker impersonation, narrow copied paths, read-only results, sanitized activity defaults, debug fixture excluded from main manifest.')
|
||||
|
||||
# Regression guard for the reported instrumentation compile failure. This deliberately
|
||||
# checks only the known constructor/use pattern; it is NOT Kotlin type checking.
|
||||
import re
|
||||
for source in (root / 'androidApp/src').rglob('*.kt'):
|
||||
assert not re.search(r"\bPdfDocument\s*\(\s*\)\s*\.\s*use\b", source.read_text()), (
|
||||
f'{source}: PdfDocument does not implement Closeable/AutoCloseable; use try/finally'
|
||||
)
|
||||
print('PASS PDF source regression guard: no PdfDocument().use call (pattern check only).')
|
||||
|
||||
# The release is intentionally pinned; prevent mixed old/new wrapper or plugin files.
|
||||
import subprocess
|
||||
import sys
|
||||
subprocess.run([sys.executable, str(root / 'scripts/check-build-config.py')], check=True)
|
||||
Reference in New Issue
Block a user