blob: 5093bc0853d9f1b371c65a1e3c2d4bb25c35a32c [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2024 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.
import json
import subprocess
import tempfile
import unittest
from pathlib import Path
from typing import Any, List
_SCRIPT_DIR = Path(__file__).parent
_BUILD_API_SCRIPT = _SCRIPT_DIR / "client"
def _write_file(path: Path, content: str):
path.write_text(content)
def _write_json(path: Path, content: Any):
with path.open("w") as f:
json.dump(content, f, sort_keys=True)
class ClientTest(unittest.TestCase):
def setUp(self):
self._temp_dir = tempfile.TemporaryDirectory()
self._build_dir = Path(self._temp_dir.name)
# The build_api_client_info file maps each module name to its .json file.
with (self._build_dir / "build_api_client_info").open("wt") as f:
f.write(
"""args=args.json
build_info=build_info.json
tests=tests.json
"""
)
# The $BUILD_DIR/args.json file is necessary to extract the
# target cpu value.
self._args_json = json.dumps({"target_cpu": "aRm64"})
# Fake tests.json with a single entry.
self._tests_json = json.dumps(
[
{
"environments": [
{
"dimensions": {
"cpu": "y64",
"os": "Linux",
}
}
],
"test": {
"cpu": "y64",
"label": "//some/test:target(//build/toolchain:host_y64)",
"name": "host_y64/obj/some/test/target_test.sh",
"os": "linux",
"path": "host_y64/obj/some/test/target_test.sh",
"runtime_deps": "host_y64/gen/some/test/target_test.deps.json",
},
},
]
)
# Fake build_info.json
self._build_info_json = json.dumps(
{
"configurations": [
{
"board": "y64",
"product": "core",
},
],
"version": "",
},
)
_write_file(self._build_dir / "args.json", self._args_json)
_write_file(self._build_dir / "tests.json", self._tests_json)
_write_file(self._build_dir / "build_info.json", self._build_info_json)
# Fake Ninja outputs.
self._ninja_outputs = {
"//foo:foo": [
"obj/foo.stamp",
],
"//bar:bar": [
"obj/bar.output",
"obj/bar.stamp",
],
"//src:lib": [
"obj/src/lib.cc.o",
],
"//src:bin": [
"obj/src/main.cc.o",
"obj/src/program",
],
"//tools:hammer(//build/toolchain:host_y64)": [
"host_y64/exe.unstripped/hammer",
"host_y64/hammer",
"host_y64/obj/tools/hammer.cc.o",
],
"//some/test:target(//build/toolchain:host_y64)": [
"host_y64/obj/some/test/target_test.sh",
"host_y64/gen/some/test/target_test.deps.json",
],
}
_write_json(self._build_dir / "ninja_outputs.json", self._ninja_outputs)
def tearDown(self):
self._temp_dir.cleanup()
def run_client(self, args: List[str | Path]) -> subprocess.CompletedProcess:
return subprocess.run(
[
_BUILD_API_SCRIPT,
"--build-dir",
str(self._build_dir),
"--host-tag=linux-y64",
]
+ [str(a) for a in args],
cwd=self._build_dir,
text=True,
capture_output=True,
)
def assert_output(
self,
args: List[str | Path],
expected_out: str,
expected_err: str = "",
expected_status: int = 0,
msg: str = "",
):
ret = self.run_client(args)
if not msg:
msg = "'%s' command" % " ".join(str(a) for a in args)
self.assertEqual(expected_err, ret.stderr, msg=msg)
self.assertEqual(expected_out, ret.stdout, msg=msg)
self.assertEqual(expected_status, ret.returncode, msg=msg)
def assert_error(
self,
args: List[str | Path],
expected_err: str,
msg: str = "",
):
self.assert_output(args, "", expected_err, expected_status=1, msg=msg)
def test_list(self):
self.assert_output(["list"], "args\nbuild_info\ntests\n")
def test_print(self):
MODULES = {
"args": self._args_json + "\n",
"tests": self._tests_json + "\n",
"build_info": self._build_info_json + "\n",
}
for module, expected in MODULES.items():
self.assert_output(["print", module], expected)
def test_print_all(self):
msg = "When printing all modules at once"
expected = {
"args": {
"file": "args.json",
"json": json.loads(self._args_json),
},
"build_info": {
"file": "build_info.json",
"json": json.loads(self._build_info_json),
},
"tests": {
"file": "tests.json",
"json": json.loads(self._tests_json),
},
}
self.assert_output(["print_all"], json.dumps(expected) + "\n")
self.assert_output(
["print_all", "--pretty"], json.dumps(expected, indent=2) + "\n"
)
def test_ninja_path_to_gn_label(self):
# Test each Ninja path individually.
for label, paths in self._ninja_outputs.items():
for path in paths:
self.assert_output(
["ninja_path_to_gn_label", path], f"{label}\n"
)
# Test each set of Ninja output paths per label.
for label, paths in self._ninja_outputs.items():
self.assert_output(["ninja_path_to_gn_label"] + paths, f"{label}\n")
# Test a single invocation with all Ninja paths, which must return the set of all labels,
# deduplicated.
all_paths = set()
for paths in self._ninja_outputs.values():
all_paths.update(paths)
all_labels = sorted(set(self._ninja_outputs.keys()))
expected = "\n".join(all_labels) + "\n"
self.assert_output(
["ninja_path_to_gn_label"] + sorted(all_paths), expected
)
# Test unknown Ninja path
self.assert_error(
["ninja_path_to_gn_label", "obj/unknown/path"],
"ERROR: Unknown Ninja target path: obj/unknown/path\n",
)
def test_gn_labels_to_ninja_paths(self):
# Test each label individually.
for label, paths in self._ninja_outputs.items():
expected = "\n".join(sorted(paths)) + "\n"
self.assert_output(["gn_label_to_ninja_paths", label], expected)
# Test all labels at the same time
all_paths = set()
for paths in self._ninja_outputs.values():
all_paths.update(paths)
expected = "\n".join(sorted(all_paths)) + "\n"
self.assert_output(
["gn_label_to_ninja_paths"] + list(self._ninja_outputs.keys()),
expected,
)
# Test unknown GN label
self.assert_error(
["gn_label_to_ninja_paths", "//unknown:label"],
"ERROR: Unknown GN label (not in the configured graph): //unknown:label\n",
)
# Test unknown GN label
self.assert_output(
[
"gn_label_to_ninja_paths",
"--allow-unknown",
"unknown_path",
"unknown:label",
],
"unknown:label\nunknown_path\n",
)
# Test that --allow_unknown does not pass unknown GN labels or absolute file paths.
self.assert_error(
[
"gn_label_to_ninja_paths",
"--allow-unknown",
"//unknown:label",
],
"ERROR: Unknown GN label (not in the configured graph): //unknown:label\n",
)
self.assert_error(
[
"gn_label_to_ninja_paths",
"--allow-unknown",
"/unknown/path",
],
"ERROR: Unknown GN label (not in the configured graph): /unknown/path\n",
)
def test_fx_build_args_to_labels(self):
_TEST_CASES = [
(["--args", "//aa"], ["//aa:aa"]),
(
["--args", "--host", "//foo/bar"],
["//foo/bar:bar(//build/toolchain:host_y64)"],
),
(["--args", "--fuchsia", "//:foo"], ["//:foo"]),
(
[
"--args",
"--host",
"//first",
"//second",
"--fuchsia",
"//third",
"//fourth",
"--fidl",
"//fifth",
],
[
"//first:first(//build/toolchain:host_y64)",
"//second:second(//build/toolchain:host_y64)",
"//third:third",
"//fourth:fourth",
"//fifth:fifth(//build/fidl:fidling)",
],
),
(
[
"--allow-unknown",
"--args",
"first_path",
"second_path",
],
["first_path", "second_path"],
),
(
[
"--allow-unknown",
"--args",
"//unknown",
"//other:unknown",
],
["//unknown:unknown", "//other:unknown"],
),
]
for args, expected_list in _TEST_CASES:
expected_out = "\n".join(expected_list) + "\n"
self.assert_output(["fx_build_args_to_labels"] + args, expected_out)
_WARNING_CASES = [
(
["host_y64/hammer"],
["//tools:hammer(//build/toolchain:host_y64)"],
"WARNING: Use '--host //tools:hammer' instead of Ninja path 'host_y64/hammer'\n",
),
]
for args, expected_list, expected_err in _WARNING_CASES:
expected_out = "\n".join(expected_list) + "\n"
self.assert_output(
["fx_build_args_to_labels", "--args"] + args,
expected_out,
expected_err=expected_err,
expected_status=0,
)
_ERROR_CASES = [
(
["host_y64/unknown"],
"ERROR: Unknown Ninja path: host_y64/unknown\n",
),
]
self.maxDiff = 1000
for args, expected_err in _ERROR_CASES:
self.assert_error(
["fx_build_args_to_labels", "--args"] + args,
expected_err=expected_err,
)
if __name__ == "__main__":
unittest.main()