coverage: llvm-cov support

pull/7196/head
Cary Converse 5 years ago committed by Cary Converse
parent 804cefc94c
commit a198e5d191
  1. 9
      azure-pipelines.yml
  2. 2
      ci/ciimage/arch/install.sh
  3. 3
      ci/ciimage/bionic/install.sh
  4. 3
      ci/ciimage/eoan/install.sh
  5. 2
      ci/ciimage/fedora/install.sh
  6. 4
      ci/ciimage/opensuse/install.sh
  7. 3
      docs/markdown/howtox.md
  8. 4
      docs/markdown/snippets/clang_coverage.md
  9. 10
      mesonbuild/backend/ninjabackend.py
  10. 11
      mesonbuild/environment.py
  11. 46
      mesonbuild/scripts/coverage.py
  12. 83
      run_unittests.py

@ -91,6 +91,7 @@ jobs:
libjsoncpp19,^
librhash0,^
libuv1,^
libxml2,^
ninja,^
python2-devel,^
python3-devel,^
@ -102,8 +103,8 @@ jobs:
displayName: Install Dependencies
- script: |
set PATH=%CYGWIN_ROOT%\bin;%SYSTEMROOT%\system32
env.exe -- python3 -m pip --disable-pip-version-check install pefile pytest-xdist jsonschema
displayName: pip install pefile pytest-xdist jsonschema
env.exe -- python3 -m pip --disable-pip-version-check install gcovr pefile pytest-xdist jsonschema
displayName: pip install gcovr pefile pytest-xdist jsonschema
- script: |
set BOOST_ROOT=
set PATH=%CYGWIN_ROOT%\bin;%SYSTEMROOT%\system32
@ -165,6 +166,8 @@ jobs:
git ^
mercurial ^
mingw-w64-$(MSYS2_ARCH)-cmake ^
mingw-w64-$(MSYS2_ARCH)-lcov ^
mingw-w64-$(MSYS2_ARCH)-libxml2 ^
mingw-w64-$(MSYS2_ARCH)-ninja ^
mingw-w64-$(MSYS2_ARCH)-pkg-config ^
mingw-w64-$(MSYS2_ARCH)-python2 ^
@ -172,7 +175,7 @@ jobs:
mingw-w64-$(MSYS2_ARCH)-python3-setuptools ^
mingw-w64-$(MSYS2_ARCH)-python3-pip ^
%TOOLCHAIN%
%MSYS2_ROOT%\usr\bin\bash -lc "python3 -m pip --disable-pip-version-check install pefile jsonschema"
%MSYS2_ROOT%\usr\bin\bash -lc "python3 -m pip --disable-pip-version-check install gcovr jsonschema pefile"
displayName: Install Dependencies
- script: |
set BOOST_ROOT=

@ -17,7 +17,7 @@ pkgs=(
)
aur_pkgs=(scalapack)
pip_pkgs=(hotdoc)
pip_pkgs=(hotdoc gcovr)
cleanup_pkgs=(go)
AUR_USER=docker

