-
Notifications
You must be signed in to change notification settings - Fork 7
Comparing changes
Open a pull request
base repository: DataDog/dd-sdk-swift-testing
base: 2.7.5
head repository: DataDog/dd-sdk-swift-testing
compare: 2.7.7-alpha1
- 9 commits
- 30 files changed
- 3 contributors
Commits on Jun 19, 2026
-
Fix flush race: seal writer on shutdown so in-flight writes aren't lo…
…st (#284) * Fix flush race: seal writer on shutdown so in-flight writes aren't lost The final flush at shutdown could skip (and permanently lose) a file that was being written concurrently. `FilesOrchestrator` hides files in `activeWrites` from the reader so the periodic upload worker never reads a half-written file. That's correct mid-session — the file is picked up on the next cycle — but at shutdown there is no next cycle: the worker is stopped right after the flush, so a file still in `activeWrites` during the final enumeration is stranded on disk, never uploaded. The window was a write submitted concurrently with the flush. Writes submitted before flush were already safe (serial writer queue + barrier in `closeCurrentFile`). Fix: - FileWriter.stop(): permanently seals the writer — drains in-flight writes via the serial-queue barrier, finalizes and closes the current file, then rejects all subsequent writes (logging the dropped event, with its encoded payload, for debugging). - FeatureStoreAndUpload.stop(): ordered teardown — stop scheduling uploads, then seal the writer, then run the final exhaustive flush. After sealing, no file can be in `activeWrites`, so the flush enumerates a complete set. - Exporter.shutdown(): drop the redundant pre-shutdown flush; each storage's stop() now performs the complete, race-free upload. A pre-stop flush could skip a file still being written that stop() would then never upload. - DDTracer.shutdown(): no longer pre-flushes — the OTel processor shutdown drains pending spans into the exporter (verified for both Simple and Batch processors) and the providers flush spans/logs/telemetry properly. Only the cross-process RUM port flush is forwarded, extracted into flushRUM(). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * Inject logger into FileWriter; add stop()/seal regression tests FileWriter now takes a `log: Logger` (like DataUploadWorker) instead of the global `Log`, so callers pass the logger from the top and tests can capture log output without mutating global state. Wired `configuration.logger` / `config.logger` through at all four construction sites (spans, logs, telemetry, coverage). Tests (FileWriterTests): - write after stop() is dropped and logged with the encoded payload (asserted via an injected RecordingLogger) - stop() drains in-flight async writes before sealing - after stop(), getAllReadableFiles() enumerates the complete set — nothing hidden in activeWrites, modelling the final shutdown flush Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * move network auto-instrumentation initialisation to the queue * increase priorities of the setup queues --------- Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Configuration menu - View commit details
-
Copy full SHA for c211bea - Browse repository at this point
Copy the full SHA c211beaView commit details -
Seal active test/suite/module as failed on premature process exit (#285)
When the test process exits without XCTest delivering the completion hooks (testCaseDidFinish / testRetryGroupDidFinish / testSuiteDidFinish / testBundleDidFinish), the in-flight suite and test spans were never ended and so were dropped from the trace. This is not a crash — the process exits in an orderly way (e.g. a test calling exit(), or a failure in an async setUp/tearDown under recent Xcode), so the unload hook still runs and the session/module spans close, but everything below the module leaks. DDXCTestObserver.stop() previously did nothing (default: break) for the .suite/.group/.test states. It now detects the premature exit and: - logs a generic premature-exit warning (no vendor-specific attribution, since a user exit() produces the identical fault), - force-closes the open test and suite as failed with an explanatory PrematureTestProcessExit error, and - ends the module as testBundleDidFinish would; the failure propagates up to the session. State is extended so .group/.test carry the references needed to recover from stop() (previously they held none). Suite-end mirrors testSuiteDidFinish but skips the feature hooks, which can't run meaningfully at process exit. Adds a synchronous regression test driving the observer into .test and forcing shutdown. It is intentionally sync: stop() unregisters the observer, which XCTest only allows on the main thread, and async test variants either crash off-main or deadlock against XCTest's main-thread wait. SDTEST-3844 Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>Configuration menu - View commit details
-
Copy full SHA for e49c497 - Browse repository at this point
Copy the full SHA e49c497View commit details
Commits on Jun 23, 2026
-
Fix non-deterministic JSON key order causing Swift Testing args to be…
… misidentified (#286) * Fix non-deterministic JSON key order causing Swift Testing args to be misidentified Add .sortedKeys to apiEncoder outputFormatting so test argument dictionaries always serialize in a consistent order, preventing the same test from being treated as different tests across runs. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * Fix the actual source of non-deterministic argument serialization in Models.swift The apiEncoder change fixed key ordering in API payloads, but the test parameters tag is encoded via a separate plain JSONEncoder() in set(parameters:). Add .sortedKeys there so the JSONGeneric dictionary keys ("name", "value", "type") always appear in a stable order in test.parameters. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * Use apiEncoder for test parameters serialization Replace the standalone JSONEncoder with the shared apiEncoder which already has .sortedKeys (and other settings) configured. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * Simplify parameterized test assertions: drop TestParameters, compare raw JSON With apiEncoder's .sortedKeys the serialized test.parameters tag is fully deterministic, so there is no need to decode into a typed struct. Sort the raw JSON strings (lexicographic order matches the p1 int values) and compare directly against expected string literals. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * Make apiEncoder/apiDecoder public so cross-module callers can use them Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Configuration menu - View commit details
-
Copy full SHA for 174c938 - Browse repository at this point
Copy the full SHA 174c938View commit details -
Configuration menu - View commit details
-
Copy full SHA for 3dd5144 - Browse repository at this point
Copy the full SHA 3dd5144View commit details
Commits on Jul 15, 2026
-
Make retriable and skippable Swift Testing tags public (#288)
The XCTest tag properties (.retriable, .tiaSkippable) are already public, but their Swift Testing equivalents (Tag.dd.retriable/nonretriable and Tag.dd.tia.skippable/unskippable) were internal, so cross-module callers could not apply them via @test(.tags(...)). Co-authored-by: Claude Sonnet 5 <[email protected]>
Configuration menu - View commit details
-
Copy full SHA for 9969926 - Browse repository at this point
Copy the full SHA 9969926View commit details -
Bound flush() retries so a persistently-retriable upload can't hang t…
…eardown (#289) * Bound flush() retries so a persistently-retriable upload can't hang teardown The synchronous `flush()` on the shutdown path retried a `.retry` upload result in an unbounded loop. A persistently retriable server (503/500/ 408/429) or a downed network — both surface as `needsRetry` — would loop forever, hanging process teardown. Cap flush retries at 3 (1 initial attempt + 3 retries). On giving up, report failure and leave the undelivered batch on disk for a later run. The background worker's indefinite cross-tick retry behaviour is unchanged; only the bounded shutdown flush is affected. Adds a regression test that a persistently-retriable upload returns failure instead of hanging, attempts exactly 4 uploads, and leaves the batch on disk. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * Fix flaky upload count in flush give-up test `testFlushGivesUpAfterMaxRetriesInsteadOfHanging` flushed while the worker was still live, so its scheduled background tick fired right after flush released the queue and added a stray 5th upload (expected 4, got 5). Stop the worker before flushing, matching the production shutdown order in `Feature.stop()` (stop periodic uploads, seal writer, then final flush) so `flush()` is the only thing uploading the batch. Deterministic 4 uploads. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Configuration menu - View commit details
-
Copy full SHA for 548f253 - Browse repository at this point
Copy the full SHA 548f253View commit details -
Remove XCTest observer on the main thread during framework unload (#291)
`XCTestObservationCenter.removeTestObserver` is main-thread-only. The observer is added from the load-time constructor (`__AutoLoadHook`, always main) but removed from the unload-time destructor (`__AutoUnloadHook`), whose thread depends on who calls `exit()`. The classic `xctest` runner exits on the main thread, but the swift-testing runner exits from a Swift Concurrency continuation on a background thread — so `removeTestObserver` ran off the main thread and raised `NSInternalInconsistencyException: "Test observers can only be registered and unregistered on the main thread."` at process teardown. `removeObserver()` now removes on the main thread: directly when already on it, otherwise via `DispatchQueue.main.async`. The hop is async on purpose — a synchronous hop can deadlock during teardown if the main thread is already finalizing, and if the block never runs because the process exits first that's harmless (the observer only needs detaching while the runner is alive). Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Configuration menu - View commit details
-
Copy full SHA for bd6b17f - Browse repository at this point
Copy the full SHA bd6b17fView commit details -
Hide bundled dependency symbols to prevent OpenTelemetry type clashes (…
…#290) * Hide bundled dependency symbols to prevent OpenTelemetry type clashes The framework statically links OpenTelemetry (api/sdk), KSCrash, Kronos, SigmaSwiftStatistics and swift-code-coverage and exported all of their symbols. When a project-under-test links its own copy of the same dependency (e.g. opentelemetry-swift at a different version), dyld coalesces the duplicate weak type-metadata symbols to a single process-wide definition. Mismatched layouts then make one image read objects through the other's metadata, crashing in swift_getObjectType (observed via OpenTelemetryContextProvider.setActiveSpan). Mark every bundled-dependency symbol non-exported via UNEXPORTED_SYMBOLS_FILE so each image binds its own copy and dyld can't coalesce them. Only the public DatadogSDKTesting API stays exported (762 symbols vs ~11,240 before). Also drop DDInstrumentationControl.openTelemetryTracer and its README section: with OpenTelemetry now fully private, the documented `as? Tracer` interop can no longer work, so the hook is removed. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * Scope symbol hiding to Release so unit tests still build `UNEXPORTED_SYMBOLS_FILE` was set on both Debug and Release configs of the framework target. The unit tests (`DatadogSDKTestingTests`) `@testable import DatadogSDKTesting` and reference internal symbols whose mangled names embed dependency types — e.g. `DDTracer.createSpanFromCrash(...) -> OpenTelemetrySdk.SpanSdk` and `EarlyFlakeDetection.init(... EventsExporter .TracerSettings...)`. The substring hide-patterns matched those, so the Debug framework stopped exporting them and the test bundle failed to link (undefined symbols). Hiding only matters for the shipped artifact, which is archived in Release. Move `UNEXPORTED_SYMBOLS_FILE` to the Release config only: tests build in Debug (all symbols exported, link OK) and the distributed xcframework is archived in Release (dependency symbols hidden). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * Run integration tests against the stripped framework The internal integration test targets should validate the framework as it ships — with bundled-dependency symbols stripped. The outer Runner (`IntegrationTests`) keeps `@testable`/Debug access, and it builds the inner consumer targets it drives, so it now passes `-configuration Release` to those nested `xcodebuild` invocations. Release is where `UNEXPORTED_SYMBOLS_FILE` applies, so the inner targets link the stripped framework. Rework the inner smoke tests into pure public-API consumers: drop the `OpenTelemetry.instance.loggerProvider` bridge (and the Runner assertions that the OTel LogRecord reached the backend). That path only works when a consumer shares the SDK's OpenTelemetry instance, which is exactly what stripping makes private — so it isn't available to a real consumer of the shipped framework. stdout capture, per-test spans and code coverage (which don't need shared OTel) still run and are still asserted. Note: a genuine second in-process OpenTelemetry copy (the coalescing repro) can't be produced here — SwiftPM dedupes the shared package product into one dynamic framework, which also breaks the framework's own static OTel linkage. That scenario is covered by dd-sdk-ios in CI, whose OTel is a separate binary. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * Add Integration build config: Debug-speed but stripped Building the inner integration targets in Release (to get the stripped framework) also dragged in -O/WMO/LTO: slow builds and optimized, hard-to-debug test failures — none of which is relevant to what these tests validate (symbol isolation, not optimization). Add an `Integration` build configuration, copied from Debug, that keeps `-Onone`/`GCC_OPTIMIZATION_LEVEL=0` (fast, debuggable) but applies the framework's `UNEXPORTED_SYMBOLS_FILE` and sets `ENABLE_TESTABILITY=NO`. The result is a framework whose exported symbol surface matches the shipped Release build exactly — 0 bundled-dependency symbols (OpenTelemetry/KSCrash/Kronos/Sigma/EventsExporter), 745 public — without the Release optimizer overhead. The Runner now builds the inner consumer targets with `-configuration Integration` instead of `Release`. Config matrix for the framework: - Debug: no strip, testability on (unit tests, @testable) - Integration: strip, testability off (integration tests, Debug-speed) - Release: strip (shipped xcframework) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Configuration menu - View commit details
-
Copy full SHA for 4507dad - Browse repository at this point
Copy the full SHA 4507dadView commit details
Commits on Jul 16, 2026
-
Configuration menu - View commit details
-
Copy full SHA for bbb0f5e - Browse repository at this point
Copy the full SHA bbb0f5eView commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff 2.7.5...2.7.7-alpha1