mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Compare commits
5 Commits
bun-v1.3.5
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
001d2642d8 | ||
|
|
8c66bda1ea | ||
|
|
541bfd0fcd | ||
|
|
476bf5f2c9 | ||
|
|
e565ab42b1 |
@@ -902,7 +902,9 @@ pub fn hoist(
|
||||
.manager = manager,
|
||||
.install_root_dependencies = install_root_dependencies,
|
||||
.workspace_filters = workspace_filters,
|
||||
.hoist_visited_packages = std.AutoHashMap(Tree.PackageID, void).init(allocator),
|
||||
};
|
||||
defer builder.hoist_visited_packages.deinit();
|
||||
|
||||
try (Tree{}).processSubtree(
|
||||
Tree.root_dep_id,
|
||||
|
||||
@@ -246,6 +246,8 @@ pub fn Builder(comptime method: BuilderMethod) type {
|
||||
sort_buf: std.ArrayListUnmanaged(DependencyID) = .{},
|
||||
workspace_filters: if (method == .filter) []const WorkspaceFilter else void = if (method == .filter) &.{},
|
||||
install_root_dependencies: if (method == .filter) bool else void,
|
||||
// Reusable set for cycle detection during hoisting to avoid repeated allocations
|
||||
hoist_visited_packages: std.AutoHashMap(PackageID, void),
|
||||
|
||||
pub fn maybeReportError(this: *@This(), comptime fmt: string, args: anytype) void {
|
||||
this.log.addErrorFmt(null, logger.Loc.Empty, this.allocator, fmt, args) catch {};
|
||||
@@ -557,6 +559,33 @@ fn hoistDependency(
|
||||
comptime method: BuilderMethod,
|
||||
builder: *Builder(method),
|
||||
) !HoistDependencyResult {
|
||||
// Clear the reusable visited set for this top-level hoisting operation
|
||||
builder.hoist_visited_packages.clearRetainingCapacity();
|
||||
return hoistDependencyWithVisited(this, as_defined, hoist_root_id, package_id, dependency, dependency_lists, trees, method, builder, &builder.hoist_visited_packages);
|
||||
}
|
||||
|
||||
fn hoistDependencyWithVisited(
|
||||
this: *Tree,
|
||||
comptime as_defined: bool,
|
||||
hoist_root_id: Id,
|
||||
package_id: PackageID,
|
||||
dependency: *const Dependency,
|
||||
dependency_lists: []Lockfile.DependencyIDList,
|
||||
trees: []Tree,
|
||||
comptime method: BuilderMethod,
|
||||
builder: *Builder(method),
|
||||
visited_packages: *std.AutoHashMap(PackageID, void),
|
||||
) !HoistDependencyResult {
|
||||
// Check if we've already visited this package during hoisting
|
||||
// This prevents infinite recursion in circular dependency scenarios
|
||||
if (visited_packages.contains(package_id)) {
|
||||
return .dependency_loop;
|
||||
}
|
||||
|
||||
// Mark this package as visited
|
||||
try visited_packages.put(package_id, {});
|
||||
// Ensure we remove this package from visited set when function exits
|
||||
defer _ = visited_packages.remove(package_id);
|
||||
const this_dependencies = this.dependencies.get(dependency_lists[this.id].items);
|
||||
for (0..this_dependencies.len) |i| {
|
||||
const dep_id = this_dependencies[i];
|
||||
@@ -614,7 +643,7 @@ fn hoistDependency(
|
||||
|
||||
// this dependency was not found in this tree, try hoisting or placing in the next parent
|
||||
if (this.parent != invalid_id and this.id != hoist_root_id) {
|
||||
const id = trees[this.parent].hoistDependency(
|
||||
const id = trees[this.parent].hoistDependencyWithVisited(
|
||||
false,
|
||||
hoist_root_id,
|
||||
package_id,
|
||||
@@ -623,6 +652,7 @@ fn hoistDependency(
|
||||
trees,
|
||||
method,
|
||||
builder,
|
||||
visited_packages,
|
||||
) catch unreachable;
|
||||
if (!as_defined or id != .dependency_loop) return id; // 1 or 2
|
||||
}
|
||||
@@ -645,6 +675,8 @@ pub const TreeFiller = std.fifo.LinearFifo(FillItem, .Dynamic);
|
||||
const string = []const u8;
|
||||
const stringZ = [:0]const u8;
|
||||
|
||||
pub const PackageID = install.PackageID;
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
@@ -662,7 +694,6 @@ const String = bun.Semver.String;
|
||||
const install = bun.install;
|
||||
const Dependency = install.Dependency;
|
||||
const DependencyID = install.DependencyID;
|
||||
const PackageID = install.PackageID;
|
||||
const PackageNameHash = install.PackageNameHash;
|
||||
const Resolution = install.Resolution;
|
||||
const invalid_dependency_id = install.invalid_dependency_id;
|
||||
|
||||
190
test/cli/install/performance-benchmark.test.ts
Normal file
190
test/cli/install/performance-benchmark.test.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
||||
|
||||
// Performance benchmarks for dependency hoisting with cycle detection
|
||||
test("benchmark: small dependency tree (10 packages)", async () => {
|
||||
const packageJson = {
|
||||
name: "bench-small",
|
||||
dependencies: {},
|
||||
};
|
||||
|
||||
// Create 10 packages with linear dependencies: pkg-0 -> pkg-1 -> pkg-2 -> ... -> pkg-9
|
||||
const files = { "package.json": JSON.stringify(packageJson) };
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const deps = i < 9 ? { [`pkg-${i + 1}`]: `file:./pkg-${i + 1}` } : {};
|
||||
files[`pkg-${i}/package.json`] = JSON.stringify({
|
||||
name: `pkg-${i}`,
|
||||
dependencies: deps,
|
||||
});
|
||||
packageJson.dependencies[`pkg-${i}`] = `file:./pkg-${i}`;
|
||||
}
|
||||
|
||||
const dir = tempDirWithFiles("bench-small", files);
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
const duration = performance.now() - start;
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).not.toContain("panic");
|
||||
console.log(`Small tree (10 packages): ${duration.toFixed(2)}ms`);
|
||||
}, 30000);
|
||||
|
||||
test("benchmark: medium dependency tree (50 packages)", async () => {
|
||||
const packageJson = {
|
||||
name: "bench-medium",
|
||||
dependencies: {},
|
||||
};
|
||||
|
||||
// Create 50 packages with linear dependencies
|
||||
const files = { "package.json": JSON.stringify(packageJson) };
|
||||
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const deps = i < 49 ? { [`pkg-${i + 1}`]: `file:./pkg-${i + 1}` } : {};
|
||||
files[`pkg-${i}/package.json`] = JSON.stringify({
|
||||
name: `pkg-${i}`,
|
||||
dependencies: deps,
|
||||
});
|
||||
packageJson.dependencies[`pkg-${i}`] = `file:./pkg-${i}`;
|
||||
}
|
||||
|
||||
const dir = tempDirWithFiles("bench-medium", files);
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
const duration = performance.now() - start;
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).not.toContain("panic");
|
||||
console.log(`Medium tree (50 packages): ${duration.toFixed(2)}ms`);
|
||||
}, 30000);
|
||||
|
||||
test("benchmark: wide dependency tree (20 packages, each depends on 5 others)", async () => {
|
||||
const packageJson = {
|
||||
name: "bench-wide",
|
||||
dependencies: {},
|
||||
};
|
||||
|
||||
// Create 20 packages where each depends on 5 others (wide tree)
|
||||
const files = { "package.json": JSON.stringify(packageJson) };
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const deps = {};
|
||||
// Each package depends on the next 5 packages (cyclically)
|
||||
for (let j = 1; j <= 5; j++) {
|
||||
const depIndex = (i + j) % 20;
|
||||
deps[`pkg-${depIndex}`] = `file:./pkg-${depIndex}`;
|
||||
}
|
||||
|
||||
files[`pkg-${i}/package.json`] = JSON.stringify({
|
||||
name: `pkg-${i}`,
|
||||
dependencies: deps,
|
||||
});
|
||||
packageJson.dependencies[`pkg-${i}`] = `file:./pkg-${i}`;
|
||||
}
|
||||
|
||||
const dir = tempDirWithFiles("bench-wide", files);
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
const duration = performance.now() - start;
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).not.toContain("panic");
|
||||
console.log(`Wide tree (20x5 deps): ${duration.toFixed(2)}ms`);
|
||||
}, 30000);
|
||||
|
||||
test("benchmark: complex dependency tree with multiple cycles", async () => {
|
||||
const packageJson = {
|
||||
name: "bench-complex",
|
||||
dependencies: {},
|
||||
};
|
||||
|
||||
// Create a complex dependency structure with multiple cycles
|
||||
const files = { "package.json": JSON.stringify(packageJson) };
|
||||
|
||||
// Create 15 packages with complex interdependencies
|
||||
const depStructure = {
|
||||
0: [1, 2, 3], // pkg-0 -> pkg-1, pkg-2, pkg-3
|
||||
1: [4, 5], // pkg-1 -> pkg-4, pkg-5
|
||||
2: [6, 7], // pkg-2 -> pkg-6, pkg-7
|
||||
3: [8, 9], // pkg-3 -> pkg-8, pkg-9
|
||||
4: [10, 0], // pkg-4 -> pkg-10, pkg-0 (cycle)
|
||||
5: [11, 1], // pkg-5 -> pkg-11, pkg-1 (cycle)
|
||||
6: [12, 2], // pkg-6 -> pkg-12, pkg-2 (cycle)
|
||||
7: [13, 3], // pkg-7 -> pkg-13, pkg-3 (cycle)
|
||||
8: [14, 4], // pkg-8 -> pkg-14, pkg-4
|
||||
9: [0, 5], // pkg-9 -> pkg-0, pkg-5 (cycle)
|
||||
10: [6, 7], // pkg-10 -> pkg-6, pkg-7
|
||||
11: [8, 9], // pkg-11 -> pkg-8, pkg-9
|
||||
12: [10, 11], // pkg-12 -> pkg-10, pkg-11
|
||||
13: [12, 4], // pkg-13 -> pkg-12, pkg-4
|
||||
14: [13, 5], // pkg-14 -> pkg-13, pkg-5
|
||||
};
|
||||
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const deps = {};
|
||||
const depIndices = depStructure[i] || [];
|
||||
|
||||
for (const depIndex of depIndices) {
|
||||
deps[`pkg-${depIndex}`] = `file:./pkg-${depIndex}`;
|
||||
}
|
||||
|
||||
files[`pkg-${i}/package.json`] = JSON.stringify({
|
||||
name: `pkg-${i}`,
|
||||
dependencies: deps,
|
||||
});
|
||||
packageJson.dependencies[`pkg-${i}`] = `file:./pkg-${i}`;
|
||||
}
|
||||
|
||||
const dir = tempDirWithFiles("bench-complex", files);
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
const duration = performance.now() - start;
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).not.toContain("panic");
|
||||
console.log(`Complex tree (15 packages, multiple cycles): ${duration.toFixed(2)}ms`);
|
||||
}, 30000);
|
||||
146
test/cli/install/simple-bench.test.ts
Normal file
146
test/cli/install/simple-bench.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
||||
|
||||
// Simple performance test with common patterns
|
||||
test("benchmark: React-like dependency pattern", async () => {
|
||||
// Simulate a typical React project structure
|
||||
const dir = tempDirWithFiles("react-bench", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "react-bench",
|
||||
dependencies: {
|
||||
"comp-a": "file:./comp-a",
|
||||
"comp-b": "file:./comp-b",
|
||||
"comp-c": "file:./comp-c",
|
||||
"shared": "file:./shared",
|
||||
},
|
||||
}),
|
||||
"comp-a/package.json": JSON.stringify({
|
||||
name: "comp-a",
|
||||
dependencies: {
|
||||
"shared": "file:../shared",
|
||||
"react": "^18.0.0",
|
||||
},
|
||||
}),
|
||||
"comp-b/package.json": JSON.stringify({
|
||||
name: "comp-b",
|
||||
dependencies: {
|
||||
"shared": "file:../shared",
|
||||
"comp-a": "file:../comp-a",
|
||||
"react": "^18.0.0",
|
||||
},
|
||||
}),
|
||||
"comp-c/package.json": JSON.stringify({
|
||||
name: "comp-c",
|
||||
dependencies: {
|
||||
"shared": "file:../shared",
|
||||
"comp-b": "file:../comp-b",
|
||||
"react": "^18.0.0",
|
||||
},
|
||||
}),
|
||||
"shared/package.json": JSON.stringify({
|
||||
name: "shared",
|
||||
dependencies: {
|
||||
"lodash": "^4.17.21",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const times = [];
|
||||
|
||||
// Run 3 times and take average
|
||||
for (let i = 0; i < 3; i++) {
|
||||
// Clean lockfile
|
||||
await Bun.spawn({
|
||||
cmd: ["rm", "-f", "bun.lock"],
|
||||
cwd: dir,
|
||||
}).exited;
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
const duration = performance.now() - start;
|
||||
times.push(duration);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).not.toContain("panic");
|
||||
}
|
||||
|
||||
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
|
||||
console.log(`React-like pattern (3 runs avg): ${avgTime.toFixed(2)}ms`);
|
||||
console.log(`Individual times: ${times.map(t => t.toFixed(2)).join(", ")}ms`);
|
||||
}, 60000);
|
||||
|
||||
test("benchmark: No circular dependencies baseline", async () => {
|
||||
// Simple linear dependency chain (no cycles)
|
||||
const dir = tempDirWithFiles("linear-bench", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "linear-bench",
|
||||
dependencies: {
|
||||
"pkg-1": "file:./pkg-1",
|
||||
},
|
||||
}),
|
||||
"pkg-1/package.json": JSON.stringify({
|
||||
name: "pkg-1",
|
||||
dependencies: {
|
||||
"pkg-2": "file:../pkg-2",
|
||||
},
|
||||
}),
|
||||
"pkg-2/package.json": JSON.stringify({
|
||||
name: "pkg-2",
|
||||
dependencies: {
|
||||
"pkg-3": "file:../pkg-3",
|
||||
},
|
||||
}),
|
||||
"pkg-3/package.json": JSON.stringify({
|
||||
name: "pkg-3",
|
||||
dependencies: {
|
||||
"lodash": "^4.17.21",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const times = [];
|
||||
|
||||
// Run 5 times for good statistical significance
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Clean lockfile
|
||||
await Bun.spawn({
|
||||
cmd: ["rm", "-f", "bun.lock"],
|
||||
cwd: dir,
|
||||
}).exited;
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
const duration = performance.now() - start;
|
||||
times.push(duration);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).not.toContain("panic");
|
||||
}
|
||||
|
||||
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
|
||||
const minTime = Math.min(...times);
|
||||
const maxTime = Math.max(...times);
|
||||
|
||||
console.log(`Linear dependencies (5 runs avg): ${avgTime.toFixed(2)}ms`);
|
||||
console.log(`Range: ${minTime.toFixed(2)}ms - ${maxTime.toFixed(2)}ms`);
|
||||
}, 60000);
|
||||
107
test/regression/issue/circular-dependency-hoist-crash.test.ts
Normal file
107
test/regression/issue/circular-dependency-hoist-crash.test.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
||||
|
||||
test("circular dependencies should not cause infinite recursion during hoisting", async () => {
|
||||
// This test reproduces a crash where circular dependencies in workspace packages
|
||||
// would cause infinite recursion in the hoistDependency function, leading to
|
||||
// stack overflow and segmentation fault.
|
||||
//
|
||||
// The circular dependency pattern is: pkg-a -> pkg-b -> pkg-c -> pkg-a
|
||||
//
|
||||
// The fix uses cycle detection rather than depth limiting to properly handle
|
||||
// both circular dependencies and legitimately deep dependency trees.
|
||||
|
||||
const dir = tempDirWithFiles("circular-deps", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "root",
|
||||
dependencies: {
|
||||
"pkg-a": "file:./pkg-a",
|
||||
"pkg-b": "file:./pkg-b",
|
||||
"pkg-c": "file:./pkg-c",
|
||||
},
|
||||
scripts: {
|
||||
"o": "bun outdated",
|
||||
},
|
||||
}),
|
||||
"pkg-a/package.json": JSON.stringify({
|
||||
name: "pkg-a",
|
||||
dependencies: {
|
||||
"pkg-b": "file:../pkg-b",
|
||||
},
|
||||
}),
|
||||
"pkg-b/package.json": JSON.stringify({
|
||||
name: "pkg-b",
|
||||
dependencies: {
|
||||
"pkg-c": "file:../pkg-c",
|
||||
},
|
||||
}),
|
||||
"pkg-c/package.json": JSON.stringify({
|
||||
name: "pkg-c",
|
||||
dependencies: {
|
||||
"pkg-a": "file:../pkg-a",
|
||||
"react": "^18.0.0",
|
||||
},
|
||||
}),
|
||||
// Include a lockfile that reproduces the problematic state
|
||||
"bun.lock": JSON.stringify(
|
||||
{
|
||||
lockfileVersion: 1,
|
||||
workspaces: {
|
||||
"": {
|
||||
name: "root",
|
||||
dependencies: {
|
||||
"pkg-a": "file:./pkg-a",
|
||||
"pkg-b": "file:./pkg-b",
|
||||
"pkg-c": "file:./pkg-c",
|
||||
},
|
||||
},
|
||||
},
|
||||
packages: {
|
||||
"js-tokens": [
|
||||
"js-tokens@4.0.0",
|
||||
"",
|
||||
{},
|
||||
"sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
],
|
||||
"loose-envify": [
|
||||
"loose-envify@1.4.0",
|
||||
"",
|
||||
{ "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } },
|
||||
"sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
],
|
||||
"pkg-a": ["pkg-a@file:pkg-a", { "dependencies": { "pkg-b": "file:../pkg-b" } }],
|
||||
"pkg-b": ["pkg-b@file:pkg-b", { "dependencies": { "pkg-c": "file:../pkg-c" } }],
|
||||
"pkg-c": ["pkg-c@file:pkg-c", { "dependencies": { "pkg-a": "file:../pkg-a", "react": "^18.0.0" } }],
|
||||
"react": [
|
||||
"react@18.3.1",
|
||||
"",
|
||||
{ "dependencies": { "loose-envify": "^1.1.0" } },
|
||||
"sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
],
|
||||
"pkg-a/pkg-b": ["pkg-b@file:pkg-b", {}],
|
||||
"pkg-b/pkg-c": ["pkg-c@file:pkg-c", {}],
|
||||
"pkg-c/pkg-a": ["pkg-a@file:pkg-a", {}],
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
});
|
||||
|
||||
// Run bun install - this should complete without crashing
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
// Should exit successfully, not crash with segmentation fault
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).not.toContain("panic");
|
||||
expect(stderr).not.toContain("Segmentation fault");
|
||||
expect(stdout).toContain("packages installed");
|
||||
}, 30000); // 30 second timeout
|
||||
Reference in New Issue
Block a user