check / check (push) Canceled after 0s
The IRC relay registers a broker wait channel on each pass of its loop, and when the 100 ms ticker fired instead of a notification it looped without removing that channel. An idle IRC listener connection added ten channels a second to the broker, and all but the last stayed after the connection closed. The ticker case now removes its channel, as the HTTP long-poll does after its timer. Broker.ListenerCount returns how many wait channels a user holds, so the test can check that an idle connection holds at most one, and none after it closes. Model: opus-5-5 Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
155 lines
2.3 KiB
Go
155 lines
2.3 KiB
Go
package broker_test
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"sneak.berlin/go/neoirc/internal/broker"
|
|
)
|
|
|
|
func TestNewBroker(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
brk := broker.New()
|
|
if brk == nil {
|
|
t.Fatal("expected non-nil broker")
|
|
}
|
|
}
|
|
|
|
func TestWaitAndNotify(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
brk := broker.New()
|
|
waitCh := brk.Wait(1)
|
|
|
|
go func() {
|
|
time.Sleep(10 * time.Millisecond)
|
|
brk.Notify(1)
|
|
}()
|
|
|
|
select {
|
|
case <-waitCh:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("timeout")
|
|
}
|
|
}
|
|
|
|
func TestNotifyWithoutWaiters(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
brk := broker.New()
|
|
brk.Notify(42) // should not panic.
|
|
}
|
|
|
|
func TestRemove(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
brk := broker.New()
|
|
waitCh := brk.Wait(1)
|
|
|
|
brk.Remove(1, waitCh)
|
|
|
|
brk.Notify(1)
|
|
|
|
select {
|
|
case <-waitCh:
|
|
t.Fatal("should not receive after remove")
|
|
case <-time.After(50 * time.Millisecond):
|
|
}
|
|
}
|
|
|
|
func TestListenerCount(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
brk := broker.New()
|
|
|
|
wantCount := func(step string, userID int64, want int) {
|
|
t.Helper()
|
|
|
|
got := brk.ListenerCount(userID)
|
|
if got != want {
|
|
t.Errorf("%s: user %d: got %d, want %d",
|
|
step, userID, got, want)
|
|
}
|
|
}
|
|
|
|
wantCount("before Wait", 1, 0)
|
|
|
|
waitCh := brk.Wait(1)
|
|
brk.Wait(1)
|
|
brk.Wait(2)
|
|
|
|
wantCount("after two Waits", 1, 2)
|
|
|
|
brk.Remove(1, waitCh)
|
|
|
|
wantCount("after Remove", 1, 1)
|
|
|
|
brk.Notify(1)
|
|
|
|
wantCount("after Notify", 1, 0)
|
|
wantCount("after Notify", 2, 1)
|
|
}
|
|
|
|
func TestMultipleWaiters(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
brk := broker.New()
|
|
waitCh1 := brk.Wait(1)
|
|
waitCh2 := brk.Wait(1)
|
|
|
|
brk.Notify(1)
|
|
|
|
select {
|
|
case <-waitCh1:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("ch1 timeout")
|
|
}
|
|
|
|
select {
|
|
case <-waitCh2:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("ch2 timeout")
|
|
}
|
|
}
|
|
|
|
func TestConcurrentWaitNotify(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
brk := broker.New()
|
|
|
|
var waitGroup sync.WaitGroup
|
|
|
|
const concurrency = 100
|
|
|
|
for idx := range concurrency {
|
|
waitGroup.Add(1)
|
|
|
|
go func(uid int64) {
|
|
defer waitGroup.Done()
|
|
|
|
waitCh := brk.Wait(uid)
|
|
|
|
brk.Notify(uid)
|
|
|
|
select {
|
|
case <-waitCh:
|
|
case <-time.After(time.Second):
|
|
t.Error("timeout")
|
|
}
|
|
}(int64(idx % 10))
|
|
}
|
|
|
|
waitGroup.Wait()
|
|
}
|
|
|
|
func TestRemoveNonexistent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
brk := broker.New()
|
|
waitCh := make(chan struct{}, 1)
|
|
|
|
brk.Remove(999, waitCh) // should not panic.
|
|
}
|