Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
13 changes: 8 additions & 5 deletions src/jsc/BunCPUProfiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,18 @@ fn build_output_path(
generate_default_filename(&mut filename_buf, is_md_format)?
};

// Append directory if specified
// Use `join` rather than `append` for both the directory and the filename
// so that an absolute `--cpu-prof-dir` or `--cpu-prof-name` is honored:
// `append` trims its input as relative to the already-rooted path and
// strips the leading separator, whereas `join` resets the accumulated path
// when a segment is absolute.
// AutoAbsPath uses CheckLength::ASSUME — Err arm is unreachable.
// See paths/Path.rs `options::Result` note.
if !config.dir.is_empty() {
// AutoAbsPath uses CheckLength::ASSUME — Err arm is unreachable.
// See paths/Path.rs `options::Result` note.
path.join(&[config.dir]).expect("unreachable");
}

// Append filename
path.append(filename).expect("unreachable");
path.join(&[filename]).expect("unreachable");
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Ok(())
}
Expand Down
12 changes: 8 additions & 4 deletions src/jsc/BunHeapProfiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,17 @@ fn build_output_path(path: &mut AutoAbsPath, config: &HeapProfilerConfig) -> Res
generate_default_filename(&mut filename_buf, config.text_format)?
};

// Append directory if specified
// Use `join` rather than `append` for both the directory and the filename
// so that an absolute `--heap-prof-dir` or `--heap-prof-name` is honored:
// `append` trims its input as relative to the already-rooted path and
// strips the leading separator (it also debug-asserts the input is
// relative), whereas `join` resets the accumulated path when a segment is
// absolute.
if !config.dir.is_empty() {
path.append(config.dir)?;
path.join(&[config.dir])?;
Comment thread
robobun marked this conversation as resolved.
}

// Append filename
path.append(filename)?;
path.join(&[filename])?;
Ok(())
}

Expand Down
59 changes: 59 additions & 0 deletions test/cli/heap-prof.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,65 @@ test("--heap-prof-dir specifies output directory for V8 format", async () => {
expect(files.length).toBeGreaterThan(0);
});

test("--heap-prof-dir honors an absolute output directory", async () => {
// Two separate directories: one is the CWD, the other is the absolute
// target. An absolute --heap-prof-dir must be written to that directory, not
// resolved relative to CWD (which stripped the leading separator).
using cwdDir = tempDir("heap-prof-abs-cwd", {});
using targetDir = tempDir("heap-prof-abs-target", {});

await using proc = Bun.spawn({
cmd: [bunExe(), "--heap-prof", "--heap-prof-dir", String(targetDir), "-e", `console.log("hello");`],
cwd: String(cwdDir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stdout.trim()).toBe("hello");
expect(stderr).toContain("Heap profile written to:");
expect(exitCode).toBe(0);

// The snapshot must land in the absolute target directory.
const targetFiles = Array.from(new Bun.Glob("Heap.*.heapsnapshot").scanSync({ cwd: String(targetDir) }));
expect(targetFiles.length).toBeGreaterThan(0);

// And nothing should have been written anywhere under CWD.
const cwdFiles = Array.from(new Bun.Glob("**/Heap.*.heapsnapshot").scanSync({ cwd: String(cwdDir) }));
expect(cwdFiles).toEqual([]);
});

test("--heap-prof-name honors an absolute path", async () => {
// An absolute --heap-prof-name must be written verbatim, not resolved
// relative to CWD (which stripped the leading separator).
using cwdDir = tempDir("heap-prof-name-abs-cwd", {});
using targetDir = tempDir("heap-prof-name-abs-target", {});
const target = join(String(targetDir), "custom.heapsnapshot");

await using proc = Bun.spawn({
cmd: [bunExe(), "--heap-prof", "--heap-prof-name", target, "-e", `console.log("hello");`],
cwd: String(cwdDir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stdout.trim()).toBe("hello");
expect(stderr).toContain("Heap profile written to:");
expect(exitCode).toBe(0);

// The snapshot must land at the absolute path.
expect(Bun.file(target).size).toBeGreaterThan(0);

// And nothing should have been written anywhere under CWD.
const cwdFiles = Array.from(new Bun.Glob("**/*.heapsnapshot").scanSync({ cwd: String(cwdDir) }));
expect(cwdFiles).toEqual([]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

test("--heap-prof-dir specifies output directory for markdown format", async () => {
using dir = tempDir("heap-prof-md-dir-test", {
"profiles": {},
Expand Down
34 changes: 34 additions & 0 deletions test/cli/run/cpu-prof.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,40 @@ describe.concurrent("--cpu-prof", () => {
expect(exitCode).toBe(0);
});

test("--cpu-prof-name honors an absolute path", async () => {
// An absolute --cpu-prof-name must be written verbatim, not resolved
// relative to CWD (which stripped the leading separator).
using cwdDir = tempDir("cpu-prof-name-abs-cwd", {
"test.js": `
function loop() {
const end = Date.now() + 100;
while (Date.now() < end) {}
}
loop();
`,
});
using targetDir = tempDir("cpu-prof-name-abs-target", {});
const target = join(String(targetDir), "custom.cpuprofile");

await using proc = Bun.spawn({
cmd: [bunExe(), "--cpu-prof", "--cpu-prof-name", target, "test.js"],
cwd: String(cwdDir),
env: bunEnv,
stdout: "inherit",
stderr: "inherit",
});

const exitCode = await proc.exited;

// The profile must land at the absolute path.
expect(Bun.file(target).size).toBeGreaterThan(0);

// And nothing should have been written anywhere under CWD.
const cwdFiles = Array.from(new Bun.Glob("**/*.cpuprofile").scanSync({ cwd: String(cwdDir) }));
expect(cwdFiles).toEqual([]);
expect(exitCode).toBe(0);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

test("--cpu-prof-dir sets custom directory", async () => {
using dir = tempDir("cpu-prof-dir", {
"test.js": `
Expand Down
Loading