integration: harden ACL test ergonomics

tsic.Curl returned ("", nil) when curl exited 0 with a zero-byte body —
the usual signature of a mid-stream reset — so EventuallyWithT could
not retry. Return an error on empty body instead.

Replace the 56 inline curl + assert.Len(13) blocks with
assertCurlDockerHostname so the empty-body fix benefits every callsite
without further touch-ups.

Gate ACL waits on actual filter visibility: snapshotClientFilters +
waitForClientFilterChange ensure the new PacketFilter has reached the
client before assertions fire; SyncOption + WithPreBarrier feeds a
server-side policy-loaded check into WaitForTailscaleSyncPerUser.

Move advertise-routes mutation out of EventuallyWithT in route_test
(cmd/hi/README forbids retrying state-mutating calls). Pace the
TestNodeOnlineStatus outer loop with a Ticker, not a Sleep.
This commit is contained in:
Kristoffer Dalby 2026-05-13 13:19:49 +00:00
parent 78fd6efb38
commit dfcc96d808
6 changed files with 321 additions and 300 deletions

View file

@ -13,7 +13,6 @@ import (
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/integration/hsic"
"tailscale.com/util/dnsname"
"github.com/juanfont/headscale/integration/integrationutil"
"github.com/juanfont/headscale/integration/tsic"
"github.com/rs/zerolog/log"
@ -23,6 +22,7 @@ import (
"golang.org/x/sync/errgroup"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/types/key"
"tailscale.com/util/dnsname"
)
func TestPingAllByIP(t *testing.T) {
@ -211,7 +211,7 @@ func testEphemeralWithOptions(t *testing.T, opts ...hsic.Option) {
nodes, err := headscale.ListNodes()
assert.NoError(ct, err)
assert.Len(ct, nodes, 0, "All ephemeral nodes should be cleaned up after logout")
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second)
}, integrationutil.StatusReadyTimeout, 2*time.Second)
}
// TestEphemeral2006DeletedTooQuickly verifies that ephemeral nodes are not
@ -298,7 +298,7 @@ func TestEphemeral2006DeletedTooQuickly(t *testing.T) {
assert.NoError(ct, err)
assertPingAllWithCollect(ct, allClients, allAddrs)
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second)
}, integrationutil.HAConvergeTimeout, 2*time.Second)
// Take down all clients, this should start an expiry timer for each.
for _, client := range allClients {
@ -894,7 +894,7 @@ func TestUpdateHostnameFromClient(t *testing.T) {
}
}
}
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second)
}, integrationutil.HAConvergeTimeout, 2*time.Second)
for _, client := range allClients {
status := client.MustStatus()
@ -980,7 +980,7 @@ func TestExpireNode(t *testing.T) {
// Assert that we have the original count - self
assert.Len(ct, status.Peers(), spec.NodesPerUser-1, "Client %s should see correct number of peers", client.Hostname())
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second)
}, integrationutil.StatusReadyTimeout, 1*time.Second)
}
headscale, err := scenario.Headscale()
@ -1069,7 +1069,7 @@ func TestExpireNode(t *testing.T) {
)
}
}
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for expired node status to propagate")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for expired node status to propagate")
}
}
@ -1307,7 +1307,7 @@ func TestNodeOnlineStatus(t *testing.T) {
// Assert that we have the original count - self
assert.Len(c, status.Peers(), len(MustTestVersions)-1)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for expected peer count")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for expected peer count")
}
headscale, err := scenario.Headscale()
@ -1320,6 +1320,11 @@ func TestNodeOnlineStatus(t *testing.T) {
log.Printf("Starting online test from %v to %v", start, end)
// Pace the outer loop at one iteration per second so the
// continuous online-check does not hammer the docker daemon.
tick := time.NewTicker(time.Second)
defer tick.Stop()
for {
// Let the test run continuously for X minutes to verify
// all nodes stay connected and has the expected status over time.
@ -1384,8 +1389,7 @@ func TestNodeOnlineStatus(t *testing.T) {
}, integrationutil.ScaledTimeout(15*time.Second), 1*time.Second)
}
// Check maximum once per second
time.Sleep(time.Second)
<-tick.C
}
}