1"""Exercise the Android Cargo lane without changing an installed toolchain."""
2import json
3import os
4from pathlib import Path
5import platform
6import shlex
7import subprocess
8import tempfile
9import unittest
10
11
12LANE = Path(__file__).resolve().parents[1] / 'platform/android/cargo.sh'
13
14
15class 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
37import json, os, sys
38print(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'))}}))
41sys.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
103if __name__ == '__main__':
104 unittest.main()