diff options
-rw-r--r-- | src/BUILD | 11 | ||||
-rw-r--r-- | src/rule_size_test.bzl | 130 |
2 files changed, 141 insertions, 0 deletions
@@ -1,6 +1,7 @@ # Packaging load(":embedded_tools.bzl", "srcsfile") +load(":rule_size_test.bzl", "rule_size_test") md5_cmd = "set -e -o pipefail && cat $(SRCS) | sort | %s | awk '{ print $$1; }' > $@" @@ -182,6 +183,16 @@ py_binary( "_nojdk", ]] +rule_size_test( + name = "embedded_tools_size_test", + src = ":embedded_tools_srcs", + # WARNING: Only adjust the number in `expect` if you are intentionally + # adding or removing embedded tools. Know that the more embedded tools there + # are in Bazel, the bigger the binary becomes and the slower Bazel starts. + expect = 503, + margin = 5, # percentage +) + filegroup( name = "embedded_jdk", srcs = select({ diff --git a/src/rule_size_test.bzl b/src/rule_size_test.bzl new file mode 100644 index 0000000000..66024ea608 --- /dev/null +++ b/src/rule_size_test.bzl @@ -0,0 +1,130 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Defines a test rule that asserts the number of output files in another rule. + +This rule operates in Bazel's analysis phase, not in its execution phase, and so +it's faster than a conventional test rule would be. + +Furthermore this rule's action does not depend on any of the inputs (because the +assertion is done in the analysis phase) so Bazel won't even build the input +files to run the test. The test has constant execution time. + +=== Use === + +Use this rule to assert the size of a filegroup or any other rule and catch +sudden, unexpected changes in the size. + +The `margin` attribute allows specifying a tolerance value (percentage), to +allow for organic, expected growth or shrinkage of the target rule. + +=== Example === + +The "resources_size_test" test fails if the number of files in +"resources" changes from 123 by more than 3 percent: + + filegroup( + name = "resources", + srcs = glob(["**"]) + [ + "//foo/bar:resources" + "//baz:resources", + ], + ) + + rule_size_test( + name = "resources_size_test", + src = ":resources", + + # Expect 123 files in ":resources", with an error margin of 3% to allow + # for slight changes. + expect = 123, + margin = 3, + ) +""" + +def _impl(ctx): + if ctx.attr.expect < 0: + fail("ERROR: rule_size_test.expect must be positive") + + if ctx.attr.margin < 0 or ctx.attr.margin > 100: + # Do not allow more than 100% change in size. + fail("ERROR: rule_size_test.margin must be in range [0..100]") + + if ctx.attr.expect == 0 and ctx.attr.margin != 0: + # Allow no margin when expecting 0 files, to avoid division by zero. + fail("ERROR: rule_size_test.margin must be 0 when " + + "rule_size_test.expect is 0") + + amount = len(ctx.attr.src[DefaultInfo].files) + + if ctx.attr.margin > 0: + if amount >= ctx.attr.expect: + diff = amount - ctx.attr.expect + else: + diff = ctx.attr.expect - amount + + if ((diff * 100) // ctx.attr.expect) > ctx.attr.margin: + fail(("ERROR: rule_size_test: expected %d file(s) within %d%% " + + "error margin, got %d file(s) (%d%% difference)") % ( + ctx.attr.expect, + ctx.attr.margin, + amount, + (diff * 100) // ctx.attr.expect, + )) + elif amount != ctx.attr.expect: + fail(("ERROR: rule_size_test: expected exactly %d file(s), got %d " + + "file(s)") % (ctx.attr.expect, amount)) + + if ctx.attr.is_windows: + test_bin = ctx.actions.declare_file(ctx.label.name + ".bat") + ctx.actions.write(output = test_bin, content = "", is_executable = True) + else: + test_bin = ctx.actions.declare_file(ctx.label.name + ".sh") + ctx.actions.write( + output = test_bin, + content = "#!/bin/sh", + is_executable = True, + ) + + return [DefaultInfo(executable = test_bin)] + +_rule_size_test = rule( + implementation = _impl, + attrs = { + # The target whose number of output files this rule asserts. The number + # of output files is the size of the target's DefaultInfo.files field. + "src": attr.label(allow_files = True), + # A non-negative integer, the expected number of files that the target + # in `src` outputs. If 0, then `margin` must also be 0. + "expect": attr.int(mandatory = True), + # A percentage value, in the range of [0..100]. Allows for tolerance in + # the difference between expected and actual number of files in `src`. + # If 0, then the target in `src` must output exactly `expect` many + # files. + "margin": attr.int(mandatory = True), + # True if running on Windows, False otherwise. + "is_windows": attr.bool(mandatory = True), + }, + test = True, +) + +def rule_size_test(name, **kwargs): + _rule_size_test( + name = name, + is_windows = select({ + "@bazel_tools//src/conditions:windows": True, + "//conditions:default": False, + }), + **kwargs + ) |