@@ -55,15 +55,40 @@ const HEAVY_SHARDS = {
5555 'schedule-4' : { dir : 'plugins/schedule' , shard : '4/4' } ,
5656 // development ~49s -> single shard is fine
5757 development : { dir : 'plugins/development' } ,
58+ // Fork-based plugins that dominated rest-* and pushed it >60s on ubuntu.
59+ // Pull them out of rest into their own (sliceable) shards.
60+ 'security-1' : { dir : 'plugins/security' , shard : '1/2' } ,
61+ 'security-2' : { dir : 'plugins/security' , shard : '2/2' } ,
62+ redis : { dir : 'plugins/redis' } ,
63+ multipart : { dir : 'plugins/multipart' } ,
5864} ;
5965
6066const HEAVY_DIRS = new Set ( Object . values ( HEAVY_SHARDS ) . map ( ( v ) => v . dir ) ) ;
6167
62- // `rest` is everything not in a heavy dir, interleaved into N light shards.
63- // The rest tests are dominated by module transform/import (full egg + tegg
64- // decorator stack per file), which barely shrinks with fewer files, so this
65- // needs more slices than raw cpu-sum suggests to hit ≤60s on ubuntu.
66- const REST_SHARD_COUNT = 8 ;
68+ // `rest` is every remaining project dir, balance-packed into N light shards by
69+ // approximate per-dir cpu weight (seconds; from a full local run). Greedy
70+ // largest-first bin-packing keeps the heaviest leftover dirs (onerror,
71+ // logrotator, watcher, tegg/plugin/tegg) on separate shards so no single shard
72+ // dominates. Unlisted dirs default to a small weight.
73+ const REST_SHARD_COUNT = 6 ;
74+ const REST_DIR_WEIGHTS = {
75+ 'plugins/watcher' : 9.7 ,
76+ 'packages/core' : 7.6 ,
77+ 'plugins/logrotator' : 7.7 ,
78+ 'plugins/onerror' : 7.3 ,
79+ 'packages/logger' : 6.9 ,
80+ 'tegg/core/dynamic-inject' : 6.8 ,
81+ 'plugins/session' : 5.3 ,
82+ 'plugins/view-nunjucks' : 3.2 ,
83+ 'packages/utils' : 2.8 ,
84+ 'tools/create-egg' : 2.7 ,
85+ 'tegg/standalone/standalone' : 2.3 ,
86+ 'tegg/plugin/controller' : 2.1 ,
87+ 'packages/koa' : 2.0 ,
88+ 'plugins/view' : 1.8 ,
89+ 'tegg/plugin/tegg' : 11.2 ,
90+ } ;
91+ const REST_DEFAULT_WEIGHT = 0.5 ;
6792
6893// The vitest projects globs (see root vitest.config.ts):
6994// packages/*, plugins/*, tools/create-egg, tegg/core/*, tegg/plugin/*, tegg/standalone/*
@@ -123,6 +148,28 @@ const baseArgs = [
123148 '20000' ,
124149] ;
125150
151+ // Greedy largest-first bin-packing of the rest dirs into REST_SHARD_COUNT bins
152+ // by approximate cpu weight, so the heaviest leftover dirs land on separate
153+ // shards. Deterministic: sort by (weight desc, name) then assign each dir to
154+ // the currently-lightest bin.
155+ function packRestDirs ( ) {
156+ const dirs = listProjectDirs ( ) . filter ( ( d ) => ! HEAVY_DIRS . has ( d ) ) ;
157+ dirs . sort ( ( a , b ) => {
158+ const wa = REST_DIR_WEIGHTS [ a ] ?? REST_DEFAULT_WEIGHT ;
159+ const wb = REST_DIR_WEIGHTS [ b ] ?? REST_DEFAULT_WEIGHT ;
160+ return wb - wa || ( a < b ? - 1 : 1 ) ;
161+ } ) ;
162+ const bins = Array . from ( { length : REST_SHARD_COUNT } , ( ) => ( { total : 0 , dirs : [ ] } ) ) ;
163+ for ( const d of dirs ) {
164+ const w = REST_DIR_WEIGHTS [ d ] ?? REST_DEFAULT_WEIGHT ;
165+ let min = bins [ 0 ] ;
166+ for ( const b of bins ) if ( b . total < min . total ) min = b ;
167+ min . total += w ;
168+ min . dirs . push ( d ) ;
169+ }
170+ return bins ;
171+ }
172+
126173const restMatch = / ^ r e s t - ( \d + ) $ / . exec ( shardName ) ;
127174
128175let targetArgs = [ ] ;
@@ -132,16 +179,14 @@ if (shardName === 'all') {
132179 // Single combined light shard (local fallback).
133180 targetArgs = listProjectDirs ( ) . filter ( ( d ) => ! HEAVY_DIRS . has ( d ) ) ;
134181} else if ( restMatch ) {
135- // Interleave the remaining dirs into REST_SHARD_COUNT shards so the heavier
136- // tegg dirs spread evenly across them. No --maxWorkers cap: fast in-process
137- // tests benefit from full parallelism.
182+ // Weight-balanced bin for this rest shard. No --maxWorkers cap: fast
183+ // in-process tests benefit from full parallelism.
138184 const idx = Number ( restMatch [ 1 ] ) - 1 ;
139185 if ( idx < 0 || idx >= REST_SHARD_COUNT ) {
140186 console . error ( `Unknown rest shard: ${ shardName } (valid: ${ restShardNames ( ) . join ( ', ' ) } )` ) ;
141187 process . exit ( 2 ) ;
142188 }
143- const restDirs = listProjectDirs ( ) . filter ( ( d ) => ! HEAVY_DIRS . has ( d ) ) ;
144- targetArgs = restDirs . filter ( ( _ , i ) => i % REST_SHARD_COUNT === idx ) ;
189+ targetArgs = packRestDirs ( ) [ idx ] . dirs ;
145190} else if ( HEAVY_SHARDS [ shardName ] ) {
146191 const entry = HEAVY_SHARDS [ shardName ] ;
147192 targetArgs = [ entry . dir , '--maxWorkers' , HEAVY_MAX_WORKERS ] ;
0 commit comments