Regres: Keep coverage span groups stable.

Update tests.

Bug: b/152192800
Change-Id: I4e57f0d2b049f0bce7459b29f7ffc3a2ca03534a
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/43309
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
diff --git a/tests/regres/cov/coverage_test.go b/tests/regres/cov/coverage_test.go
index aac05b5..6bd6b73 100644
--- a/tests/regres/cov/coverage_test.go
+++ b/tests/regres/cov/coverage_test.go
@@ -88,7 +88,7 @@
 	//      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]}}}`)
+	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))
@@ -132,7 +132,7 @@
 	//      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]}}}`)
+	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))
@@ -146,7 +146,7 @@
 	//         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]}}}`)
+	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))
@@ -160,7 +160,7 @@
 	//         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]}}}`)
+	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))
@@ -176,7 +176,7 @@
 	//                         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]}}}`)
+	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))
@@ -192,7 +192,7 @@
 	//                         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]}}}`)
+	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))
@@ -208,7 +208,22 @@
 	//                         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]}}}}`)
+	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)
+	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 checkSpans(t *testing.T, got []cov.Span, expect ...cov.Span) {
diff --git a/tests/regres/cov/import.go b/tests/regres/cov/import.go
index e3c3053..100c3cb 100644
--- a/tests/regres/cov/import.go
+++ b/tests/regres/cov/import.go
@@ -37,15 +37,47 @@
 	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 (l Span) String() string {
-	return fmt.Sprintf("%v-%v", l.Start, l.End)
+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.
 type File struct {
 	Path  string
diff --git a/tests/regres/cov/tree.go b/tests/regres/cov/tree.go
index d4c356b..398fd44 100644
--- a/tests/regres/cov/tree.go
+++ b/tests/regres/cov/tree.go
@@ -54,21 +54,35 @@
 	}
 }
 
+// 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
-func (t *Tree) Spans() []Span {
-	out := make([]Span, 0, len(t.spans))
+func (t *Tree) Spans() SpanList {
+	out := make(SpanList, 0, len(t.spans))
 	for span := range t.spans {
 		out = append(out, span)
 	}
-	sort.Slice(out, func(i, j int) bool {
-		if out[i].Start.Line < out[j].Start.Line {
-			return true
-		}
-		if out[i].Start.Line > out[j].Start.Line {
-			return false
-		}
-		return out[i].Start.Column < out[j].Start.Column
-	})
+	sort.Slice(out, func(i, j int) bool { return out[i].Before(out[j]) })
 	return out
 }
 
@@ -259,7 +273,15 @@
 
 func (tc TestCoverage) String(t *Test, s Strings) string {
 	sb := strings.Builder{}
-	sb.WriteString(fmt.Sprintf("{%v", tc.Spans))
+	sb.WriteString("{")
+	if len(tc.Spans) > 0 {
+		sb.WriteString(tc.Spans.String())
+	}
+	if tc.Group != nil {
+		sb.WriteString(" <")
+		sb.WriteString(fmt.Sprintf("%v", *tc.Group))
+		sb.WriteString(">")
+	}
 	if len(tc.Children) > 0 {
 		sb.WriteString(" ")
 		sb.WriteString(tc.Children.String(t, s))
@@ -313,9 +335,32 @@
 // SpanSet is a set of SpanIDs.
 type SpanSet map[SpanID]struct{}
 
+// SpanIDList is a list of SpanIDs
+type SpanIDList []SpanID
+
+// Compare returns -1 if l comes before o, 1 if l comes after o, otherwise 0.
+func (l SpanIDList) Compare(o SpanIDList) int {
+	switch {
+	case len(l) < len(o):
+		return -1
+	case len(l) > len(o):
+		return 1
+	}
+	for i, a := range l {
+		b := o[i]
+		switch {
+		case a < b:
+			return -1
+		case a > b:
+			return 1
+		}
+	}
+	return 0
+}
+
 // List returns the full list of sorted span ids.
-func (s SpanSet) List() []SpanID {
-	out := make([]SpanID, 0, len(s))
+func (s SpanSet) List() SpanIDList {
+	out := make(SpanIDList, 0, len(s))
 	for span := range s {
 		out = append(out, span)
 	}
@@ -422,7 +467,6 @@
 		return
 	}
 
-	// Sort by number of spans in each sets.
 	type spansetInfo struct {
 		key spansetKey
 		set SpanSet // fully expanded set
@@ -435,10 +479,25 @@
 			key: key,
 			set: set,
 			grp: SpanGroup{spans: set},
-			id:  SpanGroupID(len(spansets)),
 		})
 	}
-	sort.Slice(spansets, func(i, j int) bool { return len(spansets[i].set) > len(spansets[j].set) })
+
+	// Sort by number of spans in each sets starting with the largest.
+	sort.Slice(spansets, func(i, j int) bool {
+		a, b := spansets[i].set, spansets[j].set
+		switch {
+		case len(a) > len(b):
+			return true
+		case len(a) < len(b):
+			return false
+		}
+		return a.List().Compare(b.List()) == -1 // Just to keep output stable
+	})
+
+	// Assign IDs now that we have stable order.
+	for i := range spansets {
+		spansets[i].id = SpanGroupID(i)
+	}
 
 	// Loop over the spanGroups starting from the largest, and try to fold them
 	// into the larger sets.