Skip to content
Closed
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
11 changes: 10 additions & 1 deletion .github/workflows/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ jobs:
DD_SERVICE: dd-trace-js-integration-tests
DD_CIVISIBILITY_AGENTLESS_ENABLED: 1
DD_API_KEY: ${{ secrets.DD_API_KEY }}
OPTIONS_OVERRIDE: 1
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/node
Expand All @@ -92,6 +93,7 @@ jobs:
DD_SERVICE: dd-trace-js-integration-tests
DD_CIVISIBILITY_AGENTLESS_ENABLED: 1
DD_API_KEY: ${{ secrets.DD_API_KEY }}
OPTIONS_OVERRIDE: 1
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/node
Expand Down Expand Up @@ -148,17 +150,24 @@ jobs:
CYPRESS_VERSION: ${{ matrix.cypress-version }}
NODE_OPTIONS: '-r ./ci/init'
CYPRESS_MODULE_TYPE: ${{ matrix.module-type }}
OPTIONS_OVERRIDE: 1


integration-vitest:
runs-on: ubuntu-latest
strategy:
matrix:
version: [oldest, latest]
env:
DD_SERVICE: dd-trace-js-integration-tests
DD_CIVISIBILITY_AGENTLESS_ENABLED: 1
DD_API_KEY: ${{ secrets.DD_API_KEY }}
OPTIONS_OVERRIDE: 1
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/node/active-lts
- uses: ./.github/actions/node
with:
version: ${{ matrix.version }}
- uses: ./.github/actions/install
- run: yarn test:integration:vitest
env:
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ Most of the documentation for `dd-trace` is available on these webpages:

## Version Release Lines and Maintenance

> **Node.js v24 Notice**: We're currently adding compatibility for Node.js v24. To use the tracer with your application either continue to use Node.js v22 (LTS), or do both of the following as a workaround:
> * Install v5.52.0 (or newer) of the tracer
> * Set `--no-async-context-frame` either using a CLI argument or via `NODE_OPTIONS`
> Once support for Node.js v24 is complete this flag will no longer be needed.

| Release Line | Latest Version | Node.js | [SSI](https://docs.datadoghq.com/tracing/trace_collection/automatic_instrumentation/single-step-apm/?tab=linuxhostorvm) | [K8s Injection](https://docs.datadoghq.com/tracing/trace_collection/library_injection_local/?tab=kubernetes) |Status |Initial Release | End of Life |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| [`v1`](https://github.com/DataDog/dd-trace-js/tree/v1.x) | ![npm v1](https://img.shields.io/npm/v/dd-trace/legacy-v1?color=white&label=%20&style=flat-square) | `>= v12` | NO | NO | **EOL** | 2021-07-13 | 2022-02-25 |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { test, expect } = require('@playwright/test')
// eslint-disable-next-line no-unused-vars
const dummy = require('dummy') // This should not exist, so should throw an error

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

test.describe('exit code', () => {
test('should exit with code 1', async ({ page }) => {
await expect(page.locator('.hello-world')).toHaveText([
'Hello World'
])
})
})
114 changes: 102 additions & 12 deletions integration-tests/debugger/basic.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,20 +174,25 @@ describe('Dynamic Instrumentation', function () {
}
})

const unsupporedOrInvalidProbes = [[
it(
'should send expected error diagnostics messages if probe doesn\'t conform to expected schema',
'bad config!!!',
{ status: 'ERROR' }
], [
unsupporedOrInvalidProbesTest('bad config!!!', { status: 'ERROR' })
)

it(
'should send expected error diagnostics messages if probe type isn\'t supported',
t.generateProbeConfig({ type: 'INVALID_PROBE' })
], [
unsupporedOrInvalidProbesTest(t.generateProbeConfig({ type: 'INVALID_PROBE' }))
)

it(
'should send expected error diagnostics messages if it isn\'t a line-probe',
t.generateProbeConfig({ where: { foo: 'bar' } }) // TODO: Use valid schema for method probe instead
]]
unsupporedOrInvalidProbesTest(
t.generateProbeConfig({ where: { typeName: 'index.js', methodName: 'handlerA' } })
)
)

