| 1 | """Exercise the Android Cargo lane without changing an installed toolchain.""" |
| 2 | import json |
| 3 | import os |
| 4 | from pathlib import Path |
| 5 | import platform |
| 6 | import shlex |
| 7 | import subprocess |
| 8 | import tempfile |
| 9 | import unittest |
| 10 | |
| 11 | |
| 12 | LANE = Path(__file__).resolve().parents[1] / 'platform/android/cargo.sh' |
| 13 | |
| 14 | |
| 15 | class AndroidToolchainTests(unittest.TestCase): |
| 16 | def setUp(self): |
| 17 | self.scratch = tempfile.TemporaryDirectory(prefix='android tools ') |
| 18 | self.addCleanup(self.scratch.cleanup) |
| 19 | self.root = Path(self.scratch.name) |
| 20 | self.bin = self.root / 'bin' |
| 21 | self.bin.mkdir() |
| 22 | self.std = self.root / 'rust std' |
| 23 | self.std.mkdir() |
| 24 | host = 'darwin-x86_64' if platform.system() == 'Darwin' else 'linux-x86_64' |
| 25 | self.ndk = self.root / 'android ndk' |
| 26 | self.tools = self.ndk / 'toolchains/llvm/prebuilt' / host / 'bin' |
| 27 | self.tools.mkdir(parents=True) |
| 28 | for name in ('aarch64-linux-android24-clang', 'aarch64-linux-android24-clang++', 'llvm-ar'): |
| 29 | tool = self.tools / name |
| 30 | tool.write_text('#!/bin/sh\nexit 0\n') |
| 31 | tool.chmod(0o755) |
| 32 | rustc = self.bin / 'rustc' |
| 33 | rustc.write_text('#!/bin/sh\nprintf "%s\\n" ' + shlex.quote(str(self.std)) + '\n') |
| 34 | rustc.chmod(0o755) |
| 35 | cargo = self.bin / 'cargo' |
| 36 | cargo.write_text('''#!/usr/bin/env python3 |
| 37 | import json, os, sys |
| 38 | print(json.dumps({'arguments': sys.argv[1:], 'environment': { |
| 39 | key: value for key, value in os.environ.items() |
| 40 | if key.startswith(('CC_', 'CXX_', 'AR_', 'CARGO_TARGET'))}})) |
| 41 | sys.exit(int(os.environ.get('FAKE_CARGO_EXIT', '0'))) |
| 42 | ''') |
| 43 | cargo.chmod(0o755) |
| 44 | self.env = {**os.environ, 'PATH': str(self.bin) + os.pathsep + os.environ['PATH'], |
| 45 | 'ANDROID_NDK_HOME': str(self.ndk)} |
| 46 | self.env.pop('CARGO_TARGET_DIR', None) |
| 47 | |
| 48 | def run_lane(self, *arguments): |
| 49 | return subprocess.run([str(LANE), *arguments], env=self.env, |
| 50 | text=True, capture_output=True, timeout=10) |
| 51 | |
| 52 | def test_explicit_api_and_tools_with_spaces_reach_cargo(self): |
| 53 | result = self.run_lane('24', 'check', '-p', 'mobile', '--config', 'build.jobs = 2') |
| 54 | self.assertEqual(result.returncode, 0, result.stderr) |
| 55 | record = json.loads(result.stdout) |
| 56 | self.assertEqual(record['arguments'], [ |
| 57 | 'check', '--locked', '--target', 'aarch64-linux-android', '-p', 'mobile', '--config', 'build.jobs = 2']) |
| 58 | environment = record['environment'] |
| 59 | self.assertEqual(environment['CC_aarch64_linux_android'], |
| 60 | str(self.tools / 'aarch64-linux-android24-clang')) |
| 61 | self.assertEqual(environment['CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER'], |
| 62 | environment['CC_aarch64_linux_android']) |
| 63 | self.assertEqual(environment['CXX_aarch64_linux_android'], |
| 64 | str(self.tools / 'aarch64-linux-android24-clang++')) |
| 65 | self.assertEqual(environment['AR_aarch64_linux_android'], str(self.tools / 'llvm-ar')) |
| 66 | self.assertEqual(environment['CARGO_TARGET_DIR'], str(LANE.parents[2] / 'target/android')) |
| 67 | |
| 68 | def test_shared_cache_override_and_cargo_failure_are_preserved(self): |
| 69 | self.env.update(CARGO_TARGET_DIR=str(self.root / 'shared cache'), FAKE_CARGO_EXIT='37') |
| 70 | result = self.run_lane('24', 'build', '-p', 'mobile') |
| 71 | self.assertEqual(result.returncode, 37) |
| 72 | self.assertEqual(json.loads(result.stdout)['environment']['CARGO_TARGET_DIR'], |
| 73 | self.env['CARGO_TARGET_DIR']) |
| 74 | |
| 75 | def test_api_is_required_and_numeric(self): |
| 76 | for arguments in ((), ('24',), ('24; touch sentinel', 'build')): |
| 77 | with self.subTest(arguments=arguments): |
| 78 | result = self.run_lane(*arguments) |
| 79 | self.assertEqual(result.returncode, 2) |
| 80 | self.assertEqual(result.stdout, '') |
| 81 | |
| 82 | def test_ndk_is_explicit(self): |
| 83 | self.env.pop('ANDROID_NDK_HOME') |
| 84 | result = self.run_lane('24', 'build') |
| 85 | self.assertNotEqual(result.returncode, 0) |
| 86 | self.assertIn('set ANDROID_NDK_HOME', result.stderr) |
| 87 | self.assertEqual(result.stdout, '') |
| 88 | |
| 89 | def test_missing_api_compiler_stops_before_cargo(self): |
| 90 | result = self.run_lane('9999', 'check') |
| 91 | self.assertNotEqual(result.returncode, 0) |
| 92 | self.assertIn('NDK tool missing:', result.stderr) |
| 93 | self.assertEqual(result.stdout, '') |
| 94 | |
| 95 | def test_missing_rust_target_stops_before_cargo(self): |
| 96 | self.std.rmdir() |
| 97 | result = self.run_lane('24', 'build') |
| 98 | self.assertNotEqual(result.returncode, 0) |
| 99 | self.assertIn('install the aarch64-linux-android standard library', result.stderr) |
| 100 | self.assertEqual(result.stdout, '') |
| 101 | |
| 102 | |
| 103 | if __name__ == '__main__': |
| 104 | unittest.main() |