code-coverage / turbocov fixes

Common:
* The turbocov build target (somehow) never got hooked up to the root CMakeLists.txt file. This is now fixed.

macOS:
* Emit coverage for 'AppleClang' compiler.
* Have run-cts find the tucked-away `llvm-profdata` executable.

Windows:
* Various fixes for building with clang
* Fix turbocov stdout CRLF corruption
* Fix bad JSON with backslashes

Change-Id: I481cceafe2e72b544e13168172fc1456e5df2005
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/117880
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
Ben Clayton 2023-01-26 15:57:27 +00:00 committed by Dawn LUCI CQ
parent b4049dcf40
commit 78e4530f59
7 changed files with 78 additions and 31 deletions

View File

@ -347,7 +347,7 @@ function(common_compile_options TARGET)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
target_compile_options(${TARGET} PRIVATE "--coverage")
target_link_options(${TARGET} PRIVATE "gcov")
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
elseif(COMPILER_IS_CLANG OR COMPILER_IS_CLANG_CL)
target_compile_options(${TARGET} PRIVATE "-fprofile-instr-generate" "-fcoverage-mapping")
target_link_options(${TARGET} PRIVATE "-fprofile-instr-generate" "-fcoverage-mapping")
else()
@ -451,10 +451,13 @@ if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND (CMAKE_CXX_SIMULATE_ID STREQUAL
set(COMPILER_IS_CLANG_CL TRUE)
endif()
if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR
(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") OR
if((CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") OR
((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND
(NOT COMPILER_IS_CLANG_CL)))
set(COMPILER_IS_CLANG TRUE)
endif()
if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR COMPILER_IS_CLANG)
set(COMPILER_IS_LIKE_GNU TRUE)
endif()
@ -555,8 +558,7 @@ function(tint_default_compile_options TARGET)
${COMMON_GNU_OPTIONS}
)
if (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") OR
("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang"))
if (COMPILER_IS_CLANG)
target_compile_options(${TARGET} PRIVATE
${COMMON_CLANG_OPTIONS}
)
@ -655,9 +657,11 @@ add_custom_target(tint-format
COMMENT "Running formatter"
VERBATIM)
if (DAWN_EMIT_COVERAGE)
add_subdirectory(tools/src/cmd/turbo-cov)
if (DAWN_EMIT_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# Generates a lcov.info file at the project root.
# The tint-generate-coverage target generates a lcov.info file at the project
# root, holding the code coverage for all the tint_unitests.
# This can be used by tools such as VSCode's Coverage Gutters extension to
# visualize code coverage in the editor.
get_filename_component(CLANG_BIN_DIR ${CMAKE_C_COMPILER} DIRECTORY)

View File

@ -102,8 +102,14 @@ if (WIN32)
# Generate the NapiSymbols.lib from the NapiSymbols.def file
set(NAPI_SYMBOLS_LIB "${DAWN_NODE_GEN_DIR}/NapiSymbols.lib")
# Resolve path to lib.exe
get_filename_component(VS_BIN_DIR "${CMAKE_LINKER}" DIRECTORY)
set(LIB_EXE "${VS_BIN_DIR}/lib.exe")
get_filename_component(LINKER_BIN_DIR "${CMAKE_LINKER}" DIRECTORY)
if (EXISTS "${LINKER_BIN_DIR}/lib.exe")
set(LIB_EXE "${LINKER_BIN_DIR}/lib.exe")
elseif (EXISTS "${LINKER_BIN_DIR}/lld-link.exe")
set(LIB_EXE "${LINKER_BIN_DIR}/lld-link.exe")
else()
message(FATAL_ERROR "unable to find lib.exe or lld-link.exe")
endif()
add_custom_command(
COMMAND "${LIB_EXE}"
"/DEF:${NAPI_SYMBOLS_DEF}"

View File

@ -62,7 +62,7 @@ Napi::Value CreateGPU(const Napi::CallbackInfo& info) {
#ifdef DAWN_EMIT_COVERAGE
struct Coverage {
Coverage() : output_path_{GetOutputPath()} {
__llvm_profile_set_filename(output_path_.c_str());
__llvm_profile_set_filename(output_path_.string().c_str());
}
~Coverage() { std::filesystem::remove(output_path_); }
@ -75,7 +75,7 @@ struct Coverage {
static Napi::Value End(const Napi::CallbackInfo& info) {
__llvm_profile_write_file();
auto* coverage = static_cast<Coverage*>(info.Data());
return Napi::String::New(info.Env(), coverage->output_path_.c_str());
return Napi::String::New(info.Env(), coverage->output_path_.string().c_str());
}
private:

View File

@ -259,17 +259,36 @@ func run() error {
}
if genCoverage {
llvmCov, err := exec.LookPath("llvm-cov")
dawnOutDir := filepath.Dir(dawnNode)
profdata, err := exec.LookPath("llvm-profdata")
if err != nil {
return fmt.Errorf("failed to find LLVM, required for --coverage")
profdata = ""
if runtime.GOOS == "darwin" {
profdata = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/llvm-profdata"
if !fileutils.IsExe(profdata) {
profdata = ""
}
}
}
turboCov := filepath.Join(filepath.Dir(dawnNode), "turbo-cov"+fileutils.ExeExt)
if profdata == "" {
return fmt.Errorf("failed to find llvm-profdata, required for --coverage")
}
llvmCov := ""
turboCov := filepath.Join(dawnOutDir, "turbo-cov"+fileutils.ExeExt)
if !fileutils.IsExe(turboCov) {
turboCov = ""
if path, err := exec.LookPath("llvm-cov"); err == nil {
llvmCov = path
} else {
return fmt.Errorf("failed to find turbo-cov or llvm-cov")
}
}
r.covEnv = &cov.Env{
LLVMBin: filepath.Dir(llvmCov),
Profdata: profdata,
Binary: dawnNode,
Cov: llvmCov,
TurboCov: turboCov,
}
}

View File

@ -24,6 +24,11 @@
#include "llvm/ProfileData/Coverage/CoverageMapping.h"
#include "llvm/ProfileData/InstrProfReader.h"
#if defined(_MSC_VER)
#include <fcntl.h> // _O_BINARY
#include <io.h> // _setmode
#endif
namespace {
template <typename T>
@ -40,6 +45,13 @@ void emit(const llvm::StringRef& str) {
} // namespace
int main(int argc, const char** argv) {
#if defined(_MSC_VER)
// Change stdin & stdout from text mode to binary mode.
// This ensures sequences of \r\n are not changed to \n.
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
#endif
if (argc < 3) {
fprintf(stderr, "turbo-cov <exe> <profdata>\n");
return 1;

View File

@ -42,9 +42,10 @@ type Coverage struct {
// Env holds the environment settings for performing coverage processing.
type Env struct {
LLVMBin string // path to the LLVM bin directory
Profdata string // path to the llvm-profdata tool
Binary string // path to the executable binary
TurboCov string // path to turbo-cov (optional)
Cov string // path to the llvm-cov tool (one of Cov or TurboCov must be supplied)
TurboCov string // path to the turbo-cov tool (one of Cov or TurboCov must be supplied)
}
// RuntimeEnv returns the environment variable key=value pair for setting
@ -92,33 +93,37 @@ func (e Env) AllSourceFiles() *Coverage {
// Import uses the llvm-profdata and llvm-cov tools to import the coverage
// information from a .profraw file.
func (e Env) Import(profrawPath string) (*Coverage, error) {
llvmProfdataExe := filepath.Join(e.LLVMBin, "llvm-profdata"+fileutils.ExeExt)
llvmCovExe := filepath.Join(e.LLVMBin, "llvm-cov"+fileutils.ExeExt)
profdata := profrawPath + ".profdata"
defer os.Remove(profdata)
if err := exec.Command(
llvmProfdataExe,
if e.Profdata == "" {
return nil, fmt.Errorf("cov.Env.Profdata must be specified")
}
if e.TurboCov == "" && e.Cov == "" {
return nil, fmt.Errorf("One of cov.Env.TurboCov or cov.Env.Cov must be specified")
}
if out, err := exec.Command(
e.Profdata,
"merge",
"-sparse",
profrawPath,
"-output",
profdata).Run(); err != nil {
return nil, fmt.Errorf("llvm-profdata errored: %w", err)
profdata).CombinedOutput(); err != nil {
return nil, fmt.Errorf("llvm-profdata errored: %w\n%v", err, string(out))
}
defer os.Remove(profdata)
if e.TurboCov == "" {
data, err := exec.Command(
llvmCovExe,
e.Cov,
"export",
e.Binary,
"-instr-profile="+profdata,
"-format=text",
"-skip-expansions",
"-skip-functions").Output()
"-skip-functions").CombinedOutput()
if err != nil {
return nil, fmt.Errorf("llvm-cov errored: %v\n%v", string(err.(*exec.ExitError).Stderr), err)
return nil, fmt.Errorf("llvm-cov errored: %v\n%v", string(data), err)
}
cov, err := e.parseCov(data)
if err != nil {
@ -127,9 +132,9 @@ func (e Env) Import(profrawPath string) (*Coverage, error) {
return cov, nil
}
data, err := exec.Command(e.TurboCov, e.Binary, profdata).Output()
data, err := exec.Command(e.TurboCov, e.Binary, profdata).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("turbo-cov errored: %v\n%v", string(err.(*exec.ExitError).Stderr), err)
return nil, fmt.Errorf("turbo-cov errored: %v\n%v", string(data), err)
}
cov, err := e.parseTurboCov(data)
if err != nil {

View File

@ -19,6 +19,7 @@ import (
"compress/zlib"
"fmt"
"io"
"path/filepath"
"runtime/debug"
"sort"
"strconv"
@ -156,7 +157,7 @@ func (t *Tree) writeFilesJSON(spansByID map[SpanID]Span, sb *strings.Builder) {
sb.WriteString(`,`)
}
sb.WriteString(`"`)
sb.WriteString(path)
sb.WriteString(filepath.ToSlash(path))
sb.WriteString(`":`)
sb.WriteString(`{`)
if totalLines := file.allSpans.NumLines(); totalLines > 0 {