Skip to content

Commit 920d2a2

Browse files
[test optimization] Report code coverage relative to the repository root, not the project's root dir or working directory (#4903)
1 parent a41951c commit 920d2a2

File tree

10 files changed

+214
-6
lines changed

10 files changed

+214
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = function (a, b) {
2+
return a + b
3+
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// eslint-disable-next-line
22
const { expect } = require('chai')
3+
const dependency = require('./dependency')
34

45
describe('subproject-test', () => {
56
it('can run', () => {
67
// eslint-disable-next-line
7-
expect(1).to.equal(1)
8+
expect(dependency(1, 2)).to.equal(3)
89
})
910
})

integration-tests/cucumber/cucumber.spec.js

+49
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ versions.forEach(version => {
275275
}
276276
)
277277
})
278+
278279
it('can report code coverage', (done) => {
279280
const libraryConfigRequestPromise = receiver.payloadReceived(
280281
({ url }) => url.endsWith('/api/v2/libraries/tests/services/setting')
@@ -355,6 +356,7 @@ versions.forEach(version => {
355356
done()
356357
})
357358
})
359+
358360
it('does not report code coverage if disabled by the API', (done) => {
359361
receiver.setSettings({
360362
itr_enabled: false,
@@ -390,6 +392,7 @@ versions.forEach(version => {
390392
}
391393
)
392394
})
395+
393396
it('can skip suites received by the intelligent test runner API and still reports code coverage',
394397
(done) => {
395398
receiver.setSuitesToSkip([{
@@ -463,6 +466,7 @@ versions.forEach(version => {
463466
}
464467
)
465468
})
469+
466470
it('does not skip tests if git metadata upload fails', (done) => {
467471
receiver.setSuitesToSkip([{
468472
type: 'suite',
@@ -505,6 +509,7 @@ versions.forEach(version => {
505509
}
506510
)
507511
})
512+
508513
it('does not skip tests if test skipping is disabled by the API', (done) => {
509514
receiver.setSettings({
510515
itr_enabled: true,
@@ -543,6 +548,7 @@ versions.forEach(version => {
543548
}
544549
)
545550
})
551+
546552
it('does not skip suites if suite is marked as unskippable', (done) => {
547553
receiver.setSettings({
548554
itr_enabled: true,
@@ -611,6 +617,7 @@ versions.forEach(version => {
611617
}).catch(done)
612618
})
613619
})
620+
614621
it('only sets forced to run if suite was going to be skipped by ITR', (done) => {
615622
receiver.setSettings({
616623
itr_enabled: true,
@@ -673,6 +680,7 @@ versions.forEach(version => {
673680
}).catch(done)
674681
})
675682
})
683+
676684
it('sets _dd.ci.itr.tests_skipped to false if the received suite is not skipped', (done) => {
677685
receiver.setSuitesToSkip([{
678686
type: 'suite',
@@ -709,6 +717,7 @@ versions.forEach(version => {
709717
}).catch(done)
710718
})
711719
})
720+
712721
if (!isAgentless) {
713722
context('if the agent is not event platform proxy compatible', () => {
714723
it('does not do any intelligent test runner request', (done) => {
@@ -757,6 +766,7 @@ versions.forEach(version => {
757766
})
758767
})
759768
}
769+
760770
it('reports itr_correlation_id in test suites', (done) => {
761771
const itrCorrelationId = '4321'
762772
receiver.setItrCorrelationId(itrCorrelationId)
@@ -783,6 +793,45 @@ versions.forEach(version => {
783793
}).catch(done)
784794
})
785795
})
796+
797+
it('reports code coverage relative to the repository root, not working directory', (done) => {
798+
receiver.setSettings({
799+
itr_enabled: true,
800+
code_coverage: true,
801+
tests_skipping: false
802+
})
803+
804+
const codeCoveragesPromise = receiver
805+
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcov'), (payloads) => {
806+
const coveredFiles = payloads
807+
.flatMap(({ payload }) => payload)
808+
.flatMap(({ content: { coverages } }) => coverages)
809+
.flatMap(({ files }) => files)
810+
.map(({ filename }) => filename)
811+
812+
assert.includeMembers(coveredFiles, [
813+
'ci-visibility/subproject/features/support/steps.js',
814+
'ci-visibility/subproject/features/greetings.feature'
815+
])
816+
})
817+
818+
childProcess = exec(
819+
'../../node_modules/nyc/bin/nyc.js node ../../node_modules/.bin/cucumber-js features/*.feature',
820+
{
821+
cwd: `${cwd}/ci-visibility/subproject`,
822+
env: {
823+
...getCiVisAgentlessConfig(receiver.port)
824+
},
825+
stdio: 'inherit'
826+
}
827+
)
828+
829+
childProcess.on('exit', () => {
830+
codeCoveragesPromise.then(() => {
831+
done()
832+
}).catch(done)
833+
})
834+
})
786835
})
787836

788837
context('early flake detection', () => {

integration-tests/cypress/cypress.spec.js

+57
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,63 @@ moduleTypes.forEach(({
837837
}).catch(done)
838838
})
839839
})
840+
841+
it('reports code coverage relative to the repository root, not working directory', (done) => {
842+
receiver.setSettings({
843+
itr_enabled: false,
844+
code_coverage: true,
845+
tests_skipping: false
846+
})
847+
let command
848+
849+
if (type === 'commonJS') {
850+
const commandSuffix = version === '6.7.0'
851+
? '--config-file cypress-config.json --spec "cypress/e2e/*.cy.js"'
852+
: ''
853+
command = `../../node_modules/.bin/cypress run ${commandSuffix}`
854+
} else {
855+
command = `node --loader=${hookFile} ../../cypress-esm-config.mjs`
856+
}
857+
858+
const {
859+
NODE_OPTIONS, // NODE_OPTIONS dd-trace config does not work with cypress
860+
...restEnvVars
861+
} = getCiVisAgentlessConfig(receiver.port)
862+
863+
const eventsPromise = receiver
864+
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcov'), (payloads) => {
865+
const coveredFiles = payloads
866+
.flatMap(({ payload }) => payload)
867+
.flatMap(({ content: { coverages } }) => coverages)
868+
.flatMap(({ files }) => files)
869+
.map(({ filename }) => filename)
870+
871+
assert.includeMembers(coveredFiles, [
872+
'ci-visibility/subproject/src/utils.tsx',
873+
'ci-visibility/subproject/src/App.tsx',
874+
'ci-visibility/subproject/src/index.tsx',
875+
'ci-visibility/subproject/cypress/e2e/spec.cy.js'
876+
])
877+
}, 10000)
878+
879+
childProcess = exec(
880+
command,
881+
{
882+
cwd: `${cwd}/ci-visibility/subproject`,
883+
env: {
884+
...restEnvVars,
885+
CYPRESS_BASE_URL: `http://localhost:${webAppPort}`
886+
},
887+
stdio: 'inherit'
888+
}
889+
)
890+
891+
childProcess.on('exit', () => {
892+
eventsPromise.then(() => {
893+
done()
894+
}).catch(done)
895+
})
896+
})
840897
})
841898

842899
it('still reports correct format if there is a plugin incompatibility', (done) => {

integration-tests/jest/jest.spec.js

+42
Original file line numberDiff line numberDiff line change
@@ -1469,6 +1469,48 @@ describe('jest CommonJS', () => {
14691469
eventsPromise.then(done).catch(done)
14701470
})
14711471
})
1472+
1473+
it('reports code coverage relative to the repository root, not working directory', (done) => {
1474+
receiver.setSettings({
1475+
itr_enabled: true,
1476+
code_coverage: true,
1477+
tests_skipping: false
1478+
})
1479+
1480+
const codeCoveragesPromise = receiver
1481+
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcov'), (payloads) => {
1482+
const coveredFiles = payloads
1483+
.flatMap(({ payload }) => payload)
1484+
.flatMap(({ content: { coverages } }) => coverages)
1485+
.flatMap(({ files }) => files)
1486+
.map(({ filename }) => filename)
1487+
1488+
assert.includeMembers(coveredFiles, [
1489+
'ci-visibility/subproject/dependency.js',
1490+
'ci-visibility/subproject/subproject-test.js'
1491+
])
1492+
}, 5000)
1493+
1494+
childProcess = exec(
1495+
'node ./node_modules/jest/bin/jest --config config-jest.js --rootDir ci-visibility/subproject',
1496+
{
1497+
cwd,
1498+
env: {
1499+
...getCiVisAgentlessConfig(receiver.port),
1500+
PROJECTS: JSON.stringify([{
1501+
testMatch: ['**/subproject-test*']
1502+
}])
1503+
},
1504+
stdio: 'inherit'
1505+
}
1506+
)
1507+
1508+
childProcess.on('exit', () => {
1509+
codeCoveragesPromise.then(() => {
1510+
done()
1511+
}).catch(done)
1512+
})
1513+
})
14721514
})
14731515

