Skip to content

Add WebSocket hub lifecycle integration tests#39

Open
spektre-labs wants to merge 3 commits into
jackjin1997:mainfrom
spektre-labs:bounty/issue-1
Open

Add WebSocket hub lifecycle integration tests#39
spektre-labs wants to merge 3 commits into
jackjin1997:mainfrom
spektre-labs:bounty/issue-1

Conversation

@spektre-labs

@spektre-labs spektre-labs commented Jul 7, 2026

Copy link
Copy Markdown

Bounty delivery — TentOfTrials #1 ($45)

Issue: #1
Deliverable: market/ws/server_test.go (WebSocket hub lifecycle tests)
Win-EV: $45 · 11 comments · harness rank #7

What ships

Go hub lifecycle integration tests covering:

  • register / unregister — client count transitions + send channel close on disconnect
  • broadcast fan-out — all connected clients receive payload
  • Slow-client eviction — full send buffer triggers cleanup during broadcast (RLock path)
  • Idempotent unregister for unknown clients
  • Concurrent register/unregister churn leaves hub empty

Fire

python runtime/external_exec_kernel.py tick --fire
# or target this deliverable:
python runtime/github_pr_stage.py bounty TentOfTrials_1 --fire

σ: tests drafted from live market/ws/server.go · payout requires maintainer merge.

Summary by CodeRabbit

  • Tests
    • Added coverage for websocket hub client registration, unregistration, broadcasting, and cleanup behavior.
    • Verified that messages reach all connected clients and that slow clients are removed without affecting others.
    • Confirmed safe handling of unknown client removal and concurrent connect/disconnect activity.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a new test file, market/ws/server_test.go, containing unit tests for the ws.Hub type. It includes helper functions for logger/client setup and polling, and five test functions covering registration lifecycle, broadcast fanout, slow-client eviction, unknown-client unregistration safety, and concurrent register/unregister operations.

Changes

Hub Test Suite

Layer / File(s) Summary
Test helpers and setup
market/ws/server_test.go
Adds imports and helper functions to create loggers, test clients with buffered channels, start the hub, and poll hub state under lock.
Registration lifecycle and unknown client safety
market/ws/server_test.go
Tests verify that unregistering a client removes it and closes its send channel, and unregistering an unknown client leaves the hub unchanged.
Broadcast fanout and slow client eviction
market/ws/server_test.go
Tests verify broadcast messages reach all connected clients and that a slow client is evicted (channel closed) while fast clients remain connected.
Concurrent register/unregister churn
market/ws/server_test.go
Test spawns many goroutines performing concurrent registration/unregistration and asserts the hub ends up empty.

Estimated code review effort: 2 (Simple) | ~12 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the change, but it misses the required Summary, Changes, Testing, and Checklist sections. Rewrite the PR description to follow the template, including Summary, Changes, Testing commands/results, and the checklist items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change and clearly states the added WebSocket hub tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
market/ws/server_test.go (3)

142-152: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Replace time.Sleep with waitUntil for consistency and reliability.

Line 148 uses time.Sleep(30 * time.Millisecond) to wait for the unregister to be processed. This is inherently flaky. Since the expected state is simply clientCount(h) == 0, waitUntil is a direct fit.

♻️ Proposed refactor
-	h.unregister <- ghost
-	time.Sleep(30 * time.Millisecond)
-	if clientCount(h) != 0 {
-		t.Fatalf("ghost unregister mutated client set: count=%d", clientCount(h))
-	}
+	h.unregister <- ghost
+	waitUntil(t, func() bool { return clientCount(h) == 0 }, 200*time.Millisecond)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@market/ws/server_test.go` around lines 142 - 152, The test
TestHub_UnregisterUnknownClientIsSafe uses a fixed sleep to wait for unregister
processing, which is flaky and inconsistent with the rest of the suite. Replace
the time.Sleep call with waitUntil in this test, using the expected
clientCount(h) == 0 condition after sending on h.unregister, so the check waits
deterministically for Hub state to settle.

154-174: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Replace time.Sleep with waitUntil for the final hub-empty assertion.

Line 170 uses time.Sleep(80 * time.Millisecond) to wait for all unregister operations to drain. This is the same pattern already abstracted by waitUntil and is susceptible to flakiness under load.

♻️ Proposed refactor
 	wg.Wait()
-	time.Sleep(80 * time.Millisecond)
-	if n := clientCount(h); n != 0 {
-		t.Fatalf("expected empty hub after churn, got %d", n)
-	}
+	waitUntil(t, func() bool { return clientCount(h) == 0 }, 300*time.Millisecond)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@market/ws/server_test.go` around lines 154 - 174, The final hub-empty check
in TestHub_ConcurrentRegisterUnregister is relying on a fixed time.Sleep, which
can be flaky under load. Replace the sleep-based wait with the existing
waitUntil helper so the test waits for clientCount(h) to reach zero before
asserting, using TestHub_ConcurrentRegisterUnregister and clientCount as the key
symbols to update.

105-140: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Replace time.Sleep with waitUntil polling to reduce flakiness.

Line 118 uses a fixed time.Sleep(50 * time.Millisecond) to wait for broadcast processing. On slow CI runners this may not be enough, and on fast machines it wastes time. The rest of the file already uses waitUntil for the same pattern. Consider polling for the expected state (slow removed, fast present) instead.

