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") if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
target_compile_options(${TARGET} PRIVATE "--coverage") target_compile_options(${TARGET} PRIVATE "--coverage")
target_link_options(${TARGET} PRIVATE "gcov") 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_compile_options(${TARGET} PRIVATE "-fprofile-instr-generate" "-fcoverage-mapping")
target_link_options(${TARGET} PRIVATE "-fprofile-instr-generate" "-fcoverage-mapping") target_link_options(${TARGET} PRIVATE "-fprofile-instr-generate" "-fcoverage-mapping")
else() else()
@ -451,10 +451,13 @@ if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND (CMAKE_CXX_SIMULATE_ID STREQUAL
set(COMPILER_IS_CLANG_CL TRUE) set(COMPILER_IS_CLANG_CL TRUE)
endif() endif()
if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR if((CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") OR
(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") OR
((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND ((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND
(NOT COMPILER_IS_CLANG_CL))) (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) set(COMPILER_IS_LIKE_GNU TRUE)
endif() endif()
@ -555,8 +558,7 @@ function(tint_default_compile_options TARGET)
${COMMON_GNU_OPTIONS} ${COMMON_GNU_OPTIONS}
) )
if (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") OR if (COMPILER_IS_CLANG)
("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang"))
target_compile_options(${TARGET} PRIVATE target_compile_options(${TARGET} PRIVATE
${COMMON_CLANG_OPTIONS} ${COMMON_CLANG_OPTIONS}
) )
@ -655,9 +657,11 @@ add_custom_target(tint-format
COMMENT "Running formatter" COMMENT "Running formatter"
VERBATIM) VERBATIM)
if (DAWN_EMIT_COVERAGE)
add_subdirectory(tools/src/cmd/turbo-cov)
if (DAWN_EMIT_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") # The tint-generate-coverage target generates a lcov.info file at the project
# Generates a lcov.info file at the project root. # root, holding the code coverage for all the tint_unitests.
# This can be used by tools such as VSCode's Coverage Gutters extension to # This can be used by tools such as VSCode's Coverage Gutters extension to
# visualize code coverage in the editor. # visualize code coverage in the editor.
get_filename_component(CLANG_BIN_DIR ${CMAKE_C_COMPILER} DIRECTORY) 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 # Generate the NapiSymbols.lib from the NapiSymbols.def file
set(NAPI_SYMBOLS_LIB "${DAWN_NODE_GEN_DIR}/NapiSymbols.lib") set(NAPI_SYMBOLS_LIB "${DAWN_NODE_GEN_DIR}/NapiSymbols.lib")
# Resolve path to lib.exe # Resolve path to lib.exe
get_filename_component(VS_BIN_DIR "${CMAKE_LINKER}" DIRECTORY) get_filename_component(LINKER_BIN_DIR "${CMAKE_LINKER}" DIRECTORY)
set(LIB_EXE "${VS_BIN_DIR}/lib.exe") 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( add_custom_command(
COMMAND "${LIB_EXE}" COMMAND "${LIB_EXE}"
"/DEF:${NAPI_SYMBOLS_DEF}" "/DEF:${NAPI_SYMBOLS_DEF}"

View File

@ -62,7 +62,7 @@ Napi::Value CreateGPU(const Napi::CallbackInfo& info) {
#ifdef DAWN_EMIT_COVERAGE #ifdef DAWN_EMIT_COVERAGE
struct Coverage { struct Coverage {
Coverage() : output_path_{GetOutputPath()} { 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_); } ~Coverage() { std::filesystem::remove(output_path_); }
@ -75,7 +75,7 @@ struct Coverage {
static Napi::Value End(const Napi::CallbackInfo& info) { static Napi::Value End(const Napi::CallbackInfo& info) {
__llvm_profile_write_file(); __llvm_profile_write_file();
auto* coverage = static_cast<Coverage*>(info.Data()); 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: private:

View File

@ -259,17 +259,36 @@ func run() error {
} }
if genCoverage { if genCoverage {
llvmCov, err := exec.LookPath("llvm-cov") dawnOutDir := filepath.Dir(dawnNode)
profdata, err := exec.LookPath("llvm-profdata")
if err != nil { 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) { if !fileutils.IsExe(turboCov) {
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{ r.covEnv = &cov.Env{
LLVMBin: filepath.Dir(llvmCov), Profdata: profdata,
Binary: dawnNode, Binary: dawnNode,
Cov: llvmCov,
TurboCov: turboCov, TurboCov: turboCov,
} }
} }

View File

@ -24,6 +24,11 @@
#include "llvm/ProfileData/Coverage/CoverageMapping.h" #include "llvm/ProfileData/Coverage/CoverageMapping.h"
#include "llvm/ProfileData/InstrProfReader.h" #include "llvm/ProfileData/InstrProfReader.h"
#if defined(_MSC_VER)
#include <fcntl.h> // _O_BINARY
#include <io.h> // _setmode
#endif
namespace { namespace {
template <typename T> template <typename T>
@ -40,6 +45,13 @@ void emit(const llvm::StringRef& str) {
} // namespace } // namespace
int main(int argc, const char** argv) { 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) { if (argc < 3) {
fprintf(stderr, "turbo-cov <exe> <profdata>\n"); fprintf(stderr, "turbo-cov <exe> <profdata>\n");
return 1; return 1;

View File

@ -42,9 +42,10 @@ type Coverage struct {
// Env holds the environment settings for performing coverage processing. // Env holds the environment settings for performing coverage processing.
type Env struct { 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 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 // 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 // Import uses the llvm-profdata and llvm-cov tools to import the coverage
// information from a .profraw file. // information from a .profraw file.
func (e Env) Import(profrawPath string) (*Coverage, error) { 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" profdata := profrawPath + ".profdata"
defer os.Remove(profdata)
if err := exec.Command( if e.Profdata == "" {
llvmProfdataExe, 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", "merge",
"-sparse", "-sparse",
profrawPath, profrawPath,
"-output", "-output",
profdata).Run(); err != nil { profdata).CombinedOutput(); err != nil {
return nil, fmt.Errorf("llvm-profdata errored: %w", err) return nil, fmt.Errorf("llvm-profdata errored: %w\n%v", err, string(out))
} }
defer os.Remove(profdata)
if e.TurboCov == "" { if e.TurboCov == "" {
data, err := exec.Command( data, err := exec.Command(
llvmCovExe, e.Cov,
"export", "export",
e.Binary, e.Binary,
"-instr-profile="+profdata, "-instr-profile="+profdata,
"-format=text", "-format=text",
"-skip-expansions", "-skip-expansions",
"-skip-functions").Output() "-skip-functions").CombinedOutput()
if err != nil { 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) cov, err := e.parseCov(data)
if err != nil { if err != nil {
@ -127,9 +132,9 @@ func (e Env) Import(profrawPath string) (*Coverage, error) {
return cov, nil 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 { 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) cov, err := e.parseTurboCov(data)
if err != nil { if err != nil {

View File

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