diff --git a/docs/py_test.md b/docs/py_test.md index b2e15da6..ea1294df 100644 --- a/docs/py_test.md +++ b/docs/py_test.md @@ -95,7 +95,7 @@ py_pytest_main wraps the template rendering target and the final py_library. ## py_test
-py_test(name, srcs, main, kwargs)
+py_test(name, srcs, main, pytest_main, kwargs)
 
Identical to [py_binary](./py_binary.md), but produces a target that can be used with `bazel test`. @@ -108,6 +108,7 @@ Identical to [py_binary](./py_binary.md), but produces a target that can be used | name | Name of the rule. | none | | srcs | Python source files. | [] | | main | Entry point. Like rules_python, this is treated as a suffix of a file that should appear among the srcs. If absent, then [name].py is tried. As a final fallback, if the srcs has a single file, that is used as the main. | None | +| pytest_main | If set, generate a [py_pytest_main](#py_pytest_main) script and use it as the main. The deps should include the pytest package (as well as the coverage package if desired). | False | | kwargs | additional named parameters to py_binary_rule. | none | diff --git a/e2e/use_release/src/BUILD.bazel b/e2e/use_release/src/BUILD.bazel index 900377ba..a50b68ea 100644 --- a/e2e/use_release/src/BUILD.bazel +++ b/e2e/use_release/src/BUILD.bazel @@ -1,4 +1,4 @@ -load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_pytest_main", "py_test") +load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_test") py_binary( name = "main", @@ -9,25 +9,14 @@ py_binary( main = "__main__.py", ) -# TODO(alex): land https://github.com/aspect-build/rules_py/pull/401 and shorten this -py_pytest_main( - name = "__test__", - data = ["//:.coveragerc"], - deps = [ - "@pip//coverage", - "@pip//pytest", - ], -) - py_test( name = "test", - srcs = [ - "my_test.py", - ":__test__", - ], - main = ":__test__.py", + srcs = ["my_test.py"], + data = ["//:.coveragerc"], + pytest_main = True, deps = [ - ":__test__", ":main", + "@pip//coverage", # keep + "@pip//pytest", # keep ], ) diff --git a/examples/pytest/BUILD.bazel b/examples/pytest/BUILD.bazel index d423cacd..6e93e5f4 100644 --- a/examples/pytest/BUILD.bazel +++ b/examples/pytest/BUILD.bazel @@ -1,4 +1,4 @@ -load("@aspect_rules_py//py:defs.bzl", "py_library", "py_pytest_main", "py_test") +load("@aspect_rules_py//py:defs.bzl", "py_library", "py_test") py_library( name = "lib", @@ -6,53 +6,19 @@ py_library( imports = ["../.."], ) -py_pytest_main( - name = "__test__", - chdir = package_name(), # So that test fixtures are available at the correct path - deps = [ - "@pypi_coverage//:pkg", - "@pypi_pytest//:pkg", - ], -) - py_test( name = "pytest_test", - srcs = [ - "foo_test.py", - ":__test__", - ], + srcs = ["foo_test.py"], data = glob([ "fixtures/*.json", ]), env_inherit = ["FOO"], imports = ["../.."], - main = ":__test__.py", package_collisions = "warning", + pytest_main = True, deps = [ - ":__test__", - ":lib", - "@pypi_ftfy//:pkg", - "@pypi_neptune//:pkg", - "@pypi_pytest//:pkg", - ], -) - -py_test( - name = "nested/pytest", - srcs = [ - "foo_test.py", - ":__test__", - ], - data = glob([ - "fixtures/*.json", - ]), - env_inherit = ["FOO"], - imports = ["../.."], - main = ":__test__.py", - package_collisions = "warning", - deps = [ - ":__test__", ":lib", + "@pypi_coverage//:pkg", "@pypi_ftfy//:pkg", "@pypi_neptune//:pkg", "@pypi_pytest//:pkg", @@ -60,16 +26,12 @@ py_test( ) py_test( - name = "sharding_test", - srcs = [ - "__test__.py", - "sharding_test.py", - ], + # NB: name contains a slash as regression test for #483 + name = "sharded/test", + srcs = ["sharding_test.py"], imports = ["../.."], - main = "__test__.py", package_collisions = "warning", + pytest_main = True, shard_count = 2, - deps = [ - "__test__", - ], + deps = ["@pypi_pytest//:pkg"], ) diff --git a/examples/pytest/foo_test.py b/examples/pytest/foo_test.py index 26366250..b8696c9b 100644 --- a/examples/pytest/foo_test.py +++ b/examples/pytest/foo_test.py @@ -1,12 +1,14 @@ -import pytest import json +import os + from examples.pytest.foo import add def test_add(): assert add(1, 1) == 2, "Expected 1 + 1 to equal 2" def test_hello_json(): - content = open('fixtures/hello.json', 'r').read() + # NB: we don't use the chdir attribute so the test working directory is the repository root + content = open(os.path.join(os.getenv('TEST_TARGET').lstrip('/').split(':')[0], 'fixtures/hello.json'), 'r').read() data = json.loads(content) assert data["message"] == "Hello, world.", "Message is as expected" diff --git a/py/defs.bzl b/py/defs.bzl index dc0e1772..5316355f 100644 --- a/py/defs.bzl +++ b/py/defs.bzl @@ -117,7 +117,7 @@ def py_binary(name, srcs = [], main = None, **kwargs): _py_binary_or_test(name = name, rule = _py_binary, srcs = srcs, main = main, resolutions = resolutions, **kwargs) -def py_test(name, srcs = [], main = None, **kwargs): +def py_test(name, srcs = [], main = None, pytest_main = False, **kwargs): """Identical to [py_binary](./py_binary.md), but produces a target that can be used with `bazel test`. Args: @@ -127,6 +127,8 @@ def py_test(name, srcs = [], main = None, **kwargs): Like rules_python, this is treated as a suffix of a file that should appear among the srcs. If absent, then `[name].py` is tried. As a final fallback, if the srcs has a single file, that is used as the main. + pytest_main: If set, generate a [py_pytest_main](#py_pytest_main) script and use it as the main. + The deps should include the pytest package (as well as the coverage package if desired). **kwargs: additional named parameters to `py_binary_rule`. """ @@ -139,4 +141,14 @@ def py_test(name, srcs = [], main = None, **kwargs): if resolutions: resolutions = resolutions.to_label_keyed_dict() - _py_binary_or_test(name = name, rule = _py_test, srcs = srcs, main = main, resolutions = resolutions, **kwargs) + deps = kwargs.pop("deps", []) + if pytest_main: + if main: + fail("When pytest_main is set, the main attribute should not be set.") + pytest_main_target = name + ".pytest_main" + main = pytest_main_target + ".py" + py_pytest_main(name = pytest_main_target) + srcs.append(main) + deps.append(pytest_main_target) + + _py_binary_or_test(name = name, rule = _py_test, srcs = srcs, deps = deps, main = main, resolutions = resolutions, **kwargs) diff --git a/py/private/pytest.py.tmpl b/py/private/pytest.py.tmpl index b890b9a0..aaf5282f 100644 --- a/py/private/pytest.py.tmpl +++ b/py/private/pytest.py.tmpl @@ -17,7 +17,12 @@ import os from pathlib import Path from typing import List -import pytest +try: + import pytest +except ModuleNotFoundError as e: + print("ERROR: pytest must be included in the deps of the py_pytest_main or py_test target") + raise e + # None means coverage wasn't enabled cov = None # For workaround of https://github.com/nedbat/coveragepy/issues/963 @@ -37,7 +42,7 @@ if "COVERAGE_MANIFEST" in os.environ: coveragepy_absfile_mapping = {coverage.files.abs_file(mfe): mfe for mfe in manifest_entries} cov.start() except ModuleNotFoundError as e: - print("WARNING: python coverage setup failed. Do you need to include the 'coverage' library as a dependency of py_pytest_main?", e) + print("WARNING: python coverage setup failed. Do you need to include the 'coverage' package as a dependency of py_pytest_main?", e) pass from pytest_shard import ShardPlugin