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
8 changes: 6 additions & 2 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ pip.parse(
"windows_x86_64",
],
)
use_repo(pip, "rules_python_publish_deps")
pip.dep(name = "pytest")
pip.dep(name = "pytest-bazel")
pip.dep(name = "pytest-randomly")
pip.dep(name = "pytest-shard")
use_repo(pip, "pypi", "rules_python_publish_deps")

# ===== DEV ONLY DEPS AND SETUP BELOW HERE =====
bazel_dep(name = "sphinxdocs", version = "0.0.0", dev_dependency = True)
Expand Down Expand Up @@ -388,7 +392,7 @@ dev_pip.parse(
python_version = "3.14",
requirements_lock = "//tests/multi_pypi/beta:requirements.txt",
)
use_repo(dev_pip, "dev_pip", "pypi", "pypi_alpha", "pypi_beta", "pypiserver")
use_repo(dev_pip, "dev_pip", "pypi_alpha", "pypi_beta", "pypiserver")

# Bazel integration test setup below

Expand Down
2 changes: 2 additions & 0 deletions dev/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ dependencies = [
"pytest",
"pytest-bazel",
"pytest-mock",
"pytest-randomly",
Comment thread
rickeylev marked this conversation as resolved.
"pytest-shard",
]
736 changes: 413 additions & 323 deletions dev/requirements.txt

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dev/update_deps/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
# limitations under the License.
load("//python:py_binary.bzl", "py_binary")
load("//python:py_library.bzl", "py_library")
load("//python:pytest_test.bzl", "pytest_test")
load("//tests/support:support.bzl", "SUPPORTS_BZLMOD")
load("//tests/support/pytest_test:pytest_test.bzl", "pytest_test")

licenses(["notice"])

