[SDTEST-3771] Feature factories call the test-optimization API directly#260
Merged
Merged
Conversation
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]>
|
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]>
anmarchenko
approved these changes
May 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
KnownTests,TestManagement,TestImpactAnalysis,GitUploader) takes the specific*Apiit needs and calls it viatry await; thetracerSettingscall inDDTestMonitordoes the same.FeatureFactory.create(log:)is nowasync throws -> FT(non-optional). Factoriesthrowon backend errors;DDTestMonitorruns them through a singlerunFactory(_:errorKind:)helper that maps the throw toLibraryConfigurationErrors.TestOptimizationApi(and itsSettings/KnownTests/Git/TIA/TestManagement/Spans/Logs/Telemetrysub-APIs),APICallError, andTestOptimizationApiServiceare now public. The SDK builds the service once and stores it onDDTracer.api(non-optional); the exporter no longer exposes the API.LibraryConfigurationCommunicationErrormoved fromEventsExporterinto the main SDK (internal). The corresponding tests moved with it;Tests/EventsExporter/LibraryConfigurationErrorsTests.swiftkeeps only theHTTPClient.RequestErrordescription tests.EventsExporteris back to being a pure spans/logs/coverage pipeline: removedtracerSettings/skippableTests/knownTests/testManagementTests/searchCommits/uploadPackFileshelpers, theendpointURLsrollup, theapiproperty, and the identity fields fromExporterConfiguration(which now carries onlyenvironment,metadata,performancePreset, andlogger).CodeCoverageProvider.initisthrowsinstead ofinit?;Logger.measureoverloads use typed throws (throws(E)).Concurrency: closure-scoped writable file
The upload worker reads-and-deletes files on its own queue. Under the
test
.readAllFilespreset (minFileAgeForRead == -1, every write rollsover 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.
FilesOrchestratornow exposes a closure-scoped writer:While
bodyruns, the chosen file is "claimed" — the reader'sgetReadableFile/getAllReadableFilesfilter it out. All mutablestate lives on a private
Statestruct held insideSynced<State>, sostate-touching helpers are reachable only via
Synced.use/Synced.update— the lock is enforced by construction.
A few subtleties that matter:
Set<String>), not URL.Directory.createFile(...)andFileManager.contentsOfDirectory(at:)can return URLs that differ on macOS by
/varvs/private/varcanonicalisation, so
Set<URL>.contains(...)is unreliable.land in the same millisecond would collide on the path, and
FileManager.createFile(atPath:contents:attributes:)silentlyoverwrites an existing file. The orchestrator bumps the timestamp
until the name is unused.
FileWriterwas reverted: with propersynchronisation, 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 passDatadogSDKTestingsucceeds🤖 Generated with Claude Code