Skip to content

Commit 73bac3f

Browse files
committed
Moar tests
Signed-off-by: Danny Kopping <danny@coder.com>
1 parent 275bfca commit 73bac3f

File tree

6 files changed

+426
-127
lines changed

6 files changed

+426
-127
lines changed

provisioner/terraform/testdata/timings-aggregation/fake-terraform.sh

Lines changed: 148 additions & 0 deletions
Large diffs are not rendered by default.

provisioner/terraform/testutil.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package terraform
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"slices"
7+
"testing"
8+
9+
"github.com/cespare/xxhash"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
"google.golang.org/protobuf/encoding/protojson"
13+
protobuf "google.golang.org/protobuf/proto"
14+
15+
"github.com/coder/coder/v2/provisionersdk/proto"
16+
)
17+
18+
func ParseTimingLines(t *testing.T, input []byte) []*proto.Timing {
19+
t.Helper()
20+
21+
// Parse the input into *proto.Timing structs.
22+
var expected []*proto.Timing
23+
scanner := bufio.NewScanner(bytes.NewBuffer(input))
24+
for scanner.Scan() {
25+
line := scanner.Bytes()
26+
27+
var msg proto.Timing
28+
require.NoError(t, protojson.Unmarshal(line, &msg))
29+
30+
expected = append(expected, &msg)
31+
}
32+
require.NoError(t, scanner.Err())
33+
StableSortTimings(t, expected) // To reduce flakiness.
34+
35+
return expected
36+
}
37+
38+
func TimingsAreEqual(t *testing.T, expected []*proto.Timing, actual []*proto.Timing) bool {
39+
t.Helper()
40+
41+
// Shortcut check.
42+
if len(expected)+len(actual) == 0 {
43+
t.Logf("both timings are empty")
44+
return true
45+
}
46+
47+
// Shortcut check.
48+
if len(expected) != len(actual) {
49+
t.Logf("timings lengths are not equal: %d != %d", len(expected), len(actual))
50+
return false
51+
}
52+
53+
// Compare each element; both are expected to be sorted in a stable manner.
54+
for i := 0; i < len(expected); i++ {
55+
ex := expected[i]
56+
ac := actual[i]
57+
if !protobuf.Equal(ex, ac) {
58+
t.Logf("timings are not equivalent: %q != %q", ex.String(), ac.String())
59+
return false
60+
}
61+
}
62+
63+
return true
64+
}
65+
66+
func IngestAllSpans(t *testing.T, input []byte, aggregator *timingAggregator) {
67+
t.Helper()
68+
69+
scanner := bufio.NewScanner(bytes.NewBuffer(input))
70+
for scanner.Scan() {
71+
line := scanner.Bytes()
72+
log := parseTerraformLogLine(line)
73+
if log == nil {
74+
continue
75+
}
76+
77+
ts, span, err := extractTimingSpan(log)
78+
if err != nil {
79+
// t.Logf("%s: failed span extraction on line: %q", err, line)
80+
continue
81+
}
82+
83+
require.NotZerof(t, ts, "failed on line: %q", line)
84+
require.NotNilf(t, span, "failed on line: %q", line)
85+
86+
aggregator.ingest(ts, span)
87+
}
88+
89+
require.NoError(t, scanner.Err())
90+
}
91+
92+
func PrintTiming(t *testing.T, timing *proto.Timing) {
93+
t.Helper()
94+
95+
marshaler := protojson.MarshalOptions{
96+
Multiline: false, // Ensure it's set to false for single-line JSON
97+
Indent: "", // No indentation
98+
}
99+
100+
out, err := marshaler.Marshal(timing)
101+
assert.NoError(t, err)
102+
t.Logf("%s", out)
103+
}
104+
105+
func StableSortTimings(t *testing.T, timings []*proto.Timing) {
106+
t.Helper()
107+
108+
slices.SortStableFunc(timings, func(a, b *proto.Timing) int {
109+
if a == nil || b == nil || a.Start == nil || b.Start == nil {
110+
return 0
111+
}
112+
113+
if a.Start.AsTime().Equal(b.Start.AsTime()) {
114+
// Special case: when start times are equal, we need to keep the ordering stable, so we hash both entries
115+
// and sort based on that (since end times could be equal too, in principle).
116+
ah := xxhash.Sum64String(a.String())
117+
bh := xxhash.Sum64String(b.String())
118+
119+
if ah == bh {
120+
// WTF.
121+
t.Logf("identical timings detected!")
122+
PrintTiming(t, a)
123+
PrintTiming(t, b)
124+
return 0
125+
}
126+
127+
if ah < bh {
128+
return -1
129+
}
130+
131+
return 1
132+
}
133+
134+
if a.Start.AsTime().Before(b.Start.AsTime()) {
135+
return -1
136+
}
137+
138+
return 1
139+
})
140+
}
Lines changed: 7 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
package terraform
22