14741516
context('early flake detection', () => {

integration-tests/mocha/mocha.spec.js

+39
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,45 @@ describe('mocha CommonJS', function () {
10851085
}).catch(done)
10861086
})
10871087
})
1088+
1089+
it('reports code coverage relative to the repository root, not working directory', (done) => {
1090+
receiver.setSettings({
1091+
itr_enabled: true,
1092+
code_coverage: true,
1093+
tests_skipping: false
1094+
})
1095+
1096+
const codeCoveragesPromise = receiver
1097+
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcov'), (payloads) => {
1098+
const coveredFiles = payloads
1099+
.flatMap(({ payload }) => payload)
1100+
.flatMap(({ content: { coverages } }) => coverages)
1101+
.flatMap(({ files }) => files)
1102+
.map(({ filename }) => filename)
1103+
1104+
assert.includeMembers(coveredFiles, [
1105+
'ci-visibility/subproject/dependency.js',
1106+
'ci-visibility/subproject/subproject-test.js'
1107+
])
1108+
}, 5000)
1109+
1110+
childProcess = exec(
1111+
'../../node_modules/nyc/bin/nyc.js node ../../node_modules/mocha/bin/mocha subproject-test.js',
1112+
{
1113+
cwd: `${cwd}/ci-visibility/subproject`,
1114+
env: {
1115+
...getCiVisAgentlessConfig(receiver.port)
1116+
},
1117+
stdio: 'inherit'
1118+
}
1119+
)
1120+
1121+
childProcess.on('exit', () => {
1122+
codeCoveragesPromise.then(() => {
1123+
done()
1124+
}).catch(done)
1125+
})
1126+
})
10881127
})
10891128