@ -15,6 +15,7 @@ pkgs=(
qt4-linguist-tools qt5-default qtbase5-private-dev
python-dev
libomp-dev
llvm lcov
ldc
libclang-dev
libgcrypt20-dev
@ -45,7 +46,7 @@ done
# packages
eatmydata apt-get -y install "${pkgs[@]}"
eatmydata python3 -m pip install codecov jsonschema
eatmydata python3 -m pip install codecov gcovr jsonschema
# Install the ninja 0.10
wget https://github.com/ninja-build/ninja/releases/download/v1.10.0/ninja-linux.zip

@ -18,6 +18,7 @@ pkgs=(
qt4-linguist-tools
python-dev
libomp-dev
llvm lcov
dub ldc
mingw-w64 mingw-w64-tools nim
libclang-dev
@ -42,7 +43,7 @@ eatmydata apt-get -y build-dep meson
eatmydata apt-get -y install "${pkgs[@]}"
eatmydata apt-get -y install --no-install-recommends wine-stable # Wine is special
eatmydata python3 -m pip install hotdoc codecov jsonschema
eatmydata python3 -m pip install hotdoc codecov gcovr jsonschema
# dub stuff
dub_fetch urld

@ -21,7 +21,7 @@ dnf -y upgrade
# Install deps
dnf -y install "${pkgs[@]}"
python3 -m pip install hotdoc gobject PyGObject
python3 -m pip install hotdoc gcovr gobject PyGObject
# Cleanup
dnf -y clean all

@ -7,7 +7,7 @@ source /ci/common.sh
pkgs=(
python3-setuptools python3-wheel python3-pip python3-pytest-xdist python3 python3-lxml
ninja make git autoconf automake patch python3-Cython python3-jsonschema
elfutils gcc gcc-c++ gcc-fortran gcc-objc gcc-obj-c++ vala rust bison flex curl
elfutils gcc gcc-c++ gcc-fortran gcc-objc gcc-obj-c++ vala rust bison flex curl lcov
mono-core gtkmm3-devel gtest gmock protobuf-devel wxGTK3-3_2-devel gobject-introspection-devel
itstool gtk3-devel java-15-openjdk-devel gtk-doc llvm-devel clang-devel libSDL2-devel graphviz-devel zlib-devel zlib-devel-static
#hdf5-devel netcdf-devel libscalapack2-openmpi3-devel libscalapack2-gnu-openmpi3-hpc-devel openmpi3-devel
@ -26,7 +26,7 @@ zypper --non-interactive update
# Install deps
zypper install -y "${pkgs[@]}"
python3 -m pip install hotdoc gobject PyGObject
python3 -m pip install hotdoc gcovr gobject PyGObject
echo 'export PKG_CONFIG_PATH="/usr/lib64/mpi/gcc/openmpi3/lib64/pkgconfig:$PKG_CONFIG_PATH"' >> /ci/env_vars.sh

@ -155,8 +155,7 @@ $ ninja coverage-html (or coverage-xml)
The coverage report can be found in the meson-logs subdirectory.
Note: Currently, Meson does not support generating coverage reports
with Clang.
*New in 0.55.0* llvm-cov support for use with clang
## Add some optimization to debug builds

@ -0,0 +1,4 @@
## Clang coverage support
llvm-cov is now used to generate coverage information when clang is used as
the compiler.

@ -968,6 +968,13 @@ int dummy;
self.processed_targets[target.get_id()] = True
def generate_coverage_command(self, elem, outputs):
targets = self.build.get_targets().values()
use_llvm_cov = False
for target in targets:
for compiler in target.compilers.values():
if compiler.get_id() == 'clang' and not compiler.info.is_darwin():
use_llvm_cov = True
break
elem.add_item('COMMAND', self.environment.get_build_command() +
['--internal', 'coverage'] +
outputs +
@ -975,7 +982,8 @@ int dummy;
os.path.join(self.environment.get_source_dir(),
self.build.get_subproject_dir()),
self.environment.get_build_dir(),
self.environment.get_log_dir()])
self.environment.get_log_dir()] +
['--use_llvm_cov'] if use_llvm_cov else [])
def generate_coverage_rules(self):
e = NinjaBuildElement(self.all_outputs, 'meson-coverage', 'CUSTOM_COMMAND', 'PHONY')

@ -134,9 +134,18 @@ def detect_gcovr(min_version='3.3', new_rootdir_version='4.2', log=False):
return gcovr_exe, mesonlib.version_compare(found, '>=' + new_rootdir_version)
return None, None
def detect_llvm_cov():
tools = get_llvm_tool_names('llvm-cov')
for tool in tools:
if mesonlib.exe_exists([tool, '--version']):
return tool
return None
def find_coverage_tools():
gcovr_exe, gcovr_new_rootdir = detect_gcovr()
llvm_cov_exe = detect_llvm_cov()
lcov_exe = 'lcov'
genhtml_exe = 'genhtml'
@ -145,7 +154,7 @@ def find_coverage_tools():
if not mesonlib.exe_exists([genhtml_exe, '--version']):
genhtml_exe = None
return gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe
return gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe, llvm_cov_exe
def detect_ninja(version: str = '1.7', log: bool = False) -> str:
r = detect_ninja_command_and_version(version, log)

