Skip to content

Commit 7fa0b6b

Browse files
committedMay 13, 2023
ensure the correct deterministic sort order is produced when ordered specs are generated by a helper function
fixes a subtle bug described in #1198 - the sort order in Ordering.go was not generating a consistent order for Ordered specs generated by helper functions
1 parent 9e9e3e5 commit 7fa0b6b

File tree

4 files changed

+105
-22
lines changed

4 files changed

+105
-22
lines changed
 

‎internal/internal_integration/ordered_test.go

+51
Original file line numberDiff line numberDiff line change
@@ -1281,4 +1281,55 @@ var _ = DescribeTable("Ordered Containers",
12811281
},
12821282
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", HavePassed(),
12831283
),
1284+
1285+
// Bug reported in #1198
1286+
Entry("bug reported in #1198 - probably due to attempts to ensure deterministic order", true, func() {
1287+
Describe("outer", func() { // the only non-Ordered Describe
1288+
OrderedRun("a")
1289+
OrderedRun("b")
1290+
OrderedRun("c")
1291+
})
1292+
}, []string{"a1", "a2", "a3", "a4", "a5", "b1", "b2", "b3", "b4", "b5", "c1", "c2", "c3", "c4", "c5"}),
12841293
)
1294+
1295+
// OrderedRun creates a sequence of tests, all are intended to be Ordered - this is the reproducer provided in #1198
1296+
func OrderedRun(description string) {
1297+
Describe(description, Ordered, func() {
1298+
It(fmt.Sprint(description, "1"), func() {
1299+
rt.Run(description + "1")
1300+
})
1301+
1302+
Sub2(description)
1303+
Sub3(description)
1304+
1305+
Describe("inner", func() {
1306+
Sub4(description)
1307+
})
1308+
})
1309+
}
1310+
1311+
func Sub2(description string) {
1312+
It(fmt.Sprint(description, "2"), func() {
1313+
rt.Run(description + "2")
1314+
})
1315+
}
1316+
1317+
func Sub3(description string) {
1318+
It(fmt.Sprint(description, "3"), func() {
1319+
rt.Run(description + "3")
1320+
})
1321+
}
1322+
1323+
func Sub4(description string) {
1324+
Describe("deeply nested 1", func() {
1325+
It(fmt.Sprint(description, "4"), func() {
1326+
rt.Run(description + "4")
1327+
})
1328+
})
1329+
1330+
Describe("deeply nested 2", func() {
1331+
It(fmt.Sprint(description, "5"), func() {
1332+
rt.Run(description + "5")
1333+
})
1334+
})
1335+
}

‎internal/node.go

+9
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,15 @@ func (n Nodes) FirstNodeMarkedOrdered() Node {
875875
return Node{}
876876
}
877877

878+
func (n Nodes) IndexOfFirstNodeMarkedOrdered() int {
879+
for i := range n {
880+
if n[i].MarkedOrdered {
881+
return i
882+
}
883+
}
884+
return -1
885+
}
886+
878887
func (n Nodes) GetMaxFlakeAttempts() int {
879888
maxFlakeAttempts := 0
880889
for i := range n {

‎internal/node_test.go

+16
Original file line numberDiff line numberDiff line change
@@ -1579,6 +1579,22 @@ var _ = Describe("Nodes", func() {
15791579
})
15801580
})
15811581

