Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions integration-tests/ci-visibility/mocha-flaky/flaky-fails.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict'

const assert = require('assert')

describe('mocha-flaky', () => {
it('can retry failed tests', () => {
assert.strictEqual(1 + 2, 4)
})
})
14 changes: 14 additions & 0 deletions integration-tests/ci-visibility/mocha-flaky/flaky-passes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict'

const assert = require('assert')
let counter = 0

describe('mocha-flaky', () => {
it('can retry flaky tests', () => {
assert.strictEqual(++counter, 2)
})

it('will not retry passed tests', () => {
assert.ok(true)
})
})
305 changes: 305 additions & 0 deletions integration-tests/mocha/mocha.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const {
LIBRARY_VERSION,
TEST_PARAMETERS,
DD_CI_LIBRARY_CONFIGURATION_ERROR,
TEST_FINAL_STATUS,
} = require('../../packages/dd-trace/src/plugins/util/test')
const { DD_HOST_CPU_COUNT } = require('../../packages/dd-trace/src/plugins/util/env')
const {
Expand Down Expand Up @@ -5078,4 +5079,308 @@ describe(`mocha@${MOCHA_VERSION}`, function () {
assert.strictEqual(coverageReportUploaded, false, 'coverage report should not be uploaded')
})
})
context('final status tag', function () {
it('sets final_status tag to test status on regular tests without retry features', async () => {
receiver.setSettings({
itr_enabled: false,
code_coverage: false,
tests_skipping: false,
flaky_test_retries_enabled: false,
early_flake_detection: { enabled: false },
})

const eventsPromise = receiver
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
const events = payloads.flatMap(({ payload }) => payload.events)
const tests = events.filter(event => event.type === 'test').map(event => event.content)

tests.forEach(test => {
assert.strictEqual(
test.meta[TEST_FINAL_STATUS],
test.meta[TEST_STATUS],
`Expected TEST_FINAL_STATUS to match TEST_STATUS for test "${test.meta[TEST_NAME]}"`
)
})
})

childProcess = exec(runTestsCommand, {
cwd,
env: {
...getCiVisAgentlessConfig(receiver.port),
TESTS_TO_RUN: JSON.stringify([
'./test/ci-visibility-test.js',
'./test/fail-test.js',
]),
},
})

await Promise.all([once(childProcess, 'exit'), eventsPromise])
})

onlyLatestIt('sets tag only on last ATR retry when EFD is enabled but not active and ATR is active', async () => {
// All mocha-flaky tests are known, so EFD will be enabled but not active for them
receiver.setKnownTests({
mocha: {
'ci-visibility/mocha-flaky/flaky-passes.js': [
'mocha-flaky can retry flaky tests',
'mocha-flaky will not retry passed tests',
],
},
})
receiver.setSettings({
flaky_test_retries_enabled: true,
early_flake_detection: {
enabled: true,
slow_test_retries: { '5s': 3 },
faulty_session_threshold: 100,
},
known_tests_enabled: true,
})

const eventsPromise = receiver
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
const events = payloads.flatMap(({ payload }) => payload.events)
const tests = events
.filter(event => event.type === 'test')
.map(event => event.content)
.filter(test => test.meta[TEST_NAME] === 'mocha-flaky can retry flaky tests')

// We expect 2 executions: the failed (retry) and the passed (last one)
assert.strictEqual(tests.length, 2)

// Only the last execution (the one with status 'pass') should have TEST_FINAL_STATUS tag
tests.sort((a, b) => a.meta.start - b.meta.start).forEach((test, idx) => {
if (idx < tests.length - 1) {
assert.ok(!(TEST_FINAL_STATUS in test.meta),
'TEST_FINAL_STATUS should not be set on previous runs'
)
} else {
assert.strictEqual(test.meta[TEST_FINAL_STATUS], test.meta[TEST_STATUS])
assert.strictEqual(test.meta[TEST_STATUS], 'pass')
}
})
})