@ -12,15 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from mesonbuild import environment
from mesonbuild import environment, mesonlib
import argparse, sys, os, subprocess, pathlib
import argparse, sys, os, subprocess, pathlib, stat
def coverage(outputs, source_root, subproject_root, build_root, log_dir):
def coverage(outputs, source_root, subproject_root, build_root, log_dir, use_llvm_cov):
outfiles = []
exitcode = 0
(gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe) = environment.find_coverage_tools()
(gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe, llvm_cov_exe) = environment.find_coverage_tools()
# gcovr >= 4.2 requires a different syntax for out of source builds
if gcovr_new_rootdir:
@ -28,13 +28,18 @@ def coverage(outputs, source_root, subproject_root, build_root, log_dir):
else:
gcovr_base_cmd = [gcovr_exe, '-r', build_root]
if use_llvm_cov:
gcov_exe_args = ['--gcov-executable', llvm_cov_exe + ' gcov']
else:
gcov_exe_args = []
if not outputs or 'xml' in outputs:
if gcovr_exe:
subprocess.check_call(gcovr_base_cmd +
['-x',
'-e', subproject_root,
'-o', os.path.join(log_dir, 'coverage.xml'),
])
'-o', os.path.join(log_dir, 'coverage.xml')
] + gcov_exe_args)
outfiles.append(('Xml', pathlib.Path(log_dir, 'coverage.xml')))
elif outputs:
print('gcovr >= 3.3 needed to generate Xml coverage report')
@ -44,8 +49,8 @@ def coverage(outputs, source_root, subproject_root, build_root, log_dir):
if gcovr_exe:
subprocess.check_call(gcovr_base_cmd +
['-e', subproject_root,
'-o', os.path.join(log_dir, 'coverage.txt'),
])
'-o', os.path.join(log_dir, 'coverage.txt')
] + gcov_exe_args)
outfiles.append(('Text', pathlib.Path(log_dir, 'coverage.txt')))
elif outputs:
print('gcovr >= 3.3 needed to generate text coverage report')
@ -58,19 +63,34 @@ def coverage(outputs, source_root, subproject_root, build_root, log_dir):
initial_tracefile = covinfo + '.initial'
run_tracefile = covinfo + '.run'
raw_tracefile = covinfo + '.raw'
if use_llvm_cov:
# Create a shim to allow using llvm-cov as a gcov tool.
if mesonlib.is_windows():
llvm_cov_shim_path = os.path.join(log_dir, 'llvm-cov.bat')
with open(llvm_cov_shim_path, 'w') as llvm_cov_bat:
llvm_cov_bat.write('@"{}" gcov %*'.format(llvm_cov_exe))
else:
llvm_cov_shim_path = os.path.join(log_dir, 'llvm-cov.sh')
with open(llvm_cov_shim_path, 'w') as llvm_cov_sh:
llvm_cov_sh.write('#!/usr/bin/env sh\nexec "{}" gcov $@'.format(llvm_cov_exe))
os.chmod(llvm_cov_shim_path, os.stat(llvm_cov_shim_path).st_mode | stat.S_IEXEC)
gcov_tool_args = ['--gcov-tool', llvm_cov_shim_path]
else:
gcov_tool_args = []
subprocess.check_call([lcov_exe,
'--directory', build_root,
'--capture',
'--initial',
'--output-file',
initial_tracefile])
initial_tracefile] +
gcov_tool_args)
subprocess.check_call([lcov_exe,
'--directory', build_root,
'--capture',
'--output-file', run_tracefile,
'--no-checksum',
'--rc', 'lcov_branch_coverage=1',
])
'--rc', 'lcov_branch_coverage=1'] +
gcov_tool_args)
# Join initial and test results.
subprocess.check_call([lcov_exe,
'-a', initial_tracefile,
@ -137,6 +157,8 @@ def run(args):
const='xml', help='generate Xml report')
parser.add_argument('--html', dest='outputs', action='append_const',
const='html', help='generate Html report')
parser.add_argument('--use_llvm_cov', action='store_true',
help='use llvm-cov')
parser.add_argument('source_root')
parser.add_argument('subproject_root')
parser.add_argument('build_root')
@ -144,7 +166,7 @@ def run(args):
options = parser.parse_args(args)
return coverage(options.outputs, options.source_root,
options.subproject_root, options.build_root,
options.log_dir)
options.log_dir, options.use_llvm_cov)
if __name__ == '__main__':
sys.exit(run(sys.argv[1:]))

