@@ -9,7 +9,6 @@ import fs = require("fs");
9
9
import path = require( "path" ) ;
10
10
import mdEscape = require( "markdown-escape" ) ;
11
11
import randomSeed = require( "random-seed" ) ;
12
- import { getErrorMessageFromStack , getHash , getHashForStack } from "./utils/hashStackTrace" ;
13
12
14
13
interface Params {
15
14
/**
@@ -85,31 +84,9 @@ export type RepoStatus =
85
84
| "Detected no interesting changes"
86
85
;
87
86
88
- interface TSServerResult {
89
- oldServerFailed : boolean ;
90
- oldSpawnResult ?: SpawnResult ;
91
- newServerFailed : boolean ;
92
- newSpawnResult : SpawnResult ;
93
- replayScriptPath : string ;
94
- installCommands : ip . InstallCommand [ ] ;
95
- }
96
-
97
- interface Summary {
98
- tsServerResult : TSServerResult ;
99
- repo : git . Repo ;
100
- oldTsEntrypointPath : string ;
101
- rawErrorArtifactPath : string ;
102
- replayScriptPath : string ;
103
- repoDir : string ;
104
- downloadDir : string ;
105
- replayScriptArtifactPath : string ;
106
- replayScriptName : string ;
107
- }
108
-
109
87
interface RepoResult {
110
88
readonly status : RepoStatus ;
111
89
readonly summary ?: string ;
112
- readonly tsServerResult ?: TSServerResult ;
113
90
readonly replayScriptPath ?: string ;
114
91
readonly rawErrorPath ?: string ;
115
92
}
@@ -226,6 +203,8 @@ async function getTsServerRepoResult(
226
203
? ( await installPackagesAndGetCommands ( repo , downloadDir , repoDir , monorepoPackages , /*cleanOnFailure*/ true , diagnosticOutput ) ) !
227
204
: [ ] ;
228
205
206
+ const isUserTestRepo = ! repo . url ;
207
+
229
208
const replayScriptName = path . basename ( replayScriptArtifactPath ) ;
230
209
const replayScriptPath = path . join ( downloadDir , replayScriptName ) ;
231
210
@@ -323,173 +302,110 @@ async function getTsServerRepoResult(
323
302
}
324
303
}
325
304
326
- const tsServerResult = {
327
- oldServerFailed,
328
- oldSpawnResult,
329
- newServerFailed,
330
- newSpawnResult,
331
- replayScriptPath,
332
- installCommands,
333
- } ;
334
-
335
- if ( oldServerFailed && ! newServerFailed ) {
336
- return { status : "Detected interesting changes" , tsServerResult }
337
- }
338
- if ( ! newServerFailed ) {
339
- return { status : "Detected no interesting changes" } ;
340
- }
305
+ const owner = repo . owner ? `${ mdEscape ( repo . owner ) } /` : "" ;
306
+ const url = repo . url ? `(${ repo . url } )` : "" ;
341
307
342
- return { status : "Detected interesting changes" , tsServerResult, replayScriptPath, rawErrorPath } ;
343
- }
344
- catch ( err ) {
345
- reportError ( err , `Error running tsserver on ${ repo . url ?? repo . name } ` ) ;
346
- return { status : "Unknown failure" } ;
347
- }
348
- finally {
349
- console . log ( `Done ${ repo . url ?? repo . name } ` ) ;
350
- logStepTime ( diagnosticOutput , repo , "language service" , lsStart ) ;
351
- }
352
- }
308
+ let summary = `## [${ owner } ${ mdEscape ( repo . name ) } ]${ url } \n` ;
353
309
354
- function groupErrors ( summaries : Summary [ ] ) {
355
- const groupedOldErrors = new Map < string , Summary [ ] > ( ) ;
356
- const groupedNewErrors = new Map < string , Summary [ ] > ( ) ;
357
- let group : Map < string , Summary [ ] > ;
358
- let error : ServerHarnessOutput | string ;
359
- for ( const summary of summaries ) {
360
- if ( summary . tsServerResult . newServerFailed ) {
361
- // Group new errors
362
- error = parseServerHarnessOutput ( summary . tsServerResult . newSpawnResult ! . stdout ) ;
363
- group = groupedNewErrors ;
364
- }
365
- else if ( summary . tsServerResult . oldServerFailed ) {
366
- // Group old errors
367
- const { oldSpawnResult } = summary . tsServerResult ;
368
- error = oldSpawnResult ?. stdout
369
- ? parseServerHarnessOutput ( oldSpawnResult . stdout )
310
+ if ( oldServerFailed ) {
311
+ const oldServerError = oldSpawnResult ?. stdout
312
+ ? prettyPrintServerHarnessOutput ( oldSpawnResult . stdout , /*filter*/ true )
370
313
: `Timed out after ${ executionTimeout } ms` ;
371
-
372
- group = groupedOldErrors ;
373
- }
374
- else {
375
- continue ;
376
- }
377
-
378
- const key = typeof error === "string" ? getHash ( [ error ] ) : getHashForStack ( error . message ) ;
379
- const value = group . get ( key ) ?? [ ] ;
380
- value . push ( summary ) ;
381
- group . set ( key , value ) ;
382
- }
383
-
384
- return { groupedOldErrors, groupedNewErrors }
385
- }
386
-
387
- function getErrorMessage ( output : string ) : string {
388
- const error = parseServerHarnessOutput ( output ) ;
389
-
390
- return typeof error === "string" ? error : getErrorMessageFromStack ( error . message ) ;
391
- }
392
-
393
- function createOldErrorSummary ( summaries : Summary [ ] ) : string {
394
- const { oldSpawnResult } = summaries [ 0 ] . tsServerResult ;
395
-
396
- const oldServerError = oldSpawnResult ?. stdout
397
- ? prettyPrintServerHarnessOutput ( oldSpawnResult . stdout , /*filter*/ true )
398
- : `Timed out after ${ executionTimeout } ms` ;
399
-
400
- const errorMessage = oldSpawnResult ?. stdout ? getErrorMessage ( oldSpawnResult . stdout ) : oldServerError ;
401
-
402
- let text = `
314
+ summary += `
403
315
<details>
404
- <summary>${ errorMessage } </summary>
316
+ <summary>:warning: Note that ${ path . basename ( path . dirname ( path . dirname ( oldTsServerPath ) ) ) } had errors :warning: </summary>
405
317
406
318
\`\`\`
407
319
${ oldServerError }
408
320
\`\`\`
409
321
410
- <h4>Repos no longer reporting the error</h4>
411
- <ul>
412
- ` ;
413
-
414
- for ( const summary of summaries ) {
415
- const owner = summary . repo . owner ? `${ mdEscape ( summary . repo . owner ) } /` : "" ;
416
- const url = summary . repo . url ?? "" ;
417
-
418
- text += `<li><a href="${ url } ">${ owner + mdEscape ( summary . repo . name ) } </a></li>\n`
419
- }
420
-
421
- text += `
422
- </ul>
423
322
</details>
424
- ` ;
425
323
426
- return text ;
427
- }
324
+ ` ;
325
+ if ( ! newServerFailed ) {
326
+ summary += `
327
+ :tada: New server no longer has errors :tada:
328
+ ` ;
329
+ return { status : "Detected interesting changes" , summary }
330
+ }
331
+ }
332
+
333
+ if ( ! newServerFailed ) {
334
+ return { status : "Detected no interesting changes" } ;
335
+ }
428
336
429
- async function createNewErrorSummaryAsync ( summaries : Summary [ ] ) : Promise < string > {
430
- let text = `<h2>${ getErrorMessage ( summaries [ 0 ] . tsServerResult . newSpawnResult . stdout ) } </h2>
337
+ summary += `
431
338
432
339
\`\`\`
433
- ${ prettyPrintServerHarnessOutput ( summaries [ 0 ] . tsServerResult . newSpawnResult . stdout , /*filter*/ true ) }
340
+ ${ prettyPrintServerHarnessOutput ( newSpawnResult . stdout , /*filter*/ true ) }
434
341
\`\`\`
342
+ That is a filtered view of the text. To see the raw error text, go to ${ rawErrorArtifactPath } </code> in the <a href="${ artifactFolderUrlPlaceholder } ">artifact folder</a></li>\n
343
+ ` ;
435
344
436
- <h4>Affected repos</h4>` ;
437
-
438
- for ( const summary of summaries ) {
439
- const owner = summary . repo . owner ? `${ mdEscape ( summary . repo . owner ) } /` : "" ;
440
- const url = summary . repo . url ?? "" ;
441
-
442
- text += `
345
+ summary += `
443
346
<details>
444
- <summary><a href="${ url } ">${ owner + mdEscape ( summary . repo . name ) } </a></summary>
445
- Raw error text: <code>${ summary . rawErrorArtifactPath } </code> in the <a href="${ artifactFolderUrlPlaceholder } ">artifact folder</a>
446
- <h4>Last few requests</h4>
347
+ <summary><h3>Last few requests</h3></summary>
447
348
448
349
\`\`\`json
449
- ${ fs . readFileSync ( summary . replayScriptPath , { encoding : "utf-8" } ) . split ( / \r ? \n / ) . slice ( - 5 ) . join ( "\n" ) }
350
+ ${ fs . readFileSync ( replayScriptPath , { encoding : "utf-8" } ) . split ( / \r ? \n / ) . slice ( - 5 ) . join ( "\n" ) }
450
351
\`\`\`
451
352
452
- <h4>Repro steps</h4>
353
+ </details>
354
+
355
+ ` ;
356
+
357
+ // Markdown doesn't seem to support a <details> list item, so this chunk is in HTML
358
+
359
+ summary += `<details>
360
+ <summary><h3>Repro Steps</h3></summary>
453
361
<ol>
454
362
` ;
455
- // No url means is user test repo
456
- if ( ! summary . repo . url ) {
457
- text += `<li>Download user test <code>${ summary . repo . name } </code></li>\n` ;
363
+ if ( isUserTestRepo ) {
364
+ summary += `<li>Download user test <code>${ repo . name } </code></li>\n` ;
458
365
}
459
366
else {
460
- text += `<li><code>git clone ${ summary . repo . url } --recurse-submodules</code></li>\n` ;
367
+ summary += `<li><code>git clone ${ repo . url ! } --recurse-submodules</code></li>\n` ;
461
368
462
369
try {
463
370
console . log ( "Extracting commit SHA for repro steps" ) ;
464
- const commit = ( await execAsync ( summary . repoDir , `git rev-parse @` ) ) . trim ( ) ;
465
- text += `<li>In dir <code>${ summary . repo . name } </code>, run <code>git reset --hard ${ commit } </code></li>\n` ;
371
+ const commit = ( await execAsync ( repoDir , `git rev-parse @` ) ) . trim ( ) ;
372
+ summary += `<li>In dir <code>${ repo . name } </code>, run <code>git reset --hard ${ commit } </code></li>\n` ;
466
373
}
467
374
catch {
468
375
}
469
376
}
470
377
471
- if ( summary . tsServerResult . installCommands . length > 1 ) {
472
- text += "<li><details><summary>Install packages (exact steps are below, but it might be easier to follow the repo readme)</summary><ol>\n" ;
378
+ if ( installCommands . length > 1 ) {
379
+ summary += "<li><details><summary>Install packages (exact steps are below, but it might be easier to follow the repo readme)</summary><ol>\n" ;
473
380
}
474
- for ( const command of summary . tsServerResult . installCommands ) {
475
- text += ` <li>In dir <code>${ path . relative ( summary . downloadDir , command . directory ) } </code>, run <code>${ command . tool } ${ command . arguments . join ( " " ) } </code></li>\n` ;
381
+ for ( const command of installCommands ) {
382
+ summary += ` <li>In dir <code>${ path . relative ( downloadDir , command . directory ) } </code>, run <code>${ command . tool } ${ command . arguments . join ( " " ) } </code></li>\n` ;
476
383
}
477
- if ( summary . tsServerResult . installCommands . length > 1 ) {
478
- text += "</ol></details>\n" ;
384
+ if ( installCommands . length > 1 ) {
385
+ summary += "</ol></details>\n" ;
479
386
}
480
387
481
388
// The URL of the artifact can be determined via AzDO REST APIs, but not until after the artifact is published
482
- text += `<li>Back in the initial folder, download <code>${ summary . replayScriptArtifactPath } </code> from the <a href="${ artifactFolderUrlPlaceholder } ">artifact folder</a></li>\n` ;
483
- text += `<li><code>npm install --no-save @typescript/server-replay</code></li>\n` ;
484
- text += `<li><code>npx tsreplay ./${ summary . repo . name } ./${ summary . replayScriptName } path/to/tsserver.js</code></li>\n` ;
485
- text += `<li><code>npx tsreplay --help</code> to learn about helpful switches for debugging, logging, etc</li>\n` ;
389
+ summary += `<li>Back in the initial folder, download <code>${ replayScriptArtifactPath } </code> from the <a href="${ artifactFolderUrlPlaceholder } ">artifact folder</a></li>\n` ;
390
+ summary += `<li><code>npm install --no-save @typescript/server-replay</code></li>\n` ;
391
+ summary += `<li><code>npx tsreplay ./${ repo . name } ./${ replayScriptName } path/to/tsserver.js</code></li>\n` ;
392
+ summary += `<li><code>npx tsreplay --help</code> to learn about helpful switches for debugging, logging, etc</li>\n` ;
486
393
487
- text += `</ol>
394
+ summary += `</ol>
488
395
</details>
396
+
489
397
` ;
490
- }
491
398
492
- return text ;
399
+ return { status : "Detected interesting changes" , summary, replayScriptPath, rawErrorPath } ;
400
+ }
401
+ catch ( err ) {
402
+ reportError ( err , `Error running tsserver on ${ repo . url ?? repo . name } ` ) ;
403
+ return { status : "Unknown failure" } ;
404
+ }
405
+ finally {
406
+ console . log ( `Done ${ repo . url ?? repo . name } ` ) ;
407
+ logStepTime ( diagnosticOutput , repo , "language service" , lsStart ) ;
408
+ }
493
409
}
494
410
495
411
// Exported for testing
@@ -742,8 +658,6 @@ export async function mainAsync(params: GitParams | UserParams): Promise<void> {
742
658
743
659
const isPr = params . testType === "user" && ! ! params . prNumber
744
660
745
- var summaries : Summary [ ] = [ ] ;
746
-
747
661
let i = 1 ;
748
662
for ( const repo of repos ) {
749
663
console . log ( `Starting #${ i ++ } / ${ repos . length } : ${ repo . url ?? repo . name } ` ) ;
@@ -758,36 +672,16 @@ export async function mainAsync(params: GitParams | UserParams): Promise<void> {
758
672
: repo . name ;
759
673
const replayScriptFileName = `${ repoPrefix } .${ replayScriptFileNameSuffix } ` ;
760
674
const rawErrorFileName = `${ repoPrefix } .${ rawErrorFileNameSuffix } ` ;
761
-
762
- const rawErrorArtifactPath = path . join ( params . resultDirName , rawErrorFileName ) ;
763
- const replayScriptArtifactPath = path . join ( params . resultDirName , replayScriptFileName ) ;
764
-
765
- const { status, summary, tsServerResult, replayScriptPath, rawErrorPath } = params . entrypoint === "tsc"
675
+ const { status, summary, replayScriptPath, rawErrorPath } = params . entrypoint === "tsc"
766
676
? await getTscRepoResult ( repo , userTestsDir , oldTsEntrypointPath , newTsEntrypointPath , params . buildWithNewWhenOldFails , downloadDir , diagnosticOutput )
767
- : await getTsServerRepoResult ( repo , userTestsDir , oldTsEntrypointPath , newTsEntrypointPath , downloadDir , replayScriptArtifactPath , rawErrorArtifactPath , diagnosticOutput , isPr ) ;
677
+ : await getTsServerRepoResult ( repo , userTestsDir , oldTsEntrypointPath , newTsEntrypointPath , downloadDir , path . join ( params . resultDirName , replayScriptFileName ) , path . join ( params . resultDirName , rawErrorFileName ) , diagnosticOutput , isPr ) ;
768
678
console . log ( `Repo ${ repo . url ?? repo . name } had status "${ status } "` ) ;
769
679
statusCounts [ status ] = ( statusCounts [ status ] ?? 0 ) + 1 ;
770
-
771
680
if ( summary ) {
681
+
772
682
const resultFileName = `${ repoPrefix } .${ resultFileNameSuffix } ` ;
773
683
await fs . promises . writeFile ( path . join ( resultDirPath , resultFileName ) , summary , { encoding : "utf-8" } ) ;
774
- }
775
-
776
- if ( tsServerResult ) {
777
- summaries . push ( {
778
- tsServerResult,
779
- repo,
780
- oldTsEntrypointPath,
781
- rawErrorArtifactPath,
782
- replayScriptPath : path . join ( downloadDir , path . basename ( replayScriptArtifactPath ) ) ,
783
- repoDir : path . join ( downloadDir , repo . name ) ,
784
- downloadDir,
785
- replayScriptArtifactPath,
786
- replayScriptName : path . basename ( replayScriptArtifactPath ) ,
787
- } ) ;
788
- }
789
684
790
- if ( summary || tsServerResult ) {
791
685
// In practice, there will only be a replay script when the entrypoint is tsserver
792
686
// There can be replay steps without a summary, but then they're not interesting
793
687
if ( replayScriptPath ) {
@@ -796,7 +690,9 @@ export async function mainAsync(params: GitParams | UserParams): Promise<void> {
796
690
if ( rawErrorPath ) {
797
691
await fs . promises . copyFile ( rawErrorPath , path . join ( resultDirPath , rawErrorFileName ) ) ;
798
692
}
693
+
799
694
}
695
+
800
696
}
801
697
finally {
802
698
// Throw away the repo so we don't run out of space
@@ -831,25 +727,6 @@ export async function mainAsync(params: GitParams | UserParams): Promise<void> {
831
727
}
832
728
}
833
729
834
- // Group errors and create summary files.
835
- if ( summaries . length > 0 ) {
836
- const { groupedOldErrors, groupedNewErrors } = groupErrors ( summaries ) ;
837
-
838
- for ( let [ key , value ] of groupedOldErrors ) {
839
- const summary = createOldErrorSummary ( value ) ;
840
- const resultFileName = `!${ key } .${ resultFileNameSuffix } ` ; // Exclamation point makes the file to be put first when ordering.
841
-
842
- await fs . promises . writeFile ( path . join ( resultDirPath , resultFileName ) , summary , { encoding : "utf-8" } ) ;
843
- }
844
-
845
- for ( let [ key , value ] of groupedNewErrors ) {
846
- const summary = await createNewErrorSummaryAsync ( value ) ;
847
- const resultFileName = `${ key } .${ resultFileNameSuffix } ` ;
848
-
849
- await fs . promises . writeFile ( path . join ( resultDirPath , resultFileName ) , summary , { encoding : "utf-8" } ) ;
850
- }
851
- }
852
-
853
730
if ( params . tmpfs ) {
854
731
await execAsync ( processCwd , "sudo rm -rf " + downloadDir ) ;
855
732
await execAsync ( processCwd , "sudo rm -rf " + oldTscDirPath ) ;
@@ -1116,4 +993,4 @@ async function downloadTsNpmAsync(cwd: string, version: string, entrypoint: TsEn
1116
993
}
1117
994
1118
995
return { tsEntrypointPath, resolvedVersion } ;
1119
- }
996
+ }
0 commit comments