Skip to content

Commit 8c66bda

Browse files
Claude Botclaude
andcommitted
fix: make PackageID public for HashMap type usage
Makes Tree.PackageID public to fix compilation error when using it as HashMap key type in the cycle detection implementation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 541bfd0 commit 8c66bda

File tree

3 files changed

+361
-1
lines changed

3 files changed

+361
-1
lines changed

src/install/lockfile/Tree.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,7 @@ const String = bun.Semver.String;
692692
const install = bun.install;
693693
const Dependency = install.Dependency;
694694
const DependencyID = install.DependencyID;
695-
const PackageID = install.PackageID;
695+
pub const PackageID = install.PackageID;
696696
const PackageNameHash = install.PackageNameHash;
697697
const Resolution = install.Resolution;
698698
const invalid_dependency_id = install.invalid_dependency_id;
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import { test, expect } from "bun:test";
2+
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
3+
4+
// Performance benchmarks for dependency hoisting with cycle detection
5+
test("benchmark: small dependency tree (10 packages)", async () => {
6+
const packageJson = {
7+
name: "bench-small",
8+
dependencies: {}
9+
};
10+
11+
// Create 10 packages with linear dependencies: pkg-0 -> pkg-1 -> pkg-2 -> ... -> pkg-9
12+
const files = { "package.json": JSON.stringify(packageJson) };
13+
14+
for (let i = 0; i < 10; i++) {
15+
const deps = i < 9 ? { [`pkg-${i + 1}`]: `file:./pkg-${i + 1}` } : {};
16+
files[`pkg-${i}/package.json`] = JSON.stringify({
17+
name: `pkg-${i}`,
18+
dependencies: deps
19+
});
20+
packageJson.dependencies[`pkg-${i}`] = `file:./pkg-${i}`;
21+
}
22+
23+
const dir = tempDirWithFiles("bench-small", files);
24+
25+
const start = performance.now();
26+
27+
await using proc = Bun.spawn({
28+
cmd: [bunExe(), "install"],
29+
env: bunEnv,
30+
cwd: dir,
31+
stderr: "pipe",
32+
stdout: "pipe",
33+
});
34+
35+
const [stdout, stderr, exitCode] = await Promise.all([
36+
proc.stdout.text(),
37+
proc.stderr.text(),
38+
proc.exited,
39+
]);
40+
41+
const duration = performance.now() - start;
42+
43+
expect(exitCode).toBe(0);
44+
expect(stderr).not.toContain("panic");
45+
console.log(`Small tree (10 packages): ${duration.toFixed(2)}ms`);
46+
}, 30000);
47+
48+
test("benchmark: medium dependency tree (50 packages)", async () => {
49+
const packageJson = {
50+
name: "bench-medium",
51+
dependencies: {}
52+
};
53+
54+
// Create 50 packages with linear dependencies
55+
const files = { "package.json": JSON.stringify(packageJson) };
56+
57+
for (let i = 0; i < 50; i++) {
58+
const deps = i < 49 ? { [`pkg-${i + 1}`]: `file:./pkg-${i + 1}` } : {};
59+
files[`pkg-${i}/package.json`] = JSON.stringify({
60+
name: `pkg-${i}`,
61+
dependencies: deps
62+
});
63+
packageJson.dependencies[`pkg-${i}`] = `file:./pkg-${i}`;
64+
}
65+
66+
const dir = tempDirWithFiles("bench-medium", files);
67+
68+
const start = performance.now();
69+
70+
await using proc = Bun.spawn({
71+
cmd: [bunExe(), "install"],
72+
env: bunEnv,
73+
cwd: dir,
74+
stderr: "pipe",
75+
stdout: "pipe",
76+
});
77+
78+
const [stdout, stderr, exitCode] = await Promise.all([
79+
proc.stdout.text(),
80+
proc.stderr.text(),
81+
proc.exited,
82+
]);
83+
84+
const duration = performance.now() - start;
85+
86+
expect(exitCode).toBe(0);
87+
expect(stderr).not.toContain("panic");
88+
console.log(`Medium tree (50 packages): ${duration.toFixed(2)}ms`);
89+
}, 30000);
90+
91+
test("benchmark: wide dependency tree (20 packages, each depends on 5 others)", async () => {
92+
const packageJson = {
93+
name: "bench-wide",
94+
dependencies: {}
95+
};
96+
97+
// Create 20 packages where each depends on 5 others (wide tree)
98+
const files = { "package.json": JSON.stringify(packageJson) };
99+
100+
for (let i = 0; i < 20; i++) {
101+
const deps = {};
102+
// Each package depends on the next 5 packages (cyclically)
103+
for (let j = 1; j <= 5; j++) {
104+
const depIndex = (i + j) % 20;
105+
deps[`pkg-${depIndex}`] = `file:./pkg-${depIndex}`;
106+
}
107+
108+
files[`pkg-${i}/package.json`] = JSON.stringify({
109+
name: `pkg-${i}`,
110+
dependencies: deps
111+
});
112+
packageJson.dependencies[`pkg-${i}`] = `file:./pkg-${i}`;
113+
}
114+
115+
const dir = tempDirWithFiles("bench-wide", files);
116+
117+
const start = performance.now();
118+
119+
await using proc = Bun.spawn({
120+
cmd: [bunExe(), "install"],
121+
env: bunEnv,
122+
cwd: dir,
123+
stderr: "pipe",
124+
stdout: "pipe",
125+
});
126+
127+
const [stdout, stderr, exitCode] = await Promise.all([
128+
proc.stdout.text(),
129+
proc.stderr.text(),
130+
proc.exited,
131+
]);
132+
133+
const duration = performance.now() - start;
134+
135+
expect(exitCode).toBe(0);
136+
expect(stderr).not.toContain("panic");
137+
console.log(`Wide tree (20x5 deps): ${duration.toFixed(2)}ms`);
138+
}, 30000);
139+
140+
test("benchmark: complex dependency tree with multiple cycles", async () => {
141+
const packageJson = {
142+
name: "bench-complex",
143+
dependencies: {}
144+
};
145+
146+
// Create a complex dependency structure with multiple cycles
147+
const files = { "package.json": JSON.stringify(packageJson) };
148+
149+
// Create 15 packages with complex interdependencies
150+
const depStructure = {
151+
0: [1, 2, 3], // pkg-0 -> pkg-1, pkg-2, pkg-3
152+
1: [4, 5], // pkg-1 -> pkg-4, pkg-5
153+
2: [6, 7], // pkg-2 -> pkg-6, pkg-7
154+
3: [8, 9], // pkg-3 -> pkg-8, pkg-9
155+
4: [10, 0], // pkg-4 -> pkg-10, pkg-0 (cycle)
156+
5: [11, 1], // pkg-5 -> pkg-11, pkg-1 (cycle)
157+
6: [12, 2], // pkg-6 -> pkg-12, pkg-2 (cycle)
158+
7: [13, 3], // pkg-7 -> pkg-13, pkg-3 (cycle)
159+
8: [14, 4], // pkg-8 -> pkg-14, pkg-4
160+
9: [0, 5], // pkg-9 -> pkg-0, pkg-5 (cycle)
161+
10: [6, 7], // pkg-10 -> pkg-6, pkg-7
162+
11: [8, 9], // pkg-11 -> pkg-8, pkg-9
163+
12: [10, 11], // pkg-12 -> pkg-10, pkg-11
164+
13: [12, 4], // pkg-13 -> pkg-12, pkg-4
165+
14: [13, 5], // pkg-14 -> pkg-13, pkg-5
166+
};
167+
168+
for (let i = 0; i < 15; i++) {
169+
const deps = {};
170+
const depIndices = depStructure[i] || [];
171+
172+
for (const depIndex of depIndices) {
173+
deps[`pkg-${depIndex}`] = `file:./pkg-${depIndex}`;
174+
}
175+
176+
files[`pkg-${i}/package.json`] = JSON.stringify({
177+
name: `pkg-${i}`,
178+
dependencies: deps
179+
});
180+
packageJson.dependencies[`pkg-${i}`] = `file:./pkg-${i}`;
181+
}
182+
183+
const dir = tempDirWithFiles("bench-complex", files);
184+
185+
const start = performance.now();
186+
187+
await using proc = Bun.spawn({
188+
cmd: [bunExe(), "install"],
189+
env: bunEnv,
190+
cwd: dir,
191+
stderr: "pipe",
192+
stdout: "pipe",
193+
});
194+
195+
const [stdout, stderr, exitCode] = await Promise.all([
196+
proc.stdout.text(),
197+
proc.stderr.text(),
198+
proc.exited,
199+
]);
200+
201+
const duration = performance.now() - start;
202+
203+
expect(exitCode).toBe(0);
204+
expect(stderr).not.toContain("panic");
205+
console.log(`Complex tree (15 packages, multiple cycles): ${duration.toFixed(2)}ms`);
206+
}, 30000);
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { test, expect } from "bun:test";
2+
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
3+
4+
// Simple performance test with common patterns
5+
test("benchmark: React-like dependency pattern", async () => {
6+
// Simulate a typical React project structure
7+
const dir = tempDirWithFiles("react-bench", {
8+
"package.json": JSON.stringify({
9+
name: "react-bench",
10+
dependencies: {
11+
"comp-a": "file:./comp-a",
12+
"comp-b": "file:./comp-b",
13+
"comp-c": "file:./comp-c",
14+
"shared": "file:./shared"
15+
}
16+
}),
17+
"comp-a/package.json": JSON.stringify({
18+
name: "comp-a",
19+
dependencies: {
20+
"shared": "file:../shared",
21+
"react": "^18.0.0"
22+
}
23+
}),
24+
"comp-b/package.json": JSON.stringify({
25+
name: "comp-b",
26+
dependencies: {
27+
"shared": "file:../shared",
28+
"comp-a": "file:../comp-a",
29+
"react": "^18.0.0"
30+
}
31+
}),
32+
"comp-c/package.json": JSON.stringify({
33+
name: "comp-c",
34+
dependencies: {
35+
"shared": "file:../shared",
36+
"comp-b": "file:../comp-b",
37+
"react": "^18.0.0"
38+
}
39+
}),
40+
"shared/package.json": JSON.stringify({
41+
name: "shared",
42+
dependencies: {
43+
"lodash": "^4.17.21"
44+
}
45+
})
46+
});
47+
48+
const times = [];
49+
50+
// Run 3 times and take average
51+
for (let i = 0; i < 3; i++) {
52+
// Clean lockfile
53+
await Bun.spawn({
54+
cmd: ["rm", "-f", "bun.lock"],
55+
cwd: dir,
56+
}).exited;
57+
58+
const start = performance.now();
59+
60+
await using proc = Bun.spawn({
61+
cmd: [bunExe(), "install"],
62+
env: bunEnv,
63+
cwd: dir,
64+
stderr: "pipe",
65+
stdout: "pipe",
66+
});
67+
68+
const [stdout, stderr, exitCode] = await Promise.all([
69+
proc.stdout.text(),
70+
proc.stderr.text(),
71+
proc.exited,
72+
]);
73+
74+
const duration = performance.now() - start;
75+
times.push(duration);
76+
77+
expect(exitCode).toBe(0);
78+
expect(stderr).not.toContain("panic");
79+
}
80+
81+
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
82+
console.log(`React-like pattern (3 runs avg): ${avgTime.toFixed(2)}ms`);
83+
console.log(`Individual times: ${times.map(t => t.toFixed(2)).join(', ')}ms`);
84+
}, 60000);
85+
86+
test("benchmark: No circular dependencies baseline", async () => {
87+
// Simple linear dependency chain (no cycles)
88+
const dir = tempDirWithFiles("linear-bench", {
89+
"package.json": JSON.stringify({
90+
name: "linear-bench",
91+
dependencies: {
92+
"pkg-1": "file:./pkg-1"
93+
}
94+
}),
95+
"pkg-1/package.json": JSON.stringify({
96+
name: "pkg-1",
97+
dependencies: {
98+
"pkg-2": "file:../pkg-2"
99+
}
100+
}),
101+
"pkg-2/package.json": JSON.stringify({
102+
name: "pkg-2",
103+
dependencies: {
104+
"pkg-3": "file:../pkg-3"
105+
}
106+
}),
107+
"pkg-3/package.json": JSON.stringify({
108+
name: "pkg-3",
109+
dependencies: {
110+
"lodash": "^4.17.21"
111+
}
112+
})
113+
});
114+
115+
const times = [];
116+
117+
// Run 5 times for good statistical significance
118+
for (let i = 0; i < 5; i++) {
119+
// Clean lockfile
120+
await Bun.spawn({
121+
cmd: ["rm", "-f", "bun.lock"],
122+
cwd: dir,
123+
}).exited;
124+
125+
const start = performance.now();
126+
127+
await using proc = Bun.spawn({
128+
cmd: [bunExe(), "install"],
129+
env: bunEnv,
130+
cwd: dir,
131+
stderr: "pipe",
132+
stdout: "pipe",
133+
});
134+
135+
const [stdout, stderr, exitCode] = await Promise.all([
136+
proc.stdout.text(),
137+
proc.stderr.text(),
138+
proc.exited,
139+
]);
140+
141+
const duration = performance.now() - start;
142+
times.push(duration);
143+
144+
expect(exitCode).toBe(0);
145+
expect(stderr).not.toContain("panic");
146+
}
147+
148+
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
149+
const minTime = Math.min(...times);
150+
const maxTime = Math.max(...times);
151+
152+
console.log(`Linear dependencies (5 runs avg): ${avgTime.toFixed(2)}ms`);
153+
console.log(`Range: ${minTime.toFixed(2)}ms - ${maxTime.toFixed(2)}ms`);
154+
}, 60000);

0 commit comments

Comments
 (0)