aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCary Converse <converse@battelle.org>2020-03-29 11:28:02 -0400
committerCary Converse <cconverse711@gmail.com>2020-06-17 23:02:50 -0400
commita198e5d191820fda9142d248cd5d134e5f2a5b93 (patch)
tree3bd671071afd17576389af351e26d33d9544406a
parent804cefc94cd334fccccdf3015eb0b3f589f87515 (diff)
downloadmeson-a198e5d191820fda9142d248cd5d134e5f2a5b93.zip
meson-a198e5d191820fda9142d248cd5d134e5f2a5b93.tar.gz
meson-a198e5d191820fda9142d248cd5d134e5f2a5b93.tar.bz2
coverage: llvm-cov support
-rw-r--r--azure-pipelines.yml9
-rwxr-xr-xci/ciimage/arch/install.sh2
-rwxr-xr-xci/ciimage/bionic/install.sh3
-rwxr-xr-xci/ciimage/eoan/install.sh3
-rwxr-xr-xci/ciimage/fedora/install.sh2
-rwxr-xr-xci/ciimage/opensuse/install.sh4
-rw-r--r--docs/markdown/howtox.md3
-rw-r--r--docs/markdown/snippets/clang_coverage.md4
-rw-r--r--mesonbuild/backend/ninjabackend.py10
-rw-r--r--mesonbuild/environment.py11
-rw-r--r--mesonbuild/scripts/coverage.py46
-rwxr-xr-xrun_unittests.py83
12 files changed, 140 insertions, 40 deletions
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 0408342..3822110 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -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=
diff --git a/ci/ciimage/arch/install.sh b/ci/ciimage/arch/install.sh
index 6cbbb27..fb27c26 100755
--- a/ci/ciimage/arch/install.sh
+++ b/ci/ciimage/arch/install.sh
@@ -17,7 +17,7 @@ pkgs=(
)
aur_pkgs=(scalapack)
-pip_pkgs=(hotdoc)
+pip_pkgs=(hotdoc gcovr)
cleanup_pkgs=(go)
AUR_USER=docker
diff --git a/ci/ciimage/bionic/install.sh b/ci/ciimage/bionic/install.sh
index 47deb2a..0bfcdfb 100755
--- a/ci/ciimage/bionic/install.sh
+++ b/ci/ciimage/bionic/install.sh
@@ -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
diff --git a/ci/ciimage/eoan/install.sh b/ci/ciimage/eoan/install.sh
index 7d7a1fd..36dec72 100755
--- a/ci/ciimage/eoan/install.sh
+++ b/ci/ciimage/eoan/install.sh
@@ -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
diff --git a/ci/ciimage/fedora/install.sh b/ci/ciimage/fedora/install.sh
index f61d97e..3beb11c 100755
--- a/ci/ciimage/fedora/install.sh
+++ b/ci/ciimage/fedora/install.sh
@@ -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
diff --git a/ci/ciimage/opensuse/install.sh b/ci/ciimage/opensuse/install.sh
index b9e440d..4c8e770 100755
--- a/ci/ciimage/opensuse/install.sh
+++ b/ci/ciimage/opensuse/install.sh
@@ -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
diff --git a/docs/markdown/howtox.md b/docs/markdown/howtox.md
index 84546b7..0832060 100644
--- a/docs/markdown/howtox.md
+++ b/docs/markdown/howtox.md
@@ -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
diff --git a/docs/markdown/snippets/clang_coverage.md b/docs/markdown/snippets/clang_coverage.md
new file mode 100644
index 0000000..733a3d9
--- /dev/null
+++ b/docs/markdown/snippets/clang_coverage.md
@@ -0,0 +1,4 @@
+## Clang coverage support
+
+llvm-cov is now used to generate coverage information when clang is used as
+the compiler. \ No newline at end of file
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index 8dbb57a..252f646 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -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')
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py
index c02376e..afc2a63 100644
--- a/mesonbuild/environment.py
+++ b/mesonbuild/environment.py
@@ -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)
diff --git a/mesonbuild/scripts/coverage.py b/mesonbuild/scripts/coverage.py
index 4bd41fe..7231972 100644
--- a/mesonbuild/scripts/coverage.py
+++ b/mesonbuild/scripts/coverage.py
@@ -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:]))
diff --git a/run_unittests.py b/run_unittests.py
index 827e3c8..0a61935 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -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')