Expand Down
28 changes: 28 additions & 0 deletions dev/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ sphinx_stardocs(
"//python:py_runtime",
"//python:py_runtime_info",
"//python:py_test",
"//python:pytest_test",
"//python:repositories",
"//python/api",
"//python/api:attr_builders",
Expand Down
4 changes: 4 additions & 0 deletions news/4244.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(rules) Added {obj}`pytest_test`, which facilitates running of pytest tests.
It supports generating multiple test targets for more than one Python version
and uses `pytest-randomly` and `pytest-shard`.
([#3594](https://github.com/bazel-contrib/rules_python/issues/3594))
9 changes: 9 additions & 0 deletions python/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ filegroup(
"defs.bzl",
"packaging.bzl",
"pip.bzl",
"pytest_test.bzl",
"repositories.bzl",
"versions.bzl",
"//python/pip_install:bzl",
Expand All @@ -64,6 +65,7 @@ filegroup(

exports_files([
"defs.bzl",
"pytest_test.bzl",
"python.bzl", # Deprecated, please use defs.bzl
])

Expand Down Expand Up @@ -395,6 +397,13 @@ alias(
deprecation = "Use //python:py_test instead",
)

# keep
bzl_library(
name = "pytest_test",
srcs = ["pytest_test.bzl"],
deps = ["//python/private/pytest_test"],
)

alias(
name = "python_bzl",
actual = ":python",
Expand Down
11 changes: 6 additions & 5 deletions python/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,12 @@ bzl_library(
deps = [":visibility"],
)

bzl_library(
name = "sentinel_impl",
srcs = ["sentinel_impl.bzl"],
deps = [":py_info"],
)

bzl_library(
name = "bzlmod_enabled",
srcs = ["bzlmod_enabled.bzl"],
Expand Down Expand Up @@ -1008,11 +1014,6 @@ bzl_library(
srcs = ["runtimes_manifest_workspace.bzl"],
)

bzl_library(
name = "sentinel_impl",
srcs = ["sentinel_impl.bzl"],
)

bzl_library(
name = "text_util",
srcs = ["text_util.bzl"],
Expand Down
49 changes: 49 additions & 0 deletions python/private/pytest_test/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("//python/private:distribution_filegroup.bzl", "distribution_filegroup")
load("//python/private:visibility.bzl", "NOT_ACTUALLY_PUBLIC")

package(default_visibility = ["//:__subpackages__"])

filegroup(
name = "bootstrap_template",
srcs = ["pytest_bootstrap_template.py"],
visibility = NOT_ACTUALLY_PUBLIC,
)

# keep
bzl_library(
name = "pytest_test",
srcs = ["pytest_test.bzl"],
deps = [
"//python:py_test",
],
)

distribution_filegroup(
name = "distribution",
)

# These aliases are used to avoid duplicate targets in the deps list
alias(
name = "default_pytest",
actual = "@pypi//pytest",
visibility = NOT_ACTUALLY_PUBLIC,
)

alias(
name = "default_pytest_bazel",
actual = "@pypi//pytest_bazel",
visibility = NOT_ACTUALLY_PUBLIC,
)

alias(
name = "default_pytest_shard",
actual = "@pypi//pytest_shard",
visibility = NOT_ACTUALLY_PUBLIC,
)

alias(
name = "default_pytest_randomly",
actual = "@pypi//pytest_randomly",
Comment thread
rickeylev marked this conversation as resolved.
visibility = NOT_ACTUALLY_PUBLIC,
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import sys

# Ignored because template files are type-checked standalone without pytest_bazel in deps.
import pytest_bazel # pyrefly: ignore[missing-import]

TEST_FILES = """%TEST_FILES%""".splitlines()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,52 @@

load("//python:py_test.bzl", "py_test")

_DEFAULT_PYTEST = Label("//tests/support/pytest_test:default_pytest")
_DEFAULT_PYTEST_BAZEL = Label("//tests/support/pytest_test:default_pytest_bazel")
_DEFAULT_PYTEST = Label("//python/private/pytest_test:default_pytest")
_DEFAULT_PYTEST_BAZEL = Label("//python/private/pytest_test:default_pytest_bazel")
_DEFAULT_PYTEST_SHARD = Label("//python/private/pytest_test:default_pytest_shard")
_DEFAULT_PYTEST_RANDOMLY = Label("//python/private/pytest_test:default_pytest_randomly")

def pytest_test(
*,
name,
srcs,
pytest = None,
pytest_bazel = None,
pytest_shard = None,
pytest_randomly = None,
python_versions = None,
**kwargs):
"""Run pytest tests.

:::{versionadded} VERSION_NEXT_FEATURE
:::

Args:
name: A unique name for this target.
srcs: List of source files (test files). These are the files that
pytest will run as tests.
pytest: The pytest target to use. Defaults to @pypi//pytest.
pytest: The pytest target to use. Defaults to `@pypi//pytest`.
pytest_bazel: The pytest-bazel target to use. Defaults to
@pypi//pytest_bazel.
`@pypi//pytest_bazel`.
pytest_shard: The pytest-shard target to use. Defaults to
`@pypi//pytest_shard`.
pytest_randomly: The pytest-randomly target to use. Defaults to
`@pypi//pytest_randomly`. Pass `@rules_python//python:none` or
another empty {obj}`py_library` target to disable it.
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.
**kwargs: Additional arguments passed to {obj}`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,
pytest_shard = pytest_shard,
pytest_randomly = pytest_randomly,
python_versions = python_versions,
**kwargs
)
Expand All @@ -43,6 +57,8 @@ def pytest_test(
srcs = srcs,
pytest = pytest,
pytest_bazel = pytest_bazel,
pytest_shard = pytest_shard,
pytest_randomly = pytest_randomly,
**kwargs
)

Expand All @@ -52,6 +68,8 @@ def _multi_pytest_test(
srcs,
pytest = None,
pytest_bazel = None,
pytest_shard = None,
pytest_randomly = None,
python_versions,
**kwargs):
if "python_version" in kwargs:
Expand All @@ -70,6 +88,8 @@ def _multi_pytest_test(
srcs = srcs,
pytest = pytest,
pytest_bazel = pytest_bazel,
pytest_shard = pytest_shard,
pytest_randomly = pytest_randomly,
python_version = python_version,
**kwargs
)
Expand All @@ -91,30 +111,42 @@ def _single_pytest_test(
srcs,
pytest = None,
pytest_bazel = None,
pytest_shard = None,
pytest_randomly = None,
**kwargs):
deps = kwargs.pop("deps", None) or []
if pytest == None:
pytest = _DEFAULT_PYTEST
if pytest_bazel == None:
pytest_bazel = _DEFAULT_PYTEST_BAZEL
if pytest_shard == None:
pytest_shard = _DEFAULT_PYTEST_SHARD
if pytest_randomly == None:
pytest_randomly = _DEFAULT_PYTEST_RANDOMLY

bootstrap_target = name + "_bootstrap"
main_file = name + "_boot.py"
_write_pytest_bootstrap(
name = bootstrap_target,
srcs = srcs,
output_name = main_file,
Comment thread
rickeylev marked this conversation as resolved.
tags = ["manual"],
)

extra_deps = [
pytest,
pytest_bazel,
pytest_randomly,
]
if kwargs.get("shard_count"):
extra_deps.append(pytest_shard)

kwargs = dict(kwargs)
deps = kwargs.pop("deps", [])
py_test(
name = name,
main = main_file,
srcs = [bootstrap_target] + srcs,
deps = deps + [
pytest,
pytest_bazel,
],
deps = deps + extra_deps,
**kwargs
)

Expand Down Expand Up @@ -150,7 +182,7 @@ _write_pytest_bootstrap = rule(
"output_name": attr.string(mandatory = True),
"srcs": attr.label_list(allow_files = True),
"_bootstrap_template": attr.label(
default = "//tests/support/pytest_test:bootstrap_template",
default = "//python/private/pytest_test:bootstrap_template",
allow_single_file = True,
),
},
Expand Down
4 changes: 4 additions & 0 deletions python/private/sentinel_impl.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Label attributes with defaults cannot accept None, otherwise they fall
back to using the default. A sentinel allows detecting an intended None value.
"""

load(":py_info.bzl", "PyInfo")

SentinelInfo = provider(
doc = "Indicates this was the sentinel target.",
fields = [],
Expand All @@ -29,6 +31,8 @@ def _sentinel_impl(ctx):
SentinelInfo(),
# Also output ToolchainInfo to allow it to be used for noop toolchains
platform_common.ToolchainInfo(),
# Also output PyInfo to allow it to be used as a noop Python dependency
PyInfo(transitive_sources = depset()),
]

sentinel = rule(implementation = _sentinel_impl)
18 changes: 18 additions & 0 deletions python/pytest_test.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
A public entrypoint for `pytest_test` rule.

Use this to create test targets using [pytest](https://pypi.org/project/pytest)
instead of having to declare the main entrypoint for your {obj}`py_test` rules.

In order to use this rule you'll have to have the following packages:
* `pytest` and `pytest-bazel` - used to invoke pytest.
* `pytest-randomly` - used to have consistent random number usage.
* `pytest-shard` - optional, used only if tests need to be sharded.

:::{versionadded} VERSION_NEXT_FEATURE
:::
"""

load("//python/private/pytest_test:pytest_test.bzl", _pytest_test = "pytest_test")

pytest_test = _pytest_test
Loading
Loading