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:
Russell Keith-Magee
2025-01-10 15:24:24 +00:00
committed by GitHub
co-authored by Joe Rickerby
parent 4d0d9f6fd0
commit 0b82613d6f
16 changed files with 373 additions and 74 deletions
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"
},
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+4 -3
View File
@@ -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
View File
@@ -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)
+3
View File
@@ -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"]
+11
View File
@@ -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]
+144
View File
@@ -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,
)