From 3e671cc5ce6612d8a67495a107df5ff8091113ea Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Sun, 6 Dec 2020 10:08:26 +0100 Subject: pkg/cover: split into ELF-dependent/independent parts --- pkg/cover/html.go | 448 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 448 insertions(+) create mode 100644 pkg/cover/html.go (limited to 'pkg/cover/html.go') diff --git a/pkg/cover/html.go b/pkg/cover/html.go new file mode 100644 index 000000000..c04f3988b --- /dev/null +++ b/pkg/cover/html.go @@ -0,0 +1,448 @@ +// Copyright 2018 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package cover + +import ( + "bytes" + "encoding/csv" + "fmt" + "html" + "html/template" + "io" + "io/ioutil" + "math" + "path/filepath" + "sort" + "strconv" + "strings" +) + +func (rg *ReportGenerator) DoHTML(w io.Writer, progs []Prog) error { + files, err := rg.prepareFileMap(progs) + if err != nil { + return err + } + d := &templateData{ + Root: new(templateDir), + } + for fname, file := range files { + pos := d.Root + path := "" + for { + if path != "" { + path += "/" + } + sep := strings.IndexByte(fname, filepath.Separator) + if sep == -1 { + path += fname + break + } + dir := fname[:sep] + path += dir + if pos.Dirs == nil { + pos.Dirs = make(map[string]*templateDir) + } + if pos.Dirs[dir] == nil { + pos.Dirs[dir] = &templateDir{ + templateBase: templateBase{ + Path: path, + Name: dir, + }, + } + } + pos = pos.Dirs[dir] + fname = fname[sep+1:] + } + f := &templateFile{ + templateBase: templateBase{ + Path: path, + Name: fname, + Total: file.pcs, + Covered: file.covered, + }, + } + pos.Files = append(pos.Files, f) + if file.covered == 0 { + continue + } + lines, err := parseFile(file.filename) + if err != nil { + return err + } + var buf bytes.Buffer + for i, ln := range lines { + cov, ok := file.lines[i+1] + prog, class, count := "", "", " " + if ok { + if len(cov.count) != 0 { + if cov.prog != -1 { + prog = fmt.Sprintf("onclick='onProgClick(%v)'", cov.prog) + } + count = fmt.Sprintf("% 5v", len(cov.count)) + class = "covered" + if cov.uncovered { + class = "both" + } + } else { + class = "uncovered" + } + } + buf.WriteString(fmt.Sprintf("%v", prog, count)) + if class == "" { + buf.WriteByte(' ') + buf.Write(ln) + buf.WriteByte('\n') + } else { + buf.WriteString(fmt.Sprintf(" ", class)) + buf.Write(ln) + buf.WriteString("\n") + } + } + d.Contents = append(d.Contents, template.HTML(buf.String())) + f.Index = len(d.Contents) - 1 + + addFunctionCoverage(file, d) + } + for _, prog := range progs { + d.Progs = append(d.Progs, template.HTML(html.EscapeString(prog.Data))) + } + + processDir(d.Root) + return coverTemplate.Execute(w, d) +} + +var csvHeader = []string{ + "Filename", + "Function", + "Covered PCs", + "Total PCs", +} + +func (rg *ReportGenerator) DoCSV(w io.Writer, progs []Prog) error { + files, err := rg.prepareFileMap(progs) + if err != nil { + return err + } + var data [][]string + for fname, file := range files { + for _, function := range file.functions { + data = append(data, []string{ + fname, + function.name, + strconv.Itoa(function.covered), + strconv.Itoa(function.pcs), + }) + } + } + sort.Slice(data, func(i, j int) bool { + if data[i][0] != data[j][0] { + return data[i][0] < data[j][0] + } + return data[i][1] < data[j][1] + }) + writer := csv.NewWriter(w) + defer writer.Flush() + if err := writer.Write(csvHeader); err != nil { + return err + } + return writer.WriteAll(data) +} + +func addFunctionCoverage(file *file, data *templateData) { + var buf bytes.Buffer + for _, function := range file.functions { + percentage := "" + if function.covered > 0 { + percentage = fmt.Sprintf("%v%%", percent(function.covered, function.pcs)) + } else { + percentage = "---" + } + buf.WriteString(fmt.Sprintf("%v", function.name)) + buf.WriteString(fmt.Sprintf("%v", percentage)) + buf.WriteString(fmt.Sprintf("of %v", strconv.Itoa(function.pcs))) + buf.WriteString("
\n") + } + data.Functions = append(data.Functions, template.HTML(buf.String())) +} + +func processDir(dir *templateDir) { + for len(dir.Dirs) == 1 && len(dir.Files) == 0 { + for _, child := range dir.Dirs { + dir.Name += "/" + child.Name + dir.Files = child.Files + dir.Dirs = child.Dirs + } + } + sort.Slice(dir.Files, func(i, j int) bool { + return dir.Files[i].Name < dir.Files[j].Name + }) + for _, f := range dir.Files { + dir.Total += f.Total + dir.Covered += f.Covered + f.Percent = percent(f.Covered, f.Total) + } + for _, child := range dir.Dirs { + processDir(child) + dir.Total += child.Total + dir.Covered += child.Covered + } + dir.Percent = percent(dir.Covered, dir.Total) + if dir.Covered == 0 { + dir.Dirs = nil + dir.Files = nil + } +} + +func percent(covered, total int) int { + f := math.Ceil(float64(covered) / float64(total) * 100) + if f == 100 && covered < total { + f = 99 + } + return int(f) +} + +func parseFile(fn string) ([][]byte, error) { + data, err := ioutil.ReadFile(fn) + if err != nil { + return nil, err + } + htmlReplacer := strings.NewReplacer(">", ">", "<", "<", "&", "&", "\t", " ") + var lines [][]byte + for { + idx := bytes.IndexByte(data, '\n') + if idx == -1 { + break + } + lines = append(lines, []byte(htmlReplacer.Replace(string(data[:idx])))) + data = data[idx+1:] + } + if len(data) != 0 { + lines = append(lines, data) + } + return lines, nil +} + +type templateData struct { + Root *templateDir + Contents []template.HTML + Progs []template.HTML + Functions []template.HTML +} + +type templateBase struct { + Name string + Path string + Total int + Covered int + Percent int +} + +type templateDir struct { + templateBase + Dirs map[string]*templateDir + Files []*templateFile +} + +type templateFile struct { + templateBase + Index int +} + +var coverTemplate = template.Must(template.New("").Parse(` + + + + + + + +
+ +
+
+ {{range $i, $f := .Contents}} +
{{$f}}
+ {{end}} + {{range $i, $p := .Progs}} +
{{$p}}
+ {{end}} + {{range $i, $p := .Functions}} +
{{$p}}
+ {{end}} +
+ + + + +{{define "dir"}} + {{range $dir := .Dirs}} +
  • + + {{$dir.Name}} + + {{if $dir.Covered}}{{$dir.Percent}}%{{else}}---{{end}} + of {{$dir.Total}} + + + +
  • + {{end}} + {{range $file := .Files}} +
  • + {{if $file.Covered}} + + {{$file.Name}} + + + + {{$file.Percent}}% + + of {{$file.Total}} + + {{else}} + {{$file.Name}}--- + of {{$file.Total}} + {{end}} +
  • + {{end}} +{{end}} +`)) -- cgit mrf-deployment