8000 ci: add bazel_integration_test (#338) · rahulmutt/rules_python@e3df8bc · GitHub
[go: up one dir, main page]

Skip to content

Commit e3df8bc

Browse files
authored
ci: add bazel_integration_test (bazel-contrib#338)
This runs a py_test with a copy of bazel as a data dep. It glob()s up the sources for each example and runs nested bazel test on them. This detects whether the examples are fully working and self-contained. Follow-up step is to replace the rules_python.tgz with a HEAD version so we detect breakages.
1 parent 8c9ed92 commit e3df8bc

File tree

12 files changed

+218
-5
lines changed

12 files changed

+218
-5
lines changed

.bazelrc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# For bazel-in-bazel testing
2-
# Trick bazel into treating BUILD files under examples/* and e2e/* as being regular files
2+
# Trick bazel into treating BUILD files under examples/* as being regular files
33
# This lets us glob() up all the files inside the examples to make them inputs to tests
44
# (Note, we cannot use `common --deleted_packages` because the bazel version command doesn't support it)
5-
# To update these lines, run this command:
6-
# sed -i.bak "/^[^#].*--deleted_packages/s#=.*#=$(find examples/*/* \( -name BUILD -or -name BUILD.bazel \) | xargs -n 1 dirname | paste -sd, -)#" .bazelrc && rm .bazelrc.bak
5+
# To update these lines, run tools/bazel_integration_test/update_deleted_packages.sh
76
build --deleted_packages=examples/pip/boto,examples/pip/extras,examples/pip/helloworld
87
query --deleted_packages=examples/pip/boto,examples/pip/extras,examples/pip/helloworld
8+
9+
test --test_output=errors

examples/BUILD

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
load("//tools/bazel_integration_test:bazel_integration_test.bzl", "bazel_integration_test")
15+
1416
package(default_visibility = ["//visibility:public"])
1517

1618
licenses(["notice"]) # Apache 2.0
19+
20+
bazel_integration_test(
21+
name = "pip_example",
22+
timeout = "long",
23+
)

examples/pip/.bazelrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Watch Bazel run, and only run one at a time
2+
test --test_output=streamed

examples/pip/boto/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ licenses(["notice"]) # Apache 2.0
2222
py_test(
2323
name = "boto_test",
2424
srcs = ["boto_test.py"],
25+
python_version = "PY2",
2526
deps = [
2627
requirement("boto3"),
28+
requirement("pip"),
2729
# six is a transitive dependency via python-dateutil. Explicitly depend
2830
# on it to work around issue #70; see issue #98.
2931
requirement("six"),

examples/pip/helloworld/helloworld_test.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import helloworld
1516
import unittest
1617

17-
from examples.helloworld import helloworld
18-
1918

2019
class HelloWorldTest(unittest.TestCase):
2120

internal_deps.bzl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,13 @@ def rules_python_internal_deps():
110110
name = "piptool_deps",
111111
requirements = "@rules_python//python:requirements.txt",
112112
)
113+
114+
maybe(
115+
http_archive,
116+
name = "build_bazel_integration_testing",
117+
urls = [
118+
"https://github.com/bazelbuild/bazel-integration-testing/archive/165440b2dbda885f8d1ccb8d0f417e6cf8c54f17.zip",
119+
],
120+
strip_prefix = "bazel-integration-testing-165440b2dbda885f8d1ccb8d0f417e6cf8c54f17",
121+
sha256 = "2401b1369ef44cc42f91dc94443ef491208dbd06da1e1e10b702d8c189f098e3",
122+
)

internal_setup.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
"""Setup for rules_python tests and tools."""
22

3+
load("@build_bazel_integration_testing//tools:repositories.bzl", "bazel_binaries")
4+
35
# Requirements for building our piptool.
46
load(
57
"@piptool_deps//:requirements.bzl",
68
_piptool_install = "pip_install",
79
)
810

11+
load("//:version.bzl", "SUPPORTED_BAZEL_VERSIONS")
12+
913
def rules_python_internal_setup():
1014
"""Setup for rules_python tests and tools."""
1115

1216
# Requirements for building our piptool.
1317
_piptool_install()
18+
19+
# Depend on the Bazel binaries for running bazel-in-bazel tests
20+
bazel_binaries(versions = SUPPORTED_BAZEL_VERSIONS)

tools/bazel_integration_test/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
exports_files(["test_runner.py"])
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"Define a rule for running bazel test under Bazel"
2+
3+
load("//:version.bzl", "SUPPORTED_BAZEL_VERSIONS")
4+
load("//python:defs.bzl", "py_test")
5+
6+
BAZEL_BINARY = "@build_bazel_bazel_%s//:bazel_binary" % SUPPORTED_BAZEL_VERSIONS[0].replace(".", "_")
7+
8+
_ATTRS = {
9+
"bazel_binary": attr.label(
10+
default = BAZEL_BINARY,
11+
doc = """The bazel binary files to test against.
12+
13+
It is assumed by the test runner that the bazel binary is found at label_workspace/bazel (wksp/bazel.exe on Windows)""",
14+
),
15+
"bazel_commands": attr.string_list(
16+
default = ["info", "test ..."],
17+
doc = """The list of bazel commands to run. Defaults to `["info", "test ..."]`.
18+
19+
Note that if a command contains a bare `--` argument, the --test_arg passed to Bazel will appear before it.
20+
""",
21+
),
22+
"workspace_files": attr.label(
23+
doc = """A filegroup of all files in the workspace-under-test necessary to run the test.""",
24+
),
25+
}
26+
27+
# Avoid using non-normalized paths (workspace/../other_workspace/path)
28+
def _to_manifest_path(ctx, file):
29+
if file.short_path.startswith("../"):
30+
return file.short_path[3:]
31+
else:
32+
return ctx.workspace_name + "/" + file.short_path
33+
34+
def _config_impl(ctx):
35+
if len(SUPPORTED_BAZEL_VERSIONS) > 1:
36+
fail("""
37+
bazel_integration_test doesn't support multiple Bazel versions to test against yet.
38+
""")
39+
if len(ctx.files.workspace_files) == 0:
40+
fail("""
41+
No files were found to run under integration testing. See comment in /.bazelrc.
42+
You probably need to run
43+
tools/bazel_integration_test/update_deleted_packages.sh
44+
""")
45+
46+
# Serialize configuration file for test runner
47+
config = ctx.actions.declare_file("%s.json" % ctx.attr.name)
48+
ctx.actions.write(
49+
output = config,
50+
content = """
51+
{{
52+
"workspaceRoot": "{TMPL_workspace_root}",
53+
"bazelBinaryWorkspace": "{TMPL_bazel_binary_workspace}",
54+
"bazelCommands": [ {TMPL_bazel_commands} ]
55+
}}
56+
""".format(
57+
TMPL_workspace_root = ctx.files.workspace_files[0].dirname,
58+
TMPL_bazel_binary_workspace = ctx.attr.bazel_binary.label.workspace_name,
59+
TMPL_bazel_commands = ", ".join(["\"%s\"" % s for s in ctx.attr.bazel_commands]),
60+
),
61+
)
62+
63+
return [DefaultInfo(
64+
files = depset([config]),
65+
runfiles = ctx.runfiles(files = [config]),
66+
)]
67+
68+
_config = rule(
69+
implementation = _config_impl,
70+
doc = "Configures an integration test that runs a specified version of bazel against an external workspace.",
71+
attrs = _ATTRS,
72+
)
73+
74+
def bazel_integration_test(name, **kwargs):
75+
"""Wrapper macro to set default srcs and run a py_test with config
76+
77+
Args:
78+
name: name of the resulting py_test
79+
**kwargs: additional attributes like timeout and visibility
80+
"""
81+
# By default, we assume sources for "pip_example" are in examples/pip/**/*
82+
dirname = name[:-len("_example")]
83+
native.filegroup(
84+
name = "_%s_sources" % name,
85+
srcs = native.glob(
86+
["%s/**/*" % dirname],
87+
exclude = ["%s/bazel-*/**" % dirname],
88+
),
89+
)
90+
workspace_files = kwargs.pop("workspace_files", "_%s_sources" % name)
91+
92+
_config(
93+
name = "_%s_config" % name,
94+
workspace_files = workspace_files,
95+
)
96+
97+
py_test(
98+
name = name,
99+
srcs = [Label("//tools/bazel_integration_test:test_runner.py")],
100+
main = "test_runner.py",
101+
args = [native.package_name() + "/_%s_config.json" % name],
102+
deps = [Label("//python/runfiles")],
103+
data = [
104+
BAZEL_BINARY,
105+
"_%s_config" % name,
106+
workspace_files,
107+
],
108+
**kwargs,
109+
)
110+
111+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from pathlib import Path
2+
import json
3+
import os
4+
import platform
5+
from subprocess import Popen
6+
import sys
7+
8+
from rules_python.python.runfiles import runfiles
9+
10+
def main(conf_file):
11+
with open(conf_file) as j:
12+
config = json.load(j)
13+
r = runfiles.Create()
14+
15+
isWindows = platform.system() == 'Windows'
16+
bazelBinary = r.Rlocation(os.path.join(config['bazelBinaryWorkspace'], 'bazel.exe' if isWindows else 'bazel'))
17+
18+
workspacePath = config['workspaceRoot']
19+
# Canonicalize bazel external/some_repo/foo
20+
if workspacePath.startswith('external/'):
21+
workspacePath = '..' + workspacePath[len('external'):]
22+
23+
for command in config['bazelCommands']:
24+
bazel_args = command.split(' ')
25+
try:
26+
doubleHyphenPos = bazel_args.index('--')
27+
print("patch that in ", doubleHyphenPos)
28+
except ValueError:
29+
pass
30+
31+
32+
# Bazel's wrapper script needs this or you get
33+
# 2020/07/13 21:58:11 could not get the user's cache directory: $HOME is not defined
34+
os.environ['HOME'] = str(Path.home())
35+
36+
bazel_args.insert(0, bazelBinary)
37+
bazel_process = Popen(bazel_args, cwd = workspacePath)
38+
bazel_process.wait()
39+
if bazel_process.returncode != 0:
40+
# Test failure in Bazel is exit 3
41+
# https://github.com/bazelbuild/bazel/blob/486206012a664ecb20bdb196a681efc9a9825049/src/main/java/com/google/devtools/build/lib/util/ExitCode.java#L44
42+
sys.exit(3)
43+
44+
if __name__ == '__main__':
45+
main(sys.argv[1])

0 commit comments

Comments
 (0)
0