dd-trace 6.0.0-pre-a99e5ea → 6.0.0-pre-2d874ad

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "6.0.0-pre-a99e5ea",
3
+ "version": "6.0.0-pre-2d874ad",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -1,6 +1,9 @@
1
+ const semver = require('semver')
2
+
1
3
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
2
4
  const shimmer = require('../../datadog-shimmer')
3
- const { parseAnnotations } = require('../../dd-trace/src/plugins/util/test')
5
+ const { parseAnnotations, getTestSuitePath } = require('../../dd-trace/src/plugins/util/test')
6
+ const log = require('../../dd-trace/src/log')
4
7
 
5
8
  const testStartCh = channel('ci:playwright:test:start')
6
9
  const testFinishCh = channel('ci:playwright:test:finish')
@@ -8,6 +11,9 @@ const testFinishCh = channel('ci:playwright:test:finish')
8
11
  const testSessionStartCh = channel('ci:playwright:session:start')
9
12
  const testSessionFinishCh = channel('ci:playwright:session:finish')
10
13
 
14
+ const libraryConfigurationCh = channel('ci:playwright:library-configuration')
15
+ const knownTestsCh = channel('ci:playwright:known-tests')
16
+
11
17
  const testSuiteStartCh = channel('ci:playwright:test-suite:start')
12
18
  const testSuiteFinishCh = channel('ci:playwright:test-suite:finish')
13
19
 
@@ -16,6 +22,8 @@ const testSuiteToAr = new Map()
16
22
  const testSuiteToTestStatuses = new Map()
17
23
  const testSuiteToErrors = new Map()
18
24
 
25
+ let applyRepeatEachIndex = null
26
+
19
27
  let startedSuites = []
20
28
 
