mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-17 14:37:02 +00:00
test: Generate a prettier coverage report
To make the coverage report a bit more accessible and easier to navigate, this patch makes the coverage tests generate a new HTML coverage report (in addition to the classic variant).
This commit is contained in:
1
go.sum
1
go.sum
@@ -20,6 +20,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -44,11 +44,15 @@ go run "${UTILDIR}/gocovcat.go" .coverage/*.out \
|
|||||||
|
|
||||||
# Generate reports based on the merged output.
|
# Generate reports based on the merged output.
|
||||||
go tool cover -func="$COVER_DIR/all.out" | sort -k 3 -n > "$COVER_DIR/func.txt"
|
go tool cover -func="$COVER_DIR/all.out" | sort -k 3 -n > "$COVER_DIR/func.txt"
|
||||||
go tool cover -html="$COVER_DIR/all.out" -o "$COVER_DIR/chasquid.html"
|
go tool cover -html="$COVER_DIR/all.out" -o "$COVER_DIR/classic.html"
|
||||||
|
go run "${UTILDIR}/coverhtml.go" \
|
||||||
|
-input="$COVER_DIR/all.out" -strip=3 \
|
||||||
|
-output="$COVER_DIR/coverage.html" \
|
||||||
|
-title="chasquid coverage report" \
|
||||||
|
-notes="Generated at commit <tt>$(git describe --always --dirty)</tt> ($(git log -1 --format=%ci))"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
grep total .coverage/func.txt
|
|
||||||
echo
|
echo
|
||||||
echo "Coverage report can be found in:"
|
echo "Coverage report can be found in:"
|
||||||
echo file://$COVER_DIR/chasquid.html
|
echo file://$COVER_DIR/coverage.html
|
||||||
|
|
||||||
|
|||||||
259
test/util/coverhtml.go
Normal file
259
test/util/coverhtml.go
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
// Generate an HTML visualization of a Go coverage profile.
|
||||||
|
// Serves a similar purpose to "go tool cover -html", but has a different
|
||||||
|
// visual style.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/cover"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
input = flag.String("input", "", "input file")
|
||||||
|
output = flag.String("output", "", "output file")
|
||||||
|
strip = flag.Int("strip", 0, "how many path entries to strip")
|
||||||
|
title = flag.String("title", "Coverage report", "page title")
|
||||||
|
notes = flag.String("notes", "", "notes to add at the beginning (HTML)")
|
||||||
|
)
|
||||||
|
|
||||||
|
func errorf(f string, a ...interface{}) {
|
||||||
|
fmt.Printf(f, a...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
profiles, err := cover.ParseProfiles(*input)
|
||||||
|
if err != nil {
|
||||||
|
errorf("Error parsing input %q: %v\n", *input, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
totals := &Totals{
|
||||||
|
totalF: map[string]int{},
|
||||||
|
coveredF: map[string]int{},
|
||||||
|
}
|
||||||
|
files := []string{}
|
||||||
|
code := map[string]template.HTML{}
|
||||||
|
for _, p := range profiles {
|
||||||
|
files = append(files, p.FileName)
|
||||||
|
totals.Add(p)
|
||||||
|
|
||||||
|
fname := strings.Join(strings.Split(p.FileName, "/")[*strip:], "/")
|
||||||
|
src, err := ioutil.ReadFile(fname)
|
||||||
|
if err != nil {
|
||||||
|
errorf("Failed to read %q: %v", fname, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
code[p.FileName] = genHTML(src, p.Boundaries(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := os.Create(*output)
|
||||||
|
if err != nil {
|
||||||
|
errorf("Failed to open output file %q: %v", *output, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
Title string
|
||||||
|
Notes template.HTML
|
||||||
|
Files []string
|
||||||
|
Code map[string]template.HTML
|
||||||
|
Totals *Totals
|
||||||
|
}{
|
||||||
|
Title: *title,
|
||||||
|
Notes: template.HTML(*notes),
|
||||||
|
Files: files,
|
||||||
|
Code: code,
|
||||||
|
Totals: totals,
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl := template.Must(template.New("html").Parse(htmlTmpl))
|
||||||
|
err = tmpl.Execute(out, data)
|
||||||
|
if err != nil {
|
||||||
|
errorf("Failed to execute template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
fmt.Printf("%5.1f%% %v\n", totals.Percent(f), f)
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
fmt.Printf("Total: %.1f\n", totals.TotalPercent())
|
||||||
|
}
|
||||||
|
|
||||||
|
type Totals struct {
|
||||||
|
// Total statements.
|
||||||
|
total int
|
||||||
|
|
||||||
|
// Covered statements.
|
||||||
|
covered int
|
||||||
|
|
||||||
|
// Total statements per file.
|
||||||
|
totalF map[string]int
|
||||||
|
|
||||||
|
// Covered statements per file.
|
||||||
|
coveredF map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Totals) Add(p *cover.Profile) {
|
||||||
|
for _, b := range p.Blocks {
|
||||||
|
t.total += b.NumStmt
|
||||||
|
t.totalF[p.FileName] += b.NumStmt
|
||||||
|
if b.Count > 0 {
|
||||||
|
t.covered += b.NumStmt
|
||||||
|
t.coveredF[p.FileName] += b.NumStmt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Totals) Percent(f string) float32 {
|
||||||
|
return float32(t.coveredF[f]) / float32(t.totalF[f]) * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Totals) TotalPercent() float32 {
|
||||||
|
return float32(t.covered) / float32(t.total) * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
func genHTML(src []byte, boundaries []cover.Boundary) template.HTML {
|
||||||
|
// Position -> []Boundary
|
||||||
|
// The order matters, we expect to receive start-end pairs in order, so
|
||||||
|
// they are properly added.
|
||||||
|
bs := map[int][]cover.Boundary{}
|
||||||
|
for _, b := range boundaries {
|
||||||
|
bs[b.Offset] = append(bs[b.Offset], b)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &strings.Builder{}
|
||||||
|
for i := range src {
|
||||||
|
// Emit boundary markers.
|
||||||
|
for _, b := range bs[i] {
|
||||||
|
if b.Start {
|
||||||
|
n := 0
|
||||||
|
if b.Count > 0 {
|
||||||
|
n = int(math.Floor(b.Norm*4)) + 1
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, `<span class="cov%v" title="%v">`, n, b.Count)
|
||||||
|
} else {
|
||||||
|
w.WriteString("</span>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch b := src[i]; b {
|
||||||
|
case '>':
|
||||||
|
w.WriteString(">")
|
||||||
|
case '<':
|
||||||
|
w.WriteString("<")
|
||||||
|
case '&':
|
||||||
|
w.WriteString("&")
|
||||||
|
case '\t':
|
||||||
|
w.WriteString(" ")
|
||||||
|
default:
|
||||||
|
w.WriteByte(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return template.HTML(w.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
const htmlTmpl = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{.Title}}</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font: 100%/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||||
|
Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans",
|
||||||
|
"Helvetica Neue", Arial, sans-serif, "Apple Color Emoji",
|
||||||
|
"Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #1c3986;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #069;
|
||||||
|
}
|
||||||
|
|
||||||
|
code, pre, tt {
|
||||||
|
font-family: Monaco, Bitstream Vera Sans Mono, Lucida Console,
|
||||||
|
Terminal, Consolas, Liberation Mono, DejaVu Sans Mono,
|
||||||
|
Courier New, monospace;
|
||||||
|
color:#333;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 0.5em 0.8em;
|
||||||
|
background: #f8f8f8;
|
||||||
|
border-radius: 1em;
|
||||||
|
border:1px solid #e5e5e5;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color palette from graphiq.
|
||||||
|
.cov0 { color: red; }
|
||||||
|
.cov1 { color: #0B7BAB; }
|
||||||
|
.cov2 { color: #09639B; }
|
||||||
|
.cov3 { color: #034A8B; }
|
||||||
|
.cov4 { color: #00337C; }
|
||||||
|
.cov5 { color: #032663; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function visible(id) {
|
||||||
|
history.replaceState(undefined, undefined, "#" + id);
|
||||||
|
var all = document.getElementsByClassName("file");
|
||||||
|
for (var i = 0; i < all.length; i++) {
|
||||||
|
var elem = all.item(i);
|
||||||
|
elem.style.display = "none";
|
||||||
|
}
|
||||||
|
var chosen = document.getElementById(id);
|
||||||
|
chosen.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
var id = window.location.hash.replace("#", "");
|
||||||
|
if (id != "") {
|
||||||
|
visible(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>{{.Title}}</h1>
|
||||||
|
|
||||||
|
{{.Notes}}<p>
|
||||||
|
|
||||||
|
<tt>Total: {{.Totals.TotalPercent | printf "%.2f"}}%</tt><p>
|
||||||
|
|
||||||
|
{{range .Files}}
|
||||||
|
<tt><a onclick="visible('f::{{.}}')">
|
||||||
|
{{.}} ({{$.Totals.Percent . | printf "%.1f%%"}})</a></tt><br>
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
<div id="source">
|
||||||
|
{{range .Files}}
|
||||||
|
<pre class="file" id="f::{{.}}" style="display: none">{{index $.Code .}}</pre>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
Reference in New Issue
Block a user