Repository navigation
feat(e2e): add internal/harness and move fluentbit-multitenant onto it - #2304
Conversation
The builders landed in kube-logging#2299 with tests that assert the shape this package chose, not that the choice matches what common creates. A suite moving onto them could stop testing what it used to and still pass.This records what common.LoggingInfra, LoggingTenant and LoggingRoute hand to Create and requires the builders to equal it, object for object and in order. It passes as written, so the builders are a faithful stand-in today. Signed-off-by: Vyncint Ng <[email protected]>
Start marks the test parallel, builds the scheme, brings up the cluster, installs the operator, creates the extra namespaces and registers teardown. Teardown keeps the order common.WithCluster had: artifacts, kubeconfig, context cancel with the bounded Start check, delete cluster. t.Cleanup is LIFO, so they are registered back to front.Per suite this replaces an init() building build/_test, the beforeCleanup closure that dumps logs and collects coverage, and the scheme closure. Signed-off-by: Vyncint Ng <[email protected]>
176 lines to 93, and the first consumer internal/fixture has had.The buffer stays written out here. This is the only suite that leaves Type unset; taking fixture.Buffer's "file" default would switch both outputs from memory buffering, so the suite would pass for a different reason. Signed-off-by: Vyncint Ng <[email protected]>
|
Good shape overall, and §5.5 is the right place to stop for review. Three things.
On the verb, though: The teardown order is the one thing worth a unit test, and it is the one thing untested. Worth extracting the registration so it takes a recorder, then asserting two things: the order, and that the cluster delete still runs when an earlier step fails. The cluster and helm paths genuinely need a real cluster, so leaving those to the e2e run is right.
The cost is that it pins |
|
One more, and it is the same argument as the lifecycle one: the readiness ladder should move onto the harness too. running := []struct{ what string; cond func() bool }{ ... }
require.Eventually(t, func() bool { for _, step := range running { ... } })That shape is in 14 of the 13 suites' tests today ( Three things it should fix while moving, not just relocate: Conditions should be values, not pre-bound closures. The harness should own the budget. The census counts 17 hand-written Name the outstanding step in the failure, not just the log. On timeout Rough target, for discussion rather than as a spec: env.WaitForRunning(
wait.Operator(release),
wait.Producer(producerLabels),
wait.Aggregator(nsInfra),
wait.Aggregator(nsTenant),
)Worth settling now, since §5.5 exists to get this API right before the other twelve suites copy it. |
AnyPodShouldBeRunning takes the testing.T and the client at construction, so a condition cannot be declared as a reusable value: every suite rebuilds its own. Condition carries neither, and whoever evaluates it supplies both, so Operator, Producer and Aggregator become things a suite references. Signed-off-by: Vyncint Ng <[email protected]>
The four Cleanup calls were registered back to front inline, with a comment saying the order was load-bearing and nothing checking it. Go also abandons the remaining cleanups at the first panic, so a log dump that panicked would leave the kind cluster running for the rest of the job.The order is one list now, registered from it, and each step recovers so a failure in an early one still reaches the delete. Both are covered without a cluster: registering forward reverses the observed order, and removing the recover lets the panic through before the delete runs. Signed-off-by: Vyncint Ng <[email protected]>
Config was a struct literal at what will become 17 call sites, and adding an option meant touching the struct. New(t).With...().Start() reads better and takes one line per option. Start keeps its name: it creates a cluster and installs the operator, which Build would not suggest.WaitForRunning and WaitForReceiverLogs move the readiness ladder off the suites. The budget comes from the harness rather than a hand-written 5m/3s pair, capped by what is left of the binary's deadline so one wait cannot spend the package's. On timeout the assertion names the condition still outstanding instead of reporting Condition never satisfied.The receiver tail is no longer echoed on every poll; the archived cluster dump already carries it. Signed-off-by: Vyncint Ng <[email protected]>
It makes common the oracle, so common cannot be deleted while it stands. That is fine as scaffolding and not fine as a permanent fixture, so the condition for removing it is written down next to it. Signed-off-by: Vyncint Ng <[email protected]>
The previous run failed in three suites this branch does not touch: elasticsearch-multiversion on its own readiness budget, one on "docker rm" during kind teardown and one on kubeadm during cluster creation. Suite time was 1.6x the previous run on this branch and 3.4x on one of them, so the runner was degraded. fluentbit-multitenant passed on it. Signed-off-by: Vyncint Ng <[email protected]>
|
Four things, all on the new API rather than the behaviour. Drop the display string from the conditions. env.WaitForRunning(
wait.Operator(release),
wait.Producer(producerLabels),
wait.FluentdAggregator(nsInfra),
wait.FluentdAggregator(nsTenant),
)
That also removes the comment you could not parse. This one:
is hard to read because
type teardown []step
func (td teardown) register(t *testing.T) {
for _, s := range slices.Backward(td) {
t.Cleanup(func() {
defer func() {
if v := recover(); v != nil {
assert.Fail(t, fmt.Sprintf("teardown step %q panicked: %v", s.name, v))
}
}()
s.run()
})
}
}One parameter, no seams invented for the test. Both current tests still work: register inside a More generally, the function passing through this file is a symptom worth watching: Comments are over the line again. Same note as before: a terse why only, and only where the code cannot say it. Several of the new ones restate the next statement, and two of them (the aggregator one above, the LIFO one) are compensating for names rather than explaining a trade-off. |
PodRunning("the operator", ...) made every call site pass prose next to a
selector, and no two suites would word it the same. The constructors own both
now, so a call site passes only what varies and the failure reads
"still waiting for fluentd aggregator in infra".Aggregator is split in two: this repo has a fluentd one and a syslog-ng one,
and the name was carrying a comment to say which. PodRunning stays for
one-offs.
Signed-off-by: Vyncint Ng <[email protected]>
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 <[email protected]>
Dropped the ones restating the signature above them or the statement below, and the two compensating for a name rather than explaining a trade-off. 47 lines to 27 in harness.go, 76 to 49 across the series. Signed-off-by: Vyncint Ng <[email protected]>
internal/fixturelanded in #2299 and nothing has imported it since — 655 lines carried by the module with no consumer. This adds the lifecycle package the suites were meant to sit on, and moves one suite onto both.fluentbit-multitenant, 176 lines to 66.Startmarks the test parallel, builds the scheme, brings up the cluster, installs the operator, creates the extra namespaces and registers teardown. What that retires per suite, still present in the other twelve: aninit()buildingbuild/_test(12 files), thebeforeCleanupclosure that dumps logs and collects coverage (12), the scheme closure (102AddToSchemelines in total),context.Background()(19) andcommon.Initialize(16).Net lines are up here — the harness and its tests against 110 removed from one suite. It only pays from the second suite on, which is why one moves rather than thirteen.
Waiting
wait.Conditioncarries notesting.Tand no client, so a condition is a value a suite names rather than one it assembles; the harness supplies both. The budget comes from the harness —min(5m, remaining − 90s)— so the numbers the suites spend today do not move, but a wait cannot take the package's whole budget with it. On timeout the assertion names what was outstanding:still waiting for the aggregator in tenant, rather thanCondition never satisfied.The receiver tail is no longer echoed on every poll. It was 24 identical banners in a local run, and the archived cluster dump already carries it.
Teardown
Writing the test for the ordering turned up a second problem. Go abandons the remaining cleanups at the first panic, so a log dump that panicked would have left the kind cluster running for the rest of the job. The order is one list now, registered from it, and each step recovers so an early failure still reaches the delete.
Both are covered without a cluster. Registering forward reverses the observed order; removing the recover lets the panic through before the delete runs.
Scope
Only
internal/harness, so it importscommonandcommon/setupfor now. Givingcommon/cluster.goandcommon/setuptheir own packages is a move with no behaviour in it, and doing it now would leave two implementations live while twelve suites still use the old one. That layering inverts whencommon/goes.Semantic drift
fluentbit-multitenantis the only suite whose buffer leavesTypeunset; the other twelve set"file", and so doesfixture.Buffer. Taking the fixture default would have switched both outputs from memory to file buffering — the suite would still pass, for a different reason. The literal stays in the suite. Worth deciding separately whether"file"was meant all along.Nothing pinned that
fixtureproduces whatcommondoes: the tests added with it assert the shape that package chose, not that the choice matches the live path.TestBuildersMatchCommonrecords whatcommon.LoggingInfra,LoggingTenantandLoggingRoutehand toCreateand requires the builders to equal it, object for object and in order. It passes as written, so this migration changes no object.That test is scaffolding, not a fixture: it makes
commonthe oracle, socommoncannot be deleted while it stands. The condition for removing it — the last suite that still usescommon— is written next to it.Verification
make checkpasses,golangci-lint run --max-same-issues=0 --max-issues-per-linter=0ine2e/reports 0 issues, and all seven commits build, vet and test on their own. The migrated suite passes against a real cluster:make test-e2e-nodeps E2E_TEST=fluentbit-multitenant, 67.6s, withtag_infraandtag_tenantboth reaching the receiver.Negative controls: moving the tenancy fluentd memory request from 50M to 100M fails
TestBuildersMatchCommonon both infra and tenant; dropping the namespace de-duplication failsrepeats are dropped; the two teardown ones above. Harness unit tests are pure Go, no stub binaries.