21
29
  const STATUS_TO_TEST_STATUS = {
@@ -26,6 +34,44 @@ const STATUS_TO_TEST_STATUS = {
26
34
  }
27
35
 
28
36
  let remainingTestsByFile = {}
37
+ let isEarlyFlakeDetectionEnabled = false
38
+ let earlyFlakeDetectionNumRetries = 0
39
+ let knownTests = []
40
+ let rootDir = ''
41
+ const MINIMUM_SUPPORTED_VERSION_EFD = '1.38.0'
42
+
43
+ function isNewTest (test) {
44
+ const testSuite = getTestSuitePath(test._requireFile, rootDir)
45
+ const testsForSuite = knownTests?.playwright?.[testSuite] || []
46
+
47
+ return !testsForSuite.includes(test.title)
48
+ }
49
+
50
+ function getSuiteType (test, type) {
51
+ let suite = test.parent
52
+ while (suite && suite._type !== type) {
53
+ suite = suite.parent
54
+ }
55
+ return suite
56
+ }
57
+
58
+ // Copy of Suite#_deepClone but with a function to filter tests
59
+ function deepCloneSuite (suite, filterTest) {
60
+ const copy = suite._clone()
61
+ for (const entry of suite._entries) {
62
+ if (entry.constructor.name === 'Suite') {
63
+ copy._addSuite(deepCloneSuite(entry, filterTest))
64
+ } else {
65
+ if (filterTest(entry)) {
66
+ const copiedTest = entry._clone()
67
+ copiedTest._ddIsNew = true
68
+ copiedTest._ddIsEfdRetry = true
69
+ copy._addTest(copiedTest)
70
+ }
71
+ }
72
+ }
73
+ return copy
74
+ }
29
75
 
30
76
  function getTestsBySuiteFromTestGroups (testGroups) {
31
77
  return testGroups.reduce((acc, { requireFile, tests }) => {
@@ -153,8 +199,11 @@ function getTestSuiteError (testSuiteAbsolutePath) {
153
199
  function testBeginHandler (test, browserName) {
154
200
  const {
155
201
  _requireFile: testSuiteAbsolutePath,
156
- title: testName, _type,
157
- location: { line: testSourceLine }
202
+ title: testName,
203
+ _type,
204
+ location: {
205
+ line: testSourceLine
206
+ }
158
207
  } = test
159
208
 
160
209
  if (_type === 'beforeAll' || _type === 'afterAll') {
@@ -198,7 +247,14 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout) {
198
247
  const testResult = results[results.length - 1]
199
248
  const testAsyncResource = testToAr.get(test)
200
249
  testAsyncResource.runInAsyncScope(() => {
201
- testFinishCh.publish({ testStatus, steps: testResult.steps, error, extraTags: annotationTags })
250
+ testFinishCh.publish({
251
+ testStatus,
252
+ steps: testResult.steps,
253
+ error,
254
+ extraTags: annotationTags,
255
+ isNew: test._ddIsNew,
256
+ isEfdRetry: test._ddIsEfdRetry
257
+ })
202
258
  })
203
259
 
204
260
  if (testSuiteToTestStatuses.has(testSuiteAbsolutePath)) {
@@ -309,14 +365,54 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
309
365
 
310
366
  function runnerHook (runnerExport, playwrightVersion) {
311
367
  shimmer.wrap(runnerExport.Runner.prototype, 'runAllTests', runAllTests => async function () {
368
+ let onDone
369
+
312
370
  const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn')
313
- const rootDir = getRootDir(this)
371
+
372
+ rootDir = getRootDir(this)
314
373
 
315
374
  const processArgv = process.argv.slice(2).join(' ')
316
375
  const command = `playwright ${processArgv}`
317
376
  testSessionAsyncResource.runInAsyncScope(() => {
318
377
  testSessionStartCh.publish({ command, frameworkVersion: playwrightVersion, rootDir })
319
378
  })
379
+
380
+ const configurationPromise = new Promise((resolve) => {
381
+ onDone = resolve
382
+ })
383
+
384
+ testSessionAsyncResource.runInAsyncScope(() => {
385
+ libraryConfigurationCh.publish({ onDone })
386
+ })
387
+
388
+ try {
389
+ const { err, libraryConfig } = await configurationPromise
390
+ if (!err) {
391
+ isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
392
+ earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
393
+ }
394
+ } catch (e) {
395
+ log.error(e)
396
+ }
397
+
398
+ if (isEarlyFlakeDetectionEnabled && semver.gte(playwrightVersion, MINIMUM_SUPPORTED_VERSION_EFD)) {
399
+ const knownTestsPromise = new Promise((resolve) => {
400
+ onDone = resolve
401
+ })
402
+ testSessionAsyncResource.runInAsyncScope(() => {
403
+ knownTestsCh.publish({ onDone })
404
+ })
405
+
406
+ try {
407
+ const { err, knownTests: receivedKnownTests } = await knownTestsPromise
408
+ if (!err) {
409
+ knownTests = receivedKnownTests
410
+ }
411
+ } catch (err) {
412
+ log.error(err)
413
+ }
414
+ }
415
+
320
416
  const projects = getProjectsFromRunner(this)
321
417
 
322
418
  const runAllTestsReturn = await runAllTests.apply(this, arguments)
@@ -334,7 +430,6 @@ function runnerHook (runnerExport, playwrightVersion) {
334
430
 
335
431
  const sessionStatus = runAllTestsReturn.status || runAllTestsReturn
336
432
 
337
- let onDone
338
433
  const flushWait = new Promise(resolve => {
339
434
  onDone = resolve
340
435
  })
@@ -394,3 +489,53 @@ addHook({
394
489
  file: 'lib/runner/dispatcher.js',
395
490
  versions: ['>=1.38.0']
396
491
  }, (dispatcher) => dispatcherHookNew(dispatcher, dispatcherRunWrapperNew))
492
+
493
+ // Hook used for early flake detection. EFD only works from >=1.38.0
494
+ addHook({
495
+ name: 'playwright',
496
+ file: 'lib/common/suiteUtils.js',
497
+ versions: [`>=${MINIMUM_SUPPORTED_VERSION_EFD}`]
498
+ }, suiteUtilsPackage => {
499
+ // We grab `applyRepeatEachIndex` to use it later
500
+ // `applyRepeatEachIndex` needs to be applied to a cloned suite
501
+ applyRepeatEachIndex = suiteUtilsPackage.applyRepeatEachIndex
502
+ return suiteUtilsPackage
503
+ })
504
+
505
+ // Hook used for early flake detection. EFD only works from >=1.38.0
506
+ addHook({
507
+ name: 'playwright',
508
+ file: 'lib/runner/loadUtils.js',
509
+ versions: [`>=${MINIMUM_SUPPORTED_VERSION_EFD}`]
510
+ }, (loadUtilsPackage) => {
511
+ const oldCreateRootSuite = loadUtilsPackage.createRootSuite
512
+
513
+ async function newCreateRootSuite () {
514
+ const rootSuite = await oldCreateRootSuite.apply(this, arguments)
515
+ if (!isEarlyFlakeDetectionEnabled) {
516
+ return rootSuite
517
+ }
518
+ const newTests = rootSuite
519
+ .allTests()
520
+ .filter(isNewTest)
521
+
522
+ newTests.forEach(newTest => {
523
+ newTest._ddIsNew = true
524
+ if (newTest.expectedStatus !== 'skipped') {
525
+ const fileSuite = getSuiteType(newTest, 'file')
526
+ const projectSuite = getSuiteType(newTest, 'project')
527
+ for (let repeatEachIndex = 0; repeatEachIndex < earlyFlakeDetectionNumRetries; repeatEachIndex++) {
528
+ const copyFileSuite = deepCloneSuite(fileSuite, isNewTest)
529
+ applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
530
+ projectSuite._addSuite(copyFileSuite)
531
+ }
532
+ }
533
+ })
534
+
535
+ return rootSuite
536
+ }
537
+
538
+ loadUtilsPackage.createRootSuite = newCreateRootSuite
539
+
540
+ return loadUtilsPackage
541
+ })
@@ -11,7 +11,9 @@ const {
11
11
  TEST_SOURCE_START,
12
12
  TEST_CODE_OWNERS,
13
13
  TEST_SOURCE_FILE,
14
- TEST_CONFIGURATION_BROWSER_NAME
14
+ TEST_CONFIGURATION_BROWSER_NAME,
15
+ TEST_IS_NEW,
16
+ TEST_IS_RETRY
15
17
  } = require('../../dd-trace/src/plugins/util/test')
16
18
  const { RESOURCE_NAME } = require('../../../ext/tags')
17
19
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -109,7 +111,7 @@ class PlaywrightPlugin extends CiPlugin {
109
111
 
110
112
  this.enter(span, store)
111
113
  })
112
- this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error, extraTags }) => {
114
+ this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error, extraTags, isNew, isEfdRetry }) => {
113
115
  const store = storage.getStore()
114
116
  const span = store && store.span
115
117
  if (!span) return
@@ -122,6 +124,12 @@ class PlaywrightPlugin extends CiPlugin {
122
124
  if (extraTags) {
123
125
  span.addTags(extraTags)
124
126
  }
127
+ if (isNew) {
128
+ span.setTag(TEST_IS_NEW, 'true')
129
+ if (isEfdRetry) {
130
+ span.setTag(TEST_IS_RETRY, 'true')
131
+ }
132
+ }
125
133
 
126
134
  steps.forEach(step => {
127
135
  const stepStartTime = step.startTime.getTime()
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const RateLimiter = require('./rate_limiter')
3
4
  const Sampler = require('./sampler')
4
5
  const { setSamplingRules } = require('./startup-log')
5
6
  const SamplingRule = require('./sampling_rule')
@@ -43,6 +44,7 @@ class PrioritySampler {
43
44
  configure (env, { sampleRate, rateLimit = 100, rules = [] } = {}) {
44
45
  this._env = env
45
46
  this._rules = this._normalizeRules(rules, sampleRate, rateLimit)
47
+ this._limiter = new RateLimiter(rateLimit)
46
48
 
47
49
  setSamplingRules(this._rules)
48
50
  }
@@ -136,14 +138,17 @@ class PrioritySampler {
136
138
  context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
137
139
  context._sampling.mechanism = SAMPLING_MECHANISM_RULE
138
140
 
139
- const sampled = rule.sample()
140
- const priority = sampled ? USER_KEEP : USER_REJECT
141
+ return rule.sample() && this._isSampledByRateLimit(context)
142
+ ? USER_KEEP
143
+ : USER_REJECT
144
+ }
141
145
 
142
- if (sampled) {
143
- context._trace[SAMPLING_LIMIT_DECISION] = rule.effectiveRate
144
- }
146
+ _isSampledByRateLimit (context) {
147
+ const allowed = this._limiter.isAllowed()
148
+
149
+ context._trace[SAMPLING_LIMIT_DECISION] = this._limiter.effectiveRate()
145
150
 
146
- return priority
151
+ return allowed
147
152
  }
148
153
 
149
154
  _getPriorityByAgent (context) {