You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
275 lines
7.1 KiB
275 lines
7.1 KiB
// Copyright 2020 The SwiftShader Authors. All Rights Reserved.
|
|
//
|
|
// 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"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"../cause"
|
|
"../llvm"
|
|
)
|
|
|
|
// 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 enviroment settings for performing coverage processing.
|
|
type Env struct {
|
|
LLVM llvm.Toolchain
|
|
RootDir string // path to SwiftShader git root directory
|
|
ExePath string // path to the executable binary
|
|
TurboCov string // path to turbo-cov (optional)
|
|
}
|
|
|
|
// AppendRuntimeEnv returns the environment variables env with the
|
|
// LLVM_PROFILE_FILE environment variable appended.
|
|
func AppendRuntimeEnv(env []string, coverageFile string) []string {
|
|
return append(env, "LLVM_PROFILE_FILE="+coverageFile)
|
|
}
|
|
|
|
// 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{
|
|
"src/Common": true,
|
|
"src/Main": true,
|
|
"src/OpenGL": true,
|
|
"src/Renderer": true,
|
|
"src/Shader": true,
|
|
"src/System": true,
|
|
}
|
|
|
|
// 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(e.RootDir, "src"), func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rel, err := filepath.Rel(e.RootDir, 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"
|
|
|
|
if err := exec.Command(e.LLVM.Profdata(), "merge", "-sparse", profrawPath, "-output", profdata).Run(); err != nil {
|
|
return nil, cause.Wrap(err, "llvm-profdata errored")
|
|
}
|
|
defer os.Remove(profdata)
|
|
|
|
if e.TurboCov == "" {
|
|
args := []string{
|
|
"export",
|
|
e.ExePath,
|
|
"-instr-profile=" + profdata,
|
|
"-format=text",
|
|
}
|
|
if e.LLVM.Version.GreaterEqual(llvm.Version{Major: 9}) {
|
|
// LLVM 9 has new flags that omit stuff we don't care about.
|
|
args = append(args,
|
|
"-skip-expansions",
|
|
"-skip-functions",
|
|
)
|
|
}
|
|
|
|
data, err := exec.Command(e.LLVM.Cov(), args...).Output()
|
|
if err != nil {
|
|
return nil, cause.Wrap(err, "llvm-cov errored: %v", string(err.(*exec.ExitError).Stderr))
|
|
}
|
|
cov, err := e.parseCov(data)
|
|
if err != nil {
|
|
return nil, cause.Wrap(err, "Couldn't parse coverage json data")
|
|
}
|
|
return cov, nil
|
|
}
|
|
|
|
data, err := exec.Command(e.TurboCov, e.ExePath, profdata).Output()
|
|
if err != nil {
|
|
return nil, cause.Wrap(err, "turbo-cov errored: %v", string(err.(*exec.ExitError).Stderr))
|
|
}
|
|
cov, err := e.parseTurboCov(data)
|
|
if err != nil {
|
|
return nil, cause.Wrap(err, "Couldn't process turbo-cov output")
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
c := &Coverage{Files: make([]File, 0, len(root.Data[0].Files))}
|
|
for _, f := range root.Data[0].Files {
|
|
relpath, err := filepath.Rel(e.RootDir, 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
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
numFiles := u32()
|
|
c := &Coverage{Files: make([]File, 0, numFiles)}
|
|
for i := 0; i < int(numFiles); i++ {
|
|
path := str()
|
|
relpath, err := filepath.Rel(e.RootDir, 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 is a tree node path formed from a list of strings
|
|
type Path []string
|