/
test_packages.py
191 lines (148 loc) · 7 KB
/
test_packages.py
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
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
"""
test_packages
~~~~~~~~~~~~~~~
This test module tests if R and Python packages installed can be imported.
It's a basic test aiming to prove that the package is working properly.
The goal is to detect import errors that can be caused by incompatibilities between packages, for example:
- #1012: issue importing `sympy`
- #966: issue importing `pyarrow`
This module checks dynamically, through the `CondaPackageHelper`,
only the requested packages i.e. packages requested by `mamba install` in the `Dockerfile`s.
This means that it does not check dependencies.
This choice is a tradeoff to cover the main requirements while achieving reasonable test duration.
However it could be easily changed (or completed) to cover also dependencies.
Use `package_helper.installed_packages()` instead of `package_helper.requested_packages()`.
Example:
$ make test/base-notebook
# [...]
# test/test_packages.py::test_python_packages
# tests/base-notebook/test_packages.py::test_python_packages
# ---------------------------------------------------------------------------------------------- live log setup ----------------------------------------------------------------------------------------------
# 2022-02-17 16:44:36 [ INFO] Starting container jupyter/base-notebook ... (package_helper.py:55)
# 2022-02-17 16:44:36 [ INFO] Running jupyter/base-notebook with args {'detach': True, 'tty': True, 'command': ['start.sh', 'bash', '-c', 'sleep infinity']} ... (conftest.py:95)
# 2022-02-17 16:44:37 [ INFO] Grabbing the list of manually requested packages ... (package_helper.py:83)
# ---------------------------------------------------------------------------------------------- live log call -----------------------------------------------------------------------------------------------
# 2022-02-17 16:44:38 [ INFO] Testing the import of packages ... (test_packages.py:144)
# 2022-02-17 16:44:38 [ INFO] Trying to import mamba (test_packages.py:146)
# [...]
"""
import logging
import pytest # type: ignore
from conftest import TrackedContainer
from typing import Callable, Iterable
from package_helper import CondaPackageHelper
LOGGER = logging.getLogger(__name__)
# Mapping between package and module name
PACKAGE_MAPPING = {
# Python
"beautifulsoup4": "bs4",
"matplotlib-base": "matplotlib",
"pytables": "tables",
"scikit-image": "skimage",
"scikit-learn": "sklearn",
"spylon-kernel": "spylon_kernel",
# R
"randomforest": "randomForest",
"rcurl": "RCurl",
"rodbc": "RODBC",
"rsqlite": "DBI",
}
# List of packages that cannot be tested in a standard way
EXCLUDED_PACKAGES = [
"bzip2",
"ca-certificates",
"conda-forge::blas[build=openblas]",
"hdf5",
# TODO(asalikhov)
# When we remove a workaround for arm regarding mamba, we can
# test installation of mamba as well and remove this exception.
# See: <https://github.com/jupyter/docker-stacks/issues/1539>
"mamba[version='<0.18']",
"openssl",
"protobuf",
"python",
"r-irkernel",
"unixodbc",
]
@pytest.fixture(scope="function")
def package_helper(container: TrackedContainer) -> CondaPackageHelper:
"""Return a package helper object that can be used to perform tests on installed packages"""
return CondaPackageHelper(container)
@pytest.fixture(scope="function")
def packages(package_helper: CondaPackageHelper) -> dict[str, set[str]]:
"""Return the list of requested packages (i.e. packages explicitly installed excluding dependencies)"""
return package_helper.requested_packages()
def get_package_import_name(package: str) -> str:
"""Perform a mapping between the python package name and the name used for the import"""
return PACKAGE_MAPPING.get(package, package)
def excluded_package_predicate(package: str) -> bool:
"""Return whether a package is excluded from the list
(i.e. a package that cannot be tested with standard imports)"""
return package in EXCLUDED_PACKAGES
def python_package_predicate(package: str) -> bool:
"""Predicate matching python packages"""
return not excluded_package_predicate(package) and not r_package_predicate(package)
def r_package_predicate(package: str) -> bool:
"""Predicate matching R packages"""
return not excluded_package_predicate(package) and package.startswith("r-")
def _check_import_package(
package_helper: CondaPackageHelper, command: list[str]
) -> None:
"""Generic function executing a command"""
LOGGER.debug(f"Trying to import a package with [{command}] ...")
exec_result = package_helper.running_container.exec_run(command)
assert (
exec_result.exit_code == 0
), f"Import package failed, output: {exec_result.output}"
def check_import_python_package(
package_helper: CondaPackageHelper, package: str
) -> None:
"""Try to import a Python package from the command line"""
_check_import_package(package_helper, ["python", "-c", f"import {package}"])
def check_import_r_package(package_helper: CondaPackageHelper, package: str) -> None:
"""Try to import a R package from the command line"""
_check_import_package(package_helper, ["R", "--slave", "-e", f"library({package})"])
def _check_import_packages(
package_helper: CondaPackageHelper,
packages_to_check: Iterable[str],
check_function: Callable[[CondaPackageHelper, str], None],
) -> None:
"""Test if packages can be imported
Note: using a list of packages instead of a fixture for the list of packages
since pytest prevents use of multiple yields
"""
failures = {}
LOGGER.info("Testing the import of packages ...")
for package in packages_to_check:
LOGGER.info(f"Trying to import {package}")
try:
check_function(package_helper, package)
except AssertionError as err:
failures[package] = err
if len(failures) > 0:
raise AssertionError(failures)
@pytest.fixture(scope="function")
def r_packages(packages: dict[str, set[str]]) -> Iterable[str]:
"""Return an iterable of R packages"""
# package[2:] is to remove the leading "r-" appended on R packages
return map(
lambda package: get_package_import_name(package[2:]),
filter(r_package_predicate, packages),
)
def test_r_packages(
package_helper: CondaPackageHelper, r_packages: Iterable[str]
) -> None:
"""Test the import of specified R packages"""
_check_import_packages(package_helper, r_packages, check_import_r_package)
@pytest.fixture(scope="function")
def python_packages(packages: dict[str, set[str]]) -> Iterable[str]:
"""Return an iterable of Python packages"""
return map(get_package_import_name, filter(python_package_predicate, packages))
def test_python_packages(
package_helper: CondaPackageHelper,
python_packages: Iterable[str],
) -> None:
"""Test the import of specified python packages"""
_check_import_packages(package_helper, python_packages, check_import_python_package)