blob: 1378ceef1358e8f662d6e3e71ca55bc50637586a [file] [log] [blame]
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
import unittest
import subprocess
from collections import namedtuple
from pathlib import (
Path,
PurePath,
)
from unittest.mock import (
Mock,
mock_open,
patch,
)
import common
class AnyStringWith(str):
def __eq__(self, other):
return self in other
class AnyDict(dict):
def __eq__(self, other):
return True
class TestCommon(unittest.TestCase):
RUN_ARGS = {
"check": True,
"encoding": "ascii",
"errors": "ignore",
"env": AnyDict(),
"stderr": subprocess.PIPE,
"stdout": subprocess.PIPE,
}
@patch("common.subprocess.run")
def test_run_single_arg_error(self, mock_run):
mock_run.side_effect = Exception("foo")
with self.assertRaisesRegex(Exception, "foo"):
common.run("ls")
mock_run.assert_called_once_with(("ls", ), **self.RUN_ARGS)
@patch("common.subprocess.run")
def test_run_multi_arg_error(self, mock_run):
mock_run.side_effect = Exception("foo")
with self.assertRaisesRegex(Exception, "foo"):
common.run("ls", "-la", "./")
mock_run.assert_called_once_with(("ls", "-la", "./"), **self.RUN_ARGS)
@patch("common.subprocess.run")
def test_run_single_arg_success(self, mock_run):
MockStreams = namedtuple("MockStdout", ["stdout", "stderr"])
mock_run.side_effect = (MockStreams(
stdout="\nline 1\n\nline 2\nline3\n\n", stderr=""), )
self.assertEqual(common.run("ls"), ["line 1", "line 2", "line3"])
mock_run.assert_called_once_with(("ls", ), **self.RUN_ARGS)
@patch("common.subprocess.run")
def test_run_multi_arg_success(self, mock_run):
common.run("ls", "-la", "./")
mock_run.assert_called_once_with(("ls", "-la", "./"), **self.RUN_ARGS)
# Use `autospec=True` to mock the unbounded `Path.mkdir` and `Path.__truediv__`
# methods so that we can assert the values of both the `self` and the `other`
# arguments of calls.
@patch("pathlib.Path.mkdir", autospec=True)
@patch("pathlib.Path.__truediv__", autospec=True)
@patch("common.run")
def test_create_out_dir(self, mock_run, mock_path_div, mock_path_mkdir):
out_root_dir = Path("foo")
head_hash = ["123abc"]
head_timestamp = ["123456"]
out_sub_dir = PurePath("unittest")
dir_1 = Path(f"{out_root_dir}/{head_timestamp[0]}-{head_hash[0]}/")
dir_2 = Path(f"{dir_1}/{out_sub_dir}/")
mock_run.side_effect = (head_hash, head_timestamp)
mock_path_div.side_effect = (dir_1, dir_2)
self.assertEqual(common.create_out_dir(out_root_dir, out_sub_dir),
dir_2)
calls_run = mock_run.call_args_list
self.assertEqual(len(calls_run), 2)
self.assertEqual(calls_run[0][0], ("git", "rev-parse", "HEAD"))
self.assertEqual(calls_run[1][0],
("git", "show", "-s", "--format=%ct", "HEAD"))
calls_path_div = mock_path_div.call_args_list
self.assertEqual(len(calls_path_div), 2)
self.assertEqual(calls_path_div[0][0], (out_root_dir, "123456-123abc"))
self.assertEqual(calls_path_div[1][0], (Path(dir_1), out_sub_dir))
mock_path_mkdir.assert_called_once_with(dir_2,
parents=True,
exist_ok=True)
@patch("common.run")
def test_instrument_device_libs(self, mock_run):
device_libs = ["//foo:bar", "//foo:baz"]
obj_files = ["foo.o", "bar.o", "baz.o"]
mock_run.side_effect = (None, obj_files)
self.assertEqual(common.instrument_device_libs(device_libs, 'qux'),
obj_files)
calls_run = mock_run.call_args_list
self.assertEqual(len(calls_run), 2)
self.assertEqual(calls_run[0][0],
(common.BAZEL, "build", "--config=qux", *device_libs))
starlark_list = "[f.path for f in target.output_groups.compilation_outputs.to_list()]"
self.assertEqual(calls_run[1][0],
(common.BAZEL, "cquery", "--config=qux",
"(//foo:bar + //foo:baz)", '--output=starlark',
f"--starlark:expr='\\n'.join({starlark_list})"))
@patch("common.run")
def test_get_test_log_dirs(self, mock_run):
test_targets = ["//foo:bar", "//foo:baz"]
test_log_dir = ["/qux/testlogs"]
mock_run.side_effect = (test_log_dir, )
self.assertEqual(
common.get_test_log_dirs(test_targets),
[Path("/qux/testlogs/foo/bar"),
Path("/qux/testlogs/foo/baz")])
mock_run.assert_called_once_with(common.BAZEL, "info",
"bazel-testlogs")
@patch("common.print")
@patch("pathlib.Path.open", new_callable=mock_open)
@patch("pathlib.Path.__truediv__",
side_effect=Path.__truediv__,
autospec=True)
@patch("common.run")
def test_generate_report(self, mock_run, mock_path_div, mock_path_open,
mock_print):
for print_text_report in (True, False):
for m in [mock_run, mock_path_div, mock_path_open, mock_print]:
m.reset_mock()
with self.subTest(f"print_text_report:{print_text_report}"):
out_dir = Path("foo")
merged_profile = Path("merged.profdata")
merged_library = Path("merged.so")
report_title = "My Report"
text_report_content = [
"<contents of coverage report in text format>"
]
# (1) html report, (2) text report, (3) print text report (if requested)
mock_run.side_effect = ([], text_report_content,
text_report_content)
common.generate_report(out_dir, merged_profile, merged_library,
report_title, print_text_report)
calls_run = mock_run.call_args_list
self.assertEqual(
calls_run[0][0],
(common.LLVM_COV, "show", "--show-line-counts",
"--show-regions", f"--project-title={report_title}",
"--format=html", "--output-dir=./foo", "--instr-profile",
str(merged_profile), str(merged_library)))
self.assertEqual(calls_run[1][0],
(common.LLVM_COV, "report", "--instr-profile",
str(merged_profile), str(merged_library)))
mock_path_div.assert_called_once_with(out_dir, "report.txt")
mock_path_open.assert_called_once_with("w")
mock_path_open().write.assert_called_once_with(
*text_report_content)
if print_text_report:
self.assertEqual(len(calls_run), 3)
self.assertEqual(calls_run[2][0],
(common.LLVM_COV, "report", "--use-color",
"--instr-profile", str(merged_profile),
str(merged_library)))
calls_print = mock_print.call_args_list
self.assertEqual(len(calls_print), 2)
self.assertEqual(calls_print[0][0],
(*text_report_content, ))
self.assertEqual(calls_print[1][0],
AnyStringWith("coverage artifacts in"))
else:
self.assertEqual(len(calls_run), 2)
mock_print.assert_called_once_with(
AnyStringWith("coverage artifacts in"))
@patch("common.generate_report")
@patch("common.get_test_log_dirs")
@patch("common.instrument_device_libs")
@patch("common.run")
@patch("common.create_out_dir")
def test_measure_coverage(self, mock_create_out_dir, mock_run,
mock_instrument_device_libs,
mock_get_test_logs_dir, mock_generate_report):
out_root_dir = Path("foo")
out_sub_dir = PurePath("bar")
out_dir = Path("{out_root_dir}/123456-cafecafe/{out_sub_dir}")
device_libs_all = ["//foo:bar1", "//foo:bar2", "//foo:bar3"]
device_libs = ["//for:bar2", "//foo:bar3"]
obj_files = ["//foo:bar2.o", "//foo:bar3.o"]
merged_library = out_dir / "merged.so"
bazel_test_type = common.BazelTestType.CC_TEST
config = "my_config"
test_targets_all = ["//foo:test1", "//foo:test2", "//foo:test3"]
test_targets = ["//foo:test1", "//foo:test3"]
test_log_dirs = ["/testlogs/foo/test1", "/testlogs/foo/test3"]
profile_files = [
"/testlogs/foo/test1/coverage.profdata",
"/testlogs/foo/test3/coverage.profdata"
]
merged_profile = out_dir / "merged.profdata"
report_title = "My Report"
print_text_report = True
mock_libs_fn = Mock(side_effect=(device_libs, ))
mock_objs_fn = Mock(side_effect=(obj_files, ))
mock_test_targets_fn = Mock(side_effect=(test_targets, ))
mock_test_log_dirs_fn = Mock(side_effect=(profile_files, ))
mock_create_out_dir.side_effect = (out_dir, )
mock_run.side_effect = (
device_libs_all,
test_targets_all,
None,
None,
)
mock_instrument_device_libs.side_effect = (obj_files, )
mock_get_test_logs_dir.side_effect = (test_log_dirs, )
common.measure_coverage(
bazel_test_type=bazel_test_type,
config=config,
libs_fn=mock_libs_fn,
log_level=common.LogLevel.NONE,
objs_fn=mock_objs_fn,
out_root_dir=out_root_dir,
out_sub_dir=out_sub_dir,
print_text_report=print_text_report,
report_title=report_title,
test_log_dirs_fn=mock_test_log_dirs_fn,
test_targets_fn=mock_test_targets_fn,
)
mock_create_out_dir.assert_called_once_with(out_root_dir, out_sub_dir)
calls_run = mock_run.call_args_list
self.assertEqual(len(calls_run), 4)
self.assertEqual(calls_run[0][0],
(common.BAZEL, "query", common.DEVICE_LIBS_QUERY))
mock_libs_fn.assert_called_once_with(device_libs_all)
mock_instrument_device_libs.assert_called_once_with(
device_libs, config)
mock_objs_fn.assert_called_once_with(out_dir / "merged.so", obj_files)
self.assertEqual(
calls_run[1][0],
(common.BAZEL, "query", AnyStringWith(bazel_test_type.value)))
mock_test_targets_fn.assert_called_once_with(test_targets_all)
self.assertEqual(
calls_run[2][0],
(common.BAZEL, "coverage", "--define", "bitstream=skip",
f"--config={config}", "--test_output=all", *test_targets))
mock_get_test_logs_dir.assert_called_once_with(test_targets)
mock_test_log_dirs_fn.assert_called_once_with(test_log_dirs)
self.assertEqual(calls_run[3][0],
(common.LLVM_PROFDATA, "merge", "--sparse",
"--output", str(merged_profile), *profile_files))
mock_generate_report.assert_called_once_with(out_dir, merged_profile,
merged_library,
report_title,
print_text_report)
if __name__ == "__main__":
unittest.main()