-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvt100.go
192 lines (174 loc) · 4.31 KB
/
vt100.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uncover
import (
"fmt"
"go/build"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"text/tabwriter"
"github.com/gregoryv/nexus"
)
func Report(profiles []*Profile, out io.Writer) (coverage float64, err error) {
tabw := tabwriter.NewWriter(out, 1, 8, 4, ' ', 0)
defer tabw.Flush()
return WriteOutput(profiles, tabw)
}
var OnlyShow string
func WriteOutput(profiles []*Profile, out io.Writer) (coverage float64, err error) {
var total, covered int64
var file string
var funcs []*FuncExtent
for _, profile := range profiles {
fn := profile.Filename
file, err = findFile(fn)
if err != nil {
return
}
funcs, err = findFuncs(file)
if err != nil {
return
}
// filter funcs
if OnlyShow != "" {
tmp := make([]*FuncExtent, 0)
for _, fe := range funcs {
if strings.Index(fe.Name, OnlyShow) >= 0 {
tmp = append(tmp, fe)
}
}
funcs = tmp
}
// Match up functions and profile blocks.
for _, f := range funcs {
// always skip unreachable, eg. stringer generated
if strings.Index(f.Name, "_()") > -1 {
continue
}
c, t := f.coverage(profile)
// Only show uncovered funcs
p := percent(c, t)
switch {
case p == 0:
fmt.Fprintf(out, "%s:%d\n", fn, f.startLine)
fmt.Fprintf(out, "%s%s - UNCOVERED%s\n\n", red, f.Name, reset)
case p < 100:
fmt.Fprintf(out, "%s:%d\n", fn, f.startLine)
Write(out, profile, f)
}
total += t
covered += c
}
}
coverage = percent(covered, total)
fmt.Fprintf(out, "total:\t(statements)\t%.1f%%\n", coverage)
return
}
// Write reads the profile data from profile and generates colored
// vt100 output to stdout.
func Write(out io.Writer, profile *Profile, f *FuncExtent) error {
src, err := fileCache.load(profile.Filename)
if err != nil {
return err
}
// Filter boundaries according to the extent
funcBoundaries := make([]Boundary, 0)
for _, b := range profile.Boundaries(src) {
if b.Offset > f.End {
break
}
if b.Offset >= f.Offset {
funcBoundaries = append(funcBoundaries, b)
}
}
// Write colored source to buffer
err = vt100Gen(out, src, f.Name, funcBoundaries)
return err
}
// fileCache is used to cache already loaded files.
var fileCache = &cache{}
type cache struct {
filename string
data []byte
err error
}
func (me *cache) load(filename string) ([]byte, error) {
if filename == me.filename {
return me.data, me.err
}
me.filename = filename
file, err := findFile(filename)
if err != nil {
me.err = err
return nil, err
}
data, err := ioutil.ReadFile(file)
if err != nil {
me.err = fmt.Errorf("can't read %q: %v", filename, err)
}
me.data = data
return me.data, me.err
}
// vt100Gen generates an coverage report with the provided filename,
// source code, and tokens, and writes it to the given Writer.
// boundaries must contain pairs beginning and end
func vt100Gen(w io.Writer, src []byte, sign string, boundaries []Boundary) error {
p, err := nexus.NewPrinter(w)
p.Print(green, sign)
for i := 0; i < len(boundaries); i += 2 {
start := boundaries[i]
// Overflow bug found in the wild; though the boundaries
// should always be in pairs. Protect against panic, though
// the result is unknown.
var end Boundary
if len(boundaries) > i+1 {
end = boundaries[i+1]
}
if end.Start {
start, end = end, start
}
if start.Count == 0 {
p.Print(red)
} else {
p.Print(green)
}
// handle empty blocks
k := start.Offset
if i > 0 {
k = boundaries[i-1].Offset
}
p.Print(string(src[k:end.Offset]))
}
p.Printf("\n%s}%s\n\n", green, reset)
p.Print(reset)
return *err
}
// colors
var (
red = "\033[31m"
green = "\033[32m"
reset = "\033[0m"
)
// findFile finds the location of the named file in GOROOT, GOPATH
// etc.
func findFile(file string) (string, error) {
dir, file := filepath.Split(file)
pkg, err := build.Import(dir, ".", build.FindOnly)
if err != nil {
if _, err := os.Stat(file); err == nil {
return file, nil
}
return "", fmt.Errorf("can't find %q: %v", file, err)
}
return filepath.Join(pkg.Dir, file), nil
}
func percent(covered, total int64) float64 {
if total == 0 {
total = 1 // Avoid zero denominator.
}
return 100.0 * float64(covered) / float64(total)
}