33
import (
4-
"bufio"
5-
"bytes"
64
_ "embed"
7-
"slices"
85
"testing"
96

10-
"github.com/cespare/xxhash"
117
"github.com/stretchr/testify/assert"
128
"github.com/stretchr/testify/require"
139
"golang.org/x/tools/txtar"
14-
"google.golang.org/protobuf/encoding/protojson"
15-
protobuf "google.golang.org/protobuf/proto"
1610

1711
"github.com/coder/coder/v2/coderd/database"
1812
"github.com/coder/coder/v2/provisionersdk/proto"
@@ -79,138 +73,24 @@ func TestAggregation(t *testing.T) {
7973
file.Name, database.AllProvisionerJobTimingStageValues())
8074

8175
agg := newTimingAggregator(stage)
82-
extractAllSpans(t, file.Data, agg)
76+
IngestAllSpans(t, file.Data, agg)
8377
actualTimings = append(actualTimings, agg.aggregate()...)
8478
}
8579

86-
stableSortTimings(t, actualTimings) // To reduce flakiness.
87-
require.True(t, timingsAreEqual(t, expectedTimings.Data, actualTimings))
80+
expected := ParseTimingLines(t, expectedTimings.Data)
81+
StableSortTimings(t, actualTimings) // To reduce flakiness.
82+
if !assert.True(t, TimingsAreEqual(t, expected, actualTimings)) {
83+
printExpectation(t, expected)
84+
}
8885
})
8986
}
9087
}
9188

92-
func timingsAreEqual(t *testing.T, input []byte, actual []*proto.Timing) bool {
93-
t.Helper()
94-
95-
// Parse the input into *proto.Timing structs.
96-
var expected []*proto.Timing
97-
scanner := bufio.NewScanner(bytes.NewBuffer(input))
98-
for scanner.Scan() {
99-
line := scanner.Bytes()
100-
101-
var msg proto.Timing
102-
require.NoError(t, protojson.Unmarshal(line, &msg))
103-
104-
expected = append(expected, &msg)
105-
}
106-
require.NoError(t, scanner.Err())
107-
108-
// Shortcut check.
109-
if len(expected)+len(actual) == 0 {
110-
t.Logf("both timings are empty")
111-
return true
112-
}
113-
114-
// Shortcut check.
115-
if len(expected) != len(actual) {
116-
t.Logf("timings lengths are not equal: %d != %d", len(expected), len(actual))
117-
printExpectation(t, actual)
118-
return false
119-
}
120-
121-
// Compare each element; both are expected to be sorted in a stable manner.
122-
for i := 0; i < len(expected); i++ {
123-
ex := expected[i]
124-
ac := actual[i]
125-
if !protobuf.Equal(ex, ac) {
126-
t.Logf("timings are not equivalent: %q != %q", ex.String(), ac.String())
127-
printExpectation(t, actual)
128-
return false
129-
}
130-
}
131-
132-
return true
133-
}
134-
135-
func extractAllSpans(t *testing.T, input []byte, aggregator *timingAggregator) {
136-
t.Helper()
137-
138-
scanner := bufio.NewScanner(bytes.NewBuffer(input))
139-
for scanner.Scan() {
140-
line := scanner.Bytes()
141-
log := parseTerraformLogLine(line)
142-
if log == nil {
143-
continue
144-
}
145-
146-
ts, span, err := extractTimingSpan(log)
147-
if err != nil {
148-
// t.Logf("%s: failed span extraction on line: %q", err, line)
149-
continue
150-
}
151-
152-
require.NotZerof(t, ts, "failed on line: %q", line)
153-
require.NotNilf(t, span, "failed on line: %q", line)
154-
155-
aggregator.ingest(ts, span)
156-
}
157-
158-
require.NoError(t, scanner.Err())
159-
}
160-
16189
func printExpectation(t *testing.T, actual []*proto.Timing) {
16290
t.Helper()
16391

16492
t.Log("expected:")
16593
for _, a := range actual {
166-
printTiming(t, a)
167-
}
168-
}
169-
170-
func printTiming(t *testing.T, timing *proto.Timing) {
171-
t.Helper()
172-
173-
marshaler := protojson.MarshalOptions{
174-
Multiline: false, // Ensure it's set to false for single-line JSON
175-
Indent: "", // No indentation
94+
PrintTiming(t, a)
17695
}
177-
178-
out, err := marshaler.Marshal(timing)
179-
assert.NoError(t, err)
180-
t.Logf("%s", out)
181-
}
182-
183-
func stableSortTimings(t *testing.T, timings []*proto.Timing) {
184-
slices.SortStableFunc(timings, func(a, b *proto.Timing) int {
185-
if a == nil || b == nil || a.Start == nil || b.Start == nil {
186-
return 0
187-
}
188-
189-
if a.Start.AsTime().Equal(b.Start.AsTime()) {
190-
// Special case: when start times are equal, we need to keep the ordering stable, so we hash both entries
191-
// and sort based on that (since end times could be equal too, in principle).
192-
ah := xxhash.Sum64String(a.String())
193-
bh := xxhash.Sum64String(b.String())
194-
195-
if ah == bh {
196-
// WTF.
197-
t.Logf("identical timings detected!")
198-
printTiming(t, a)
199-
printTiming(t, b)
200-
return 0
201-
}
202-
203-
if ah < bh {
204-
return -1
205-
}
206-
207-
return 1
208-
}
209-
210-
if a.Start.AsTime().Before(b.Start.AsTime()) {
211-
return -1
212-
}
213-
214-
return 1
215-
})
21696
}

0 commit comments

Comments
 (0)