mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-23 03:19:51 +00:00
pkg/cover: implement function coverage calculation
This commit is contained in:
parent
0b9318b447
commit
6f0ea384b1
@ -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),
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ func (mgr *Manager) httpCoverCover(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
}
|
||||
if err := reportGenerator.Do(w, progs); err != nil {
|
||||
if err := reportGenerator.DoHTML(w, progs); err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ func main() {
|
||||
}
|
||||
progs := []cover.Prog{{PCs: pcs}}
|
||||
buf := new(bytes.Buffer)
|
||||
if err := rg.Do(buf, progs); err != nil {
|
||||
if err := rg.DoHTML(buf, progs); err != nil {
|
||||
failf("%v", err)
|
||||
}
|
||||
fn, err := osutil.TempFile("syz-cover")
|
||||
|
Loading…
Reference in New Issue
Block a user