diff --git a/packages/browser/test/unit/profiling/integration.test.ts b/packages/browser/test/unit/profiling/integration.test.ts index b5c4ad7c82a5..c706158bb82e 100644 --- a/packages/browser/test/unit/profiling/integration.test.ts +++ b/packages/browser/test/unit/profiling/integration.test.ts @@ -36,7 +36,7 @@ describe('BrowserProfilingIntegration', () => { tracesSampleRate: 1, profilesSampleRate: 1, environment: 'test-environment', - dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + dsn: 'https://public@dsn.ingest.sentry.io/1337', transport: _opts => { return { flush, diff --git a/packages/profiling-node/README.md b/packages/profiling-node/README.md index 4357e23bb194..1991717ccc39 100644 --- a/packages/profiling-node/README.md +++ b/packages/profiling-node/README.md @@ -30,7 +30,7 @@ import * as Sentry from '@sentry/node'; import { nodeProfilingIntegration } from '@sentry/profiling-node'; Sentry.init({ - dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + dsn: 'https://public@dsn.ingest.sentry.io/1337', debug: true, tracesSampleRate: 1, profilesSampleRate: 1, // Set profiling sampling rate. diff --git a/packages/profiling-node/jest.config.js b/packages/profiling-node/jest.config.js deleted file mode 100644 index 89bda645921b..000000000000 --- a/packages/profiling-node/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -const baseConfig = require('../../jest/jest.config.js'); - -module.exports = { - ...baseConfig, - testEnvironment: 'node', -}; diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json index baa199bb3fd0..207ce5e5579a 100644 --- a/packages/profiling-node/package.json +++ b/packages/profiling-node/package.json @@ -70,9 +70,9 @@ "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", "build:watch": "run-p build:transpile:watch build:types:watch", "build:tarball": "npm pack", - "test:watch": "cross-env SENTRY_PROFILER_BINARY_DIR=build jest --watch", + "test:watch": "cross-env SENTRY_PROFILER_BINARY_DIR=build vitest --watch", "test:bundle": "node test-binaries.esbuild.js", - "test": "cross-env SENTRY_PROFILER_BINARY_DIR=lib jest --config jest.config.js" + "test": "cross-env SENTRY_PROFILER_BINARY_DIR=lib vitest run" }, "dependencies": { "@sentry/core": "8.19.0", diff --git a/packages/profiling-node/src/cpu_profiler.ts b/packages/profiling-node/src/cpu_profiler.ts index 9ab470e2ca70..883bca1fc313 100644 --- a/packages/profiling-node/src/cpu_profiler.ts +++ b/packages/profiling-node/src/cpu_profiler.ts @@ -1,3 +1,4 @@ +import { createRequire } from 'node:module'; import { arch as _arch, platform as _platform } from 'node:os'; import { join, resolve } from 'node:path'; import { env, versions } from 'node:process'; @@ -28,6 +29,7 @@ const built_from_source_path = resolve(__dirname, '..', `./sentry_cpu_profiler-$ */ // eslint-disable-next-line complexity export function importCppBindingsModule(): PrivateV8CpuProfilerBindings { + const require = createRequire(import.meta.url); // If a binary path is specified, use that. if (env['SENTRY_PROFILER_BINARY_PATH']) { const envPath = env['SENTRY_PROFILER_BINARY_PATH']; diff --git a/packages/profiling-node/src/integration.ts b/packages/profiling-node/src/integration.ts index b05a919fc949..9f0f963dd5c5 100644 --- a/packages/profiling-node/src/integration.ts +++ b/packages/profiling-node/src/integration.ts @@ -399,7 +399,7 @@ export const _nodeProfilingIntegration = ((): ProfilingIntegration => { const options = client.getOptions(); const mode = - (options.profilesSampleRate === undefined || options.profilesSampleRate === 0) && !options.profilesSampler + (options.profilesSampleRate == undefined || options.profilesSampleRate === 0) && !options.profilesSampler ? 'continuous' : 'span'; switch (mode) { diff --git a/packages/profiling-node/test/bindings.test.ts b/packages/profiling-node/test/bindings.test.ts index c524a277bfa9..40efe0445543 100644 --- a/packages/profiling-node/test/bindings.test.ts +++ b/packages/profiling-node/test/bindings.test.ts @@ -1,4 +1,6 @@ -import { platform } from 'os'; +import { describe, expect, it } from 'vitest'; + +import { platform } from 'node:os'; // Contains unit tests for some of the C++ bindings. These functions // are exported on the private bindings object, so we can test them and // they should not be used outside of this file. @@ -22,7 +24,10 @@ const cases = [ describe('GetFrameModule', () => { it.each( platform() === 'win32' - ? cases.map(([abs_path, expected]) => [abs_path ? `C:${abs_path.replace(/\//g, '\\')}` : '', expected]) + ? (cases.map(([abs_path, expected]) => [ + abs_path ? `C:${abs_path.replace(/\//g, '\\')}` : '', + expected, + ]) as string[][]) : cases, )('%s => %s', (abs_path: string, expected: string) => { expect(PrivateCpuProfilerBindings.getFrameModule(abs_path)).toBe(expected); diff --git a/packages/profiling-node/test/cpu_profiler.test.ts b/packages/profiling-node/test/cpu_profiler.test.ts index c1086003c1af..d2d40437d053 100644 --- a/packages/profiling-node/test/cpu_profiler.test.ts +++ b/packages/profiling-node/test/cpu_profiler.test.ts @@ -1,11 +1,14 @@ +import { describe, expect, it } from 'vitest'; + import type { ContinuousThreadCpuProfile, ThreadCpuProfile } from '@sentry/types'; import { CpuProfilerBindings, PrivateCpuProfilerBindings } from '../src/cpu_profiler'; import type { RawThreadCpuProfile } from '../src/types'; +import { constructItWithTimeout } from './test-utils'; // Required because we test a hypothetical long profile // and we cannot use advance timers as the c++ relies on -// actual event loop ticks that we cannot advance from jest. -jest.setTimeout(60_000); +// actual event loop ticks that we cannot advance from vitest. +const itWithTimeout = constructItWithTimeout(60_000); function fail(message: string): never { throw new Error(message); @@ -68,7 +71,7 @@ const assertValidMeasurements = (measurement: RawThreadCpuProfile['measurements' }; describe('Private bindings', () => { - it('does not crash if collect resources is false', async () => { + itWithTimeout('does not crash if collect resources is false', async () => { PrivateCpuProfilerBindings.startProfiling('profiled-program'); await wait(100); expect(() => { @@ -77,7 +80,7 @@ describe('Private bindings', () => { }).not.toThrow(); }); - it('throws if invalid format is supplied', async () => { + itWithTimeout('throws if invalid format is supplied', async () => { PrivateCpuProfilerBindings.startProfiling('profiled-program'); await wait(100); expect(() => { @@ -86,7 +89,7 @@ describe('Private bindings', () => { }).toThrow('StopProfiling expects a valid format type as second argument.'); }); - it('collects resources', async () => { + itWithTimeout('collects resources', async () => { PrivateCpuProfilerBindings.startProfiling('profiled-program'); await wait(100); @@ -103,7 +106,7 @@ describe('Private bindings', () => { } }); - it('does not collect resources', async () => { + itWithTimeout('does not collect resources', async () => { PrivateCpuProfilerBindings.startProfiling('profiled-program'); await wait(100); @@ -115,12 +118,12 @@ describe('Private bindings', () => { }); describe('Profiler bindings', () => { - it('exports profiler binding methods', () => { + itWithTimeout('exports profiler binding methods', () => { expect(typeof CpuProfilerBindings['startProfiling']).toBe('function'); expect(typeof CpuProfilerBindings['stopProfiling']).toBe('function'); }); - it('profiles a program', async () => { + itWithTimeout('profiles a program', async () => { const profile = await profiled('profiled-program', async () => { await wait(100); }); @@ -130,7 +133,7 @@ describe('Profiler bindings', () => { assertValidSamplesAndStacks(profile.stacks, profile.samples); }); - it('adds thread_id info', async () => { + itWithTimeout('adds thread_id info', async () => { const profile = await profiled('profiled-program', async () => { await wait(100); }); @@ -146,7 +149,7 @@ describe('Profiler bindings', () => { } }); - it('caps stack depth at 128', async () => { + itWithTimeout('caps stack depth at 128', async () => { const recurseToDepth = async (depth: number): Promise => { if (depth === 0) { // Wait a bit to make sure stack gets sampled here @@ -168,7 +171,7 @@ describe('Profiler bindings', () => { } }); - it('does not record two profiles when titles match', () => { + itWithTimeout('does not record two profiles when titles match', () => { CpuProfilerBindings.startProfiling('same-title'); CpuProfilerBindings.startProfiling('same-title'); @@ -179,7 +182,7 @@ describe('Profiler bindings', () => { expect(second).toBe(null); }); - it('multiple calls with same title', () => { + itWithTimeout('multiple calls with same title', () => { CpuProfilerBindings.startProfiling('same-title'); expect(() => { CpuProfilerBindings.stopProfiling('same-title', 0); @@ -187,11 +190,11 @@ describe('Profiler bindings', () => { }).not.toThrow(); }); - it('does not crash if stopTransaction is called before startTransaction', () => { + itWithTimeout('does not crash if stopTransaction is called before startTransaction', () => { expect(CpuProfilerBindings.stopProfiling('does not exist', 0)).toBe(null); }); - it('does crash if name is invalid', () => { + itWithTimeout('does crash if name is invalid', () => { expect(() => CpuProfilerBindings.stopProfiling('', 0)).toThrow(); // @ts-expect-error test invalid input expect(() => CpuProfilerBindings.stopProfiling(undefined)).toThrow(); @@ -201,12 +204,12 @@ describe('Profiler bindings', () => { expect(() => CpuProfilerBindings.stopProfiling({})).toThrow(); }); - it('does not throw if stopTransaction is called before startTransaction', () => { + itWithTimeout('does not throw if stopTransaction is called before startTransaction', () => { expect(CpuProfilerBindings.stopProfiling('does not exist', 0)).toBe(null); expect(() => CpuProfilerBindings.stopProfiling('does not exist', 0)).not.toThrow(); }); - it('compiles with eager logging by default', async () => { + itWithTimeout('compiles with eager logging by default', async () => { const profile = await profiled('profiled-program', async () => { await wait(100); }); @@ -215,7 +218,7 @@ describe('Profiler bindings', () => { expect(profile.profiler_logging_mode).toBe('eager'); }); - it('chunk format type', async () => { + itWithTimeout('chunk format type', async () => { const profile = await profiled( 'non nullable stack', async () => { @@ -240,7 +243,7 @@ describe('Profiler bindings', () => { } }); - it('stacks are not null', async () => { + itWithTimeout('stacks are not null', async () => { const profile = await profiled('non nullable stack', async () => { await wait(1000); fibonacci(36); @@ -251,7 +254,7 @@ describe('Profiler bindings', () => { assertValidSamplesAndStacks(profile.stacks, profile.samples); }); - it('samples at ~99hz', async () => { + itWithTimeout('samples at ~99hz', async () => { CpuProfilerBindings.startProfiling('profile'); await wait(100); const profile = CpuProfilerBindings.stopProfiling('profile', 0); @@ -275,7 +278,7 @@ describe('Profiler bindings', () => { } }); - it('collects memory footprint', async () => { + itWithTimeout('collects memory footprint', async () => { CpuProfilerBindings.startProfiling('profile'); await wait(1000); const profile = CpuProfilerBindings.stopProfiling('profile', 0); @@ -291,7 +294,7 @@ describe('Profiler bindings', () => { assertValidMeasurements(profile.measurements['memory_footprint']); }); - it('collects cpu usage', async () => { + itWithTimeout('collects cpu usage', async () => { CpuProfilerBindings.startProfiling('profile'); await wait(1000); const profile = CpuProfilerBindings.stopProfiling('profile', 0); @@ -307,7 +310,7 @@ describe('Profiler bindings', () => { assertValidMeasurements(profile.measurements['cpu_usage']); }); - it('does not overflow measurement buffer if profile runs longer than 30s', async () => { + itWithTimeout('does not overflow measurement buffer if profile runs longer than 30s', async () => { CpuProfilerBindings.startProfiling('profile'); await wait(35000); const profile = CpuProfilerBindings.stopProfiling('profile', 0); @@ -316,7 +319,6 @@ describe('Profiler bindings', () => { expect(profile?.measurements?.['memory_footprint']?.values.length).toBeLessThanOrEqual(300); }); - // eslint-disable-next-line jest/no-disabled-tests it.skip('includes deopt reason', async () => { // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#52-the-object-being-iterated-is-not-a-simple-enumerable function iterateOverLargeHashTable() { diff --git a/packages/profiling-node/test/integration.test.ts b/packages/profiling-node/test/integration.test.ts index 92d1018e18d4..71a97fe3ee46 100644 --- a/packages/profiling-node/test/integration.test.ts +++ b/packages/profiling-node/test/integration.test.ts @@ -1,4 +1,6 @@ -import { EventEmitter } from 'events'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { EventEmitter } from 'node:events'; import type { Transport } from '@sentry/types'; @@ -7,7 +9,7 @@ import { _nodeProfilingIntegration } from '../src/integration'; describe('ProfilingIntegration', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('has a name', () => { expect(_nodeProfilingIntegration().name).toBe('ProfilingIntegration'); @@ -15,8 +17,8 @@ describe('ProfilingIntegration', () => { it('does not call transporter if null profile is received', () => { const transport: Transport = { - send: jest.fn().mockImplementation(() => Promise.resolve()), - flush: jest.fn().mockImplementation(() => Promise.resolve()), + send: vi.fn().mockImplementation(() => Promise.resolve()), + flush: vi.fn().mockImplementation(() => Promise.resolve()), }; const integration = _nodeProfilingIntegration(); const emitter = new EventEmitter(); @@ -43,14 +45,14 @@ describe('ProfilingIntegration', () => { it('binds to spanStart, spanEnd and beforeEnvelope', () => { const transport: Transport = { - send: jest.fn().mockImplementation(() => Promise.resolve()), - flush: jest.fn().mockImplementation(() => Promise.resolve()), + send: vi.fn().mockImplementation(() => Promise.resolve()), + flush: vi.fn().mockImplementation(() => Promise.resolve()), }; const integration = _nodeProfilingIntegration(); const client = { - on: jest.fn(), - emit: jest.fn(), + on: vi.fn(), + emit: vi.fn(), getOptions: () => { return { _metadata: {}, @@ -63,7 +65,7 @@ describe('ProfilingIntegration', () => { getTransport: () => transport, } as unknown as NodeClient; - const spy = jest.spyOn(client, 'on'); + const spy = vi.spyOn(client, 'on'); integration?.setup?.(client); diff --git a/packages/profiling-node/test/spanProfileUtils.test.ts b/packages/profiling-node/test/spanProfileUtils.test.ts index 766a0059d02e..25d937999905 100644 --- a/packages/profiling-node/test/spanProfileUtils.test.ts +++ b/packages/profiling-node/test/spanProfileUtils.test.ts @@ -1,3 +1,5 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + import * as Sentry from '@sentry/node'; import { getMainCarrier } from '@sentry/core'; @@ -13,13 +15,12 @@ function makeClientWithHooks(): [Sentry.NodeClient, Transport] { stackParser: Sentry.defaultStackParser, tracesSampleRate: 1, profilesSampleRate: 1, - debug: true, environment: 'test-environment', - dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [integration], transport: _opts => Sentry.makeNodeTransport({ - url: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + url: 'https://public@dsn.ingest.sentry.io/1337', recordDroppedEvent: () => { return undefined; }, @@ -35,13 +36,12 @@ function makeContinuousProfilingClient(): [Sentry.NodeClient, Transport] { stackParser: Sentry.defaultStackParser, tracesSampleRate: 1, profilesSampleRate: undefined, - debug: true, environment: 'test-environment', - dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [integration], transport: _opts => Sentry.makeNodeTransport({ - url: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + url: 'https://public@dsn.ingest.sentry.io/1337', recordDroppedEvent: () => { return undefined; }, @@ -57,10 +57,9 @@ function makeClientOptions( return { stackParser: Sentry.defaultStackParser, integrations: [_nodeProfilingIntegration()], - debug: true, transport: _opts => Sentry.makeNodeTransport({ - url: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + url: 'https://public@dsn.ingest.sentry.io/1337', recordDroppedEvent: () => { return undefined; }, @@ -73,14 +72,14 @@ const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); describe('automated span instrumentation', () => { beforeEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); // We will mock the carrier as if it has been initialized by the SDK, else everything is short circuited getMainCarrier().__SENTRY__ = {}; GLOBAL_OBJ._sentryDebugIds = undefined as any; }); afterEach(() => { - jest.clearAllMocks(); - jest.restoreAllMocks(); + vi.clearAllMocks(); + vi.restoreAllMocks(); delete getMainCarrier().__SENTRY__; }); @@ -89,7 +88,7 @@ describe('automated span instrumentation', () => { Sentry.setCurrentClient(client); client.init(); - const transportSpy = jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); + const transportSpy = vi.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); const transaction = Sentry.startInactiveSpan({ forceTransaction: true, name: 'profile_hub' }); await wait(500); @@ -100,14 +99,14 @@ describe('automated span instrumentation', () => { }); it('logger warns user if there are insufficient samples and discards the profile', async () => { - const logSpy = jest.spyOn(logger, 'log'); + const logSpy = vi.spyOn(logger, 'log'); const [client, transport] = makeClientWithHooks(); Sentry.setCurrentClient(client); client.init(); // @ts-expect-error we just mock the return type and ignore the signature - jest.spyOn(CpuProfilerBindings, 'stopProfiling').mockImplementation(() => { + vi.spyOn(CpuProfilerBindings, 'stopProfiling').mockImplementation(() => { return { samples: [ { @@ -124,7 +123,7 @@ describe('automated span instrumentation', () => { }; }); - jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); + vi.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); const transaction = Sentry.startInactiveSpan({ forceTransaction: true, name: 'profile_hub' }); transaction.end(); @@ -139,14 +138,14 @@ describe('automated span instrumentation', () => { }); it('logger warns user if traceId is invalid', async () => { - const logSpy = jest.spyOn(logger, 'log'); + const logSpy = vi.spyOn(logger, 'log'); const [client, transport] = makeClientWithHooks(); Sentry.setCurrentClient(client); client.init(); // @ts-expect-error we just mock the return type and ignore the signature - jest.spyOn(CpuProfilerBindings, 'stopProfiling').mockImplementation(() => { + vi.spyOn(CpuProfilerBindings, 'stopProfiling').mockImplementation(() => { return { samples: [ { @@ -168,7 +167,7 @@ describe('automated span instrumentation', () => { }; }); - jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); + vi.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); Sentry.getCurrentScope().getPropagationContext().traceId = 'boop'; const transaction = Sentry.startInactiveSpan({ @@ -189,10 +188,10 @@ describe('automated span instrumentation', () => { Sentry.setCurrentClient(client); client.init(); - const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); - const stopProfilingSpy = jest.spyOn(CpuProfilerBindings, 'stopProfiling'); + const startProfilingSpy = vi.spyOn(CpuProfilerBindings, 'startProfiling'); + const stopProfilingSpy = vi.spyOn(CpuProfilerBindings, 'stopProfiling'); - jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); + vi.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); const transaction = Sentry.startInactiveSpan({ forceTransaction: true, name: 'profile_hub' }); await wait(500); @@ -209,7 +208,7 @@ describe('automated span instrumentation', () => { Sentry.setCurrentClient(client); client.init(); - const transportSpy = jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); + const transportSpy = vi.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); const transaction = Sentry.startInactiveSpan({ forceTransaction: true, name: 'profile_hub' }); await wait(500); @@ -251,9 +250,9 @@ describe('automated span instrumentation', () => { Sentry.setCurrentClient(client); client.init(); - jest.spyOn(CpuProfilerBindings, 'stopProfiling').mockReturnValue(null); + vi.spyOn(CpuProfilerBindings, 'stopProfiling').mockReturnValue(null); // Emit is sync, so we can just assert that we got here - const transportSpy = jest.spyOn(transport, 'send').mockImplementation(() => { + const transportSpy = vi.spyOn(transport, 'send').mockImplementation(() => { // Do nothing so we don't send events to Sentry return Promise.resolve({}); }); @@ -274,7 +273,7 @@ describe('automated span instrumentation', () => { Sentry.setCurrentClient(client); client.init(); - const onPreprocessEvent = jest.fn(); + const onPreprocessEvent = vi.fn(); client.on('preprocessEvent', onPreprocessEvent); @@ -293,7 +292,7 @@ describe('automated span instrumentation', () => { }); it('automated span instrumentation does not support continuous profiling', () => { - const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); + const startProfilingSpy = vi.spyOn(CpuProfilerBindings, 'startProfiling'); const [client] = makeClientWithHooks(); Sentry.setCurrentClient(client); @@ -309,7 +308,7 @@ describe('automated span instrumentation', () => { }); it('does not crash if stop is called multiple times', async () => { - const stopProfilingSpy = jest.spyOn(CpuProfilerBindings, 'stopProfiling'); + const stopProfilingSpy = vi.spyOn(CpuProfilerBindings, 'stopProfiling'); const [client] = makeClientWithHooks(); Sentry.setCurrentClient(client); @@ -328,7 +327,7 @@ describe('automated span instrumentation', () => { }; // @ts-expect-error we just mock the return type and ignore the signature - jest.spyOn(CpuProfilerBindings, 'stopProfiling').mockImplementation(() => { + vi.spyOn(CpuProfilerBindings, 'stopProfiling').mockImplementation(() => { return { samples: [ { @@ -354,7 +353,7 @@ describe('automated span instrumentation', () => { Sentry.setCurrentClient(client); client.init(); - const transportSpy = jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); + const transportSpy = vi.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); const transaction = Sentry.startInactiveSpan({ forceTransaction: true, name: 'profile_hub' }); await wait(500); @@ -383,7 +382,7 @@ describe('automated span instrumentation', () => { describe('continuous profiling', () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); // We will mock the carrier as if it has been initialized by the SDK, else everything is short circuited getMainCarrier().__SENTRY__ = {}; GLOBAL_OBJ._sentryDebugIds = undefined as any; @@ -396,14 +395,14 @@ describe('continuous profiling', () => { integration._profiler.stop(); } - jest.clearAllMocks(); - jest.restoreAllMocks(); - jest.runAllTimers(); + vi.clearAllMocks(); + vi.restoreAllMocks(); + vi.runAllTimers(); delete getMainCarrier().__SENTRY__; }); it('initializes the continuous profiler and binds the sentry client', () => { - const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); + const startProfilingSpy = vi.spyOn(CpuProfilerBindings, 'startProfiling'); const [client] = makeContinuousProfilingClient(); Sentry.setCurrentClient(client); @@ -422,7 +421,7 @@ describe('continuous profiling', () => { }); it('starts a continuous profile', () => { - const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); + const startProfilingSpy = vi.spyOn(CpuProfilerBindings, 'startProfiling'); const [client] = makeContinuousProfilingClient(); Sentry.setCurrentClient(client); @@ -438,8 +437,8 @@ describe('continuous profiling', () => { }); it('multiple calls to start abort previous profile', () => { - const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); - const stopProfilingSpy = jest.spyOn(CpuProfilerBindings, 'stopProfiling'); + const startProfilingSpy = vi.spyOn(CpuProfilerBindings, 'startProfiling'); + const stopProfilingSpy = vi.spyOn(CpuProfilerBindings, 'stopProfiling'); const [client] = makeContinuousProfilingClient(); Sentry.setCurrentClient(client); @@ -457,8 +456,8 @@ describe('continuous profiling', () => { }); it('restarts a new chunk after previous', async () => { - const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); - const stopProfilingSpy = jest.spyOn(CpuProfilerBindings, 'stopProfiling'); + const startProfilingSpy = vi.spyOn(CpuProfilerBindings, 'startProfiling'); + const stopProfilingSpy = vi.spyOn(CpuProfilerBindings, 'stopProfiling'); const [client] = makeContinuousProfilingClient(); Sentry.setCurrentClient(client); @@ -471,14 +470,14 @@ describe('continuous profiling', () => { } integration._profiler.start(); - jest.advanceTimersByTime(5001); + vi.advanceTimersByTime(5001); expect(stopProfilingSpy).toHaveBeenCalledTimes(1); expect(startProfilingSpy).toHaveBeenCalledTimes(2); }); it('stops a continuous profile after interval', async () => { - const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); - const stopProfilingSpy = jest.spyOn(CpuProfilerBindings, 'stopProfiling'); + const startProfilingSpy = vi.spyOn(CpuProfilerBindings, 'startProfiling'); + const stopProfilingSpy = vi.spyOn(CpuProfilerBindings, 'stopProfiling'); const [client] = makeContinuousProfilingClient(); Sentry.setCurrentClient(client); @@ -491,13 +490,13 @@ describe('continuous profiling', () => { } integration._profiler.start(); - jest.advanceTimersByTime(5001); + vi.advanceTimersByTime(5001); expect(stopProfilingSpy).toHaveBeenCalledTimes(1); }); it('manullly stopping a chunk doesnt restart the profiler', async () => { - const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); - const stopProfilingSpy = jest.spyOn(CpuProfilerBindings, 'stopProfiling'); + const startProfilingSpy = vi.spyOn(CpuProfilerBindings, 'startProfiling'); + const stopProfilingSpy = vi.spyOn(CpuProfilerBindings, 'stopProfiling'); const [client] = makeContinuousProfilingClient(); Sentry.setCurrentClient(client); @@ -510,17 +509,17 @@ describe('continuous profiling', () => { } integration._profiler.start(); - jest.advanceTimersByTime(1000); + vi.advanceTimersByTime(1000); integration._profiler.stop(); expect(stopProfilingSpy).toHaveBeenCalledTimes(1); - jest.advanceTimersByTime(1000); + vi.advanceTimersByTime(1000); expect(startProfilingSpy).toHaveBeenCalledTimes(1); }); it('continuous mode does not instrument spans', () => { - const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); + const startProfilingSpy = vi.spyOn(CpuProfilerBindings, 'startProfiling'); const [client] = makeContinuousProfilingClient(); Sentry.setCurrentClient(client); @@ -532,7 +531,7 @@ describe('continuous profiling', () => { it('sends as profile_chunk envelope type', async () => { // @ts-expect-error we just mock the return type and ignore the signature - jest.spyOn(CpuProfilerBindings, 'stopProfiling').mockImplementation(() => { + vi.spyOn(CpuProfilerBindings, 'stopProfiling').mockImplementation(() => { return { samples: [ { @@ -558,16 +557,16 @@ describe('continuous profiling', () => { Sentry.setCurrentClient(client); client.init(); - const transportSpy = jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); + const transportSpy = vi.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); const integration = client.getIntegrationByName('ProfilingIntegration'); if (!integration) { throw new Error('Profiling integration not found'); } integration._profiler.start(); - jest.advanceTimersByTime(1000); + vi.advanceTimersByTime(1000); integration._profiler.stop(); - jest.advanceTimersByTime(1000); + vi.advanceTimersByTime(1000); expect(transportSpy.mock.calls?.[0]?.[0]?.[1]?.[0]?.[0]?.type).toBe('profile_chunk'); }); @@ -577,7 +576,7 @@ describe('continuous profiling', () => { Sentry.setCurrentClient(client); client.init(); - const transportSpy = jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); + const transportSpy = vi.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); const nonProfiledTransaction = Sentry.startInactiveSpan({ forceTransaction: true, name: 'profile_hub' }); nonProfiledTransaction.end(); @@ -619,14 +618,14 @@ describe('span profiling mode', () => { ['profilesSampleRate=1', makeClientOptions({ profilesSampleRate: 1 })], ['profilesSampler is defined', makeClientOptions({ profilesSampler: () => 1 })], ])('%s', async (_label, options) => { - const logSpy = jest.spyOn(logger, 'log'); + const logSpy = vi.spyOn(logger, 'log'); const client = new Sentry.NodeClient({ ...options, - dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1, transport: _opts => Sentry.makeNodeTransport({ - url: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + url: 'https://public@dsn.ingest.sentry.io/1337', recordDroppedEvent: () => { return undefined; }, @@ -637,14 +636,14 @@ describe('span profiling mode', () => { Sentry.setCurrentClient(client); client.init(); - const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); + const startProfilingSpy = vi.spyOn(CpuProfilerBindings, 'startProfiling'); const transport = client.getTransport(); if (!transport) { throw new Error('Transport not found'); } - jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); + vi.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); Sentry.startInactiveSpan({ forceTransaction: true, name: 'profile_hub' }); expect(startProfilingSpy).toHaveBeenCalled(); @@ -662,7 +661,7 @@ describe('continuous profiling mode', () => { it.each([ ['profilesSampleRate=0', makeClientOptions({ profilesSampleRate: 0 })], ['profilesSampleRate=undefined', makeClientOptions({ profilesSampleRate: undefined })], - // @ts-expect-error test invalid value + // @ts-expect-error test invalid null value ['profilesSampleRate=null', makeClientOptions({ profilesSampleRate: null })], [ 'profilesSampler is not defined and profilesSampleRate is not set', @@ -671,11 +670,11 @@ describe('continuous profiling mode', () => { ])('%s', async (_label, options) => { const client = new Sentry.NodeClient({ ...options, - dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1, transport: _opts => Sentry.makeNodeTransport({ - url: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + url: 'https://public@dsn.ingest.sentry.io/1337', recordDroppedEvent: () => { return undefined; }, @@ -686,19 +685,22 @@ describe('continuous profiling mode', () => { Sentry.setCurrentClient(client); client.init(); - const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); + const startProfilingSpy = vi.spyOn(CpuProfilerBindings, 'startProfiling'); const transport = client.getTransport(); + expect(transport).toBeDefined(); if (!transport) { - throw new Error('Transport not found'); + return; } - jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); + vi.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); const integration = client.getIntegrationByName('ProfilingIntegration'); + expect(integration).toBeDefined(); if (!integration) { - throw new Error('Profiling integration not found'); + return; } + integration._profiler.start(); const callCount = startProfilingSpy.mock.calls.length; expect(startProfilingSpy).toHaveBeenCalled(); diff --git a/packages/profiling-node/test/spanProfileUtils.worker.test.ts b/packages/profiling-node/test/spanProfileUtils.worker.test.ts index a119f80292d5..aab15aa96a02 100644 --- a/packages/profiling-node/test/spanProfileUtils.worker.test.ts +++ b/packages/profiling-node/test/spanProfileUtils.worker.test.ts @@ -1,11 +1,19 @@ +import { expect, vi } from 'vitest'; + // Mock the modules before the import, so that the value is initialized before the module is loaded -jest.mock('worker_threads', () => { +vi.mock('worker_threads', () => { return { isMainThread: false, threadId: 9999, }; }); -jest.setTimeout(10000); + +import { constructItWithTimeout } from './test-utils'; + +// Required because we test a hypothetical long profile +// and we cannot use advance timers as the c++ relies on +// actual event loop ticks that we cannot advance from vitest. +const itWithTimeout = constructItWithTimeout(10_000); import * as Sentry from '@sentry/node'; import type { Transport } from '@sentry/types'; @@ -17,13 +25,12 @@ function makeContinuousProfilingClient(): [Sentry.NodeClient, Transport] { stackParser: Sentry.defaultStackParser, tracesSampleRate: 1, profilesSampleRate: undefined, - debug: true, environment: 'test-environment', - dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [integration], transport: _opts => Sentry.makeNodeTransport({ - url: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + url: 'https://public@dsn.ingest.sentry.io/1337', recordDroppedEvent: () => { return undefined; }, @@ -33,12 +40,12 @@ function makeContinuousProfilingClient(): [Sentry.NodeClient, Transport] { return [client, client.getTransport() as Transport]; } -it('worker threads context', () => { +itWithTimeout('worker threads context', () => { const [client, transport] = makeContinuousProfilingClient(); Sentry.setCurrentClient(client); client.init(); - const transportSpy = jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); + const transportSpy = vi.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); const nonProfiledTransaction = Sentry.startInactiveSpan({ forceTransaction: true, name: 'profile_hub' }); nonProfiledTransaction.end(); diff --git a/packages/profiling-node/test/test-utils.ts b/packages/profiling-node/test/test-utils.ts new file mode 100644 index 000000000000..a5524697fd66 --- /dev/null +++ b/packages/profiling-node/test/test-utils.ts @@ -0,0 +1,9 @@ +import type { TestFunction } from 'vitest'; +import { it } from 'vitest'; + +export function constructItWithTimeout(timeout: number) { + // eslint-disable-next-line @typescript-eslint/ban-types + return function itWithTimeout(name: string | Function, fn?: TestFunction) { + return it(name, fn, timeout); + }; +} diff --git a/packages/profiling-node/test/utils.test.ts b/packages/profiling-node/test/utils.test.ts index fe661e41f07f..b882b915e215 100644 --- a/packages/profiling-node/test/utils.test.ts +++ b/packages/profiling-node/test/utils.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import type { Event } from '@sentry/types'; import { addItemToEnvelope, createEnvelope, uuid4 } from '@sentry/utils'; diff --git a/packages/profiling-node/tsconfig.test.json b/packages/profiling-node/tsconfig.test.json index 52333183eb70..c401c76a5305 100644 --- a/packages/profiling-node/tsconfig.test.json +++ b/packages/profiling-node/tsconfig.test.json @@ -1,11 +1,11 @@ { "extends": "./tsconfig.json", - "include": ["test/**/*", "src/**/*.d.ts"], + "include": ["test/**/*", "src/**/*.d.ts", "vite.config.ts"], "compilerOptions": { // should include all types from `./tsconfig.json` plus types for all test frameworks used - "types": ["node", "jest"] + "types": ["node"] // other package-specific, test-specific options } diff --git a/packages/profiling-node/vite.config.ts b/packages/profiling-node/vite.config.ts new file mode 100644 index 000000000000..0582a58f479a --- /dev/null +++ b/packages/profiling-node/vite.config.ts @@ -0,0 +1,5 @@ +import baseConfig from '../../vite/vite.config'; + +export default { + ...baseConfig, +};