for (const [title, config, customErrorDiagnosticsObj] of unsupporedOrInvalidProbes) {
it(title, function (done) {
function unsupporedOrInvalidProbesTest (config, customErrorDiagnosticsObj) {
return function (done) {
let receivedAckUpdate = false

t.agent.on('remote-config-ack-update', (id, version, state, error) => {
Expand Down Expand Up @@ -238,7 +243,7 @@ describe('Dynamic Instrumentation', function () {
function endIfDone () {
if (receivedAckUpdate && expectedPayloads.length === 0) done()
}
})
}
}

describe('multiple probes at the same location', function () {
Expand Down Expand Up @@ -305,6 +310,7 @@ describe('Dynamic Instrumentation', function () {
}
} else if (diagnostics.status === 'EMITTING') {
const expected = expectedPayloads.get(diagnostics.probeId)
assert.ok(expected, `expected payload not found for probe ${diagnostics.probeId}`)
expectedPayloads.delete(diagnostics.probeId)
assertObjectContains(event, expected)
}
Expand Down Expand Up @@ -343,12 +349,95 @@ describe('Dynamic Instrumentation', function () {
t.agent.addRemoteConfig(rcConfig2)
})

it('should support only triggering the probes whos conditions are met', function (done) {
it('should only trigger the probes whos conditions are met (all have conditions)', function (done) {
let installed = 0
const rcConfig1 = t.generateRemoteConfig({
when: { json: { eq: [{ getmember: [{ getmember: [{ ref: 'request' }, 'params'] }, 'name'] }, 'invalid'] } }
})
const rcConfig2 = t.generateRemoteConfig({
when: { json: { eq: [{ getmember: [{ getmember: [{ ref: 'request' }, 'params'] }, 'name'] }, 'bar'] } }
})
const expectedPayloads = new Map([
[rcConfig2.config.id, {
ddsource: 'dd_debugger',
service: 'node',
debugger: { diagnostics: { probeId: rcConfig2.config.id, probeVersion: 0, status: 'EMITTING' } }
}]
])

t.agent.on('debugger-diagnostics', ({ payload }) => {
payload.forEach((event) => {
const { diagnostics } = event.debugger
if (diagnostics.status === 'INSTALLED') {
if (++installed === 2) {
t.axios.get(t.breakpoint.url).catch(done)
}
} else if (diagnostics.status === 'EMITTING') {
const expected = expectedPayloads.get(diagnostics.probeId)
assert.ok(expected, `expected payload not found for probe ${diagnostics.probeId}`)
expectedPayloads.delete(diagnostics.probeId)
assertObjectContains(event, expected)
}
})
endIfDone()
})

t.agent.addRemoteConfig(rcConfig1)
t.agent.addRemoteConfig(rcConfig2)

function endIfDone () {
if (expectedPayloads.size === 0) done()
}
})

it('trigger on met condition, even if other condition throws (all have conditions)', function (done) {
let installed = 0
// this condition will throw because `foo` is not defined
const rcConfig1 = t.generateRemoteConfig({ when: { json: { eq: [{ ref: 'foo' }, 'bar'] } } })
const rcConfig2 = t.generateRemoteConfig({
when: { json: { eq: [{ getmember: [{ getmember: [{ ref: 'request' }, 'params'] }, 'name'] }, 'bar'] } }
})
const expectedPayloads = new Map([
[rcConfig2.config.id, {
ddsource: 'dd_debugger',
service: 'node',
debugger: { diagnostics: { probeId: rcConfig2.config.id, probeVersion: 0, status: 'EMITTING' } }
}]
])

t.agent.on('debugger-diagnostics', ({ payload }) => {
payload.forEach((event) => {
const { diagnostics } = event.debugger
if (diagnostics.status === 'INSTALLED') {
if (++installed === 2) {
t.axios.get(t.breakpoint.url).catch(done)
}
} else if (diagnostics.status === 'EMITTING') {
const expected = expectedPayloads.get(diagnostics.probeId)
assert.ok(expected, `expected payload not found for probe ${diagnostics.probeId}`)
expectedPayloads.delete(diagnostics.probeId)
assertObjectContains(event, expected)
}
})
endIfDone()
})

t.agent.addRemoteConfig(rcConfig1)
t.agent.addRemoteConfig(rcConfig2)

function endIfDone () {
if (expectedPayloads.size === 0) done()
}
})

it('should only trigger the probes whos conditions are met (not all have conditions)', function (done) {
let installed = 0
const rcConfig1 = t.generateRemoteConfig({
when: { json: { eq: [{ getmember: [{ getmember: [{ ref: 'request' }, 'params'] }, 'name'] }, 'invalid'] } }
})
const rcConfig2 = t.generateRemoteConfig({
when: { json: { eq: [{ getmember: [{ getmember: [{ ref: 'request' }, 'params'] }, 'name'] }, 'bar'] } }
})
const rcConfig3 = t.generateRemoteConfig()
const expectedPayloads = new Map([
[rcConfig2.config.id, {
Expand All @@ -372,6 +461,7 @@ describe('Dynamic Instrumentation', function () {
}
} else if (diagnostics.status === 'EMITTING') {
const expected = expectedPayloads.get(diagnostics.probeId)
assert.ok(expected, `expected payload not found for probe ${diagnostics.probeId}`)
expectedPayloads.delete(diagnostics.probeId)
assertObjectContains(event, expected)
}
Expand Down
17 changes: 7 additions & 10 deletions integration-tests/debugger/snapshot-pruning.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,13 @@ describe('Dynamic Instrumentation', function () {
it('should prune snapshot if payload is too large', function (done) {
t.agent.on('debugger-input', ({ payload: [payload] }) => {
assert.isBelow(Buffer.byteLength(JSON.stringify(payload)), 1024 * 1024) // 1MB
assert.deepEqual(payload.debugger.snapshot.captures, {
lines: {
[t.breakpoint.line]: {
locals: {
notCapturedReason: 'Snapshot was too large',
size: 6
}
}
}
})
assert.notProperty(payload.debugger.snapshot, 'captures')
assert.strictEqual(
payload.debugger.snapshot.captureError,
'Snapshot was too large (max allowed size is 1 MiB). ' +
'Consider reducing the capture depth or turn off "Capture Variables" completely, ' +
'and instead include the variables of interest directly in the message template.'
)
done()
})

Expand Down
19 changes: 13 additions & 6 deletions integration-tests/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,23 @@ function spawnProc (filename, options = {}, stdioHandler, stderrHandler) {

async function createSandbox (dependencies = [], isGitRepo = false,
integrationTestsPaths = ['./integration-tests/*'], followUpCommand) {
/* To execute integration tests without a sandbox uncomment the next line
* and do `yarn link && yarn link dd-trace` */
// return { folder: path.join(process.cwd(), 'integration-tests'), remove: async () => {} }
// We might use NODE_OPTIONS to init the tracer. We don't want this to affect this operations
const { NODE_OPTIONS, ...restOfEnv } = process.env
const noSandbox = String(process.env.DD_NO_INTEGRATION_TESTS_SANDBOX)
if (noSandbox === '1' || noSandbox.toLowerCase() === 'true') {
// Execute integration tests without a sandbox. This is useful when you have other components
// yarn-linked into dd-trace and want to run the integration tests against them.

// Link dd-trace to itself, then...
await exec('yarn link')
await exec('yarn link dd-trace')
// ... run the tests in the current directory.
return { folder: path.join(process.cwd(), 'integration-tests'), remove: async () => {} }
}
const folder = path.join(os.tmpdir(), id().toString())
const out = path.join(folder, 'dd-trace.tgz')
const allDependencies = [`file:${out}`].concat(dependencies)

// We might use NODE_OPTIONS to init the tracer. We don't want this to affect this operations
const { NODE_OPTIONS, ...restOfEnv } = process.env

fs.mkdirSync(folder)
const addCommand = `yarn add ${allDependencies.join(' ')} --ignore-engines`
const addOptions = { cwd: folder, env: restOfEnv }
Expand Down
31 changes: 31 additions & 0 deletions integration-tests/playwright/playwright.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,37 @@ versions.forEach((version) => {
runTest(done, { isRedirecting: true })
})
})

context('run session status', () => {
it('session status is not changed if it fails before running any test', (done) => {
const receiverPromise = receiver
.gatherPayloadsMaxTimeout(({ url }) => url === '/api/v2/citestcycle', (payloads) => {
const events = payloads.flatMap(({ payload }) => payload.events)
const testSession = events.find(event => event.type === 'test_session_end').content
assert.equal(testSession.meta[TEST_STATUS], 'fail')
})

receiver.setSettings({ test_management: { enabled: true } })

childProcess = exec(
'./node_modules/.bin/playwright test -c playwright.config.js exit-code-test.js',
{
cwd,
env: {
...getCiVisAgentlessConfig(receiver.port),
PW_BASE_URL: `http://localhost:${webAppPort}`,
TEST_DIR: './ci-visibility/playwright-tests-exit-code'
},
stdio: 'pipe'
}
)

childProcess.on('exit', (exitCode) => {
assert.equal(exitCode, 1)
receiverPromise.then(() => done()).catch(done)
})
})
})
}
})
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dd-trace",
"version": "5.52.0",
"version": "5.53.0",
"description": "Datadog APM tracing client for JavaScript",
"main": "index.js",
"typings": "index.d.ts",
Expand Down
Loading