forked from beeware/briefcase
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_ToolCache.py
More file actions
268 lines (214 loc) · 9.03 KB
/
test_ToolCache.py
File metadata and controls
268 lines (214 loc) · 9.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
import importlib
import locale
import os
import platform
import shutil
import sys
from pathlib import Path
from unittest.mock import MagicMock
import httpx
import pytest
from cookiecutter.main import cookiecutter
import briefcase.integrations
from briefcase.integrations.base import ToolCache
from .test_tool_registry import integrations_modules, tools_for_module
def test_toolcache_typing():
"""Tool typing for ToolCache is correct."""
# Tools that are intentionally not annotated in ToolCache.
tools_unannotated = {"cookiecutter"}
# Tool names to exclude from the dynamic annotation checks; they are manually checked.
tool_names_skip_dynamic_check = {
"app_context", # Tested by the Docker module
"git", # An external API, not a Briefcase Tool
"xcode_cli", # Tested by the Xcode module
"ETC_OS_RELEASE", # A constant, not a tool
}
# Tool classes to exclude from dynamic annotation checks.
tool_klasses_skip_dynamic_checks = {
"Git",
"DockerAppContext",
"NativeAppContext",
"LinuxDeployQtPlugin",
"LinuxDeployGtkPlugin",
"LinuxDeployURLPlugin",
"LinuxDeployLocalFilePlugin",
}
# Ensure all modules containing Tools are exported in ``briefcase.integrations``.
assert sorted(integrations_modules()) == sorted(briefcase.integrations.__all__)
# Ensure defined Tool modules/classes are annotated in ToolCache.
for tool_module_name in briefcase.integrations.__all__:
if tool_module_name not in tools_unannotated:
assert tool_module_name in ToolCache.__annotations__
for tool_name in tools_for_module(tool_module_name):
if tool_name not in tool_klasses_skip_dynamic_checks:
assert tool_name in ToolCache.__annotations__.values()
# Ensure annotated tools use valid Tool names.
for tool_name, tool_klass_name in ToolCache.__annotations__.items():
if tool_name not in tool_names_skip_dynamic_check:
assert tool_name in briefcase.integrations.__all__
assert tool_klass_name in tools_for_module(tool_name)
tool_klass = getattr(
importlib.import_module(f"briefcase.integrations.{tool_name}"),
tool_klass_name,
)
assert tool_name == tool_klass.name
# Manually check tools that aren't Tool classes or use special annotations.
app_context_klasses = [
briefcase.integrations.docker.DockerAppContext.__name__,
briefcase.integrations.subprocess.Subprocess.__name__,
]
app_context_annotated = ToolCache.__annotations__["app_context"].split(" | ")
assert sorted(app_context_klasses) == sorted(app_context_annotated)
assert ToolCache.__annotations__["git"] == "git_"
assert ToolCache.__annotations__["xcode_cli"] == "XcodeCliTools"
assert ToolCache.__annotations__["ETC_OS_RELEASE"] == "Path"
def test_third_party_tools_available():
"""Third party tools are available."""
assert ToolCache.os is os
assert ToolCache.platform is platform
assert ToolCache.shutil is shutil
assert ToolCache.sys is sys
assert ToolCache.cookiecutter is cookiecutter
assert ToolCache.httpx is httpx
def test_always_true(simple_tools, tmp_path):
"""Implicit boolean casts are always True."""
assert simple_tools or False
simple_tools["app-1"].app_context = "tool"
assert simple_tools["app-1"] or False
def test_mapping_protocol(simple_tools):
"""ToolCache is a mapping."""
simple_tools["app-1"].tool = "tool 1"
simple_tools["app-2"].tool = "tool 2"
assert list(simple_tools) == ["app-1", "app-2"]
assert len(simple_tools) == 2
assert simple_tools["app-1"].tool == "tool 1"
assert simple_tools["app-2"].tool == "tool 2"
def test_host_arch_and_os(simple_tools):
"""Arch and OS represent host arch and OS."""
assert simple_tools.host_arch == platform.machine()
assert simple_tools.host_os == platform.system()
def test_get_host_arch_windows_arm64(dummy_console, monkeypatch, tmp_path):
"""ARM64 is detected on Windows with a pre-3.12 Python x86_64 interpreter."""
mock_ctypes = MagicMock()
mock_ctypes.c_ushort.return_value.value = 0xAA64 # IMAGE_FILE_MACHINE_ARM64
mock_platform = MagicMock()
mock_platform.machine.return_value = "AMD64"
mock_platform.system.return_value = "Windows"
mock_sys = MagicMock()
mock_sys.version_info = (3, 11, 0)
mock_sys.getwindowsversion.return_value.build = 16299
mock_sys.maxsize = 2**64
monkeypatch.setattr(ToolCache, "platform", mock_platform)
monkeypatch.setattr(ToolCache, "sys", mock_sys)
monkeypatch.setitem(sys.modules, "ctypes", mock_ctypes)
tools = ToolCache(console=dummy_console, base_path=tmp_path)
assert tools.host_arch == "ARM64"
def test_get_host_arch_windows_not_arm64(dummy_console, monkeypatch, tmp_path):
"""AMD64 is returned when IsWow64Process2 reports the native machine is not
ARM64."""
mock_ctypes = MagicMock()
mock_ctypes.windll.kernel32.IsWow64Process2.return_value = 0
mock_platform = MagicMock()
mock_platform.machine.return_value = "AMD64"
mock_platform.system.return_value = "Windows"
mock_sys = MagicMock()
mock_sys.version_info = (3, 11, 0)
mock_sys.getwindowsversion.return_value.build = 16299
mock_sys.maxsize = 2**64
monkeypatch.setattr(ToolCache, "platform", mock_platform)
monkeypatch.setattr(ToolCache, "sys", mock_sys)
monkeypatch.setitem(sys.modules, "ctypes", mock_ctypes)
tools = ToolCache(console=dummy_console, base_path=tmp_path)
assert tools.host_arch == "AMD64"
def test_get_host_arch_windows_python312(dummy_console, monkeypatch, tmp_path):
"""On Python 3.12+, IsWow64Process2 is not called; platform.machine() is used
directly."""
mock_platform = MagicMock()
mock_platform.machine.return_value = "AMD64"
mock_platform.system.return_value = "Windows"
mock_sys = MagicMock()
mock_sys.version_info = (3, 12, 0)
mock_sys.maxsize = 2**64
monkeypatch.setattr(ToolCache, "platform", mock_platform)
monkeypatch.setattr(ToolCache, "sys", mock_sys)
tools = ToolCache(console=dummy_console, base_path=tmp_path)
assert tools.host_arch == "AMD64"
def test_base_path_is_path(dummy_console, simple_tools):
"""Base path is always a Path."""
# The BaseCommand tests have much more extensive tests for this path.
assert isinstance(simple_tools.base_path, Path)
tools = ToolCache(
console=dummy_console,
base_path="/home/data",
)
assert isinstance(tools.base_path, Path)
def test_home_path_default(simple_tools):
"""Home path default is current user's home directory."""
assert simple_tools.home_path == Path.home()
@pytest.mark.skipif(platform.system() == "Windows", reason="Linux/macOS specific tests")
@pytest.mark.parametrize(
("home_path", "expected_path"),
[
(None, Path.home()),
("/path/to/home", Path("/path/to/home")),
("~", Path.home()),
("~/dir", Path.home() / "dir"),
],
)
def test_nonwindows_home_path(dummy_console, home_path, expected_path, tmp_path):
"""Home path is always expanded or defaulted."""
tools = ToolCache(
console=dummy_console,
base_path=tmp_path,
home_path=home_path,
)
assert tools.home_path == expected_path
@pytest.mark.skipif(platform.system() != "Windows", reason="Windows specific tests")
@pytest.mark.parametrize(
("home_path", "expected_path"),
[
(None, Path.home()),
("Y:\\path\\to\\home", Path("Y:\\path\\to\\home")),
("~", Path.home()),
("~/dir", Path.home() / "dir"),
],
)
def test_windows_home_path(dummy_console, home_path, expected_path, tmp_path):
"""Home path is always expanded or defaulted."""
tools = ToolCache(
console=dummy_console,
base_path=tmp_path,
home_path=home_path,
)
assert tools.home_path == expected_path
@pytest.mark.parametrize(("maxsize", "is_32bit"), [(2**32, True), (2**64, False)])
def test_is_32bit_python(dummy_console, maxsize, is_32bit, monkeypatch, tmp_path):
"""Whether Python is 32bits is sensitive to `sys.maxsize`."""
monkeypatch.setattr(sys, "maxsize", maxsize)
tools = ToolCache(
console=dummy_console,
base_path=tmp_path,
)
assert tools.is_32bit_python is is_32bit
@pytest.mark.parametrize(
("mock_encoding", "expected_encoding"),
[
("iso-123", "ISO-123"),
("", "ISO-4242"),
(None, "ISO-4242"),
],
)
def test_system_encoding(simple_tools, mock_encoding, expected_encoding, monkeypatch):
"""The expected system encoding is returned."""
if sys.version_info < (3, 11):
monkeypatch.setattr(
locale, "getdefaultlocale", MagicMock(return_value=("aa_BB", mock_encoding))
)
else:
monkeypatch.setattr(
locale, "getencoding", MagicMock(return_value=mock_encoding)
)
monkeypatch.setattr(
briefcase.integrations.base, "DEFAULT_SYSTEM_ENCODING", "ISO-4242"
)
assert simple_tools.system_encoding == expected_encoding