dd-trace 5.52.0 → 5.53.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/README.md +5 -0
  2. package/index.d.ts +54 -6
  3. package/package.json +1 -1
  4. package/packages/datadog-instrumentations/src/amqplib.js +8 -5
  5. package/packages/datadog-instrumentations/src/child_process.js +2 -1
  6. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +16 -1
  7. package/packages/datadog-instrumentations/src/couchbase.js +2 -1
  8. package/packages/datadog-instrumentations/src/cucumber.js +41 -46
  9. package/packages/datadog-instrumentations/src/express.js +2 -6
  10. package/packages/datadog-instrumentations/src/fs.js +6 -5
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  12. package/packages/datadog-instrumentations/src/helpers/register.js +17 -12
  13. package/packages/datadog-instrumentations/src/http/client.js +2 -1
  14. package/packages/datadog-instrumentations/src/iovalkey.js +51 -0
  15. package/packages/datadog-instrumentations/src/jest.js +49 -41
  16. package/packages/datadog-instrumentations/src/kafkajs.js +21 -8
  17. package/packages/datadog-instrumentations/src/mocha/main.js +33 -46
  18. package/packages/datadog-instrumentations/src/mocha/utils.js +72 -75
  19. package/packages/datadog-instrumentations/src/mysql2.js +3 -1
  20. package/packages/datadog-instrumentations/src/net.js +3 -1
  21. package/packages/datadog-instrumentations/src/next.js +6 -14
  22. package/packages/datadog-instrumentations/src/pg.js +5 -11
  23. package/packages/datadog-instrumentations/src/playwright.js +60 -69
  24. package/packages/datadog-instrumentations/src/url.js +9 -17
  25. package/packages/datadog-instrumentations/src/vitest.js +55 -75
  26. package/packages/datadog-plugin-cucumber/src/index.js +29 -18
  27. package/packages/datadog-plugin-iovalkey/src/index.js +18 -0
  28. package/packages/datadog-plugin-jest/src/index.js +14 -8
  29. package/packages/datadog-plugin-kafkajs/src/producer.js +8 -5
  30. package/packages/datadog-plugin-mocha/src/index.js +55 -35
  31. package/packages/datadog-plugin-playwright/src/index.js +26 -20
  32. package/packages/datadog-plugin-redis/src/index.js +8 -3
  33. package/packages/datadog-plugin-vitest/src/index.js +53 -42
  34. package/packages/datadog-shimmer/src/shimmer.js +164 -33
  35. package/packages/dd-trace/src/appsec/graphql.js +2 -2
  36. package/packages/dd-trace/src/appsec/index.js +14 -11
  37. package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
  38. package/packages/dd-trace/src/appsec/rasp/utils.js +11 -6
  39. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -2
  40. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -2
  41. package/packages/dd-trace/src/appsec/telemetry/rasp.js +0 -9
  42. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +6 -6
  43. package/packages/dd-trace/src/config.js +1 -1
  44. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +59 -7
  45. package/packages/dd-trace/src/debugger/devtools_client/index.js +10 -26
  46. package/packages/dd-trace/src/debugger/devtools_client/send.js +8 -7
  47. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +15 -7
  48. package/packages/dd-trace/src/debugger/devtools_client/state.js +21 -1
  49. package/packages/dd-trace/src/dogstatsd.js +2 -0
  50. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  51. package/packages/dd-trace/src/plugins/index.js +1 -0
  52. package/packages/dd-trace/src/proxy.js +0 -4
  53. package/packages/dd-trace/src/serverless.js +0 -48
  54. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +8 -0
  55. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
@@ -26,7 +26,7 @@ const knownTestsCh = channel('ci:vitest:known-tests')
26
26
  const isEarlyFlakeDetectionFaultyCh = channel('ci:vitest:is-early-flake-detection-faulty')
27
27
  const testManagementTestsCh = channel('ci:vitest:test-management-tests')
28
28
 
29
- const taskToAsync = new WeakMap()
29
+ const taskToCtx = new WeakMap()
30
30
  const taskToStatuses = new WeakMap()
31
31
  const newTasks = new WeakSet()
32
32
  const disabledTasks = new WeakSet()
