Skip to content

Commit 6526de3

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 b7ff762 commit 6526de3

File tree

2 files changed

+493
-0
lines changed

2 files changed

+493
-0
lines changed

tests/test_checkdependencies.py

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

0 commit comments

Comments
 (0)