blob: 1fb11ee62d6ace4af58bfad6625c87a351dcc6d9 [file] [log] [blame]
Laszlo Csomord784b5f2018-06-20 02:06:58 -07001# Copyright 2018 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Defines a test rule that asserts the number of output files in another rule.
16
17This rule operates in Bazel's analysis phase, not in its execution phase, and so
18it's faster than a conventional test rule would be.
19
20Furthermore this rule's action does not depend on any of the inputs (because the
21assertion is done in the analysis phase) so Bazel won't even build the input
22files to run the test. The test has constant execution time.
23
24=== Use ===
25
26Use this rule to assert the size of a filegroup or any other rule and catch
27sudden, unexpected changes in the size.
28
29The `margin` attribute allows specifying a tolerance value (percentage), to
30allow for organic, expected growth or shrinkage of the target rule.
31
32=== Example ===
33
34The "resources_size_test" test fails if the number of files in
35"resources" changes from 123 by more than 3 percent:
36
37 filegroup(
38 name = "resources",
39 srcs = glob(["**"]) + [
40 "//foo/bar:resources"
41 "//baz:resources",
42 ],
43 )
44
45 rule_size_test(
46 name = "resources_size_test",
47 src = ":resources",
48
49 # Expect 123 files in ":resources", with an error margin of 3% to allow
50 # for slight changes.
51 expect = 123,
52 margin = 3,
53 )
54"""
55
56def _impl(ctx):
57 if ctx.attr.expect < 0:
58 fail("ERROR: rule_size_test.expect must be positive")
59
60 if ctx.attr.margin < 0 or ctx.attr.margin > 100:
61 # Do not allow more than 100% change in size.
62 fail("ERROR: rule_size_test.margin must be in range [0..100]")
63
64 if ctx.attr.expect == 0 and ctx.attr.margin != 0:
65 # Allow no margin when expecting 0 files, to avoid division by zero.
66 fail("ERROR: rule_size_test.margin must be 0 when " +
67 "rule_size_test.expect is 0")
68
laurentlba48e10e2019-05-14 09:07:37 -070069 amount = len(ctx.attr.src[DefaultInfo].files.to_list())
Laszlo Csomord784b5f2018-06-20 02:06:58 -070070
71 if ctx.attr.margin > 0:
72 if amount >= ctx.attr.expect:
73 diff = amount - ctx.attr.expect
74 else:
75 diff = ctx.attr.expect - amount
76
77 if ((diff * 100) // ctx.attr.expect) > ctx.attr.margin:
78 fail(("ERROR: rule_size_test: expected %d file(s) within %d%% " +
79 "error margin, got %d file(s) (%d%% difference)") % (
80 ctx.attr.expect,
81 ctx.attr.margin,
82 amount,
83 (diff * 100) // ctx.attr.expect,
84 ))
85 elif amount != ctx.attr.expect:
86 fail(("ERROR: rule_size_test: expected exactly %d file(s), got %d " +
87 "file(s)") % (ctx.attr.expect, amount))
88
89 if ctx.attr.is_windows:
90 test_bin = ctx.actions.declare_file(ctx.label.name + ".bat")
Laszlo Csomorbaafedd2019-01-14 04:53:25 -080091
92 # CreateProcessW can launch .bat files directly as long as they are NOT
93 # empty. Therefore we write a .bat file with a comment in it.
94 ctx.actions.write(
95 output = test_bin,
96 content = "@REM dummy",
97 is_executable = True,
98 )
Laszlo Csomord784b5f2018-06-20 02:06:58 -070099 else:
100 test_bin = ctx.actions.declare_file(ctx.label.name + ".sh")
101 ctx.actions.write(
102 output = test_bin,
103 content = "#!/bin/sh",
104 is_executable = True,
105 )
106
107 return [DefaultInfo(executable = test_bin)]
108
109_rule_size_test = rule(
110 implementation = _impl,
111 attrs = {
112 # The target whose number of output files this rule asserts. The number
113 # of output files is the size of the target's DefaultInfo.files field.
114 "src": attr.label(allow_files = True),
115 # A non-negative integer, the expected number of files that the target
116 # in `src` outputs. If 0, then `margin` must also be 0.
117 "expect": attr.int(mandatory = True),
118 # A percentage value, in the range of [0..100]. Allows for tolerance in
119 # the difference between expected and actual number of files in `src`.
120 # If 0, then the target in `src` must output exactly `expect` many
121 # files.
122 "margin": attr.int(mandatory = True),
123 # True if running on Windows, False otherwise.
124 "is_windows": attr.bool(mandatory = True),
125 },
126 test = True,
127)
128
129def rule_size_test(name, **kwargs):
130 _rule_size_test(
131 name = name,
132 is_windows = select({
133 "@bazel_tools//src/conditions:windows": True,
134 "//conditions:default": False,
135 }),
136 **kwargs
137 )