diff options
| author | Jouni Hogander <jouni.hogander@unikie.com> | 2020-08-14 15:08:54 +0300 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2020-09-26 17:47:37 +0200 |
| commit | 6f0ea384b170a6e3db2647ee8759a9cf6c85b1da (patch) | |
| tree | c4942e275a526ecd26e83f792a98dfab7c7f49d8 /pkg | |
| parent | 0b9318b447b1d74804947f2c725a96017686cac6 (diff) | |
pkg/cover: implement function coverage calculation
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/cover/report.go | 81 | ||||
| -rw-r--r-- | pkg/cover/report_test.go | 40 |
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 } |