childProcess = exec(runTestsCommand, {
cwd,
env: {
...getCiVisAgentlessConfig(receiver.port),
TESTS_TO_RUN: JSON.stringify(['./mocha-flaky/flaky-passes.js']),
},
})

await Promise.all([once(childProcess, 'exit'), eventsPromise])
})

it('sets final_status tag to test status reported to test framework on last retry', async () => {
// ci-visibility-test-2.js will be treated as a new test (not in known tests)
const knownTestFile = 'ci-visibility/test/ci-visibility-test.js'
receiver.setKnownTests({
mocha: { [knownTestFile]: ['ci visibility can report tests'] },
})
const NUM_RETRIES_EFD = 3
receiver.setSettings({
early_flake_detection: {
enabled: true,
slow_test_retries: { '5s': NUM_RETRIES_EFD },
faulty_session_threshold: 100,
},
known_tests_enabled: true,
})

const eventsPromise = receiver
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
const events = payloads.flatMap(({ payload }) => payload.events)
const tests = events.filter(event => event.type === 'test').map(event => event.content)

// Known test: every execution is final
const knownTests = tests.filter(test => test.meta[TEST_SUITE] === knownTestFile)
knownTests.forEach(test => {
assert.strictEqual(test.meta[TEST_FINAL_STATUS], test.meta[TEST_STATUS])
})

// New test: only the last retry should have the tag
const newTests = tests.filter(test =>
test.meta[TEST_SUITE] === 'ci-visibility/test/ci-visibility-test-2.js'
)

newTests.sort((a, b) => a.meta.start - b.meta.start).forEach((test, index) => {
if (index < newTests.length - 1) {
assert.ok(!(TEST_FINAL_STATUS in test.meta))
} else {
// only the last execution should have the final status
assert.strictEqual(test.meta[TEST_FINAL_STATUS], 'pass')
}
})
})

childProcess = exec(runTestsCommand, {
cwd,
env: {
...getCiVisAgentlessConfig(receiver.port),
TESTS_TO_RUN: JSON.stringify([
'./test/ci-visibility-test.js',
'./test/ci-visibility-test-2.js',
]),
},
})

await Promise.all([once(childProcess, 'exit'), eventsPromise])
})

onlyLatestIt('sets final_status tag to test status reported to test framework on last retry', async () => {
receiver.setSettings({
itr_enabled: false,
code_coverage: false,
tests_skipping: false,
flaky_test_retries_enabled: true,
early_flake_detection: { enabled: false },
})

const eventsPromise = receiver
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
const events = payloads.flatMap(({ payload }) => payload.events)
const tests = events.filter(event => event.type === 'test').map(event => event.content)

// Test that eventually passes: finalStatus 'pass' only on last attempt
// These tests will pass after the second retry
const eventuallyPassingTests = tests.filter(
test => test.meta[TEST_SUITE] === 'ci-visibility/mocha-flaky/flaky-passes.js'
)
eventuallyPassingTests.sort((a, b) => a.meta.start - b.meta.start).forEach((test, index) => {
if (index < eventuallyPassingTests.length - 1) {
assert.ok(!(TEST_FINAL_STATUS in test.meta))
} else {
assert.strictEqual(test.meta[TEST_FINAL_STATUS], 'pass')
}
})

// Test that always passes: finalStatus 'pass' only on last attempt
const alwaysPassingTests = tests.filter(
test => test.meta[TEST_SUITE] === 'ci-visibility/test/ci-visibility-test.js'
)
alwaysPassingTests.sort((a, b) => a.meta.start - b.meta.start).forEach((test, index) => {
if (index < alwaysPassingTests.length - 1) {
assert.ok(!(TEST_FINAL_STATUS in test.meta))
} else {
assert.strictEqual(test.meta[TEST_FINAL_STATUS], 'pass')
}
})

// Test that always fails: finalStatus 'fail' only on last attempt
const alwaysFailingTests = tests.filter(
test => test.meta[TEST_SUITE] === 'ci-visibility/mocha-flaky/flaky-fails.js'
)
alwaysFailingTests.sort((a, b) => a.meta.start - b.meta.start).forEach((test, index) => {
if (index < alwaysFailingTests.length - 1) {
assert.ok(!(TEST_FINAL_STATUS in test.meta))
} else {
assert.strictEqual(test.meta[TEST_FINAL_STATUS], 'fail')
}
})
})

