Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions cibuildwheel/platforms/ios.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,19 +559,19 @@ def build(options: Options, tmp_path: Path) -> None:
env=test_env,
)

if not build_options.test_sources:
# iOS requires an explicit test-sources, as the project directory
# isn't visible on the simulator.

msg = "Testing on iOS requires a definition of test-sources."
raise errors.FatalError(msg)
testbed_app_path = testbed_path / "iOSTestbed" / "app"

# Copy the test sources to the testbed app
copy_test_sources(
build_options.test_sources,
build_options.package_dir,
testbed_path / "iOSTestbed" / "app",
)
if build_options.test_sources:
copy_test_sources(
build_options.test_sources,
build_options.package_dir,
testbed_app_path,
)
else:
(testbed_app_path / "test_fail.py").write_text(
resources.TEST_FAIL_CWD_FILE.read_text()
)

log.step("Installing test requirements...")
# Install the compiled wheel (with any test extras), plus
Expand All @@ -598,6 +598,26 @@ def build(options: Options, tmp_path: Path) -> None:

log.step("Running test suite...")

# iOS doesn't support placeholders in the test command,
# because the source dir isn't visible on the simulator.
if (
"{project}" in build_options.test_command
or "{package}" in build_options.test_command
):
msg = unwrap_preserving_paragraphs(
f"""
iOS tests configured with a test command that uses the "{{project}}" or
"{{package}}" placeholder. iOS tests cannot use placeholders, because the
source directory is not visible on the simulator.

In addition, iOS tests must run as a Python module, so the test command
must begin with 'python -m'.

Test command: {build_options.test_command!r}
"""
)
raise errors.FatalError(msg)

test_command_parts = shlex.split(build_options.test_command)
if test_command_parts[0:2] != ["python", "-m"]:
first_part = test_command_parts[0]
Expand Down
10 changes: 6 additions & 4 deletions cibuildwheel/platforms/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,18 +398,20 @@ def build_in_container(
wheel=wheel_to_test,
)

test_cwd = testing_temp_dir / "test_cwd"
container.call(["mkdir", "-p", test_cwd])

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)
# Use the test_fail.py file to raise a nice error if the user
# tries to run tests in the cwd
container.copy_into(resources.TEST_FAIL_CWD_FILE, test_cwd / "test_fail.py")

container.call(["sh", "-c", test_command_prepared], cwd=test_cwd, env=virtualenv_env)

Expand Down
11 changes: 8 additions & 3 deletions cibuildwheel/platforms/macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,8 +706,9 @@ def build(options: Options, tmp_path: Path) -> None:
wheel=repaired_wheel,
)

test_cwd = identifier_tmp_dir / "test_cwd"

if build_options.test_sources:
test_cwd = identifier_tmp_dir / "test_cwd"
# only create test_cwd if it doesn't already exist - it
# may have been created during a previous `testing_arch`
if not test_cwd.exists():
Expand All @@ -718,8 +719,12 @@ def build(options: Options, tmp_path: Path) -> None:
test_cwd,
)
else:
# There are no test sources. Run the tests in the project directory.
test_cwd = Path.cwd()
# Use the test_fail.py file to raise a nice error if the user
# tries to run tests in the cwd
test_cwd.mkdir(exist_ok=True)
(test_cwd / "test_fail.py").write_text(
resources.TEST_FAIL_CWD_FILE.read_text()
)

shell_with_arch(test_command_prepared, cwd=test_cwd, env=virtualenv_env)

Expand Down
10 changes: 6 additions & 4 deletions cibuildwheel/platforms/pyodide.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,17 +522,19 @@ 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)

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.cwd()
# Use the test_fail.py file to raise a nice error if the user
# tries to run tests in the cwd
(test_cwd / "test_fail.py").write_text(resources.TEST_FAIL_CWD_FILE.read_text())

shell(test_command_prepared, cwd=test_cwd, env=virtualenv_env)

Expand Down
22 changes: 12 additions & 10 deletions cibuildwheel/platforms/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,24 +588,26 @@ def build(options: Options, tmp_path: Path) -> None:
# run the tests from a temp dir, with an absolute path in the command
# (this ensures that Python runs the tests against the installed wheel
# and not the repo code)
test_command_prepared = prepare_command(
build_options.test_command,
project=Path.cwd(),
package=options.globals.package_dir.resolve(),
wheel=repaired_wheel,
)
test_cwd = identifier_tmp_dir / "test_cwd"
test_cwd.mkdir()

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.cwd()
# Use the test_fail.py file to raise a nice error if the user
# tries to run tests in the cwd
(test_cwd / "test_fail.py").write_text(resources.TEST_FAIL_CWD_FILE.read_text())

