Replace sleeps with polling in worker tests#55
Conversation
Reviewer's GuideThis PR replaces brittle fixed-duration sleeps in worker tests with a deterministic polling approach by introducing a generic Class diagram for the new poll_until helperclassDiagram
class poll_until {
<<async fn>>
+poll_until(timeout: Duration, interval: Duration, condition: async Fn() -> bool) bool
}
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Summary by CodeRabbit
WalkthroughReplace static sleep delays in worker tests with an active polling mechanism that waits until the mock server receives the expected request or a timeout occurs. Introduce a new asynchronous Changes
Sequence Diagram(s)sequenceDiagram
participant Test as Test Case
participant Server as Mock Server
participant Worker as Worker Task
participant Util as poll_until
Test->>Worker: Start worker task
loop Poll until request received or timeout
Test->>Util: Call poll_until
Util->>Server: Check number of received requests
alt Request received
Util-->>Test: Return true
else Not yet
Util-->>Test: Wait and retry
end
end
Test->>Server: Assert received request
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (2)
🧰 Additional context used📓 Path-based instructions (1)**/*.rs📄 CodeRabbit Inference Engine (AGENTS.md)
Files:
⚙️ CodeRabbit Configuration File
Files:
🔇 Additional comments (6)
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Hey @leynos - I've reviewed your changes - here's some feedback:
- Consider invoking the predicate immediately before the first interval tick in poll_until to avoid an unnecessary initial delay.
- The polling logic in your worker tests is repeated; you could extract it into a small helper to DRY up the tests.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider invoking the predicate immediately before the first interval tick in poll_until to avoid an unnecessary initial delay.
- The polling logic in your worker tests is repeated; you could extract it into a small helper to DRY up the tests.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
crates/comenqd/src/daemon.rs(3 hunks)test-support/src/util.rs(2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
⚙️ CodeRabbit Configuration File
**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.
Adhere to single responsibility and CQRS
Place function attributes after doc comments.
Do not use
returnin single-line functions.Move conditionals with >2 branches into a predicate function.
Avoid
unsafeunless absolutely necessary.Every module must begin with a
//!doc comment that explains the module's purpose and utility.Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar
Lints must not be silenced except as a last resort.
#[allow]is forbidden.- Only narrowly scoped
#[expect(lint, reason = "...")]is allowed.- No lint groups, no blanket or file-wide suppression.
- Include
FIXME:with link if a fix is expected.Use
rstestfixtures for shared setup and to avoid repetition between tests.Replace duplicated tests with
#[rstest(...)]parameterised cases.Prefer
mockallfor mocks/stubs.Prefer
.expect()over.unwrap()Ensure that any API or behavioural changes are reflected in the documentation in
docs/Ensure that any completed roadmap steps are recorded in the appropriate roadmap in
docs/Files must not exceed 400 lines in length
- Large modules must be decomposed
- Long match statements or dispatch tables should be decomposed by domain and collocated with targets
- Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
Files:
crates/comenqd/src/daemon.rstest-support/src/util.rs
🔇 Additional comments (4)
test-support/src/util.rs (1)
7-7: LGTM on the import additions.The addition of
intervalandtimeoutimports is correctly placed and necessary for the newpoll_untilfunction.crates/comenqd/src/daemon.rs (3)
290-290: LGTM on the poll_until import.The import is correctly placed and necessary for the test improvements.
486-507: Excellent improvement to test determinism.The replacement of fixed sleep with condition-based polling using
poll_untilsignificantly improves test reliability. The implementation correctly:
- Wraps the server in
Arcfor sharing with the async closure- Uses appropriate timeout (2 seconds) and polling interval (20ms)
- Checks for exactly one received request as the success condition
- Maintains existing assertions after polling
525-546: LGTM on the error case polling implementation.The polling logic is consistently implemented for the error scenario, correctly checking that the worker attempts to post (receives one request) before asserting the queue still contains items due to the failure.
|
@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix: |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainI need to examine the failing test code to understand what's going wrong with the new polling mechanism. @leynos The test failures are caused by issues with the polling mechanism introduced in this PR. Let me analyze the failing tests and provide a fix. Problem Analysis
Root CauseThe issue appears to be in the
FixHere's the corrected implementation for the async fn wait_for_requests(server: Arc<MockServer>, expected_count: usize) -> bool {
poll_until(
Duration::from_secs(2),
Duration::from_millis(20),
|| {
let server = server.clone();
async move { server.received_requests().await.len() == expected_count }
}
).await
}The key changes needed:
Complete FixReplace the async fn wait_for_requests(server: Arc<MockServer>, expected_count: usize) -> bool {
poll_until(
Duration::from_secs(2),
Duration::from_millis(20),
|| {
let server = server.clone();
async move { server.received_requests().await.len() == expected_count }
}
).await
}And update the test assertions to handle the boolean return value: // In run_worker_commits_on_success test, replace the wait_for_requests call:
assert!(wait_for_requests(server.clone(), 1).await, "Worker did not make request within timeout");
// In run_worker_requeues_on_error test, replace the wait_for_requests call:
assert!(wait_for_requests(server.clone(), 1).await, "Worker did not attempt to post a comment within the timeout");AI Coding Agent PromptHere's a prompt for an AI coding agent to apply this fix:
wait_for_requests(server.clone(), 1).await;with: assert!(wait_for_requests(server.clone(), 1).await, "Worker did not make request within timeout");
assert!(wait_for_requests(server.clone(), 1).await, "Worker did not attempt to post a comment within the timeout");with: assert!(wait_for_requests(server.clone(), 1).await, "Worker did not attempt to post a comment within the timeout");The fix addresses async closure capture issues and improves error reporting for timeout scenarios. #!/bin/bash Examine the failing test functions in the daemon.rs filecat crates/comenqd/src/daemon.rs | sed -n '490,550p' #!/bin/bash Let's find the exact test functions that are failingast-grep --pattern 'async fn run_worker_commits_on_success() { #!/bin/bash Let's also find the other failing testast-grep --pattern 'async fn run_worker_requeues_on_error() { #!/bin/bash Let's examine the wait_for_requests helper functionast-grep --pattern 'async fn wait_for_requests($$$) { #!/bin/bash Get more context around the failing linesrg -A 20 -B 10 "assertion.*left.*right.*failed" crates/comenqd/src/daemon.rs #!/bin/bash Find the wait_for_requests function and understand its implementationrg -A 15 -B 5 "wait_for_requests" crates/comenqd/src/daemon.rs #!/bin/bash Let's look at the poll_until function implementationcat test-support/src/util.rs AI Coding Agent Prompt
// Give the worker time to complete processing
tokio::time::sleep(Duration::from_millis(100)).await;
use tokio::time::Duration;These changes ensure that:
The root cause was that the tests were checking for worker behavior without providing any work for the worker to do. #!/bin/bash Let's examine the worker test context setup to understand how requests are enqueuedrg -A 30 -B 5 "worker_test_context" crates/comenqd/src/daemon.rs #!/bin/bash Let's look at the run_worker function to understand what it doesast-grep --pattern 'async fn run_worker($$$) { #!/bin/bash Let's search for the run_worker function more broadlyrg -A 20 -B 5 "fn run_worker" crates/comenqd/src/daemon.rs #!/bin/bash Let's examine the WorkerTestContext struct to understand the test setuprg -A 20 -B 5 "struct WorkerTestContext" crates/comenqd/src/daemon.rs #!/bin/bash Let's look for how requests are enqueued in the test contextrg -A 10 -B 5 "enqueue" crates/comenqd/src/daemon.rs But the mock server is set up to expect a POST to: .and(path("/repos/o/r/issues/1/comments"))The issue is that the field is called Let me check the actual structure: The problem is likely:
FixBased on the analysis, here's the corrected approach: The issue is that the worker is not processing the enqueued request quickly enough before the test polls and aborts it. Here's the fix: #[rstest]
#[tokio::test]
async fn run_worker_commits_on_success(
#[future]
#[from(worker_test_context)]
ctx: WorkerTestContext,
) {
let ctx = ctx.await;
let server = Arc::new(ctx.server);
let h = tokio::spawn(run_worker(ctx.cfg.clone(), ctx.rx, ctx.octo));
// Wait longer and allow the worker to actually process the request
let request_received = wait_for_requests(server.clone(), 1).await;
// Give the worker additional time to complete processing and commit
tokio::time::sleep(Duration::from_millis(200)).await;
h.abort();
assert!(
request_received,
"Worker did not post a comment within the timeout",
);
assert_eq!(server.received_requests().await.expect("requests").len(), 1);
// Check queue directory after giving worker time to commit
assert_eq!(
stdfs::read_dir(&ctx.cfg.queue_path)
.expect("read queue directory")
.count(),
0,
"Queue should be empty after successful processing"
);
}
#[rstest]
#[tokio::test]
async fn run_worker_requeues_on_error(
#[future]
#[with(500)]
#[from(worker_test_context)]
ctx: WorkerTestContext,
) {
let ctx = ctx.await;
let server = Arc::new(ctx.server);
let h = tokio::spawn(run_worker(ctx.cfg.clone(), ctx.rx, ctx.octo));
let request_attempted = wait_for_requests(server.clone(), 1).await;
// Give the worker time to process the error and requeue
tokio::time::sleep(Duration::from_millis(200)).await;
h.abort();
assert!(
request_attempted,
"Worker did not attempt to post a comment within the timeout",
);
assert_eq!(server.received_requests().await.expect("requests").len(), 1);
assert!(
stdfs::read_dir(&ctx.cfg.queue_path)
.expect("read queue directory")
.count()
> 0,
"Queue should retain job after API failure"
);
}AI Coding Agent Prompt
// Give the worker time to process the error and requeue
tokio::time::sleep(Duration::from_millis(200)).await;
use std::time::Duration;EXPLANATION:
The fix allows the worker to complete its processing cycle after making the HTTP request. #!/bin/bash Let's look at the CommentRequest structure to understand the field differencesrg -A 10 -B 5 "struct CommentRequest" crates/comenqd/src/daemon.rs #!/bin/bash Let's search for CommentRequest more broadly to see the structureast-grep --pattern 'struct CommentRequest { #!/bin/bash Let's find CommentRequest definition across the codebasefd -e rs -x rg -l "struct.*CommentRequest" #!/bin/bash Let's examine the CommentRequest in the lib or other filesrg -A 10 "struct CommentRequest" #!/bin/bash Search for CommentRequest definition in the codebaserg -A 15 "CommentRequest" --type rust AI Coding Agent Prompt
assert!(
stdfs::read_dir(&ctx.cfg.queue_path)
.expect("read queue directory")
.count()
> 0,
"Queue should retain job after API failure"
);
EXPLANATION:
The 200ms delay allows step 3-4 to complete before the test checks the final queue state. |
|
Have these now been resolved?
|
Summary
poll_untilhelper for deterministic async testsTesting
make fmtmake lintmake testhttps://chatgpt.com/codex/tasks/task_e_689132602eb4832290a3f62573192158
Summary by Sourcery
Add a generic poll_until helper for deterministic asynchronous tests and refactor worker tests to use polling instead of fixed sleeps for improved reliability.
New Features:
Enhancements: