Files
bun.sh/test/cli/run/cpu-prof.test.ts
2025-11-01 20:17:56 -07:00

191 lines
6.0 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { readdirSync, readFileSync } from "fs";
import { bunEnv, bunExe, tempDir } from "harness";
import { join } from "path";
describe.concurrent("--cpu-prof", () => {
test("generates CPU profile with default name", async () => {
using dir = tempDir("cpu-prof", {
"test.js": `
// CPU-intensive task
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const now = performance.now();
while (now + 50 > performance.now()) {
Bun.inspect(fibonacci(20));
}
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "--cpu-prof", "test.js"],
cwd: String(dir),
env: bunEnv,
stdout: "inherit",
stderr: "inherit",
});
const exitCode = await proc.exited;
// Check that a .cpuprofile file was created
const files = readdirSync(String(dir));
const profileFiles = files.filter(f => f.endsWith(".cpuprofile"));
expect(profileFiles.length).toBeGreaterThan(0);
expect(exitCode).toBe(0);
// Read and validate the profile
const profilePath = join(String(dir), profileFiles[0]);
const profileContent = readFileSync(profilePath, "utf-8");
const profile = JSON.parse(profileContent);
// Validate Chrome CPU Profiler format
expect(profile).toHaveProperty("nodes");
expect(profile).toHaveProperty("startTime");
expect(profile).toHaveProperty("endTime");
expect(profile).toHaveProperty("samples");
expect(profile).toHaveProperty("timeDeltas");
expect(Array.isArray(profile.nodes)).toBe(true);
expect(Array.isArray(profile.samples)).toBe(true);
expect(Array.isArray(profile.timeDeltas)).toBe(true);
// Validate root node
expect(profile.nodes.length).toBeGreaterThan(0);
const rootNode = profile.nodes[0];
expect(rootNode.id).toBe(1);
expect(rootNode.callFrame.functionName).toBe("(root)");
// Validate node structure
profile.nodes.forEach((node: any) => {
expect(node).toHaveProperty("id");
expect(node).toHaveProperty("callFrame");
expect(node).toHaveProperty("hitCount");
expect(node.callFrame).toHaveProperty("functionName");
expect(node.callFrame).toHaveProperty("scriptId");
expect(node.callFrame).toHaveProperty("url");
expect(node.callFrame).toHaveProperty("lineNumber");
expect(node.callFrame).toHaveProperty("columnNumber");
});
// Validate samples point to valid nodes
const nodeIds = new Set(profile.nodes.map((n: any) => n.id));
profile.samples.forEach((sample: number) => {
expect(nodeIds.has(sample)).toBe(true);
});
// Validate time deltas
expect(profile.timeDeltas.length).toBe(profile.samples.length);
// For very fast programs, start and end times might be equal or very close
expect(profile.startTime).toBeLessThanOrEqual(profile.endTime);
// CRITICAL: Validate timestamps are positive and in microseconds
// Chrome DevTools requires timestamps in microseconds since Unix epoch
// A valid timestamp should be > 1000000000000000 (around year 2001)
// and < 3000000000000000 (around year 2065)
expect(profile.startTime).toBeGreaterThan(1000000000000000);
expect(profile.startTime).toBeLessThan(3000000000000000);
expect(profile.endTime).toBeGreaterThan(1000000000000000);
expect(profile.endTime).toBeLessThan(3000000000000000);
});
test("--cpu-prof-name sets custom filename", async () => {
using dir = tempDir("cpu-prof-name", {
"test.js": `
function loop() {
const end = Date.now() + 32;
while (Date.now() < end) {}
}
loop();
`,
});
const customName = "my-profile.cpuprofile";
await using proc = Bun.spawn({
cmd: [bunExe(), "--cpu-prof", "--cpu-prof-name", customName, "test.js"],
cwd: String(dir),
env: bunEnv,
stdout: "inherit",
stderr: "inherit",
});
const exitCode = await proc.exited;
const files = readdirSync(String(dir));
expect(files).toContain(customName);
expect(exitCode).toBe(0);
});
test("--cpu-prof-dir sets custom directory", async () => {
using dir = tempDir("cpu-prof-dir", {
"test.js": `
function loop() {
const end = Date.now() + 32;
while (Date.now() < end) {}
}
loop();
`,
"profiles": {},
});
await using proc = Bun.spawn({
cmd: [bunExe(), "--cpu-prof", "--cpu-prof-dir", "profiles", "test.js"],
cwd: String(dir),
env: bunEnv,
stdout: "inherit",
stderr: "inherit",
});
const exitCode = await proc.exited;
const profilesDir = join(String(dir), "profiles");
const files = readdirSync(profilesDir);
const profileFiles = files.filter(f => f.endsWith(".cpuprofile"));
expect(profileFiles.length).toBeGreaterThan(0);
expect(exitCode).toBe(0);
});
test("profile captures function names", async () => {
using dir = tempDir("cpu-prof-functions", {
"test.js": `
function myFunction() {
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return sum;
}
myFunction();
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "--cpu-prof", "test.js"],
cwd: String(dir),
env: bunEnv,
stdout: "inherit",
stderr: "inherit",
});
const exitCode = await proc.exited;
const files = readdirSync(String(dir));
const profileFiles = files.filter(f => f.endsWith(".cpuprofile"));
expect(profileFiles.length).toBeGreaterThan(0);
const profilePath = join(String(dir), profileFiles[0]);
const profile = JSON.parse(readFileSync(profilePath, "utf-8"));
// Check that we captured some meaningful function names
const functionNames = profile.nodes.map((n: any) => n.callFrame.functionName);
expect(functionNames.some((name: string) => name !== "(root)" && name !== "(program)")).toBe(true);
expect(exitCode).toBe(0);
});
});