/
istanbul_coverage.go
109 lines (97 loc) · 3.09 KB
/
istanbul_coverage.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
// Code for parsing Istanbul JSON coverage results.
package test
import (
"bytes"
"encoding/json"
"path/filepath"
"strings"
"core"
)
func looksLikeIstanbulCoverageResults(results []byte) bool {
// This works because this is the only JSON format that we accept. If we accept another
// we may need to get cleverer about it.
return bytes.HasPrefix(results, []byte("{"))
}
func parseIstanbulCoverageResults(target *core.BuildTarget, coverage *core.TestCoverage, data []byte) error {
files := map[string]istanbulFile{}
if err := json.Unmarshal(data, &files); err != nil {
return err
}
for filename, file := range files {
coverage.Files[sanitiseFileName(target, filename)] = file.toLineCoverage()
}
coverage.Tests[target.Label] = coverage.Files
return nil
}
type istanbulFile struct {
// StatementMap identifies the start and end for each statement.
StatementMap map[string]istanbulLocation `json:"statementMap"`
// Statements identifies the covered statements.
Statements map[string]int `json:"s"`
}
// An istanbulLocation defines a start/end location in the instrumented source code.
type istanbulLocation struct {
Start istanbulLineLocation `json:"start"`
End istanbulLineLocation `json:"end"`
}
// An istanbulLineLocation defines a single location in the instrumented source code.
type istanbulLineLocation struct {
Column int `json:"column"`
Line int `json:"line"`
}
// toLineCoverage coverts this object to our internal format.
func (file *istanbulFile) toLineCoverage() []core.LineCoverage {
ret := make([]core.LineCoverage, file.maxLineNumber())
for statement, count := range file.Statements {
val := core.Uncovered
if count > 0 {
val = core.Covered
}
s := file.StatementMap[statement]
for i := s.Start.Line; i <= s.End.Line; i++ {
if val > ret[i-1] {
ret[i-1] = val // -1 because 1-indexed
}
}
}
return ret
}
// maxLineNumber returns the highest line number present in this file.
func (file *istanbulFile) maxLineNumber() int {
max := 0
for _, s := range file.StatementMap {
if s.End.Line > max {
max = s.End.Line
}
}
return max
}
// sanitiseFileName strips out any build/test paths found in the given file.
func sanitiseFileName(target *core.BuildTarget, filename string) string {
if s := sanitiseFileNameDir(filename, target.OutDir(), false); s != "" {
return s
} else if s := sanitiseFileNameDir(filename, target.TmpDir(), true); s != "" {
return s
} else if s := sanitiseFileNameDir(filename, target.TestDir(), true); s != "" {
return s
}
return filename
}
// sanitiseFileNameDir attempts to strip off a directory from the middle of a given path.
// It returns a non-empty string if successful.
// If matchAnyLastDir is true it will match any directory for the last component.
func sanitiseFileNameDir(filename string, dir string, matchAnyLastDir bool) string {
if matchAnyLastDir {
dir = filepath.Dir(dir)
}
if index := strings.Index(filename, dir); index != -1 {
ret := filename[index+len(dir)+1:]
if matchAnyLastDir {
if index := strings.IndexRune(ret, filepath.Separator); index != -1 {
return ret[index+1:]
}
}
return ret
}
return ""
}