chore: optimise unit tests (#2293)
* optimise unit tests - Cache loading the config TOML Unit test time: 13.1s -> 10.5s * optimise unit tests - use the same pyproject validator across the tests Unit test time: 10.5s -> 5.7s * optimise unit tests - selector optimisation Unit test time: 5.7s -> 5.1s * optimise unit tests - build options computation cache Unit test time: 5.1s -> 3.2s * Use functools.cache rather than functools.lru_cache * Don't overwrite build_options when applying functools.cache
This commit is contained in:
@@ -591,6 +591,10 @@ class Options:
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
self.pyproject_toml = None
|
self.pyproject_toml = None
|
||||||
|
|
||||||
|
# cache the build options method so repeated calls don't need to
|
||||||
|
# resolve the options again
|
||||||
|
self.build_options = functools.cache(self._compute_build_options)
|
||||||
|
|
||||||
@functools.cached_property
|
@functools.cached_property
|
||||||
def config_file_path(self) -> Path | None:
|
def config_file_path(self) -> Path | None:
|
||||||
args = self.command_line_arguments
|
args = self.command_line_arguments
|
||||||
@@ -667,9 +671,11 @@ class Options:
|
|||||||
allow_empty=allow_empty,
|
allow_empty=allow_empty,
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_options(self, identifier: str | None) -> BuildOptions:
|
def _compute_build_options(self, identifier: str | None) -> BuildOptions:
|
||||||
"""
|
"""
|
||||||
Compute BuildOptions for a single run configuration.
|
Compute BuildOptions for a single run configuration. Normally accessed
|
||||||
|
through the `build_options` method, which is the same but the result
|
||||||
|
is cached.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with self.reader.identifier(identifier):
|
with self.reader.identifier(identifier):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import fnmatch
|
|
||||||
import itertools
|
import itertools
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from fnmatch import fnmatch
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import bracex
|
import bracex
|
||||||
@@ -18,9 +18,10 @@ def selector_matches(patterns: str, string: str) -> bool:
|
|||||||
expansion. For example, 'cp{36,37}-*' would match either of 'cp36-*' or
|
expansion. For example, 'cp{36,37}-*' would match either of 'cp36-*' or
|
||||||
'cp37-*'.
|
'cp37-*'.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
patterns_list = patterns.split()
|
patterns_list = patterns.split()
|
||||||
expanded_patterns = itertools.chain.from_iterable(bracex.expand(p) for p in patterns_list)
|
expanded_patterns = itertools.chain.from_iterable(bracex.expand(p) for p in patterns_list)
|
||||||
return any(fnmatch.fnmatch(string, pat) for pat in expanded_patterns)
|
return any(fnmatch(string, pat) for pat in expanded_patterns)
|
||||||
|
|
||||||
|
|
||||||
class EnableGroup(Enum):
|
class EnableGroup(Enum):
|
||||||
@@ -59,15 +60,11 @@ class BuildSelector:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# filter out groups that are not enabled
|
# filter out groups that are not enabled
|
||||||
if EnableGroup.CPythonFreeThreading not in self.enable and selector_matches(
|
if EnableGroup.CPythonFreeThreading not in self.enable and fnmatch(build_id, "cp3??t-*"):
|
||||||
"cp3??t-*", build_id
|
|
||||||
):
|
|
||||||
return False
|
return False
|
||||||
if EnableGroup.CPythonPrerelease not in self.enable and selector_matches(
|
if EnableGroup.CPythonPrerelease not in self.enable and fnmatch(build_id, "cp314*"):
|
||||||
"cp314*", build_id
|
|
||||||
):
|
|
||||||
return False
|
return False
|
||||||
if EnableGroup.PyPy not in self.enable and selector_matches("pp*", build_id):
|
if EnableGroup.PyPy not in self.enable and fnmatch(build_id, "pp*"):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
should_build = selector_matches(self.build_config, build_id)
|
should_build = selector_matches(self.build_config, build_id)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import functools
|
||||||
import tomllib
|
import tomllib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Final
|
from typing import Final
|
||||||
@@ -16,6 +17,8 @@ VIRTUALENV: Final[Path] = PATH / "virtualenv.toml"
|
|||||||
CIBUILDWHEEL_SCHEMA: Final[Path] = PATH / "cibuildwheel.schema.json"
|
CIBUILDWHEEL_SCHEMA: Final[Path] = PATH / "cibuildwheel.schema.json"
|
||||||
|
|
||||||
|
|
||||||
|
# this value is cached because it's used a lot in unit tests
|
||||||
|
@functools.cache
|
||||||
def read_python_configs(config: PlatformName) -> list[dict[str, str]]:
|
def read_python_configs(config: PlatformName) -> list[dict[str, str]]:
|
||||||
with BUILD_PLATFORMS.open("rb") as f:
|
with BUILD_PLATFORMS.open("rb") as f:
|
||||||
loaded_file = tomllib.load(f)
|
loaded_file = tomllib.load(f)
|
||||||
|
|||||||
@@ -10,15 +10,22 @@ from cibuildwheel.util import resources
|
|||||||
DIR = Path(__file__).parent.resolve()
|
DIR = Path(__file__).parent.resolve()
|
||||||
|
|
||||||
|
|
||||||
def test_validate_default_schema():
|
@pytest.fixture(scope="session")
|
||||||
|
def validator() -> validate_pyproject.api.Validator:
|
||||||
|
"""
|
||||||
|
Reuse the validator for all tests, to keep unit tests fast.
|
||||||
|
"""
|
||||||
|
return validate_pyproject.api.Validator()
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_default_schema(validator: validate_pyproject.api.Validator) -> None:
|
||||||
with resources.DEFAULTS.open("rb") as f:
|
with resources.DEFAULTS.open("rb") as f:
|
||||||
example = tomllib.load(f)
|
example = tomllib.load(f)
|
||||||
|
|
||||||
validator = validate_pyproject.api.Validator()
|
|
||||||
assert validator(example) is not None
|
assert validator(example) is not None
|
||||||
|
|
||||||
|
|
||||||
def test_validate_container_engine():
|
def test_validate_container_engine(validator: validate_pyproject.api.Validator) -> None:
|
||||||
"""
|
"""
|
||||||
This test checks container engine can be overridden - it used to be a
|
This test checks container engine can be overridden - it used to be a
|
||||||
global option but is now a build option.
|
global option but is now a build option.
|
||||||
@@ -38,12 +45,13 @@ def test_validate_container_engine():
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
validator = validate_pyproject.api.Validator()
|
|
||||||
assert validator(example) is not None
|
assert validator(example) is not None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("platform", ["macos", "windows"])
|
@pytest.mark.parametrize("platform", ["macos", "windows"])
|
||||||
def test_validate_bad_container_engine(platform: str) -> None:
|
def test_validate_bad_container_engine(
|
||||||
|
validator: validate_pyproject.api.Validator, platform: str
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
container-engine is not a valid option for macos or windows
|
container-engine is not a valid option for macos or windows
|
||||||
"""
|
"""
|
||||||
@@ -54,12 +62,11 @@ def test_validate_bad_container_engine(platform: str) -> None:
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
validator = validate_pyproject.api.Validator()
|
|
||||||
with pytest.raises(validate_pyproject.error_reporting.ValidationError):
|
with pytest.raises(validate_pyproject.error_reporting.ValidationError):
|
||||||
validator(example)
|
validator(example)
|
||||||
|
|
||||||
|
|
||||||
def test_overrides_select():
|
def test_overrides_select(validator: validate_pyproject.api.Validator) -> None:
|
||||||
example = tomllib.loads(
|
example = tomllib.loads(
|
||||||
"""
|
"""
|
||||||
[[tool.cibuildwheel.overrides]]
|
[[tool.cibuildwheel.overrides]]
|
||||||
@@ -68,11 +75,10 @@ def test_overrides_select():
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
validator = validate_pyproject.api.Validator()
|
|
||||||
assert validator(example) is not None
|
assert validator(example) is not None
|
||||||
|
|
||||||
|
|
||||||
def test_overrides_no_select():
|
def test_overrides_no_select(validator: validate_pyproject.api.Validator) -> None:
|
||||||
example = tomllib.loads(
|
example = tomllib.loads(
|
||||||
"""
|
"""
|
||||||
[[tool.cibuildwheel.overrides]]
|
[[tool.cibuildwheel.overrides]]
|
||||||
@@ -80,12 +86,11 @@ def test_overrides_no_select():
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
validator = validate_pyproject.api.Validator()
|
|
||||||
with pytest.raises(validate_pyproject.error_reporting.ValidationError):
|
with pytest.raises(validate_pyproject.error_reporting.ValidationError):
|
||||||
validator(example)
|
validator(example)
|
||||||
|
|
||||||
|
|
||||||
def test_overrides_only_select():
|
def test_overrides_only_select(validator: validate_pyproject.api.Validator) -> None:
|
||||||
example = tomllib.loads(
|
example = tomllib.loads(
|
||||||
"""
|
"""
|
||||||
[[tool.cibuildwheel.overrides]]
|
[[tool.cibuildwheel.overrides]]
|
||||||
@@ -93,12 +98,11 @@ def test_overrides_only_select():
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
validator = validate_pyproject.api.Validator()
|
|
||||||
with pytest.raises(validate_pyproject.error_reporting.ValidationError):
|
with pytest.raises(validate_pyproject.error_reporting.ValidationError):
|
||||||
validator(example)
|
validator(example)
|
||||||
|
|
||||||
|
|
||||||
def test_overrides_valid_inherit():
|
def test_overrides_valid_inherit(validator: validate_pyproject.api.Validator) -> None:
|
||||||
example = tomllib.loads(
|
example = tomllib.loads(
|
||||||
"""
|
"""
|
||||||
[[tool.cibuildwheel.overrides]]
|
[[tool.cibuildwheel.overrides]]
|
||||||
@@ -108,11 +112,10 @@ def test_overrides_valid_inherit():
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
validator = validate_pyproject.api.Validator()
|
|
||||||
assert validator(example) is not None
|
assert validator(example) is not None
|
||||||
|
|
||||||
|
|
||||||
def test_overrides_invalid_inherit():
|
def test_overrides_invalid_inherit(validator: validate_pyproject.api.Validator) -> None:
|
||||||
example = tomllib.loads(
|
example = tomllib.loads(
|
||||||
"""
|
"""
|
||||||
[[tool.cibuildwheel.overrides]]
|
[[tool.cibuildwheel.overrides]]
|
||||||
@@ -122,12 +125,11 @@ def test_overrides_invalid_inherit():
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
validator = validate_pyproject.api.Validator()
|
|
||||||
with pytest.raises(validate_pyproject.error_reporting.ValidationError):
|
with pytest.raises(validate_pyproject.error_reporting.ValidationError):
|
||||||
validator(example)
|
validator(example)
|
||||||
|
|
||||||
|
|
||||||
def test_overrides_invalid_inherit_value():
|
def test_overrides_invalid_inherit_value(validator: validate_pyproject.api.Validator) -> None:
|
||||||
example = tomllib.loads(
|
example = tomllib.loads(
|
||||||
"""
|
"""
|
||||||
[[tool.cibuildwheel.overrides]]
|
[[tool.cibuildwheel.overrides]]
|
||||||
@@ -137,12 +139,11 @@ def test_overrides_invalid_inherit_value():
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
validator = validate_pyproject.api.Validator()
|
|
||||||
with pytest.raises(validate_pyproject.error_reporting.ValidationError):
|
with pytest.raises(validate_pyproject.error_reporting.ValidationError):
|
||||||
validator(example)
|
validator(example)
|
||||||
|
|
||||||
|
|
||||||
def test_docs_examples():
|
def test_docs_examples(validator: validate_pyproject.api.Validator) -> None:
|
||||||
"""
|
"""
|
||||||
Parse out all the configuration examples, build valid TOML out of them, and
|
Parse out all the configuration examples, build valid TOML out of them, and
|
||||||
make sure they pass.
|
make sure they pass.
|
||||||
@@ -182,5 +183,5 @@ def test_docs_examples():
|
|||||||
print(example_txt)
|
print(example_txt)
|
||||||
print()
|
print()
|
||||||
example = tomllib.loads(example_txt)
|
example = tomllib.loads(example_txt)
|
||||||
validator = validate_pyproject.api.Validator()
|
|
||||||
assert validator(example) is not None
|
assert validator(example) is not None
|
||||||
|
|||||||
Reference in New Issue
Block a user