childProcess = exec(runTestsCommand, {
cwd,
env: {
...getCiVisAgentlessConfig(receiver.port),
TESTS_TO_RUN: JSON.stringify([
'./mocha-flaky/flaky-passes.js',
'ci-visibility/test/ci-visibility-test.js',
'./mocha-flaky/flaky-fails.js',
]),
},
})

await Promise.all([once(childProcess, 'exit'), eventsPromise])
})

it('sets final_status tag to skip for disabled tests', async () => {
receiver.setSettings({ test_management: { enabled: true } })
receiver.setTestManagementTests({
mocha: {
suites: {
'ci-visibility/test-management/test-disabled-1.js': {
tests: {
'disable tests can disable a test': {
properties: { disabled: true },
},
},
},
},
},
})

const eventsPromise = receiver
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
const events = payloads.flatMap(({ payload }) => payload.events)
const tests = events.filter(event => event.type === 'test').map(event => event.content)

const disabledTest = tests.find(test => test.meta[TEST_NAME] === 'disable tests can disable a test')
assert.ok(disabledTest)
assert.strictEqual(disabledTest.meta[TEST_STATUS], 'skip')
assert.strictEqual(disabledTest.meta[TEST_MANAGEMENT_IS_DISABLED], 'true')
assert.strictEqual(disabledTest.meta[TEST_FINAL_STATUS], 'skip')
})

childProcess = exec(runTestsCommand, {
cwd,
env: {
...getCiVisAgentlessConfig(receiver.port),
TESTS_TO_RUN: JSON.stringify(['./test-management/test-disabled-1.js']),
SHOULD_CHECK_RESULTS: '1',
},
})

await Promise.all([once(childProcess, 'exit'), eventsPromise])
})

it('sets final_status tag to skip for quarantined tests', async () => {
receiver.setSettings({ test_management: { enabled: true } })
receiver.setTestManagementTests({
mocha: {
suites: {
'ci-visibility/test-management/test-quarantine-1.js': {
tests: {
'quarantine tests can quarantine a test': {
properties: { quarantined: true },
},
},
},
},
},
})

const eventsPromise = receiver
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
const events = payloads.flatMap(({ payload }) => payload.events)
const tests = events.filter(event => event.type === 'test').map(event => event.content)

const quarantinedTest = tests.find(test => test.meta[TEST_NAME] === 'quarantine tests can quarantine a test')
assert.ok(quarantinedTest)

// Quarantined tests still run and report their actual status.
// However, the final status tag must be reported as 'skip'
assert.strictEqual(quarantinedTest.meta[TEST_STATUS], 'fail')
assert.strictEqual(quarantinedTest.meta[TEST_MANAGEMENT_IS_QUARANTINED], 'true')
assert.strictEqual(quarantinedTest.meta[TEST_FINAL_STATUS], 'skip')

const passingTest = tests.find(test => test.meta[TEST_NAME] === 'quarantine tests can pass normally')
assert.ok(passingTest)
assert.strictEqual(passingTest.meta[TEST_STATUS], 'pass')
assert.strictEqual(passingTest.meta[TEST_FINAL_STATUS], 'pass')
})

childProcess = exec(runTestsCommand, {
cwd,
env: {
...getCiVisAgentlessConfig(receiver.port),
TESTS_TO_RUN: JSON.stringify(['./test-management/test-quarantine-1.js']),
SHOULD_CHECK_RESULTS: '1',
},
})

await Promise.all([once(childProcess, 'exit'), eventsPromise])
})
})
})
Loading
Loading