dd-trace 5.99.1 → 5.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE-3rdparty.csv +0 -1
  2. package/package.json +4 -4
  3. package/packages/datadog-instrumentations/src/cucumber.js +69 -5
  4. package/packages/datadog-instrumentations/src/express.js +3 -2
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  6. package/packages/datadog-instrumentations/src/hono.js +15 -4
  7. package/packages/datadog-instrumentations/src/jest.js +84 -58
  8. package/packages/datadog-instrumentations/src/mocha/main.js +18 -22
  9. package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
  10. package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
  11. package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
  12. package/packages/datadog-instrumentations/src/playwright.js +108 -18
  13. package/packages/datadog-instrumentations/src/router.js +53 -33
  14. package/packages/datadog-instrumentations/src/vitest.js +76 -30
  15. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
  16. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
  17. package/packages/datadog-plugin-bullmq/src/consumer.js +3 -2
  18. package/packages/datadog-plugin-bullmq/src/producer.js +25 -11
  19. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +32 -9
  20. package/packages/datadog-plugin-cypress/src/support.js +22 -21
  21. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  22. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  23. package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
  24. package/packages/datadog-plugin-playwright/src/index.js +6 -0
  25. package/packages/datadog-plugin-router/src/index.js +13 -0
  26. package/packages/dd-trace/index.js +4 -3
  27. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  28. package/packages/dd-trace/src/baggage.js +10 -0
  29. package/packages/dd-trace/src/config/generated-config-types.d.ts +17 -41
  30. package/packages/dd-trace/src/config/index.js +6 -5
  31. package/packages/dd-trace/src/config/normalize-service.js +31 -0
  32. package/packages/dd-trace/src/config/supported-configurations.json +15 -32
  33. package/packages/dd-trace/src/debugger/config.js +1 -1
  34. package/packages/dd-trace/src/encode/0.4.js +1 -1
  35. package/packages/dd-trace/src/encode/tags-processors.js +3 -3
  36. package/packages/dd-trace/src/heap_snapshots.js +4 -4
  37. package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
  38. package/packages/dd-trace/src/opentelemetry/context_manager.js +11 -8
  39. package/packages/dd-trace/src/opentelemetry/span-helpers.js +170 -0
  40. package/packages/dd-trace/src/opentelemetry/span.js +14 -42
  41. package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
  42. package/packages/dd-trace/src/opentracing/propagation/text_map.js +31 -10
  43. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +42 -12
  44. package/packages/dd-trace/src/opentracing/span.js +3 -2
  45. package/packages/dd-trace/src/plugins/util/ci.js +119 -32
  46. package/packages/dd-trace/src/plugins/util/test.js +293 -27
  47. package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
  48. package/packages/dd-trace/src/propagation-hash/index.js +1 -1
  49. package/packages/dd-trace/src/proxy.js +3 -3
  50. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  51. package/packages/dd-trace/src/span_processor.js +1 -1
  52. package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
  53. package/packages/dd-trace/src/tracer_metadata.js +1 -1
  54. package/vendor/dist/path-to-regexp/LICENSE +0 -21
  55. package/vendor/dist/path-to-regexp/index.js +0 -1
@@ -103,42 +103,129 @@ function getGitHubEventPayload () {
103
103
  return JSON.parse(readFileSync(path, 'utf8'))
104
104
  }
105
105
 
