diff --git a/.bazelrc b/.bazelrc index 6aee0a663d..3eb20420aa 100644 --- a/.bazelrc +++ b/.bazelrc @@ -3,7 +3,7 @@ # This lets us glob() up all the files inside the examples to make them inputs to tests # (Note, we cannot use `common --deleted_packages` because the bazel version command doesn't support it) # To update these lines, run tools/bazel_integration_test/update_deleted_packages.sh -build --deleted_packages=examples/legacy_pip_import/boto,examples/legacy_pip_import/extras,examples/legacy_pip_import/helloworld,examples/pip_install,examples/pip_parse,examples/py_import -query --deleted_packages=examples/legacy_pip_import/boto,examples/legacy_pip_import/extras,examples/legacy_pip_import/helloworld,examples/pip_install,examples/pip_parse,examples/py_import +build --deleted_packages=examples/legacy_pip_import/boto,examples/legacy_pip_import/extras,examples/legacy_pip_import/helloworld,examples/pip_install,examples/pip_parse,examples/py_import,examples/pytest,examples/wheel,examples/wheel/lib +query --deleted_packages=examples/legacy_pip_import/boto,examples/legacy_pip_import/extras,examples/legacy_pip_import/helloworld,examples/pip_install,examples/pip_parse,examples/py_import,examples/pytest,examples/wheel,examples/wheel/lib test --test_output=errors diff --git a/examples/BUILD b/examples/BUILD index e263c07368..2fdc3ddeb5 100644 --- a/examples/BUILD +++ b/examples/BUILD @@ -36,3 +36,8 @@ bazel_integration_test( name = "py_import_example", timeout = "long", ) + +bazel_integration_test( + name="pytest_example", + timeout = "long" +) diff --git a/examples/pytest/BUILD b/examples/pytest/BUILD new file mode 100644 index 0000000000..33868363bf --- /dev/null +++ b/examples/pytest/BUILD @@ -0,0 +1,31 @@ +load("@rules_python//python:defs.bzl", "py_binary", + "py_library", "py_test", "py_test_suite", "pytest_test") +load("@pip//:requirements.bzl", "requirement") + +TEST_DEPS = [ + requirement("pluggy"), + requirement("py"), + requirement("pytest"), + requirement("pytest-instafail"), + requirement("pytest-trio"), + requirement("pytest-mock"), +] + +py_library( + name="lib", + srcs="main.py" +) + +py_test_suite( + name="unit", + size="small", + srcs="main_tests.py", + args=[ + "--instafail", + ], + deps=[ + ":lib", + ] + TEST_DEPS, +) + + diff --git a/examples/pytest/WORKSPACE b/examples/pytest/WORKSPACE new file mode 100644 index 0000000000..f4137bb1b2 --- /dev/null +++ b/examples/pytest/WORKSPACE @@ -0,0 +1,15 @@ +workspace(name="example_repo") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name="rules_python", + sha256="778197e26c5fbeb07ac2a2c5ae405b30f6cb7ad1f5510ea6fdac03bded96cc6f", + url="https://github.com/bazelbuild/rules_python/releases/download/0.2.0/rules_python-0.2.0.tar.gz", +) + +load("@rules_python//python:pip.bzl", "pip_install", "pip_repositories") + +pip_install( + requirements="//:requirements.txt", +) diff --git a/examples/pytest/main.py b/examples/pytest/main.py new file mode 100644 index 0000000000..ae9bece989 --- /dev/null +++ b/examples/pytest/main.py @@ -0,0 +1,2 @@ +def add(first, second): + return first + second diff --git a/examples/pytest/main_tests.py b/examples/pytest/main_tests.py new file mode 100644 index 0000000000..2a36ef1e2c --- /dev/null +++ b/examples/pytest/main_tests.py @@ -0,0 +1,9 @@ +import pytest + +from .main import add + + +@pytest.mark.parameterize("first,second,expected", [1, 2, 3]) +def test_add(first, second, expected): + result = add(first, second) + assert result == expected diff --git a/examples/pytest/requirements.txt b/examples/pytest/requirements.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python/BUILD b/python/BUILD index d4b6e3e018..6485f251c1 100644 --- a/python/BUILD +++ b/python/BUILD @@ -47,6 +47,8 @@ filegroup( "packaging.bzl", "pip.bzl", "private/reexports.bzl", + "pytest.bzl", + "suite.bzl", "whl.bzl", ], visibility = ["//:__pkg__"], @@ -144,4 +146,6 @@ exports_files([ "packaging.bzl", "pip.bzl", "whl.bzl", + "pytest.bzl", + "suite.bzl" ]) diff --git a/python/defs.bzl b/python/defs.bzl index 28073d4716..e50136865a 100644 --- a/python/defs.bzl +++ b/python/defs.bzl @@ -22,7 +22,6 @@ migrated to Starlark, their implementations will be moved here. load("@bazel_tools//tools/python:srcs_version.bzl", _find_requirements = "find_requirements") load("@bazel_tools//tools/python:toolchain.bzl", _py_runtime_pair = "py_runtime_pair") load(":private/reexports.bzl", "internal_PyInfo", "internal_PyRuntimeInfo") - # Exports of native-defined providers. PyInfo = internal_PyInfo diff --git a/python/pytest.bzl b/python/pytest.bzl new file mode 100644 index 0000000000..c3280124fb --- /dev/null +++ b/python/pytest.bzl @@ -0,0 +1,81 @@ +load("@rules_python//python:defs.bzl", "PyInfo", "py_test") + +def _stringify(paths): + return repr(paths) + +def _pytest_runner_impl(ctx): + if len(ctx.attr.srcs) == 0: + fail("No test files specified.") + + expanded_args = [ctx.expand_location(arg, ctx.attr.data) for arg in ctx.attr.args] + + runner = ctx.actions.declare_file(ctx.attr.name) + ctx.actions.write( + runner, + """ +if __name__ == "__main__": + import sys + import pytest + + args = ["-ra"] + %s + sys.argv[1:] + %s + + sys.exit(pytest.main(args))""" % (_stringify(expanded_args), _stringify([src.path for src in ctx.files.srcs])), + is_executable = True, + ) + + return [ + DefaultInfo( + files = depset([runner]), + runfiles = ctx.runfiles(ctx.files.data), + executable = runner, + ), + ] + +_pytest_runner = rule( + _pytest_runner_impl, + attrs = { + "srcs": attr.label_list( + allow_files = [".py"], + ), + "deps": attr.label_list( + providers = [ + PyInfo, + ], + ), + "args": attr.string_list( + default = [], + ), + "data": attr.label_list( + allow_empty = True, + allow_files = True, + ), + "python_version": attr.string( + values = ["PY2", "PY3"], + default = "PY3", + ), + }, +) + +def pytest_test(name, srcs, deps = None, args = None, data = None, python_version = None, **kwargs): + runner_target = "%s-runner.py" % name + + _pytest_runner( + name = runner_target, + testonly = True, + srcs = srcs, + deps = deps, + args = args, + data = data, + python_version = python_version, + ) + + py_test( + name = name, + python_version = python_version, + srcs = srcs + [runner_target], + deps = deps, + main = runner_target, + legacy_create_init = False, + imports = ["."], + **kwargs + ) diff --git a/python/suite.bzl b/python/suite.bzl new file mode 100644 index 0000000000..de5dd501e0 --- /dev/null +++ b/python/suite.bzl @@ -0,0 +1,37 @@ +# load("@rules_python//python:defs.bzl", "py_library") +load(":pytest.bzl", "pytest_test") + +def _is_test(file): + return file.startswith("test_") or file.endswith("_tests.py") + +def py_test_suite(name, srcs, size = None, deps = None, python_version = None, imports = None, visibility = None, **kwargs): + library_name = "%s-test-lib" % name + + py_library( + name = library_name, + testonly = True, + srcs = srcs, + deps = deps, + imports = imports, + ) + + tests = [] + for src in srcs: + if _is_test(src): + test_name = "%s-%s" % (name, src) + + tests.append(test_name) + + pytest_test( + name = test_name, + size = size, + srcs = [src], + deps = [library_name], + python_version = python_version, + **kwargs + ) + native.test_suite( + name = name, + tests = tests, + visibility = visibility, + )