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 ) ;
0 commit comments