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(profiling-node): Switch to vitest #13030

Closed
wants to merge 2 commits into from
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
2 changes: 1 addition & 1 deletion packages/browser/test/unit/profiling/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion packages/profiling-node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 0 additions & 6 deletions packages/profiling-node/jest.config.js

This file was deleted.

4 changes: 2 additions & 2 deletions packages/profiling-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions packages/profiling-node/src/cpu_profiler.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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'];
Expand Down
2 changes: 1 addition & 1 deletion packages/profiling-node/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
9 changes: 7 additions & 2 deletions packages/profiling-node/test/bindings.test.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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);
Expand Down
48 changes: 25 additions & 23 deletions packages/profiling-node/test/cpu_profiler.test.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -68,7 +71,7 @@
};

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(() => {
Expand All @@ -77,7 +80,7 @@
}).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(() => {
Expand All @@ -86,7 +89,7 @@
}).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);

Expand All @@ -103,7 +106,7 @@
}
});

it('does not collect resources', async () => {
itWithTimeout('does not collect resources', async () => {
PrivateCpuProfilerBindings.startProfiling('profiled-program');
await wait(100);

Expand All @@ -115,12 +118,12 @@
});

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);
});
Expand All @@ -130,7 +133,7 @@
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);
});
Expand All @@ -142,11 +145,11 @@
throw new Error('No samples');
}
for (const sample of samples) {
expect(sample.thread_id).toBe('0');

Check failure on line 148 in packages/profiling-node/test/cpu_profiler.test.ts

View workflow job for this annotation

GitHub Actions / Compile & Test Profiling Bindings (v22.4) ubuntu-20.04, 22.4, node:22-alpine3.18, musl

test/cpu_profiler.test.ts > Profiler bindings > adds thread_id info

AssertionError: expected '2' to be '0' // Object.is equality - Expected + Received - 0 + 2 ❯ test/cpu_profiler.test.ts:148:32

Check failure on line 148 in packages/profiling-node/test/cpu_profiler.test.ts

View workflow job for this annotation

GitHub Actions / Compile & Test Profiling Bindings (v18) ubuntu-20.04, 18, node:18-alpine3.17, musl

test/cpu_profiler.test.ts > Profiler bindings > adds thread_id info

AssertionError: expected '2' to be '0' // Object.is equality - Expected + Received - 0 + 2 ❯ test/cpu_profiler.test.ts:148:32

Check failure on line 148 in packages/profiling-node/test/cpu_profiler.test.ts

View workflow job for this annotation

GitHub Actions / Compile & Test Profiling Bindings (v20) ubuntu-20.04, 20, node:20-alpine3.17, musl

test/cpu_profiler.test.ts > Profiler bindings > adds thread_id info

AssertionError: expected '2' to be '0' // Object.is equality - Expected + Received - 0 + 2 ❯ test/cpu_profiler.test.ts:148:32

Check failure on line 148 in packages/profiling-node/test/cpu_profiler.test.ts

View workflow job for this annotation

GitHub Actions / Compile & Test Profiling Bindings (v16) ubuntu-20.04, 16, node:16-alpine3.16, musl

test/cpu_profiler.test.ts > Profiler bindings > adds thread_id info

AssertionError: expected '2' to be '0' // Object.is equality - Expected + Received - 0 + 2 ❯ test/cpu_profiler.test.ts:148:32

Check failure on line 148 in packages/profiling-node/test/cpu_profiler.test.ts

View workflow job for this annotation

GitHub Actions / Compile & Test Profiling Bindings (v16) windows-2022, 16, x64, glibc

test/cpu_profiler.test.ts > Profiler bindings > adds thread_id info

AssertionError: expected '2' to be '0' // Object.is equality - Expected + Received - 0 + 2 ❯ test/cpu_profiler.test.ts:148:32

Check failure on line 148 in packages/profiling-node/test/cpu_profiler.test.ts

View workflow job for this annotation

GitHub Actions / Compile & Test Profiling Bindings (v18) windows-2022, 18, x64, glibc

test/cpu_profiler.test.ts > Profiler bindings > adds thread_id info

AssertionError: expected '2' to be '0' // Object.is equality - Expected + Received - 0 + 2 ❯ test/cpu_profiler.test.ts:148:32

Check failure on line 148 in packages/profiling-node/test/cpu_profiler.test.ts

View workflow job for this annotation

GitHub Actions / Compile & Test Profiling Bindings (v20) windows-2022, 20, x64, glibc

test/cpu_profiler.test.ts > Profiler bindings > adds thread_id info

AssertionError: expected '2' to be '0' // Object.is equality - Expected + Received - 0 + 2 ❯ test/cpu_profiler.test.ts:148:32

Check failure on line 148 in packages/profiling-node/test/cpu_profiler.test.ts

View workflow job for this annotation

GitHub Actions / Compile & Test Profiling Bindings (v22.4) windows-2022, 22.4, x64, glibc

test/cpu_profiler.test.ts > Profiler bindings > adds thread_id info

AssertionError: expected '2' to be '0' // Object.is equality - Expected + Received - 0 + 2 ❯ test/cpu_profiler.test.ts:148:32
}
});

it('caps stack depth at 128', async () => {
itWithTimeout('caps stack depth at 128', async () => {
const recurseToDepth = async (depth: number): Promise<number> => {
if (depth === 0) {
// Wait a bit to make sure stack gets sampled here
Expand All @@ -168,7 +171,7 @@
}
});

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');

Expand All @@ -179,19 +182,19 @@
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);
CpuProfilerBindings.stopProfiling('same-title', 0);
}).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();
Expand All @@ -201,12 +204,12 @@
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);
});
Expand All @@ -215,7 +218,7 @@
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 () => {
Expand All @@ -240,7 +243,7 @@
}
});

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);
Expand All @@ -251,7 +254,7 @@
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);
Expand All @@ -275,7 +278,7 @@
}
});

it('collects memory footprint', async () => {
itWithTimeout('collects memory footprint', async () => {
CpuProfilerBindings.startProfiling('profile');
await wait(1000);
const profile = CpuProfilerBindings.stopProfiling('profile', 0);
Expand All @@ -291,7 +294,7 @@
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);
Expand All @@ -307,7 +310,7 @@
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);
Expand All @@ -316,7 +319,6 @@
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() {
Expand Down
20 changes: 11 additions & 9 deletions packages/profiling-node/test/integration.test.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -7,16 +9,16 @@ import { _nodeProfilingIntegration } from '../src/integration';

describe('ProfilingIntegration', () => {
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('has a name', () => {
expect(_nodeProfilingIntegration().name).toBe('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();
Expand All @@ -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: {},
Expand All @@ -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);

Expand Down
Loading
Loading