mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-10-17 07:25:13 +00:00
Use the new `cmdline.ts --coverage` flag in https://github.com/gpuweb/cts/pull/2206 to write out the per-test coverage to a unique file. Change-Id: Iea273217eede2fa615b78cd6f69f036976dcfd35 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/117883 Kokoro: Ben Clayton <bclayton@google.com> Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Ben Clayton <bclayton@chromium.org>
281 lines
7.3 KiB
Go
281 lines
7.3 KiB
Go
// Copyright 2022 The Dawn Authors
|
|
//
|
|
// 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.
|
|
|
|
package cov
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"dawn.googlesource.com/dawn/tools/src/fileutils"
|
|
)
|
|
|
|
// File describes the coverage spans in a single source file.
|
|
type File struct {
|
|
Path string
|
|
Covered SpanList // Spans with coverage
|
|
Uncovered SpanList // Compiled spans without coverage
|
|
}
|
|
|
|
// Coverage describes the coverage spans for all the source files for a single
|
|
// process invocation.
|
|
type Coverage struct {
|
|
Files []File
|
|
}
|
|
|
|
// Env holds the environment settings for performing coverage processing.
|
|
type Env struct {
|
|
Profdata string // path to the llvm-profdata tool
|
|
Binary string // path to the executable binary
|
|
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)
|
|
}
|
|
|
|
// AllSourceFiles returns a *Coverage containing all the source files without
|
|
// coverage data. This populates the coverage view with files even if they
|
|
// didn't get compiled.
|
|
func (e Env) AllSourceFiles() *Coverage {
|
|
var ignorePaths = map[string]bool{
|
|
//
|
|
}
|
|
|
|
projectRoot := fileutils.DawnRoot()
|
|
|
|
// Gather all the source files to include them even if there is no coverage
|
|
// information produced for these files. This highlights files that aren't
|
|
// even compiled.
|
|
cov := Coverage{}
|
|
allFiles := map[string]struct{}{}
|
|
filepath.Walk(filepath.Join(projectRoot, "src"), func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rel, err := filepath.Rel(projectRoot, path)
|
|
if err != nil || ignorePaths[rel] {
|
|
return filepath.SkipDir
|
|
}
|
|
if !info.IsDir() {
|
|
switch filepath.Ext(path) {
|
|
case ".h", ".c", ".cc", ".cpp", ".hpp":
|
|
if _, seen := allFiles[rel]; !seen {
|
|
cov.Files = append(cov.Files, File{Path: rel})
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
return &cov
|
|
}
|
|
|
|
// 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) {
|
|
profdata := profrawPath + ".profdata"
|
|
defer os.Remove(profdata)
|
|
|
|
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).CombinedOutput(); err != nil {
|
|
return nil, fmt.Errorf("llvm-profdata errored: %w\n%v", err, string(out))
|
|
}
|
|
|
|
if e.TurboCov == "" {
|
|
data, err := exec.Command(
|
|
e.Cov,
|
|
"export",
|
|
e.Binary,
|
|
"-instr-profile="+profdata,
|
|
"-format=text",
|
|
"-skip-expansions",
|
|
"-skip-functions").CombinedOutput()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("llvm-cov errored: %v\n%v", string(data), err)
|
|
}
|
|
cov, err := e.parseCov(data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse coverage json data: %w", err)
|
|
}
|
|
return cov, nil
|
|
}
|
|
|
|
data, err := exec.Command(e.TurboCov, e.Binary, profdata).CombinedOutput()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("turbo-cov errored: %v\n%v", string(data), err)
|
|
}
|
|
cov, err := e.parseTurboCov(data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to process turbo-cov output: %w", err)
|
|
}
|
|
|
|
return cov, nil
|
|
}
|
|
|
|
func appendSpan(spans []Span, span Span) []Span {
|
|
if c := len(spans); c > 0 && spans[c-1].End == span.Start {
|
|
spans[c-1].End = span.End
|
|
} else {
|
|
spans = append(spans, span)
|
|
}
|
|
return spans
|
|
}
|
|
|
|
// https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
|
|
// https://stackoverflow.com/a/56792192
|
|
func (e Env) parseCov(raw []byte) (*Coverage, error) {
|
|
// line int, col int, count int64, hasCount bool, isRegionEntry bool
|
|
type segment []interface{}
|
|
|
|
type file struct {
|
|
// expansions ignored
|
|
Name string `json:"filename"`
|
|
Segments []segment `json:"segments"`
|
|
// summary ignored
|
|
}
|
|
|
|
type data struct {
|
|
Files []file `json:"files"`
|
|
}
|
|
|
|
root := struct {
|
|
Data []data `json:"data"`
|
|
}{}
|
|
err := json.NewDecoder(bytes.NewReader(raw)).Decode(&root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
projectRoot := fileutils.DawnRoot()
|
|
|
|
c := &Coverage{Files: make([]File, 0, len(root.Data[0].Files))}
|
|
for _, f := range root.Data[0].Files {
|
|
relpath, err := filepath.Rel(projectRoot, f.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if strings.HasPrefix(relpath, "..") {
|
|
continue
|
|
}
|
|
file := File{Path: relpath}
|
|
for sIdx := 0; sIdx+1 < len(f.Segments); sIdx++ {
|
|
start := Location{(int)(f.Segments[sIdx][0].(float64)), (int)(f.Segments[sIdx][1].(float64))}
|
|
end := Location{(int)(f.Segments[sIdx+1][0].(float64)), (int)(f.Segments[sIdx+1][1].(float64))}
|
|
if covered := f.Segments[sIdx][2].(float64) != 0; covered {
|
|
file.Covered = appendSpan(file.Covered, Span{start, end})
|
|
} else {
|
|
file.Uncovered = appendSpan(file.Uncovered, Span{start, end})
|
|
}
|
|
}
|
|
if len(file.Covered) > 0 {
|
|
c.Files = append(c.Files, file)
|
|
}
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
// parseTurboCov parses coverage information from a `turbo-cov` file.
|
|
// See tools/src/cmd/turbo-cov/README.md for more information
|
|
func (e Env) parseTurboCov(data []byte) (*Coverage, error) {
|
|
u32 := func() uint32 {
|
|
out := binary.LittleEndian.Uint32(data)
|
|
data = data[4:]
|
|
return out
|
|
}
|
|
u8 := func() uint8 {
|
|
out := data[0]
|
|
data = data[1:]
|
|
return out
|
|
}
|
|
str := func() string {
|
|
len := u32()
|
|
out := data[:len]
|
|
data = data[len:]
|
|
return string(out)
|
|
}
|
|
|
|
projectRoot := fileutils.DawnRoot()
|
|
|
|
numFiles := u32()
|
|
c := &Coverage{Files: make([]File, 0, numFiles)}
|
|
for i := 0; i < int(numFiles); i++ {
|
|
path := str()
|
|
relpath, err := filepath.Rel(projectRoot, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if strings.HasPrefix(relpath, "..") {
|
|
continue
|
|
}
|
|
|
|
file := File{Path: relpath}
|
|
|
|
type segment struct {
|
|
location Location
|
|
count int
|
|
covered bool
|
|
}
|
|
|
|
numSegements := u32()
|
|
segments := make([]segment, numSegements)
|
|
for j := range segments {
|
|
segment := &segments[j]
|
|
segment.location.Line = int(u32())
|
|
segment.location.Column = int(u32())
|
|
segment.count = int(u32())
|
|
segment.covered = u8() != 0
|
|
}
|
|
|
|
for sIdx := 0; sIdx+1 < len(segments); sIdx++ {
|
|
start := segments[sIdx].location
|
|
end := segments[sIdx+1].location
|
|
if segments[sIdx].covered {
|
|
if segments[sIdx].count > 0 {
|
|
file.Covered = appendSpan(file.Covered, Span{start, end})
|
|
} else {
|
|
file.Uncovered = appendSpan(file.Uncovered, Span{start, end})
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(file.Covered) > 0 {
|
|
c.Files = append(c.Files, file)
|
|
}
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
// Path uniquely identifies a test that was run to produce coverage.
|
|
// Paths are split into a hierarchical sequence of strings, where the 0'th
|
|
// string represents the root of the hierarchy and the last string is typically
|
|
// the leaf name of the test.
|
|
type Path []string
|