@@ -6,6 +6,37 @@ const fs = require('node:fs')
66const path = require ( 'node:path' )
77const process = require ( 'node:process' )
88
9+ /** 匹配换行符(兼容 CRLF) */
10+ const CRLF_RE = / \r ? \n /
11+ /** 替换换行为转义表示 */
12+ const NEWLINE_REPLACE_RE = / \n / g
13+ /** 匹配 rollback 阶段(不区分大小写) */
14+ const ROLLBACK_RE = / r o l l b a c k / i
15+ /** 按管道符分隔 token */
16+ const TOKEN_SEPARATOR_RE = / \s + \| \s + /
17+ /** 匹配截断的 bg hex / 未闭合 bg / 未闭合 px token(多行) */
18+ const BG_PX_FALLBACK_RE = / \b b g - \s + \[ # ? [ 0 - 9 a - f A - F ] { 3 , 8 } \] ? | \b b g - \[ [ ^ \] ] * $ | \b p x - \[ [ ^ \] ] * $ / m
19+ /** 截断的 bg hex token */
20+ const TRUNCATED_BG_HEX_RE = / \b b g - \s + \[ # ? [ 0 - 9 a - f A - F ] { 3 , 8 } \] ? / g
21+ /** 未闭合 bg token */
22+ const UNTERMINATED_BG_RE = / \b b g - \[ [ ^ \] ] * $ / gm
23+ /** 未闭合 px token */
24+ const UNTERMINATED_PX_RE = / \b p x - \[ [ ^ \] ] * $ / gm
25+ /** bg token 内含空白 */
26+ const BG_WHITESPACE_INSIDE_RE = / \b b g - \[ [ ^ \] \s ] * \s [ ^ \] \s ] * \] / g
27+ /** px token 内含空白 */
28+ const PX_WHITESPACE_INSIDE_RE = / \b p x - \[ [ ^ \] \s ] * \s [ ^ \] \s ] * \] / g
29+ /** 缓存失效相关关键词 */
30+ const CACHE_INVALIDATION_RE = / i n v a l i d a t i o n | c o n t e x t - n o t - f o u n d | c a c h e /
31+ /** 文件系统竞态相关关键词 */
32+ const FS_RACE_RE = / e n o e n t | e p e r m | e b u s y | e a c c e s | c r l f | l f | r e n a m e | p a t h /
33+ /** 进程/超时相关关键词 */
34+ const PROCESS_TIMEOUT_RE = / t i m e o u t | e x c e e d e d | w a t c h p r o c e s s e x i t e d | s i g k i l l | f a t a l | k i l l e d /
35+ /** 匹配 mutation kind */
36+ const MUTATION_KIND_RE = / m u t a t i o n = ( t e m p l a t e | s c r i p t | s t y l e ) / g
37+ /** 匹配 round 名称 */
38+ const ROUND_NAME_RE = / r o u n d = ( [ a - z 0 - 9 - ] + ) / g
39+
940const ROOT_DIR = path . resolve ( process . cwd ( ) , 'e2e/benchmark/e2e-watch-hmr' )
1041const SNAPSHOTS_DIR = path . join ( ROOT_DIR , 'snapshots' )
1142const FAILURES_DIR = path . join ( ROOT_DIR , 'failures' )
@@ -35,7 +66,7 @@ function listFilesSafe(dir, filter) {
3566
3667function parseKvContent ( content ) {
3768 const out = { }
38- for ( const line of content . split ( / \r ? \n / ) ) {
69+ for ( const line of content . split ( CRLF_RE ) ) {
3970 const index = line . indexOf ( '=' )
4071 if ( index <= 0 ) {
4172 continue
@@ -61,10 +92,10 @@ function summarizeDiff(before, after) {
6192 }
6293 const beforeContext = before
6394 . slice ( Math . max ( 0 , index - 40 ) , Math . min ( before . length , index + 120 ) )
64- . replace ( / \n / g , '\\n' )
95+ . replace ( NEWLINE_REPLACE_RE , '\\n' )
6596 const afterContext = after
6697 . slice ( Math . max ( 0 , index - 40 ) , Math . min ( after . length , index + 120 ) )
67- . replace ( / \n / g , '\\n' )
98+ . replace ( NEWLINE_REPLACE_RE , '\\n' )
6899 return `firstDiff=${ index } , len=${ before . length } ->${ after . length } \n before=${ beforeContext } \n after=${ afterContext } `
69100}
70101
@@ -99,7 +130,7 @@ function resolvePhase(rawPhase, errorText) {
99130 if ( rawPhase === 'add' || rawPhase === 'modify' ) {
100131 return 'hot-update'
101132 }
102- if ( / r o l l b a c k / i . test ( errorText ) ) {
133+ if ( ROLLBACK_RE . test ( errorText ) ) {
103134 return 'rollback'
104135 }
105136 return 'hot-update'
@@ -118,13 +149,13 @@ function pickPrimaryFailure(failureLogs, failureSnapshots) {
118149 phase : resolvePhase ( kv . phase || '' , kv . error || '' ) ,
119150 project : kv . project || 'unknown' ,
120151 sourceFile : kv . source || 'unknown' ,
121- tokens : ( kv . tokens || '' ) . split ( / \s + \| \s + / ) . filter ( Boolean ) ,
152+ tokens : ( kv . tokens || '' ) . split ( TOKEN_SEPARATOR_RE ) . filter ( Boolean ) ,
122153 error : kv . error || '' ,
123154 }
124155 }
125156
126157 if ( failureSnapshots . length > 0 ) {
127- const item = failureSnapshots [ failureSnapshots . length - 1 ]
158+ const item = failureSnapshots . at ( - 1 )
128159 return {
129160 source : 'snapshot' ,
130161 file : item . dir ,
@@ -164,7 +195,7 @@ function pickFailureSnapshot(primary, snapshots) {
164195 && item . meta . roundName === primary . round
165196 && item . meta . phase === primary . phaseRaw ,
166197 )
167- return exact || candidates [ candidates . length - 1 ]
198+ return exact || candidates . at ( - 1 )
168199}
169200
170201function pickMetricFromReport ( report , primary ) {
@@ -218,9 +249,7 @@ function pickSnippet(source, probes) {
218249 }
219250
220251 if ( hitIndex < 0 ) {
221- const fallback = source . match (
222- / \b b g - \s + \[ # ? [ 0 - 9 a - f A - F ] { 3 , 8 } \] ? | \b b g - \[ [ ^ \] ] * $ | \b p x - \[ [ ^ \] ] * $ / m,
223- )
252+ const fallback = source . match ( BG_PX_FALLBACK_RE )
224253 if ( fallback ?. index != null ) {
225254 hitIndex = fallback . index
226255 }
@@ -237,11 +266,11 @@ function pickSnippet(source, probes) {
237266
238267function detectTokenAnomalies ( source ) {
239268 const patterns = [
240- { name : 'truncated-bg-hex' , re : / \b b g - \s + \[ # ? [ 0 - 9 a - f A - F ] { 3 , 8 } \] ? / g } ,
241- { name : 'unterminated-bg-token' , re : / \b b g - \[ [ ^ \] ] * $ / gm } ,
242- { name : 'unterminated-px-token' , re : / \b p x - \[ [ ^ \] ] * $ / gm } ,
243- { name : 'bg-whitespace-inside-token' , re : / \b b g - \[ [ ^ \] \s ] * \s [ ^ \] \s ] * \] / g } ,
244- { name : 'px-whitespace-inside-token' , re : / \b p x - \[ [ ^ \] \s ] * \s [ ^ \] \s ] * \] / g } ,
269+ { name : 'truncated-bg-hex' , re : TRUNCATED_BG_HEX_RE } ,
270+ { name : 'unterminated-bg-token' , re : UNTERMINATED_BG_RE } ,
271+ { name : 'unterminated-px-token' , re : UNTERMINATED_PX_RE } ,
272+ { name : 'bg-whitespace-inside-token' , re : BG_WHITESPACE_INSIDE_RE } ,
273+ { name : 'px-whitespace-inside-token' , re : PX_WHITESPACE_INSIDE_RE } ,
245274 ]
246275
247276 const findings = [ ]
@@ -267,7 +296,7 @@ function scoreAttribution(primary, evidence) {
267296 [ '进程/超时问题' , 0 ] ,
268297 ] )
269298
270- if ( / i n v a l i d a t i o n | c o n t e x t - n o t - f o u n d | c a c h e / . test ( text ) ) {
299+ if ( CACHE_INVALIDATION_RE . test ( text ) ) {
271300 scores . set (
272301 'cache key/invalidation' ,
273302 scores . get ( 'cache key/invalidation' ) + 2 ,
@@ -286,13 +315,13 @@ function scoreAttribution(primary, evidence) {
286315 scores . get ( 'transform emit mismatch' ) + 3 ,
287316 )
288317 }
289- if ( / e n o e n t | e p e r m | e b u s y | e a c c e s | c r l f | l f | r e n a m e | p a t h / . test ( text ) ) {
318+ if ( FS_RACE_RE . test ( text ) ) {
290319 scores . set (
291320 '文件系统竞态/路径换行差异' ,
292321 scores . get ( '文件系统竞态/路径换行差异' ) + 3 ,
293322 )
294323 }
295- if ( / t i m e o u t | e x c e e d e d | w a t c h p r o c e s s e x i t e d | s i g k i l l | f a t a l | k i l l e d / . test ( text ) ) {
324+ if ( PROCESS_TIMEOUT_RE . test ( text ) ) {
296325 scores . set ( '进程/超时问题' , scores . get ( '进程/超时问题' ) + 3 )
297326 }
298327 if ( primary . phase === 'rollback' ) {
@@ -393,7 +422,7 @@ function generateDiffSummary() {
393422 lines . push ( `- ${ path . basename ( file ) } ` )
394423 const content = readUtf8 ( file ) . trim ( )
395424 if ( content ) {
396- for ( const line of content . split ( / \r ? \n / ) ) {
425+ for ( const line of content . split ( CRLF_RE ) ) {
397426 lines . push ( ` ${ line } ` )
398427 }
399428 }
@@ -619,8 +648,8 @@ function publishJobSummary() {
619648 for ( const log of logs ) {
620649 const content = fs . readFileSync ( path . join ( FAILURES_DIR , log ) , 'utf8' )
621650 const kindMatches
622- = content . match ( / m u t a t i o n = ( t e m p l a t e | s c r i p t | s t y l e ) / g ) || [ ]
623- const roundMatches = content . match ( / r o u n d = ( [ a - z 0 - 9 - ] + ) / g ) || [ ]
651+ = content . match ( MUTATION_KIND_RE ) || [ ]
652+ const roundMatches = content . match ( ROUND_NAME_RE ) || [ ]
624653 for ( const matched of kindMatches ) {
625654 failedKinds . add ( matched . replace ( 'mutation=' , '' ) )
626655 }
0 commit comments