diff --git a/tests/pytest_test/BUILD.bazel b/tests/pytest_test/BUILD.bazel index b15094a615..204671cb3e 100644 --- a/tests/pytest_test/BUILD.bazel +++ b/tests/pytest_test/BUILD.bazel @@ -1,5 +1,11 @@ load("//tests/support:support.bzl", "SUPPORTS_BZLMOD") -load("//tests/support/pytest_test:pytest_test.bzl", "pytest_test") +load( + "//tests/support/pytest_test:pytest_test.bzl", + "pytest_test", +) +load(":pytest_test_tests.bzl", "pytest_test_test_suite") + +pytest_test_test_suite(name = "pytest_test_tests") pytest_test( name = "pytest_script_venv_test", @@ -20,3 +26,15 @@ pytest_test( ], target_compatible_with = SUPPORTS_BZLMOD, ) + +pytest_test( + name = "pytest_multipy_default_test", + srcs = [ + "basic_test.py", + ], + python_versions = [ + "3.14", + "3.13", + ], + target_compatible_with = SUPPORTS_BZLMOD, +) diff --git a/tests/pytest_test/pytest_test_tests.bzl b/tests/pytest_test/pytest_test_tests.bzl new file mode 100644 index 0000000000..8699ac2ebd --- /dev/null +++ b/tests/pytest_test/pytest_test_tests.bzl @@ -0,0 +1,37 @@ +"""Tests for pytest_test.""" + +load("@rules_testing//lib:test_suite.bzl", "test_suite") +load( + "//tests/support/pytest_test:pytest_test.bzl", + "get_version_test_name", +) + +_tests = [] + +def _test_get_version_test_name(env): + want = { + ("foo_test", "3.14"): "foo_py3.14_test", + ("foo_test", "3.10"): "foo_py3.10_test", + ("foo_test", "py3.14"): "foo_py3.14_test", + ("foo_tests", "3.14"): "foo_py3.14_tests", + ("foo", "3.14"): "foo_py3.14", + ("test_foo", "3.14"): "test_foo_py3.14", + ("basic_test", "3.11"): "basic_py3.11_test", + ("pytest_default_test", "3.12"): "pytest_default_py3.12_test", + } + + actual = { + (name, ver): get_version_test_name(name, ver) + for (name, ver) in want.keys() + } + env.expect.that_dict(actual).contains_exactly(want) + +_tests.append(_test_get_version_test_name) + +def pytest_test_test_suite(name): + """Create the test suite. + + Args: + name: The name of the test suite. + """ + test_suite(name = name, basic_tests = _tests) diff --git a/tests/support/pytest_test/pytest_test.bzl b/tests/support/pytest_test/pytest_test.bzl index 893b22e5ce..bbdfffb24f 100644 --- a/tests/support/pytest_test/pytest_test.bzl +++ b/tests/support/pytest_test/pytest_test.bzl @@ -11,6 +11,7 @@ def pytest_test( srcs, pytest = None, pytest_bazel = None, + python_versions = None, **kwargs): """Run pytest tests. @@ -21,9 +22,76 @@ def pytest_test( pytest: The pytest target to use. Defaults to @pypi//pytest. pytest_bazel: The pytest-bazel target to use. Defaults to @pypi//pytest_bazel. + python_versions: List of Python versions to test against. If specified, + a test is created for each version and grouped under a test_suite + named `name`. **kwargs: Additional arguments passed to py_test. Note that `main` is not a supported argument. """ + if python_versions != None: + _multi_pytest_test( + name = name, + srcs = srcs, + pytest = pytest, + pytest_bazel = pytest_bazel, + python_versions = python_versions, + **kwargs + ) + else: + _single_pytest_test( + name = name, + srcs = srcs, + pytest = pytest, + pytest_bazel = pytest_bazel, + **kwargs + ) + +def _multi_pytest_test( + *, + name, + srcs, + pytest = None, + pytest_bazel = None, + python_versions, + **kwargs): + if "python_version" in kwargs: + fail( + "Cannot specify both python_version and python_versions in " + + "pytest_test; use one or the other.", + ) + if not python_versions: + fail("python_versions must not be empty for {}".format(name)) + + tests = [] + for python_version in python_versions: + test_name = _get_version_test_name(name, python_version) + _single_pytest_test( + name = test_name, + srcs = srcs, + pytest = pytest, + pytest_bazel = pytest_bazel, + python_version = python_version, + **kwargs + ) + tests.append(":" + test_name) + + test_suite_kwargs = {} + if "visibility" in kwargs: + test_suite_kwargs["visibility"] = kwargs["visibility"] + + native.test_suite( + name = name, + tests = tests, + **test_suite_kwargs + ) + +def _single_pytest_test( + *, + name, + srcs, + pytest = None, + pytest_bazel = None, + **kwargs): if pytest == None: pytest = _DEFAULT_PYTEST if pytest_bazel == None: @@ -37,17 +105,32 @@ def pytest_test( output_name = main_file, ) + kwargs = dict(kwargs) + deps = kwargs.pop("deps", []) py_test( name = name, main = main_file, srcs = [bootstrap_target] + srcs, - deps = kwargs.pop("deps", []) + [ + deps = deps + [ pytest, pytest_bazel, ], **kwargs ) +def _get_version_test_name(name, python_version): + version_str = str(python_version) + if not version_str.startswith("py"): + version_str = "py" + version_str + + if name.endswith("_test"): + return "{}_{}_test".format(name[:-len("_test")], version_str) + elif name.endswith("_tests"): + return "{}_{}_tests".format(name[:-len("_tests")], version_str) + return "{}_{}".format(name, version_str) + +get_version_test_name = _get_version_test_name + def _write_pytest_bootstrap_impl(ctx): output = ctx.actions.declare_file(ctx.attr.output_name) test_files = "\n".join([f.short_path for f in ctx.files.srcs])