test_command_prepared = prepare_command(
build_options.test_command,
project=Path.cwd(),
package=options.globals.package_dir.resolve(),
wheel=repaired_wheel,
)
shell(test_command_prepared, cwd=test_cwd, env=virtualenv_env)

# we're all done here; move it to output (remove if already exists)
Expand Down
36 changes: 36 additions & 0 deletions cibuildwheel/resources/testing_temp_dir_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# this file is copied to the testing cwd, to raise the below error message if
# pytest/unittest is run from there.

import sys
import unittest
from typing import NoReturn


class TestStringMethods(unittest.TestCase):
def test_fail(self) -> NoReturn:
if sys.platform == "ios":
msg = (
"You tried to run tests from the testbed app's working "
"directory, without specifying `test-sources`. "
"On iOS, you must copy your test files to the testbed app by "
"setting the `test-sources` option in your cibuildwheel "
"configuration."
)
else:
msg = (
"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. "
"\n\n"
"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. "
"\n\n"
"Alternatively, you can specify your test files using the "
"`test-sources` option, and cibuildwheel will copy them to the "
"working directory for testing."
)

self.fail(msg)
1 change: 1 addition & 0 deletions cibuildwheel/util/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
VIRTUALENV: Final[Path] = PATH / "virtualenv.toml"
CIBUILDWHEEL_SCHEMA: Final[Path] = PATH / "cibuildwheel.schema.json"
PYTHON_BUILD_STANDALONE_RELEASES: Final[Path] = PATH / "python-build-standalone-releases.json"
TEST_FAIL_CWD_FILE: Final[Path] = PATH / "testing_temp_dir_file.py"


# this value is cached because it's used a lot in unit tests
Expand Down
23 changes: 6 additions & 17 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ cibuildwheel to run tests, add the following YAML to your CI config file:
```yaml
env:
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest ./tests"
CIBW_TEST_COMMAND: "pytest {project}/tests"
```

!!! tab "Azure Pipelines"
Expand All @@ -27,7 +27,7 @@ cibuildwheel to run tests, add the following YAML to your CI config file:
```yaml
variables:
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest ./tests"
CIBW_TEST_COMMAND: "pytest {project}/tests"
```

!!! tab "Travis CI"
Expand All @@ -38,18 +38,7 @@ cibuildwheel to run tests, add the following YAML to your CI config file:
env:
global:
- CIBW_TEST_REQUIRES=pytest
- CIBW_TEST_COMMAND="pytest ./tests"
```

!!! tab "AppVeyor"

> appveyor.yml ([docs](https://www.appveyor.com/docs/build-configuration/#environment-variables))

```yaml
environment:
global:
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest {project}\\tests"
- CIBW_TEST_COMMAND="pytest {project}/tests"
```

!!! tab "CircleCI"
Expand All @@ -61,7 +50,7 @@ cibuildwheel to run tests, add the following YAML to your CI config file:
job_name:
environment:
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest ./tests"
CIBW_TEST_COMMAND: "pytest {project}/tests"
```

!!! tab "Gitlab CI"
Expand All @@ -72,7 +61,7 @@ cibuildwheel to run tests, add the following YAML to your CI config file:
linux:
variables:
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest ./tests"
CIBW_TEST_COMMAND: "pytest {project}/tests"
```

!!! tab "Cirrus CI"
Expand All @@ -82,7 +71,7 @@ cibuildwheel to run tests, add the following YAML to your CI config file:
```yaml
env:
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest ./tests"
CIBW_TEST_COMMAND: "pytest {project}/tests"
```

## Configuration file {: #configuration-file}
Expand Down
46 changes: 23 additions & 23 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ This option can also be set using the [command-line option](#command-line) `--pl

```bash
export CIBW_BUILD='cp37-*'
export CIBW_TEST_COMMAND='pytest ./tests'
export CIBW_TEST_COMMAND='pytest {project}/tests'
cibuildwheel --platform linux .
```

