fix(test): make the subscription-lapse test deterministic - #14
Merged
Merged
Conversation
SunHat.storekit enables _billingGracePeriodEnabled, and makeSession() calls resetToDefaultState(), which restores it for every test. With grace period on, expireSubscription() is ambiguous: the subscription can enter a grace period instead of lapsing, and grace keeps the transaction in Transaction.currentEntitlements with its ORIGINAL expiration date. The manager then correctly reports .active and the lapse assertions fail. The failure was bimodal, which is what gave it away — the test either converged in under 0.3s or never converged at all, while the other five StoreKit tests in the same run finished in 0.02-0.15s each. A starved process would have slowed all of them. - makeSession() now clears billingGracePeriodIsEnabled and shouldEnterBillingRetryOnRenewal, so "expire" means "lapse" here. Grace-period and billing-retry mapping stay covered by three tests in AdFreeEntitlementResolverTests, which drive the resolver directly. - refreshUntil() polls against a 20s wall-clock deadline instead of 50 fixed attempts, so machine load can't clip the budget. The fast path still returns as soon as the state converges.
StoreManagerStoreKitTests hung for a full 40 minute run budget on this machine while its .serialized siblings sat waiting behind it, so the run ended with no results at all instead of one red suite. Added .timeLimit(.minutes(2)) so a hang now costs two minutes instead of the whole run.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
StoreManagerStoreKitTests.expirationDropsEntitlementfailed intermittently on theSunHatUnitTestsscheme while passing onSunHat— not a scheme difference, and not machine load.Root cause.
SunHat.storekitsets_billingGracePeriodEnabled: true, andmakeSession()callsresetToDefaultState(), which restores it for every test. With grace period on,session.expireSubscription(...)is ambiguous: the subscription can enter a grace period instead of lapsing. Grace keeps the transaction inTransaction.currentEntitlementswith its original expiration date — which is exactly the failure signature we saw: state.active,activeProductIDstill monthly,expirationDate = 2026-10-06, the untouched original expiry one month out from the purchase.The manager was behaving correctly. The test was asserting a lapse against a session that had entered grace.
What ruled out a timeout. The failure was bimodal:
Sub-second or never. A starved process would have slowed the whole suite, and it didn't.
Fix.
makeSession()clearsbillingGracePeriodIsEnabledandshouldEnterBillingRetryOnRenewal, so "expire" means "lapse" in this suite. Grace-period and billing-retry mapping stay covered by three tests inAdFreeEntitlementResolverTeststhat drive the resolver directly.refreshUntil()polls against a 20 s wall-clock deadline instead of 50 fixed attempts, so load can't clip the budget. The fast path still returns as soon as state converges.Test-only change; no production code touched.