Skip to content

Commit ab95ee6

Browse files
committed
✅ unit test patching & checkdependencies modules
- tests/test_patching.py: tests covering platform detection, architecture checks, API level comparisons, version checking, and logical conjunctions used in recipe patch conditionals - tests/test_checkdependencies.py: tests covering module import verification, version requirement checking, and environment variable handling
1 parent 4536b72 commit ab95ee6

File tree

2 files changed

+521
-0
lines changed

2 files changed

+521
-0
lines changed

tests/test_checkdependencies.py

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import sys
2+
from unittest import mock
3+
4+
from pythonforandroid import checkdependencies
5+
6+
7+
class TestCheckPythonDependencies:
8+
"""Test check_python_dependencies function."""
9+
10+
@mock.patch('pythonforandroid.checkdependencies.import_module')
11+
def test_all_modules_present(self, mock_import):
12+
"""Test that check_python_dependencies completes when all modules are present."""
13+
# Mock all required modules
14+
mock_colorama = mock.Mock()
15+
mock_colorama.__version__ = '0.4.0'
16+
mock_sh = mock.Mock()
17+
mock_sh.__version__ = '1.12'
18+
mock_appdirs = mock.Mock()
19+
mock_jinja2 = mock.Mock()
20+
21+
def import_side_effect(name):
22+
if name == 'colorama':
23+
return mock_colorama
24+
elif name == 'sh':
25+
return mock_sh
26+
elif name == 'appdirs':
27+
return mock_appdirs
28+
elif name == 'jinja2':
29+
return mock_jinja2
30+
raise ImportError(f"No module named '{name}'")
31+
32+
mock_import.side_effect = import_side_effect
33+
34+
with mock.patch.object(sys, 'modules', {
35+
'colorama': mock_colorama,
36+
'sh': mock_sh,
37+
'appdirs': mock_appdirs,
38+
'jinja2': mock_jinja2
39+
}):
40+
checkdependencies.check_python_dependencies()
41+
42+
@mock.patch('builtins.exit')
43+
@mock.patch('builtins.print')
44+
@mock.patch('pythonforandroid.checkdependencies.import_module')
45+
def test_missing_module_without_version(self, mock_import, mock_print, mock_exit):
46+
"""Test error message when module without version requirement is missing."""
47+
modules_dict = {}
48+
49+
def import_side_effect(name):
50+
if name == 'appdirs':
51+
raise ImportError(f"No module named '{name}'")
52+
mock_mod = mock.Mock()
53+
mock_mod.__version__ = '1.0'
54+
modules_dict[name] = mock_mod
55+
return mock_mod
56+
57+
mock_import.side_effect = import_side_effect
58+
59+
with mock.patch.object(sys, 'modules', modules_dict):
60+
checkdependencies.check_python_dependencies()
61+
62+
# Verify error message was printed
63+
error_calls = [str(call) for call in mock_print.call_args_list]
64+
assert any('appdirs' in call and 'ERROR' in call for call in error_calls)
65+
mock_exit.assert_called_once_with(1)
66+
67+
@mock.patch('builtins.exit')
68+
@mock.patch('builtins.print')
69+
@mock.patch('pythonforandroid.checkdependencies.import_module')
70+
def test_missing_module_with_version(self, mock_import, mock_print, mock_exit):
71+
"""Test error message when module with version requirement is missing."""
72+
modules_dict = {}
73+
74+
def import_side_effect(name):
75+
if name == 'colorama':
76+
raise ImportError(f"No module named '{name}'")
77+
mock_mod = mock.Mock()
78+
mock_mod.__version__ = '1.0'
79+
modules_dict[name] = mock_mod
80+
return mock_mod
81+
82+
mock_import.side_effect = import_side_effect
83+
84+
with mock.patch.object(sys, 'modules', modules_dict):
85+
checkdependencies.check_python_dependencies()
86+
87+
# Verify error message includes version requirement
88+
error_calls = [str(call) for call in mock_print.call_args_list]
89+
assert any('colorama' in call and '0.3.3' in call for call in error_calls)
90+
mock_exit.assert_called_once_with(1)
91+
92+
@mock.patch('builtins.exit')
93+
@mock.patch('builtins.print')
94+
@mock.patch('pythonforandroid.checkdependencies.import_module')
95+
def test_module_version_too_old(self, mock_import, mock_print, mock_exit):
96+
"""Test error when module version is too old."""
97+
mock_colorama = mock.Mock()
98+
mock_colorama.__version__ = '0.2.0' # Too old, needs 0.3.3
99+
modules_dict = {'colorama': mock_colorama}
100+
101+
def import_side_effect(name):
102+
if name == 'colorama':
103+
return mock_colorama
104+
mock_mod = mock.Mock()
105+
mock_mod.__version__ = '1.0'
106+
modules_dict[name] = mock_mod
107+
return mock_mod
108+
109+
mock_import.side_effect = import_side_effect
110+
111+
with mock.patch.object(sys, 'modules', modules_dict):
112+
checkdependencies.check_python_dependencies()
113+
114+
# Verify error message about version
115+
error_calls = [str(call) for call in mock_print.call_args_list]
116+
assert any('version' in call.lower() and 'colorama' in call for call in error_calls)
117+
mock_exit.assert_called_once_with(1)
118+
119+
@mock.patch('pythonforandroid.checkdependencies.import_module')
120+
def test_module_version_acceptable(self, mock_import):
121+
"""Test that acceptable versions pass."""
122+
mock_colorama = mock.Mock()
123+
mock_colorama.__version__ = '0.4.0' # Newer than 0.3.3
124+
mock_sh = mock.Mock()
125+
mock_sh.__version__ = '1.12' # Newer than 1.10
126+
127+
def import_side_effect(name):
128+
if name == 'colorama':
129+
return mock_colorama
130+
elif name == 'sh':
131+
return mock_sh
132+
mock_mod = mock.Mock()
133+
return mock_mod
134+
135+
mock_import.side_effect = import_side_effect
136+
137+
with mock.patch.object(sys, 'modules', {
138+
'colorama': mock_colorama,
139+
'sh': mock_sh
140+
}):
141+
# Should complete without error
142+
checkdependencies.check_python_dependencies()
143+
144+
@mock.patch('pythonforandroid.checkdependencies.import_module')
145+
def test_module_without_version_attribute(self, mock_import):
146+
"""Test handling of modules that don't have __version__."""
147+
mock_colorama = mock.Mock(spec=[]) # No __version__ attribute
148+
modules_dict = {'colorama': mock_colorama}
149+
150+
def import_side_effect(name):
151+
if name == 'colorama':
152+
return mock_colorama
153+
mock_mod = mock.Mock()
154+
modules_dict[name] = mock_mod
155+
return mock_mod
156+
157+
mock_import.side_effect = import_side_effect
158+
159+
with mock.patch.object(sys, 'modules', modules_dict):
160+
# Should complete without error (version check is skipped)
161+
checkdependencies.check_python_dependencies()
162+
163+
164+
class TestCheck:
165+
"""Test the main check() function."""
166+
167+
@mock.patch('pythonforandroid.checkdependencies.check_python_dependencies')
168+
@mock.patch('pythonforandroid.checkdependencies.check_and_install_default_prerequisites')
169+
def test_check_with_skip_prerequisites(self, mock_prereqs, mock_python_deps):
170+
"""Test check() skips prerequisites when SKIP_PREREQUISITES_CHECK=1."""
171+
with mock.patch.dict('os.environ', {'SKIP_PREREQUISITES_CHECK': '1'}):
172+
checkdependencies.check()
173+
174+
mock_prereqs.assert_not_called()
175+
mock_python_deps.assert_called_once()
176+
177+
@mock.patch('pythonforandroid.checkdependencies.check_python_dependencies')
178+
@mock.patch('pythonforandroid.checkdependencies.check_and_install_default_prerequisites')
179+
def test_check_without_skip(self, mock_prereqs, mock_python_deps):
180+
"""Test check() runs prerequisites when SKIP_PREREQUISITES_CHECK is not set."""
181+
with mock.patch.dict('os.environ', {}, clear=True):
182+
checkdependencies.check()
183+
184+
mock_prereqs.assert_called_once()
185+
mock_python_deps.assert_called_once()
186+
187+
@mock.patch('pythonforandroid.checkdependencies.check_python_dependencies')
188+
@mock.patch('pythonforandroid.checkdependencies.check_and_install_default_prerequisites')
189+
def test_check_with_skip_set_to_zero(self, mock_prereqs, mock_python_deps):
190+
"""Test check() runs prerequisites when SKIP_PREREQUISITES_CHECK=0."""
191+
with mock.patch.dict('os.environ', {'SKIP_PREREQUISITES_CHECK': '0'}):
192+
checkdependencies.check()
193+
194+
mock_prereqs.assert_called_once()
195+
mock_python_deps.assert_called_once()

0 commit comments

Comments
 (0)