Skip to content

Commit 04c98b0

Browse files
committed
add coverage filtering
1 parent b5ee0c0 commit 04c98b0

File tree

4 files changed

+165
-83
lines changed

4 files changed

+165
-83
lines changed

UNITTESTS/mbed_unittest.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
from __future__ import print_function
2424
import os
2525
import logging
26+
import re
2627

2728
from unit_test.options import get_options_parser, \
2829
pretty_print_test_options
2930
from unit_test.settings import DEFAULT_CMAKE_GENERATORS
3031
from unit_test.test import UnitTestTool
3132
from unit_test.new import UnitTestGeneratorTool
33+
from unit_test.coverage import CoverageAPI
3234

3335
def _mbed_unittest_test(options, cwd, pwd):
3436
if options is None:
@@ -80,14 +82,28 @@ def _mbed_unittest_test(options, cwd, pwd):
8082
tool.build_tests()
8183

8284
if options.run_only:
85+
tool.run_tests(filter_regex=options.test_regex)
86+
8387
# If code coverage generation:
8488
if options.coverage:
85-
# Run tests and generate reports
86-
tool.generate_coverage_report(coverage_output_type=options.coverage,
87-
excludes=[pwd, options.build],
88-
build_path=options.build)
89-
else:
90-
tool.run_tests(filter_regex=options.test_regex) # Only run tests
89+
cov_api = CoverageAPI(
90+
mbed_os_root=os.path.normpath(os.path.join(pwd, "..")),
91+
build_dir=options.build)
92+
93+
# Generate reports
94+
outputs = [options.coverage]
95+
if options.coverage == "both":
96+
outputs = ["html", "xml"]
97+
98+
excludes = [pwd, options.build]
99+
100+
if not options.include_headers:
101+
excludes.append(re.compile(".*\\.h"))
102+
103+
cov_api.generate_reports(outputs=outputs,
104+
excludes=excludes,
105+
filter_regex=options.test_regex,
106+
build_path=options.build)
91107

92108
def _mbed_unittest_new(options, pwd):
93109
if options is None:

UNITTESTS/unit_test/coverage.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
"""
2+
Copyright (c) 2018, Arm Limited
3+
SPDX-License-Identifier: Apache-2.0
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
17+
18+
GENERATE TEST CODE COVERAGE
19+
"""
20+
21+
import os
22+
import logging
23+
import posixpath
24+
import re
25+
26+
from .utils import execute_program
27+
from .get_tools import get_gcov_program, \
28+
get_gcovr_program
29+
30+
class CoverageAPI(object):
31+
"""
32+
Generate code coverage reports for unit tests.
33+
"""
34+
def __init__(self, mbed_os_root=None, build_dir=None):
35+
self.root = mbed_os_root
36+
37+
if not self.root:
38+
self.root = os.path.normpath(os.path.join(
39+
os.path.dirname(os.path.realpath(__file__)),
40+
"../.."))
41+
42+
self.build_dir = build_dir
43+
44+
if not self.build_dir:
45+
logging.error("No build directory given for CoverageAPI.")
46+
47+
def _gen_cmd(self, coverage_type, excludes, filter_regex):
48+
# Generate coverage generation command:
49+
args = [get_gcovr_program(),
50+
"--gcov-executable",
51+
get_gcov_program(),
52+
"-r",
53+
os.path.relpath(self.root, self.build_dir),
54+
"."]
55+
56+
if coverage_type == "html":
57+
args.extend(["--html",
58+
"--html-detail",
59+
"-o",
60+
"./coverage/index.html"])
61+
elif coverage_type == "xml":
62+
args.extend(["-x",
63+
"-o",
64+
"./coverage.xml"])
65+
66+
# Add exclude filters:
67+
for path in excludes:
68+
# Use posix separators if path is string
69+
if isinstance(path, ("".__class__, u"".__class__)):
70+
path = path.replace("\\", "/")
71+
args.extend(["-e", path])
72+
# Use regular expressions as is
73+
elif isinstance(path, type(re.compile(""))):
74+
args.extend(["-e", path.pattern])
75+
76+
# Add include filters:
77+
if filter_regex:
78+
filters = filter_regex.split(",")
79+
80+
for filt in filters:
81+
regex = "(.+/)?%s" % filt.replace("-", "/")
82+
args.extend(["-f", regex])
83+
84+
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
85+
args.append("-v")
86+
87+
return args
88+
89+
def generate_reports(self,
90+
outputs,
91+
excludes=None,
92+
filter_regex=None,
93+
build_path=None):
94+
"""
95+
Run tests to generate coverage data, and generate coverage reports.
96+
97+
Positional arguments:
98+
outputs - list of types to generate
99+
100+
Keyword arguments:
101+
excludes - list of paths to exclude from the report
102+
filter_regex - comma-separated string to use for test filtering
103+
build_path - build path
104+
"""
105+
106+
if get_gcovr_program() is None:
107+
logging.error("No gcovr tool found in path. \
108+
Cannot generate coverage reports.")
109+
return
110+
111+
if build_path is None:
112+
build_path = os.getcwd()
113+
114+
if outputs is None:
115+
outputs = []
116+
117+
if excludes is None:
118+
excludes = []
119+
120+
for output in outputs:
121+
if output == "html":
122+
# Create build directory if not exist.
123+
coverage_path = os.path.join(build_path, "coverage")
124+
if not os.path.exists(coverage_path):
125+
os.mkdir(coverage_path)
126+
127+
args = self._gen_cmd(output, excludes, filter_regex)
128+
129+
if output == "html":
130+
execute_program(args,
131+
"HTML code coverage report generation failed.",
132+
"HTML code coverage report created.")
133+
elif output == "xml":
134+
execute_program(args,
135+
"XML code coverage report generation failed.",
136+
"XML code coverage report created.")

UNITTESTS/unit_test/options.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ def get_options_parser():
7575
help="Generate code coverage report",
7676
dest="coverage")
7777

