Skip to content

[SDTEST-3771] Feature factories call the test-optimization API directly#260

Merged
ypopovych merged 4 commits into
mainfrom
sdtest-3771-feature-factories-use-api-directly
May 26, 2026
Merged

[SDTEST-3771] Feature factories call the test-optimization API directly#260
ypopovych merged 4 commits into
mainfrom
sdtest-3771-feature-factories-use-api-directly

Conversation

@ypopovych

@ypopovych ypopovych commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Each feature factory (KnownTests, TestManagement, TestImpactAnalysis, GitUploader) takes the specific *Api it needs and calls it via try await; the tracerSettings call in DDTestMonitor does the same.
  • FeatureFactory.create(log:) is now async throws -> FT (non-optional). Factories throw on backend errors; DDTestMonitor runs them through a single runFactory(_:errorKind:) helper that maps the throw to LibraryConfigurationErrors.
  • TestOptimizationApi (and its Settings/KnownTests/Git/TIA/TestManagement/Spans/Logs/Telemetry sub-APIs), APICallError, and TestOptimizationApiService are now public. The SDK builds the service once and stores it on DDTracer.api (non-optional); the exporter no longer exposes the API.
  • LibraryConfigurationCommunicationError moved from EventsExporter into the main SDK (internal). The corresponding tests moved with it; Tests/EventsExporter/LibraryConfigurationErrorsTests.swift keeps only the HTTPClient.RequestError description tests.
  • EventsExporter is back to being a pure spans/logs/coverage pipeline: removed tracerSettings / skippableTests / knownTests / testManagementTests / searchCommits / uploadPackFiles helpers, the endpointURLs rollup, the api property, and the identity fields from ExporterConfiguration (which now carries only environment, metadata, performancePreset, and logger).
  • CodeCoverageProvider.init is throws instead of init?; Logger.measure overloads use typed throws (throws(E)).

Concurrency: closure-scoped writable file

The upload worker reads-and-deletes files on its own queue. Under the
test .readAllFiles preset (minFileAgeForRead == -1, every write rolls
over to a new file) it used to be able to read a freshly-created file
before the writer had appended its bytes — and delete it. The writer's
later append would either lose the value or, after the user's optimisation
that runs the purge step outside the lock, the purge step itself would
delete the file the writer was about to use.

FilesOrchestrator now exposes a closure-scoped writer:

func withWritableFile<T>(writeSize: UInt64,
                         _ body: (WritableFile, _ isNew: Bool) throws -> T) throws -> T

While body runs, the chosen file is "claimed" — the reader's
getReadableFile / getAllReadableFiles filter it out. All mutable
state lives on a private State struct held inside Synced<State>, so
state-touching helpers are reachable only via Synced.use / Synced.update
— the lock is enforced by construction.

A few subtleties that matter:

  • Active writes are tracked by file name (Set<String>), not URL.
    Directory.createFile(...) and FileManager.contentsOfDirectory(at:)
    can return URLs that differ on macOS by /var vs /private/var
    canonicalisation, so Set<URL>.contains(...) is unreliable.
  • File names use millisecond-resolution timestamps. Two writes that
    land in the same millisecond would collide on the path, and
    FileManager.createFile(atPath:contents:attributes:) silently
    overwrites an existing file. The orchestrator bumps the timestamp
    until the name is unused.
  • The retry-write fix in FileWriter was reverted: with proper
    synchronisation, write retries aren't needed.

Test plan

  • xcodebuild -scheme EventsExporter -sdk macosx -destination 'platform=macOS,arch=arm64' test — 91 tests pass (10/10 consecutive runs)
  • xcodebuild -scheme DatadogSDKTesting -sdk macosx -destination 'platform=macOS,arch=arm64' test — 373 tests pass
  • iOS Simulator build of DatadogSDKTesting succeeds
  • Run integration tests in CI

🤖 Generated with Claude Code

Replace the EventsExporter helper-method surface with direct API access
inside each feature factory. The SDK now owns the TestOptimizationApi,
factories are async/throws, and the exporter is back to being a pure
spans/logs/coverage pipeline.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@ypopovych
ypopovych requested review from a team as code owners May 25, 2026 15:53
@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented May 25, 2026

Copy link
Copy Markdown

Pipelines

Fix all issues with BitsAI

⚠️ Warnings

🚦 3 Pipeline jobs failed

Integration Tests | Xcode_26.2 / iOSsim   View in Datadog   GitHub Actions

🔄 Retry job. This looks flaky and may succeed on retry. Test runner timed out while preparing to run tests.

Integration Tests | Xcode_26.2 / visionOSsim   View in Datadog   GitHub Actions

🔄 Retry job. This looks flaky and may succeed on retry. The test runner timed out while preparing to run tests.

Integration Tests | Xcode_26.4 / visionOSsim   View in Datadog   GitHub Actions

🔄 Retry job. This looks flaky and may succeed on retry. The test runner timed out while preparing to run tests.

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 7fdd1b4 | Docs | Datadog PR Page | Give us feedback!

ypopovych and others added 3 commits May 25, 2026 17:26
The upload worker reads-and-deletes files on its own queue. With the
test `.readAllFiles` performance preset (minFileAgeForRead = -1) the
worker can pick up a writable file in between FilesOrchestrator
`getWritableFile` (which has just created it) and the actual `append`.
The append then throws ENOENT and the value is lost via
`LogsExporter.writeLog`'s `try?`. Recovery: drop the dangling
`_currentFile` reference and retry once with a fresh file. Only the
missing-file error triggers the retry; permission/disk-full errors
still propagate so callers can react.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Synchronise the writer and the upload-worker reader through the
orchestrator instead of retrying writes that lose their file out
from under them.

- Replace `getWritableFile` with `withWritableFile(writeSize:body:)`.
  While `body` runs, the chosen file is "claimed" — the reader's
  `getReadableFile` / `getAllReadableFiles` filter it out, so a
  partial file can never be read-and-deleted by the upload worker
  mid-append.
- Move every state-touching helper onto a private `State` struct
  held inside `Synced<State>`. Locked methods are reachable only
  through `Synced.use(_:)` / `Synced.update(_:)`, so the lock is
  enforced by construction rather than by naming convention.
- Track active writes by file name (`Set<String>`) instead of URL —
  `Directory.createFile(...)` and `FileManager.contentsOfDirectory(at:)`
  can return URLs that differ in `/var` vs `/private/var`
  canonicalisation, which breaks `Set<URL>.contains(...)`. File
  names are unique within the directory and stable.
- Bump the millisecond-resolution creation timestamp until the file
  name is unused so two writes in the same millisecond never
  collide on a path and silently overwrite each other.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The UITests and UnitTests test plans were missing GITHUB_BASE_REF,
GITHUB_EVENT_PATH, GITHUB_JOB, GITHUB_RUN_ATTEMPT, GITHUB_SERVER_URL,
and JOB_CHECK_RUN_ID — vars the IntegrationTests parent plan already
forwards. Add them and reorder so all three plans group the CI-identity
variables identically (GITHUB_* in the same order, then JOB_CHECK_RUN_ID,
then DD_TEST_RUNNER / DD_API_KEY / SRCROOT, then the DD_* settings, then
plan-specific feature flags).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@ypopovych
ypopovych merged commit 330a43d into main May 26, 2026
41 of 81 checks passed
@ypopovych
ypopovych deleted the sdtest-3771-feature-factories-use-api-directly branch May 26, 2026 13:48
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.

2 participants