106
- function getJobIDFromDiagFile (runnerTemp) {
107
- if (!runnerTemp || !existsSync(runnerTemp)) { return null }
106
+ const uniq = (items) => [...new Set(items)]
107
+
108
+ /**
109
+ * GitHub runner diagnostic logs live under the runner installation directory in `_diag`.
110
+ * On many runners, we can derive the installation directory from RUNNER_TEMP:
111
+ * <runnerRoot>/_work/_temp -> <runnerRoot>/_diag
112
+ *
113
+ * This is much more robust than relying on hardcoded paths, especially on self-hosted runners
114
+ * and GHES environments where the runner may be installed under arbitrary directories/users.
115
+ */
116
+ function getGithubDiagnosticDirsFromEnv (runnerTemp) {
117
+ const dirs = []
118
+
119
+ if (runnerTemp) {
120
+ // RUNNER_TEMP is typically: <runnerRoot>/_work/_temp
121
+ const runnerRoot = path.resolve(runnerTemp, '..', '..').replaceAll(path.sep, '/')
122
+ // Bounded-depth patterns cover every runner layout we've observed
123
+ // (including cached/<version>/_diag) without assuming a `cached` wrapper
124
+ // and without walking the whole tree.
125
+ dirs.push(
126
+ path.posix.join(runnerRoot, 'actions-runner', '_diag'),
127
+ `${runnerRoot}/actions-runner/*/_diag`,
128
+ `${runnerRoot}/actions-runner/*/*/_diag`,
129
+ path.posix.join(runnerRoot, '_diag'),
130
+ `${runnerRoot}/*/_diag`,
131
+ `${runnerRoot}/*/*/_diag`
132
+ )
133
+ }
108
134
 
109
- // RUNNER_TEMP usually looks like:
110
- // Linux/mac hosted: /home/runner/work/_temp
111
- // Windows hosted: C:\actions-runner\_work\_temp
112
- // Self-hosted (unix): /opt/actions-runner/_work/_temp
135
+ return uniq(dirs.filter(Boolean))
136
+ }
113
137
 
114
- const workDir = path.dirname(runnerTemp) // .../work or .../_work
115
- const runnerRoot = path.dirname(workDir) // /home/runner/ (runner root)
138
+ function hasMagicChars (str) {
139
+ return str.includes('*') || str.includes('?')
140
+ }
116
141
 
117
- const dirs = [
118
- path.join(runnerRoot, 'cached', '_diag'),
119
- path.join(runnerRoot, '_diag'),
120
- path.join(runnerRoot, 'actions-runner', 'cached', '_diag'),
121
- path.join(runnerRoot, 'actions-runner', '_diag'),
122
- ]
142
+ // Expands a glob pattern with only `*`/`?` at path-segment boundaries (no `**`)
143
+ // into matching concrete paths using readdirSync — no external dependency needed.
144
+ function expandGlobPattern (pattern) {
145
+ const parts = pattern.split(/[/\\]/)
146
+ const wildcardIdx = parts.findIndex(p => hasMagicChars(p))
147
+ if (wildcardIdx === -1) return [pattern]
123
148
 
124
- const isWin = process.platform === 'win32'
149
+ const prefix = parts.slice(0, wildcardIdx).join('/')
150
+ const results = []
125
151
 
126
- // Hardcoded fallbacks
127
- if (isWin) {
128
- dirs.push(
129
- 'C:/actions-runner/cached/_diag',
130
- 'C:/actions-runner/_diag',
131
- )
132
- } else {
133
- dirs.push(
134
- '/home/runner/actions-runner/cached/_diag',
135
- '/home/runner/actions-runner/_diag',
136
- '/opt/actions-runner/_diag',
137
- )
152
+ function walk (dir, segIdx) {
153
+ if (segIdx === parts.length) {
154
+ results.push(dir)
155
+ return
156
+ }
157
+ const seg = parts[segIdx]
158
+ if (!hasMagicChars(seg)) {
159
+ walk(`${dir}/${seg}`, segIdx + 1)
160
+ return
161
+ }
162
+ try {
163
+ const re = new RegExp(
164
+ '^' + seg.replaceAll(/[.+^${}()|[\]\\]/g, String.raw`\$&`).replaceAll('*', String.raw`[^/\\]*`).replaceAll('?', String.raw`[^/\\]`) + '$'
165
+ )
166
+ for (const entry of readdirSync(dir)) {
167
+ if (re.test(entry)) {
168
+ walk(`${dir}/${entry}`, segIdx + 1)
169
+ }
170
+ }
171
+ } catch {
172
+ // directory doesn't exist or isn't accessible
173
+ }
138
174
  }
139
175
 
140
- // Remove duplicates
141
- const possibleDiagsPaths = [...new Set(dirs)]
176
+ walk(prefix, wildcardIdx)
177
+ return results
178
+ }
179
+
180
+ /**
181
+ * Expands a mixed list of literal directories and glob patterns into concrete
182
+ * directories. Literals pass through unchanged (existence is checked later).
183
+ */
184
+ function expandDiagnosticDirCandidates (candidates) {
185
+ const expanded = []
186
+ for (const candidate of candidates) {
187
+ if (hasMagicChars(candidate)) {
188
+ expanded.push(...expandGlobPattern(candidate))
189
+ } else {
190
+ expanded.push(candidate)
191
+ }
192
+ }
193
+
194
+ return uniq(expanded)
195
+ }
196
+
197
+ const githubWellKnownDiagnosticDirsUnix = [
198
+ '/home/runner/actions-runner/_diag',
199
+ '/opt/actions-runner/_diag',
200
+ ]
201
+ const githubWellKnownDiagnosticDirsWin = [
202
+ 'C:/actions-runner/_diag',
203
+ ]
204
+
205
+ // Glob patterns covering layouts that namespace `_diag` under one or two
206
+ // intermediate directories. This includes both observed SaaS layouts
207
+ // (<runnerRoot>/cached/_diag pre-2.334.0, <runnerRoot>/cached/<version>/_diag
208
+ // since v2.334.0) and hypothetical future layouts that follow the same shape
209
+ // without a `cached` wrapper (e.g. <runnerRoot>/<version>/_diag). Depth is
210
+ // bounded on purpose: `*` matches a single segment, so no filesystem walk.
211
+ const githubWellKnownDiagnosticDirPatternsUnix = [
212
+ '/home/runner/actions-runner/*/_diag',
213
+ '/home/runner/actions-runner/*/*/_diag',
214
+ ]
215
+ const githubWellKnownDiagnosticDirPatternsWin = ['C:/actions-runner/*/_diag', 'C:/actions-runner/*/*/_diag']
216
+
217
+ const githubJobIDRegex = /"job":\s*{[\s\S]*?"v"\s*:\s*(\d+)(?:\.0)?/
218
+
219
+ function getJobIDFromDiagFile () {
220
+ const runnerTemp = getValueFromEnvSources('RUNNER_TEMP')
221
+ if (!runnerTemp || !existsSync(runnerTemp)) { return null }
222
+
223
+ const isWin = process.platform === 'win32'
224
+ const patterns = isWin ? githubWellKnownDiagnosticDirPatternsWin : githubWellKnownDiagnosticDirPatternsUnix
225
+ const literals = isWin ? githubWellKnownDiagnosticDirsWin : githubWellKnownDiagnosticDirsUnix
226
+ const possibleDiagsPaths = expandDiagnosticDirCandidates([
227
+ ...getGithubDiagnosticDirsFromEnv(runnerTemp), ...patterns, ...literals,
228
+ ])
142
229
 
143
230
  // This will hold the names of the worker log files that (potentially) contain the Job ID
144
231
  let workerLogFiles = []
@@ -177,7 +264,7 @@ function getJobIDFromDiagFile (runnerTemp) {
177
264
  const filePath = path.posix.join(chosenDiagPath, logFile)
178
265
  const content = readFileSync(filePath, 'utf8')
179
266
 
180
- const match = content.match(/"job":\s*{[\s\S]*?"v"\s*:\s*(\d+)(?:\.0)?/)
267
+ const match = content.match(githubJobIDRegex)
181
268
 
182
269
  // match[1] is the captured group with the display name
183
270
  if (match && match[1]) { return match[1] }
@@ -188,6 +275,7 @@ function getJobIDFromDiagFile (runnerTemp) {
188
275
 
189
276
  module.exports = {
190
277
  normalizeRef,
278
+ expandGlobPattern,
191
279
  getJobIDFromDiagFile,
192
280
  getCIMetadata () {
193
281
  const env = getEnvironmentVariables()
@@ -366,7 +454,6 @@ module.exports = {
366
454
  GITHUB_RUN_ATTEMPT,
367
455
  GITHUB_JOB,
368
456
  GITHUB_BASE_REF,
369
- RUNNER_TEMP,
370
457
  JOB_CHECK_RUN_ID,
371
458
  } = env
372
459
 
@@ -378,7 +465,7 @@ module.exports = {
378
465
  }
379
466
 
380
467
  // Build the job url extracting the job ID. If extraction fails, job url is constructed as a generalized url
381
- const GITHUB_JOB_ID = JOB_CHECK_RUN_ID ?? getJobIDFromDiagFile(RUNNER_TEMP)
468
+ const GITHUB_JOB_ID = JOB_CHECK_RUN_ID ?? getJobIDFromDiagFile()
382
469
  const jobUrl =
383
470
  GITHUB_JOB_ID === null
384
471
  ? `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}/checks`
@@ -217,6 +217,8 @@ const TEST_MANAGEMENT_IS_QUARANTINED = 'test.test_management.is_quarantined'
217
217
  const TEST_MANAGEMENT_ENABLED = 'test.test_management.enabled'
218
218
  const TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED = 'test.test_management.attempt_to_fix_passed'
219
219
 
220
+ const MAX_TEST_OPTIMIZATION_SUMMARY_ITEMS = 10
221
+
220
222
  // Impacted tests
221
223
  const POSSIBLE_BASE_BRANCHES = ['main', 'master', 'preprod', 'prod', 'dev', 'development', 'trunk']
222
224
  const BASE_LIKE_BRANCH_FILTER = /^(main|master|preprod|prod|dev|development|trunk|release\/.*|hotfix\/.*)$/
@@ -358,7 +360,14 @@ module.exports = {
358
360
  GIT_REPOSITORY_URL,
359
361
  DYNAMIC_NAME_RE,
360
362
  collectDynamicNamesFromTraces,
363
+ collectTestOptimizationSummariesFromTraces,
361
364
  logDynamicNamesWarning,
365
+ recordAttemptToFixExecution,
366
+ collectAttemptToFixExecutionsFromTraces,
367
+ formatAttemptToFixSummary,
368
+ logAttemptToFixTestExecution,
369
+ formatDynamicNamesSummary,
370
+ logTestOptimizationSummary,
362
371
  }
363
372
 
364
373
  // Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
@@ -1327,23 +1336,149 @@ function getModifiedFilesFromDiff (diff) {
1327
1336
  }
1328
1337
 
1329
1338
  /**
1330
- * Scans serialized worker trace payloads for tests tagged with TEST_HAS_DYNAMIC_NAME
1331
- * and populates the provided Set. Silently ignores parse errors.
1339
+ * @typedef {object} AttemptToFixExecutionResult
1340
+ * @property {string} name
1341
+ * @property {number} executions
1342
+ * @property {number} failedCount
1343
+ * @property {boolean} isDisabled
1344
+ * @property {boolean} isQuarantined
1345
+ */
1346
+
1347
+ /**
1348
+ * @typedef {Map<string, AttemptToFixExecutionResult>} AttemptToFixExecutions
1349
+ */
1350
+
1351
+ /**
1352
+ * Formats a test name for user-facing Test Optimization summaries.
1353
+ *
1354
+ * @param {string | undefined} testSuite
1355
+ * @param {string} testName
1356
+ * @returns {string}
1357
+ */
1358
+ function formatTestOptimizationName (testSuite, testName) {
1359
+ return testSuite ? `${testSuite} › ${testName}` : testName
1360
+ }
1361
+
1362
+ /**
1363
+ * Renders a bounded bullet list for Test Optimization summaries.
1364
+ *
1365
+ * @param {Array<{ text: string, suffix?: string }>} items
1366
+ * @returns {string}
1367
+ */
1368
+ function formatTestOptimizationList (items) {
1369
+ const shown = items.slice(0, MAX_TEST_OPTIMIZATION_SUMMARY_ITEMS)
1370
+ const more = items.length - shown.length
1371
+ const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
1372
+
1373
+ return shown.map(({ text, suffix }) => ` • ${text}${suffix ? ` (${suffix})` : ''}`).join('\n') + moreSuffix
1374
+ }
1375
+
1376
+ /**
1377
+ * Logs a compact message when an attempt-to-fix test execution starts.
1378
+ *
1379
+ * @param {string | undefined} testSuite
1380
+ * @param {string} testName
1381
+ * @param {Set<string>} [loggedAttemptToFixTests]
1382
+ */
1383
+ function logAttemptToFixTestExecution (testSuite, testName, loggedAttemptToFixTests) {
1384
+ if (!testName) return
1385
+
1386
+ const name = formatTestOptimizationName(testSuite, testName)
1387
+ if (loggedAttemptToFixTests) {
1388
+ if (loggedAttemptToFixTests.has(name)) return
1389
+
1390
+ loggedAttemptToFixTests.add(name)
1391
+ }
1392
+
1393
+ // eslint-disable-next-line no-console -- Intentional user-facing attempt-to-fix progress report
1394
+ console.warn(`Datadog Test Optimization: attempting to fix ${name}`)
1395
+ }
1396
+
1397
+ /**
1398
+ * Records a single attempt-to-fix execution for the end-of-session user summary.
1399
+ *
1400
+ * @param {AttemptToFixExecutions} attemptToFixExecutions
1401
+ * @param {{
1402
+ * testSuite?: string,
1403
+ * testName: string,
1404
+ * status: string,
1405
+ * isDisabled?: boolean,
1406
+ * isQuarantined?: boolean
1407
+ * }} execution
1408
+ */
1409
+ function recordAttemptToFixExecution (attemptToFixExecutions, execution) {
1410
+ if (!execution?.testName) return
1411
+
1412
+ const { testSuite, testName, status, isDisabled, isQuarantined } = execution
1413
+ const name = formatTestOptimizationName(testSuite, testName)
1414
+ let result = attemptToFixExecutions.get(name)
1415
+
1416
+ if (!result) {
1417
+ result = {
1418
+ name,
1419
+ executions: 0,
1420
+ failedCount: 0,
1421
+ isDisabled: false,
1422
+ isQuarantined: false,
1423
+ }
1424
+ attemptToFixExecutions.set(name, result)
1425
+ }
1426
+
1427
+ result.executions++
1428
+ result.isDisabled = result.isDisabled || !!isDisabled
1429
+ result.isQuarantined = result.isQuarantined || !!isQuarantined
1430
+
1431
+ if (status === 'fail') {
1432
+ result.failedCount++
1433
+ }
1434
+ }
1435
+
1436
+ function collectDynamicNameFromTraceSpan (span, newTestsWithDynamicNames) {
1437
+ const meta = span.meta
1438
+ if (meta?.[TEST_HAS_DYNAMIC_NAME] !== 'true') return
1439
+
1440
+ const suite = meta[TEST_SUITE]
1441
+ const name = meta[TEST_NAME]
1442
+ if (suite && name) {
1443
+ newTestsWithDynamicNames.add(`${suite} › ${name}`)
1444
+ }
1445
+ }
1446
+
1447
+ function collectAttemptToFixExecutionFromTraceSpan (span, attemptToFixExecutions) {
1448
+ const meta = span.meta
1449
+ if (meta?.[TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX] !== 'true') return
1450
+
1451
+ recordAttemptToFixExecution(attemptToFixExecutions, {
1452
+ testSuite: meta[TEST_SUITE],
1453
+ testName: meta[TEST_NAME],
1454
+ status: meta[TEST_STATUS],
1455
+ isDisabled: meta[TEST_MANAGEMENT_IS_DISABLED] === 'true',
1456
+ isQuarantined: meta[TEST_MANAGEMENT_IS_QUARANTINED] === 'true',
1457
+ })
1458
+ }
1459
+
1460
+ /**
1461
+ * Scans serialized worker trace payloads and populates Test Optimization summary data.
1462
+ * Silently ignores parse errors.
1332
1463
  *
1333
1464
  * @param {string} data - JSON-serialized traces from a worker
1334
- * @param {Set<string>} newTestsWithDynamicNames - Set to populate with "suite › name" strings
1465
+ * @param {{
1466
+ * newTestsWithDynamicNames?: Set<string>,
1467
+ * attemptToFixExecutions?: AttemptToFixExecutions
1468
+ * }} summaries
1335
1469
  */
1336
- function collectDynamicNamesFromTraces (data, newTestsWithDynamicNames) {
1470
+ function collectTestOptimizationSummariesFromTraces (data, summaries) {
1471
+ const { newTestsWithDynamicNames, attemptToFixExecutions } = summaries
1472
+
1337
1473
  try {
1338
1474
  const traces = JSON.parse(data)
1339
1475
  for (const trace of traces) {
1340
1476
  for (const span of trace) {
1341
- if (span.meta?.[TEST_HAS_DYNAMIC_NAME] === 'true') {
1342
- const suite = span.meta[TEST_SUITE]
1343
- const name = span.meta[TEST_NAME]
1344
- if (suite && name) {
1345
- newTestsWithDynamicNames.add(`${suite} › ${name}`)
1346
- }
1477
+ if (newTestsWithDynamicNames) {
1478
+ collectDynamicNameFromTraceSpan(span, newTestsWithDynamicNames)
1479
+ }
1480
+ if (attemptToFixExecutions) {
1481
+ collectAttemptToFixExecutionFromTraceSpan(span, attemptToFixExecutions)
1347
1482
  }
1348
1483
  }
1349
1484
  }
@@ -1353,33 +1488,164 @@ function collectDynamicNamesFromTraces (data, newTestsWithDynamicNames) {
1353
1488
  }
1354
1489
 
1355
1490
  /**
1356
- * Logs a "Datadog Test Optimization" warning about new tests with dynamic names.
1357
- * Clears the Set after logging. No-op if the Set is empty.
1491
+ * Scans serialized worker trace payloads for tests tagged with TEST_HAS_DYNAMIC_NAME
1492
+ * and populates the provided Set. Silently ignores parse errors.
1358
1493
  *
1359
- * @param {Set<string>} newTestsWithDynamicNames
1494
+ * @param {string} data - JSON-serialized traces from a worker
1495
+ * @param {Set<string>} newTestsWithDynamicNames - Set to populate with "suite › name" strings
1360
1496
  */
1361
- function logDynamicNamesWarning (newTestsWithDynamicNames) {
1362
- if (newTestsWithDynamicNames.size === 0) return
1497
+ function collectDynamicNamesFromTraces (data, newTestsWithDynamicNames) {
1498
+ collectTestOptimizationSummariesFromTraces(data, { newTestsWithDynamicNames })
1499
+ }
1363
1500
 
1364
- const MAX_SHOWN = 10
1365
- const names = [...newTestsWithDynamicNames]
1366
- const shown = names.slice(0, MAX_SHOWN)
1367
- const more = names.length - shown.length
1368
- const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
1369
- const nameList = shown.map(n => ` • ${n}`).join('\n') + moreSuffix
1501
+ /**
1502
+ * Scans serialized worker trace payloads for attempt-to-fix test spans.
1503
+ *
1504
+ * @param {string} data - JSON-serialized traces from a worker
1505
+ * @param {AttemptToFixExecutions} attemptToFixExecutions
1506
+ */
1507
+ function collectAttemptToFixExecutionsFromTraces (data, attemptToFixExecutions) {
1508
+ collectTestOptimizationSummariesFromTraces(data, { attemptToFixExecutions })
1509
+ }
1370
1510
 
1371
- const line = '-'.repeat(50)
1372
- // eslint-disable-next-line no-console -- Intentional user-facing session summary
1373
- console.warn(
1374
- `\n${line}\nDatadog Test Optimization\n${line}\n` +
1511
+ function getAttemptToFixManagementNotes (result) {
1512
+ const notes = []
1513
+
1514
+ if (result.isDisabled) {
1515
+ notes.push('Test was marked as disabled but was run because it is attempt to fix.')
1516
+ }
1517
+ if (result.isQuarantined) {
1518
+ notes.push('Test was marked as quarantined but was not quarantined because it is attempt to fix.')
1519
+ }
1520
+
1521
+ return notes
1522
+ }
1523
+
1524
+ function hasAttemptToFixManagementNotes (result) {
1525
+ return result.isDisabled || result.isQuarantined
1526
+ }
1527
+
1528
+ function addAttemptToFixResultLine (lines, result) {
1529
+ lines.push(` • ${result.name}`)
1530
+
1531
+ for (const note of getAttemptToFixManagementNotes(result)) {
1532
+ lines.push(` ${note}`)
1533
+ }
1534
+ }
1535
+
1536
+ /**
1537
+ * Formats the attempt-to-fix end-of-session summary.
1538
+ *
1539
+ * @param {AttemptToFixExecutions} attemptToFixExecutions
1540
+ * @returns {string}
1541
+ */
1542
+ function formatAttemptToFixSummary (attemptToFixExecutions) {
1543
+ if (attemptToFixExecutions.size === 0) return ''
1544
+
1545
+ const results = [...attemptToFixExecutions.values()]
1546
+ const failedResults = results.filter(result => result.failedCount > 0)
1547
+ const totalExecutions = results.reduce((total, result) => total + result.executions, 0)
1548
+
1549
+ if (failedResults.length === 0) {
1550
+ const lines = [
1551
+ `Attempt to fix passed: all ${totalExecutions} execution(s) passed for ${results.length} test(s).`,
1552
+ ]
1553
+
1554
+ for (const result of results) {
1555
+ if (hasAttemptToFixManagementNotes(result)) {
1556
+ addAttemptToFixResultLine(lines, result)
1557
+ }
1558
+ }
1559
+
1560
+ return lines.join('\n')
1561
+ }
1562
+
1563
+ const totalFailedExecutions = failedResults.reduce(
1564
+ (total, result) => total + result.failedCount,
1565
+ 0
1566
+ )
1567
+ const lines = [
1568
+ `Attempt to fix failed: ${totalFailedExecutions} of ${totalExecutions} execution(s) failed ` +
1569
+ `across ${failedResults.length} of ${results.length} test(s).`,
1570
+ ]
1571
+
1572
+ for (const result of failedResults) {
1573
+ addAttemptToFixResultLine(lines, result)
1574
+ }
1575
+ for (const result of results) {
1576
+ if (result.failedCount === 0 && hasAttemptToFixManagementNotes(result)) {
1577
+ addAttemptToFixResultLine(lines, result)
1578
+ }
1579
+ }
1580
+
1581
+ return lines.join('\n')
1582
+ }
1583
+
1584
+ /**
1585
+ * Formats the dynamic-name warning section of the Test Optimization summary.
1586
+ *
1587
+ * @param {Set<string>} newTestsWithDynamicNames
1588
+ * @returns {string}
1589
+ */
1590
+ function formatDynamicNamesSummary (newTestsWithDynamicNames) {
1591
+ if (newTestsWithDynamicNames.size === 0) return ''
1592
+
1593
+ const items = [...newTestsWithDynamicNames].map(name => ({ text: name }))
1594
+ return (
1375
1595
  `${newTestsWithDynamicNames.size} test(s) detected as new but their names contain ` +
1376
1596
  'dynamic data (timestamps, UUIDs, etc.).\n' +
1377
1597
  'Tests with changing names are always treated as new on every run, ' +
1378
1598
  'causing unnecessary Early Flake Detection retries and preventing correct new test detection.\n' +
1379
1599
  'Consider using stable, deterministic test names.\n\n' +
1380
- `${nameList}\n`
1600
+ formatTestOptimizationList(items)
1381
1601
  )
1382
- newTestsWithDynamicNames.clear()
1602
+ }
1603
+
1604
+ /**
1605
+ * Logs a single Test Optimization session summary.
1606
+ *
1607
+ * @param {{
1608
+ * attemptToFixExecutions?: AttemptToFixExecutions,
1609
+ * newTestsWithDynamicNames?: Set<string>,
1610
+ * extraSections?: string[]
1611
+ * }} summary
1612
+ */
1613
+ function logTestOptimizationSummary (summary) {
1614
+ const { attemptToFixExecutions, newTestsWithDynamicNames, extraSections = [] } = summary
1615
+ const sections = []
1616
+ const attemptToFixSummary = attemptToFixExecutions
1617
+ ? formatAttemptToFixSummary(attemptToFixExecutions)
1618
+ : ''
1619
+ const dynamicNamesSummary = newTestsWithDynamicNames
1620
+ ? formatDynamicNamesSummary(newTestsWithDynamicNames)
1621
+ : ''
1622
+
1623
+ if (attemptToFixSummary) sections.push(attemptToFixSummary)
1624
+ sections.push(...extraSections.filter(Boolean))
1625
+ if (dynamicNamesSummary) sections.push(dynamicNamesSummary)
1626
+
1627
+ if (sections.length === 0) return
1628
+
1629
+ const line = '-'.repeat(50)
1630
+ // eslint-disable-next-line no-console -- Intentional user-facing session summary
1631
+ console.warn(`\n${line}\nDatadog Test Optimization\n${line}\n${sections.join('\n\n')}\n`)
1632
+
1633
+ if (attemptToFixExecutions) {
1634
+ attemptToFixExecutions.clear()
1635
+ }
1636
+ if (newTestsWithDynamicNames) {
1637
+ newTestsWithDynamicNames.clear()
1638
+ }
1639
+ }
1640
+
1641
+ /**
1642
+ * Logs a "Datadog Test Optimization" warning about new tests with dynamic names.
1643
+ * Clears the Set after logging. No-op if the Set is empty.
1644
+ *
1645
+ * @param {Set<string>} newTestsWithDynamicNames
1646
+ */
1647
+ function logDynamicNamesWarning (newTestsWithDynamicNames) {
1648
+ logTestOptimizationSummary({ newTestsWithDynamicNames })
1383
1649
  }
1384
1650
 
1385
1651
  function isModifiedTest (testPath, testStartLine, testEndLine, modifiedFiles, testFramework) {
@@ -14,12 +14,12 @@ class SSIHeuristics {
14
14
  * @param {import('../config/config-base')} config - Tracer configuration
15
15
  */
16
16
  constructor (config) {
17
- const longLivedThreshold = config.profiling.longLivedThreshold || DEFAULT_LONG_LIVED_THRESHOLD
17
+ const longLivedThreshold = config.DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD || DEFAULT_LONG_LIVED_THRESHOLD
18
18
  if (typeof longLivedThreshold !== 'number' || longLivedThreshold <= 0) {
19
19
  this.longLivedThreshold = DEFAULT_LONG_LIVED_THRESHOLD
20
20
  log.warn(
21
21
  'Invalid SSIHeuristics.longLivedThreshold value: %s. Using default value:',
22
- config.profiling.longLivedThreshold,
22
+ config.DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD,
23
23
  DEFAULT_LONG_LIVED_THRESHOLD
24
24
  )
25
25
  } else {
@@ -33,7 +33,7 @@ class PropagationHashManager {
33
33
  * @returns {boolean}
34
34
  */
35
35
  isEnabled () {
36
- return this._config?.propagateProcessTags?.enabled === true
36
+ return this._config?.DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED === true
37
37
  }
38
38
 
39
39
  /**
@@ -113,11 +113,11 @@ class Tracer extends NoopProxy {
113
113
  const propagationHash = require('./propagation-hash')
114
114
  propagationHash.configure(config)
115
115
 
116
- if (config.crashtracking.enabled) {
116
+ if (config.DD_CRASHTRACKING_ENABLED) {
117
117
  require('./crashtracking').start(config)
118
118
  }
119
119
 
120
- if (config.heapSnapshot.count > 0) {
120
+ if (config.DD_HEAP_SNAPSHOT_COUNT > 0) {
121
121
  require('./heap_snapshots').start(config)
122
122
  }
123
123
 
@@ -229,7 +229,7 @@ class Tracer extends NoopProxy {
229
229
  initializeOpenTelemetryLogs(config)
230
230
  }
231
231
 
232
- if (config.otelMetricsEnabled) {
232
+ if (config.DD_METRICS_OTEL_ENABLED) {
233
233
  const { initializeOpenTelemetryMetrics } = require('./opentelemetry/metrics')
234
234
  initializeOpenTelemetryMetrics(config)
235
235
  }
@@ -42,7 +42,7 @@ module.exports = {
42
42
  this.stop()
43
43
  const clientConfig = DogStatsDClient.generateClientConfig(config)
44
44
 
45
- if (config.propagateProcessTags?.enabled) {
45
+ if (config.DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED) {
46
46
  for (const tag of processTags.tagsArray) {
47
47
  clientConfig.tags.push(tag)
48
48
  }
@@ -25,7 +25,7 @@ class SpanProcessor {
25
25
  this._spanSampler = new SpanSampler(config.sampler)
26
26
  this._gitMetadataTagger = new GitMetadataTagger(config)
27
27
 
28
- this._processTags = config.propagateProcessTags?.enabled
28
+ this._processTags = config.DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED
29
29
  ? processTags.serialized
30
30
  : false
31
31
  }
@@ -163,12 +163,14 @@ function getProducts (config) {
163
163
  * @param {import('../config/config-base')} config
164
164
  */
165
165
  function getInstallSignature (config) {
166
- const { installSignature: sig } = config
167
- if (sig && (sig.id || sig.time || sig.type)) {
166
+ const id = config.DD_INSTRUMENTATION_INSTALL_ID
167
+ const time = config.DD_INSTRUMENTATION_INSTALL_TIME
168
+ const type = config.DD_INSTRUMENTATION_INSTALL_TYPE
169
+ if (id || time || type) {
168
170
  return {
169
- install_id: sig.id,
170
- install_time: sig.time,
171
- install_type: sig.type,
171
+ install_id: id,
172
+ install_time: time,
173
+ install_type: type,
172
174
  }
173
175
  }
174
176
  }
@@ -14,7 +14,7 @@ function storeConfig (config) {
14
14
  const { containerId } = require('./exporters/common/docker')
15
15
  const processTags = require('./process-tags')
16
16
 
17
- const processTagsSerialized = config.propagateProcessTags?.enabled
17
+ const processTagsSerialized = config.DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED
18
18
  ? (processTags.serialized || null)
19
19
  : null
20
20
 
@@ -1,21 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com)
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.