Skip to content

DeallocTests 4.0: dependency injection as a package trait, deprecate DeallocTester - #21

Open
DanielCech wants to merge 9 commits into
dc/feat/leak-hints-swiftuifrom
dc/feat/v4
Open

DanielCech wants to merge 9 commits into
dc/feat/leak-hints-swiftuifrom
dc/feat/v4

Conversation

@DanielCech

@DanielCech DanielCech commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Stacked on #20 → #19 → #18. Merge those first. GitHub will then retarget this PR to master.

⚠️ This is the breaking 4.0 release. The README has a "Migrating to 4.0" section.

Why

Since the beginning, DeallocTests has come in two flavours: DeallocTests (with STRV Dependency Injection) and DeallocTestsDIFree. They were built from one symlinked source folder with conditional compilation. That has three costs:

  • Everyone downloads STRV's DI library, even people who never use it.
  • The symlink and conditional-compilation setup is fragile (Reliable dealloc checks, real tests and CI (3.1) #18 already had to work around it).
  • Two products with two module names confuse people, and the README had to explain which one to pick.

Swift 6.1 added package traits, the official way to make part of a package optional. And since #19, there's a simpler API (expectDeallocation) that makes the original DeallocTester unnecessary.

What changes for users

One product; dependency injection is on by default, with an opt-out

There's now a single DeallocTests product. STRV Dependency Injection support is the DependencyInjection trait, which is on by default, because most of our projects use it. Projects that don't can turn it off:

// Most projects: nothing to configure, STRV Dependency Injection support included
.package(url: "https://github.com/strvcom/DeallocTests.git", from: "4.0.0")

// Projects without STRV Dependency Injection: the dependency isn't even downloaded
.package(url: "https://github.com/strvcom/DeallocTests.git", from: "4.0.0", traits: [])

What I verified:

  • SwiftPM: with a test consumer package, the default pins ios-dependency-injection, and traits: [] creates no pin at all.
  • Xcode projects: the DI sample uses the default, and Xcode checks out the DI package. The DIFree sample has an empty traits = ( ); on its package reference, and Xcode doesn't check it out at all. There's a known report of Xcode ignoring disabled default traits in some workspace setups, but it doesn't affect a regular project with a package reference.
  • Turning the trait off from an Xcode project needs Xcode 26.4 or later. The common case (on) needs no configuration, so it works with any Xcode that supports Swift 6.1.

DeallocTester is deprecated

DeallocTester, DeallocTest and DeallocTestable still work, but they show deprecation warnings pointing to expectDeallocation. They'll be removed in 5.0. The README has a before/after example and a table mapping every old feature to its replacement (checkClasses → trackForDeallocation, actionBeforeCheck → afterRelease, and so on).

Removed

  • The DeallocTestsDIFree product. Use DeallocTests and import DeallocTests.
  • DefaultInitializable (deprecated in 3.1, unrelated to dealloc testing).

New minimums

  • Swift 6.1 / Xcode 16.3 (needed for package traits).
  • Xcode 26.4 only if you enable the trait from an Xcode project.

Sample apps

  • DeallocTestsAppDIFreeSPM is now the XCTest example of expectDeallocation. It has no conformances file, and each screen is a one-line test. The deliberate leak is reported on the test's line with the someClosure hint.
  • DeallocTestsAppDIFreeSPM also shows the opt-out: its Xcode project turns the trait off.
  • DeallocTestsAppSPM uses the default trait and is the Swift Testing example. Its DeallocTester tests stay as a reference for the deprecated API.

Tests and CI

  • swift test passes in the default configuration and with --disable-default-traits (25 and 22 Swift Testing tests plus the XCTest suites). The library builds with zero warnings in both.
  • iOS simulator package tests pass (28 Swift Testing tests).
  • Both sample apps tested on the simulator. The only failure is the intentional leak.
  • CI now runs the macOS tests in both trait configurations. The sample builds moved to the macos-26 runner, because traits in an Xcode project need Xcode 26.4.

Upgrading STRV projects

  • Projects using DeallocTests with STRV Dependency Injection: bump to 4.0. Nothing else is required, but DeallocTester tests now show deprecation warnings.
  • Projects using DeallocTestsDIFree: switch to the DeallocTests product and import DeallocTests, and turn off the default trait.

🤖 Generated with Claude Code

DanielCech and others added 9 commits October 2, 2026 16:20
- One DeallocTests target; STRV Dependency Injection behind the
  DependencyInjection trait (off by default, not downloaded when off)
- Remove the DeallocTestsDIFree product and the symlinked target
- Require Swift 6.1 (tools-version 6.1)
- Deprecate DeallocTester, DeallocTest, DeallocTestable, ClassNameIdentifiable
- Remove DefaultInitializable
- DIFree sample uses expectDeallocation with XCTest; DI sample enables the trait
- CI tests both trait configurations, samples build on macos-26
- README migration guide, CHANGELOG

BREAKING CHANGE: DeallocTestsDIFree is removed and DI support requires the
DependencyInjection trait.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Most projects use STRV Dependency Injection, so it's on by default.
Projects that don't use it opt out with `traits: []` (or an empty traits
list in an Xcode project) and the dependency isn't downloaded.

- DI sample relies on the default trait
- DIFree sample opts out in its Xcode project
- CI tests the default and --disable-default-traits

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Swift names private and local types Module.(unknown context at $…).Name.
Found while adopting DeallocTests in JustFlip.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The message named the protocol twice ("CreateDeckUseCase resolved for
CreateDeckUseCase") and didn't say why the check is skipped. Found while
adopting DeallocTests in JustFlip, whose deck use cases are structs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Hints only inspect the leaked object's own properties. In JustFlip, a
SettingsCoordinator kept alive by its parent's childCoordinators got a hint
about its own cancellables; the message now also says the object may be
held from outside (a parent's list of children, a cache, a singleton).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@observable stores each property as _name and adds _$observationRegistrar.
In JustFlip an @observable store's hint read `_cancellables`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
In JustFlip's full test run, a host window sometimes took longer than 2 s to
appear on a loaded simulator, failing the check with 'The test window could
not be shown'. The waits return as soon as the state is reached.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DeallocTests only uses AsyncContainer's init, clean(), releaseSharedInstances()
and resolve(type:). JustFlip, on DI 1.x, needed a revision override to adopt
DeallocTests 4; verified with a consumer pinned to DI 1.0.4.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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