Files
652c1fae98 other(CI): introduce iOS 27 CI jobs (#7705)
* introduce ios27 CI jobs

* add iOS 27 to OSVersionEquivalent

* Generating new test snapshots (#7706)

* Generating new test snapshots (#7711)

* skip tests where storefront changing doesn't work (#7712)

* fix: preserve optional callback results with Xcode 27 (#7714)

* test: use consumables for custom transaction listener streams (#7715)

* Fix orchestrator cancellation test coverage on iOS 27 (#7717)

* Fix orchestrator cancellation test coverage on iOS 27

* Preserve cancellation test and mock availability on older SDKs

* Stabilize StoreKit transaction fetcher test fixtures on iOS 27 (#7716)

* test: stabilize StoreKit transaction fetcher fixtures on iOS 27

* Limit StoreKit fixture workaround to OS 27

* Wait for StoreKitTest intro eligibility reset on OS 27 (#7718)

* Wait for StoreKitTest intro eligibility reset on OS 27

* Give intro eligibility reset a 30-second timeout

* Avoid OS 27 product lookup in custom listener fixtures

Create custom-stream consumable transactions directly through SKTestSession on OS 27, avoiding the empty Product.products response seen in CI job 786483. Preserve the older-OS purchase path and all listener assertions. Test-only; no SDK or public API changes.

Validation: custom-stream tests passed 20/20 on iOS 27 and 6/6 on iOS 26.3.1; broader listener suites passed on iOS 27. SwiftLint and git diff --check passed.

* Retry transient empty StoreKitTest fixture lookups on OS 27

CI 786512 failed renewal diagnostics before purchasing because Product.products returned no fixture product. Retry only empty shared fixture lookups for up to 10 seconds on OS 27; keep earlier OS behavior, propagate errors/cancellation, and still fail on persistently missing products. No SDK or public API changes.

Add regression coverage for recovery, immediate success, bounded empty results, zero-timeout behavior, and error propagation. Validation: 186/186 selected iOS 27 runs and 63/63 iOS 26.3.1 runs; final helper tests 15/15 on each OS. SwiftLint and diff checks passed. UI failures remain covered by separate PR #7720.

* Un-skip the StaticString tests that still pass on Swift 6.4 (#7722)

* Un-skip the StaticString tests that still pass on Swift 6.4

`@_disfavoredOverload` is only ignored for labeled and closure-taking
calls, so the unlabeled `logIn(_:)` and the `String?`-sibling Builder
API still emit the warning. Both tests pass on Xcode 27.0.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Restore why the StaticString/String overload pair exists

The rewritten comment explained which call shapes @_disfavoredOverload
still steers, but dropped the reason we rely on it at all: the
StaticString overloads are deprecated yet typechecker-favored so that a
hardcoded app user ID warns at compile time. Without that, a future
reader can't tell what the broken behavior costs us.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* Initialize StoreKitTest before observing transaction updates

Start the shared fixture listener after SKTestSession configuration instead of class setup. CI logs showed the early listener using Sandbox before XcodeTest initialization, with subsequent fixture product requests staying in Sandbox.

Test-only; no SDK or public API changes. Validated cache tests on two fresh iOS 27 simulators, 90 focused executions on the existing iOS 27 simulator, and 70 on iOS 26.3.1. A broader first-launch run timed out in testOnePurchasedProduct; its suite passed all 12 executions on rerun. SwiftLint passed.

* Fix paywall URL callback delivery across session resets (#7720)

* Fix paywall URL callback delivery across session resets

* Release audio-session handler after configuration test

Balance registration in testSetsPlaybackCategoryWithMixing so the static session registry cannot retain stale state and affect a later mock reusing the same ObjectIdentifier. Test-only cleanup; no production or public API changes.

Reproduced the failing suite on iOS 26.5 before the fix. All 7 audio-session tests passed 20 repetitions (140/140) after cleanup. SwiftLint and git diff --check passed.

* Preserve Sendable isolation when registering paywall callbacks

Forward URL and web-checkout callbacks through explicitly MainActor Sendable closures. Avoid the older-compiler function-conversion warnings rejected by CocoaPods lint without changing public declarations or suppressing warnings.

Validation: Xcode 16.4 RC Release simulator build passed for arm64 and x86_64. Callback and audio-session tests passed 81/81 on iOS 26.5 and 81/81 on iOS 27. SwiftLint and diff checks passed. Full pod lib lint remains for CI because local Bundler 2.6.6 is unavailable.

---------

Co-authored-by: Josh Holtz <me@joshholtz.com>

* Create observer-mode transaction fixtures through StoreKitTest on iOS 27

The first CI purchase attempted interactive Sandbox authentication despite product lookup using XcodeTest. Avoid Product.purchase for these detector fixtures on OS 27, read back the verified transaction and JWS, and preserve the existing callback and deduplication assertions. Older OS versions keep the original setup.

Test-only; no SDK or public API changes. Observer suite passed 30/30 executions on a fresh iOS 27 simulator and 30/30 on iOS 26.3.1. SwiftLint passed.

* Initialize StoreKitTest in a separate process before iOS 27 CI tests

The first test host can keep its purchase connection in Sandbox even while product lookup uses XcodeTest. Configure the local StoreKit session in a preflight process, then run the unfiltered suite on the same simulator without erasing or reinstalling it. Fail the job if preflight fails. Enable only for the iOS 27 core job.

CI/test-only workaround; no production or public API changes. Fresh simulator preflight followed by the full StoreKit target: 491 passed, 23 skipped, zero failures. Second fresh simulator: preflight then the previously hanging purchase test passed 10/10. iOS 26 compatibility: 14 passed. SwiftLint, Ruby/YAML syntax and four lane-control checks passed. Validated via xcodebuild; the complete Fastlane lane awaits CI.

* other: restore Result-based continuation now that #7723 is merged (#7727)

* fix: preserve errors when Result's Success is inferred as optional (#7723)

* fix: preserve errors when Result's Success is inferred as optional

`Result.init(_:_:)` takes `Success?`, so when `Success` is itself optional the
argument is promoted into a second optional layer where an absent value is
indistinguishable from a present one, and the error is never read. Built with
Xcode 27 this makes `syncAttributesAndOfferingsIfNeeded()` return `nil` rather
than throwing, since `Success` is inferred as `Offerings?` inside
`continuation.resume(with:)`.

Add an overload constrained to an optional `Success` that takes `Success`
directly and checks the error first, so `nil` remains a valid success. Both
overloads now yield a `.failure` regardless of which one is selected.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: cover both continuation return shapes for Result init selection

Most of the SDK's completion-handler-to-`async` wrappers return a non-optional
value, where `Success` can only be inferred one way. Cover that shape alongside
the optional one, and drop the two cases that duplicated existing coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: cover a nil value and nil error through an async continuation

The two initializers diverge here: the preserving one treats it as a valid
success while the unwrapping one traps, so pin which one an optional `Success`
reaches.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* Restore Result-based continuation in syncAttributesAndOfferingsIfNeededAsync

#7714 split this into an explicit if-let because Result(offerings, error)
inferred Success as Offerings?, promoted the absent value into the outer
layer and never read the error. #7723 fixed that in the initializer, so the
workaround is no longer needed.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* [1/7] Support current and older Xcode toolchains (#7736)

* Support current and older Xcode toolchains [skip ci]

* Use compiler guards and shared StoreKit fixture helper [skip ci]

* Clarify StoreKit 27 presentation and refund errors [skip ci]

* Account for the standard refund error description prefix [skip ci]

* Avoid an unknown-error warning for recognized StoreKit errors [skip ci]

* Exercise the public error conversion in the logging regression test [skip ci]

* [2/7] Synchronize event tests without changing background scheduling (#7740)

* Synchronize event tests without changing background scheduling [skip ci]

* Await event storage directly in backend integration coverage [skip ci]

* Own the events manager in backend integration tests [skip ci]

* Configure the integration test backend explicitly [skip ci]

* Explain direct event manager integration testing [skip ci]

* [3/7] Isolate asynchronous test work with a debug-only warmup hook (#7741)

* Isolate asynchronous test work with a debug-only warmup hook [skip ci]

* Drain eligibility warmups even when the leak check fails [skip ci]

* Await eligibility warmup tasks during test teardown

* [4/7] Stabilize StoreKit integration fixtures and scope OS 27 exceptions (#7742)

* Stabilize StoreKit integration fixtures and scope OS 27 exceptions [skip ci]

* Verify recovered purchase completion through unfinished transactions [skip ci]

* Use consumable fixtures for purchase event tests [skip ci]

* Quarantine known SK1 receipt deduplication race on iOS 27 [skip ci]

* Verify each consumable transaction after switching users [skip ci]

* Scope finish assertions to each test purchase [skip ci]

* Allow backend attribution posting to finish in integration test

* Wait for unfinished StoreKit fixture transactions before fetching

* Quarantine deferred late-identifier SK1 finishing case on iOS 27

* Scope missing unfinished fixture exception to iOS and tvOS 27

* Allow persisted ad events to finish posting in integration test

* Clarify async test failure handling comment [skip ci]

* Centralize StoreKit test exceptions and transaction helpers

* Check live StoreKit identifiers when waiting for queue removal [skip ci]

* Verify virtual currency purchases finish with their StoreKit identifiers

* [5/7] Prepare local StoreKit sessions for Maestro tests (#7743)

* Prepare local StoreKit sessions for Maestro tests [skip ci]

* Wait for StoreKit warmup before Maestro purchase flows

* Pass Maestro warmup flags as UserDefaults launch arguments [skip ci]

* [6/7] Update tvOS and backend snapshot references (#7744)

* Add tvOS 27 snapshot references [skip ci]

* Update load-shedder snapshot for price variable compatibility

* [7/7] Extend Xcode 27 validation across CI jobs (#7745)

* Extend Xcode 27 validation across CI jobs

* Align backend test device default with iOS 27

* Keep CI cleanup running when simulator environment restoration fails

* Address Bugbot findings on the Xcode 27 CI jobs

- Wait for eligibility warmups before asserting Purchases does not leak.
  Teardown blocks run LIFO, so the leak check was running first while
  in-flight warmup work could still retain the instances.
- Let the RevenueCatUI lane opt out of test diagnostics collection via
  RC_COLLECT_TEST_DIAGNOSTICS, and set it on the iOS 27 UI job. That lane
  uses xcodebuild directly, so it never picked up SCAN_XCARGS like the
  other Xcode 27 jobs, and can hang in simctl diagnose after tests pass.
- Skip the StoreKitTest preflight when generating snapshots on iOS, as
  tvOS already does. run-test-ios-27 sets RC_STOREKIT_TEST_PREFLIGHT
  unconditionally and also runs in the generate-snapshot workflow.
- Apply the Xcode 27 validation xcconfig to installation-tests-carthage.
  It defaults to Xcode 27 but was the only source-building job missing
  the supported deployment targets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Clear the singleton before waiting on eligibility warmups

The previous commit reordered the teardown blocks so warmups drained
before the leak check. That timed out after 60s in the backend
integration tests: the warmup tasks only finish once
`Purchases.clearSingleton()` runs, so waiting on them first deadlocks.

Collapse both into a single teardown block with the steps in the order
they actually depend on: clear the singleton, drain the warmups, then
assert nothing leaked. This still keeps the leak assertion after the
warmups, which is what the original ordering got wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* other: re-enable storefront tests on iOS 27.2+ (#7797)

* re-enable storefront tests on iOS 27.2+

* copy update

* Restore the original eligibility warmup teardown order

Bugbot flagged the LIFO teardown ordering here as a bug, but its premise
does not hold: the warmup tasks capture `PaywallCacheWarming`, which
`Purchases` owns, so they never retain a `Purchases` instance and cannot
make the leak check fail.

Both reorderings tried in this branch made the backend integration tests
time out after 60s waiting on warmups that only drain once the instance
is torn down. Restore the original order and document why it is the way
it is, so the next reader does not make the same change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Wait for finish() to settle in testFiltersOutFinishedTransaction

`run-test-ios-27` flaked here: the fetcher reported both the finished and
the unfinished fixture. `finish()` is eventually consistent on OS 27, so
the finished transaction can still be listed in `Transaction.unfinished`
after it returns.

Wait for the unfinished queue to settle before reading the fetcher, the
same way the sibling fixtures in this file already do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Retry failing macOS unit tests, like the iOS and tvOS lanes do

`run-test-tvos-and-macos` moves to Xcode 27 in this branch and the macOS
step has been failing intermittently since. It is a different test each
time (the plan runs in a random order) and the failures are timing ones:
"main run loop was unresponsive", a redirect returning no response.

The iOS and tvOS lanes already absorb this with `scan`'s 5 retries. This
lane drives xcodebuild directly and had none, which is why it is the only
one that goes red. Ask xcodebuild to retry the failing tests instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Don't fail backend integration tests on stalled OS 27 eligibility warmups

This branch moves the backend integration jobs to Xcode 27 / iOS 27, and
since then a different test goes red on most runs with "Eligibility cache
warmup did not finish after 60s". Intro eligibility under StoreKitTest is
already known to misbehave on OS 27 (PR #7718, and the OS 27 exceptions in
AvailabilityChecks), and because the paywall cache is an actor, one stalled
check blocks every warmup queued behind it -- hence the count never being 1.

The wait is isolation hygiene, not a product assertion, so on OS 27 it now
warns with the pending count instead of failing an unrelated test. Every
other OS keeps the hard assertion, as do the unit tests that use the
tracker directly: leniency is opt-in via `failOnTimeout`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: Josh Holtz <me@joshholtz.com>
Co-authored-by: Antonio Pallares <ajpallares@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 14:34:04 -05:00
..