4.5.1 Analysis-Phase Testing
Analysis-phase tests are unit tests for custom rule implementations. They analyze a target under test, inspect the providers and actions that the rule declared, and report a Bazel test result without waiting for the target's build actions to execute.1,2 Use them when the question is "did my rule analyze correctly?", not "did the generated file contain the right bytes?"
fail() would break analysis instead of reporting a test failure.What Runs During Analysis
An analysis test is itself a test target. Its implementation function runs during the analysis phase, receives a ctx, reads attributes populated for the test, and must return AnalysisTestResultInfo instead of registering actions.2 The provider carries a success boolean and a message. Bazel uses that analysis-time result to generate a stub test executable with the matching pass/fail outcome.3
That boundary is the reason analysis tests are fast and precise. They can assert on DefaultInfo, custom providers, output groups, selected files, and sometimes declared actions. They do not compile the target under test, execute its tools, or read generated outputs. If the test needs to inspect artifact contents, use a normal execution-time test from 1.2 Testing Strategy, or a validation action from 4.2.4 Error Handling & Validation.4
Analysis tests are the best current option for checking the inner workings of rules: actions and providers are visible during analysis, while file contents belong to execution-time tests.1 That complements the operator-facing testing strategy from 1.2 Testing Strategy and sets up the broader fixture-based approach in 4.5.2 Ruleset Integration Tests.
The Common Skylib Shape
The standard approach uses bazel_skylib's analysistest helpers rather than hand-writing the lower-level result provider. The pattern has three pieces: an implementation function that performs assertions, a globally-bound test rule created with analysistest.make(), and a loading-phase setup macro that creates both the subject target and the test target.1
Skylib pairs that public contract with executable specifications: its generated unittest reference documents the supported lifecycle, while tests/unittest_tests.bzl exercises assertions, expected failures, action inspection, and configuration-specific cases.5 Start with the public reference. Use the tests to resolve edge behavior rather than treating private implementation fields as API.
load("@bazel_skylib//lib:unittest.bzl", "asserts", "analysistest")
load(":myrules.bzl", "MyInfo", "myrule")
def _provider_contract_test_impl(ctx):
env = analysistest.begin(ctx)
target = analysistest.target_under_test(env)
asserts.equals(env, "expected", target[MyInfo].mode)
return analysistest.end(env)
provider_contract_test = analysistest.make(_provider_contract_test_impl)
def _test_provider_contract():
myrule(
name = "provider_contract_subject",
tags = ["manual"],
)
provider_contract_test(
name = "provider_contract_test",
target_under_test = ":provider_contract_subject",
)
The subject target is usually tagged manual. That keeps helper targets out of broad wildcard builds such as :all, while still letting the analysis test depend on them directly.1 This matters even more for negative tests, where the subject is intentionally invalid.
Do not call fail() for normal assertion failures inside an analysis test. A raw fail() turns the analysis itself into a build break. The testing framework instead records assertion failures in the test environment and reports them through analysistest.end(env), so Bazel sees a test failure rather than an unstructured analysis error.1
What To Assert
Provider assertions are the smallest useful analysis tests. If a rule promises to return MyInfo(headers = ..., mode = ...), the test should create a small subject target and read target[MyInfo] directly.1 This catches accidental provider field changes before downstream rules discover them through harder-to-debug failures.
Action assertions are useful when the rule's public contract includes how it schedules work: the number of actions, output basenames, mnemonics, command-line fragments, or whether a validation output stayed out of ordinary action inputs.1,4 The official Skylib pattern uses analysistest.target_actions(env) for this. Separately, _skylark_testable = True is an experimental testing-only hook for direct Actions provider and ctx.created_actions() access, not a blanket requirement for every action assertion.6
Failure tests check that the rule rejects bad inputs with the expected message. analysistest.make(..., expect_failure = True) tells the framework that the subject target should fail to analyze, and the implementation can assert on the failure text with asserts.expect_failure(env, "...").1 This is the right place to lock down user-facing diagnostics from 4.2.4 Error Handling & Validation: if the message guides BUILD authors today, keep it stable tomorrow.
Configuration-Specific Behavior
Rule behavior often depends on configuration: compilation mode, build settings, toolchains, fragments, or transitions. The low-level testing.analysis_test() API accepts attrs, fragments, toolchains, and attr_values. Attributes may use configuration transitions defined with analysis_test_transition.2 In the Skylib helper, analysistest.make(config_settings = {...}) lets a suite contain one test for -c opt behavior and another for -c dbg behavior in the same Bazel invocation.1
That is better than asking the whole test command to run under one flag:
bazel test //mypkg:myrules_test -c opt
A command-line flag changes the entire test suite. A config-specific analysis test changes the configured target under test for one test case. Keep these cases small, because analysis-testing features are intentionally limited in dependency graph size, with the limit controlled by --analysis_testing_deps_limit.2
Keep The Suite Focused
Analysis tests are not a replacement for end-to-end ruleset tests. Use them for the rule implementation contract: providers, declared outputs, action shape, error messages, and selected configuration behavior. Use fixture workspaces when you need to run real bazel build, bazel test, bazel run, module extension setup, toolchain registration, or generated repository behavior. That boundary continues in 4.5.2 Ruleset Integration Tests.
Coverage Is Adjacent, Not The Same Mechanism
InstrumentedFilesInfo is relevant to custom test rules and coverage-aware rules, but it is not an analysis-test result. Coverage support tells Bazel which source files and metadata files participate in coverage collection, and custom rules usually construct that data with coverage_common.instrumented_files_info(...).4 Test this contract when your rule defines coverage behavior, but do not confuse coverage instrumentation with the pass/fail mechanism of analysis tests.
The mini-ruleset's analysis test suite asserts provider fields, actions, failures, and configuration-sensitive behavior against real Glyph targets.
Use analysis tests to protect the analysis-time API of a rule: providers, output groups, action declarations, configuration branches, and diagnostics.
If the assertion needs a built artifact, a process exit code, runfiles behavior, or a real user workspace, move to execution tests or integration fixtures. Analysis tests are sharp because they stay inside analysis.
Check your understanding · 4 questions
1.When is an analysis-phase test the right tool for a custom rule?
Select one answer
2.Which statements describe analysis-phase testing accurately?
Select all that apply
3.Which practices describe analysis-test failure testing correctly?
Select all that apply
4.A rule should return different provider data under -c opt, but you do not want to run the whole analysis-test suite under that flag. Which pattern matches the article?
Select one answer
Footnotes
-
Testing — Skylib
analysistestpatterns for provider checks, action checks, expected failures, configuration-specific behavior, naming, and setup macros. ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 -
testing —
testing.analysis_test()contract, implementation timing, attrs/fragments/toolchains,attr_values, dependency limit flag, and no-action restriction. ↩1 ↩2 ↩3 ↩4 -
AnalysisTestResultInfo — required success/message provider and generated stub test executable behavior. ↩
-
Rules — artifact-content validation, validation-output analysis test example, and coverage instrumentation with
InstrumentedFilesInfo. ↩1 ↩2 ↩3 -
Bazel Skylib repository map —
docs/unittest_doc.mddocuments the supported API andtests/unittest_tests.bzlacts as its executable specification. ↩ -
.bzl files —
_skylark_testable,analysis_test_transition(), andrule(analysis_test = True)API reference. ↩