@ -4829,6 +4829,74 @@ recommended as it is not supported on some platforms''')
self.assertEqual(parsed_help['usage'], parsed_section['usage'])
self.assertEqual(parsed_help['arguments'], parsed_section['arguments'])
def test_coverage(self):
gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
if not gcovr_exe:
raise unittest.SkipTest('gcovr not found, or too old')
testdir = os.path.join(self.common_test_dir, '1 trivial')
env = get_fake_env(testdir, self.builddir, self.prefix)
cc = env.detect_c_compiler(MachineChoice.HOST)
if cc.get_id() == 'clang':
if not mesonbuild.environment.detect_llvm_cov():
raise unittest.SkipTest('llvm-cov not found')
if cc.get_id() == 'msvc':
raise unittest.SkipTest('Test only applies to non-MSVC compilers')
self.init(testdir, extra_args=['-Db_coverage=true'])
self.build()
self.run_tests()
self.run_target('coverage')
def test_coverage_html(self):
gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
if not gcovr_exe:
raise unittest.SkipTest('gcovr not found, or too old')
testdir = os.path.join(self.common_test_dir, '1 trivial')
env = get_fake_env(testdir, self.builddir, self.prefix)
cc = env.detect_c_compiler(MachineChoice.HOST)
if cc.get_id() == 'clang':
if not mesonbuild.environment.detect_llvm_cov():
raise unittest.SkipTest('llvm-cov not found')
if cc.get_id() == 'msvc':
raise unittest.SkipTest('Test only applies to non-MSVC compilers')
self.init(testdir, extra_args=['-Db_coverage=true'])
self.build()
self.run_tests()
self.run_target('coverage-html')
def test_coverage_text(self):
gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
if not gcovr_exe:
raise unittest.SkipTest('gcovr not found, or too old')
testdir = os.path.join(self.common_test_dir, '1 trivial')
env = get_fake_env(testdir, self.builddir, self.prefix)
cc = env.detect_c_compiler(MachineChoice.HOST)
if cc.get_id() == 'clang':
if not mesonbuild.environment.detect_llvm_cov():
raise unittest.SkipTest('llvm-cov not found')
if cc.get_id() == 'msvc':
raise unittest.SkipTest('Test only applies to non-MSVC compilers')
self.init(testdir, extra_args=['-Db_coverage=true'])
self.build()
self.run_tests()
self.run_target('coverage-text')
def test_coverage_xml(self):
gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
if not gcovr_exe:
raise unittest.SkipTest('gcovr not found, or too old')
testdir = os.path.join(self.common_test_dir, '1 trivial')
env = get_fake_env(testdir, self.builddir, self.prefix)
cc = env.detect_c_compiler(MachineChoice.HOST)
if cc.get_id() == 'clang':
if not mesonbuild.environment.detect_llvm_cov():
raise unittest.SkipTest('llvm-cov not found')
if cc.get_id() == 'msvc':
raise unittest.SkipTest('Test only applies to non-MSVC compilers')
self.init(testdir, extra_args=['-Db_coverage=true'])
self.build()
self.run_tests()
self.run_target('coverage-xml')
class FailureTests(BasePlatformTests):
'''
Tests that test failure conditions. Build files here should be dynamically
@ -6301,21 +6369,6 @@ class LinuxlikeTests(BasePlatformTests):
for i in compdb:
self.assertIn("-fsanitize=address", i["command"])
def test_coverage(self):
gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
if not gcovr_exe:
raise unittest.SkipTest('gcovr not found')
if not shutil.which('genhtml') and not gcovr_new_rootdir:
raise unittest.SkipTest('genhtml not found and gcovr is too old')
if 'clang' in os.environ.get('CC', ''):
# We need to use llvm-cov instead of gcovr with clang
raise unittest.SkipTest('Coverage does not work with clang right now, help wanted!')
testdir = os.path.join(self.common_test_dir, '1 trivial')
self.init(testdir, extra_args=['-Db_coverage=true'])
self.build()
self.run_tests()
self.run_target('coverage-html')
def test_cross_find_program(self):
testdir = os.path.join(self.unit_test_dir, '11 cross prog')
crossfile = tempfile.NamedTemporaryFile(mode='w')

Loading…
Cancel
Save