Skip to content

Fix flaky StreamVideo tests#1118

Draft
ipavlidakis wants to merge 2 commits intodevelopfrom
iliaspavlidakis/investigate-streamvideo-test-failu
Draft

Fix flaky StreamVideo tests#1118
ipavlidakis wants to merge 2 commits intodevelopfrom
iliaspavlidakis/investigate-streamvideo-test-failu

Conversation

@ipavlidakis
Copy link
Copy Markdown
Contributor

@ipavlidakis ipavlidakis commented Apr 16, 2026

Summary

  • replace brittle sleep-based test waits with state-driven assertions in Call_IntegrationTests
  • remove a ring-event subscription race from the accept integration flow
  • make device deletion checks eventually consistent instead of asserting a single immediate snapshot
  • harden state-machine tests that expected no transition so unexpected transitions fail fast
  • switch the shared XCTest wait helper to Task.sleep
  • stabilize timer resubscription coverage and drop duplicate duration coverage from Call_Tests

Testing

  • Ran xcodebuild -project StreamVideo.xcodeproj -scheme StreamVideo -testPlan StreamVideo -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.4' test
  • Verified targeted integration cases individually with plain xcodebuild
  • Final full-suite run completed with no failing tests

Summary by CodeRabbit

  • Tests
    • Enhanced test stability by replacing fixed time delays with event-based assertions across the test suite.
    • Improved test infrastructure helpers for more reliable asynchronous testing.

@ipavlidakis ipavlidakis requested a review from a team as a code owner April 16, 2026 13:38
@ipavlidakis ipavlidakis marked this pull request as draft April 16, 2026 13:38
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e68ee5ca-af21-4893-91c2-bb48c4ac4466

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request removes one test case and refactors multiple test files to replace fixed-delay waits with event-driven assertion patterns. Changes eliminate timing dependencies and improve test reliability through eventual assertions and async helpers across integration, unit, and WebRTC test suites.

Changes

Cohort / File(s) Summary
Test Removal
StreamVideoTests/Call/Call_Tests.swift
Removed test_call_duration test method that verified call duration computation and state transitions (36 lines).
Test Wait Utility
StreamVideoTests/TestUtils/XCTestCase+Wait.swift
Changed wait(for:) implementation from XCTestExpectation-based blocking to Task.sleep(nanoseconds:), with early-return for intervals ≤ 0.
Integration Test Refactoring
StreamVideoTests/IntegrationTests/Call_IntegrationTests.swift
Replaced fixed delays and point-in-time assertions with .assertEventually and .assertEventuallyInMainActor for device deletion, call state updates, livestream backstage joins, and audio room scenarios.
Unit Test Helpers
StreamVideoTests/Utils/Proximity/Policies/VideoProximityPolicy_Tests.swift, StreamVideoTests/Utils/Timers/TimerPublisher_Tests.swift
Added assertState(...) helper with main-actor fulfillment conditions and explicit assertions; replaced fixed delays with XCTest expectation-based waits for first values.
WebRTC Test Helpers
StreamVideoTests/WebRTC/v2/StateMachine/Stages/WebRTCCoordinatorStateMachine_DisconnectedStageTests.swift, WebRTCCoordinatorStateMachine_FastReconnectingStageTests.swift
Disabled over-fulfillment assertions and refined transition fulfillment conditions to handle both matching and non-matching target states; adjusted timeout to 2 seconds for inverted expectations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 No more waiting for the clock to tick,
Events guide our tests—no tricks nor tricks!
From delays to assertions that truly care,
Our tests now breathe the async air! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix flaky StreamVideo tests' accurately summarizes the main objective of the changeset, which focuses on replacing brittle sleep-based waits with state-driven assertions and removing flaky test patterns.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch iliaspavlidakis/investigate-streamvideo-test-failu

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 and usage tips.

)
}

// MARK: - Duration
Copy link
Copy Markdown
Contributor Author

@ipavlidakis ipavlidakis Apr 16, 2026

Choose a reason for hiding this comment

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

That same behavior is already covered directly in CallState_Tests.swift (line 224), CallState_Tests.swift (line 237), and CallState_Tests.swift (line 278).

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
StreamVideoTests/WebRTC/v2/StateMachine/Stages/WebRTCCoordinatorStateMachine_DisconnectedStageTests.swift (1)

396-409: ⚠️ Potential issue | 🟠 Major

Wrong-stage transitions are treated as success here.

Line 407 fulfills the non-inverted expectation after a mismatched target without recording a failure, and Line 396 disables over-fulfill checks. That means this helper can pass when the state machine lands on the wrong stage, or when it reaches the expected stage and then immediately transitions somewhere unexpected.

