Skip to content

Commit 2428429

Browse files
authored
refactor(e2e): make the teardown steps a type
registerTeardown took a cleanup func and an onPanic func, both injected only so the test could watch them. The steps are a type now, the list is literal at the call site, and register takes one argument.The ordering test registers inside a subtest, so it exercises testing's own LIFO rather than a stand-in. The panic test still needs a stub: register reports through assert.Fail, and a failing subtest fails its parent, so a real one could not assert that the delete still ran. Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
1 parent db3507a commit 2428429

2 files changed

Lines changed: 57 additions & 76 deletions

File tree

e2e/internal/harness/harness.go

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -177,14 +177,12 @@ func (b *Builder) Start() *Env {
177177
dumpNamespaces: dumpNamespaces(b.cfg),
178178
}
179179

180-
registerTeardown(t.Cleanup,
181-
func(step string, v any) { assert.Fail(t, fmt.Sprintf("teardown step %q panicked: %v", step, v)) },
182-
teardownSteps(
183-
env.collectArtifacts,
184-
func() { assert.NoError(t, c.Cleanup()) },
185-
func() { stopCluster(t, cancel, startErr) },
186-
func() { assert.NoError(t, common.DeleteTestCluster(b.cfg.cluster)) },
187-
))
180+
teardown{
181+
{"artifacts", env.collectArtifacts},
182+
{"kubeconfig", func() { assert.NoError(t, c.Cleanup()) }},
183+
{"stop", func() { stopCluster(t, cancel, startErr) }},
184+
{"delete", func() { assert.NoError(t, common.DeleteTestCluster(b.cfg.cluster)) }},
185+
}.register(t)
188186

189187
setup.LoggingOperator(t, c, setup.LoggingOperatorOptionFunc(func(o *setup.LoggingOperatorOptions) {
190188
o.Namespace = b.cfg.controlNamespace
@@ -303,35 +301,30 @@ func (p *pending) String() string {
303301
return p.name
304302
}
305303

306-
type teardownStep struct {
304+
type step struct {
307305
name string
308306
run func()
309307
}
310308

311-
// teardownSteps lists them in the order they have to run. This order is what
312-
// kept clusters from leaking before it moved here, so it is one list rather
313-
// than four Cleanup calls in reverse.
314-
func teardownSteps(artifacts, kubeconfig, stop, deleteCluster func()) []teardownStep {
315-
return []teardownStep{
316-
{"artifacts", artifacts},
317-
{"kubeconfig", kubeconfig},
318-
{"stop", stop},
319-
{"delete", deleteCluster},
320-
}
309+
type teardown []step
310+
311+
// cleanupT is the part of testing.T register needs.
312+
type cleanupT interface {
313+
Cleanup(func())
314+
Errorf(format string, args ...any)
321315
}
322316

323-
// registerTeardown hands the steps over back to front, because Cleanup is LIFO,
324-
// and isolates each one: Go abandons the remaining cleanups at the first panic,
325-
// which would strand the cluster.
326-
func registerTeardown(cleanup func(func()), onPanic func(step string, v any), steps []teardownStep) {
327-
for _, step := range slices.Backward(steps) {
328-
cleanup(func() {
317+
// register goes back to front because Cleanup is LIFO, and isolates each step:
318+
// Go abandons the rest at the first panic, which would strand the cluster.
319+
func (td teardown) register(t cleanupT) {
320+
for _, s := range slices.Backward(td) {
321+
t.Cleanup(func() {
329322
defer func() {
330323
if v := recover(); v != nil {
331-
onPanic(step.name, v)
324+
assert.Fail(t, fmt.Sprintf("teardown step %q panicked: %v", s.name, v))
332325
}
333326
}()
334-
step.run()
327+
s.run()
335328
})
336329
}
337330
}

e2e/internal/harness/harness_test.go

Lines changed: 37 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package harness
1616

1717
import (
1818
"errors"
19+
"fmt"
1920
"os"
2021
"path/filepath"
2122
"slices"
@@ -32,59 +33,53 @@ import (
3233
"github.com/kube-logging/logging-operator/pkg/sdk/logging/api/v1beta1"
3334
)
3435

35-
// cleanupRecorder stands in for testing.T's Cleanup, so the order the steps end
36-
// up running in can be asserted without a cluster.
37-
type cleanupRecorder struct {
38-
registered []func()
39-
}
36+
// Registered in a subtest so the order comes from testing's own LIFO rather
37+
// than a stand-in for it.
38+
func TestTeardownRunsInDeclaredOrder(t *testing.T) {
39+
var ran []string
40+
record := func(name string) step { return step{name, func() { ran = append(ran, name) }} }
4041

41-
func (c *cleanupRecorder) Cleanup(fn func()) {
42-
c.registered = append(c.registered, fn)
43-
}
42+
t.Run("teardown", func(t *testing.T) {
43+
teardown{record("artifacts"), record("kubeconfig"), record("stop"), record("delete")}.register(t)
44+
})
4445

45-
// run does what testing does at the end of a test: LIFO.
46-
func (c *cleanupRecorder) run() {
47-
for _, fn := range slices.Backward(c.registered) {
48-
fn()
49-
}
46+
assert.Equal(t, []string{"artifacts", "kubeconfig", "stop", "delete"}, ran)
5047
}
5148

52-
// The order here is what kept clusters from leaking: the log dump needs the
53-
// cache and the kubeconfig, both of which the later steps take away.
54-
func TestTeardownRunsInDeclaredOrder(t *testing.T) {
55-
var ran []string
56-
record := func(name string) func() { return func() { ran = append(ran, name) } }
49+
// A real subtest cannot be used here: register reports the panic, which would
50+
// fail the subtest and this test with it.
51+
type reportingT struct {
52+
cleanups []func()
53+
reported []string
54+
}
5755

58-
recorder := &cleanupRecorder{}
59-
registerTeardown(recorder.Cleanup, failOnPanic(t), teardownSteps(
60-
record("artifacts"), record("kubeconfig"), record("stop"), record("delete"),
61-
))
62-
recorder.run()
56+
func (r *reportingT) Cleanup(fn func()) { r.cleanups = append(r.cleanups, fn) }
57+
func (r *reportingT) Errorf(format string, args ...any) {
58+
r.reported = append(r.reported, fmt.Sprintf(format, args...))
59+
}
6360

64-
assert.Equal(t, []string{"artifacts", "kubeconfig", "stop", "delete"}, ran)
61+
func (r *reportingT) runCleanups() {
62+
for _, fn := range slices.Backward(r.cleanups) {
63+
fn()
64+
}
6565
}
6666

6767
// Go abandons the remaining cleanups at the first panic, so without the recover
68-
// in registerTeardown a panicking log dump would leave the cluster running.
68+
// a panicking log dump would leave the cluster running.
6969
func TestTeardownDeletesTheClusterAfterAnEarlierPanic(t *testing.T) {
70-
var ran, panicked []string
71-
record := func(name string) func() { return func() { ran = append(ran, name) } }
72-
73-
recorder := &cleanupRecorder{}
74-
registerTeardown(recorder.Cleanup,
75-
func(step string, _ any) { panicked = append(panicked, step) },
76-
teardownSteps(
77-
func() { ran = append(ran, "artifacts"); panic("boom") },
78-
record("kubeconfig"), record("stop"), record("delete"),
79-
))
80-
recorder.run()
70+
var ran []string
71+
record := func(name string) step { return step{name, func() { ran = append(ran, name) }} }
8172

82-
assert.Equal(t, []string{"artifacts", "kubeconfig", "stop", "delete"}, ran)
83-
assert.Equal(t, []string{"artifacts"}, panicked, "the panic is reported, not swallowed")
84-
}
73+
recorder := &reportingT{}
74+
teardown{
75+
{"artifacts", func() { ran = append(ran, "artifacts"); panic("boom") }},
76+
record("kubeconfig"), record("stop"), record("delete"),
77+
}.register(recorder)
78+
recorder.runCleanups()
8579

86-
func failOnPanic(t *testing.T) func(string, any) {
87-
return func(step string, v any) { assert.Fail(t, "unexpected panic", "%s: %v", step, v) }
80+
assert.Equal(t, []string{"artifacts", "kubeconfig", "stop", "delete"}, ran)
81+
assert.Len(t, recorder.reported, 1, "the panic is reported, not swallowed")
82+
assert.Contains(t, recorder.reported[0], `"artifacts" panicked`)
8883
}
8984

9085
func TestBuildScheme(t *testing.T) {
@@ -133,8 +128,6 @@ func TestDumpNamespaces(t *testing.T) {
133128
config: config{controlNamespace: "infra", namespaces: []string{"tenant"}},
134129
want: []string{"infra", "tenant", "default"},
135130
},
136-
// A suite that runs in default, or names it explicitly, would otherwise
137-
// have stern dump it twice.
138131
"repeats are dropped": {
139132
config: config{controlNamespace: "default", namespaces: []string{"tenant", "tenant"}},
140133
want: []string{"default", "tenant"},
@@ -152,12 +145,8 @@ func TestDumpNamespaces(t *testing.T) {
152145
}
153146
}
154147

155-
// The cap is what the suites spend by hand today; the deadline is what stops
156-
// one wait taking the whole package budget with it.
157148
func TestWaitBudgetStaysUnderTheCapAndTheDeadline(t *testing.T) {
158-
env := &Env{T: t}
159-
160-
budget := env.waitBudget()
149+
budget := (&Env{T: t}).waitBudget()
161150

162151
assert.Positive(t, budget)
163152
assert.LessOrEqual(t, budget, waitBudget)
@@ -174,7 +163,6 @@ func TestArtifactPath(t *testing.T) {
174163
require.NoError(t, err)
175164

176165
assert.Equal(t, filepath.Join(root, "build/_test", "cluster-TestSomething.log"), path)
177-
// The directory each suite used to build in its own init().
178166
info, err := os.Stat(filepath.Dir(path))
179167
require.NoError(t, err)
180168
assert.True(t, info.IsDir())

0 commit comments

Comments
 (0)