78+
parser.add_argument("--include-headers",
79+
action="store_true",
80+
help="Include headers to coverage reports, defaults to false.",
81+
dest="include_headers")
82+
7883
parser.add_argument("-m",
7984
"--make-program",
8085
default=get_make_tool(),
@@ -140,3 +145,4 @@ def pretty_print_test_options(options=None):
140145
if options.coverage:
141146
logging.info(" [%s] \tGenerate coverage reports", "SET")
142147
logging.info(" \t\t - Output: %s", options.coverage)
148+
logging.info(" \t\t - Include headers: %s", options.include_headers)

UNITTESTS/unit_test/test.py

Lines changed: 1 addition & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@
2727
from .get_tools import get_make_tool, \
2828
get_cmake_tool, \
2929
get_cxx_tool, \
30-
get_c_tool, \
31-
get_gcov_program, \
32-
get_gcovr_program
30+
get_c_tool
3331
from .settings import DEFAULT_CMAKE_GENERATORS
3432

3533
class UnitTestTool(object):
@@ -115,80 +113,6 @@ def build_tests(self):
115113
"Building unit tests failed.",
116114
"Unit tests built successfully.")
117115

118-
def _get_coverage_script(self, coverage_type, excludes):
119-
args = [get_gcovr_program(),
120-
"--gcov-executable",
121-
get_gcov_program(),
122-
"-r",
123-
"../..",
124-
"."]
125-
126-
if coverage_type == "html":
127-
args.extend(["--html",
128-
"--html-detail",
129-
"-o",
130-
"./coverage/index.html"])
131-
elif coverage_type == "xml":
132-
args.extend(["-x",
133-
"-o",
134-
"./coverage.xml"])
135-
136-
for path in excludes:
137-
args.extend(["-e", path.replace("\\", "/")])
138-
139-
#Exclude header files from report
140-
args.extend(["-e", ".*\.h"])
141-
142-
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
143-
args.append("-v")
144-
145-
return args
146-
147-
def generate_coverage_report(self,
148-
coverage_output_type=None,
149-
excludes=None,
150-
build_path=None):
151-
"""
152-
Run tests to generate coverage data, and generate coverage reports.
153-
"""
154-
155-
self.run_tests()
156-
157-
if get_gcovr_program() is None:
158-
logging.error("No gcovr tool found in path. \
159-
Cannot generate coverage report.")
160-
return
161-
162-
if build_path is None:
163-
build_path = os.getcwd()
164-
165-
if coverage_output_type is None:
166-
logging.warning("No coverage output type give. \
167-
Cannot generate coverage reports.")
168-
return
169-
170-
if excludes is None:
171-
excludes = []
172-
173-
if coverage_output_type == "html" or coverage_output_type == "both":
174-
# Create build directory if not exist.
175-
coverage_path = os.path.join(build_path, "coverage")
176-
if not os.path.exists(coverage_path):
177-
os.mkdir(coverage_path)
178-
179-
args = self._get_coverage_script("html", excludes)
180-
181-
execute_program(args,
182-
"HTML code coverage report generation failed.",
183-
"HTML code coverage report created.")
184-
185-
if coverage_output_type == "xml" or coverage_output_type == "both":
186-
args = self._get_coverage_script("xml", excludes)
187-
188-
execute_program(args,
189-
"XML code coverage report generation failed.",
190-
"XML code coverage report created.")
191-
192116
def run_tests(self, filter_regex=None):
193117
"""
194118
Run unit tests.

0 commit comments

Comments
 (0)