🛠️ Suggested fix
         subject.transition = { target in
             Task {
                 if target.id == expectedTarget {
                     await validationHandler(target)
                     transitionExpectation.fulfill()
                 } else if let expectedTarget {
-                    transitionExpectation
-                        .expectationDescription =
-                        "Expectation to land on id:\(expectedTarget) but instead landed on id:\(target.id)."
+                    XCTFail(
+                        "Expected transition to \(expectedTarget), but landed on \(target.id).",
+                        file: file,
+                        line: line
+                    )
                     transitionExpectation.fulfill()
                 } else if expectedTarget == nil {
                     transitionExpectation.fulfill()
                 }
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@StreamVideoTests/WebRTC/v2/StateMachine/Stages/WebRTCCoordinatorStateMachine_DisconnectedStageTests.swift`
around lines 396 - 409, The helper currently disables over-fulfill checks
(transitionExpectation.assertForOverFulfill = false) and fulfills the
non-inverted expectation even on mismatched targets inside subject.transition,
allowing wrong-stage transitions to be treated as success; restore/assert
over-fulfill behavior (remove or set assertForOverFulfill = true) and change the
subject.transition closure so it only fulfills transitionExpectation when
target.id == expectedTarget after running validationHandler, and on a mismatched
target (else branch) record a test failure (e.g., XCTFail with a message using
expectedTarget and target.id) or fulfill a separate inverted expectation instead
of fulfilling the success expectation; keep the expectedTarget == nil branch
behavior consistent (fulfill only when that case is genuinely expected).
StreamVideoTests/WebRTC/v2/StateMachine/Stages/WebRTCCoordinatorStateMachine_FastReconnectingStageTests.swift (1)

288-301: ⚠️ Potential issue | 🟠 Major

Unexpected transitions still satisfy the expectation.

Line 299 fulfills the non-inverted expectation even when target.id != expectedTarget, and Line 288 suppresses over-fulfill failures. So this helper can now pass on the wrong transition instead of failing fast.

🛠️ Suggested fix
         subject.transition = { target in
             Task {
                 if target.id == expectedTarget {
                     await validationHandler(target)
                     transitionExpectation.fulfill()
                 } else if let expectedTarget {
-                    transitionExpectation
-                        .expectationDescription =
-                        "Expectation to land on id:\(expectedTarget) but instead landed on id:\(target.id)."
+                    XCTFail(
+                        "Expected transition to \(expectedTarget), but landed on \(target.id).",
+                        file: file,
+                        line: line
+                    )
                     transitionExpectation.fulfill()
                 } else if expectedTarget == nil {
                     transitionExpectation.fulfill()
                 }
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@StreamVideoTests/WebRTC/v2/StateMachine/Stages/WebRTCCoordinatorStateMachine_FastReconnectingStageTests.swift`
around lines 288 - 301, The helper currently allows unexpected transitions to
satisfy the expectation because transitionExpectation.assertForOverFulfill is
disabled and the subject.transition closure fulfills the expectation even when
target.id != expectedTarget; change it so only the matching target (when
target.id == expectedTarget) calls transitionExpectation.fulfill and triggers
validationHandler, while any non-matching transition should not fulfill the
expectation but instead record a test failure (e.g., XCTFail with a descriptive
message using expectedTarget and target.id) or immediately fail the test; also
restore/assert assertForOverFulfill behavior (remove or set to true) so extra
fulfills are flagged. Ensure you update the closure in subject.transition and
references to expectedTarget/validationHandler accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@StreamVideoTests/Utils/Proximity/Policies/VideoProximityPolicy_Tests.swift`:
- Around line 141-151: The current assertState uses fulfilmentInMainActor which
returns as soon as the predicate is true, so zero-call expectations can pass
immediately and miss later changes; update assertState to handle the
expectedTimesCalledChangeVideoState == 0 case by first capturing the current
incomingVideoQualitySettings and timesCalledChangeVideoState (via
mockCall.state.incomingVideoQualitySettings and
mockCallController.timesCalled(.changeVideoState)), then wait a short grace
period (e.g. 100–200ms) on the main actor and assert that both values did not
change during that window; keep the existing behaviour for
expectedTimesCalledChangeVideoState > 0 (use fulfilmentInMainActor as before to
wait until the predicate becomes true).

---

Outside diff comments:
In
`@StreamVideoTests/WebRTC/v2/StateMachine/Stages/WebRTCCoordinatorStateMachine_DisconnectedStageTests.swift`:
- Around line 396-409: The helper currently disables over-fulfill checks
(transitionExpectation.assertForOverFulfill = false) and fulfills the
non-inverted expectation even on mismatched targets inside subject.transition,
allowing wrong-stage transitions to be treated as success; restore/assert
over-fulfill behavior (remove or set assertForOverFulfill = true) and change the
subject.transition closure so it only fulfills transitionExpectation when
target.id == expectedTarget after running validationHandler, and on a mismatched
target (else branch) record a test failure (e.g., XCTFail with a message using
expectedTarget and target.id) or fulfill a separate inverted expectation instead
of fulfilling the success expectation; keep the expectedTarget == nil branch
behavior consistent (fulfill only when that case is genuinely expected).

In
`@StreamVideoTests/WebRTC/v2/StateMachine/Stages/WebRTCCoordinatorStateMachine_FastReconnectingStageTests.swift`:
- Around line 288-301: The helper currently allows unexpected transitions to
satisfy the expectation because transitionExpectation.assertForOverFulfill is
disabled and the subject.transition closure fulfills the expectation even when
target.id != expectedTarget; change it so only the matching target (when
target.id == expectedTarget) calls transitionExpectation.fulfill and triggers
validationHandler, while any non-matching transition should not fulfill the
expectation but instead record a test failure (e.g., XCTFail with a descriptive
message using expectedTarget and target.id) or immediately fail the test; also
restore/assert assertForOverFulfill behavior (remove or set to true) so extra
fulfills are flagged. Ensure you update the closure in subject.transition and
references to expectedTarget/validationHandler accordingly.
🪄 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: ed514cbe-c5cd-4110-976a-2bb60e4760aa

📥 Commits

Reviewing files that changed from the base of the PR and between 149c5bd and e5ed258.

📒 Files selected for processing (7)
  • StreamVideoTests/Call/Call_Tests.swift
  • StreamVideoTests/IntegrationTests/Call_IntegrationTests.swift
  • StreamVideoTests/TestUtils/XCTestCase+Wait.swift
  • StreamVideoTests/Utils/Proximity/Policies/VideoProximityPolicy_Tests.swift
  • StreamVideoTests/Utils/Timers/TimerPublisher_Tests.swift
  • StreamVideoTests/WebRTC/v2/StateMachine/Stages/WebRTCCoordinatorStateMachine_DisconnectedStageTests.swift
  • StreamVideoTests/WebRTC/v2/StateMachine/Stages/WebRTCCoordinatorStateMachine_FastReconnectingStageTests.swift
💤 Files with no reviewable changes (1)
  • StreamVideoTests/Call/Call_Tests.swift

Comment on lines +141 to +151
private func assertState(
incomingVideoQualitySettings expectedIncomingVideoQualitySettings: IncomingVideoQualitySettings,
timesCalledChangeVideoState expectedTimesCalledChangeVideoState: Int,
lastChangeVideoStateValue expectedLastChangeVideoStateValue: Bool? = nil,
file: StaticString = #filePath,
line: UInt = #line
) async {
await fulfilmentInMainActor(timeout: 2, filePath: file, line: line) {
self.mockCall.state.incomingVideoQualitySettings == expectedIncomingVideoQualitySettings
&& self.mockCallController.timesCalled(.changeVideoState) == expectedTimesCalledChangeVideoState
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

assertState no longer observes the “nothing happens” cases.

Line 148 uses fulfilmentInMainActor, which completes on the first successful predicate evaluation. For the zero-call scenarios in this file, that condition is often already true immediately, so the helper returns without any grace window and a delayed changeVideoState / state mutation would be missed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@StreamVideoTests/Utils/Proximity/Policies/VideoProximityPolicy_Tests.swift`
around lines 141 - 151, The current assertState uses fulfilmentInMainActor which
returns as soon as the predicate is true, so zero-call expectations can pass
immediately and miss later changes; update assertState to handle the
expectedTimesCalledChangeVideoState == 0 case by first capturing the current
incomingVideoQualitySettings and timesCalledChangeVideoState (via
mockCall.state.incomingVideoQualitySettings and
mockCallController.timesCalled(.changeVideoState)), then wait a short grace
period (e.g. 100–200ms) on the main actor and assert that both values did not
change during that window; keep the existing behaviour for
expectedTimesCalledChangeVideoState > 0 (use fulfilmentInMainActor as before to
wait until the predicate becomes true).

@github-actions
Copy link
Copy Markdown

1 Message
📖 Skipping Danger since the Pull Request is classed as Draft/Work In Progress

Generated by 🚫 Danger

@sonarqubecloud
Copy link
Copy Markdown

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