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 (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/csv"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
@ -41,6 +42,13 @@ type symbol struct {
|
|||||||
end uint64
|
end uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var CSVHeader = []string{
|
||||||
|
"Filename",
|
||||||
|
"Function",
|
||||||
|
"Covered PCs",
|
||||||
|
"Total PCs",
|
||||||
|
}
|
||||||
|
|
||||||
func MakeReportGenerator(target *targets.Target, kernelObject, srcDir, buildDir string) (*ReportGenerator, error) {
|
func MakeReportGenerator(target *targets.Target, kernelObject, srcDir, buildDir string) (*ReportGenerator, error) {
|
||||||
rg := &ReportGenerator{
|
rg := &ReportGenerator{
|
||||||
target: target,
|
target: target,
|
||||||
@ -70,12 +78,18 @@ func MakeReportGenerator(target *targets.Target, kernelObject, srcDir, buildDir
|
|||||||
|
|
||||||
type file struct {
|
type file struct {
|
||||||
lines map[int]line
|
lines map[int]line
|
||||||
|
functions map[string]*function
|
||||||
totalPCs map[uint64]bool
|
totalPCs map[uint64]bool
|
||||||
coverPCs map[uint64]bool
|
coverPCs map[uint64]bool
|
||||||
totalInline map[int]bool
|
totalInline map[int]bool
|
||||||
coverInline map[int]bool
|
coverInline map[int]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type function struct {
|
||||||
|
totalPCs map[uint64]bool
|
||||||
|
coverPCs map[uint64]bool
|
||||||
|
}
|
||||||
|
|
||||||
type line struct {
|
type line struct {
|
||||||
count map[int]bool
|
count map[int]bool
|
||||||
prog int
|
prog int
|
||||||
@ -83,7 +97,23 @@ type line struct {
|
|||||||
symbolCovered bool
|
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)
|
coveredPCs := make(map[uint64]bool)
|
||||||
allPCs := make(map[uint64]bool)
|
allPCs := make(map[uint64]bool)
|
||||||
symbols := 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 {
|
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 {
|
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 {
|
for pc, frames := range rg.pcs {
|
||||||
covered := coveredPCs[pc]
|
covered := coveredPCs[pc]
|
||||||
@ -133,6 +163,8 @@ func (rg *ReportGenerator) Do(w io.Writer, progs []Prog) error {
|
|||||||
f.coverPCs[pc] = true
|
f.coverPCs[pc] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function := getFunction(f.functions, frame.Func)
|
||||||
|
function.totalPCs[pc] = true
|
||||||
if !covered {
|
if !covered {
|
||||||
ln := f.lines[frame.Line]
|
ln := f.lines[frame.Line]
|
||||||
if !frame.Inline || len(ln.count) == 0 {
|
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)]
|
ln.symbolCovered = symbols[rg.findSymbol(pc)]
|
||||||
f.lines[frame.Line] = ln
|
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 {
|
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 {
|
if f == nil {
|
||||||
f = &file{
|
f = &file{
|
||||||
lines: make(map[int]line),
|
lines: make(map[int]line),
|
||||||
|
functions: make(map[string]*function),
|
||||||
totalPCs: make(map[uint64]bool),
|
totalPCs: make(map[uint64]bool),
|
||||||
coverPCs: make(map[uint64]bool),
|
coverPCs: make(map[uint64]bool),
|
||||||
totalInline: make(map[int]bool),
|
totalInline: make(map[int]bool),
|
||||||
@ -161,7 +196,43 @@ func getFile(files map[string]*file, name string) *file {
|
|||||||
return f
|
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{
|
d := &templateData{
|
||||||
Root: new(templateDir),
|
Root: new(templateDir),
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,11 @@ package cover
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/csv"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -88,7 +90,7 @@ func TestReportGenerator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testReportGenerator(t *testing.T, target *targets.Target, test Test) {
|
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 err != nil {
|
||||||
if test.Result == "" {
|
if test.Result == "" {
|
||||||
t.Fatalf("expected no error, but got:\n%v", err)
|
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 != "" {
|
if test.Result != "" {
|
||||||
t.Fatalf("got no error, but expected %q", test.Result)
|
t.Fatalf("got no error, but expected %q", test.Result)
|
||||||
}
|
}
|
||||||
|
checkCSVReport(t, csv)
|
||||||
_ = rep
|
_ = rep
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +139,7 @@ void __sanitizer_cov_trace_pc() { printf("%llu", (long long)__builtin_return_add
|
|||||||
return bin
|
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")
|
dir, err := ioutil.TempDir("", "syz-cover-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
bin := buildTestBinary(t, target, test, dir)
|
||||||
rg, err := MakeReportGenerator(target, bin, dir, dir)
|
rg, err := MakeReportGenerator(target, bin, dir, dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if test.Result == "" {
|
if test.Result == "" {
|
||||||
var pcs []uint64
|
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})
|
test.Progs = append(test.Progs, Prog{Data: "main", PCs: pcs})
|
||||||
}
|
}
|
||||||
out := new(bytes.Buffer)
|
html := new(bytes.Buffer)
|
||||||
if err := rg.Do(out, test.Progs); err != nil {
|
if err := rg.DoHTML(html, test.Progs); err != nil {
|
||||||
return nil, err
|
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)
|
http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
progs := []cover.Prog{{PCs: pcs}}
|
progs := []cover.Prog{{PCs: pcs}}
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
if err := rg.Do(buf, progs); err != nil {
|
if err := rg.DoHTML(buf, progs); err != nil {
|
||||||
failf("%v", err)
|
failf("%v", err)
|
||||||
}
|
}
|
||||||
fn, err := osutil.TempFile("syz-cover")
|
fn, err := osutil.TempFile("syz-cover")
|
||||||
|
Loading…
Reference in New Issue
Block a user