blob: 842d5ae423649282a600fc96cfa870908c4d12f3 [file]
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests for the _verify_bazel2gn check in //scripts/shac/bazel_migration.star."""
load("//scripts/shac/bazel_migration.star", "_verify_bazel2gn")
_SENTINEL_GN = "## BAZEL2GN SENTINEL - DO NOT EDIT BELOW THIS LINE ##\n"
_VARS = {"fuchsia_dir": ".", "fuchsia_build_dir": "out"}
_TOOL_PATHS = testing.file(
content = json.encode([
{"name": "bazel2gn", "os": "linux", "cpu": cpu, "path": "bazel2gn", "label": "//build/tools/bazel2gn"}
for cpu in ["x64", "arm64"]
]),
affected = False,
)
# Example messages, as produced by bazel2gn.
_NO_BAZEL_FILE = "BUILD.gn has the bazel2gn sentinel but there's no BUILD.bazel next to it"
_BAD_CRATE = "crate \":lib\" of rustc_test is not defined in the same BUILD.bazel"
_WITH_DIRECTORY_LIST = [testing.any_args, "--check_only", "--json", testing.any_args, "--directory_list", testing.any_args]
def _run(files, retcode = 1, diagnostics = None, stdout = "", stderr = "", expected_bazel2gn_cmd = _WITH_DIRECTORY_LIST):
"""Runs the check with a mocked bazel2gn, which must match `expected_bazel2gn_cmd`."""
return testing.run(
_verify_bazel2gn,
files = files | {
"out/bazel2gn": testing.file(affected = False),
"out/tool_paths.json": _TOOL_PATHS,
},
vars = _VARS,
exec_mocks = [
testing.exec_mock(
cmd = expected_bazel2gn_cmd,
retcode = retcode,
stdout = json.encode(diagnostics) if diagnostics != None else stdout,
stderr = stderr,
),
],
)
def test_skipped():
# No exec mocks, so this fails if bazel2gn is run.
res = testing.run(_verify_bazel2gn, vars = _VARS, files = {
"foo/BUILD.gn": testing.file(content = _SENTINEL_GN, affected = False),
"foo/foo.cc": testing.file(action = "M"),
"bar/BUILD.gn": testing.file(content = "group(\"bar\") {}\n", action = "M"),
"bar/BUILD.bazel": testing.file(action = "M"),
# BUILD.bazel without BUILD.gn.
"baz/BUILD.bazel": testing.file(action = "A"),
# Deleted BUILD.gn.
"qux/BUILD.gn": testing.file(action = "D"),
})
asserts.eq(res.findings, ())
def test_in_sync():
res = _run(
files = {"foo/BUILD.gn": testing.file(content = _SENTINEL_GN, action = "M")},
retcode = 0,
)
asserts.eq(res.findings, ())
def test_out_of_sync():
"""Out-of-sync directories are reported on their BUILD.gn."""
res = _run(
files = {
"BUILD.gn": testing.file(content = _SENTINEL_GN, affected = False),
"BUILD.bazel": testing.file(action = "M"),
"foo/BUILD.gn": testing.file(content = _SENTINEL_GN, action = "M"),
},
diagnostics = [
{"directory": ".", "file": "BUILD.gn", "out_of_sync": True, "message": "root diff"},
{"directory": "foo", "file": "foo/BUILD.gn", "out_of_sync": True, "message": "foo diff"},
],
)
fix = "fx bazel2gn -d . -d foo\n"
asserts.eq([f.filepath for f in res.findings], ["BUILD.gn", "foo/BUILD.gn"])
asserts.true(res.findings[0].message.endswith(fix + "\nroot diff"))
asserts.true(res.findings[1].message.endswith(fix + "\nfoo diff"))
def test_deleted_bazel_file():
"""A deleted BUILD.bazel next to a bazel2gn-managed BUILD.gn is checked."""
res = _run(
files = {
"foo/BUILD.gn": testing.file(content = _SENTINEL_GN, affected = False),
"foo/BUILD.bazel": testing.file(action = "D"),
},
diagnostics = [{"directory": "foo", "file": "foo/BUILD.gn", "message": _NO_BAZEL_FILE}],
)
asserts.eq(res.findings, (
testing.finding(filepath = "foo/BUILD.gn", message = "bazel2gn failed: " + _NO_BAZEL_FILE),
))
def test_untracked_file():
"""Findings on files outside this repository go in the message instead."""
res = _run(
files = {"foo/BUILD.gn": testing.file(content = _SENTINEL_GN, action = "M")},
diagnostics = [
{"directory": "vendor/x", "file": "vendor/x/BUILD.bazel", "line": 3, "message": _BAD_CRATE},
{"directory": "vendor/y", "file": "vendor/y/BUILD.gn", "message": _NO_BAZEL_FILE},
],
)
asserts.eq(res.findings, (
testing.finding(message = "vendor/x/BUILD.bazel:3: bazel2gn failed: " + _BAD_CRATE),
testing.finding(message = "vendor/y/BUILD.gn: bazel2gn failed: " + _NO_BAZEL_FILE),
))
def test_modified_tool_checks_all():
res = _run(
files = {
"build/tools/bazel2gn/cmd/main.go": testing.file(action = "M"),
"foo/BUILD.gn": testing.file(content = _SENTINEL_GN, affected = False),
},
# Without --directory_list, bazel2gn checks all directories.
expected_bazel2gn_cmd = [testing.any_args, "--check_only", "--json", testing.any_args],
diagnostics = [{"directory": "foo", "file": "foo/BUILD.gn", "out_of_sync": True, "message": "diff"}],
)
asserts.eq([f.filepath for f in res.findings], ["foo/BUILD.gn"])
def test_other_errors():
"""Errors other than out-of-sync BUILD.gn files start with the error itself."""
res = _run(
files = {
"foo/BUILD.gn": testing.file(content = _SENTINEL_GN, affected = False),
"foo/BUILD.bazel": testing.file(content = "load()\n\nrustc_test()\n", action = "M"),
"bar/BUILD.gn": testing.file(content = _SENTINEL_GN, action = "M"),
},
diagnostics = [
{"directory": "bar", "file": "bar/BUILD.gn", "message": _NO_BAZEL_FILE},
{"directory": "foo", "file": "foo/BUILD.bazel", "line": 3, "message": _BAD_CRATE},
],
)
asserts.eq(res.findings, (
testing.finding(filepath = "bar/BUILD.gn", message = "bazel2gn failed: " + _NO_BAZEL_FILE),
testing.finding(filepath = "foo/BUILD.bazel", line = 3, message = "bazel2gn failed: " + _BAD_CRATE),
))
def test_error_without_file():
res = _run(
files = {"foo/BUILD.gn": testing.file(content = _SENTINEL_GN, action = "M")},
diagnostics = [{"directory": "/elsewhere", "message": "directory is not inside --fuchsia_dir"}],
)
asserts.eq(res.findings, (
testing.finding(message = "bazel2gn failed: directory is not inside --fuchsia_dir"),
))
def test_fatal_error():
"""bazel2gn prints no diagnostics if it fails before syncing directories."""
res = _run(
files = {"foo/BUILD.gn": testing.file(content = _SENTINEL_GN, action = "M")},
stderr = "FATAL: --gn_bin is required\n",
)
asserts.eq(res.findings, (
testing.finding(message = "bazel2gn failed: FATAL: --gn_bin is required"),
))
def test_non_json_output():
res = _run(
files = {"foo/BUILD.gn": testing.file(content = _SENTINEL_GN, action = "M")},
stdout = "panic: oops\n",
)
asserts.eq(res.findings, (
testing.finding(message = "bazel2gn failed: panic: oops"),
))