Expand Down Expand Up @@ -1284,23 +1284,23 @@ Shell command to run tests after the build. The wheel will be installed
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 your project directory. When specifying
`test-command`, you can optionally use the placeholders `{package}` and
`{project}` to pass in the location of your test code:
To ensure the wheel is imported by your tests (instead of your source copy),
**Tests are executed from a temporary directory**, outside of your source
tree. To access your test code, you have a couple of options:

- `{package}` is the path to the package being built - the `package_dir`
argument supplied to cibuildwheel on the command line.
- `{project}` is an absolute path to the project root - the working directory
where cibuildwheel was called.
- You can use the [`test-sources`](#test-sources) setting to copy specific
files from your source tree into the temporary directory. When using
test-sources, use relative paths in your test command, as if they were
relative to the project root.

Using `{package}` or `{project}` used to be required, but since cibuildwheel
3.0, tests are run from the project root by default. This means that you can
use relative paths in your test command, and they will be relative to the
project root.
- You can use the `{package}` or `{project}` placeholders in your
`test-command` to refer to the package being built or the project root,
respectively.

Alternatively, you can use the [`test-sources`](#test-sources) setting to
create a temporary folder populated with a specific subset of project files to
run your test suite.
- `{package}` is the path to the package being built - the `package_dir`
argument supplied to cibuildwheel on the command line.
- `{project}` is an absolute path to the project root - the working
directory where cibuildwheel was called.

On all platforms other than iOS, the command is run in a shell, so you can write things like `cmd1 && cmd2`.

Expand All @@ -1318,18 +1318,18 @@ Platform-specific environment variables are also available:<br/>
```toml
[tool.cibuildwheel]
# Run the package tests using `pytest`
test-command = "pytest ./tests"
test-command = "pytest {project}/tests"

# Trigger an install of the package, but run nothing of note
test-command = "echo Wheel installed"

# Multiline example
test-command = [
"pytest ./tests",
"python ./test.py",
"pytest {project}/tests",
"python {project}/test.py",
]

# run tests on ios
# run tests on ios - when test-sources is set, use relative paths, not {project} or {package}
[tool.cibuildwheel.ios]
test-sources = ["tests"]
test-command = "python -m pytest ./tests"
Expand All @@ -1341,17 +1341,17 @@ Platform-specific environment variables are also available:<br/>

```yaml
# Run the package tests using `pytest`
CIBW_TEST_COMMAND: pytest ./tests
CIBW_TEST_COMMAND: pytest {project}/tests

# Trigger an install of the package, but run nothing of note
CIBW_TEST_COMMAND: "echo Wheel installed"

# Multi-line example - join with && on all platforms
CIBW_TEST_COMMAND: >
pytest ./tests &&
python ./test.py
pytest {project}/tests &&
python {project}/test.py

# run tests on ios
# run tests on ios - when test-sources is set, use relative paths, not {project} or {package}
CIBW_TEST_SOURCES_IOS: tests
CIBW_TEST_COMMAND_IOS: python -m pytest ./tests
```
Expand Down
2 changes: 1 addition & 1 deletion test/test_abi_variants.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def test_abi_none(tmp_path, capfd):
project_dir,
add_env={
"CIBW_TEST_REQUIRES": "pytest",
"CIBW_TEST_COMMAND": f"{utils.invoke_pytest()} ./test",
"CIBW_TEST_COMMAND": f"{utils.invoke_pytest()} {{project}}/test",
# limit the number of builds for test performance reasons
"CIBW_BUILD": "cp38-* cp{}{}-* cp313t-* pp310-*".format(*utils.SINGLE_PYTHON_VERSION),
"CIBW_ENABLE": "all",
Expand Down
4 changes: 2 additions & 2 deletions test/test_before_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ def test(tmp_path, build_frontend_env):
"CIBW_TEST_REQUIRES": "pytest",
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
"CIBW_TEST_COMMAND": f"false || {utils.invoke_pytest()} ./test",
"CIBW_TEST_COMMAND": f"false || {utils.invoke_pytest()} {{project}}/test",
# pytest fails on GraalPy 24.2.0 on Windows so we skip it there
# until https://github.com/oracle/graalpython/issues/490 is fixed
"CIBW_TEST_COMMAND_WINDOWS": "where graalpy || pytest ./test",
"CIBW_TEST_COMMAND_WINDOWS": "where graalpy || pytest {project}/test",
**build_frontend_env,
},
)
Expand Down
Loading