Skip to content

test: migrate RealtimeTests to Swift Testing (Phase 7)#1141

Open
grdsdev wants to merge 2 commits into
mainfrom
test/sdk-1254-realtime-tests-swift-testing
Open

test: migrate RealtimeTests to Swift Testing (Phase 7)#1141
grdsdev wants to merge 2 commits into
mainfrom
test/sdk-1254-realtime-tests-swift-testing

Conversation

@grdsdev

@grdsdev grdsdev commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements SDK-1254: migrates every XCTest suite in Tests/RealtimeTests to Swift Testing (@Suite/@Test, #expect/#require, Issue.record), per the SDK-435 phase-by-phase migration plan.

Details

  • Adds Tests/RealtimeTests/TestSupport.swift: a shared waitUntil(timeout:pollInterval:condition:) polling helper that replaces XCTestExpectation/fulfillment(of:timeout:) — Swift Testing has no direct async-condition-with-timeout primitive.
  • RealtimeTests.swift: keeps the withMainSerialExecutor requirement (previously applied via invokeTest() override), now wrapped per test body, with @Suite(.serialized) so the process-global executor flag can't race across concurrently-scheduled tests.
  • Converts setUp/tearDown to init()/deinit, keeping class-based suites where teardown must actively cancel background tasks (e.g. sut.disconnect()), not just rely on ARC releasing the struct.
  • WebSocketTests.swift: serializes the suite (@Suite(.serialized)) — its cert-pinning tests import self-signed identities via SecPKCS12Import, which is flaky when several tests run concurrently under Swift Testing's default parallel execution (XCTest ran these serially). Converts completion-handler-based challenge-forwarding tests to confirmation(_:).
  • Package.swift: adds RealtimeTests to swift6TestTargets now that the target is fully migrated (gets full Swift 6 language mode checking, matching other migrated modules).

Behavior, coverage, and assertions are unchanged — only the test framework and incidental concurrency helpers differ.

Testing

  • swift build (whole package) — clean.
  • swift test --filter RealtimeTests — 254 tests / 22 suites pass, run 5x in a row with no flakiness (including the WebSocketTests cert-pinning tests, which were flaky before serializing the suite).
  • ./scripts/format.sh — applied.

Converts every XCTest suite in Tests/RealtimeTests to Swift Testing
(@Suite/@test, #expect/#require, Issue.record), per SDK-435 phase 7
(SDK-1254).

- Replaces XCTestExpectation/fulfillment(of:timeout:) with a shared
  `waitUntil` polling helper (Tests/RealtimeTests/TestSupport.swift),
  since Swift Testing has no direct async-condition-with-timeout
  primitive.
- Preserves `withMainSerialExecutor` usage in RealtimeTests.swift,
  now applied per-test-body with `@Suite(.serialized)` to keep the
  process-global executor flag from racing across concurrently
  running tests.
- Converts XCTestCase setUp/tearDown to struct/class init()/deinit,
  keeping class-based suites (with explicit `sut.disconnect()` in
  deinit) where teardown must run background-task cancellation, not
  just ARC release.
- Serializes WebSocketTests (`@Suite(.serialized)`): its cert-pinning
  tests import self-signed identities via SecPKCS12Import, which is
  flaky when several tests run concurrently (Swift Testing's default
  parallel execution, vs. XCTest's serial method execution).
- Converts completion-handler-based challenge-forwarding tests to
  `confirmation(_:)`.
- Adds "RealtimeTests" to Package.swift's swift6TestTargets now that
  the target is fully migrated.

Behavior, coverage, and assertions are unchanged; only the test
framework and incidental concurrency helpers differ.
@grdsdev
grdsdev requested a review from a team as a code owner July 23, 2026 08:55
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ad4e0d2-df85-4df7-96a0-91787ef832af

📥 Commits

Reviewing files that changed from the base of the PR and between 7a4756b and ef2be04.

📒 Files selected for processing (3)
  • Tests/RealtimeTests/ConnectionManagerTests.swift
  • Tests/RealtimeTests/WebSocketTests.swift
  • dictionary.txt
🚧 Files skipped from review as they are similar to previous changes (2)
  • Tests/RealtimeTests/WebSocketTests.swift
  • Tests/RealtimeTests/ConnectionManagerTests.swift

📝 Walkthrough

Summary by CodeRabbit

  • Refactor

    • Migrated the Realtime test suite from XCTest to Swift Testing with updated assertions and suite/test annotations.
    • Added shared async polling utilities to improve timing reliability in Realtime tests.
  • Tests

    • Kept existing coverage across connections, channels, messaging, presence, broadcasts, reconnection, serialization, lifecycle behavior, and WebSocket handling.
    • Strengthened leak detection and improved synchronization for async/reconnect scenarios.
  • Chores

    • Updated Swift-version handling for relevant Realtime test targets for Swift 6 compatibility.
    • Added a small dictionary fixture entry.

Walkthrough

The Realtime test target is migrated from XCTest to Swift Testing using @Suite, @Test, #expect, and Issue.record. Test setup, teardown, asynchronous expectations, leak checks, and WebSocket challenge handling are adapted to the new framework. Shared polling helpers and serialized execution support are added, while existing coverage for channels, connections, serialization, presence, lifecycle, and push behavior is retained. RealtimeTests is also included in the Swift 6 test-target configuration.

Possibly related PRs

Suggested labels: Realtime


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.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Capability matrix drift detected

The following capabilities are marked implemented in swift but have no registered symbols to verify:

  • auth.passkey.register_passkey (no symbols list — cannot confirm implementation exists)
  • auth.passkey.sign_in_with_passkey (no symbols list — cannot confirm implementation exists)
  • client.authentication_integration.third_party_auth (no symbols list — cannot confirm implementation exists)
  • client.authentication_integration.cross_client_token_sync (no symbols list — cannot confirm implementation exists)
  • client.authentication_integration.oauth_flow_type (no symbols list — cannot confirm implementation exists)
  • client.authentication_integration.session_url_detection (no symbols list — cannot confirm implementation exists)
  • client.session_management.custom_storage (no symbols list — cannot confirm implementation exists)
  • client.session_management.persist_session (no symbols list — cannot confirm implementation exists)
  • client.request_configuration.global_headers (no symbols list — cannot confirm implementation exists)
  • client.observability.trace_propagation (no symbols list — cannot confirm implementation exists)
  • database.query.from_table (no symbols list — cannot confirm implementation exists)
  • database.query.select (no symbols list — cannot confirm implementation exists)
  • database.query.schema_selection (no symbols list — cannot confirm implementation exists)
  • database.query.rpc (no symbols list — cannot confirm implementation exists)
  • database.mutate.insert (no symbols list — cannot confirm implementation exists)
  • database.mutate.update (no symbols list — cannot confirm implementation exists)
  • database.mutate.upsert (no symbols list — cannot confirm implementation exists)
  • database.mutate.delete (no symbols list — cannot confirm implementation exists)
  • database.mutate.select_after_mutation (no symbols list — cannot confirm implementation exists)
  • database.using_filters.eq (no symbols list — cannot confirm implementation exists)
  • database.using_filters.neq (no symbols list — cannot confirm implementation exists)
  • database.using_filters.gt (no symbols list — cannot confirm implementation exists)
  • database.using_filters.gte (no symbols list — cannot confirm implementation exists)
  • database.using_filters.lt (no symbols list — cannot confirm implementation exists)
  • database.using_filters.lte (no symbols list — cannot confirm implementation exists)
  • database.using_filters.like (no symbols list — cannot confirm implementation exists)
  • database.using_filters.ilike (no symbols list — cannot confirm implementation exists)
  • database.using_filters.is (no symbols list — cannot confirm implementation exists)
  • database.using_filters.in (no symbols list — cannot confirm implementation exists)
  • database.using_filters.contains (no symbols list — cannot confirm implementation exists)
  • database.using_filters.contained_by (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_gt (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_gte (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_lt (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_lte (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_adjacent (no symbols list — cannot confirm implementation exists)
  • database.using_filters.overlaps (no symbols list — cannot confirm implementation exists)
  • database.using_filters.text_search (no symbols list — cannot confirm implementation exists)
  • database.using_filters.match (no symbols list — cannot confirm implementation exists)
  • database.using_filters.not (no symbols list — cannot confirm implementation exists)
  • database.using_filters.or (no symbols list — cannot confirm implementation exists)
  • database.using_filters.raw (no symbols list — cannot confirm implementation exists)
  • database.using_filters.regex (no symbols list — cannot confirm implementation exists)
  • database.using_filters.regex_icase (no symbols list — cannot confirm implementation exists)
  • database.using_filters.is_distinct (no symbols list — cannot confirm implementation exists)
  • database.using_filters.like_all (no symbols list — cannot confirm implementation exists)
  • database.using_filters.like_any (no symbols list — cannot confirm implementation exists)
  • database.using_filters.ilike_all (no symbols list — cannot confirm implementation exists)
  • database.using_filters.ilike_any (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.order (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.limit (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.range (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.single_row (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.strip_nulls (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.format_csv (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.format_geojson (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.max_affected_rows (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.request_cancellation (no symbols list — cannot confirm implementation exists)
  • database.configuration.auto_retry (no symbols list — cannot confirm implementation exists)
  • functions.invocation.invoke (no symbols list — cannot confirm implementation exists)
  • functions.invocation.set_auth_token (no symbols list — cannot confirm implementation exists)
  • functions.invocation.method_override (no symbols list — cannot confirm implementation exists)
  • functions.invocation.streaming_response (no symbols list — cannot confirm implementation exists)
  • functions.invocation.request_cancellation (no symbols list — cannot confirm implementation exists)
  • realtime.client.connect (no symbols list — cannot confirm implementation exists)
  • realtime.client.disconnect (no symbols list — cannot confirm implementation exists)
  • realtime.client.get_channels (no symbols list — cannot confirm implementation exists)
  • realtime.client.remove_channel (no symbols list — cannot confirm implementation exists)
  • realtime.client.remove_all_channels (no symbols list — cannot confirm implementation exists)
  • realtime.client.connection_state (no symbols list — cannot confirm implementation exists)
  • realtime.client.listen_heartbeats (no symbols list — cannot confirm implementation exists)
  • realtime.client.set_auth_token (no symbols list — cannot confirm implementation exists)
  • realtime.client.channel (no symbols list — cannot confirm implementation exists)
  • realtime.channel.subscribe (no symbols list — cannot confirm implementation exists)
  • realtime.channel.unsubscribe (no symbols list — cannot confirm implementation exists)
  • realtime.channel.send (no symbols list — cannot confirm implementation exists)
  • realtime.channel.broadcast_http (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.postgres_changes (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.subscribe_presence (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.private_channel (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.broadcast_self (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.broadcast_ack (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.broadcast_replay (no symbols list — cannot confirm implementation exists)
  • realtime.presence.track (no symbols list — cannot confirm implementation exists)
  • realtime.presence.untrack (no symbols list — cannot confirm implementation exists)
  • realtime.presence.presence_key (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.custom_websocket_transport (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.reconnect_backoff (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.heartbeat_interval (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.access_token_callback (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.deferred_disconnect (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.custom_logger (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.binary_protocol (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.get_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.list_file_buckets (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.update_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.delete_file_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.empty_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.access_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.upload (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.download (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.move (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.copy (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.remove (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.create_signed_url (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.create_signed_urls (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.create_signed_upload_url (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.upload_with_signed_url (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.update_file (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.file_exists (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.file_info (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.list_files_paginated (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.copy_cross_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.move_cross_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.upload_with_metadata (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.url_cache_nonce (no symbols list — cannot confirm implementation exists)

These may have been renamed, removed, or never registered. Please update the capability matrix.
See: https://github.com/supabase/sdk/blob/main/docs/capability-matrix.md

- WebSocketTests: mark local delegate test doubles @unchecked Sendable
  to satisfy Sendable-conforming URLSessionDelegate/URLSessionTaskDelegate
  under strict concurrency (compile error on all xcodebuild jobs)
- ConnectionManagerTests: replace fixed 50ms sleep with waitUntil poll
  to avoid a race under parallel Swift Testing execution (flaky on Linux CI)
- dictionary: add 'deinits' for cspell

@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

🤖 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 `@Tests/RealtimeTests/RealtimeChannelTests.swift`:
- Around line 929-944: Update the `@MainActor` Testing_waitUntil helper to perform
one final condition() check after the polling loop reaches its deadline,
matching the shared waitUntil behavior. Preserve the existing polling and
timeout flow, and return successfully if the condition becomes true at the
boundary or immediately after the loop.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8dbe8404-0b2b-4cfb-bfa1-1d2321106f48

📥 Commits

Reviewing files that changed from the base of the PR and between 1dc076c and 7a4756b.

📒 Files selected for processing (23)
  • Package.swift
  • Tests/RealtimeTests/CallbackManagerTests.swift
  • Tests/RealtimeTests/ChannelStateManagerTests.swift
  • Tests/RealtimeTests/ConnectionManagerTests.swift
  • Tests/RealtimeTests/ExportsTests.swift
  • Tests/RealtimeTests/PostgresActionTests.swift
  • Tests/RealtimeTests/PostgresJoinConfigTests.swift
  • Tests/RealtimeTests/PresenceActionTests.swift
  • Tests/RealtimeTests/PushV2Tests.swift
  • Tests/RealtimeTests/RealtimeChannelBroadcastTests.swift
  • Tests/RealtimeTests/RealtimeChannelTests.swift
  • Tests/RealtimeTests/RealtimeColdStartTests.swift
  • Tests/RealtimeTests/RealtimeErrorTests.swift
  • Tests/RealtimeTests/RealtimeJoinConfigTests.swift
  • Tests/RealtimeTests/RealtimeLifecycleTests.swift
  • Tests/RealtimeTests/RealtimeMessageV2Tests.swift
  • Tests/RealtimeTests/RealtimePostgresFilterTests.swift
  • Tests/RealtimeTests/RealtimePostgresFilterValueTests.swift
  • Tests/RealtimeTests/RealtimeSerializerTests.swift
  • Tests/RealtimeTests/RealtimeTests.swift
  • Tests/RealtimeTests/TestSupport.swift
  • Tests/RealtimeTests/WebSocketTests.swift
  • Tests/RealtimeTests/_PushTests.swift

Comment on lines +929 to +944

/// `@MainActor`-safe wrapper around the shared, non-isolated `waitUntil` helper —
/// avoids a "passing a `@MainActor`-isolated closure as a `@Sendable` closure" diagnostic
/// when the condition captures main-actor-isolated state (e.g. `RealtimeChannelV2.status`).
@MainActor
private func Testing_waitUntil(
timeout: TimeInterval,
pollInterval: UInt64,
condition: @MainActor @escaping () -> Bool
) async {
let deadline = Date().addingTimeInterval(timeout)
while Date() < deadline {
if condition() { return }
try? await Task.sleep(nanoseconds: pollInterval)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Missing final check in Testing_waitUntil can cause flaky failures.

Unlike the shared waitUntil helper (Tests/RealtimeTests/TestSupport.swift), this loop returns without re-checking condition() once the deadline passes. On slow CI, a status change landing right at/after the deadline boundary will be missed, and the subsequent #expect (e.g. in waitForChannelStatus) will fail spuriously.

🔧 Proposed fix
 `@MainActor`
 private func Testing_waitUntil(
   timeout: TimeInterval,
   pollInterval: UInt64,
   condition: `@MainActor` `@escaping` () -> Bool
 ) async {
   let deadline = Date().addingTimeInterval(timeout)
   while Date() < deadline {
     if condition() { return }
     try? await Task.sleep(nanoseconds: pollInterval)
   }
+  _ = condition()
 }
🤖 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 `@Tests/RealtimeTests/RealtimeChannelTests.swift` around lines 929 - 944,
Update the `@MainActor` Testing_waitUntil helper to perform one final condition()
check after the polling loop reaches its deadline, matching the shared waitUntil
behavior. Preserve the existing polling and timeout flow, and return
successfully if the condition becomes true at the boundary or immediately after
the loop.

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.

2 participants