blob: d6619b9f69e57ff7f948073caf6319e80616fcbc [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.
"""Unit tests for bazel_build_args.py."""
import io
import os
import sys
import tempfile
import unittest
from pathlib import Path
_SCRIPT_DIR = os.path.dirname(__file__)
sys.path.insert(0, _SCRIPT_DIR)
import bazel_build_args
class ParseAqueryOutputTest(unittest.TestCase):
def test_parse_aquery_no_filter(self) -> None:
aquery_output = """
{
"targets": [
{ "id": 1, "label": "//src:main" },
{ "id": 2, "label": "//src:lib" }
],
"configuration": [
{ "id": 1, "mnemonic": "k8-fastbuild" }
],
"actions": [
{
"targetId": 1,
"mnemonic": "CppCompile",
"configurationId": 1,
"arguments": ["exec", "clang", "-c", "src/main.cc", "-o", "main.o"],
"environmentVariables": [
{ "key": "PATH", "value": "/bin" }
]
},
{
"targetId": 1,
"mnemonic": "CppLink",
"configurationId": 1,
"arguments": ["exec", "clang", "main.o", "-o", "bin"]
}
]
}
"""
result = bazel_build_args.parse_aquery_output(aquery_output)
self.assertEqual(len(result.actions), 2)
self.assertEqual(result.actions[0].mnemonic, "CppCompile")
self.assertEqual(result.actions[0].target, "//src:main")
self.assertEqual(result.actions[0].env_vars, {"PATH": "/bin"})
self.assertListEqual(
result.actions[0].args,
[
"exec",
"clang",
"-c",
"src/main.cc",
"-o",
"main.o",
],
)
self.assertEqual(result.actions[1].mnemonic, "CppLink")
self.assertListEqual(
result.actions[1].args,
[
"exec",
"clang",
"main.o",
"-o",
"bin",
],
)
def test_parse_aquery_with_mnemonic_filter(self) -> None:
aquery_output = """
{
"targets": [
{ "id": 1, "label": "//src:main" },
{ "id": 2, "label": "//src:lib" }
],
"configuration": [
{ "id": 1, "mnemonic": "k8-fastbuild" }
],
"actions": [
{
"targetId": 1,
"mnemonic": "CppCompile",
"configurationId": 1,
"arguments": ["exec", "clang", "-c", "src/main.cc"]
},
{
"targetId": 2,
"mnemonic": "CppLink",
"configurationId": 1,
"arguments": ["exec", "clang", "main.o", "-o", "bin"]
}
]
}
"""
result = bazel_build_args.parse_aquery_output(
aquery_output,
filter_mnemonics=["CppCompile"],
)
self.assertEqual(len(result.actions), 1)
self.assertEqual(result.actions[0].mnemonic, "CppCompile")
def test_parse_aquery_malformed_json_returns_empty(self) -> None:
suppressed_stderr = io.StringIO()
orig_stderr = sys.stderr
sys.stderr = suppressed_stderr
try:
result = bazel_build_args.parse_aquery_output("malformed { json }")
finally:
sys.stderr = orig_stderr
self.assertEqual(len(result.actions), 0)
class ExpandArgsFromDiskTest(unittest.TestCase):
def test_recursive_disk_expansion(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
execroot = Path(tmp_dir)
# Create a standard .params file
params_file = execroot / "target.params"
params_file.write_text("-DCOMPILE_FLAG\n@nested.flags\n")
# Create a nested custom flags file
nested_file = execroot / "nested.flags"
nested_file.write_text("-DNESTED_FLAG\n--env-file\nenv.vars\n")
# Create an env file
env_file = execroot / "env.vars"
env_file.write_text("KEY=VAL\n")
raw_args = [
"exec",
"clang",
"@target.params",
"-o",
"output",
]
result = bazel_build_args.expand_args_from_disk(
raw_args, {}, str(execroot)
)
self.assertListEqual(
result.expanded_args,
[
"exec",
"clang",
"-DCOMPILE_FLAG",
"-DNESTED_FLAG",
"-o",
"output",
],
)
self.assertListEqual(result.env_vars, ["KEY=VAL"])
self.assertEqual(len(result.warnings), 0)
def test_cycle_detection(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
execroot = Path(tmp_dir)
# Create a cycling params file
params_file = execroot / "cycle.params"
params_file.write_text("@cycle.params\n")
raw_args = ["@cycle.params"]
result = bazel_build_args.expand_args_from_disk(
raw_args, {}, str(execroot)
)
self.assertListEqual(result.expanded_args, ["@cycle.params"])
self.assertListEqual(result.env_vars, [])
self.assertEqual(len(result.warnings), 1)
self.assertEqual(
result.warnings[0],
"Cycle detected in response file @cycle.params: cycle.params -> cycle.params",
)
def test_missing_file_reports_warning(self) -> None:
result = bazel_build_args.expand_args_from_disk(
["@missing.params"], {}, "/non_existent_root"
)
self.assertListEqual(result.expanded_args, ["@missing.params"])
self.assertListEqual(result.env_vars, [])
self.assertEqual(len(result.warnings), 1)
self.assertIn("was not found on disk", result.warnings[0])
def test_safe_path_normalization_prevents_false_positives(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
execroot = Path(tmp_dir)
pwd_sub_dir = execroot / "mypwd"
pwd_sub_dir.mkdir()
nested_file = pwd_sub_dir / "target.params"
nested_file.write_text("-DCOMPILE_FLAG\n")
raw_args = ["@mypwd/target.params"]
result = bazel_build_args.expand_args_from_disk(
raw_args, {}, str(execroot)
)
self.assertListEqual(result.expanded_args, ["-DCOMPILE_FLAG"])
self.assertListEqual(result.env_vars, [])
self.assertEqual(len(result.warnings), 0)
def test_normalize_path_strips_env_pwd_but_keeps_folder_pwd(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
execroot = Path(tmp_dir)
# Directory named "pwd" should be kept
pwd_dir = execroot / "pwd"
pwd_dir.mkdir()
file_in_pwd = pwd_dir / "target.params"
file_in_pwd.write_text("-DPWD_FOLDER_FLAG\n")
# File in root to be found via stripped ${pwd}/ prefix
file_in_root = execroot / "target.params"
file_in_root.write_text("-DROOT_FLAG\n")
# Test that ${pwd}/ prefix gets stripped and targets file in root
result_stripped = bazel_build_args.expand_args_from_disk(
["@${pwd}/target.params"], {}, str(execroot)
)
self.assertListEqual(result_stripped.expanded_args, ["-DROOT_FLAG"])
# Test that pwd/ prefix is NOT stripped and targets file in pwd/ directory
result_not_stripped = bazel_build_args.expand_args_from_disk(
["@pwd/target.params"], {}, str(execroot)
)
self.assertListEqual(
result_not_stripped.expanded_args, ["-DPWD_FOLDER_FLAG"]
)
if __name__ == "__main__":
unittest.main()