Files
SpotiFLAC-Mobile/scripts/test_build_android.py
T

193 lines
8.8 KiB
Python

"""Check release selection and audits without building APKs or deleting artifacts."""
import json
import os
import shutil
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
FLUTTER_STUB = """import json, os, sys
from pathlib import Path
args = sys.argv[1:]
if Path(sys.argv[0]).name == 'fvm':
assert args.pop(0) == 'flutter'
with open(os.environ['BUILD_TEST_LOG'], 'a') as log:
log.write(json.dumps({'tool': Path(sys.argv[0]).name, 'args': args,
'sdk': os.environ.get('SPOTIFLAC_DISCORD_SDK', '1')}) + '\\n')
if args == ['--version', '--machine']:
print(json.dumps({'frameworkVersion': os.environ.get('BUILD_TEST_FLUTTER_VERSION', '3.47.5')}))
else:
assert args[:2] == ['build', 'apk']
output = Path('build/app/outputs/flutter-apk')
output.mkdir(parents=True, exist_ok=True)
for abi in os.environ['SPOTIFLAC_RUST_ANDROID_ABIS'].split(','):
(output / ('app-' + abi + '-release.apk')).write_bytes(b'fixture split')
(output / 'app-release.apk').write_bytes(b'fixture universal')
"""
PYTHON_STUB = """import json, os, sys
if sys.argv[1] == 'scripts/check_backend_apk.py':
with open(os.environ['BUILD_TEST_LOG'], 'a') as log:
log.write(json.dumps({'tool': 'audit', 'args': sys.argv[2:]}) + '\\n')
else:
os.execv(sys.executable, [sys.executable, *sys.argv[1:]])
"""
class AndroidBuildScriptTest(unittest.TestCase):
def setUp(self):
directory = tempfile.TemporaryDirectory()
self.addCleanup(directory.cleanup)
self.project = Path(directory.name)
(self.project / "scripts").mkdir()
shutil.copyfile(Path(__file__).with_name("build_android.sh"), self.project / "scripts/build_android.sh")
(self.project / ".fvmrc").write_text('{"flutter":"3.47.5"}')
sdk = self.project / "third_party/spotiflac_discord/sdk"
sdk.mkdir(parents=True)
(sdk / "discord_partner_sdk.aar").write_bytes(b"staged fixture")
for name in (
"notices/Discord-License-Notices.txt",
"android/prefab/modules/discord_partner_sdk/include/discordpp.h",
"android/jni/arm64-v8a/libdiscord_partner_sdk.so",
"android/jni/armeabi-v7a/libdiscord_partner_sdk.so",
):
path = sdk / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(b"staged fixture")
self.tools = self.project / "tools"
self.tools.mkdir()
self.log = self.project / "calls.jsonl"
self.env = os.environ.copy()
for key in ("SPOTIFLAC_DISCORD_SDK", "SPOTIFLAC_RUST_ANDROID_ABIS"):
self.env.pop(key, None)
self.env.update(PATH=str(self.tools) + os.pathsep + os.defpath, BUILD_TEST_LOG=str(self.log))
self.tool(self.tools / "python3", PYTHON_STUB)
git = self.tools / "git"
git.write_text("#!/bin/sh\necho 1234abcd\n")
git.chmod(0o755)
self.pinned = self.project / ".fvm/flutter_sdk/bin/flutter"
self.tool(self.pinned, FLUTTER_STUB)
def tool(self, path, source):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("#!" + sys.executable + "\n" + source)
path.chmod(0o755)
def run_build(self, *args):
return subprocess.run(
["/bin/bash", str(self.project / "scripts/build_android.sh"), *args],
cwd=self.project, env=self.env, text=True, capture_output=True,
)
def calls(self):
return [json.loads(line) for line in self.log.read_text().splitlines()]
def test_default_full_audits_two_splits_and_universal(self):
result = self.run_build("--verbose")
self.assertEqual(result.returncode, 0, result.stderr)
calls = self.calls()
build = next(call for call in calls if call["args"][:2] == ["build", "apk"])
self.assertIn("android-arm64,android-arm", build["args"])
self.assertIn("--verbose", build["args"])
audits = [call["args"] for call in calls if call["tool"] == "audit"]
self.assertEqual([Path(args[0]).name for args in audits], [
"app-arm64-v8a-release.apk", "app-armeabi-v7a-release.apk", "app-release.apk",
])
self.assertTrue(all("--discord-sdk" in args for args in audits))
self.assertEqual(audits[-1][audits[-1].index("--abis") + 1], "arm64-v8a,armeabi-v7a")
def test_lite_single_abi_ignores_and_preserves_previous_artifacts(self):
self.env["SPOTIFLAC_RUST_ANDROID_ABIS"] = "arm64-v8a"
output = self.project / "build/app/outputs/flutter-apk"
output.mkdir(parents=True)
previous = output / "app-armeabi-v7a-release.apk"
previous.write_bytes(b"previous build")
result = self.run_build("--lite")
self.assertEqual(result.returncode, 0, result.stderr)
calls = self.calls()
build = next(call for call in calls if call["args"][:2] == ["build", "apk"])
self.assertEqual(build["sdk"], "0")
self.assertNotIn("--lite", build["args"])
self.assertIn("android-arm64", build["args"])
audits = [call["args"] for call in calls if call["tool"] == "audit"]
self.assertEqual(len(audits), 2)
self.assertTrue(all("--discord-sdk" not in args for args in audits))
self.assertTrue(all(args[args.index("--abis") + 1] == "arm64-v8a" for args in audits))
self.assertEqual(previous.read_bytes(), b"previous build")
def test_explicit_sdk_disable_is_honored_without_lite_argument(self):
self.env["SPOTIFLAC_DISCORD_SDK"] = "0"
self.env["SPOTIFLAC_RUST_ANDROID_ABIS"] = "armeabi-v7a"
result = self.run_build()
self.assertEqual(result.returncode, 0, result.stderr)
audits = [call["args"] for call in self.calls() if call["tool"] == "audit"]
self.assertEqual(len(audits), 2)
self.assertTrue(all("--discord-sdk" not in args for args in audits))
self.assertTrue(all(args[args.index("--abis") + 1] == "armeabi-v7a" for args in audits))
def test_fvm_fallback_uses_pinned_version(self):
self.pinned.unlink()
self.tool(self.tools / "fvm", FLUTTER_STUB)
result = self.run_build()
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(self.calls()[0]["tool"], "fvm")
def test_production_requires_full_even_with_inherited_sdk_disable(self):
self.env["SPOTIFLAC_DISCORD_SDK"] = "0"
result = self.run_build("--production")
self.assertEqual(result.returncode, 0, result.stderr)
calls = self.calls()
build = next(call for call in calls if call["args"][:2] == ["build", "apk"])
self.assertEqual(build["sdk"], "1")
self.assertNotIn("--production", build["args"])
self.assertTrue(all("--discord-sdk" in call["args"] for call in calls if call["tool"] == "audit"))
def test_production_rejects_lite_in_either_argument_order(self):
for arguments in (("--production", "--lite"), ("--lite", "--production")):
result = self.run_build(*arguments)
self.assertNotEqual(result.returncode, 0)
self.assertIn("cannot be combined", result.stderr)
self.assertFalse(self.log.exists())
def test_production_missing_sdk_components_stops_before_flutter(self):
sdk = self.project / "third_party/spotiflac_discord/sdk"
for name in (
"discord_partner_sdk.aar", "notices/Discord-License-Notices.txt",
"android/prefab/modules/discord_partner_sdk/include/discordpp.h",
"android/jni/arm64-v8a/libdiscord_partner_sdk.so",
"android/jni/armeabi-v7a/libdiscord_partner_sdk.so",
):
with self.subTest(component=name):
path = sdk / name
data = path.read_bytes()
path.unlink()
result = self.run_build("--production")
self.assertNotEqual(result.returncode, 0)
self.assertIn("production requires", result.stderr)
path.write_bytes(data)
self.assertFalse(self.log.exists())
def test_wrong_flutter_version_stops_before_build_or_audit(self):
self.env["BUILD_TEST_FLUTTER_VERSION"] = "0.0.0"
result = self.run_build()
self.assertNotEqual(result.returncode, 0)
self.assertIn("Flutter 3.47.5 is required", result.stderr)
self.assertEqual(len(self.calls()), 1)
def test_invalid_or_overridden_abis_stop_before_flutter(self):
for abis in ("", "x86_64", "arm64-v8a,arm64-v8a", "armeabi-v7a, arm64-v8a"):
with self.subTest(abis=abis):
self.env["SPOTIFLAC_RUST_ANDROID_ABIS"] = abis
self.assertNotEqual(self.run_build().returncode, 0)
self.env.pop("SPOTIFLAC_RUST_ANDROID_ABIS")
self.assertNotEqual(self.run_build("--target-platform=android-x64").returncode, 0)
self.assertFalse(self.log.exists())
if __name__ == "__main__":
unittest.main()