Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: DataDog/dd-sdk-swift-testing
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2.7.5
Choose a base ref
...
head repository: DataDog/dd-sdk-swift-testing
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 2.7.7-alpha1
Choose a head ref
  • 9 commits
  • 30 files changed
  • 3 contributors

Commits on Jun 19, 2026

  1. 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]>
    ypopovych and claude authored Jun 19, 2026
    Configuration menu
    Copy the full SHA
    c211bea View commit details
    Browse the repository at this point in the history
  2. 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]>
    ypopovych and claude authored Jun 19, 2026
    Configuration menu
    Copy the full SHA
    e49c497 View commit details
    Browse the repository at this point in the history

Commits on Jun 23, 2026

  1. 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]>
    ypopovych and claude authored Jun 23, 2026
    Configuration menu
    Copy the full SHA
    174c938 View commit details
    Browse the repository at this point in the history
  2. [CHORE] Release v2.7.5

    ypopovych authored Jun 23, 2026
    Configuration menu
    Copy the full SHA
    3dd5144 View commit details
    Browse the repository at this point in the history

Commits on Jul 15, 2026

  1. 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]>
    ypopovych and claude authored Jul 15, 2026
    Configuration menu
    Copy the full SHA
    9969926 View commit details
    Browse the repository at this point in the history
  2. 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]>
    ypopovych and claude authored Jul 15, 2026
    Configuration menu
    Copy the full SHA
    548f253 View commit details
    Browse the repository at this point in the history
  3. 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]>
    ypopovych and claude authored Jul 15, 2026
    Configuration menu
    Copy the full SHA
    bd6b17f View commit details
    Browse the repository at this point in the history
  4. 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]>
    ypopovych and claude authored Jul 15, 2026
    Configuration menu
    Copy the full SHA
    4507dad View commit details
    Browse the repository at this point in the history

Commits on Jul 16, 2026

  1. Configuration menu
    Copy the full SHA
    bbb0f5e View commit details
    Browse the repository at this point in the history
Loading