@@ -27,7 +27,11 @@ import {
27
27
ParallelXCTestOutputParser ,
28
28
XCTestOutputParser ,
29
29
} from "./TestParsers/XCTestOutputParser" ;
30
- import { SwiftTestingOutputParser } from "./TestParsers/SwiftTestingOutputParser" ;
30
+ import {
31
+ SwiftTestingOutputParser ,
32
+ SymbolRenderer ,
33
+ TestSymbol ,
34
+ } from "./TestParsers/SwiftTestingOutputParser" ;
31
35
import { LoggingDebugAdapterTracker } from "../debugger/logTracker" ;
32
36
import { TaskOperation } from "../tasks/TaskQueue" ;
33
37
import { TestXUnitParser } from "./TestXUnitParser" ;
@@ -36,7 +40,12 @@ import { TestRunArguments } from "./TestRunArguments";
36
40
import { TemporaryFolder } from "../utilities/tempFolder" ;
37
41
import { TestClass , runnableTag , upsertTestItem } from "./TestDiscovery" ;
38
42
import { TestCoverage } from "../coverage/LcovResults" ;
39
- import { BuildConfigurationFactory , TestingConfigurationFactory } from "../debugger/buildConfig" ;
43
+ import {
44
+ BuildConfigurationFactory ,
45
+ SwiftTestingBuildAguments ,
46
+ SwiftTestingConfigurationSetup ,
47
+ TestingConfigurationFactory ,
48
+ } from "../debugger/buildConfig" ;
40
49
import { TestKind , isDebugging , isRelease } from "./TestKind" ;
41
50
import { reduceTestItemChildren } from "./TestUtils" ;
42
51
import { CompositeCancellationToken } from "../utilities/cancellation" ;
@@ -67,6 +76,7 @@ export class TestRunProxy {
67
76
private queuedOutput : string [ ] = [ ] ;
68
77
private _testItems : vscode . TestItem [ ] ;
69
78
private iteration : number | undefined ;
79
+ private attachments : { [ key : string ] : string [ ] } = { } ;
70
80
public coverage : TestCoverage ;
71
81
public token : CompositeCancellationToken ;
72
82
@@ -177,6 +187,12 @@ export class TestRunProxy {
177
187
this . addedTestItems . push ( { testClass, parentIndex } ) ;
178
188
} ;
179
189
190
+ public addAttachment = ( testIndex : number , attachment : string ) => {
191
+ const attachments = this . attachments [ testIndex ] ?? [ ] ;
192
+ attachments . push ( attachment ) ;
193
+ this . attachments [ testIndex ] = attachments ;
194
+ } ;
195
+
180
196
public getTestIndex ( id : string , filename ?: string ) : number {
181
197
return this . testItemFinder . getIndex ( id , filename ) ;
182
198
}
@@ -231,6 +247,8 @@ export class TestRunProxy {
231
247
if ( ! this . runStarted ) {
232
248
this . testRunStarted ( ) ;
233
249
}
250
+
251
+ this . reportAttachments ( ) ;
234
252
this . testRun ?. end ( ) ;
235
253
this . testRunCompleteEmitter . fire ( ) ;
236
254
this . token . dispose ( ) ;
@@ -259,6 +277,25 @@ export class TestRunProxy {
259
277
}
260
278
}
261
279
280
+ private reportAttachments ( ) {
281
+ const attachmentKeys = Object . keys ( this . attachments ) ;
282
+ if ( attachmentKeys . length > 0 ) {
283
+ let attachment = "" ;
284
+ const totalAttachments = attachmentKeys . reduce ( ( acc , key ) => {
285
+ const attachments = this . attachments [ key ] ;
286
+ attachment = attachments . length ? attachments [ 0 ] : attachment ;
287
+ return acc + attachments . length ;
288
+ } , 0 ) ;
289
+
290
+ if ( attachment ) {
291
+ attachment = path . dirname ( attachment ) ;
292
+ this . appendOutput (
293
+ `${ SymbolRenderer . eventMessageSymbol ( TestSymbol . attachment ) } ${ SymbolRenderer . ansiEscapeCodePrefix } 90mRecorded ${ totalAttachments } attachment${ totalAttachments === 1 ? "" : "s" } to ${ attachment } ${ SymbolRenderer . resetANSIEscapeCode } `
294
+ ) ;
295
+ }
296
+ }
297
+ }
298
+
262
299
private performAppendOutput (
263
300
testRun : vscode . TestRun ,
264
301
output : string ,
@@ -321,7 +358,8 @@ export class TestRunner {
321
358
: new XCTestOutputParser ( ) ;
322
359
this . swiftTestOutputParser = new SwiftTestingOutputParser (
323
360
this . testRun . testRunStarted ,
324
- this . testRun . addParameterizedTestCase
361
+ this . testRun . addParameterizedTestCase ,
362
+ this . testRun . addAttachment
325
363
) ;
326
364
}
327
365
@@ -334,7 +372,8 @@ export class TestRunner {
334
372
// The SwiftTestingOutputParser holds state and needs to be reset between iterations.
335
373
this . swiftTestOutputParser = new SwiftTestingOutputParser (
336
374
this . testRun . testRunStarted ,
337
- this . testRun . addParameterizedTestCase
375
+ this . testRun . addParameterizedTestCase ,
376
+ this . testRun . addAttachment
338
377
) ;
339
378
this . testRun . setIteration ( iteration ) ;
340
379
}
@@ -519,18 +558,28 @@ export class TestRunner {
519
558
// Run swift-testing first, then XCTest.
520
559
// swift-testing being parallel by default should help these run faster.
521
560
if ( this . testArgs . hasSwiftTestingTests ) {
522
- const fifoPipePath = this . generateFifoPipePath ( ) ;
561
+ const testRunTime = Date . now ( ) ;
562
+ const fifoPipePath = this . generateFifoPipePath ( testRunTime ) ;
523
563
524
- await TemporaryFolder . withNamedTemporaryFile ( fifoPipePath , async ( ) => {
564
+ await TemporaryFolder . withNamedTemporaryFiles ( [ fifoPipePath ] , async ( ) => {
525
565
// macOS/Linux require us to create the named pipe before we use it.
526
566
// Windows just lets us communicate by specifying a pipe path without any ceremony.
527
567
if ( process . platform !== "win32" ) {
528
568
await execFile ( "mkfifo" , [ fifoPipePath ] , undefined , this . folderContext ) ;
529
569
}
530
-
531
- const testBuildConfig = await TestingConfigurationFactory . swiftTestingConfig (
570
+ // Create the swift-testing configuration JSON file, peparing any
571
+ // directories the configuration may require.
572
+ const attachmentFolder = await SwiftTestingConfigurationSetup . setupAttachmentFolder (
532
573
this . folderContext ,
574
+ testRunTime
575
+ ) ;
576
+ const swiftTestingArgs = await SwiftTestingBuildAguments . build (
533
577
fifoPipePath ,
578
+ attachmentFolder
579
+ ) ;
580
+ const testBuildConfig = await TestingConfigurationFactory . swiftTestingConfig (
581
+ this . folderContext ,
582
+ swiftTestingArgs ,
534
583
this . testKind ,
535
584
this . testArgs . swiftTestArgs ,
536
585
true
@@ -540,17 +589,25 @@ export class TestRunner {
540
589
return this . testRun . runState ;
541
590
}
542
591
592
+ const outputStream = this . testOutputWritable ( TestLibrary . swiftTesting , runState ) ;
593
+
543
594
// Watch the pipe for JSONL output and parse the events into test explorer updates.
544
595
// The await simply waits for the watching to be configured.
545
596
await this . swiftTestOutputParser . watch ( fifoPipePath , runState ) ;
546
597
547
598
await this . launchTests (
548
599
runState ,
549
600
this . testKind === TestKind . parallel ? TestKind . standard : this . testKind ,
550
- this . testOutputWritable ( TestLibrary . swiftTesting , runState ) ,
601
+ outputStream ,
551
602
testBuildConfig ,
552
603
TestLibrary . swiftTesting
553
604
) ;
605
+
606
+ await SwiftTestingConfigurationSetup . cleanupAttachmentFolder (
607
+ this . folderContext ,
608
+ testRunTime ,
609
+ this . workspaceContext . outputChannel
610
+ ) ;
554
611
} ) ;
555
612
}
556
613
@@ -774,21 +831,32 @@ export class TestRunner {
774
831
throw new Error ( `Build failed with exit code ${ buildExitCode } ` ) ;
775
832
}
776
833
834
+ const testRunTime = Date . now ( ) ;
777
835
const subscriptions : vscode . Disposable [ ] = [ ] ;
778
836
const buildConfigs : Array < vscode . DebugConfiguration | undefined > = [ ] ;
779
- const fifoPipePath = this . generateFifoPipePath ( ) ;
837
+ const fifoPipePath = this . generateFifoPipePath ( testRunTime ) ;
780
838
781
- await TemporaryFolder . withNamedTemporaryFile ( fifoPipePath , async ( ) => {
839
+ await TemporaryFolder . withNamedTemporaryFiles ( [ fifoPipePath ] , async ( ) => {
782
840
if ( this . testArgs . hasSwiftTestingTests ) {
783
841
// macOS/Linux require us to create the named pipe before we use it.
784
842
// Windows just lets us communicate by specifying a pipe path without any ceremony.
785
843
if ( process . platform !== "win32" ) {
786
844
await execFile ( "mkfifo" , [ fifoPipePath ] , undefined , this . folderContext ) ;
787
845
}
846
+ // Create the swift-testing configuration JSON file, peparing any
847
+ // directories the configuration may require.
848
+ const attachmentFolder = await SwiftTestingConfigurationSetup . setupAttachmentFolder (
849
+ this . folderContext ,
850
+ testRunTime
851
+ ) ;
852
+ const swiftTestingArgs = await SwiftTestingBuildAguments . build (
853
+ fifoPipePath ,
854
+ attachmentFolder
855
+ ) ;
788
856
789
857
const swiftTestBuildConfig = await TestingConfigurationFactory . swiftTestingConfig (
790
858
this . folderContext ,
791
- fifoPipePath ,
859
+ swiftTestingArgs ,
792
860
this . testKind ,
793
861
this . testArgs . swiftTestArgs ,
794
862
true
@@ -930,9 +998,6 @@ export class TestRunner {
930
998
}
931
999
} ,
932
1000
reason => {
933
- this . workspaceContext . outputChannel . logDiagnostic (
934
- `Failed to debug test: ${ reason } `
935
- ) ;
936
1001
subscriptions . forEach ( sub => sub . dispose ( ) ) ;
937
1002
reject ( reason ) ;
938
1003
}
@@ -942,6 +1007,13 @@ export class TestRunner {
942
1007
943
1008
// Run each debugging session sequentially
944
1009
await debugRuns . reduce ( ( p , fn ) => p . then ( ( ) => fn ( ) ) , Promise . resolve ( ) ) ;
1010
+
1011
+ // Clean up any leftover resources
1012
+ await SwiftTestingConfigurationSetup . cleanupAttachmentFolder (
1013
+ this . folderContext ,
1014
+ testRunTime ,
1015
+ this . workspaceContext . outputChannel
1016
+ ) ;
945
1017
} ) ;
946
1018
}
947
1019
@@ -996,10 +1068,10 @@ export class TestRunner {
996
1068
}
997
1069
}
998
1070
999
- private generateFifoPipePath ( ) : string {
1071
+ private generateFifoPipePath ( testRunDateNow : number ) : string {
1000
1072
return process . platform === "win32"
1001
- ? `\\\\.\\pipe\\vscodemkfifo-${ Date . now ( ) } `
1002
- : path . join ( os . tmpdir ( ) , `vscodemkfifo-${ Date . now ( ) } ` ) ;
1073
+ ? `\\\\.\\pipe\\vscodemkfifo-${ testRunDateNow } `
1074
+ : path . join ( os . tmpdir ( ) , `vscodemkfifo-${ testRunDateNow } ` ) ;
1003
1075
}
1004
1076
}
1005
1077
0 commit comments