Skip to content

Commit

Permalink
Internal change
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 481919456
  • Loading branch information
A Googler authored and kotlaja committed Oct 19, 2022
1 parent 3761b07 commit 1d8608e
Show file tree
Hide file tree
Showing 17 changed files with 6,199 additions and 0 deletions.
3 changes: 3 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
licenses(["notice"])

exports_files(["LICENSE"])
30 changes: 30 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# How to Contribute

We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.

## Contributor License Agreement

Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution;
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.

You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.

## Code reviews

All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.

## Community Guidelines

This project
follows [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/)
.

1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Expand Down
7 changes: 7 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module(
name = "rules_testing",
compatibility_level = 1,
version = "0.0.1",
)

bazel_dep(name = "bazel_skylib", version = "1.3.0")
128 changes: 128 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Testing Rule's Implementation

Use this framework to test a rule's implementation function.

Bazel's evaluation is separated into three phases: loading, analysis, and
execution. Bazel evaluates a rule during the analysis phase. For each target
rule's implementation function is evaluated, given providers from all of
target's it directly depends on, toolchains and configuration values. The
implementation may create actions and returns its own set of providers.

With this framework, you can arrange a target and its direct dependencies, using
the rule you would like to test. The framework lets you set custom configuration
values and runs the analysis phase on the target. Within your test function, you
can write asserts on, for example, providers returned by the target under test,
attributes collected by aspects, or other observable information.

## Arranging the test

The arrange part of a test defines a target using the rule under test and sets
up its dependencies. This is done by writing a macro, which runs during the
loading phase, that instantiates the target under test and dependencies. All the
targets taking part in the arrangement should be tagged with `manual` so that
they are ignored by common build patterns (e.g. `//...` or `foo:all`).

Example:

```python
load("@rules_proto/defs:proto_library.bzl", "proto_library")


def test_basic(name):
"""Verifies basic behavior of a proto_library rule."""
# (1) Arrange
proto_library(name=name + '_foo', srcs=["foo.proto"], deps=[name + "_bar"], tags=["manual"])
proto_library(name=name + '_bar', srcs=["bar.proto"], tags=["manual"])

# (2) Act
...
```

TIP: Source source files aren't required to exist. This is because the analysis
phase only records the path to source files; they aren't read until after the
analysis phase. The macro function should be named after the behaviour being
tested (e.g. `test_frob_compiler_passed_qux_flag`). The setup targets should
follow the
[macro naming conventions](https://bazel.build/rules/macros#conventions), that
is all targets should include the name argument as a prefix -- this helps tests
avoid creating conflicting names.

<!-- TODO(ilist): Mocking implicit dependencies -->

### Limitations

Bazel limits the number of transitive dependencies that can be used in the
setup. The limit is controlled by
[`--analysis_testing_deps_limit`](https://bazel.build/reference/command-line-reference#flag--analysis_testing_deps_limit)
flag.

Mocking toolchains (adding a toolchain used only in the test) is not possible at
the moment.

## Running the analysis phase

The act part runs the analysis phase for a specific target and calls a user
supplied function. All of the work is done by Bazel and the framework. Use
`analysis_test` macro to pass in the target to analyse and a function that will
be called with the analysis results:

```python
load("@rules_testing//lib:analysis_test.bzl", "analysis_test")


def test_basic(name):
...

# (2) Act
analysis_test(name, target=name + "_foo", impl=_test_basic)
```

<!-- TODO(ilist): Setting configuration flags -->

## Assertions

The assert function (in example `_test_basic`) gets `env` and `target` as
parameters.

The environment `env` provides functions to write fluent asserts. `target` is a
map of providers returned by the tested target.

```python


def _test_basic(env, target):
env.assert_that(target).runfiles().contains_at_least("foo.txt")
env.assert_that(target).action_generating("foo.txt").contains_flag_values("--a")

```

<!-- TODO(ilist): ### Assertions on providers -->
<!-- TODO(ilist): ### Assertions on actions -->
<!-- TODO(ilist): ## testing aspects -->
## Collecting the tests together

Use `test_suite` function to collect all tests together:

```python
load("@rules_testing//lib:analysis_test.bzl", "test_suite")


def proto_library_test_suite(name):
test_suite(
name=name,
tests=[
test_basic,
test_advanced,
]
)
```

In your `BUILD` file instantiate the suite:

```
load("//path/to/your/package:proto_library_tests.bzl", "proto_library_test_suite")
proto_library_test_suite(name = "proto_library_test_suite")
```

The function instantiates all test macros and wraps them into a single target. This removes the need
to load and call each test separately in the `BUILD` file.
14 changes: 14 additions & 0 deletions WORKSPACE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
workspace(name = "rules_testing")

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")

maybe(
http_archive,
name = "bazel_skylib",
sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
"https://github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
],
)
Empty file added WORKSPACE.bzlmod
Empty file.
59 changes: 59 additions & 0 deletions lib/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")

licenses(["notice"])

package(
default_visibility = ["//visibility:public"],
)

bzl_library(
name = "analysis_test_bzl",
srcs = ["analysis_test.bzl"],
deps = [
"//lib:truth_bzl",
],
)

bzl_library(
name = "truth_bzl",
srcs = ["truth.bzl"],
visibility = [
"//:__subpackages__",
"//tools/build_defs/python/tests/base_rules:__subpackages__",
],
deps = [
"//lib:util_bzl",
"@bazel_skylib//lib:types",
"@bazel_skylib//lib:unittest",
],
)

bzl_library(
name = "util_bzl",
srcs = ["util.bzl"],
visibility = [
"//devtools/python/blaze:__subpackages__",
"//:__subpackages__",
"//tools/build_defs/python/tests:__subpackages__",
],
deps = [
"@bazel_skylib//lib:paths",
"@bazel_skylib//lib:types",
"@bazel_skylib//lib:unittest",
"@bazel_skylib//rules:write_file",
],
)

filegroup(
name = "test_deps",
testonly = True,
srcs = [
"BUILD",
":analysis_test_bzl",
":truth_bzl",
":util_bzl",
],
visibility = [
"//tools/build_defs/python/tests/base_rules:__pkg__",
],
)
Loading

0 comments on commit 1d8608e

Please sign in to comment.