-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcomplexity_test.go
More file actions
147 lines (112 loc) · 3.93 KB
/
complexity_test.go
File metadata and controls
147 lines (112 loc) · 3.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package main
import (
"testing"
"time"
"github.com/rsanheim/plur/rspec"
)
// Complexity detection tests measure execution time at multiple scales
// and alert if time growth exceeds expected O(n) behavior.
//
// If doubling input more than doubles time (with 1.5x tolerance), we may have O(n²).
// TestGrouperComplexity detects if grouping algorithm degrades beyond O(n)
func TestGrouperComplexity(t *testing.T) {
if testing.Short() {
t.Skip("skipping complexity test in short mode")
}
sizes := []int{1000, 2000, 4000, 8000}
workers := 8
iterations := 50
times := make([]time.Duration, len(sizes))
for idx, size := range sizes {
files := generateSpecFiles(size)
start := time.Now()
for i := 0; i < iterations; i++ {
GroupSpecFilesBySize(files, workers)
}
times[idx] = time.Since(start) / time.Duration(iterations)
t.Logf("GroupSpecFilesBySize: size=%5d, time=%v", size, times[idx])
}
checkLinearScaling(t, "GroupSpecFilesBySize", sizes, times)
}
// TestGrouperRuntimeComplexity detects if runtime-based grouping degrades beyond O(n)
func TestGrouperRuntimeComplexity(t *testing.T) {
if testing.Short() {
t.Skip("skipping complexity test in short mode")
}
sizes := []int{1000, 2000, 4000, 8000}
workers := 8
iterations := 50
times := make([]time.Duration, len(sizes))
for idx, size := range sizes {
files := generateSpecFiles(size)
runtimeData := generateRuntimeData(files)
start := time.Now()
for i := 0; i < iterations; i++ {
GroupSpecFilesByRuntime(files, workers, runtimeData)
}
times[idx] = time.Since(start) / time.Duration(iterations)
t.Logf("GroupSpecFilesByRuntime: size=%5d, time=%v", size, times[idx])
}
checkLinearScaling(t, "GroupSpecFilesByRuntime", sizes, times)
}
// TestRSpecParserComplexity detects if parsing degrades beyond O(n)
func TestRSpecParserComplexity(t *testing.T) {
if testing.Short() {
t.Skip("skipping complexity test in short mode")
}
sizes := []int{1000, 2000, 4000, 8000}
iterations := 20
times := make([]time.Duration, len(sizes))
for idx, size := range sizes {
lines := generateRSpecJSONLines(size, 0.02)
start := time.Now()
for i := 0; i < iterations; i++ {
parser := rspec.NewOutputParser()
for _, line := range lines {
parser.ParseLine(line)
}
}
times[idx] = time.Since(start) / time.Duration(iterations)
t.Logf("RSpecParser: size=%5d, time=%v", size, times[idx])
}
checkLinearScaling(t, "RSpecParser", sizes, times)
}
// TestTestCollectorComplexity detects if test collection degrades beyond O(n)
func TestTestCollectorComplexity(t *testing.T) {
if testing.Short() {
t.Skip("skipping complexity test in short mode")
}
sizes := []int{1000, 2000, 4000, 8000}
iterations := 20
times := make([]time.Duration, len(sizes))
for idx, size := range sizes {
notifications := generateTestNotifications(size, 0.05)
start := time.Now()
for i := 0; i < iterations; i++ {
collector := NewTestCollector()
for _, n := range notifications {
collector.AddNotification(n)
}
collector.BuildResult(5 * time.Second)
}
times[idx] = time.Since(start) / time.Duration(iterations)
t.Logf("TestCollector: size=%5d, time=%v", size, times[idx])
}
checkLinearScaling(t, "TestCollector", sizes, times)
}
// checkLinearScaling verifies that time grows at most linearly with input size.
// If time ratio exceeds 2x the size ratio, we likely have O(n²) behavior.
func checkLinearScaling(t *testing.T, name string, sizes []int, times []time.Duration) {
t.Helper()
for i := 1; i < len(sizes); i++ {
sizeRatio := float64(sizes[i]) / float64(sizes[i-1])
timeRatio := float64(times[i]) / float64(times[i-1])
// Allow tolerance over linear scaling to account for system noise at small scales
threshold := sizeRatio * 2.5
if timeRatio > threshold {
t.Errorf("%s: potential O(n²) detected between size %d and %d: "+
"size ratio=%.2f, time ratio=%.2f (threshold=%.2f)",
name, sizes[i-1], sizes[i], sizeRatio, timeRatio, threshold)
}
}
}