Stop the slow test packages waiting (closes #263)
check / check (push) Failing after 1m9s

The slowest test packages spent their time waiting rather than testing.

db.Params gains an optional PasswordCost (zero means bcrypt.DefaultCost), and the db, ircserver and handlers tests hash at bcrypt.MinCost. A test IRC server's relay checks its queue on broker notification instead of polling. The service and handlers tests build config.Config directly and run in parallel; config exports DefaultTrustedProxies and ParseTrustedProxies for them. Fixed sleeps give way to waits for the event. Production defaults are unchanged.

What still takes time is CPU work under -race or a timeout a test is about.

Model: opus-5-5
This commit was merged in pull request #267.
This commit is contained in:
clawbot committed 2026-11-11 01:15:43 +01:00
1 parent 847f5c6fec
commit ed5125606a
15 files changed
+558 -261

No files matched your search

+129 -89
View File
@@ -1,7 +1,3 @@
// Tests use a global viper instance for configuration,
// making parallel execution unsafe.
//
//nolint:paralleltest
package service_test
import (
@@ -59,28 +55,12 @@ func newTestEnv(t *testing.T) *testEnv {
}
},
logger.New,
func(
lifecycle fx.Lifecycle,
globs *globals.Globals,
log *logger.Logger,
) (*config.Config, error) {
cfg, err := config.New(
lifecycle, config.Params{ //nolint:exhaustruct
Globals: globs, Logger: log,
},
)
if err != nil {
return nil, fmt.Errorf(
"test config: %w", err,
)
func() *config.Config {
return &config.Config{
DBURL: dbURL,
OperName: "admin",
OperPassword: "secret",
}
cfg.DBURL = dbURL
cfg.Port = 0
cfg.OperName = "admin"
cfg.OperPassword = "secret"
return cfg, nil
},
func(
lifecycle fx.Lifecycle,
@@ -136,6 +116,8 @@ func createSession(
// environment starts on an empty database while another one
// is still open.
func TestEachTestEnvHasItsOwnDatabase(t *testing.T) {
t.Parallel()
ctx := t.Context()
first := newTestEnv(t)
@@ -146,6 +128,8 @@ func TestEachTestEnvHasItsOwnDatabase(t *testing.T) {
}
func TestFanOut(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -173,6 +157,8 @@ func TestFanOut(t *testing.T) {
}
func TestJoinChannel(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -213,6 +199,8 @@ func TestJoinChannel(t *testing.T) {
}
func TestPartChannel(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -313,6 +301,8 @@ func joinConcurrently(
// concurrent JOINs of a +l 2 channel with one member, one
// gets in and the other gets ERR_CHANNELISFULL.
func TestConcurrentJoinsRespectUserLimit(t *testing.T) {
t.Parallel()
const userLimit = 2
env := newTestEnv(t)
@@ -375,6 +365,8 @@ func TestConcurrentJoinsRespectUserLimit(t *testing.T) {
// two concurrent JOINs that create a channel, exactly one
// makes its session the operator.
func TestConcurrentFirstJoinsMakeOneOperator(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -426,6 +418,8 @@ func TestConcurrentFirstJoinsMakeOneOperator(t *testing.T) {
// either joins a channel that still exists or gets
// ERR_NOSUCHCHANNEL, and never fails otherwise.
func TestJoinConcurrentWithLastPart(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -491,6 +485,8 @@ func TestJoinConcurrentWithLastPart(t *testing.T) {
// quits, gets in only once the channel is deleted, into a
// new channel of that name with no key.
func TestJoinConcurrentWithLastQuit(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -569,81 +565,95 @@ const quitCrowdSize = 10
// ahead of that JOIN. The quitting session is also on a
// channel with quitCrowdSize other sessions, made before the
// watcher's channel, so that its QUIT reaches the watcher
// last.
// last. Each trial runs on a database of its own.
func TestQuitQueuedAheadOfNewNickHoldersJoin(t *testing.T) {
env := newTestEnv(t)
ctx := t.Context()
t.Parallel()
for trial := range concurrentJoinTrials {
nick := fmt.Sprintf("reused%d", trial)
crowd := fmt.Sprintf("#crowd%d", trial)
shared := fmt.Sprintf("#shared%d", trial)
quitterSID := createSession(ctx, t, env.db, nick)
t.Run(fmt.Sprintf("trial %d", trial), func(t *testing.T) {
t.Parallel()
for _, channel := range []string{crowd, shared} {
_, err := env.svc.JoinChannel(ctx, quitterSID, 0, nick, channel, "")
if err != nil {
t.Fatalf("join %s: %v", channel, err)
}
checkQuitQueuedAheadOfJoin(t, newTestEnv(t))
})
}
}
// checkQuitQueuedAheadOfJoin runs one trial of
// TestQuitQueuedAheadOfNewNickHoldersJoin on env.
func checkQuitQueuedAheadOfJoin(t *testing.T, env *testEnv) {
t.Helper()
const (
nick = "reused"
crowd = "#crowd"
shared = "#shared"
watcherNick = "watcher"
)
ctx := t.Context()
quitterSID := createSession(ctx, t, env.db, nick)
for _, channel := range []string{crowd, shared} {
_, err := env.svc.JoinChannel(ctx, quitterSID, 0, nick, channel, "")
if err != nil {
t.Fatalf("join %s: %v", channel, err)
}
}
for member := range quitCrowdSize {
memberNick := fmt.Sprintf("crowd%dx%d", trial, member)
memberSID := createSession(ctx, t, env.db, memberNick)
for member := range quitCrowdSize {
memberNick := fmt.Sprintf("crowd%d", member)
memberSID := createSession(ctx, t, env.db, memberNick)
_, err := env.svc.JoinChannel(ctx, memberSID, 0, memberNick, crowd, "")
if err != nil {
t.Fatalf("join %s: %v", memberNick, err)
}
_, err := env.svc.JoinChannel(ctx, memberSID, 0, memberNick, crowd, "")
if err != nil {
t.Fatalf("join %s: %v", memberNick, err)
}
}
watcherNick := fmt.Sprintf("watcher%d", trial)
watcherSID, watcherClientID, _, err := env.db.CreateSession(
ctx, watcherNick, watcherNick, "localhost", "127.0.0.1",
)
if err != nil {
t.Fatalf("create session %s: %v", watcherNick, err)
}
watcherSID, watcherClientID, _, err := env.db.CreateSession(
ctx, watcherNick, watcherNick, "localhost", "127.0.0.1",
_, err = env.svc.JoinChannel(ctx, watcherSID, 0, watcherNick, shared, "")
if err != nil {
t.Fatalf("join %s: %v", watcherNick, err)
}
_, lastQueueID, err := env.db.PollMessages(ctx, watcherClientID, 0, 0)
if err != nil {
t.Fatalf("poll: %v", err)
}
errs := runConcurrently(
func() error {
env.svc.BroadcastQuit(ctx, quitterSID, nick, "")
return nil
},
func() error {
return takeNickAndJoin(ctx, env, nick, shared)
},
)
if errs[1] != nil {
t.Fatalf("new session: %v", errs[1])
}
msgs, _, err := env.db.PollMessages(
ctx, watcherClientID, lastQueueID, 0,
)
if err != nil {
t.Fatalf("poll: %v", err)
}
commands := commandsFrom(msgs, nick)
if !slices.Equal(commands, []string{irc.CmdQuit, irc.CmdJoin}) {
t.Errorf(
"watcher got %v from %s, want QUIT then JOIN",
commands, nick,
)
if err != nil {
t.Fatalf("create session %s: %v", watcherNick, err)
}
_, err = env.svc.JoinChannel(ctx, watcherSID, 0, watcherNick, shared, "")
if err != nil {
t.Fatalf("join %s: %v", watcherNick, err)
}
_, lastQueueID, err := env.db.PollMessages(ctx, watcherClientID, 0, 0)
if err != nil {
t.Fatalf("poll: %v", err)
}
errs := runConcurrently(
func() error {
env.svc.BroadcastQuit(ctx, quitterSID, nick, "")
return nil
},
func() error {
return takeNickAndJoin(ctx, env, nick, shared)
},
)
if errs[1] != nil {
t.Fatalf("trial %d: new session: %v", trial, errs[1])
}
msgs, _, err := env.db.PollMessages(
ctx, watcherClientID, lastQueueID, 0,
)
if err != nil {
t.Fatalf("poll: %v", err)
}
commands := commandsFrom(msgs, nick)
if !slices.Equal(commands, []string{irc.CmdQuit, irc.CmdJoin}) {
t.Errorf(
"trial %d: watcher got %v from %s, want QUIT then JOIN",
trial, commands, nick,
)
}
}
}
@@ -691,6 +701,8 @@ func commandsFrom(msgs []db.IRCMessage, nick string) []string {
// into a +i channel uses the invite up, so that a JOIN
// after leaving gets ERR_INVITEONLYCHAN.
func TestJoinUsesUpInvite(t *testing.T) {
t.Parallel()
const channel = "#club"
env := newTestEnv(t)
@@ -736,6 +748,8 @@ func TestJoinUsesUpInvite(t *testing.T) {
// session's nick, username and hostname refuses its JOIN
// with ERR_BANNEDFROMCHAN.
func TestJoinRefusedByHostmaskBan(t *testing.T) {
t.Parallel()
const channel = "#club"
env := newTestEnv(t)
@@ -774,6 +788,8 @@ func TestJoinRefusedByHostmaskBan(t *testing.T) {
// keeps a channel other members are still on and deletes
// the channel when its last member parts.
func TestPartDeletesChannelOnlyWhenEmpty(t *testing.T) {
t.Parallel()
const channel = "#room"
env := newTestEnv(t)
@@ -821,6 +837,8 @@ func TestPartDeletesChannelOnlyWhenEmpty(t *testing.T) {
// TestKickOfLastMemberDeletesChannel checks that a KICK
// that leaves a channel with no members deletes it.
func TestKickOfLastMemberDeletesChannel(t *testing.T) {
t.Parallel()
const channel = "#room"
env := newTestEnv(t)
@@ -845,6 +863,8 @@ func TestKickOfLastMemberDeletesChannel(t *testing.T) {
}
func TestSendChannelMessage(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -892,6 +912,8 @@ func TestSendChannelMessage(t *testing.T) {
}
func TestBroadcastQuit(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -929,6 +951,8 @@ func TestBroadcastQuit(t *testing.T) {
// error as a DM to an unknown nick. The trigger deletes the
// target's session just before the DM is stored.
func TestSendDirectMessageTargetQuits(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -960,6 +984,8 @@ func TestSendDirectMessageTargetQuits(t *testing.T) {
// trigger deletes the channel just before the message is
// stored.
func TestSendChannelMessageChannelDeleted(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -991,6 +1017,8 @@ func TestSendChannelMessageChannelDeleted(t *testing.T) {
}
func TestSendChannelMessage_Moderated(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -1042,6 +1070,8 @@ func TestSendChannelMessage_Moderated(t *testing.T) {
// not spend its hashcash stamp: the operator can then send
// the same message with it.
func TestRefusedChannelMessageLeavesStampUnspent(t *testing.T) {
t.Parallel()
const (
channel = "#paid"
bits = 2
@@ -1067,6 +1097,8 @@ func TestRefusedChannelMessageLeavesStampUnspent(t *testing.T) {
for _, refusal := range refusals {
t.Run(refusal.name, func(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -1119,6 +1151,8 @@ func TestRefusedChannelMessageLeavesStampUnspent(t *testing.T) {
}
func TestQueryUserMode(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -1169,6 +1203,8 @@ func TestQueryUserMode(t *testing.T) {
}
func TestUserhostReply(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
ctx := t.Context()
@@ -1274,10 +1310,14 @@ func applyUserModeTests() []applyUserModeTest {
}
func TestApplyUserMode(t *testing.T) {
t.Parallel()
env := newTestEnv(t)
for i, test := range applyUserModeTests() {
t.Run(test.modeStr, func(t *testing.T) {
t.Parallel()
ctx := t.Context()
sid := createSession(
ctx, t, env.db, fmt.Sprintf("user%d", i),