Skip to content

Commit be3e577

Browse files
leodidoona-agent
andcommitted
test: improve stability test for dependency-aware sorting
The previous test passed by coincidence because input was already in expected order. New test verifies stability by using multiple input orderings and checking that relative order within each depth group is preserved. Also adds missing 'sort' import required by sort.SliceStable. Co-authored-by: Ona <[email protected]>
1 parent 53461e3 commit be3e577

File tree

2 files changed

+97
-10
lines changed

2 files changed

+97
-10
lines changed

pkg/leeway/build.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"os/exec"
1717
"path/filepath"
1818
"regexp"
19+
"sort"
1920
"strconv"
2021
"strings"
2122
"sync"

pkg/leeway/build_sort_test.go

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -216,20 +216,106 @@ func TestCalculateDependencyDepth(t *testing.T) {
216216
}
217217

218218
// TestSortPackagesByDependencyDepth_Stability tests that sorting is stable
219+
// A stable sort preserves the relative order of elements with equal keys
219220
func TestSortPackagesByDependencyDepth_Stability(t *testing.T) {
220-
// Create packages with same depth - order should be preserved
221-
packages := []*Package{
222-
{fullNameOverride: "pkg1", dependencies: []*Package{}},
223-
{fullNameOverride: "pkg2", dependencies: []*Package{}},
224-
{fullNameOverride: "pkg3", dependencies: []*Package{}},
221+
// Create a shared leaf dependency
222+
leaf := &Package{fullNameOverride: "leaf", dependencies: []*Package{}}
223+
224+
// Create multiple packages at depth 1 (all depend on leaf)
225+
depth1Packages := []*Package{
226+
{fullNameOverride: "d1-alpha", dependencies: []*Package{leaf}},
227+
{fullNameOverride: "d1-beta", dependencies: []*Package{leaf}},
228+
{fullNameOverride: "d1-gamma", dependencies: []*Package{leaf}},
229+
{fullNameOverride: "d1-delta", dependencies: []*Package{leaf}},
225230
}
226231

227-
sorted := sortPackagesByDependencyDepth(packages)
232+
// Create multiple packages at depth 0 (no dependencies)
233+
depth0Packages := []*Package{
234+
{fullNameOverride: "d0-alpha", dependencies: []*Package{}},
235+
{fullNameOverride: "d0-beta", dependencies: []*Package{}},
236+
{fullNameOverride: "d0-gamma", dependencies: []*Package{}},
237+
}
228238

229-
// All have depth 0, so order should be preserved
230-
require.Equal(t, "pkg1", sorted[0].FullName())
231-
require.Equal(t, "pkg2", sorted[1].FullName())
232-
require.Equal(t, "pkg3", sorted[2].FullName())
239+
// Test with different input orderings to verify stability
240+
// The key insight: within each depth group, relative order must be preserved
241+
testCases := []struct {
242+
name string
243+
input []*Package
244+
}{
245+
{
246+
name: "depth1 first, then depth0",
247+
input: []*Package{
248+
depth1Packages[0], depth1Packages[1], depth1Packages[2], depth1Packages[3],
249+
depth0Packages[0], depth0Packages[1], depth0Packages[2],
250+
},
251+
},
252+
{
253+
name: "depth0 first, then depth1",
254+
input: []*Package{
255+
depth0Packages[0], depth0Packages[1], depth0Packages[2],
256+
depth1Packages[0], depth1Packages[1], depth1Packages[2], depth1Packages[3],
257+
},
258+
},
259+
{
260+
name: "interleaved",
261+
input: []*Package{
262+
depth1Packages[0], depth0Packages[0], depth1Packages[1], depth0Packages[1],
263+
depth1Packages[2], depth0Packages[2], depth1Packages[3],
264+
},
265+
},
266+
{
267+
name: "reverse interleaved",
268+
input: []*Package{
269+
depth0Packages[2], depth1Packages[3], depth0Packages[1], depth1Packages[2],
270+
depth0Packages[0], depth1Packages[1], depth1Packages[0],
271+
},
272+
},
273+
}
274+
275+
for _, tc := range testCases {
276+
t.Run(tc.name, func(t *testing.T) {
277+
// Record the input order of packages at each depth
278+
inputOrderDepth0 := []string{}
279+
inputOrderDepth1 := []string{}
280+
for _, pkg := range tc.input {
281+
if len(pkg.dependencies) == 0 {
282+
inputOrderDepth0 = append(inputOrderDepth0, pkg.FullName())
283+
} else {
284+
inputOrderDepth1 = append(inputOrderDepth1, pkg.FullName())
285+
}
286+
}
287+
288+
sorted := sortPackagesByDependencyDepth(tc.input)
289+
290+
// Extract the output order at each depth
291+
outputOrderDepth0 := []string{}
292+
outputOrderDepth1 := []string{}
293+
for _, pkg := range sorted {
294+
if len(pkg.dependencies) == 0 {
295+
outputOrderDepth0 = append(outputOrderDepth0, pkg.FullName())
296+
} else {
297+
outputOrderDepth1 = append(outputOrderDepth1, pkg.FullName())
298+
}
299+
}
300+
301+
// Depth 1 packages should come before depth 0 packages
302+
require.Equal(t, 7, len(sorted), "should have all 7 packages")
303+
304+
// First 4 should be depth 1, last 3 should be depth 0
305+
for i := 0; i < 4; i++ {
306+
require.Equal(t, 1, len(sorted[i].dependencies), "first 4 should be depth 1")
307+
}
308+
for i := 4; i < 7; i++ {
309+
require.Equal(t, 0, len(sorted[i].dependencies), "last 3 should be depth 0")
310+
}
311+
312+
// Stability check: relative order within each depth group must match input order
313+
require.Equal(t, inputOrderDepth1, outputOrderDepth1,
314+
"depth 1 packages should maintain relative input order (stability)")
315+
require.Equal(t, inputOrderDepth0, outputOrderDepth0,
316+
"depth 0 packages should maintain relative input order (stability)")
317+
})
318+
}
233319
}
234320

235321
// TestSortPackagesByDependencyDepth_Performance tests with larger graphs

0 commit comments

Comments
 (0)