You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Expand testpolicy design for shard_count and integer flaky.
Document timeout, flaky, and shard_count as execution-reflecting attrs;
add shard_count recommender (future), integer flaky semantics from figma/bazel#13,
and attr-policy maxValue for shard_count after figma/bazel#12.
@@ -12,14 +12,19 @@ We want two related capabilities for Bazel `BUILD` files:
12
12
13
13
1.**Static attribute-policy linting** in `buildifier` — enforce declarative rules about
14
14
attribute values, e.g. forbid `timeout = "eternal"` unless the target is on an
15
-
approved allow-list, forbid `exclusive` in a test's `tags`, and similar
16
-
attribute/rule-kind constraints. Purely static, config-driven, no runtime data.
15
+
approved allow-list, forbid `exclusive` in a test's `tags`, cap `shard_count` at 50,
16
+
and similar attribute/rule-kind constraints. Purely static, config-driven, no runtime data.
17
17
18
18
2.**Empirical test-tuning** in a **new, separate tool** (`testpolicy`) — read
19
19
historical test-execution stats from a metrics warehouse, compute recommended
20
-
`timeout` and `flaky` values (and a flakiness score), and **emit the `buildozer`
20
+
`timeout`, `flaky`, and (in theory) `shard_count` values, and **emit the `buildozer`
21
21
commands** that would repair the repo. `buildifier` never touches the warehouse.
22
22
23
+
`timeout`, `flaky`, and `shard_count` are the three test attributes meant to reflect
24
+
**how the test currently executes**, not how it was designed. `shard_count` tuning is
25
+
feasible only if telemetry can separate fixture/SUT setup time from assertion time;
26
+
otherwise the tool cannot tell whether more shards would shorten the critical path.
27
+
23
28
These are split deliberately: `buildifier` stays a fast, hermetic, offline static
24
29
linter; all data-dependent analysis lives in a tool that queries a warehouse and
25
30
produces buildozer commands. **The tool's job ends when those commands are produced** —
@@ -34,6 +39,16 @@ whatever CI/automation invokes the tool.
34
39
| How empirical recommendations are applied | Tool emits `buildozer` commands (its terminal deliverable); applying/PRs are downstream. `buildifier` stays purely static |
35
40
| Where policy config lives | Extend the existing `.buildifier.json` config |
36
41
42
+
### Motivating use cases
43
+
44
+
| Policy | Encoding | Context |
45
+
|---|---|---|
46
+
| No `timeout = "eternal"` without approval |`forbidValues` + `allowlist`| Initial ask |
47
+
| No `exclusive` in test `tags`|`forbidListItems`| Initial ask |
48
+
| No `local = True` on tests |`forbidValues: ["True"]`| Hermetic-test policy |
49
+
| No `execution_requirements["no-cache"] = "1"`|`forbidDictEntries`| Cache-bypass policy |
50
+
|`shard_count` ≤ 50 on tests |`maxValue: 50`|[figma/bazel#12](https://github.com/figma/bazel/pull/12) removed Bazel's hardcoded 50-shard cap; repos that want to keep that limit (or any other bound) can enforce it in buildifier instead of forking Bazel |
51
+
37
52
---
38
53
39
54
## 2. Background: how `buildifier` warnings work today
@@ -78,8 +93,8 @@ whatever CI/automation invokes the tool.
78
93
A **single generalized** warning, `attr-policy`, driven entirely by config. It covers
79
94
the initial asks (eternal-timeout allow-list, no `exclusive` on tests) and any future
80
95
"attribute constrained on rule-kind unless allow-listed" rule **without new Go code** —
81
-
including boolean literals (`local = True`) and dict attributes
82
-
(`execution_requirements = {"no-cache": "1"}`).
96
+
including boolean literals (`local = True`), dict attributes
97
+
(`execution_requirements = {"no-cache": "1"}`), and numeric bounds (`shard_count` ≤ 50).
83
98
84
99
Rationale for one generalized warning vs. many hardcoded ones: buildifier warnings are
85
100
individually toggleable by name, but the *policy content* here is site-specific and
@@ -121,6 +136,14 @@ generic and lets each repo express its own policy.
121
136
"no-cache":"1"
122
137
},
123
138
"message":"Do not set execution_requirements['no-cache'] = '1'."
139
+
},
140
+
{
141
+
"name":"max-shard-count",
142
+
"ruleKinds": ["*_test"],
143
+
"attr":"shard_count",
144
+
"maxValue":50, // numeric range constraint (see below)
145
+
"allowlist": ["//huge_suite:..."],
146
+
"message":"shard_count must not exceed 50; add the target to the allowlist for larger suites."
124
147
}
125
148
]
126
149
}
@@ -141,6 +164,8 @@ generic and lets each repo express its own policy.
141
164
|`forbidDictEntries`| object (string→string) | Dict attr must not contain any of these key→value pairs. |
142
165
|`requireDictEntries`| object (string→string) | Dict attr must contain all of these key→value pairs. |
143
166
|`forbidDictKeys`|[]string | Dict attr must not contain any of these keys (value ignored). |
167
+
|`minValue`| int | Numeric attr must be ≥ this (inclusive). At least one of `minValue` / `maxValue` required for the numeric family. |
168
+
|`maxValue`| int | Numeric attr must be ≤ this (inclusive). |
144
169
|`required`| bool | Attr must be present at all. |
145
170
|`allowlist`|[]string | Target patterns exempt from this rule (see grammar below). |
146
171
|`suppressible`| bool (default `true`) | Whether `# buildifier: disable=attr-policy` silences this rule. `false` = a "hard" rule enforced authoritatively by CI regardless of in-file comments (see §6). |
@@ -164,10 +189,19 @@ Dict values are normalized the same way as scalars (string literal or
164
189
presence of a key regardless of its value (useful when any non-default value is
165
190
undesirable but the exact value varies).
166
191
192
+
**Numeric matching (`minValue` / `maxValue`).** Covers integer attributes such as
193
+
`shard_count`. Read the attribute with `rule.AttrLiteral` (a `*build.LiteralExpr`
194
+
whose `Token` is the decimal representation) and parse with `strconv.Atoi`. An absent
195
+
attribute does not violate `maxValue` / `minValue` — only an explicitly set value
196
+
outside the range is flagged. This is the mechanism repos use to re-impose limits
197
+
Bazel no longer enforces globally (e.g. the former 50-shard cap removed in
0 commit comments