aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorJouni Hogander <jouni.hogander@unikie.com>2020-08-14 15:08:54 +0300
committerDmitry Vyukov <dvyukov@google.com>2020-09-26 17:47:37 +0200
commit6f0ea384b170a6e3db2647ee8759a9cf6c85b1da (patch)
treec4942e275a526ecd26e83f792a98dfab7c7f49d8 /pkg
parent0b9318b447b1d74804947f2c725a96017686cac6 (diff)
pkg/cover: implement function coverage calculation
Diffstat (limited to 'pkg')
-rw-r--r--pkg/cover/report.go81
-rw-r--r--pkg/cover/report_test.go40
2 files changed, 109 insertions, 12 deletions
diff --git a/pkg/cover/report.go b/pkg/cover/report.go
index a302b7013..666babb02 100644
--- a/pkg/cover/report.go
+++ b/pkg/cover/report.go
@@ -6,6 +6,7 @@ package cover
import (
"bufio"
"bytes"
+ "encoding/csv"
"fmt"
"html"
"html/template"
@@ -41,6 +42,13 @@ type symbol struct {
end uint64
}
+var CSVHeader = []string{
+ "Filename",
+ "Function",
+ "Covered PCs",
+ "Total PCs",
+}
+
func MakeReportGenerator(target *targets.Target, kernelObject, srcDir, buildDir string) (*ReportGenerator, error) {
rg := &ReportGenerator{
target: target,
@@ -70,12 +78,18 @@ func MakeReportGenerator(target *targets.Target, kernelObject, srcDir, buildDir
type file struct {
lines map[int]line
+ functions map[string]*function
totalPCs map[uint64]bool
coverPCs map[uint64]bool
totalInline map[int]bool
coverInline map[int]bool
}
+type function struct {
+ totalPCs map[uint64]bool
+ coverPCs map[uint64]bool
+}
+
type line struct {
count map[int]bool
prog int
@@ -83,7 +97,23 @@ type line struct {
symbolCovered bool
}
-func (rg *ReportGenerator) Do(w io.Writer, progs []Prog) error {
+func (rg *ReportGenerator) DoHTML(buf io.Writer, progs []Prog) error {
+ files, err := rg.prepareFileMap(progs)
+ if err != nil {
+ return err
+ }
+ return rg.generateHTML(buf, progs, files)
+}
+
+func (rg *ReportGenerator) DoCSV(buf io.Writer, progs []Prog) error {
+ files, err := rg.prepareFileMap(progs)
+ if err != nil {
+ return err
+ }
+ return rg.generateCSV(buf, progs, files)
+}
+
+func (rg *ReportGenerator) prepareFileMap(progs []Prog) (map[string]*file, error) {
coveredPCs := make(map[uint64]bool)
allPCs := make(map[uint64]bool)
symbols := make(map[uint64]bool)
@@ -113,10 +143,10 @@ func (rg *ReportGenerator) Do(w io.Writer, progs []Prog) error {
}
}
if len(allPCs) == 0 {
- return fmt.Errorf("no coverage collected so far")
+ return nil, fmt.Errorf("no coverage collected so far")
}
if len(coveredPCs) == 0 {
- return fmt.Errorf("coverage (%v) doesn't match coverage callbacks", len(allPCs))
+ return nil, fmt.Errorf("coverage (%v) doesn't match coverage callbacks", len(allPCs))
}
for pc, frames := range rg.pcs {
covered := coveredPCs[pc]
@@ -133,6 +163,8 @@ func (rg *ReportGenerator) Do(w io.Writer, progs []Prog) error {
f.coverPCs[pc] = true
}
}
+ function := getFunction(f.functions, frame.Func)
+ function.totalPCs[pc] = true
if !covered {
ln := f.lines[frame.Line]
if !frame.Inline || len(ln.count) == 0 {
@@ -140,10 +172,12 @@ func (rg *ReportGenerator) Do(w io.Writer, progs []Prog) error {
ln.symbolCovered = symbols[rg.findSymbol(pc)]
f.lines[frame.Line] = ln
}
+ } else {
+ function.coverPCs[pc] = true
}
}
}
- return rg.generate(w, progs, files)
+ return files, nil
}
func getFile(files map[string]*file, name string) *file {
@@ -151,6 +185,7 @@ func getFile(files map[string]*file, name string) *file {
if f == nil {
f = &file{
lines: make(map[int]line),
+ functions: make(map[string]*function),
totalPCs: make(map[uint64]bool),
coverPCs: make(map[uint64]bool),
totalInline: make(map[int]bool),
@@ -161,7 +196,43 @@ func getFile(files map[string]*file, name string) *file {
return f
}
-func (rg *ReportGenerator) generate(w io.Writer, progs []Prog, files map[string]*file) error {
+func getFunction(functions map[string]*function, name string) *function {
+ f := functions[name]
+ if f == nil {
+ f = &function{
+ totalPCs: make(map[uint64]bool),
+ coverPCs: make(map[uint64]bool),
+ }
+ functions[name] = f
+ }
+ return f
+}
+
+func (rg *ReportGenerator) generateCSV(w io.Writer, progs []Prog, files map[string]*file) error {
+ data := [][]string{
+ CSVHeader,
+ }
+
+ for fname, file := range files {
+ for funcName, function := range file.functions {
+ line := []string{filepath.Clean(fname), funcName,
+ strconv.Itoa(len(function.coverPCs)),
+ strconv.Itoa(len(function.totalPCs))}
+ data = append(data, line)
+ }
+ }
+ writer := csv.NewWriter(w)
+ defer writer.Flush()
+
+ err := writer.WriteAll(data)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (rg *ReportGenerator) generateHTML(w io.Writer, progs []Prog, files map[string]*file) error {
d := &templateData{
Root: new(templateDir),
}
diff --git a/pkg/cover/report_test.go b/pkg/cover/report_test.go
index d7a893352..f2253032d 100644
--- a/pkg/cover/report_test.go
+++ b/pkg/cover/report_test.go
@@ -9,9 +9,11 @@ package cover
import (
"bytes"
+ "encoding/csv"
"io/ioutil"
"os"
"path/filepath"
+ "reflect"
"regexp"
"runtime"
"strconv"
@@ -88,7 +90,7 @@ func TestReportGenerator(t *testing.T) {
}
func testReportGenerator(t *testing.T, target *targets.Target, test Test) {
- rep, err := generateReport(t, target, test)
+ rep, csv, err := generateReport(t, target, test)
if err != nil {
if test.Result == "" {
t.Fatalf("expected no error, but got:\n%v", err)
@@ -101,6 +103,7 @@ func testReportGenerator(t *testing.T, target *targets.Target, test Test) {
if test.Result != "" {
t.Fatalf("got no error, but expected %q", test.Result)
}
+ checkCSVReport(t, csv)
_ = rep
}
@@ -136,7 +139,7 @@ void __sanitizer_cov_trace_pc() { printf("%llu", (long long)__builtin_return_add
return bin
}
-func generateReport(t *testing.T, target *targets.Target, test Test) ([]byte, error) {
+func generateReport(t *testing.T, target *targets.Target, test Test) ([]byte, []byte, error) {
dir, err := ioutil.TempDir("", "syz-cover-test")
if err != nil {
t.Fatal(err)
@@ -145,7 +148,7 @@ func generateReport(t *testing.T, target *targets.Target, test Test) ([]byte, er
bin := buildTestBinary(t, target, test, dir)
rg, err := MakeReportGenerator(target, bin, dir, dir)
if err != nil {
- return nil, err
+ return nil, nil, err
}
if test.Result == "" {
var pcs []uint64
@@ -175,9 +178,32 @@ func generateReport(t *testing.T, target *targets.Target, test Test) ([]byte, er
}
test.Progs = append(test.Progs, Prog{Data: "main", PCs: pcs})
}
- out := new(bytes.Buffer)
- if err := rg.Do(out, test.Progs); err != nil {
- return nil, err
+ html := new(bytes.Buffer)
+ if err := rg.DoHTML(html, test.Progs); err != nil {
+ return nil, nil, err
+ }
+ csv := new(bytes.Buffer)
+ if err := rg.DoCSV(csv, test.Progs); err != nil {
+ return nil, nil, err
+ }
+
+ return html.Bytes(), csv.Bytes(), nil
+}
+
+func checkCSVReport(t *testing.T, CSVReport []byte) {
+ csvReader := csv.NewReader(bytes.NewBuffer(CSVReport))
+ lines, err := csvReader.ReadAll()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(lines[0], CSVHeader) {
+ t.Fatalf("Heading line in CSV doesn't match %v", lines[0])
+ }
+
+ for _, line := range lines {
+ if line[1] == "main" && line[2] != "1" && line[3] != "1" {
+ t.Fatalf("Function coverage percentage doesn't match %v vs. %v", line[2], "100")
+ }
}
- return out.Bytes(), nil
}