Hi there,
I’m investigating an issue where tuist test crashes in a workspace that contains an app plus a framework module with host-less unit tests (a .unitTests target with no TEST_HOST).
(example project with reprducible issue is attached below)
Bare tuist test runs the auto-generated <Workspace>-Workspace scheme. That scheme’s launch runnable is the app and its test action has shouldUseLaunchSchemeArgsEnv = YES, so the host-less test bundle runs inside the app’s launch context, against a build-products directory that also contains all of the app’s dependencies. The xctest runner then crashes during bootstrap, before any test runs:
xctest (NNNNN) encountered an error (Early unexpected exit, operation never finished
bootstrapping - no restart will be attempted. (Underlying Error: The test runner crashed
while preparing to run tests: xctest at <external symbol>))
** TEST FAILED ** (xcodebuild exit code 65)
I narrowed the trigger to one dependency: if the app links point-free swift-sharing, the host-less test process dlopens Sharing.framework (and its SwiftUI → AppIntents → LocalStatusKit closure) at runtime — even though neither the test bundle nor its module links it — and crashes in XCTest’s class-realization. Remove Sharing from the products directory and it passes.
This repo reproduces it deterministically. I’m posting to ask whether the behavior — running a host-less module test in the app launch context, against the app’s full products directory — is expected, and what the supported way to isolate it is.
Environment
- Tuist 4.198.1 (also reproduces on 4.152.0)
- Xcode 26.3 (17C528)
- Simulator: iPhone 17 Pro, iOS 26.4
swift-sharing2.8.1 (latest version), built as a dynamic.framework(seeTuist/Package.swift)- Host-less unit tests written with Swift Testing (XCTest behaves the same)
Reproduction
mise install # Tuist 4.198.1
tuist install
tuist test # runs the auto-generated Sample-Workspace scheme -> CRASHES (exit 65)
Contrast — the same module test passes when Sharing.framework is not in the products directory (the module’s own scheme, built into a clean DerivedData):
xcodebuild test -scheme Feature -workspace Sample.xcworkspace \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.4' \
-derivedDataPath /tmp/clean-dd # -> TEST SUCCEEDED
Bare tuist test runs the auto-generated Sample-Workspace scheme, whose generated .xcscheme has:
<LaunchAction ...>
<BuildableProductRunnable>
<BuildableReference ... BuildableName = "App.app" .../> <!-- the app is the runnable -->
</BuildableProductRunnable>
</LaunchAction>
<TestAction ... shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference><!-- AppTests.xctest (app-hosted) --></TestableReference>
<TestableReference><!-- FeatureTests.xctest (host-less) --></TestableReference>
</Testables>
</TestAction>
What happens
FeatureTests links almost nothing — otool -L shows only:
FeatureTests.xctest: XCTest, libXCTestSwiftSupport.dylib, Feature.framework, Testing
Feature.framework: Foundation, libswiftCoreFoundation.dylib
…yet a DYLD_PRINT_LIBRARIES trace of that host-less process (run via the aggregate) shows it loading, post-launch, a large closure that is not linked by the bundle:
Sharing, Sharing1, Sharing2, PerceptionCore, Dependencies, CombineSchedulers, ...
SwiftUI → AppIntents → ActivityKit → StatusKit → LocalStatusKit → SystemWake
XCTest’s test discovery then enumerates every Obj-C/Swift class in the process and crashes realizing a class in the system framework LocalStatusKit:
_XCTestMain
-[XCTestDriver _runTests]
+[XCTestCase(RuntimeUtilities) _allSubclasses]
objc_copyClassList → realizeAllClasses()
<class in LocalStatusKit.framework>
swift_getSingletonMetadata → _swift_relocateClassMetadata
computeMetadataBoundsFromSuperclass(...) ← SIGSEGV (signal 11)
So the segfault itself is a downstream Apple XCTest + Swift class-realization crash. Tuist’s role is that bare tuist test runs the host-less bundle in the app launch context, against a products directory that contains the app’s swift-sharing, so that framework gets loaded into the otherwise Foundation-only logic-test process.
Key observations
- Passes when
Sharing.frameworkis absent from the products dir (module’s own scheme, clean DerivedData). Crashes when it is present (the aggregate builds the app that links it). - The trigger is specifically
swift-sharing. I bisected the app’s dependencies one at a time; linking onlySharingreproduces it, while the app’s other dependencies (Firebase, GoogleSignIn, Mixpanel, ComposableArchitecture, etc.) do not. -only-testing:FeatureTestson the aggregate scheme still crashes, so it isn’t caused by other test bundles running first — it’s the host-less bundle’s launch/discovery context.- Same (empty)
TEST_HOSTandDebugconfig in both the passing and crashing case; only the scheme (app launch context + the app’s products dir) differs.
Questions
- Is it expected that bare
tuist testruns host-less module unit tests through the auto-generated<Workspace>-Workspacescheme whose launch runnable is the app (shouldUseLaunchSchemeArgsEnv = YES), executing a Foundation-only logic test in the full app/SwiftUI runtime context and against the app’s full products directory? - Is there a supported way to make host-less module tests run isolated (their own scheme / not in the app launch context) under bare
tuist test, instead of scripting explicit per-schemexcodebuild/tuist test <scheme>invocations in CI? - Should the auto-generated workspace scheme avoid setting an app as the launch runnable for a test action that includes host-less test bundles?
Note: Workspace(generationOptions: .options(autogeneratedWorkspaceSchemes: .disabled)) removes the persisted <Workspace>-Workspace Xcode scheme (only on Tuist ≥ 4.198; it was a no-op on 4.152), but bare tuist test still synthesizes and runs a <Workspace>-Workspace test action, so it doesn’t change this behavior.
Probably also a point-free
The actual segfault is an Apple XCTest + Swift-metadata bug (realizing a LocalStatusKit class with unresolvable superclass metadata), triggered by swift-sharing being loaded into the test process. I’m filing here because Tuist controls whether the host-less module test runs in that context at all. I’ll cross-post to point-free as appropriate.
Thanks for your help!
tuist-hostless-test-repro.zip (25.6 KB)