10901129
context('early flake detection', () => {

packages/datadog-instrumentations/src/jest.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
127127

128128
if (repositoryRoot) {
129129
this.testSourceFile = getTestSuitePath(context.testPath, repositoryRoot)
130+
this.repositoryRoot = repositoryRoot
130131
}
131132

132133
this.isEarlyFlakeDetectionEnabled = this.testEnvironmentOptions._ddIsEarlyFlakeDetectionEnabled
@@ -667,10 +668,13 @@ function jestAdapterWrapper (jestAdapter, jestVersion) {
667668
* controls whether coverage is reported.
668669
*/
669670
if (environment.testEnvironmentOptions?._ddTestCodeCoverageEnabled) {
671+
const root = environment.repositoryRoot || environment.rootDir
672+
670673
const coverageFiles = getCoveredFilenamesFromCoverage(environment.global.__coverage__)
671-
.map(filename => getTestSuitePath(filename, environment.rootDir))
674+
.map(filename => getTestSuitePath(filename, root))
675+
672676
asyncResource.runInAsyncScope(() => {
673-
testSuiteCodeCoverageCh.publish({ coverageFiles, testSuite: environment.testSuite })
677+
testSuiteCodeCoverageCh.publish({ coverageFiles, testSuite: environment.testSourceFile })
674678
})
675679
}
676680
testSuiteFinishCh.publish({ status, errorMessage })

packages/datadog-plugin-cypress/src/cypress-plugin.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -658,10 +658,22 @@ class CypressPlugin {
658658
log.warn('There is no active test span in dd:afterEach handler')
659659
return null
660660
}
661-
const { state, error, isRUMActive, testSourceLine, testSuite, testName, isNew, isEfdRetry } = test
661+
const {
662+
state,
663+
error,
664+
isRUMActive,
665+
testSourceLine,
666+
testSuite,
667+
testSuiteAbsolutePath,
668+
testName,
669+
isNew,
670+
isEfdRetry
671+
} = test
662672
if (coverage && this.isCodeCoverageEnabled && this.tracer._tracer._exporter?.exportCoverage) {
663673
const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
664-
const relativeCoverageFiles = coverageFiles.map(file => getTestSuitePath(file, this.rootDir))
674+
const relativeCoverageFiles = [...coverageFiles, testSuiteAbsolutePath].map(
675+
file => getTestSuitePath(file, this.repositoryRoot || this.rootDir)
676+
)
665677
if (!relativeCoverageFiles.length) {
666678
incrementCountMetric(TELEMETRY_CODE_COVERAGE_EMPTY)
667679
}

packages/datadog-plugin-cypress/src/support.js

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ afterEach(function () {
8888
const testInfo = {
8989
testName: currentTest.fullTitle(),
9090
testSuite: Cypress.mocha.getRootSuite().file,
91+
testSuiteAbsolutePath: Cypress.spec && Cypress.spec.absolute,
9192
state: currentTest.state,
9293
error: currentTest.err,
9394
isNew: currentTest._ddIsNew,

packages/datadog-plugin-mocha/src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class MochaPlugin extends CiPlugin {
8585
}
8686

8787
const relativeCoverageFiles = [...coverageFiles, suiteFile]
88-
.map(filename => getTestSuitePath(filename, this.sourceRoot))
88+
.map(filename => getTestSuitePath(filename, this.repositoryRoot || this.sourceRoot))
8989

9090
const { _traceId, _spanId } = testSuiteSpan.context()
9191

0 commit comments

Comments
 (0)