| // 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) | |
| } | |
| } |