Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[test optimization] [SDTEST-1529] Add quarantined tests logic #5236

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
47 changes: 44 additions & 3 deletions integration-tests/ci-visibility-intake.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,43 @@ const DEFAULT_SETTINGS = {
code_coverage: true,
tests_skipping: true,
itr_enabled: true,
require_git: false,
early_flake_detection: {
enabled: false,
slow_test_retries: {
'5s': 3
}
},
flaky_test_retries_enabled: false,
di_enabled: false,
known_tests_enabled: false,
test_management: {
enabled: false
}
}

const DEFAULT_SUITES_TO_SKIP = []
const DEFAULT_GIT_UPLOAD_STATUS = 200
const DEFAULT_KNOWN_TESTS_UPLOAD_STATUS = 200
const DEFAULT_KNOWN_TESTS_RESPONSE_STATUS = 200
const DEFAULT_INFO_RESPONSE = {
endpoints: ['/evp_proxy/v2', '/debugger/v1/input']
}
const DEFAULT_CORRELATION_ID = '1234'
const DEFAULT_KNOWN_TESTS = ['test-suite1.js.test-name1', 'test-suite2.js.test-name2']

const DEFAULT_QUARANTINED_TESTS = {}
const DEFAULT_QUARANTINED_TESTS_RESPONSE_STATUS = 200

let settings = DEFAULT_SETTINGS
let suitesToSkip = DEFAULT_SUITES_TO_SKIP
let gitUploadStatus = DEFAULT_GIT_UPLOAD_STATUS
let infoResponse = DEFAULT_INFO_RESPONSE
let correlationId = DEFAULT_CORRELATION_ID
let knownTests = DEFAULT_KNOWN_TESTS
let knownTestsStatusCode = DEFAULT_KNOWN_TESTS_UPLOAD_STATUS
let knownTestsStatusCode = DEFAULT_KNOWN_TESTS_RESPONSE_STATUS
let waitingTime = 0
let quarantineResponse = DEFAULT_QUARANTINED_TESTS
let quarantineResponseStatusCode = DEFAULT_QUARANTINED_TESTS_RESPONSE_STATUS

class FakeCiVisIntake extends FakeAgent {
setKnownTestsResponseCode (statusCode) {
Expand Down Expand Up @@ -71,6 +83,14 @@ class FakeCiVisIntake extends FakeAgent {
waitingTime = newWaitingTime
}

setQuarantinedTests (newQuarantinedTests) {
quarantineResponse = newQuarantinedTests
}

setQuarantinedTestsResponseCode (newStatusCode) {
quarantineResponseStatusCode = newStatusCode
}

async start () {
const app = express()
app.use(bodyParser.raw({ limit: Infinity, type: 'application/msgpack' }))
Expand Down Expand Up @@ -219,6 +239,25 @@ class FakeCiVisIntake extends FakeAgent {
})
})

app.post([
'/api/v2/test/libraries/test-management/tests',
'/evp_proxy/:version/api/v2/test/libraries/test-management/tests'
], (req, res) => {
res.setHeader('content-type', 'application/json')
const data = JSON.stringify({
data: {
attributes: {
modules: quarantineResponse
}
}
})
res.status(quarantineResponseStatusCode).send(data)
this.emit('message', {
headers: req.headers,
url: req.url
})
})

