@@ -27,7 +27,11 @@ import {
2727 ParallelXCTestOutputParser ,
2828 XCTestOutputParser ,
2929} from "./TestParsers/XCTestOutputParser" ;
30- import { SwiftTestingOutputParser } from "./TestParsers/SwiftTestingOutputParser" ;
30+ import {
31+ SwiftTestingOutputParser ,
32+ SymbolRenderer ,
33+ TestSymbol ,
34+ } from "./TestParsers/SwiftTestingOutputParser" ;
3135import { LoggingDebugAdapterTracker } from "../debugger/logTracker" ;
3236import { TaskOperation } from "../tasks/TaskQueue" ;
3337import { TestXUnitParser } from "./TestXUnitParser" ;
@@ -36,7 +40,12 @@ import { TestRunArguments } from "./TestRunArguments";
3640import { TemporaryFolder } from "../utilities/tempFolder" ;
3741import { TestClass , runnableTag , upsertTestItem } from "./TestDiscovery" ;
3842import { 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" ;
4049import { TestKind , isDebugging , isRelease } from "./TestKind" ;
4150import { reduceTestItemChildren } from "./TestUtils" ;
4251import { CompositeCancellationToken } from "../utilities/cancellation" ;
@@ -67,6 +76,7 @@ export class TestRunProxy {
6776 private queuedOutput : string [ ] = [ ] ;
6877 private _testItems : vscode . TestItem [ ] ;
6978 private iteration : number | undefined ;
79+ private attachments : { [ key : string ] : string [ ] } = { } ;
7080 public coverage : TestCoverage ;
7181 public token : CompositeCancellationToken ;
7282
@@ -177,6 +187,12 @@ export class TestRunProxy {
177187 this . addedTestItems . push ( { testClass, parentIndex } ) ;
178188 } ;
179189
190+ public addAttachment = ( testIndex : number , attachment : string ) => {
191+ const attachments = this . attachments [ testIndex ] ?? [ ] ;
192+ attachments . push ( attachment ) ;
193+ this . attachments [ testIndex ] = attachments ;
194+ } ;
195+
180196 public getTestIndex ( id : string , filename ?: string ) : number {
181197 return this . testItemFinder . getIndex ( id , filename ) ;
182198 }
@@ -231,6 +247,8 @@ export class TestRunProxy {
231247 if ( ! this . runStarted ) {
232248 this . testRunStarted ( ) ;
233249 }
250+
251+ this . reportAttachments ( ) ;
234252 this . testRun ?. end ( ) ;
235253 this . testRunCompleteEmitter . fire ( ) ;
236254 this . token . dispose ( ) ;
@@ -259,6 +277,25 @@ export class TestRunProxy {
259277 }
260278 }
261279
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+
262299 private performAppendOutput (
263300 testRun : vscode . TestRun ,
264301 output : string ,
@@ -321,7 +358,8 @@ export class TestRunner {
321358 : new XCTestOutputParser ( ) ;
322359 this . swiftTestOutputParser = new SwiftTestingOutputParser (
323360 this . testRun . testRunStarted ,
324- this . testRun . addParameterizedTestCase
361+ this . testRun . addParameterizedTestCase ,
362+ this . testRun . addAttachment
325363 ) ;
326364 }
327365
@@ -334,7 +372,8 @@ export class TestRunner {
334372 // The SwiftTestingOutputParser holds state and needs to be reset between iterations.
335373 this . swiftTestOutputParser = new SwiftTestingOutputParser (
336374 this . testRun . testRunStarted ,
337- this . testRun . addParameterizedTestCase
375+ this . testRun . addParameterizedTestCase ,
376+ this . testRun . addAttachment
338377 ) ;
339378 this . testRun . setIteration ( iteration ) ;
340379 }
@@ -519,18 +558,28 @@ export class TestRunner {
519558 // Run swift-testing first, then XCTest.
520559 // swift-testing being parallel by default should help these run faster.
521560 if ( this . testArgs . hasSwiftTestingTests ) {
522- const fifoPipePath = this . generateFifoPipePath ( ) ;
561+ const testRunTime = Date . now ( ) ;
562+ const fifoPipePath = this . generateFifoPipePath ( testRunTime ) ;
523563
524- await TemporaryFolder . withNamedTemporaryFile ( fifoPipePath , async ( ) => {
564+ await TemporaryFolder . withNamedTemporaryFiles ( [ fifoPipePath ] , async ( ) => {
525565 // macOS/Linux require us to create the named pipe before we use it.
526566 // Windows just lets us communicate by specifying a pipe path without any ceremony.
527567 if ( process . platform !== "win32" ) {
528568 await execFile ( "mkfifo" , [ fifoPipePath ] , undefined , this . folderContext ) ;
529569 }
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 (
532573 this . folderContext ,
574+ testRunTime
575+ ) ;
576+ const swiftTestingArgs = await SwiftTestingBuildAguments . build (
533577 fifoPipePath ,
578+ attachmentFolder
579+ ) ;
580+ const testBuildConfig = await TestingConfigurationFactory . swiftTestingConfig (
581+ this . folderContext ,
582+ swiftTestingArgs ,
534583 this . testKind ,
535584 this . testArgs . swiftTestArgs ,
536585 true
@@ -540,17 +589,25 @@ export class TestRunner {
540589 return this . testRun . runState ;
541590 }
542591
592+ const outputStream = this . testOutputWritable ( TestLibrary . swiftTesting , runState ) ;
593+
543594 // Watch the pipe for JSONL output and parse the events into test explorer updates.
544595 // The await simply waits for the watching to be configured.
545596 await this . swiftTestOutputParser . watch ( fifoPipePath , runState ) ;
546597
547598 await this . launchTests (
548599 runState ,
549600 this . testKind === TestKind . parallel ? TestKind . standard : this . testKind ,
550- this . testOutputWritable ( TestLibrary . swiftTesting , runState ) ,
601+ outputStream ,
551602 testBuildConfig ,
552603 TestLibrary . swiftTesting
553604 ) ;
605+
606+ await SwiftTestingConfigurationSetup . cleanupAttachmentFolder (
607+ this . folderContext ,
608+ testRunTime ,
609+ this . workspaceContext . outputChannel
610+ ) ;
554611 } ) ;
555612 }
556613
@@ -774,21 +831,32 @@ export class TestRunner {
774831 throw new Error ( `Build failed with exit code ${ buildExitCode } ` ) ;
775832 }
776833
834+ const testRunTime = Date . now ( ) ;
777835 const subscriptions : vscode . Disposable [ ] = [ ] ;
778836 const buildConfigs : Array < vscode . DebugConfiguration | undefined > = [ ] ;
779- const fifoPipePath = this . generateFifoPipePath ( ) ;
837+ const fifoPipePath = this . generateFifoPipePath ( testRunTime ) ;
780838
781- await TemporaryFolder . withNamedTemporaryFile ( fifoPipePath , async ( ) => {
839+ await TemporaryFolder . withNamedTemporaryFiles ( [ fifoPipePath ] , async ( ) => {
782840 if ( this . testArgs . hasSwiftTestingTests ) {
783841 // macOS/Linux require us to create the named pipe before we use it.
784842 // Windows just lets us communicate by specifying a pipe path without any ceremony.
785843 if ( process . platform !== "win32" ) {
786844 await execFile ( "mkfifo" , [ fifoPipePath ] , undefined , this . folderContext ) ;
787845 }
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+ ) ;
788856
789857 const swiftTestBuildConfig = await TestingConfigurationFactory . swiftTestingConfig (
790858 this . folderContext ,
791- fifoPipePath ,
859+ swiftTestingArgs ,
792860 this . testKind ,
793861 this . testArgs . swiftTestArgs ,
794862 true
@@ -930,9 +998,6 @@ export class TestRunner {
930998 }
931999 } ,
9321000 reason => {
933- this . workspaceContext . outputChannel . logDiagnostic (
934- `Failed to debug test: ${ reason } `
935- ) ;
9361001 subscriptions . forEach ( sub => sub . dispose ( ) ) ;
9371002 reject ( reason ) ;
9381003 }
@@ -942,6 +1007,13 @@ export class TestRunner {
9421007
9431008 // Run each debugging session sequentially
9441009 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+ ) ;
9451017 } ) ;
9461018 }
9471019
@@ -996,10 +1068,10 @@ export class TestRunner {
9961068 }
9971069 }
9981070
999- private generateFifoPipePath ( ) : string {
1071+ private generateFifoPipePath ( testRunDateNow : number ) : string {
10001072 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 } ` ) ;
10031075 }
10041076}
10051077
0 commit comments