blob: 703f6bdcb9723fcb4bfd9c36a21eff2d37c4226b [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 pathlib import Path
from unittest.mock import mock_open, patch
import coverage_off_target
class AnyStringWith(str):
def __eq__(self, other):
return self in other
class TestExtractProfileData(unittest.TestCase):
@patch("coverage_off_target.subprocess.run")
def test_run_single_arg_error(self, mock_run):
mock_run.side_effect = Exception("foo")
with self.assertRaisesRegex(Exception, "foo"):
coverage_off_target.run("ls")
mock_run.assert_called_once_with(("ls",),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
check=True)
@patch("coverage_off_target.subprocess.run")
def test_run_multi_arg_error(self, mock_run):
mock_run.side_effect = Exception("foo")
with self.assertRaisesRegex(Exception, "foo"):
coverage_off_target.run("ls", "-la", "./")
mock_run.assert_called_once_with(("ls", "-la", "./"),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
check=True)
@patch("coverage_off_target.subprocess.run")
def test_run_single_arg_success(self, mock_run):
coverage_off_target.run("ls")
mock_run.assert_called_once_with(("ls",),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
check=True)
@patch("coverage_off_target.subprocess.run")
def test_run_multi_arg_success(self, mock_run):
coverage_off_target.run("ls", "-la", "./")
mock_run.assert_called_once_with(("ls", "-la", "./"),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
check=True)
@patch("coverage_off_target.run")
def test_gather_and_build_libs(self, mock_run):
libs_all = ["//foo/bar1", "//foo/bar2", "//foo/incompat"]
libs_incompat = ["//foo/incompat"]
libs = list(set(libs_all) - set(libs_incompat))
# (1) return all libs, (2) return incompat., (3) build
mock_run.side_effect = (libs_all, libs_incompat, [])
self.assertEqual(coverage_off_target.gather_and_build_libs(), libs)
calls = mock_run.call_args_list
self.assertEqual(len(calls), 3)
self.assertEqual(calls[0][0][:2], (coverage_off_target.BAZEL, "query"))
self.assertEqual(calls[1][0][:2], (coverage_off_target.BAZEL, "cquery"))
self.assertEqual(calls[2][0], (coverage_off_target.BAZEL, "build",
"--config=coverage_clang", *libs))
@patch("coverage_off_target.run")
def test_create_merged_library(self, mock_run):
libs = ["//foo/bar1", "//foo/bar2"]
libs_query = "(//foo/bar1 + //foo/bar2)"
merged_so = Path("merged.so")
objs = ["foo/bar1.o", "foo/bar2.o"]
# (1) return object files, (2) link.
mock_run.side_effect = (objs, [])
coverage_off_target.create_merged_library(libs, merged_so)
calls = mock_run.call_args_list
self.assertEqual(len(calls), 2)
self.assertEqual(calls[0][0][:3],
(coverage_off_target.BAZEL, "cquery", libs_query))
self.assertEqual(
calls[1][0],
(coverage_off_target.LLD, "--shared", "-o", str(merged_so), *objs))
@patch("coverage_off_target.run")
def test_measure_unit_test_coverage(self, mock_run):
tests = ["//foo:test1", "//foo:test2"]
testlogs = ["/some/long/path"]
profiles = [
"/some/long/path/foo/test1/coverage.dat",
"/some/long/path/foo/test2/coverage.dat"
]
merged_profile = Path("merged.profdata")
# (1) find test targets, (2) measure coverage, (3) get testlogs dir, (4) merge
# profile data.
mock_run.side_effect = (tests, [], testlogs, [])
coverage_off_target.measure_unit_test_coverage(merged_profile)
calls = mock_run.call_args_list
self.assertEqual(len(calls), 4)
self.assertEqual(calls[0][0][:2], (coverage_off_target.BAZEL, "query"))
self.assertEqual(calls[1][0], (coverage_off_target.BAZEL, "coverage",
"--config=coverage_clang", *tests))
self.assertEqual(calls[2][0],
(coverage_off_target.BAZEL, "info", "bazel-testlogs"))
self.assertEqual(
calls[3][0],
(coverage_off_target.LLVM_PROFDATA, "merge", "--sparse", "--output",
str(merged_profile), *profiles))
@patch("coverage_off_target.print")
@patch("pathlib.Path.open", new_callable=mock_open)
@patch("pathlib.Path.__truediv__",
side_effect=Path.__truediv__,
autospec=True)
@patch("coverage_off_target.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_so = Path("merged.so")
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)
coverage_off_target.generate_report(out_dir, merged_profile,
merged_so,
print_text_report)
calls_run = mock_run.call_args_list
self.assertEqual(
(*calls_run[0][0][:2], *calls_run[0][0][-5:]),
(coverage_off_target.LLVM_COV, "show",
"--format=html", "--output-dir=./foo", "--instr-profile",
str(merged_profile), str(merged_so)))
self.assertEqual(
calls_run[1][0],
(coverage_off_target.LLVM_COV, "report", "--instr-profile",
str(merged_profile), str(merged_so)))
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],
(coverage_off_target.LLVM_COV, "report",
"--use-color", "--instr-profile",
str(merged_profile), str(merged_so)))
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"))
# 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("coverage_off_target.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_dir = Path(
f"{out_root_dir}/{head_timestamp[0]}-{head_hash[0]}/unittest/")
mock_run.side_effect = (head_hash, head_timestamp)
mock_path_div.side_effect = (out_dir,)
self.assertEqual(coverage_off_target.create_out_dir(out_root_dir),
out_dir)
calls = mock_run.call_args_list
self.assertEqual(len(calls), 2)
self.assertEqual(calls[0][0], ("git", "rev-parse", "HEAD"))
self.assertEqual(calls[1][0],
("git", "show", "-s", "--format=%ct", "HEAD"))
mock_path_div.assert_called_once_with(out_root_dir,
"123456-123abc/unittest/")
mock_path_mkdir.assert_called_once_with(out_dir,
parents=True,
exist_ok=True)
@patch("coverage_off_target.generate_report")
@patch("coverage_off_target.measure_unit_test_coverage")
@patch("coverage_off_target.create_merged_library")
@patch("coverage_off_target.gather_and_build_libs")
@patch("coverage_off_target.create_out_dir")
def test_main(self, mock_create_out_dir, mock_gather_and_build_libs,
mock_create_merged_library, mock_measure_unit_test_coverage,
mock_generate_report):
out_dir = Path("foo")
merged_so = out_dir / "merged.so"
merged_profile = out_dir / "merged.profdata"
libs = ["//foo/bar1", "//foo/bar2"]
mock_create_out_dir.side_effect = (out_dir,)
mock_gather_and_build_libs.side_effect = (libs,)
coverage_off_target.main()
mock_create_out_dir.assert_called_once_with(Path("coverage"))
mock_gather_and_build_libs.assert_called()
mock_create_merged_library.assert_called_once_with(libs, merged_so)
mock_measure_unit_test_coverage.assert_called_once_with(merged_profile)
mock_generate_report.assert_called_once_with(out_dir, merged_profile,
merged_so, False)
if __name__ == "__main__":
unittest.main()