return new Promise((resolve, reject) => {
const timeoutObj = setTimeout(() => {
reject(new Error('Intake timed out starting up'))
Expand All @@ -237,8 +276,10 @@ class FakeCiVisIntake extends FakeAgent {
settings = DEFAULT_SETTINGS
suitesToSkip = DEFAULT_SUITES_TO_SKIP
gitUploadStatus = DEFAULT_GIT_UPLOAD_STATUS
knownTestsStatusCode = DEFAULT_KNOWN_TESTS_UPLOAD_STATUS
knownTestsStatusCode = DEFAULT_KNOWN_TESTS_RESPONSE_STATUS
infoResponse = DEFAULT_INFO_RESPONSE
quarantineResponseStatusCode = DEFAULT_QUARANTINED_TESTS_RESPONSE_STATUS
quarantineResponse = DEFAULT_QUARANTINED_TESTS
this.removeAllListeners()
if (this.waitingTimeoutId) {
clearTimeout(this.waitingTimeoutId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Feature: Quarantine
Scenario: Say quarantine
When the greeter says quarantine
Then I should have heard "quarantine"
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const assert = require('assert')
const { When, Then } = require('@cucumber/cucumber')

Then('I should have heard {string}', function (expectedResponse) {
assert.equal(this.whatIHeard, 'fail')
})

When('the greeter says quarantine', function () {
this.whatIHeard = 'quarantine'
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { test, expect } = require('@playwright/test')

test.beforeEach(async ({ page }) => {
await page.goto(process.env.PW_BASE_URL)
})

test.describe('quarantine', () => {
test('should quarantine failed test', async ({ page }) => {
await expect(page.locator('.hello-world')).toHaveText([
'Hello Warld'
])
})
})
11 changes: 11 additions & 0 deletions integration-tests/ci-visibility/quarantine/test-quarantine-1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { expect } = require('chai')

describe('quarantine tests', () => {
it('can quarantine a test', () => {
expect(1 + 2).to.equal(4)
})

it('can pass normally', () => {
expect(1 + 2).to.equal(3)
})
})
11 changes: 11 additions & 0 deletions integration-tests/ci-visibility/quarantine/test-quarantine-2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { expect } = require('chai')

describe('quarantine tests 2', () => {
it('can quarantine a test', () => {
expect(1 + 2).to.equal(3)
})

it('can pass normally', () => {
expect(1 + 2).to.equal(3)
})
})
6 changes: 5 additions & 1 deletion integration-tests/ci-visibility/run-jest.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ if (process.env.COLLECT_COVERAGE_FROM) {
jest.runCLI(
options,
options.projects
).then(() => {
).then((results) => {
if (process.send) {
process.send('finished')
}
if (process.env.SHOULD_CHECK_RESULTS) {
const exitCode = results.results.success ? 0 : 1
process.exit(exitCode)
}
})
7 changes: 5 additions & 2 deletions integration-tests/ci-visibility/run-mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ if (process.env.TESTS_TO_RUN) {
mocha.addFile(require.resolve('./test/ci-visibility-test.js'))
mocha.addFile(require.resolve('./test/ci-visibility-test-2.js'))
}
mocha.run(() => {
mocha.run((failures) => {
if (process.send) {
process.send('finished')
}
}).on('end', () => {
if (process.env.SHOULD_CHECK_RESULTS && failures > 0) {
process.exit(1)
}
}).on('end', (res) => {
// eslint-disable-next-line
console.log('end event: can add event listeners to mocha')
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, test, expect } from 'vitest'

describe('quarantine tests', () => {
test('can quarantine a test', () => {
expect(1 + 2).to.equal(4)
})

test('can pass normally', () => {
expect(1 + 2).to.equal(3)
})
})
93 changes: 92 additions & 1 deletion integration-tests/cucumber/cucumber.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ const {
DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX,
DI_DEBUG_ERROR_LINE_SUFFIX,
TEST_RETRY_REASON,
DD_TEST_IS_USER_PROVIDED_SERVICE
DD_TEST_IS_USER_PROVIDED_SERVICE,
TEST_MANAGEMENT_ENABLED,
TEST_MANAGEMENT_IS_QUARANTINED
} = require('../../packages/dd-trace/src/plugins/util/test')
const { DD_HOST_CPU_COUNT } = require('../../packages/dd-trace/src/plugins/util/env')

Expand Down Expand Up @@ -2029,5 +2031,94 @@ versions.forEach(version => {
}).catch(done)
})
})

context('quarantine', () => {
beforeEach(() => {
receiver.setQuarantinedTests({
cucumber: {
suites: {
'ci-visibility/features-quarantine/quarantine.feature': {
tests: {
'Say quarantine': {
properties: {
quarantined: true
}
}
}
}
}
}
})
})

const getTestAssertions = (isQuarantining) =>
receiver
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
const events = payloads.flatMap(({ payload }) => payload.events)
const failedTest = events.find(event => event.type === 'test').content
const testSession = events.find(event => event.type === 'test_session_end').content

if (isQuarantining) {
assert.propertyVal(testSession.meta, TEST_MANAGEMENT_ENABLED, 'true')
} else {
assert.notProperty(testSession.meta, TEST_MANAGEMENT_ENABLED)
}

assert.equal(failedTest.resource, 'ci-visibility/features-quarantine/quarantine.feature.Say quarantine')

assert.equal(failedTest.meta[TEST_STATUS], 'fail')
if (isQuarantining) {
assert.propertyVal(failedTest.meta, TEST_MANAGEMENT_IS_QUARANTINED, 'true')
} else {
assert.notProperty(failedTest.meta, TEST_MANAGEMENT_IS_QUARANTINED)
}
})

const runTest = (done, isQuarantining, extraEnvVars) => {
const testAssertionsPromise = getTestAssertions(isQuarantining)

childProcess = exec(
'./node_modules/.bin/cucumber-js ci-visibility/features-quarantine/*.feature',
{
cwd,
env: {
...getCiVisAgentlessConfig(receiver.port),
...extraEnvVars
},
stdio: 'inherit'
}
)

childProcess.on('exit', exitCode => {
testAssertionsPromise.then(() => {
if (isQuarantining) {
// even though a test fails, the exit code is 1 because the test is quarantined
assert.equal(exitCode, 0)
} else {
assert.equal(exitCode, 1)
}
done()
}).catch(done)
})
}

it('can quarantine tests', (done) => {
receiver.setSettings({ test_management: { enabled: true } })

runTest(done, true)
})

it('fails if quarantine is not enabled', (done) => {
receiver.setSettings({ test_management: { enabled: false } })

runTest(done, false)
})

it('does not enable quarantine tests if DD_TEST_MANAGEMENT_ENABLED is set to false', (done) => {
receiver.setSettings({ test_management: { enabled: true } })

runTest(done, false, { DD_TEST_MANAGEMENT_ENABLED: '0' })
})
})
})
})
5 changes: 4 additions & 1 deletion integration-tests/cypress-esm-config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import cypress from 'cypress'

async function runCypress () {
await cypress.run({
const results = await cypress.run({
config: {
defaultCommandTimeout: 1000,
e2e: {
Expand Down Expand Up @@ -39,6 +39,9 @@ async function runCypress () {
screenshotOnRunFailure: false
}
})
if (results.totalFailed !== 0) {
process.exit(1)
}
}

runCypress()
Loading
Loading