1582+
Describe("IndexOfFirstNodeMarkedOrdered", func() {
1583+
Context("when there are nodes marked ordered", func() {
1584+
It("returns the index of the first one", func() {
1585+
nodes := Nodes{N(), N("A", ntCon, Ordered), N("B", ntCon, Ordered), N()}
1586+
Ω(nodes.IndexOfFirstNodeMarkedOrdered()).Should(Equal(1))
1587+
})
1588+
})
1589+
1590+
Context("when there is no node marked ordered", func() {
1591+
It("returns -1", func() {
1592+
nodes := Nodes{N(), N(), N()}
1593+
Ω(nodes.IndexOfFirstNodeMarkedOrdered()).Should(Equal(-1))
1594+
})
1595+
})
1596+
})
1597+
15821598
Describe("GetMaxFlakeAttempts", func() {
15831599
Context("when there is no node marked with FlakeAttempts decorator", func() {
15841600
It("returns 0", func() {

‎internal/ordering.go

+29-22
Original file line numberDiff line numberDiff line change
@@ -27,36 +27,43 @@ func (s *SortableSpecs) Swap(i, j int) { s.Indexes[i], s.Indexes[j] = s.Indexes[
2727
func (s *SortableSpecs) Less(i, j int) bool {
2828
a, b := s.Specs[s.Indexes[i]], s.Specs[s.Indexes[j]]
2929

30-
firstOrderedA := a.Nodes.FirstNodeMarkedOrdered()
31-
firstOrderedB := b.Nodes.FirstNodeMarkedOrdered()
32-
if firstOrderedA.ID == firstOrderedB.ID && !firstOrderedA.IsZero() {
33-
// strictly preserve order in ordered containers. ID will track this as IDs are generated monotonically
34-
return a.FirstNodeWithType(types.NodeTypeIt).ID < b.FirstNodeWithType(types.NodeTypeIt).ID
30+
aNodes, bNodes := a.Nodes.WithType(types.NodeTypesForContainerAndIt), b.Nodes.WithType(types.NodeTypesForContainerAndIt)
31+
32+
firstOrderedAIdx, firstOrderedBIdx := aNodes.IndexOfFirstNodeMarkedOrdered(), bNodes.IndexOfFirstNodeMarkedOrdered()
33+
if firstOrderedAIdx > -1 && firstOrderedBIdx > -1 && aNodes[firstOrderedAIdx].ID == bNodes[firstOrderedBIdx].ID {
34+
// strictly preserve order within an ordered containers. ID will track this as IDs are generated monotonically
35+
return aNodes.FirstNodeWithType(types.NodeTypeIt).ID < bNodes.FirstNodeWithType(types.NodeTypeIt).ID
36+
}
37+
38+
// if either spec is in an ordered container - only use the nodes up to the outermost ordered container
39+
if firstOrderedAIdx > -1 {
40+
aNodes = aNodes[:firstOrderedAIdx+1]
41+
}
42+
if firstOrderedBIdx > -1 {
43+
bNodes = bNodes[:firstOrderedBIdx+1]
3544
}
3645

37-
aCLs := a.Nodes.WithType(types.NodeTypesForContainerAndIt).CodeLocations()
38-
bCLs := b.Nodes.WithType(types.NodeTypesForContainerAndIt).CodeLocations()
39-
for i := 0; i < len(aCLs) && i < len(bCLs); i++ {
40-
aCL, bCL := aCLs[i], bCLs[i]
41-
if aCL.FileName < bCL.FileName {
42-
return true
43-
} else if aCL.FileName > bCL.FileName {
44-
return false
46+
for i := 0; i < len(aNodes) && i < len(bNodes); i++ {
47+
aCL, bCL := aNodes[i].CodeLocation, bNodes[i].CodeLocation
48+
if aCL.FileName != bCL.FileName {
49+
return aCL.FileName < bCL.FileName
4550
}
46-
if aCL.LineNumber < bCL.LineNumber {
47-
return true
48-
} else if aCL.LineNumber > bCL.LineNumber {
49-
return false
51+
if aCL.LineNumber != bCL.LineNumber {
52+
return aCL.LineNumber < bCL.LineNumber
5053
}
5154
}
5255
// either everything is equal or we have different lengths of CLs
53-
if len(aCLs) < len(bCLs) {
54-
return true
55-
} else if len(aCLs) > len(bCLs) {
56-
return false
56+
if len(aNodes) != len(bNodes) {
57+
return len(aNodes) < len(bNodes)
5758
}
5859
// ok, now we are sure everything was equal. so we use the spec text to break ties
59-
return a.Text() < b.Text()
60+
for i := 0; i < len(aNodes); i++ {
61+
if aNodes[i].Text != bNodes[i].Text {
62+
return aNodes[i].Text < bNodes[i].Text
63+
}
64+
}
65+
// ok, all those texts were equal. we'll use the ID of the most deeply nested node as a last resort
66+
return aNodes[len(aNodes)-1].ID < bNodes[len(bNodes)-1].ID
6067
}
6168

6269
type GroupedSpecIndices []SpecIndices

0 commit comments

Comments
 (0)
Please sign in to comment.