♻️ Proposed refactor
-	h.broadcast <- []byte("pressure")
-	time.Sleep(50 * time.Millisecond)
+	h.broadcast <- []byte("pressure")
+	waitUntil(t, func() bool {
+		h.mu.RLock()
+		_, slowPresent := h.clients[slow]
+		_, fastPresent := h.clients[fast]
+		h.mu.RUnlock()
+		return !slowPresent && fastPresent
+	}, 300*time.Millisecond)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@market/ws/server_test.go` around lines 105 - 140, The broadcast eviction test
uses a fixed sleep to wait for Hub cleanup, which is flaky and unnecessary. In
TestHub_BroadcastEvictsSlowClient, replace the time.Sleep-based pause after
h.broadcast with waitUntil polling for the expected Hub state, using h.clients
and the slow/fast client presence checks already in the test. Keep the
assertions on slow client removal, fast client retention, and closed send
channel, but only run them after the polling condition is satisfied.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@market/ws/server_test.go`:
- Around line 132-139: The slow client closure assertion in the broadcast test
is checking `slow.send` before draining its buffered prefill message, so the
receive can still վերադարձ `ok=true` even after close. Update the test around
`slow.send` to first consume the buffered item (the prefilled message) and then
perform the closed-channel check, using the existing `slow.send` setup and
broadcast flow in the same test to verify the channel is actually closed.

---

Nitpick comments:
In `@market/ws/server_test.go`:
- Around line 142-152: The test TestHub_UnregisterUnknownClientIsSafe uses a
fixed sleep to wait for unregister processing, which is flaky and inconsistent
with the rest of the suite. Replace the time.Sleep call with waitUntil in this
test, using the expected clientCount(h) == 0 condition after sending on
h.unregister, so the check waits deterministically for Hub state to settle.
- Around line 154-174: The final hub-empty check in
TestHub_ConcurrentRegisterUnregister is relying on a fixed time.Sleep, which can
be flaky under load. Replace the sleep-based wait with the existing waitUntil
helper so the test waits for clientCount(h) to reach zero before asserting,
using TestHub_ConcurrentRegisterUnregister and clientCount as the key symbols to
update.
- Around line 105-140: The broadcast eviction test uses a fixed sleep to wait
for Hub cleanup, which is flaky and unnecessary. In
TestHub_BroadcastEvictsSlowClient, replace the time.Sleep-based pause after
h.broadcast with waitUntil polling for the expected Hub state, using h.clients
and the slow/fast client presence checks already in the test. Keep the
assertions on slow client removal, fast client retention, and closed send
channel, but only run them after the polling condition is satisfied.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2c627b13-6deb-4d22-9cd9-cc40f6bd1958

📥 Commits

Reviewing files that changed from the base of the PR and between 1462fe7 and d60bb6e.

📒 Files selected for processing (1)
  • market/ws/server_test.go

Comment thread market/ws/server_test.go
Comment on lines +132 to +139
select {
case _, ok := <-slow.send:
if ok {
t.Fatal("slow client send channel should be closed")
}
default:
t.Fatal("expected closed slow send channel")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Slow client send channel check will fail due to buffered "prefill" message.

The slow client's buffer (size 1) is pre-filled with "prefill" at line 110. When broadcast closes slow.send, the buffered item remains readable. A receive on a closed channel with pending items returns the item with ok=true, so the check at line 133-135 will hit t.Fatal("slow client send channel should be closed") instead of confirming closure.

🐛 Proposed fix: drain the channel before checking closure
 	select {
-	case _, ok := <-slow.send:
-		if ok {
-			t.Fatal("slow client send channel should be closed")
-		}
-	default:
-		t.Fatal("expected closed slow send channel")
+	case v, ok := <-slow.send:
+		if ok && string(v) != "prefill" {
+			t.Fatal("unexpected buffered message in slow send channel")
+		}
+		if !ok {
+			break
+		}
+		// drain remaining buffered item, next read should confirm closure
+		if _, ok := <-slow.send; ok {
+			t.Fatal("slow client send channel should be closed after draining")
+		}
+	default:
+		t.Fatal("expected closed slow send channel")
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
select {
case _, ok := <-slow.send:
if ok {
t.Fatal("slow client send channel should be closed")
}
default:
t.Fatal("expected closed slow send channel")
}
select {
case v, ok := <-slow.send:
if ok && string(v) != "prefill" {
t.Fatal("unexpected buffered message in slow send channel")
}
if !ok {
break
}
// drain remaining buffered item, next read should confirm closure
if _, ok := <-slow.send; ok {
t.Fatal("slow client send channel should be closed after draining")
}
default:
t.Fatal("expected closed slow send channel")
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@market/ws/server_test.go` around lines 132 - 139, The slow client closure
assertion in the broadcast test is checking `slow.send` before draining its
buffered prefill message, so the receive can still վերադարձ `ok=true` even after
close. Update the test around `slow.send` to first consume the buffered item
(the prefilled message) and then perform the closed-channel check, using the
existing `slow.send` setup and broadcast flow in the same test to verify the
channel is actually closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant