Skip to content

Convert the cross-platform swift test suites from XCTest to Swift Testing#25828

Draft
jkmassel wants to merge 6 commits into
trunkfrom
jkmassel/swift-testing-migration
Draft

Convert the cross-platform swift test suites from XCTest to Swift Testing#25828
jkmassel wants to merge 6 commits into
trunkfrom
jkmassel/swift-testing-migration

Conversation

@jkmassel

@jkmassel jkmassel commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Converts the last XCTest suites in the repo-root swift test run — the macOS cross-platform gate added in #25344 — to Swift Testing. That gate now runs 233 Swift Testing cases across 53 suites and zero XCTest cases.

What changed

38 test files across the four targets that still had XCTest, plus one deletion:

  • GutenbergProcessorsTests (5), JetpackStatsWidgetsCoreTests (3), WordPressFluxTests (1), WordPressSharedTests (29). WordPressCoreTests was already Swift Testing.
  • Delete WordPressFluxTests/XCTestManifests.swift — the Linux allTests() manifest is dead under Swift Testing, and was already #if !canImport(ObjectiveC) (empty on Apple platforms).

Most of it is mechanical: XCTestCase subclass → struct suite, func testX@Test func, XCTAssert*#expect / try #require / Issue.record, setUpinit. Four suites needed more than a rote swap.

WordPressFluxTests runs on @MainActor now

Flux's Dispatcher traps off the main thread (assertMainThread(), Dispatcher.swift:41). XCTest ran these on the main thread so they passed; Swift Testing runs on the cooperative pool, so the plain conversion aborted the entire swift test process with Assertion failed: Dispatcher should only be called from the main thread — a trap, not a single recorded failure. Pinning the suite to @MainActor restores main-thread execution.

DebouncerTests pumps the run loop instead of awaiting

Debouncer schedules a Timer on the current run loop. XCTest's wait(for:timeout:) spun that run loop; Swift Testing's await/confirmation don't, so a naive async port never fires the timer. These stay synchronous and spin RunLoop.current.run(mode:before:) until the callback lands or the deadline passes, wrapped in withExtendedLifetimeDebouncer.deinit fires the pending callback on dealloc, so releasing it early corrupts the timing. testDebouncerRunsImmediatelyIfReleased relies on that same deinit behavior and stays a pure synchronous check.

Smaller notes

  • WidgetDataCacheReaderTests' assert(…line:) helper becomes sourceLocation: SourceLocation = #_sourceLocation, forwarded to #expect(…, sourceLocation:) / Issue.record(…, sourceLocation:) so failures still point at the call site.
  • LazyTests stays a final class — its @Lazy property wrapper mutates and its tearDown (→ deinit) resets static state, both wanting reference semantics.
  • XCTAssertEqual(x.count, 0) and XCTAssertEqual(y, "") became #expect(x.isEmpty) / #expect(y.isEmpty): the == 0 / == "" forms trip SwiftLint's empty_count / empty_string, which the XCTAssertEqual argument form did not.
  • Dropped a disabled xtestPerformance case — XCTest measure has no Swift Testing equivalent.

The relocated suites (#25815, #25825)

After this branch was first written, #25815 (the KeystoneTests move) and #25825 (three Foundation extensions) relocated 15 more XCTest suites into WordPressSharedTests. The branch is rebased on top of them and converts those too, so the gate stays entirely Swift Testing instead of re-gaining XCTest the moment it merges. Two needed care: NotificationCenterObserveOnceTests is pinned to @Suite(.serialized) — its two tests share NotificationCenter.default and one observes without a filter, so under Swift Testing's default parallelism it would catch the other's posts; and QueueTests stays a final class because Queue is a value type it mutates across each test.

The suite runs faster, too

Runner-reported execution time drops from ~1.6s to ~0.5s for the same 233 tests (one warm run: 1.56s → 0.52s). Swift Testing parallelizes by default, whereas swift test runs XCTest serially within the bundle. The win is entirely DebouncerTests: its four timer-based cases sleep ~1.4s in total and ran serially under XCTest (Executed 4 tests … in 1.414 seconds), which by itself was most of the before-run. Swift Testing overlaps them with each other and the other 229 tests, so the run collapses to about its single slowest test. Not the goal of the change, just a free improvement to the gate.

Not in this PR

  • RichContentFormatterTests.swift and WPUserAgentTests.swift are exclude:d from the macOS WordPressSharedTests target (they don't build on macOS), so they're outside the swift test set and stay XCTest.
  • Pre-existing swift-format warnings in the Gutenberg fixtures (column-0 multiline HTML strings) are left as-is; reformatting them is unrelated churn. This PR introduces no new swift-format warnings.

Test plan

  • swift test from the repo root: 233 Swift Testing cases in 53 suites pass; the XCTest runner reports Executed 0 tests.
  • rake lint[<changed files>]: 0 violations.
  • swift-format lint on the changed files: no new warnings — the three fully-rewritten suites (DebouncerTests, StringRankedSearchTests, URLIncrementalFilenameTests) went from 5/4/6 warnings to 0.
  • CI (Buildkite build #33419) compiles and runs these files on the simulator via Modules/Package.swift (WordPressUnitTests.xctestplan): the WordPress + Jetpack Build for Testing and Unit Tests jobs are green — the conversions run on iOS, not just macOS.

@dangermattic

Copy link
Copy Markdown
Collaborator
1 Warning
⚠️ This PR is larger than 500 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
1 Message
📖 This PR is still a Draft: some checks will be skipped.

Generated by 🚫 Danger

@wpmobilebot

wpmobilebot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor
App Icon📲 You can test the changes from this Pull Request in WordPress by scanning the QR code below to install the corresponding build.
App NameWordPress
ConfigurationRelease-Alpha
Build Number33419
VersionPR #25828
Bundle IDorg.wordpress.alpha
Commita8840ed
Installation URL7htd9uolc1nr8
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

wpmobilebot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor
App Icon📲 You can test the changes from this Pull Request in Jetpack by scanning the QR code below to install the corresponding build.
App NameJetpack
ConfigurationRelease-Alpha
Build Number33419
VersionPR #25828
Bundle IDcom.jetpack.alpha
Commita8840ed
Installation URL2rgbqj4ntk770
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

jkmassel added 6 commits July 23, 2026 16:20
WordPressFlux's Dispatcher asserts it is only called on the main thread. XCTest ran tests on the main thread; Swift Testing runs them off the main thread, so the suite is pinned to @mainactor. Also removes the dead XCTestManifests.swift, obsolete under Swift Testing.
DebouncerTests keep their Timer-based cases synchronous and spin the run loop, since Swift Testing's async waiting does not pump the run loop the way XCTest's wait(for:timeout:) did.
#25815 (KeystoneTests move) and #25825 (Foundation extensions) relocated 15 more XCTest suites into WordPressSharedTests after this branch was first written; convert them too so the swift test set stays entirely Swift Testing. NotificationCenterObserveOnceTests is pinned to @suite(.serialized) because its two tests share NotificationCenter.default and would race under Swift Testing's parallelism; QueueTests stays a final class because Queue is a value type it mutates.
@jkmassel
jkmassel force-pushed the jkmassel/swift-testing-migration branch from bc3811a to a8840ed Compare July 23, 2026 22:31
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.

3 participants