Commit b4a27407 by Ben Clayton

Coverage: Improve uncovered visualizations

List all files, including those that have no coverage. Emit spans that were compiled, but not covered. Emit a % coverage per file. Bug: b/152192800 Fixes: b/153182184 Change-Id: I31c831273a8d3ee89c7ce0737b6e05398ff4f51b Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/43491 Kokoro-Result: kokoro <noreply+kokoro@google.com> Reviewed-by: 's avatarNicolas Capens <nicolascapens@google.com> Tested-by: 's avatarBen Clayton <bclayton@google.com>
parent bdb68078
...@@ -334,8 +334,8 @@ func coverage(file string, spans ...cov.Span) *cov.Coverage { ...@@ -334,8 +334,8 @@ func coverage(file string, spans ...cov.Span) *cov.Coverage {
return &cov.Coverage{ return &cov.Coverage{
[]cov.File{ []cov.File{
cov.File{ cov.File{
Path: file, Path: file,
Spans: spans, Covered: spans,
}, },
}, },
} }
......
...@@ -18,7 +18,6 @@ import ( ...@@ -18,7 +18,6 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
...@@ -28,60 +27,20 @@ import ( ...@@ -28,60 +27,20 @@ import (
"../llvm" "../llvm"
) )
// Location describes a single line-column position in a source file. var ignorePaths = map[string]bool{
type Location struct { "src/Common": true,
Line, Column int "src/Main": true,
"src/OpenGL": true,
"src/Renderer": true,
"src/Shader": true,
"src/System": true,
} }
func (l Location) String() string {
return fmt.Sprintf("%v:%v", l.Line, l.Column)
}
// Compare returns -1 if l comes before o, 1 if l comes after o, otherwise 0.
func (l Location) Compare(o Location) int {
switch {
case l.Line < o.Line:
return -1
case l.Line > o.Line:
return 1
}
return 0
}
// Before returns true if l comes before o.
func (l Location) Before(o Location) bool { return l.Compare(o) == -1 }
// Span describes a start and end interval in a source file.
type Span struct {
Start, End Location
}
func (s Span) String() string {
return fmt.Sprintf("%v-%v", s.Start, s.End)
}
// Compare returns -1 if l comes before o, 1 if l comes after o, otherwise 0.
func (s Span) Compare(o Span) int {
switch {
case s.Start.Before(o.Start):
return -1
case o.Start.Before(s.Start):
return 1
case s.End.Before(o.End):
return -1
case o.End.Before(s.End):
return 1
}
return 0
}
// Before returns true if span s comes before o.
func (s Span) Before(o Span) bool { return s.Compare(o) == -1 }
// File describes the coverage spans in a single source file. // File describes the coverage spans in a single source file.
type File struct { type File struct {
Path string Path string
Spans []Span Covered SpanList // Spans with coverage
Uncovered SpanList // Compiled spans without coverage
} }
// Coverage describes the coverage spans for all the source files for a single // Coverage describes the coverage spans for all the source files for a single
...@@ -148,9 +107,45 @@ func (e Env) Import(profrawPath string) (*Coverage, error) { ...@@ -148,9 +107,45 @@ func (e Env) Import(profrawPath string) (*Coverage, error) {
if err != nil { if err != nil {
return nil, cause.Wrap(err, "Couldn't process turbo-cov output") return nil, cause.Wrap(err, "Couldn't process turbo-cov output")
} }
// Gather all the source files to include them even if there is no coverage
// information produced for these files. This highlights files that aren't
// even compiled.
allFiles := map[string]struct{}{}
for _, file := range cov.Files {
allFiles[file.Path] = struct{}{}
}
filepath.Walk(filepath.Join(e.RootDir, "src"), func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
rel, err := filepath.Rel(e.RootDir, path)
if err != nil || ignorePaths[rel] {
return filepath.SkipDir
}
if !info.IsDir() {
switch filepath.Ext(path) {
case ".h", ".c", ".cc", ".cpp", ".hpp":
if _, seen := allFiles[rel]; !seen {
cov.Files = append(cov.Files, File{Path: rel})
}
}
}
return nil
})
return cov, nil return cov, nil
} }
func appendSpan(spans []Span, span Span) []Span {
if c := len(spans); c > 0 && spans[c-1].End == span.Start {
spans[c-1].End = span.End
} else {
spans = append(spans, span)
}
return spans
}
// https://clang.llvm.org/docs/SourceBasedCodeCoverage.html // https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
// https://stackoverflow.com/a/56792192 // https://stackoverflow.com/a/56792192
func (e Env) parseCov(raw []byte) (*Coverage, error) { func (e Env) parseCov(raw []byte) (*Coverage, error) {
...@@ -189,16 +184,13 @@ func (e Env) parseCov(raw []byte) (*Coverage, error) { ...@@ -189,16 +184,13 @@ func (e Env) parseCov(raw []byte) (*Coverage, error) {
for sIdx := 0; sIdx+1 < len(f.Segments); sIdx++ { for sIdx := 0; sIdx+1 < len(f.Segments); sIdx++ {
start := Location{(int)(f.Segments[sIdx][0].(float64)), (int)(f.Segments[sIdx][1].(float64))} start := Location{(int)(f.Segments[sIdx][0].(float64)), (int)(f.Segments[sIdx][1].(float64))}
end := Location{(int)(f.Segments[sIdx+1][0].(float64)), (int)(f.Segments[sIdx+1][1].(float64))} end := Location{(int)(f.Segments[sIdx+1][0].(float64)), (int)(f.Segments[sIdx+1][1].(float64))}
covered := f.Segments[sIdx][2].(float64) != 0 if covered := f.Segments[sIdx][2].(float64) != 0; covered {
if covered { file.Covered.Add(Span{start, end})
if c := len(file.Spans); c > 0 && file.Spans[c-1].End == start { } else {
file.Spans[c-1].End = end file.Uncovered.Add(Span{start, end})
} else {
file.Spans = append(file.Spans, Span{start, end})
}
} }
} }
if len(file.Spans) > 0 { if len(file.Covered) > 0 {
c.Files = append(c.Files, file) c.Files = append(c.Files, file)
} }
} }
...@@ -257,16 +249,16 @@ func (e Env) parseTurboCov(data []byte) (*Coverage, error) { ...@@ -257,16 +249,16 @@ func (e Env) parseTurboCov(data []byte) (*Coverage, error) {
for sIdx := 0; sIdx+1 < len(segments); sIdx++ { for sIdx := 0; sIdx+1 < len(segments); sIdx++ {
start := segments[sIdx].location start := segments[sIdx].location
end := segments[sIdx+1].location end := segments[sIdx+1].location
if segments[sIdx].count > 0 { if segments[sIdx].covered {
if c := len(file.Spans); c > 0 && file.Spans[c-1].End == start { if segments[sIdx].count > 0 {
file.Spans[c-1].End = end file.Covered.Add(Span{start, end})
} else { } else {
file.Spans = append(file.Spans, Span{start, end}) file.Uncovered.Add(Span{start, end})
} }
} }
} }
if len(file.Spans) > 0 { if len(file.Covered) > 0 {
c.Files = append(c.Files, file) c.Files = append(c.Files, file)
} }
} }
......
...@@ -50,6 +50,11 @@ func (t *Tree) JSON(revision string) string { ...@@ -50,6 +50,11 @@ func (t *Tree) JSON(revision string) string {
sb := &strings.Builder{} sb := &strings.Builder{}
sb.WriteString(`{`) sb.WriteString(`{`)
spansByID := map[SpanID]Span{}
for span, id := range t.spans {
spansByID[id] = span
}
// write the revision // write the revision
sb.WriteString(`"r":"` + revision + `"`) sb.WriteString(`"r":"` + revision + `"`)
...@@ -75,7 +80,7 @@ func (t *Tree) JSON(revision string) string { ...@@ -75,7 +80,7 @@ func (t *Tree) JSON(revision string) string {
// write the files // write the files
sb.WriteString(`,"f":`) sb.WriteString(`,"f":`)
t.writeFilesJSON(sb) t.writeFilesJSON(spansByID, sb)
sb.WriteString(`}`) sb.WriteString(`}`)
return sb.String() return sb.String()
...@@ -125,7 +130,13 @@ func (t *Tree) writeSpansJSON(sb *strings.Builder) { ...@@ -125,7 +130,13 @@ func (t *Tree) writeSpansJSON(sb *strings.Builder) {
sb.WriteString(`]`) sb.WriteString(`]`)
} }
func (t *Tree) writeFilesJSON(sb *strings.Builder) { func (t *Tree) writeSpanJSON(span Span, sb *strings.Builder) {
sb.WriteString(fmt.Sprintf("[%v,%v,%v,%v]",
span.Start.Line, span.Start.Column,
span.End.Line, span.End.Column))
}
func (t *Tree) writeFilesJSON(spansByID map[SpanID]Span, sb *strings.Builder) {
paths := make([]string, 0, len(t.files)) paths := make([]string, 0, len(t.files))
for path := range t.files { for path := range t.files {
paths = append(paths, path) paths = append(paths, path)
...@@ -135,6 +146,20 @@ func (t *Tree) writeFilesJSON(sb *strings.Builder) { ...@@ -135,6 +146,20 @@ func (t *Tree) writeFilesJSON(sb *strings.Builder) {
sb.WriteString(`{`) sb.WriteString(`{`)
for i, path := range paths { for i, path := range paths {
file := t.files[path] file := t.files[path]
uncovered := append(SpanList{}, file.allSpans...)
file.tcm.traverse(func(tc *TestCoverage) {
for id := range tc.Spans {
uncovered.Remove(spansByID[id])
}
})
percentage := 0.0
if totalLines := file.allSpans.NumLines(); totalLines > 0 {
uncoveredLines := uncovered.NumLines()
percentage = 1.0 - (float64(uncoveredLines) / float64(totalLines))
}
if i > 0 { if i > 0 {
sb.WriteString(`,`) sb.WriteString(`,`)
} }
...@@ -142,8 +167,12 @@ func (t *Tree) writeFilesJSON(sb *strings.Builder) { ...@@ -142,8 +167,12 @@ func (t *Tree) writeFilesJSON(sb *strings.Builder) {
sb.WriteString(path) sb.WriteString(path)
sb.WriteString(`":`) sb.WriteString(`":`)
sb.WriteString(`{`) sb.WriteString(`{`)
sb.WriteString(`"g":`) sb.WriteString(`"p":`)
sb.WriteString(fmt.Sprintf("%v", percentage))
sb.WriteString(`,"g":`)
t.writeSpanGroupsJSON(file.spangroups, sb) t.writeSpanGroupsJSON(file.spangroups, sb)
sb.WriteString(`,"u":`)
t.writeUncoveredJSON(file, uncovered, sb)
sb.WriteString(`,"c":`) sb.WriteString(`,"c":`)
t.writeCoverageMapJSON(file.tcm, sb) t.writeCoverageMapJSON(file.tcm, sb)
sb.WriteString(`}`) sb.WriteString(`}`)
...@@ -190,6 +219,17 @@ func (t *Tree) writeSpanGroupJSON(group SpanGroup, sb *strings.Builder) { ...@@ -190,6 +219,17 @@ func (t *Tree) writeSpanGroupJSON(group SpanGroup, sb *strings.Builder) {
sb.WriteString(`}`) sb.WriteString(`}`)
} }
func (t *Tree) writeUncoveredJSON(tf *treeFile, uncovered SpanList, sb *strings.Builder) {
sb.WriteString(`[`)
for i, span := range uncovered {
if i > 0 {
sb.WriteString(`,`)
}
t.writeSpanJSON(span, sb)
}
sb.WriteString(`]`)
}
func (t *Tree) writeCoverageMapJSON(c TestCoverageMap, sb *strings.Builder) { func (t *Tree) writeCoverageMapJSON(c TestCoverageMap, sb *strings.Builder) {
ids := make([]TestIndex, 0, len(c)) ids := make([]TestIndex, 0, len(c))
for id := range c { for id := range c {
...@@ -326,10 +366,14 @@ func (p *parser) parseFile() *treeFile { ...@@ -326,10 +366,14 @@ func (p *parser) parseFile() *treeFile {
if p.peek() == '{' { if p.peek() == '{' {
p.dict(func(key string) { p.dict(func(key string) {
switch key { switch key {
case "p":
p.double()
case "g": case "g":
file.spangroups = p.parseSpanGroups() file.spangroups = p.parseSpanGroups()
case "c": case "c":
p.parseCoverageMap(file.tcm) p.parseCoverageMap(file.tcm)
case "u":
p.parseUncovered(file)
default: default:
p.fail("Unknown file key: '%s'", key) p.fail("Unknown file key: '%s'", key)
} }
...@@ -371,6 +415,12 @@ func (p *parser) parseCoverageMap(tcm TestCoverageMap) { ...@@ -371,6 +415,12 @@ func (p *parser) parseCoverageMap(tcm TestCoverageMap) {
}) })
} }
func (p *parser) parseUncovered(tf *treeFile) {
p.array(func(int) {
tf.allSpans.Add(p.parseSpan())
})
}
func (p *parser) parseCoverage(tc *TestCoverage) { func (p *parser) parseCoverage(tc *TestCoverage) {
p.dict(func(key string) { p.dict(func(key string) {
switch key { switch key {
...@@ -505,6 +555,26 @@ func (p *parser) integer() int { ...@@ -505,6 +555,26 @@ func (p *parser) integer() int {
return i return i
} }
func (p *parser) double() float64 {
sb := strings.Builder{}
for {
if c := p.peek(); c != '.' && (c < '0' || c > '9') {
break
}
sb.WriteByte(p.next())
}
if sb.Len() == 0 {
p.fail("Expected double, got '%c'", p.peek())
return 0
}
f, err := strconv.ParseFloat(sb.String(), 64)
if err != nil {
p.fail("Failed to parse double: %v", err)
return 0
}
return f
}
func (p *parser) fail(msg string, args ...interface{}) { func (p *parser) fail(msg string, args ...interface{}) {
if p.err == nil { if p.err == nil {
msg = fmt.Sprintf(msg, args...) msg = fmt.Sprintf(msg, args...)
......
// Copyright 2020 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cov
import (
"fmt"
"sort"
)
// Location describes a single line-column position in a source file.
type Location struct {
Line, Column int
}
func (l Location) String() string {
return fmt.Sprintf("%v:%v", l.Line, l.Column)
}
// Compare returns -1 if l comes before o, 1 if l comes after o, otherwise 0.
func (l Location) Compare(o Location) int {
switch {
case l.Line < o.Line:
return -1
case l.Line > o.Line:
return 1
case l.Column < o.Column:
return -1
case l.Column > o.Column:
return 1
}
return 0
}
// Before returns true if l comes before o.
func (l Location) Before(o Location) bool { return l.Compare(o) == -1 }
// After returns true if l comes after o.
func (l Location) After(o Location) bool { return l.Compare(o) == 1 }
// Span describes a start and end interval in a source file.
type Span struct {
Start, End Location
}
func (s Span) String() string {
return fmt.Sprintf("%v-%v", s.Start, s.End)
}
// Compare returns -1 if l comes before o, 1 if l comes after o, otherwise 0.
func (s Span) Compare(o Span) int {
switch {
case s.Start.Before(o.Start):
return -1
case o.Start.Before(s.Start):
return 1
case s.End.Before(o.End):
return -1
case o.End.Before(s.End):
return 1
}
return 0
}
// Before returns true if span s comes before o.
func (s Span) Before(o Span) bool { return s.Compare(o) == -1 }
// SpanList is a sorted list of spans. Use SpanList.Add() to insert new spans.
type SpanList []Span
// Add adds the Span to the SpanList, merging and expanding overlapping spans.
func (l *SpanList) Add(s Span) {
// [===]
// [0] [1] | idxStart: 2 | idxEnd: 2
// [0] [1] | idxStart: 0 | idxEnd: 0
// [ 0 ] [ 1 ] [ 2 ] [ 3 ] | idxStart: 1 | idxEnd: 2
// [0] [1] [2] [3] [4] | idxStart: 2 | idxEnd: 2
idxStart := sort.Search(len(*l), func(i int) bool { return (*l)[i].End.Compare(s.Start) >= 0 })
idxEnd := sort.Search(len(*l), func(i int) bool { return (*l)[i].Start.Compare(s.End) > 0 })
if idxStart < idxEnd {
if first := (*l)[idxStart]; first.Start.Before(s.Start) {
s.Start = first.Start
}
if last := (*l)[idxEnd-1]; last.End.After(s.End) {
s.End = last.End
}
}
merged := append(SpanList{}, (*l)[:idxStart]...)
merged = append(merged, s)
merged = append(merged, (*l)[idxEnd:]...)
*l = merged
}
// Remove cuts out the Span from the SpanList, removing and trimming overlapping
// spans.
func (l *SpanList) Remove(s Span) {
if s.Start == s.End {
return // zero length == no split.
}
// [===]
// [0] [1] | idxStart: 2 | idxEnd: 2
// [0] [1] | idxStart: 0 | idxEnd: 0
// [ 0 ] [ 1 ] [ 2 ] [ 3 ] | idxStart: 1 | idxEnd: 2
// [0] [1] [2] [3] [4] | idxStart: 2 | idxEnd: 2
idxStart := sort.Search(len(*l), func(i int) bool { return (*l)[i].End.Compare(s.Start) > 0 })
idxEnd := sort.Search(len(*l), func(i int) bool { return (*l)[i].Start.Compare(s.End) >= 0 })
merged := append(SpanList{}, (*l)[:idxStart]...)
if idxStart < idxEnd {
first, last := (*l)[idxStart], (*l)[idxEnd-1]
if first.Start.Compare(s.Start) < 0 {
merged = append(merged, Span{first.Start, s.Start})
}
if last.End.Compare(s.End) > 0 {
merged = append(merged, Span{s.End, last.End})
}
}
merged = append(merged, (*l)[idxEnd:]...)
*l = merged
}
// Compare returns -1 if l comes before o, 1 if l comes after o, otherwise 0.
func (l SpanList) Compare(o SpanList) int {
switch {
case len(l) < len(o):
return -1
case len(l) > len(o):
return 1
}
for i, a := range l {
switch a.Compare(o[i]) {
case -1:
return -1
case 1:
return 1
}
}
return 0
}
// NumLines returns the total number of lines covered by all spans in the list.
func (l SpanList) NumLines() int {
seen := map[int]struct{}{}
for _, span := range l {
for s := span.Start.Line; s <= span.End.Line; s++ {
if _, ok := seen[s]; !ok {
seen[s] = struct{}{}
}
}
}
return len(seen)
}
// Copyright 2020 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cov_test
import (
"reflect"
"testing"
cov "."
)
func TestSpanListAddNoMerge(t *testing.T) {
l := cov.SpanList{}
l.Add(span(3, 1, 3, 5))
checkSpanList(t, l, span(3, 1, 3, 5))
l.Add(span(4, 1, 4, 5))
checkSpanList(t, l, span(3, 1, 3, 5), span(4, 1, 4, 5))
l.Add(span(2, 1, 2, 5))
checkSpanList(t, l, span(2, 1, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5))
}
func TestSpanListAddExpand(t *testing.T) {
l := cov.SpanList{span(1, 1, 1, 5), span(5, 4, 5, 7), span(9, 1, 9, 5)}
// Expand front (column)
l.Add(span(5, 1, 5, 5))
checkSpanList(t, l, span(1, 1, 1, 5), span(5, 1, 5, 7), span(9, 1, 9, 5))
// Expand back (column)
l.Add(span(5, 5, 5, 9))
checkSpanList(t, l, span(1, 1, 1, 5), span(5, 1, 5, 9), span(9, 1, 9, 5))
// Expand front (line)
l.Add(span(4, 3, 5, 2))
checkSpanList(t, l, span(1, 1, 1, 5), span(4, 3, 5, 9), span(9, 1, 9, 5))
// Expand back (line)
l.Add(span(5, 4, 6, 3))
checkSpanList(t, l, span(1, 1, 1, 5), span(4, 3, 6, 3), span(9, 1, 9, 5))
// Expand front (touching)
l.Add(span(4, 2, 4, 3))
checkSpanList(t, l, span(1, 1, 1, 5), span(4, 2, 6, 3), span(9, 1, 9, 5))
// Expand back (touching)
l.Add(span(6, 3, 6, 4))
checkSpanList(t, l, span(1, 1, 1, 5), span(4, 2, 6, 4), span(9, 1, 9, 5))
}
func TestSpanListAddMergeOverlap(t *testing.T) {
l := cov.SpanList{span(1, 1, 1, 5), span(5, 4, 5, 7), span(9, 1, 9, 5)}
l.Add(span(1, 3, 5, 6))
checkSpanList(t, l, span(1, 1, 5, 7), span(9, 1, 9, 5))
l.Add(span(5, 5, 9, 3))
checkSpanList(t, l, span(1, 1, 9, 5))
}
func TestSpanListAddMergeTouching(t *testing.T) {
l := cov.SpanList{span(1, 1, 1, 5), span(5, 4, 5, 7), span(9, 1, 9, 5)}
l.Add(span(1, 5, 9, 1))
checkSpanList(t, l, span(1, 1, 9, 5))
}
func TestSpanListRemoveNothing(t *testing.T) {
l := cov.SpanList{span(2, 1, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5)}
l.Remove(span(1, 1, 2, 1))
checkSpanList(t, l, span(2, 1, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5))
l.Remove(span(2, 5, 3, 1))
checkSpanList(t, l, span(2, 1, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5))
l.Remove(span(3, 5, 4, 1))
checkSpanList(t, l, span(2, 1, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5))
l.Remove(span(4, 5, 10, 10))
checkSpanList(t, l, span(2, 1, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5))
}
func TestSpanListRemoveWhole(t *testing.T) {
l := cov.SpanList{span(2, 1, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5)}
l.Remove(span(3, 1, 3, 5))
checkSpanList(t, l, span(2, 1, 2, 5), span(4, 1, 4, 5))
l.Remove(span(1, 1, 3, 3))
checkSpanList(t, l, span(4, 1, 4, 5))
l.Remove(span(3, 1, 4, 5))
checkSpanList(t, l)
}
func TestSpanListRemoveZeroLength(t *testing.T) {
l := cov.SpanList{span(2, 1, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5)}
l.Remove(span(3, 1, 3, 1))
checkSpanList(t, l, span(2, 1, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5))
l.Remove(span(3, 5, 3, 5))
checkSpanList(t, l, span(2, 1, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5))
}
func TestSpanListRemoveTrim(t *testing.T) {
l := cov.SpanList{span(2, 1, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5)}
l.Remove(span(2, 1, 2, 2))
checkSpanList(t, l, span(2, 2, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5))
l.Remove(span(2, 4, 2, 5))
checkSpanList(t, l, span(2, 2, 2, 4), span(3, 1, 3, 5), span(4, 1, 4, 5))
l.Remove(span(2, 5, 3, 2))
checkSpanList(t, l, span(2, 2, 2, 4), span(3, 2, 3, 5), span(4, 1, 4, 5))
l.Remove(span(3, 4, 3, 5))
checkSpanList(t, l, span(2, 2, 2, 4), span(3, 2, 3, 4), span(4, 1, 4, 5))
l.Remove(span(4, 1, 4, 2))
checkSpanList(t, l, span(2, 2, 2, 4), span(3, 2, 3, 4), span(4, 2, 4, 5))
l.Remove(span(4, 4, 4, 5))
checkSpanList(t, l, span(2, 2, 2, 4), span(3, 2, 3, 4), span(4, 2, 4, 4))
}
func TestSpanListRemoveSplit(t *testing.T) {
l := cov.SpanList{span(2, 1, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5)}
l.Remove(span(2, 2, 2, 3))
checkSpanList(t, l, span(2, 1, 2, 2), span(2, 3, 2, 5), span(3, 1, 3, 5), span(4, 1, 4, 5))
l.Remove(span(3, 2, 3, 4))
checkSpanList(t, l, span(2, 1, 2, 2), span(2, 3, 2, 5), span(3, 1, 3, 2), span(3, 4, 3, 5), span(4, 1, 4, 5))
l.Remove(span(4, 2, 4, 2)) // zero length == no split
checkSpanList(t, l, span(2, 1, 2, 2), span(2, 3, 2, 5), span(3, 1, 3, 2), span(3, 4, 3, 5), span(4, 1, 4, 5))
}
func span(startLine, startColumn, endLine, endColumn int) cov.Span {
return cov.Span{
Start: cov.Location{Line: startLine, Column: startColumn},
End: cov.Location{Line: endLine, Column: endColumn},
}
}
func checkSpanList(t *testing.T, got cov.SpanList, expect ...cov.Span) {
if expect == nil {
expect = cov.SpanList{}
}
if !reflect.DeepEqual(got, cov.SpanList(expect)) {
t.Errorf("SpanList not as expected.\nGot:\n%v\nExpect:\n%v", got, cov.SpanList(expect))
}
}
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
type treeFile struct { type treeFile struct {
tcm TestCoverageMap tcm TestCoverageMap
spangroups map[SpanGroupID]SpanGroup spangroups map[SpanGroupID]SpanGroup
allSpans SpanList
} }
func newTreeFile() *treeFile { func newTreeFile() *treeFile {
...@@ -52,28 +53,6 @@ func (t *Tree) init() { ...@@ -52,28 +53,6 @@ func (t *Tree) init() {
} }
} }
// SpanList is a list of Spans
type SpanList []Span
// Compare returns -1 if l comes before o, 1 if l comes after o, otherwise 0.
func (l SpanList) Compare(o SpanList) int {
switch {
case len(l) < len(o):
return -1
case len(l) > len(o):
return 1
}
for i, a := range l {
switch a.Compare(o[i]) {
case -1:
return -1
case 1:
return 1
}
}
return 0
}
// Spans returns all the spans used by the tree // Spans returns all the spans used by the tree
func (t *Tree) Spans() SpanList { func (t *Tree) Spans() SpanList {
out := make(SpanList, len(t.spans)) out := make(SpanList, len(t.spans))
...@@ -109,7 +88,7 @@ func (t *Tree) index(path Path) []indexedTest { ...@@ -109,7 +88,7 @@ func (t *Tree) index(path Path) []indexedTest {
return out return out
} }
func (t *Tree) addSpans(spans []Span) SpanSet { func (t *Tree) addSpans(spans SpanList) SpanSet {
out := make(SpanSet, len(spans)) out := make(SpanSet, len(spans))
for _, s := range spans { for _, s := range spans {
id, ok := t.spans[s] id, ok := t.spans[s]
...@@ -138,8 +117,15 @@ nextFile: ...@@ -138,8 +117,15 @@ nextFile:
t.files[file.Path] = tf t.files[file.Path] = tf
} }
for _, span := range file.Covered {
tf.allSpans.Add(span)
}
for _, span := range file.Uncovered {
tf.allSpans.Add(span)
}
// Add all the spans to the map, get the span ids // Add all the spans to the map, get the span ids
spans := t.addSpans(file.Spans) spans := t.addSpans(file.Covered)
// Starting from the test root, walk down the test tree. // Starting from the test root, walk down the test tree.
tcm, test := tf.tcm, t.testRoot tcm, test := tf.tcm, t.testRoot
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment