Add a test-sources configuration option. (#2062)
* Add test_sources configuration option. * Add pyodide implementation. * Modify default test behavior to run in the project directory. * Update the test for running in the project directory. * Apply suggestions from code review Co-authored-by: Joe Rickerby <joerick@mac.com> * Add shlex quoting to test-sources. * Restructure ctypes example to avoid issues running from the project folder. --------- Co-authored-by: Joe Rickerby <joerick@mac.com>
This commit is contained in:
co-authored by
Joe Rickerby
parent
4d0d9f6fd0
commit
0b82613d6f
@@ -143,6 +143,7 @@ Options
|
||||
| | [`CIBW_DEPENDENCY_VERSIONS`](https://cibuildwheel.pypa.io/en/stable/options/#dependency-versions) | Specify how cibuildwheel controls the versions of the tools it uses |
|
||||
| **Testing** | [`CIBW_TEST_COMMAND`](https://cibuildwheel.pypa.io/en/stable/options/#test-command) | Execute a shell command to test each built wheel |
|
||||
| | [`CIBW_BEFORE_TEST`](https://cibuildwheel.pypa.io/en/stable/options/#before-test) | Execute a shell command before testing each wheel |
|
||||
| | [`CIBW_TEST_SOURCES`](https://cibuildwheel.pypa.io/en/stable/options/#test-sources) | Files and folders from the source tree that are copied into an isolated tree before running the tests |
|
||||
| | [`CIBW_TEST_REQUIRES`](https://cibuildwheel.pypa.io/en/stable/options/#test-requires) | Install Python dependencies before running the tests |
|
||||
| | [`CIBW_TEST_EXTRAS`](https://cibuildwheel.pypa.io/en/stable/options/#test-extras) | Install your wheel for testing using extras_require |
|
||||
| | [`CIBW_TEST_SKIP`](https://cibuildwheel.pypa.io/en/stable/options/#test-skip) | Skip running tests on some builds |
|
||||
|
||||
+14
-4
@@ -21,12 +21,12 @@ from .typing import PathOrStr
|
||||
from .util import (
|
||||
BuildFrontendConfig,
|
||||
BuildSelector,
|
||||
copy_test_sources,
|
||||
find_compatible_wheel,
|
||||
get_build_verbosity_extra_flags,
|
||||
prepare_command,
|
||||
read_python_configs,
|
||||
split_config_settings,
|
||||
test_fail_cwd_file,
|
||||
unwrap,
|
||||
)
|
||||
|
||||
@@ -401,9 +401,19 @@ def build_in_container(
|
||||
package=container_package_dir,
|
||||
wheel=wheel_to_test,
|
||||
)
|
||||
test_cwd = testing_temp_dir / "test_cwd"
|
||||
container.call(["mkdir", "-p", test_cwd])
|
||||
container.copy_into(test_fail_cwd_file, test_cwd / "test_fail.py")
|
||||
|
||||
if build_options.test_sources:
|
||||
test_cwd = testing_temp_dir / "test_cwd"
|
||||
container.call(["mkdir", "-p", test_cwd])
|
||||
copy_test_sources(
|
||||
build_options.test_sources,
|
||||
build_options.package_dir,
|
||||
test_cwd,
|
||||
copy_into=container.copy_into,
|
||||
)
|
||||
else:
|
||||
# There are no test sources. Run the tests in the project directory.
|
||||
test_cwd = PurePosixPath(container_project_path)
|
||||
|
||||
container.call(["sh", "-c", test_command_prepared], cwd=test_cwd, env=virtualenv_env)
|
||||
|
||||
|
||||
+12
-4
@@ -31,6 +31,7 @@ from .util import (
|
||||
BuildSelector,
|
||||
call,
|
||||
combine_constraints,
|
||||
copy_test_sources,
|
||||
detect_ci_provider,
|
||||
download,
|
||||
find_compatible_wheel,
|
||||
@@ -44,7 +45,6 @@ from .util import (
|
||||
read_python_configs,
|
||||
shell,
|
||||
split_config_settings,
|
||||
test_fail_cwd_file,
|
||||
unwrap,
|
||||
virtualenv,
|
||||
)
|
||||
@@ -736,9 +736,17 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
wheel=repaired_wheel,
|
||||
)
|
||||
|
||||
test_cwd = identifier_tmp_dir / "test_cwd"
|
||||
test_cwd.mkdir(exist_ok=True)
|
||||
(test_cwd / "test_fail.py").write_text(test_fail_cwd_file.read_text())
|
||||
if build_options.test_sources:
|
||||
test_cwd = identifier_tmp_dir / "test_cwd"
|
||||
test_cwd.mkdir(exist_ok=True)
|
||||
copy_test_sources(
|
||||
build_options.test_sources,
|
||||
build_options.package_dir,
|
||||
test_cwd,
|
||||
)
|
||||
else:
|
||||
# There are no test sources. Run the tests in the project directory.
|
||||
test_cwd = Path(".").resolve()
|
||||
|
||||
shell_with_arch(test_command_prepared, cwd=test_cwd, env=virtualenv_env)
|
||||
|
||||
|
||||
+11
-3
@@ -9,7 +9,7 @@ import enum
|
||||
import functools
|
||||
import shlex
|
||||
import textwrap
|
||||
from collections.abc import Generator, Iterable, Set
|
||||
from collections.abc import Callable, Generator, Iterable, Set
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal, Mapping, Sequence, Union # noqa: TID251
|
||||
|
||||
@@ -92,6 +92,7 @@ class BuildOptions:
|
||||
dependency_constraints: DependencyConstraints | None
|
||||
test_command: str | None
|
||||
before_test: str | None
|
||||
test_sources: list[str]
|
||||
test_requires: list[str]
|
||||
test_extras: str
|
||||
test_groups: list[str]
|
||||
@@ -171,11 +172,12 @@ class ListFormat(OptionFormat):
|
||||
A format that joins lists with a separator.
|
||||
"""
|
||||
|
||||
def __init__(self, sep: str) -> None:
|
||||
def __init__(self, sep: str, quote: Callable[[str], str] | None = None) -> None:
|
||||
self.sep = sep
|
||||
self.quote = quote if quote else lambda s: s
|
||||
|
||||
def format_list(self, value: SettingList) -> str:
|
||||
return self.sep.join(str(v) for v in value)
|
||||
return self.sep.join(self.quote(str(v)) for v in value)
|
||||
|
||||
def merge_values(self, before: str, after: str) -> str:
|
||||
return f"{before}{self.sep}{after}"
|
||||
@@ -711,6 +713,11 @@ class Options:
|
||||
dependency_versions = self.reader.get("dependency-versions")
|
||||
test_command = self.reader.get("test-command", option_format=ListFormat(sep=" && "))
|
||||
before_test = self.reader.get("before-test", option_format=ListFormat(sep=" && "))
|
||||
test_sources = shlex.split(
|
||||
self.reader.get(
|
||||
"test-sources", option_format=ListFormat(sep=" ", quote=shlex.quote)
|
||||
)
|
||||
)
|
||||
test_requires = self.reader.get(
|
||||
"test-requires", option_format=ListFormat(sep=" ")
|
||||
).split()
|
||||
@@ -819,6 +826,7 @@ class Options:
|
||||
return BuildOptions(
|
||||
globals=self.globals,
|
||||
test_command=test_command,
|
||||
test_sources=test_sources,
|
||||
test_requires=[*test_requires, *test_requirements_from_groups],
|
||||
test_extras=test_extras,
|
||||
test_groups=test_groups,
|
||||
|
||||
+12
-4
@@ -21,6 +21,7 @@ from .util import (
|
||||
BuildSelector,
|
||||
call,
|
||||
combine_constraints,
|
||||
copy_test_sources,
|
||||
download,
|
||||
ensure_node,
|
||||
extract_zip,
|
||||
@@ -31,7 +32,6 @@ from .util import (
|
||||
read_python_configs,
|
||||
shell,
|
||||
split_config_settings,
|
||||
test_fail_cwd_file,
|
||||
virtualenv,
|
||||
)
|
||||
|
||||
@@ -387,9 +387,17 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
package=build_options.package_dir.resolve(),
|
||||
)
|
||||
|
||||
test_cwd = identifier_tmp_dir / "test_cwd"
|
||||
test_cwd.mkdir(exist_ok=True)
|
||||
(test_cwd / "test_fail.py").write_text(test_fail_cwd_file.read_text())
|
||||
if build_options.test_sources:
|
||||
test_cwd = identifier_tmp_dir / "test_cwd"
|
||||
test_cwd.mkdir(exist_ok=True)
|
||||
copy_test_sources(
|
||||
build_options.test_sources,
|
||||
build_options.package_dir,
|
||||
test_cwd,
|
||||
)
|
||||
else:
|
||||
# There are no test sources. Run the tests in the project directory.
|
||||
test_cwd = Path(".").resolve()
|
||||
|
||||
shell(test_command_prepared, cwd=test_cwd, env=virtualenv_env)
|
||||
|
||||
|
||||
@@ -426,6 +426,21 @@
|
||||
],
|
||||
"title": "CIBW_TEST_EXTRAS"
|
||||
},
|
||||
"test-sources": {
|
||||
"description": "Test files that are required by the test environment",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": "CIBW_TEST_SOURCES"
|
||||
},
|
||||
"test-groups": {
|
||||
"description": "Install extra groups when testing",
|
||||
"oneOf": [
|
||||
@@ -529,6 +544,9 @@
|
||||
"test-extras": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-sources": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-requires": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
}
|
||||
@@ -618,6 +636,9 @@
|
||||
"test-extras": {
|
||||
"$ref": "#/properties/test-extras"
|
||||
},
|
||||
"test-sources": {
|
||||
"$ref": "#/properties/test-sources"
|
||||
},
|
||||
"test-groups": {
|
||||
"$ref": "#/properties/test-groups"
|
||||
},
|
||||
@@ -728,6 +749,9 @@
|
||||
"test-extras": {
|
||||
"$ref": "#/properties/test-extras"
|
||||
},
|
||||
"test-sources": {
|
||||
"$ref": "#/properties/test-sources"
|
||||
},
|
||||
"test-groups": {
|
||||
"$ref": "#/properties/test-groups"
|
||||
},
|
||||
@@ -776,6 +800,9 @@
|
||||
"test-extras": {
|
||||
"$ref": "#/properties/test-extras"
|
||||
},
|
||||
"test-sources": {
|
||||
"$ref": "#/properties/test-sources"
|
||||
},
|
||||
"test-groups": {
|
||||
"$ref": "#/properties/test-groups"
|
||||
},
|
||||
@@ -837,6 +864,9 @@
|
||||
"test-extras": {
|
||||
"$ref": "#/properties/test-extras"
|
||||
},
|
||||
"test-sources": {
|
||||
"$ref": "#/properties/test-sources"
|
||||
},
|
||||
"test-groups": {
|
||||
"$ref": "#/properties/test-groups"
|
||||
},
|
||||
@@ -885,6 +915,9 @@
|
||||
"test-extras": {
|
||||
"$ref": "#/properties/test-extras"
|
||||
},
|
||||
"test-sources": {
|
||||
"$ref": "#/properties/test-sources"
|
||||
},
|
||||
"test-groups": {
|
||||
"$ref": "#/properties/test-groups"
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@ repair-wheel-command = ""
|
||||
|
||||
test-command = ""
|
||||
before-test = ""
|
||||
test-sources = []
|
||||
test-requires = []
|
||||
test-extras = []
|
||||
test-groups = []
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# this file is copied to the testing cwd, to raise the below error message if
|
||||
# pytest/unittest is run from there.
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class TestStringMethods(unittest.TestCase):
|
||||
def test_fail(self):
|
||||
self.fail(
|
||||
"cibuildwheel executes tests from a different working directory to "
|
||||
"your project. This ensures only your wheel is imported, preventing "
|
||||
"Python from accessing files that haven't been packaged into the "
|
||||
"wheel. Please specify a path to your tests when invoking pytest "
|
||||
"using the {project} placeholder, e.g. `pytest {project}` or "
|
||||
"`pytest {project}/tests`. cibuildwheel will replace {project} with "
|
||||
"the path to your project."
|
||||
)
|
||||
+38
-3
@@ -17,7 +17,7 @@ import time
|
||||
import typing
|
||||
import urllib.request
|
||||
from collections import defaultdict
|
||||
from collections.abc import Generator, Iterable, Mapping, MutableMapping, Sequence
|
||||
from collections.abc import Callable, Generator, Iterable, Mapping, MutableMapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from functools import lru_cache, total_ordering
|
||||
@@ -36,6 +36,7 @@ from packaging.utils import parse_wheel_filename
|
||||
from packaging.version import Version
|
||||
from platformdirs import user_cache_path
|
||||
|
||||
from . import errors
|
||||
from ._compat import tomllib
|
||||
from .architecture import Architecture
|
||||
from .errors import FatalError
|
||||
@@ -66,8 +67,6 @@ install_certifi_script: Final[Path] = resources_dir / "install_certifi.py"
|
||||
|
||||
free_thread_enable_313: Final[Path] = resources_dir / "free-threaded-enable-313.xml"
|
||||
|
||||
test_fail_cwd_file: Final[Path] = resources_dir / "testing_temp_dir_file.py"
|
||||
|
||||
|
||||
class EnableGroups(enum.Enum):
|
||||
"""
|
||||
@@ -425,6 +424,42 @@ def move_file(src_file: Path, dst_file: Path) -> Path:
|
||||
return Path(resulting_file).resolve(strict=True)
|
||||
|
||||
|
||||
def copy_into_local(src: Path, dst: PurePath) -> None:
|
||||
"""Copy a path from src to dst, regardless of whether it's a file or a directory."""
|
||||
# Ensure the target folder location exists
|
||||
Path(dst.parent).mkdir(exist_ok=True, parents=True)
|
||||
|
||||
if src.is_dir():
|
||||
shutil.copytree(src, dst)
|
||||
else:
|
||||
shutil.copy(src, dst)
|
||||
|
||||
|
||||
def copy_test_sources(
|
||||
test_sources: list[str],
|
||||
package_dir: Path,
|
||||
test_dir: PurePath,
|
||||
copy_into: Callable[[Path, PurePath], None] = copy_into_local,
|
||||
) -> None:
|
||||
"""Copy the list of test sources from the package to the test directory.
|
||||
|
||||
:param test_sources: A list of test paths, relative to the package_dir.
|
||||
:param package_dir: The root of the package directory.
|
||||
:param test_dir: The folder where test sources should be placed.
|
||||
:param copy_info: The copy function to use. By default, does a local
|
||||
filesystem copy; but an OCIContainer.copy_info method (or equivalent)
|
||||
can be provided.
|
||||
"""
|
||||
for test_path in test_sources:
|
||||
source = package_dir.resolve() / test_path
|
||||
|
||||
if not source.exists():
|
||||
msg = f"Test source {test_path} does not exist."
|
||||
raise errors.FatalError(msg)
|
||||
|
||||
copy_into(source, test_dir / test_path)
|
||||
|
||||
|
||||
class DependencyConstraints:
|
||||
def __init__(self, base_file_path: Path):
|
||||
assert base_file_path.exists()
|
||||
|
||||
+12
-4
@@ -27,6 +27,7 @@ from .util import (
|
||||
BuildSelector,
|
||||
call,
|
||||
combine_constraints,
|
||||
copy_test_sources,
|
||||
download,
|
||||
extract_zip,
|
||||
find_compatible_wheel,
|
||||
@@ -38,7 +39,6 @@ from .util import (
|
||||
read_python_configs,
|
||||
shell,
|
||||
split_config_settings,
|
||||
test_fail_cwd_file,
|
||||
unwrap,
|
||||
virtualenv,
|
||||
)
|
||||
@@ -572,9 +572,17 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
package=options.globals.package_dir.resolve(),
|
||||
wheel=repaired_wheel,
|
||||
)
|
||||
test_cwd = identifier_tmp_dir / "test_cwd"
|
||||
test_cwd.mkdir()
|
||||
(test_cwd / "test_fail.py").write_text(test_fail_cwd_file.read_text())
|
||||
if build_options.test_sources:
|
||||
test_cwd = identifier_tmp_dir / "test_cwd"
|
||||
test_cwd.mkdir()
|
||||
copy_test_sources(
|
||||
build_options.test_sources,
|
||||
build_options.package_dir,
|
||||
test_cwd,
|
||||
)
|
||||
else:
|
||||
# There are no test sources. Run the tests in the project directory.
|
||||
test_cwd = Path(".").resolve()
|
||||
|
||||
shell(test_command_prepared, cwd=test_cwd, env=virtualenv_env)
|
||||
|
||||
|
||||
+41
-5
@@ -1424,17 +1424,22 @@ Platform-specific environment variables are also available:<br/>
|
||||
> Execute a shell command to test each built wheel
|
||||
|
||||
Shell command to run tests after the build. The wheel will be installed
|
||||
automatically and available for import from the tests. To ensure the wheel is
|
||||
imported by your tests (instead of your source copy), **tests are not run from
|
||||
your project directory**. Use the placeholders `{project}` and `{package}` when
|
||||
specifying paths in your project. If this variable is not set, your wheel will
|
||||
not be installed after building.
|
||||
automatically and available for import from the tests. If this variable is not
|
||||
set, your wheel will not be installed after building.
|
||||
|
||||
By default, tests are executed from from your project directory. When specifying
|
||||
`CIBW_TEST_COMMAND`, you can use the placeholders `{project}` and `{package}` to
|
||||
pass in the location of your test code:
|
||||
|
||||
- `{project}` is an absolute path to the project root - the working directory
|
||||
where cibuildwheel was called.
|
||||
- `{package}` is the path to the package being built - the `package_dir`
|
||||
argument supplied to cibuildwheel on the command line.
|
||||
|
||||
Alternatively, you can use the [`CIBW_TEST_SOURCES`](#test-sources) setting to
|
||||
create a temporary folder populated with a specific subset of project files to
|
||||
run your test suite.
|
||||
|
||||
The command is run in a shell, so you can write things like `cmd1 && cmd2`.
|
||||
|
||||
Platform-specific environment variables are also available:<br/>
|
||||
@@ -1549,6 +1554,37 @@ Platform-specific environment variables are also available:<br/>
|
||||
In configuration files, you can use an array, and the items will be joined with `&&`.
|
||||
|
||||
|
||||
### `CIBW_TEST_SOURCES` {: #test-sources}
|
||||
> Files and folders from the source tree that are copied into an isolated tree before running the tests
|
||||
|
||||
A space-separated list of files and folders, relative to the root of the
|
||||
project, required for running the tests. If specified, these files and folders
|
||||
will be copied into a temporary folder, and that temporary folder will be used
|
||||
as the working directory for running the test suite.
|
||||
|
||||
Platform-specific environment variables are also available:<br/>
|
||||
`CIBW_TEST_SOURCES_MACOS` | `CIBW_TEST_SOURCES_WINDOWS` | `CIBW_TEST_SOURCES_LINUX` | `CIBW_TEST_SOURCES_PYODIDE`
|
||||
|
||||
#### Examples
|
||||
|
||||
!!! tab examples "Environment variables"
|
||||
|
||||
```yaml
|
||||
# Copy the "tests" folder, plus "data/test-image.png" from the source folder to the test folder.
|
||||
CIBW_TEST_SOURCES: tests data/test-image.png
|
||||
```
|
||||
|
||||
!!! tab examples "pyproject.toml"
|
||||
|
||||
```toml
|
||||
# Copy the "tests" folder, plus "data/test-image.png" from the source folder to the test folder.
|
||||
[tool.cibuildwheel]
|
||||
test-sources = ["tests", "data/test-image.png"]
|
||||
```
|
||||
|
||||
In configuration files, you can use an array, and the items will be joined with a space.
|
||||
|
||||
|
||||
### `CIBW_TEST_REQUIRES` {: #test-requires}
|
||||
> Install Python dependencies before running the tests
|
||||
|
||||
|
||||
@@ -101,18 +101,19 @@ ctypes_project.files["setup.py"] = textwrap.dedent(
|
||||
setup(
|
||||
name="ctypesexample",
|
||||
version="1.0.0",
|
||||
package_dir = {"": "src"},
|
||||
py_modules = ["ctypesexample.summing"],
|
||||
ext_modules=[
|
||||
CTypesExtension(
|
||||
"ctypesexample.csumlib",
|
||||
["ctypesexample/csumlib.c"],
|
||||
["src/ctypesexample/csumlib.c"],
|
||||
),
|
||||
],
|
||||
cmdclass={'build_ext': build_ext, 'bdist_wheel': bdist_wheel_abi_none},
|
||||
)
|
||||
"""
|
||||
)
|
||||
ctypes_project.files["ctypesexample/csumlib.c"] = textwrap.dedent(
|
||||
ctypes_project.files["src/ctypesexample/csumlib.c"] = textwrap.dedent(
|
||||
"""
|
||||
#ifdef _WIN32
|
||||
#define LIBRARY_API __declspec(dllexport)
|
||||
@@ -136,7 +137,7 @@ ctypes_project.files["ctypesexample/csumlib.c"] = textwrap.dedent(
|
||||
}
|
||||
"""
|
||||
)
|
||||
ctypes_project.files["ctypesexample/summing.py"] = textwrap.dedent(
|
||||
ctypes_project.files["src/ctypesexample/summing.py"] = textwrap.dedent(
|
||||
"""
|
||||
import ctypes
|
||||
import pathlib
|
||||
|
||||
+36
-27
@@ -186,34 +186,43 @@ def test_failing_test(tmp_path):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_runner", ["pytest", "unittest"])
|
||||
def test_bare_pytest_invocation(
|
||||
tmp_path: Path, capfd: pytest.CaptureFixture[str], test_runner: str
|
||||
) -> None:
|
||||
"""Check that if a user runs pytest in the the test cwd, it raises a helpful error"""
|
||||
def test_bare_pytest_invocation(tmp_path: Path, test_runner: str) -> None:
|
||||
"""Check that if a user runs a bare test suite, it runs in the project folder"""
|
||||
project_dir = tmp_path / "project"
|
||||
output_dir = tmp_path / "output"
|
||||
project_with_a_test.generate(project_dir)
|
||||
|
||||
with pytest.raises(subprocess.CalledProcessError):
|
||||
utils.cibuildwheel_run(
|
||||
project_dir,
|
||||
output_dir=output_dir,
|
||||
add_env={
|
||||
"CIBW_TEST_REQUIRES": "pytest" if test_runner == "pytest" else "",
|
||||
"CIBW_TEST_COMMAND": (
|
||||
"python -m pytest" if test_runner == "pytest" else "python -m unittest"
|
||||
),
|
||||
# Skip CPython 3.8 on macOS arm64, see comment above in
|
||||
# 'test_failing_test'
|
||||
"CIBW_SKIP": "cp38-macosx_arm64",
|
||||
},
|
||||
)
|
||||
|
||||
assert len(os.listdir(output_dir)) == 0
|
||||
|
||||
captured = capfd.readouterr()
|
||||
|
||||
assert (
|
||||
"Please specify a path to your tests when invoking pytest using the {project} placeholder"
|
||||
in captured.out + captured.err
|
||||
actual_wheels = utils.cibuildwheel_run(
|
||||
project_dir,
|
||||
add_env={
|
||||
"CIBW_TEST_REQUIRES": "pytest" if test_runner == "pytest" else "",
|
||||
"CIBW_TEST_COMMAND": (
|
||||
"python -m pytest"
|
||||
if test_runner == "pytest"
|
||||
else "python -m unittest discover test spam_test.py"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
# check that we got the right wheels
|
||||
expected_wheels = utils.expected_wheels("spam", "0.1.0")
|
||||
assert set(actual_wheels) == set(expected_wheels)
|
||||
|
||||
|
||||
def test_test_sources(tmp_path):
|
||||
project_dir = tmp_path / "project"
|
||||
project_with_a_test.generate(project_dir)
|
||||
|
||||
# build and test the wheels in the test cwd, after copying in the test sources.
|
||||
actual_wheels = utils.cibuildwheel_run(
|
||||
project_dir,
|
||||
add_env={
|
||||
"CIBW_TEST_REQUIRES": "pytest",
|
||||
"CIBW_TEST_COMMAND": "pytest",
|
||||
"CIBW_TEST_COMMAND_WINDOWS": "pytest",
|
||||
"CIBW_TEST_SOURCES": "test",
|
||||
},
|
||||
)
|
||||
|
||||
# also check that we got the right wheels
|
||||
expected_wheels = utils.expected_wheels("spam", "0.1.0")
|
||||
assert set(actual_wheels) == set(expected_wheels)
|
||||
|
||||
@@ -24,6 +24,7 @@ skip = ["*musllinux*"]
|
||||
environment = {FOO="BAR"}
|
||||
|
||||
test-command = "pyproject"
|
||||
test-sources = ["test", "other dir"]
|
||||
|
||||
manylinux-x86_64-image = "manylinux1"
|
||||
|
||||
@@ -74,11 +75,13 @@ def test_options_1(tmp_path, monkeypatch):
|
||||
local = options.build_options("cp38-manylinux_x86_64")
|
||||
assert local.manylinux_images is not None
|
||||
assert local.test_command == "pyproject"
|
||||
assert local.test_sources == ["test", "other dir"]
|
||||
assert local.manylinux_images["x86_64"] == pinned_x86_64_container_image["manylinux1"]
|
||||
|
||||
local = options.build_options("cp37-manylinux_x86_64")
|
||||
assert local.manylinux_images is not None
|
||||
assert local.test_command == "pyproject-override"
|
||||
assert local.test_sources == ["test", "other dir"]
|
||||
assert local.manylinux_images["x86_64"] == pinned_x86_64_container_image["manylinux2014"]
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import shlex
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -23,6 +24,7 @@ test-command = "pyproject"
|
||||
test-requires = "something"
|
||||
test-extras = ["one", "two"]
|
||||
test-groups = ["three", "four"]
|
||||
test-sources = ["five", "six and seven"]
|
||||
|
||||
manylinux-x86_64-image = "manylinux1"
|
||||
|
||||
@@ -50,6 +52,10 @@ def test_simple_settings(tmp_path, platform, fname):
|
||||
|
||||
assert options_reader.get("test-command") == "pyproject"
|
||||
assert options_reader.get("archs", option_format=ListFormat(" ")) == "auto"
|
||||
assert (
|
||||
options_reader.get("test-sources", option_format=ListFormat(" ", quote=shlex.quote))
|
||||
== "five 'six and seven'"
|
||||
)
|
||||
assert (
|
||||
options_reader.get("test-requires", option_format=ListFormat(" "))
|
||||
== {"windows": "something", "macos": "else", "linux": "other many"}[platform]
|
||||
@@ -90,6 +96,7 @@ def test_envvar_override(tmp_path, platform):
|
||||
"CIBW_TEST_GROUPS": "mgroup two",
|
||||
"CIBW_TEST_REQUIRES_LINUX": "scod",
|
||||
"CIBW_TEST_GROUPS_LINUX": "lgroup",
|
||||
"CIBW_TEST_SOURCES": 'first "second third"',
|
||||
},
|
||||
)
|
||||
|
||||
@@ -99,6 +106,10 @@ def test_envvar_override(tmp_path, platform):
|
||||
assert options_reader.get("manylinux-x86_64-image") == "manylinux_2_24"
|
||||
assert options_reader.get("manylinux-i686-image") == "manylinux2014"
|
||||
|
||||
assert (
|
||||
options_reader.get("test-sources", option_format=ListFormat(" ", quote=shlex.quote))
|
||||
== 'first "second third"'
|
||||
)
|
||||
assert (
|
||||
options_reader.get("test-requires", option_format=ListFormat(" "))
|
||||
== {"windows": "docs", "macos": "docs", "linux": "scod"}[platform]
|
||||
|
||||
@@ -2,11 +2,14 @@ from __future__ import annotations
|
||||
|
||||
import textwrap
|
||||
from pathlib import PurePath
|
||||
from unittest.mock import Mock, call
|
||||
|
||||
import pytest
|
||||
|
||||
from cibuildwheel import errors
|
||||
from cibuildwheel.util import (
|
||||
FlexibleVersion,
|
||||
copy_test_sources,
|
||||
find_compatible_wheel,
|
||||
fix_ansi_codes_for_github_actions,
|
||||
format_safe,
|
||||
@@ -221,3 +224,144 @@ def test_flexible_version_comparisons():
|
||||
assert FlexibleVersion("1.0.1-rhel") > FlexibleVersion("1.0")
|
||||
assert FlexibleVersion("1.0.1-rhel") < FlexibleVersion("1.1")
|
||||
assert FlexibleVersion("1.0.1") == FlexibleVersion("v1.0.1")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_project(tmp_path):
|
||||
"""Create a directory structure that contains a range of files."""
|
||||
project_path = tmp_path / "project"
|
||||
|
||||
(project_path / "src/deep").mkdir(parents=True)
|
||||
(project_path / "tests/deep").mkdir(parents=True)
|
||||
(project_path / "other").mkdir(parents=True)
|
||||
|
||||
(project_path / "pyproject.toml").write_text("A pyproject.toml file")
|
||||
(project_path / "test.cfg").write_text("A test config file")
|
||||
|
||||
(project_path / "src/__init__.py").write_text("source init")
|
||||
(project_path / "src/module.py").write_text("source module")
|
||||
(project_path / "src/deep/__init__.py").write_text("deep source init")
|
||||
|
||||
(project_path / "tests/test_module.py").write_text("test module")
|
||||
(project_path / "tests/deep/test_module.py").write_text("deep test module")
|
||||
(project_path / "tests/deep/__init__.py").write_text("deep test init")
|
||||
|
||||
(project_path / "other/module.py").write_text("other module")
|
||||
|
||||
return project_path
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("test_sources", "expected", "not_expected"),
|
||||
[
|
||||
# Empty test_sources copies nothing.
|
||||
pytest.param(
|
||||
[],
|
||||
[],
|
||||
[
|
||||
"pyproject.toml",
|
||||
"test.cfg",
|
||||
"other/module.py",
|
||||
"src/__init__.py",
|
||||
"src/module.py",
|
||||
"src/deep/__init__.py",
|
||||
"tests/test_module.py",
|
||||
"tests/deep/__init__.py",
|
||||
"tests/deep/test_module.py",
|
||||
],
|
||||
id="empty",
|
||||
),
|
||||
# Single standalone files
|
||||
pytest.param(
|
||||
["pyproject.toml", "tests/deep/test_module.py"],
|
||||
["pyproject.toml", "tests/deep/test_module.py"],
|
||||
[
|
||||
"test.cfg",
|
||||
"other/module.py",
|
||||
"src/__init__.py",
|
||||
"src/module.py",
|
||||
"src/deep/__init__.py",
|
||||
"tests/test_module.py",
|
||||
"tests/deep/__init__.py",
|
||||
],
|
||||
id="single-file",
|
||||
),
|
||||
# A full Directory
|
||||
pytest.param(
|
||||
["tests"],
|
||||
[
|
||||
"tests/test_module.py",
|
||||
"tests/deep/__init__.py",
|
||||
"tests/deep/test_module.py",
|
||||
],
|
||||
[
|
||||
"pyproject.toml",
|
||||
"test.cfg",
|
||||
"other/module.py",
|
||||
"src/__init__.py",
|
||||
"src/module.py",
|
||||
"src/deep/__init__.py",
|
||||
],
|
||||
id="top-level-directory",
|
||||
),
|
||||
# A partial deep directory
|
||||
pytest.param(
|
||||
["tests/deep"],
|
||||
[
|
||||
"tests/deep/__init__.py",
|
||||
"tests/deep/test_module.py",
|
||||
],
|
||||
[
|
||||
"pyproject.toml",
|
||||
"test.cfg",
|
||||
"other/module.py",
|
||||
"src/__init__.py",
|
||||
"src/module.py",
|
||||
"src/deep/__init__.py",
|
||||
"tests/test_module.py",
|
||||
],
|
||||
id="partial-directory",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_copy_test_sources(tmp_path, sample_project, test_sources, expected, not_expected):
|
||||
"""Test sources can be copied into the test directory."""
|
||||
target = tmp_path / "somewhere/test_cwd"
|
||||
copy_test_sources(test_sources, sample_project, target)
|
||||
|
||||
for path in expected:
|
||||
assert (tmp_path / "somewhere/test_cwd" / path).is_file()
|
||||
|
||||
for path in not_expected:
|
||||
assert not (tmp_path / "somewhere/test_cwd" / path).exists()
|
||||
|
||||
|
||||
def test_copy_test_sources_missing_file(tmp_path, sample_project):
|
||||
"""If test_sources references a folder that doesn't exist, an error is raised."""
|
||||
|
||||
with pytest.raises(
|
||||
errors.FatalError,
|
||||
match=r"Test source tests/does_not_exist.py does not exist.",
|
||||
):
|
||||
copy_test_sources(
|
||||
["pyproject.toml", "tests/does_not_exist.py"],
|
||||
sample_project,
|
||||
tmp_path / "somewhere/test_cwd",
|
||||
)
|
||||
|
||||
|
||||
def test_copy_test_sources_alternate_copy_into(sample_project):
|
||||
"""If an alternate copy_into method is provided, it is used."""
|
||||
|
||||
target = PurePath("/container/test_cwd")
|
||||
copy_into = Mock()
|
||||
|
||||
copy_test_sources(["pyproject.toml", "tests"], sample_project, target, copy_into=copy_into)
|
||||
|
||||
copy_into.assert_has_calls(
|
||||
[
|
||||
call(sample_project / "pyproject.toml", target / "pyproject.toml"),
|
||||
call(sample_project / "tests", target / "tests"),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user