@@ -455,7 +455,7 @@ addHook({
455
455
  // test start (only tests that are not marked as skip or todo)
456
456
  // `onBeforeTryTask` is run for every repetition and attempt of the test
457
457
  shimmer.wrap(VitestTestRunner.prototype, 'onBeforeTryTask', onBeforeTryTask => async function (task, retryInfo) {
458
- if (!testStartCh.hasSubscribers) {
458
+ if (!testPassCh.hasSubscribers && !testErrorCh.hasSubscribers && !testSkipCh.hasSubscribers) {
459
459
  return onBeforeTryTask.apply(this, arguments)
460
460
  }
461
461
  const testName = getTestName(task)
@@ -500,15 +500,14 @@ addHook({
500
500
 
501
501
  const promises = {}
502
502
  const shouldSetProbe = isDiEnabled && numAttempt === 1
503
- const asyncResource = taskToAsync.get(task)
503
+ const ctx = taskToCtx.get(task)
504
504
  const testError = task.result?.errors?.[0]
505
- if (asyncResource) {
506
- asyncResource.runInAsyncScope(() => {
507
- testErrorCh.publish({
508
- error: testError,
509
- shouldSetProbe,
510
- promises
511
- })
505
+ if (ctx) {
506
+ testErrorCh.publish({
507
+ error: testError,
508
+ shouldSetProbe,
509
+ promises,
510
+ ...ctx.currentStore
512
511
  })
513
512
  // We wait for the probe to be set
514
513
  if (promises.setProbePromise) {
@@ -528,17 +527,13 @@ addHook({
528
527
  // as long as it's not the _last_ iteration (which will be finished normally)
529
528
 
530
529
  // TODO: check test duration (not to repeat if it's too slow)
531
- const asyncResource = taskToAsync.get(task)
532
- if (asyncResource) {
530
+ const ctx = taskToCtx.get(task)
531
+ if (ctx) {
533
532
  if (lastExecutionStatus === 'fail') {
534
533
  const testError = task.result?.errors?.[0]
535
- asyncResource.runInAsyncScope(() => {
536
- testErrorCh.publish({ error: testError })
537
- })
534
+ testErrorCh.publish({ error: testError, ...ctx.currentStore })
538
535
  } else {
539
- asyncResource.runInAsyncScope(() => {
540
- testPassCh.publish({ task })
541
- })
536
+ testPassCh.publish({ task, ...ctx.currentStore })
542
537
  }
543
538
  if (shouldFlipStatus) {
544
539
  statuses.push(lastExecutionStatus)
@@ -555,16 +550,12 @@ addHook({
555
550
  statuses.push(lastExecutionStatus)
556
551
  }
557
552
 
558
- const asyncResource = taskToAsync.get(task)
553
+ const ctx = taskToCtx.get(task)
559
554
  if (lastExecutionStatus === 'fail') {
560
555
  const testError = task.result?.errors?.[0]
561
- asyncResource.runInAsyncScope(() => {
562
- testErrorCh.publish({ error: testError })
563
- })
556
+ testErrorCh.publish({ error: testError, ...ctx.currentStore })
564
557
  } else {
565
- asyncResource.runInAsyncScope(() => {
566
- testPassCh.publish({ task })
567
- })
558
+ testPassCh.publish({ task, ...ctx.currentStore })
568
559
  }
569
560
  }
570
561
 
@@ -573,31 +564,29 @@ addHook({
573
564
  !isRetryReasonAttemptToFix &&
574
565
  !isRetryReasonEfd
575
566
 
576
- const asyncResource = new AsyncResource('bound-anonymous-fn')
577
- taskToAsync.set(task, asyncResource)
567
+ const ctx = {
568
+ testName,
569
+ testSuiteAbsolutePath: task.file.filepath,
570
+ isRetry: numAttempt > 0 || numRepetition > 0,
571
+ isRetryReasonEfd,
572
+ isRetryReasonAttemptToFix: isRetryReasonAttemptToFix && numRepetition > 0,
573
+ isNew,
574
+ mightHitProbe: isDiEnabled && numAttempt > 0,
575
+ isAttemptToFix: attemptToFixTasks.has(task),
576
+ isDisabled: disabledTasks.has(task),
577
+ isQuarantined,
578
+ isRetryReasonAtr
579
+ }
580
+ taskToCtx.set(task, ctx)
578
581
 
579
- asyncResource.runInAsyncScope(() => {
580
- testStartCh.publish({
581
- testName,
582
- testSuiteAbsolutePath: task.file.filepath,
583
- isRetry: numAttempt > 0 || numRepetition > 0,
584
- isRetryReasonEfd,
585
- isRetryReasonAttemptToFix: isRetryReasonAttemptToFix && numRepetition > 0,
586
- isNew,
587
- mightHitProbe: isDiEnabled && numAttempt > 0,
588
- isAttemptToFix: attemptToFixTasks.has(task),
589
- isDisabled: disabledTasks.has(task),
590
- isQuarantined,
591
- isRetryReasonAtr
592
- })
593
- })
582
+ testStartCh.runStores(ctx, () => { })
594
583
  return onBeforeTryTask.apply(this, arguments)
595
584
  })
596
585
 
597
586
  // test finish (only passed tests)
598
587
  shimmer.wrap(VitestTestRunner.prototype, 'onAfterTryTask', onAfterTryTask =>
599
588
  async function (task, { retry: retryCount }) {
600
- if (!testFinishTimeCh.hasSubscribers) {
589
+ if (!testPassCh.hasSubscribers && !testErrorCh.hasSubscribers && !testSkipCh.hasSubscribers) {
601
590
  return onAfterTryTask.apply(this, arguments)
602
591
  }
603
592
  const result = await onAfterTryTask.apply(this, arguments)
@@ -605,7 +594,7 @@ addHook({
605
594
  const { testManagementAttemptToFixRetries } = getProvidedContext()
606
595
 
607
596
  const status = getVitestTestStatus(task, retryCount)
608
- const asyncResource = taskToAsync.get(task)
597
+ const ctx = taskToCtx.get(task)
609
598
 
610
599
  const { isDiEnabled } = getProvidedContext()
611
600
 
@@ -626,11 +615,13 @@ addHook({
626
615
  }
627
616
  }
628
617
 
629
- if (asyncResource) {
618
+ if (ctx) {
630
619
  // We don't finish here because the test might fail in a later hook (afterEach)
631
- asyncResource.runInAsyncScope(() => {
632
- testFinishTimeCh.publish({ status, task, attemptToFixPassed, attemptToFixFailed })
633
- })
620
+ ctx.status = status
621
+ ctx.task = task
622
+ ctx.attemptToFixPassed = attemptToFixPassed
623
+ ctx.attemptToFixFailed = attemptToFixFailed
624
+ testFinishTimeCh.runStores(ctx, () => { })
634
625
  }
635
626
 
636
627
  return result
@@ -728,19 +719,14 @@ addHook({
728
719
  }, (vitestPackage, frameworkVersion) => {
729
720
  shimmer.wrap(vitestPackage, 'startTests', startTests => async function (testPaths) {
730
721
  let testSuiteError = null
731
- if (!testSuiteStartCh.hasSubscribers) {
722
+ if (!testSuiteFinishCh.hasSubscribers) {
732
723
  return startTests.apply(this, arguments)
733
724
  }
734
725
  // From >=3.0.1, the first arguments changes from a string to an object containing the filepath
735
726
  const testSuiteAbsolutePath = testPaths[0]?.filepath || testPaths[0]
736
727
 
737
- const testSuiteAsyncResource = new AsyncResource('bound-anonymous-fn')
738
- testSuiteAsyncResource.runInAsyncScope(() => {
739
- testSuiteStartCh.publish({
740
- testSuiteAbsolutePath,
741
- frameworkVersion
742
- })
743
- })
728
+ const testSuiteCtx = { testSuiteAbsolutePath, frameworkVersion }
729
+ testSuiteStartCh.runStores(testSuiteCtx, () => { })
744
730
  const startTestsResponse = await startTests.apply(this, arguments)
745
731
 
746
732
  let onFinish = null
@@ -752,7 +738,7 @@ addHook({
752
738
 
753
739
  // Only one test task per test, even if there are retries
754
740
  testTasks.forEach(task => {
755
- const testAsyncResource = taskToAsync.get(task)
741
+ const testCtx = taskToCtx.get(task)
756
742
  const { result } = task
757
743
  // We have to trick vitest into thinking that the test has passed
758
744
  // but we want to report it as failed if it did fail
@@ -768,10 +754,8 @@ addHook({
768
754
  isDisabled: disabledTasks.has(task)
769
755
  })
770
756
  } else if (state === 'pass' && !isSwitchedStatus) {
771
- if (testAsyncResource) {
772
- testAsyncResource.runInAsyncScope(() => {
773
- testPassCh.publish({ task })
774
- })
757
+ if (testCtx) {
758
+ testPassCh.publish({ task, ...testCtx.currentStore })
775
759
  }
776
760
  } else if (state === 'fail' || isSwitchedStatus) {
777
761
  let testError
@@ -792,16 +776,15 @@ addHook({
792
776
  }
793
777
  }
794
778
 
795
- if (testAsyncResource) {
779
+ if (testCtx) {
796
780
  const isRetry = task.result?.retryCount > 0
797
781
  // `duration` is the duration of all the retries, so it can't be used if there are retries
798
- testAsyncResource.runInAsyncScope(() => {
799
- testErrorCh.publish({
800
- duration: !isRetry ? duration : undefined,
801
- error: testError,
802
- hasFailedAllRetries,
803
- attemptToFixFailed
804
- })
782
+ testErrorCh.publish({
783
+ duration: !isRetry ? duration : undefined,
784
+ error: testError,
785
+ hasFailedAllRetries,
786
+ attemptToFixFailed,
787
+ ...testCtx.currentStore
805
788
  })
806
789
  }
807
790
  if (errors?.length) {
@@ -831,14 +814,11 @@ addHook({
831
814
  }
832
815
 
833
816
  if (testSuiteError) {
834
- testSuiteAsyncResource.runInAsyncScope(() => {
835
- testSuiteErrorCh.publish({ error: testSuiteError })
836
- })
817
+ testSuiteCtx.error = testSuiteError
818
+ testSuiteErrorCh.runStores(testSuiteCtx, () => { })
837
819
  }
838
820
 
839
- testSuiteAsyncResource.runInAsyncScope(() => {
840
- testSuiteFinishCh.publish({ status: testSuiteResult.state, onFinish })
841
- })
821
+ testSuiteFinishCh.publish({ status: testSuiteResult.state, onFinish, ...testSuiteCtx.currentStore })
842
822
 
843
823
  // TODO: fix too frequent flushes
844
824
  await onFinishPromise
@@ -221,13 +221,8 @@ class CucumberPlugin extends CiPlugin {
221
221
  this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
222
222
  })
223
223
 
224
- this.addSub('ci:cucumber:test:start', ({
225
- testName,
226
- testFileAbsolutePath,
227
- testSourceLine,
228
- isParallel,
229
- promises
230
- }) => {
224
+ this.addBind('ci:cucumber:test:start', (ctx) => {
225
+ const { testName, testFileAbsolutePath, testSourceLine, isParallel, promises } = ctx
231
226
  const store = storage('legacy').getStore()
232
227
  const testSuite = getTestSuitePath(testFileAbsolutePath, this.sourceRoot)
233
228
  const testSourceFile = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
@@ -240,22 +235,23 @@ class CucumberPlugin extends CiPlugin {
240
235
  extraTags[CUCUMBER_IS_PARALLEL] = 'true'
241
236
  }
242
237
 
243
- const testSpan = this.startTestSpan(testName, testSuite, extraTags)
238
+ const span = this.startTestSpan(testName, testSuite, extraTags)
244
239
 
245
- this.enter(testSpan, store)
240
+ ctx.parentStore = store
241
+ ctx.currentStore = { ...store, span }
246
242
 
247
- this.activeTestSpan = testSpan
243
+ this.activeTestSpan = span
248
244
  // Time we give the breakpoint to be hit
249
245
  if (promises && this.runningTestProbe) {
250
246
  promises.hitBreakpointPromise = new Promise((resolve) => {
251
247
  setTimeout(resolve, BREAKPOINT_HIT_GRACE_PERIOD_MS)
252
248
  })
253
249
  }
250
+
251
+ return ctx.currentStore
254
252
  })
255
253
 
256
- this.addSub('ci:cucumber:test:retry', ({ isFirstAttempt, error, isAtrRetry }) => {
257
- const store = storage('legacy').getStore()
258
- const span = store.span
254
+ this.addSub('ci:cucumber:test:retry', ({ span, isFirstAttempt, error, isAtrRetry }) => {
259
255
  if (!isFirstAttempt) {
260
256
  span.setTag(TEST_IS_RETRY, 'true')
261
257
  if (isAtrRetry) {
@@ -284,7 +280,9 @@ class CucumberPlugin extends CiPlugin {
284
280
  finishAllTraceSpans(span)
285
281
  })
286
282
 
287
- this.addSub('ci:cucumber:test-step:start', ({ resource }) => {
283
+ this.addBind('ci:cucumber:test-step:start', (ctx) => {
284
+ const { resource } = ctx
285
+
288
286
  const store = storage('legacy').getStore()
289
287
  const childOf = store ? store.span : store
290
288
  const span = this.tracer.startSpan('cucumber.step', {
@@ -295,7 +293,10 @@ class CucumberPlugin extends CiPlugin {
295
293
  [RESOURCE_NAME]: resource
296
294
  }
297
295
  })
298
- this.enter(span, store)
296
+ ctx.parentStore = store
297
+ ctx.currentStore = { ...store, span }
298
+
299
+ return ctx.currentStore
299
300
  })
300
301
 
301
302
  this.addSub('ci:cucumber:worker-report:trace', (traces) => {
@@ -329,6 +330,7 @@ class CucumberPlugin extends CiPlugin {
329
330
  })
330
331
 
331
332
  this.addSub('ci:cucumber:test:finish', ({
333
+ span,
332
334
  isStep,
333
335
  status,
334
336
  skipReason,
@@ -345,7 +347,6 @@ class CucumberPlugin extends CiPlugin {
345
347
  isDisabled,
346
348
  isQuarantined
347
349
  }) => {
348
- const span = storage('legacy').getStore().span
349
350
  const statusTag = isStep ? 'step.status' : TEST_STATUS
350
351
 
351
352
  span.setTag(statusTag, status)
@@ -425,11 +426,21 @@ class CucumberPlugin extends CiPlugin {
425
426
  }
426
427
  })
427
428
 
428
- this.addSub('ci:cucumber:error', (err) => {
429
+ this.addBind('ci:cucumber:error', (ctx) => {
430
+ const { err } = ctx
429
431
  if (err) {
430
- const span = storage('legacy').getStore().span
432
+ const span = ctx.currentStore.span
431
433
  span.setTag('error', err)
434
+
435
+ ctx.parentStore = ctx.currentStore
436
+ ctx.currentStore = { ...ctx.currentStore, span }
432
437
  }
438
+
439
+ return ctx.currentStore
440
+ })
441
+
442
+ this.addBind('ci:cucumber:test:fn', (ctx) => {
443
+ return ctx.currentStore
433
444
  })
434
445
  }
435
446
 
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ const RedisPlugin = require('../../datadog-plugin-redis/src')
4
+
5
+ class IOValkeyPlugin extends RedisPlugin {
6
+ static get id () {
7
+ return 'iovalkey'
8
+ }
9
+
10
+ static get system () { return 'valkey' }
11
+
12
+ constructor (...args) {
13
+ super(...args)
14
+ this._spanType = 'valkey'
15
+ }
16
+ }
17
+
18
+ module.exports = IOValkeyPlugin
@@ -333,15 +333,24 @@ class JestPlugin extends CiPlugin {
333
333
  this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, files.length)
334
334
  })
335
335
 
336
- this.addSub('ci:jest:test:start', (test) => {
336
+ this.addBind('ci:jest:test:start', (ctx) => {
337
337
  const store = storage('legacy').getStore()
338
- const span = this.startTestSpan(test)
338
+ const span = this.startTestSpan(ctx)
339
+
340
+ ctx.parentStore = store
341
+ ctx.currentStore = { ...store, span }
339
342
 
340
- this.enter(span, store)
341
343
  this.activeTestSpan = span
344
+
345
+ return ctx.currentStore
346
+ })
347
+
348
+ this.addBind('ci:jest:test:fn', (ctx) => {
349
+ return ctx.currentStore
342
350
  })
343
351
 
344
352
  this.addSub('ci:jest:test:finish', ({
353
+ span,
345
354
  status,
346
355
  testStartLine,
347
356
  attemptToFixPassed,
@@ -349,7 +358,6 @@ class JestPlugin extends CiPlugin {
349
358
  attemptToFixFailed,
350
359
  isAtrRetry
351
360
  }) => {
352
- const span = storage('legacy').getStore().span
353
361
  span.setTag(TEST_STATUS, status)
354
362
  if (testStartLine) {
355
363
  span.setTag(TEST_SOURCE_START, testStartLine)
@@ -384,11 +392,9 @@ class JestPlugin extends CiPlugin {
384
392
  this.activeTestSpan = null
385
393
  })
386
394
 
387
- this.addSub('ci:jest:test:err', ({ error, shouldSetProbe, promises }) => {
395
+ this.addSub('ci:jest:test:err', ({ span, error, shouldSetProbe, promises }) => {
388
396
  if (error) {
389
- const store = storage('legacy').getStore()
390
- if (store && store.span) {
391
- const span = store.span
397
+ if (span) {
392
398
  span.setTag(TEST_STATUS, 'fail')
393
399
  span.setTag('error', getFormattedError(error, this.repositoryRoot))
394
400
  if (shouldSetProbe) {
@@ -67,7 +67,7 @@ class KafkajsProducerPlugin extends ProducerPlugin {
67
67
  }
68
68
  }
69
69
 
70
- start ({ topic, messages, bootstrapServers, clusterId }) {
70
+ start ({ topic, messages, bootstrapServers, clusterId, disableHeaderInjection }) {
71
71
  const span = this.startSpan({
72
72
  resource: topic,
73
73
  meta: {
@@ -85,10 +85,11 @@ class KafkajsProducerPlugin extends ProducerPlugin {
85
85
  }
86
86
  for (const message of messages) {
87
87
  if (message !== null && typeof message === 'object') {
88
- if (!message.headers) {
89
- message.headers = {}
88
+ // message headers are not supported for kafka broker versions <0.11
89
+ if (!disableHeaderInjection) {
90
+ message.headers ??= {}
91
+ this.tracer.inject(span, 'text_map', message.headers)
90
92
  }
91
- this.tracer.inject(span, 'text_map', message.headers)
92
93
  if (this.config.dsmEnabled) {
93
94
  const payloadSize = getMessageSize(message)
94
95
  const edgeTags = ['direction:out', `topic:${topic}`, 'type:kafka']
@@ -98,7 +99,9 @@ class KafkajsProducerPlugin extends ProducerPlugin {
98
99
  }
99
100
 
100
101
  const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
101
- DsmPathwayCodec.encode(dataStreamsContext, message.headers)
102
+ if (!disableHeaderInjection) {
103
+ DsmPathwayCodec.encode(dataStreamsContext, message.headers)
104
+ }
102
105
  }
103
106
  }
104
107
  }
@@ -109,12 +109,9 @@ class MochaPlugin extends CiPlugin {
109
109
  this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
110
110
  })
111
111
 
112
- this.addSub('ci:mocha:test-suite:start', ({
113
- testSuiteAbsolutePath,
114
- isUnskippable,
115
- isForcedToRun,
116
- itrCorrelationId
117
- }) => {
112
+ this.addBind('ci:mocha:test-suite:start', (ctx) => {
113
+ const { testSuiteAbsolutePath, isUnskippable, isForcedToRun, itrCorrelationId } = ctx
114
+
118
115
  // If the test module span is undefined, the plugin has not been initialized correctly and we bail out
119
116
  if (!this.testModuleSpan) {
120
117
  return
@@ -164,38 +161,51 @@ class MochaPlugin extends CiPlugin {
164
161
  testSuiteSpan.setTag(ITR_CORRELATION_ID, itrCorrelationId)
165
162
  }
166
163
  const store = storage('legacy').getStore()
167
- this.enter(testSuiteSpan, store)
164
+ ctx.parentStore = store
165
+ ctx.currentStore = { ...store, testSuiteSpan }
168
166
  this._testSuites.set(testSuite, testSuiteSpan)
169
167
  })
170
168
 
171
- this.addSub('ci:mocha:test-suite:finish', (status) => {
172
- const store = storage('legacy').getStore()
173
- if (store && store.span) {
174
- const span = store.span
169
+ this.addSub('ci:mocha:test-suite:finish', ({ testSuiteSpan, status }) => {
170
+ if (testSuiteSpan) {
175
171
  // the test status of the suite may have been set in ci:mocha:test-suite:error already
176
- if (!span.context()._tags[TEST_STATUS]) {
177
- span.setTag(TEST_STATUS, status)
172
+ if (!testSuiteSpan.context()._tags[TEST_STATUS]) {
173
+ testSuiteSpan.setTag(TEST_STATUS, status)
178
174
  }
179
- span.finish()
175
+ testSuiteSpan.finish()
180
176
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
181
177
  }
182
178
  })
183
179
 
184
- this.addSub('ci:mocha:test-suite:error', (err) => {
185
- const store = storage('legacy').getStore()
186
- if (store && store.span) {
187
- const span = store.span
188
- span.setTag('error', err)
189
- span.setTag(TEST_STATUS, 'fail')
180
+ this.addBind('ci:mocha:test-suite:error', (ctx) => {
181
+ const { error } = ctx
182
+ const testSuiteSpan = ctx.currentStore?.testSuiteSpan
183
+
184
+ if (testSuiteSpan) {
185
+ testSuiteSpan.setTag('error', error)
186
+ testSuiteSpan.setTag(TEST_STATUS, 'fail')
187
+
188
+ ctx.parentStore = ctx.currentStore
189
+ ctx.currentStore = { ...ctx.currentStore, testSuiteSpan }
190
190
  }
191
+
192
+ return ctx.currentStore
191
193
  })
192
194
 
193
- this.addSub('ci:mocha:test:start', (testInfo) => {
195
+ this.addBind('ci:mocha:test:fn', (ctx) => {
196
+ return ctx.currentStore
197
+ })
198
+
199
+ this.addBind('ci:mocha:test:start', (ctx) => {
194
200
  const store = storage('legacy').getStore()
195
- const span = this.startTestSpan(testInfo)
201
+ const span = this.startTestSpan(ctx)
202
+
203
+ ctx.parentStore = store
204
+ ctx.currentStore = { ...store, span }
196
205
 
197
- this.enter(span, store)
198
206
  this.activeTestSpan = span
207
+
208
+ return ctx.currentStore
199
209
  })
200
210
 
201
211
  this.addSub('ci:mocha:worker:finish', () => {
@@ -203,6 +213,7 @@ class MochaPlugin extends CiPlugin {
203
213
  })
204
214
 
205
215
  this.addSub('ci:mocha:test:finish', ({
216
+ span,
206
217
  status,
207
218
  hasBeenRetried,
208
219
  isLastRetry,
@@ -212,9 +223,6 @@ class MochaPlugin extends CiPlugin {
212
223
  isAttemptToFixRetry,
213
224
  isAtrRetry
214
225
  }) => {
215
- const store = storage('legacy').getStore()
216
- const span = store?.span
217
-
218
226
  if (span) {
219
227
  span.setTag(TEST_STATUS, status)
220
228
  if (hasBeenRetried) {
@@ -260,19 +268,26 @@ class MochaPlugin extends CiPlugin {
260
268
  }
261
269
  })
262
270
 
263
- this.addSub('ci:mocha:test:skip', (testInfo) => {
271
+ this.addBind('ci:mocha:test:skip', (ctx) => {
264
272
  const store = storage('legacy').getStore()
265
273
  // skipped through it.skip, so the span is not created yet
266
274
  // for this test
267
275
  if (!store) {
268
- const testSpan = this.startTestSpan(testInfo)
269
- this.enter(testSpan, store)
276
+ const span = this.startTestSpan(ctx)
277
+
278
+ ctx.parentStore = store
279
+ ctx.currentStore = { ...store, span }
280
+
281
+ this.activeTestSpan = span
270
282
  }
283
+
284
+ return ctx.currentStore
271
285
  })
272
286
 
273
- this.addSub('ci:mocha:test:error', (err) => {
274
- const store = storage('legacy').getStore()
275
- const span = store?.span
287
+ this.addBind('ci:mocha:test:error', (ctx) => {
288
+ const { err } = ctx
289
+ const span = ctx.currentStore?.span
290
+
276
291
  if (err && span) {
277
292
  if (err.constructor.name === 'Pending' && !this.forbidPending) {
278
293
  span.setTag(TEST_STATUS, 'skip')
@@ -280,12 +295,17 @@ class MochaPlugin extends CiPlugin {
280
295
  span.setTag(TEST_STATUS, 'fail')
281
296
  span.setTag('error', err)
282
297
  }
298
+
299
+ ctx.parentStore = ctx.currentStore
300
+ ctx.currentStore = { ...ctx.currentStore, span }
301
+
302
+ this.activeTestSpan = span
283
303
  }
304
+
305
+ return ctx.currentStore
284
306
  })
285
307
 
286
- this.addSub('ci:mocha:test:retry', ({ isFirstAttempt, willBeRetried, err, test, isAtrRetry }) => {
287
- const store = storage('legacy').getStore()
288
- const span = store?.span
308
+ this.addSub('ci:mocha:test:retry', ({ span, isFirstAttempt, willBeRetried, err, test, isAtrRetry }) => {
289
309
  if (span) {
290
310
  span.setTag(TEST_STATUS, 'fail')
291
311
  if (!isFirstAttempt) {