From 111e3df45dddd5b4ae98ad957f8183980c90a503 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Wed, 10 May 2017 19:31:12 +0300 Subject: [PATCH 1/2] Moved coverage commands to a standalone script. --- mesonbuild/backend/ninjabackend.py | 12 ++++++ mesonbuild/coredata.py | 1 + mesonbuild/mesonmain.py | 3 ++ mesonbuild/scripts/coverage.py | 61 ++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 mesonbuild/scripts/coverage.py diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 4885f4bbe..572724805 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -567,6 +567,18 @@ int dummy; self.processed_targets[target.name + target.type_suffix()] = True def generate_coverage_rules(self, outfile): + e = NinjaBuildElement(self.all_outputs, 'coverage', 'CUSTOM_COMMAND', 'PHONY') + e.add_item('COMMAND', [sys.executable, + self.environment.get_build_command(), + '--internal', 'coverage', + self.environment.get_source_dir(), + self.environment.get_build_dir(), + self.environment.get_log_dir()]) + e.add_item('description', 'Generates coverage reports.') + e.write(outfile) + self.generate_coverage_legacy_rules(outfile) + + def generate_coverage_legacy_rules(self, outfile): (gcovr_exe, lcov_exe, genhtml_exe) = environment.find_coverage_tools() added_rule = False if gcovr_exe: diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index a336278b2..7bc20f8b6 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -334,6 +334,7 @@ forbidden_target_names = {'clean': None, 'clean-ctlist': None, 'clean-gcno': None, 'clean-gcda': None, + 'coverage': None, 'coverage-text': None, 'coverage-xml': None, 'coverage-html': None, diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 282df363d..4fecc5847 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -249,6 +249,9 @@ def run_script_command(args): elif cmdname == 'dist': import mesonbuild.scripts.dist as abc cmdfunc = abc.run + elif cmdname == 'coverage': + import mesonbuild.scripts.coverage as abc + cmdfunc = abc.run else: raise MesonException('Unknown internal command {}.'.format(cmdname)) return cmdfunc(cmdargs) diff --git a/mesonbuild/scripts/coverage.py b/mesonbuild/scripts/coverage.py new file mode 100644 index 000000000..59e475a84 --- /dev/null +++ b/mesonbuild/scripts/coverage.py @@ -0,0 +1,61 @@ +# Copyright 2017 The Meson development team + +# 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. + +from mesonbuild import environment + +import sys, os, subprocess, re + +def coverage(source_root, build_root, log_dir): + (gcovr_exe, lcov_exe, genhtml_exe) = environment.find_coverage_tools() + if gcovr_exe: + subprocess.check_call([gcovr_exe, + '-x', + '-r', source_root, + '-o', os.path.join(log_dir, 'coverage.xml'), + ]) + subprocess.check_call([gcovr_exe, + '-r', source_root, + '-o', os.path.join(log_dir, 'coverage.txt'), + ]) + if lcov_exe and genhtml_exe: + htmloutdir = os.path.join(log_dir, 'coveragereport') + covinfo = os.path.join(log_dir, 'coverage.info') + lcov_command = [lcov_exe, + '--directory', build_root, + '--capture', + '--output-file', covinfo, + '--no-checksum', + '--rc', 'lcov_branch_coverage=1', + ] + genhtml_command = [genhtml_exe, + '--prefix', build_root, + '--output-directory', htmloutdir, + '--title', 'Code coverage', + '--legend', + '--show-details', + '--branch-coverage', + covinfo] + subprocess.check_call(lcov_command) + subprocess.check_call(genhtml_command) + return 0 + +def run(args): + if not os.path.isfile('build.ninja'): + print('Coverage currently only works with the Ninja backend.') + return 1 + source_root, build_root, log_dir = args[:] + return coverage(source_root, build_root, log_dir) + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) \ No newline at end of file From 95f8cb93b3d0ce99ff99146517625e0d79e848ad Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Wed, 10 May 2017 19:57:47 +0300 Subject: [PATCH 2/2] Strip system directories and show coverage for files not executed at all. Closes #1721. --- mesonbuild/scripts/coverage.py | 56 ++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/mesonbuild/scripts/coverage.py b/mesonbuild/scripts/coverage.py index 59e475a84..441ec0c8e 100644 --- a/mesonbuild/scripts/coverage.py +++ b/mesonbuild/scripts/coverage.py @@ -14,7 +14,12 @@ from mesonbuild import environment -import sys, os, subprocess, re +import sys, os, subprocess + +def remove_dir_from_trace(lcov_command, covfile, dirname): + tmpfile = covfile + '.tmp' + subprocess.check_call([lcov_command, '--remove', covfile, dirname, '-o', tmpfile]) + os.replace(tmpfile, covfile) def coverage(source_root, build_root, log_dir): (gcovr_exe, lcov_exe, genhtml_exe) = environment.find_coverage_tools() @@ -31,23 +36,36 @@ def coverage(source_root, build_root, log_dir): if lcov_exe and genhtml_exe: htmloutdir = os.path.join(log_dir, 'coveragereport') covinfo = os.path.join(log_dir, 'coverage.info') - lcov_command = [lcov_exe, - '--directory', build_root, - '--capture', - '--output-file', covinfo, - '--no-checksum', - '--rc', 'lcov_branch_coverage=1', - ] - genhtml_command = [genhtml_exe, - '--prefix', build_root, - '--output-directory', htmloutdir, - '--title', 'Code coverage', - '--legend', - '--show-details', - '--branch-coverage', - covinfo] - subprocess.check_call(lcov_command) - subprocess.check_call(genhtml_command) + initial_tracefile = covinfo + '.initial' + run_tracefile = covinfo + '.run' + subprocess.check_call([lcov_exe, + '--directory', build_root, + '--capture', + '--initial', + '--output-file', + initial_tracefile]) + subprocess.check_call([lcov_exe, + '--directory', build_root, + '--capture', + '--output-file', run_tracefile, + '--no-checksum', + '--rc', 'lcov_branch_coverage=1', + ]) + # Join initial and test results. + subprocess.check_call([lcov_exe, + '-a', initial_tracefile, + '-a', run_tracefile, + '-o', covinfo]) + remove_dir_from_trace(lcov_exe, covinfo, '/usr/include/*') + remove_dir_from_trace(lcov_exe, covinfo, '/usr/local/include/*') + subprocess.check_call([genhtml_exe, + '--prefix', build_root, + '--output-directory', htmloutdir, + '--title', 'Code coverage', + '--legend', + '--show-details', + '--branch-coverage', + covinfo]) return 0 def run(args): @@ -58,4 +76,4 @@ def run(args): return coverage(source_root, build_root, log_dir) if __name__ == '__main__': - sys.exit(run(sys.argv[1:])) \ No newline at end of file + sys.exit(run(sys.argv[1:]))