From 04c98b02234e3101b30d8dd45311e537ab0dc179 Mon Sep 17 00:00:00 2001 From: Lari-Matias Orjala Date: Mon, 17 Sep 2018 15:38:28 +0300 Subject: [PATCH] add coverage filtering --- UNITTESTS/mbed_unittest.py | 28 +++++-- UNITTESTS/unit_test/coverage.py | 136 ++++++++++++++++++++++++++++++++ UNITTESTS/unit_test/options.py | 6 ++ UNITTESTS/unit_test/test.py | 78 +----------------- 4 files changed, 165 insertions(+), 83 deletions(-) create mode 100644 UNITTESTS/unit_test/coverage.py diff --git a/UNITTESTS/mbed_unittest.py b/UNITTESTS/mbed_unittest.py index e8e6eadad9..627c58ed0e 100755 --- a/UNITTESTS/mbed_unittest.py +++ b/UNITTESTS/mbed_unittest.py @@ -23,12 +23,14 @@ UNIT TEST BUILD & RUN from __future__ import print_function import os import logging +import re from unit_test.options import get_options_parser, \ pretty_print_test_options from unit_test.settings import DEFAULT_CMAKE_GENERATORS from unit_test.test import UnitTestTool from unit_test.new import UnitTestGeneratorTool +from unit_test.coverage import CoverageAPI def _mbed_unittest_test(options, cwd, pwd): if options is None: @@ -80,14 +82,28 @@ def _mbed_unittest_test(options, cwd, pwd): tool.build_tests() if options.run_only: + tool.run_tests(filter_regex=options.test_regex) + # If code coverage generation: if options.coverage: - # Run tests and generate reports - tool.generate_coverage_report(coverage_output_type=options.coverage, - excludes=[pwd, options.build], - build_path=options.build) - else: - tool.run_tests(filter_regex=options.test_regex) # Only run tests + cov_api = CoverageAPI( + mbed_os_root=os.path.normpath(os.path.join(pwd, "..")), + build_dir=options.build) + + # Generate reports + outputs = [options.coverage] + if options.coverage == "both": + outputs = ["html", "xml"] + + excludes = [pwd, options.build] + + if not options.include_headers: + excludes.append(re.compile(".*\\.h")) + + cov_api.generate_reports(outputs=outputs, + excludes=excludes, + filter_regex=options.test_regex, + build_path=options.build) def _mbed_unittest_new(options, pwd): if options is None: diff --git a/UNITTESTS/unit_test/coverage.py b/UNITTESTS/unit_test/coverage.py new file mode 100644 index 0000000000..ba0adbb960 --- /dev/null +++ b/UNITTESTS/unit_test/coverage.py @@ -0,0 +1,136 @@ +""" +Copyright (c) 2018, Arm Limited +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +GENERATE TEST CODE COVERAGE +""" + +import os +import logging +import posixpath +import re + +from .utils import execute_program +from .get_tools import get_gcov_program, \ + get_gcovr_program + +class CoverageAPI(object): + """ + Generate code coverage reports for unit tests. + """ + def __init__(self, mbed_os_root=None, build_dir=None): + self.root = mbed_os_root + + if not self.root: + self.root = os.path.normpath(os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "../..")) + + self.build_dir = build_dir + + if not self.build_dir: + logging.error("No build directory given for CoverageAPI.") + + def _gen_cmd(self, coverage_type, excludes, filter_regex): + # Generate coverage generation command: + args = [get_gcovr_program(), + "--gcov-executable", + get_gcov_program(), + "-r", + os.path.relpath(self.root, self.build_dir), + "."] + + if coverage_type == "html": + args.extend(["--html", + "--html-detail", + "-o", + "./coverage/index.html"]) + elif coverage_type == "xml": + args.extend(["-x", + "-o", + "./coverage.xml"]) + + # Add exclude filters: + for path in excludes: + # Use posix separators if path is string + if isinstance(path, ("".__class__, u"".__class__)): + path = path.replace("\\", "/") + args.extend(["-e", path]) + # Use regular expressions as is + elif isinstance(path, type(re.compile(""))): + args.extend(["-e", path.pattern]) + + # Add include filters: + if filter_regex: + filters = filter_regex.split(",") + + for filt in filters: + regex = "(.+/)?%s" % filt.replace("-", "/") + args.extend(["-f", regex]) + + if logging.getLogger().getEffectiveLevel() == logging.DEBUG: + args.append("-v") + + return args + + def generate_reports(self, + outputs, + excludes=None, + filter_regex=None, + build_path=None): + """ + Run tests to generate coverage data, and generate coverage reports. + + Positional arguments: + outputs - list of types to generate + + Keyword arguments: + excludes - list of paths to exclude from the report + filter_regex - comma-separated string to use for test filtering + build_path - build path + """ + + if get_gcovr_program() is None: + logging.error("No gcovr tool found in path. \ + Cannot generate coverage reports.") + return + + if build_path is None: + build_path = os.getcwd() + + if outputs is None: + outputs = [] + + if excludes is None: + excludes = [] + + for output in outputs: + if output == "html": + # Create build directory if not exist. + coverage_path = os.path.join(build_path, "coverage") + if not os.path.exists(coverage_path): + os.mkdir(coverage_path) + + args = self._gen_cmd(output, excludes, filter_regex) + + if output == "html": + execute_program(args, + "HTML code coverage report generation failed.", + "HTML code coverage report created.") + elif output == "xml": + execute_program(args, + "XML code coverage report generation failed.", + "XML code coverage report created.") diff --git a/UNITTESTS/unit_test/options.py b/UNITTESTS/unit_test/options.py index f192006cdf..0c9eefa7af 100644 --- a/UNITTESTS/unit_test/options.py +++ b/UNITTESTS/unit_test/options.py @@ -75,6 +75,11 @@ def get_options_parser(): help="Generate code coverage report", dest="coverage") + parser.add_argument("--include-headers", + action="store_true", + help="Include headers to coverage reports, defaults to false.", + dest="include_headers") + parser.add_argument("-m", "--make-program", default=get_make_tool(), @@ -140,3 +145,4 @@ Mbed OS unit testing: if options.coverage: logging.info(" [%s] \tGenerate coverage reports", "SET") logging.info(" \t\t - Output: %s", options.coverage) + logging.info(" \t\t - Include headers: %s", options.include_headers) diff --git a/UNITTESTS/unit_test/test.py b/UNITTESTS/unit_test/test.py index 6be991bb79..dbf515e4e0 100644 --- a/UNITTESTS/unit_test/test.py +++ b/UNITTESTS/unit_test/test.py @@ -27,9 +27,7 @@ from .utils import execute_program from .get_tools import get_make_tool, \ get_cmake_tool, \ get_cxx_tool, \ - get_c_tool, \ - get_gcov_program, \ - get_gcovr_program + get_c_tool from .settings import DEFAULT_CMAKE_GENERATORS class UnitTestTool(object): @@ -115,80 +113,6 @@ class UnitTestTool(object): "Building unit tests failed.", "Unit tests built successfully.") - def _get_coverage_script(self, coverage_type, excludes): - args = [get_gcovr_program(), - "--gcov-executable", - get_gcov_program(), - "-r", - "../..", - "."] - - if coverage_type == "html": - args.extend(["--html", - "--html-detail", - "-o", - "./coverage/index.html"]) - elif coverage_type == "xml": - args.extend(["-x", - "-o", - "./coverage.xml"]) - - for path in excludes: - args.extend(["-e", path.replace("\\", "/")]) - - #Exclude header files from report - args.extend(["-e", ".*\.h"]) - - if logging.getLogger().getEffectiveLevel() == logging.DEBUG: - args.append("-v") - - return args - - def generate_coverage_report(self, - coverage_output_type=None, - excludes=None, - build_path=None): - """ - Run tests to generate coverage data, and generate coverage reports. - """ - - self.run_tests() - - if get_gcovr_program() is None: - logging.error("No gcovr tool found in path. \ - Cannot generate coverage report.") - return - - if build_path is None: - build_path = os.getcwd() - - if coverage_output_type is None: - logging.warning("No coverage output type give. \ - Cannot generate coverage reports.") - return - - if excludes is None: - excludes = [] - - if coverage_output_type == "html" or coverage_output_type == "both": - # Create build directory if not exist. - coverage_path = os.path.join(build_path, "coverage") - if not os.path.exists(coverage_path): - os.mkdir(coverage_path) - - args = self._get_coverage_script("html", excludes) - - execute_program(args, - "HTML code coverage report generation failed.", - "HTML code coverage report created.") - - if coverage_output_type == "xml" or coverage_output_type == "both": - args = self._get_coverage_script("xml", excludes) - - execute_program(args, - "XML code coverage report generation failed.", - "XML code coverage report created.") - def run_tests(self, filter_regex=None): """ Run unit tests.