blob: 9a82012263b8772642f2494c31e58125f050e0bf [file] [log] [blame]
// Copyright 2022 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package cov_test
import (
"reflect"
"strings"
"testing"
"dawn.googlesource.com/dawn/tools/src/cov"
)
var (
fileA = "coverage/file/a"
fileB = "coverage/file/b"
fileC = "coverage/file/c"
fileD = "coverage/file/c"
span0 = cov.Span{cov.Location{3, 2}, cov.Location{3, 9}}
span1 = cov.Span{cov.Location{4, 1}, cov.Location{5, 1}}
span2 = cov.Span{cov.Location{5, 5}, cov.Location{5, 7}}
span3 = cov.Span{cov.Location{7, 2}, cov.Location{7, 7}}
)
// a
// ╭───────┴───────╮
// b c
// ╭───┴───╮ ╭───┴───╮
// d e f g
// ╭─┴─╮ ╭─┴─╮ ╭─┴─╮ ╭─┴─╮
// h i j k l m n o
// ╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮ ╭╯
// p q r s t u v w x y z
//
func TestTree(t *testing.T) {
tree := &cov.Tree{}
t.Log("Add 'b' with the coverage [0,1]")
tree.Add(cov.Path{"a", "b"}, coverage(fileA, span0, span1))
// [0,1]
// (a)
// ╭─────╯
// b
checkSpans(t, tree.Spans(), span0, span1)
checkTests(t, tree, `{a:{b}}`)
checkCoverage(t, tree, fileA, `a:{[0,1]}`)
t.Log("Add 'i' with the coverage [0,1]")
tree.Add(cov.Path{"a", "b", "d", "i"}, coverage(fileA, span0, span1))
// [0,1]
// (a)
// ╭─────╯
// b
// ╭──╯
// d
// ╰─╮
// i
checkSpans(t, tree.Spans(), span0, span1)
checkTests(t, tree, `{a:{b:{d:{i}}}}`)
checkCoverage(t, tree, fileA, `a:{[0,1]}`)
t.Log("Add 'e' with the coverage [0,1,2]")
tree.Add(cov.Path{"a", "b", "e"}, coverage(fileA, span0, span1, span2))
// [0,1]
// (a)
// ┏━━━━━┛
// (b)
// ╭──┺━━┓
// d (e)[2]
// ╰─╮
// i
checkSpans(t, tree.Spans(), span0, span1, span2)
checkTests(t, tree, `{a:{b:{d:{i} e}}}`)
checkCoverage(t, tree, fileA, `a:{[0,1] b:{e:{[2]}}}`)
t.Log("Add 'n' with the coverage [0,3]")
tree.Add(cov.Path{"a", "c", "g", "n"}, coverage(fileA, span0, span3))
// [0]
// (a)
// ┏━━━━━┻━━━━━┓
// [1](b) (c)[3]
// ╭──┺━━┓ ╰──╮
// d (e)[2] g
// ╰─╮ ╭─╯
// i n
checkSpans(t, tree.Spans(), span0, span1, span2, span3)
checkTests(t, tree, `{a:{b:{d:{i}e}c:{g:{n}}}}`)
checkCoverage(t, tree, fileA, `a:{[0] b:{[1] e:{[2]}} c:{[3]}}`)
t.Log("Add 'o' with the coverage [0, 3]")
tree.Add(cov.Path{"a", "c", "g", "o"}, coverage(fileA, span0, span3))
// [0]
// (a)
// ┏━━━━━━━┻━━━━━━━┓
// [1](b) (c)[3]
// ╭──┺━━┓ ╰──╮
// d (e)[2] g
// ╰─╮ ╭─┴─╮
// i n o
checkSpans(t, tree.Spans(), span0, span1, span2, span3)
checkTests(t, tree, `{a:{b:{d:{i}e}c:{g:{n o}}}}`)
checkCoverage(t, tree, fileA, `a:{[0] b:{[1] e:{[2]}} c:{[3]}}`)
t.Log("Add 'f' with the coverage [1]")
tree.Add(cov.Path{"a", "c", "f"}, coverage(fileA, span1))
// (a)
// ┏━━━━━━━━┻━━━━━━━━┓
// [0,1](b) (c)
// ╭──┺━━┓ ┏━━┻━━┓
// d (e)[2] [1](f) (g)[0,3]
// ╰─╮ ╭─┴─╮
// i n o
checkSpans(t, tree.Spans(), span0, span1, span2, span3)
checkTests(t, tree, `{a:{b:{d:{i} e} c:{f g:{n o}}}}`)
checkCoverage(t, tree, fileA, `a:{b:{[0,1] e:{[2]}} c:{f:{[1]} g:{[0,3]}}}`)
t.Log("Add 'j' with the coverage [3]")
tree.Add(cov.Path{"a", "b", "e", "j"}, coverage(fileA, span3))
// (a)
// ┏━━━━━━━━┻━━━━━━━━┓
// (b) (c)
// ┏━━━┻━━━┓ ┏━━┻━━┓
// [0,1](d) (e)[3] [1](f) (g)[0,3]
// ╰─╮ ╭─╯ ╭─┴─╮
// i j n o
checkSpans(t, tree.Spans(), span0, span1, span2, span3)
checkTests(t, tree, `{a:{b:{d:{i} e:{j}} c:{f g:{n o}}}}`)
checkCoverage(t, tree, fileA, `a:{b:{d:{[0,1]} e:{[3]}} c:{f:{[1]} g:{[0,3]}}}`)
t.Log("Add 'k' with the coverage [3]")
tree.Add(cov.Path{"a", "b", "e", "k"}, coverage(fileA, span3))
// (a)
// ┏━━━━━━━━┻━━━━━━━━┓
// (b) (c)
// ┏━━━┻━━━┓ ┏━━┻━━┓
// [0,1](d) (e)[3] [1](f) (g)[0,3]
// ╰─╮ ╭─┴─╮ ╭─┴─╮
// i j k n o
checkSpans(t, tree.Spans(), span0, span1, span2, span3)
checkTests(t, tree, `{a:{b:{d:{i} e:{j k}} c:{f g:{n o}}}}`)
checkCoverage(t, tree, fileA, `a:{b:{d:{[0,1]} e:{[3]}} c:{f:{[1]} g:{[0,3]}}}`)
t.Log("Add 'v' with the coverage [1,2]")
tree.Add(cov.Path{"a", "c", "f", "l", "v"}, coverage(fileA, span1, span2))
// (a)
// ┏━━━━━━━━┻━━━━━━━━━━┓
// (b) (c)
// ┏━━━┻━━━┓ ┏━━┻━━┓
// [0,1](d) (e)[3] [1,2](f) (g)[0,3]
// ╰─╮ ╭─┴─╮ ╭─╯ ╭─┴─╮
// i j k l n o
// ╭╯
// v
checkSpans(t, tree.Spans(), span0, span1, span2, span3)
checkTests(t, tree, `{a:{b:{d:{i} e:{j k}} c:{f:{l:{v}} g:{n o}}}}`)
checkCoverage(t, tree, fileA, `a:{b:{d:{[0,1]} e:{[3]}} c:{f:{[1,2]} g:{[0,3]}}}`)
t.Log("Add 'x' with the coverage [1,2]")
tree.Add(cov.Path{"a", "c", "f", "l", "x"}, coverage(fileA, span1, span2))
// (a)
// ┏━━━━━━━━┻━━━━━━━━━━┓
// (b) (c)
// ┏━━━┻━━━┓ ┏━━┻━━┓
// [0,1](d) (e)[3] [1,2](f) (g)[0,3]
// ╰─╮ ╭─┴─╮ ╭─╯ ╭─┴─╮
// i j k l n o
// ╭┴╮
// v x
checkSpans(t, tree.Spans(), span0, span1, span2, span3)
checkTests(t, tree, `{a:{b:{d:{i} e:{j k}} c:{f:{l:{v x}} g:{n o}}}}`)
checkCoverage(t, tree, fileA, `a:{b:{d:{[0,1]} e:{[3]}} c:{f:{[1,2]} g:{[0,3]}}}`)
t.Log("Add 'z' with the coverage [2]")
tree.Add(cov.Path{"a", "c", "g", "n", "z"}, coverage(fileA, span2))
// (a)
// ┏━━━━━━━━┻━━━━━━━━━━━━┓
// (b) (c)
// ┏━━━┻━━━┓ ┏━━━━┻━━━━┓
// [0,1](d) (e)[3] [1,2](f) (g)
// ╰─╮ ╭─┴─╮ ╭─╯ ┏━┻━┓
// i j k l [2](n) (o)[0,3]
// ╭┴╮ ╭╯
// v x z
checkSpans(t, tree.Spans(), span0, span1, span2, span3)
checkTests(t, tree, `{a:{b:{d:{i} e:{j k}} c:{f:{l:{v x}} g:{n: {z} o}}}}`)
checkCoverage(t, tree, fileA, `a:{b:{d:{[0,1]} e:{[3]}} c:{f:{[1,2]} g:{n:{[2]} o:{[0,3]}}}}`)
tree.Optimize()
// (a)
// ┏━━━━━━━━┻━━━━━━━━━━━━┓
// (b) (c)
// ┏━━━┻━━━┓ ┏━━━━┻━━━━┓
// <0>(d) (e)[3] <2>(f) (g)
// ╰─╮ ╭─┴─╮ ╭─╯ ┏━┻━┓
// i j k l [2](n) (o)<1>
// ╭┴╮ ╭╯
// v x z
checkSpans(t, tree.Spans(), span0, span1, span2, span3)
checkGroups(t, tree.FileSpanGroups(fileA), map[cov.SpanGroupID]cov.SpanGroup{
0: cov.SpanGroup{Spans: spans(0, 1)},
1: cov.SpanGroup{Spans: spans(0, 3)},
2: cov.SpanGroup{Spans: spans(1, 2)},
})
checkTests(t, tree, `{a:{b:{d:{i} e:{j k}} c:{f:{l:{v x}} g:{n: {z} o}}}}`)
checkCoverage(t, tree, fileA, `a:{b:{d:{<0>} e:{[3]}} c:{f:{<2>} g:{n:{[2]} o:{<1>}}}}`)
}
func TestTreeOptInvertForCommon(t *testing.T) {
tree := &cov.Tree{}
tree.Add(cov.Path{"a", "b"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "c"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "d"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "e"}, coverage(fileA, span1))
tree.Add(cov.Path{"a", "f"}, coverage(fileA, span1))
tree.Add(cov.Path{"a", "g"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "h"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "i"}, coverage(fileA, span0))
// (a)
// ┏━━━┳━━━┳━━━┳━┻━┳━━━┳━━━┳━━━┓
// (b) (c) (d) (e) (f) (g) (h) (i)
// [0] [0] [0] [1] [1] [0] [0] [0]
checkSpans(t, tree.Spans(), span0, span1)
checkTests(t, tree, `{a:{b c d e f g h i}}`)
checkCoverage(t, tree, fileA, `a:{b:{[0]} c:{[0]} d:{[0]} e:{[1]} f:{[1]} g:{[0]} h:{[0]} i:{[0]}}`)
tree.Optimize()
// [0]
// (a)
// ╭───┬───┬───┲━┻━┱───┬───┬───╮
// b c d ┏┛ ┗┓ g h i
// (e) (f)
// <0> <0>
checkSpans(t, tree.Spans(), span0, span1)
checkGroups(t, tree.FileSpanGroups(fileA), map[cov.SpanGroupID]cov.SpanGroup{
0: cov.SpanGroup{Spans: spans(0, 1)},
})
checkTests(t, tree, `{a:{b c d e f g h i}}`)
checkCoverage(t, tree, fileA, `a:{[0] e:{<0>} f:{<0>}}`)
}
func TestTreeOptDontInvertForCommon(t *testing.T) {
tree := &cov.Tree{}
tree.Add(cov.Path{"a", "b"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "c"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "d"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "e"}, coverage(fileA, span1))
tree.Add(cov.Path{"a", "f"}, coverage(fileA, span1))
tree.Add(cov.Path{"a", "g"}, coverage(fileA, span2))
tree.Add(cov.Path{"a", "h"}, coverage(fileA, span2))
tree.Add(cov.Path{"a", "i"}, coverage(fileA, span2))
// (a)
// ┏━━━┳━━━┳━━━┳━┻━┳━━━┳━━━┳━━━┓
// (b) (c) (d) (e) (f) (g) (h) (i)
// [0] [0] [0] [1] [1] [2] [2] [2]
checkSpans(t, tree.Spans(), span0, span1, span2)
checkTests(t, tree, `{a:{b c d e f g h i}}`)
checkCoverage(t, tree, fileA, `a:{b:{[0]} c:{[0]} d:{[0]} e:{[1]} f:{[1]} g:{[2]} h:{[2]} i:{[2]}}`)
tree.Optimize()
// (a)
// ┏━━━┳━━━┳━━━┳━┻━┳━━━┳━━━┳━━━┓
// (b) (c) (d) (e) (f) (g) (h) (i)
// [0] [0] [0] [1] [1] [2] [2] [2]
checkSpans(t, tree.Spans(), span0, span1, span2)
checkTests(t, tree, `{a:{b c d e f g h i}}`)
checkCoverage(t, tree, fileA, `a:{b:{[0]} c:{[0]} d:{[0]} e:{[1]} f:{[1]} g:{[2]} h:{[2]} i:{[2]}}`)
}
func checkSpans(t *testing.T, got []cov.Span, expect ...cov.Span) {
if !reflect.DeepEqual(got, expect) {
t.Errorf("Spans not as expected.\nGot: %+v\nExpect: %+v", got, expect)
}
}
func checkGroups(t *testing.T, got, expect map[cov.SpanGroupID]cov.SpanGroup) {
if !reflect.DeepEqual(got, expect) {
t.Errorf("SpanGroupss not as expected.\nGot: %+v\nExpect: %+v", got, expect)
}
}
func checkTests(t *testing.T, tree *cov.Tree, expect string) {
g, e := tree.Tests().String(tree.Strings()), expect
if tg, te := trimWS(g), trimWS(e); tg != te {
t.Errorf("Tests not as expected.\nGot:\n%v\nExpect:\n%v\n------\nGot: %v\nExpect: %v", g, e, tg, te)
}
}
func checkCoverage(t *testing.T, tree *cov.Tree, file string, expect string) {
g, e := tree.FileCoverage(file).String(tree.Tests(), tree.Strings()), expect
if tg, te := trimWS(g), trimWS(e); tg != te {
t.Errorf("Coverage not as expected.\nGot:\n%v\nExpect:\n%v\n------\nGot: %v\nExpect: %v", g, e, tg, te)
}
}
func trimWS(s string) string {
s = strings.ReplaceAll(s, " ", "")
s = strings.ReplaceAll(s, "\n", "")
return s
}
func coverage(file string, spans ...cov.Span) *cov.Coverage {
return &cov.Coverage{
[]cov.File{
cov.File{
Path: file,
Covered: spans,
},
},
}
}
func spans(ids ...cov.SpanID) cov.SpanSet {
out := make(cov.SpanSet, len(ids))
for _, id := range ids {
out[id] = struct{}{}
}
return out
}
func TestTreeEncodeDecode(t *testing.T) {
orig := &cov.Tree{}
orig.Add(cov.Path{"a", "b"}, coverage(fileA, span0, span1))
orig.Add(cov.Path{"a", "b", "d", "i"}, coverage(fileA, span0, span1))
orig.Add(cov.Path{"a", "b", "e"}, coverage(fileA, span0, span1, span2))
orig.Add(cov.Path{"a", "c", "g", "n"}, coverage(fileB, span0, span3))
orig.Add(cov.Path{"a", "c", "g", "o"}, coverage(fileB, span0, span3))
orig.Add(cov.Path{"a", "c", "f"}, coverage(fileA, span1))
orig.Add(cov.Path{"a", "b", "e", "j"}, coverage(fileC, span3))
orig.Add(cov.Path{"a", "b", "e", "k"}, coverage(fileA, span3))
orig.Add(cov.Path{"a", "c", "f", "l", "v"}, coverage(fileA, span1, span2))
orig.Add(cov.Path{"a", "c", "f", "l", "x"}, coverage(fileA, span1, span2))
orig.Add(cov.Path{"a", "c", "g", "n", "z"}, coverage(fileC, span2))
orig.Add(cov.Path{"a", "b"}, coverage(fileA, span0, span1))
orig.Add(cov.Path{"a", "b"}, coverage(fileA, span0, span1))
origJSON := orig.JSON("revision goes here")
read, revision, err := cov.ReadJSON(strings.NewReader(origJSON))
if err != nil {
t.Fatalf("cov.ReadJSON() failed with: %v", err)
}
readJSON := read.JSON(revision)
if origJSON != readJSON {
t.Fatalf("Encode -> Decode -> Encode produced different results:\nOriginal:\n\n%v\n\nRead:\n\n%v", origJSON, readJSON)
}
}