Skip to content

Commit 25e3cac

Browse files
committed
Basic toolchain.py unit tests
First simple set of tests for toolchain.py Also refactors `Context.prepare_build_environment()` slightly. Splits concerns to improve readability and ease unit testing.
1 parent f6f6f19 commit 25e3cac

File tree

2 files changed

+119
-36
lines changed

2 files changed

+119
-36
lines changed

pythonforandroid/build.py

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,58 @@
2727
RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API)
2828

2929

30+
def get_ndk_platform_dir(ndk_dir, ndk_api, arch):
31+
ndk_platform_dir_exists = True
32+
platform_dir = arch.platform_dir
33+
ndk_platform = join(
34+
ndk_dir,
35+
'platforms',
36+
'android-{}'.format(ndk_api),
37+
platform_dir)
38+
if not exists(ndk_platform):
39+
warning("ndk_platform doesn't exist: {}".format(ndk_platform))
40+
ndk_platform_dir_exists = False
41+
return ndk_platform, ndk_platform_dir_exists
42+
43+
44+
def get_toolchain_versions(ndk_dir, arch):
45+
toolchain_versions = []
46+
toolchain_path_exists = True
47+
toolchain_prefix = arch.toolchain_prefix
48+
toolchain_path = join(ndk_dir, 'toolchains')
49+
if isdir(toolchain_path):
50+
toolchain_contents = glob.glob('{}/{}-*'.format(toolchain_path,
51+
toolchain_prefix))
52+
toolchain_versions = [split(path)[-1][len(toolchain_prefix) + 1:]
53+
for path in toolchain_contents]
54+
else:
55+
warning('Could not find toolchain subdirectory!')
56+
toolchain_path_exists = False
57+
return toolchain_versions, toolchain_path_exists
58+
59+
60+
def get_targets(sdk_dir):
61+
if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')):
62+
avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager'))
63+
targets = avdmanager('list', 'target').stdout.decode('utf-8').split('\n')
64+
elif exists(join(sdk_dir, 'tools', 'android')):
65+
android = sh.Command(join(sdk_dir, 'tools', 'android'))
66+
targets = android('list').stdout.decode('utf-8').split('\n')
67+
else:
68+
raise BuildInterruptingException(
69+
'Could not find `android` or `sdkmanager` binaries in Android SDK',
70+
instructions='Make sure the path to the Android SDK is correct')
71+
return targets
72+
73+
74+
def get_available_apis(sdk_dir):
75+
targets = get_targets(sdk_dir)
76+
apis = [s for s in targets if re.match(r'^ *API level: ', s)]
77+
apis = [re.findall(r'[0-9]+', s) for s in apis]
78+
apis = [int(s[0]) for s in apis if s]
79+
return apis
80+
81+
3082
class Context(object):
3183
'''A build context. If anything will be built, an instance this class
3284
will be instantiated and used to hold all the build state.'''
@@ -238,20 +290,7 @@ def prepare_build_environment(self,
238290
self.android_api = android_api
239291

240292
check_target_api(android_api, self.archs[0].arch)
241-
242-
if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')):
243-
avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager'))
244-
targets = avdmanager('list', 'target').stdout.decode('utf-8').split('\n')
245-
elif exists(join(sdk_dir, 'tools', 'android')):
246-
android = sh.Command(join(sdk_dir, 'tools', 'android'))
247-
targets = android('list').stdout.decode('utf-8').split('\n')
248-
else:
249-
raise BuildInterruptingException(
250-
'Could not find `android` or `sdkmanager` binaries in Android SDK',
251-
instructions='Make sure the path to the Android SDK is correct')
252-
apis = [s for s in targets if re.match(r'^ *API level: ', s)]
253-
apis = [re.findall(r'[0-9]+', s) for s in apis]
254-
apis = [int(s[0]) for s in apis if s]
293+
apis = get_available_apis(self.sdk_dir)
255294
info('Available Android APIs are ({})'.format(
256295
', '.join(map(str, apis))))
257296
if android_api in apis:
@@ -340,33 +379,17 @@ def prepare_build_environment(self,
340379

341380
# This would need to be changed if supporting multiarch APKs
342381
arch = self.archs[0]
343-
platform_dir = arch.platform_dir
344382
toolchain_prefix = arch.toolchain_prefix
345-
toolchain_version = None
346-
self.ndk_platform = join(
347-
self.ndk_dir,
348-
'platforms',
349-
'android-{}'.format(self.ndk_api),
350-
platform_dir)
351-
if not exists(self.ndk_platform):
352-
warning('ndk_platform doesn\'t exist: {}'.format(
353-
self.ndk_platform))
354-
ok = False
383+
self.ndk_platform, ndk_platform_dir_exists = get_ndk_platform_dir(
384+
self.ndk_dir, self.ndk_api, arch)
385+
ok = ok and ndk_platform_dir_exists
355386

356387
py_platform = sys.platform
357388
if py_platform in ['linux2', 'linux3']:
358389
py_platform = 'linux'
359-
360-
toolchain_versions = []
361-
toolchain_path = join(self.ndk_dir, 'toolchains')
362-
if isdir(toolchain_path):
363-
toolchain_contents = glob.glob('{}/{}-*'.format(toolchain_path,
364-
toolchain_prefix))
365-
toolchain_versions = [split(path)[-1][len(toolchain_prefix) + 1:]
366-
for path in toolchain_contents]
367-
else:
368-
warning('Could not find toolchain subdirectory!')
369-
ok = False
390+
toolchain_versions, toolchain_path_exists = get_toolchain_versions(
391+
self.ndk_dir, arch)
392+
ok = ok and toolchain_path_exists
370393
toolchain_versions.sort()
371394

372395
toolchain_versions_gcc = []

tests/test_toolchain.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import sys
2+
import pytest
3+
import mock
4+
from pythonforandroid.toolchain import ToolchainCL
5+
from pythonforandroid.util import BuildInterruptingException
6+
7+
8+
class TestToolchainCL:
9+
10+
def test_help(self):
11+
"""
12+
Calling with `--help` should print help and exit 0.
13+
"""
14+
argv = ['toolchain.py', '--help', '--storage-dir=/tmp']
15+
with mock.patch('sys.argv', argv), pytest.raises(SystemExit) as ex_info, \
16+
mock.patch('argparse.ArgumentParser.print_help') as m_print_help:
17+
ToolchainCL()
18+
assert ex_info.value.code == 0
19+
assert m_print_help.call_args_list == [mock.call()]
20+
21+
@pytest.mark.skipif(sys.version_info < (3, 0), reason="requires python3")
22+
def test_unknown(self):
23+
"""
24+
Calling with unknown args should print help and exit 1.
25+
"""
26+
argv = ['toolchain.py', '--unknown']
27+
with mock.patch('sys.argv', argv), pytest.raises(SystemExit) as ex_info, \
28+
mock.patch('argparse.ArgumentParser.print_help') as m_print_help:
29+
ToolchainCL()
30+
assert ex_info.value.code == 1
31+
assert m_print_help.call_args_list == [mock.call()]
32+
33+
def test_create(self):
34+
"""
35+
Basic `create` distribution test.
36+
"""
37+
argv = ['toolchain.py', 'create', '--sdk-dir=/tmp/android-sdk', '--ndk-dir=/tmp/android-ndk']
38+
with mock.patch('sys.argv', argv), \
39+
mock.patch('pythonforandroid.build.get_available_apis') as m_get_available_apis, \
40+
mock.patch('pythonforandroid.build.get_toolchain_versions') as m_get_toolchain_versions, \
41+
mock.patch('pythonforandroid.build.get_ndk_platform_dir') as m_get_ndk_platform_dir, \
42+
mock.patch('pythonforandroid.toolchain.build_dist_from_args') as m_build_dist_from_args:
43+
m_get_available_apis.return_value = [27]
44+
m_get_toolchain_versions.return_value = (['4.9'], True)
45+
m_get_ndk_platform_dir.return_value = (
46+
'/tmp/android-ndk/platforms/android-21/arch-arm', True)
47+
ToolchainCL()
48+
assert m_get_available_apis.call_args_list == [mock.call('/tmp/android-sdk')]
49+
assert m_get_toolchain_versions.call_args_list == [
50+
mock.call('/tmp/android-ndk', mock.ANY)]
51+
assert m_build_dist_from_args.call_count == 1
52+
53+
def test_create_no_sdk_dir(self):
54+
"""
55+
The `--sdk-dir` is mandatory to `create` a distribution.
56+
"""
57+
argv = ['toolchain.py', 'create']
58+
with mock.patch('sys.argv', argv), pytest.raises(BuildInterruptingException) as ex_info:
59+
ToolchainCL()
60+
assert ex_info.value.message == 'Android SDK dir was not specified, exiting.'

0 commit comments

Comments
 (0)