@@ -39,6 +39,7 @@ import { TestCoverage } from "../coverage/LcovResults";
3939import { BuildConfigurationFactory , TestingConfigurationFactory } from "../debugger/buildConfig" ;
4040import { TestKind , isDebugging , isRelease } from "./TestKind" ;
4141import { reduceTestItemChildren } from "./TestUtils" ;
42+ import { CompositeCancellationToken } from "../utilities/cancellation" ;
4243
4344export enum TestLibrary {
4445 xctest = "XCTest" ,
@@ -65,6 +66,7 @@ export class TestRunProxy {
6566 private _testItems : vscode . TestItem [ ] ;
6667 private iteration : number | undefined ;
6768 public coverage : TestCoverage ;
69+ public token : CompositeCancellationToken ;
6870
6971 public testRunCompleteEmitter = new vscode . EventEmitter < void > ( ) ;
7072 public onTestRunComplete : vscode . Event < void > ;
@@ -87,14 +89,20 @@ export class TestRunProxy {
8789 return this . _testItems ;
8890 }
8991
92+ public get isCancellationRequested ( ) : boolean {
93+ return this . token . isCancellationRequested ;
94+ }
95+
9096 constructor (
9197 private testRunRequest : vscode . TestRunRequest ,
9298 private controller : vscode . TestController ,
9399 private args : TestRunArguments ,
94- private folderContext : FolderContext
100+ private folderContext : FolderContext ,
101+ testProfileCancellationToken : vscode . CancellationToken
95102 ) {
96103 this . _testItems = args . testItems ;
97104 this . coverage = new TestCoverage ( folderContext ) ;
105+ this . token = new CompositeCancellationToken ( testProfileCancellationToken ) ;
98106 this . onTestRunComplete = this . testRunCompleteEmitter . event ;
99107 }
100108
@@ -144,6 +152,7 @@ export class TestRunProxy {
144152 } ) ;
145153
146154 this . testRun = this . controller . createTestRun ( this . testRunRequest ) ;
155+ this . token . add ( this . testRun . token ) ;
147156 this . _testItems = [ ...this . testItems , ...addedTestItems ] ;
148157
149158 // Forward any output captured before the testRun was created.
@@ -217,6 +226,7 @@ export class TestRunProxy {
217226 }
218227 this . testRun ?. end ( ) ;
219228 this . testRunCompleteEmitter . fire ( ) ;
229+ this . token . dispose ( ) ;
220230 }
221231
222232 public setIteration ( iteration : number ) {
@@ -288,13 +298,14 @@ export class TestRunner {
288298 private testKind : TestKind ,
289299 private request : vscode . TestRunRequest ,
290300 private folderContext : FolderContext ,
291- private controller : vscode . TestController
301+ private controller : vscode . TestController ,
302+ token : vscode . CancellationToken
292303 ) {
293304 this . testArgs = new TestRunArguments (
294305 this . ensureRequestIncludesTests ( this . request ) ,
295306 isDebugging ( testKind )
296307 ) ;
297- this . testRun = new TestRunProxy ( request , controller , this . testArgs , folderContext ) ;
308+ this . testRun = new TestRunProxy ( request , controller , this . testArgs , folderContext , token ) ;
298309 this . xcTestOutputParser =
299310 testKind === TestKind . parallel
300311 ? new ParallelXCTestOutputParser (
@@ -358,10 +369,11 @@ export class TestRunner {
358369 TestKind . standard ,
359370 request ,
360371 folderContext ,
361- controller
372+ controller ,
373+ token
362374 ) ;
363375 onCreateTestRun . fire ( runner . testRun ) ;
364- await runner . runHandler ( token ) ;
376+ await runner . runHandler ( ) ;
365377 } ,
366378 true ,
367379 runnableTag
@@ -374,10 +386,11 @@ export class TestRunner {
374386 TestKind . parallel ,
375387 request ,
376388 folderContext ,
377- controller
389+ controller ,
390+ token
378391 ) ;
379392 onCreateTestRun . fire ( runner . testRun ) ;
380- await runner . runHandler ( token ) ;
393+ await runner . runHandler ( ) ;
381394 } ,
382395 false ,
383396 runnableTag
@@ -390,10 +403,11 @@ export class TestRunner {
390403 TestKind . release ,
391404 request ,
392405 folderContext ,
393- controller
406+ controller ,
407+ token
394408 ) ;
395409 onCreateTestRun . fire ( runner . testRun ) ;
396- await runner . runHandler ( token ) ;
410+ await runner . runHandler ( ) ;
397411 } ,
398412 false ,
399413 runnableTag
@@ -407,15 +421,16 @@ export class TestRunner {
407421 TestKind . coverage ,
408422 request ,
409423 folderContext ,
410- controller
424+ controller ,
425+ token
411426 ) ;
412427 onCreateTestRun . fire ( runner . testRun ) ;
413428 if ( request . profile ) {
414429 request . profile . loadDetailedCoverage = async ( testRun , fileCoverage ) => {
415430 return runner . testRun . coverage . loadDetailedCoverage ( fileCoverage . uri ) ;
416431 } ;
417432 }
418- await runner . runHandler ( token ) ;
433+ await runner . runHandler ( ) ;
419434 await vscode . commands . executeCommand ( "testing.openCoverage" ) ;
420435 } ,
421436 false ,
@@ -430,10 +445,11 @@ export class TestRunner {
430445 TestKind . debug ,
431446 request ,
432447 folderContext ,
433- controller
448+ controller ,
449+ token
434450 ) ;
435451 onCreateTestRun . fire ( runner . testRun ) ;
436- await runner . runHandler ( token ) ;
452+ await runner . runHandler ( ) ;
437453 } ,
438454 false ,
439455 runnableTag
@@ -446,10 +462,11 @@ export class TestRunner {
446462 TestKind . debugRelease ,
447463 request ,
448464 folderContext ,
449- controller
465+ controller ,
466+ token
450467 ) ;
451468 onCreateTestRun . fire ( runner . testRun ) ;
452- await runner . runHandler ( token ) ;
469+ await runner . runHandler ( ) ;
453470 } ,
454471 false ,
455472 runnableTag
@@ -463,13 +480,18 @@ export class TestRunner {
463480 * @param token Cancellation token
464481 * @returns When complete
465482 */
466- async runHandler ( token : vscode . CancellationToken ) {
483+ async runHandler ( ) {
467484 const runState = new TestRunnerTestRunState ( this . testRun ) ;
485+
486+ const cancellationDisposable = this . testRun . token . onCancellationRequested ( ( ) => {
487+ this . testRun . appendOutput ( "\r\nTest run cancelled." ) ;
488+ } ) ;
489+
468490 try {
469491 if ( isDebugging ( this . testKind ) ) {
470- await this . debugSession ( token , runState ) ;
492+ await this . debugSession ( runState ) ;
471493 } else {
472- await this . runSession ( token , runState ) ;
494+ await this . runSession ( runState ) ;
473495 }
474496 } catch ( error ) {
475497 this . workspaceContext . outputChannel . log ( `Error: ${ getErrorDescription ( error ) } ` ) ;
@@ -481,14 +503,12 @@ export class TestRunner {
481503 await this . testRun . computeCoverage ( ) ;
482504 }
483505
506+ cancellationDisposable . dispose ( ) ;
484507 await this . testRun . end ( ) ;
485508 }
486509
487510 /** Run test session without attaching to a debugger */
488- async runSession (
489- token : vscode . CancellationToken ,
490- runState : TestRunnerTestRunState
491- ) : Promise < TestRunState > {
511+ async runSession ( runState : TestRunnerTestRunState ) : Promise < TestRunState > {
492512 // Run swift-testing first, then XCTest.
493513 // swift-testing being parallel by default should help these run faster.
494514 if ( this . testArgs . hasSwiftTestingTests ) {
@@ -509,13 +529,7 @@ export class TestRunner {
509529 true
510530 ) ;
511531
512- if ( testBuildConfig === null ) {
513- return this . testRun . runState ;
514- }
515-
516- const outputStream = this . testOutputWritable ( TestLibrary . swiftTesting , runState ) ;
517- if ( token . isCancellationRequested ) {
518- outputStream . end ( ) ;
532+ if ( testBuildConfig === null || this . testRun . isCancellationRequested ) {
519533 return this . testRun . runState ;
520534 }
521535
@@ -526,8 +540,7 @@ export class TestRunner {
526540 await this . launchTests (
527541 runState ,
528542 this . testKind === TestKind . parallel ? TestKind . standard : this . testKind ,
529- token ,
530- outputStream ,
543+ this . testOutputWritable ( TestLibrary . swiftTesting , runState ) ,
531544 testBuildConfig ,
532545 TestLibrary . swiftTesting
533546 ) ;
@@ -541,13 +554,8 @@ export class TestRunner {
541554 this . testArgs . xcTestArgs ,
542555 true
543556 ) ;
544- if ( testBuildConfig === null ) {
545- return this . testRun . runState ;
546- }
547557
548- const parsedOutputStream = this . testOutputWritable ( TestLibrary . xctest , runState ) ;
549- if ( token . isCancellationRequested ) {
550- parsedOutputStream . end ( ) ;
558+ if ( testBuildConfig === null || this . testRun . isCancellationRequested ) {
551559 return this . testRun . runState ;
552560 }
553561
@@ -557,8 +565,7 @@ export class TestRunner {
557565 await this . launchTests (
558566 runState ,
559567 this . testKind ,
560- token ,
561- parsedOutputStream ,
568+ this . testOutputWritable ( TestLibrary . xctest , runState ) ,
562569 testBuildConfig ,
563570 TestLibrary . xctest
564571 ) ;
@@ -570,26 +577,20 @@ export class TestRunner {
570577 private async launchTests (
571578 runState : TestRunnerTestRunState ,
572579 testKind : TestKind ,
573- token : vscode . CancellationToken ,
574580 outputStream : stream . Writable ,
575581 testBuildConfig : vscode . DebugConfiguration ,
576582 testLibrary : TestLibrary
577583 ) {
578584 try {
579585 switch ( testKind ) {
580586 case TestKind . coverage :
581- await this . runCoverageSession (
582- token ,
583- outputStream ,
584- testBuildConfig ,
585- testLibrary
586- ) ;
587+ await this . runCoverageSession ( outputStream , testBuildConfig , testLibrary ) ;
587588 break ;
588589 case TestKind . parallel :
589- await this . runParallelSession ( token , outputStream , testBuildConfig , runState ) ;
590+ await this . runParallelSession ( outputStream , testBuildConfig , runState ) ;
590591 break ;
591592 default :
592- await this . runStandardSession ( token , outputStream , testBuildConfig , testKind ) ;
593+ await this . runStandardSession ( outputStream , testBuildConfig , testKind ) ;
593594 break ;
594595 }
595596 } catch ( error ) {
@@ -613,7 +614,6 @@ export class TestRunner {
613614
614615 /** Run tests outside of debugger */
615616 async runStandardSession (
616- token : vscode . CancellationToken ,
617617 outputStream : stream . Writable ,
618618 testBuildConfig : vscode . DebugConfiguration ,
619619 testKind : TestKind
@@ -679,19 +679,21 @@ export class TestRunner {
679679 }
680680 } ) ;
681681
682- this . folderContext . taskQueue . queueOperation ( new TaskOperation ( task ) , token ) ;
682+ this . folderContext . taskQueue . queueOperation (
683+ new TaskOperation ( task ) ,
684+ this . testRun . token
685+ ) ;
683686 } ) ;
684687 }
685688
686689 /** Run tests with code coverage, and parse coverage results */
687690 async runCoverageSession (
688- token : vscode . CancellationToken ,
689691 outputStream : stream . Writable ,
690692 testBuildConfig : vscode . DebugConfiguration ,
691693 testLibrary : TestLibrary
692694 ) {
693695 try {
694- await this . runStandardSession ( token , outputStream , testBuildConfig , TestKind . coverage ) ;
696+ await this . runStandardSession ( outputStream , testBuildConfig , TestKind . coverage ) ;
695697 } catch ( error ) {
696698 // If this isn't a standard test failure, forward the error and skip generating coverage.
697699 if ( error !== 1 ) {
@@ -704,7 +706,6 @@ export class TestRunner {
704706
705707 /** Run tests in parallel outside of debugger */
706708 async runParallelSession (
707- token : vscode . CancellationToken ,
708709 outputStream : stream . Writable ,
709710 testBuildConfig : vscode . DebugConfiguration ,
710711 runState : TestRunnerTestRunState
@@ -714,7 +715,6 @@ export class TestRunner {
714715
715716 try {
716717 testBuildConfig . args = await this . runStandardSession (
717- token ,
718718 outputStream ,
719719 {
720720 ...testBuildConfig ,
@@ -743,13 +743,13 @@ export class TestRunner {
743743 }
744744
745745 /** Run test session inside debugger */
746- async debugSession ( token : vscode . CancellationToken , runState : TestRunnerTestRunState ) {
746+ async debugSession ( runState : TestRunnerTestRunState ) {
747747 // Perform a build all first to produce the binaries we'll run later.
748748 let buildOutput = "" ;
749749 try {
750750 await this . runStandardSession (
751- token ,
752- // discard the output as we dont want to associate it with the test run.
751+ // Capture the output to print it in case of a build error.
752+ // We dont want to associate it with the test run.
753753 new stream . Writable ( {
754754 write : ( chunk , encoding , next ) => {
755755 buildOutput += chunk . toString ( ) ;
@@ -846,6 +846,11 @@ export class TestRunner {
846846 const debugRuns = validBuildConfigs . map ( config => {
847847 return ( ) =>
848848 new Promise < void > ( ( resolve , reject ) => {
849+ if ( this . testRun . isCancellationRequested ) {
850+ resolve ( ) ;
851+ return ;
852+ }
853+
849854 // add cancelation
850855 const startSession = vscode . debug . onDidStartDebugSession ( session => {
851856 if ( config . testType === TestLibrary . xctest ) {
@@ -862,12 +867,13 @@ export class TestRunner {
862867 outputHandler ( output ) ;
863868 } ) ;
864869
865- const cancellation = token . onCancellationRequested ( ( ) => {
870+ const cancellation = this . testRun . token . onCancellationRequested ( ( ) => {
866871 this . workspaceContext . outputChannel . logDiagnostic (
867872 "Test Debugging Cancelled" ,
868873 this . folderContext . name
869874 ) ;
870875 vscode . debug . stopDebugging ( session ) ;
876+ resolve ( ) ;
871877 } ) ;
872878 subscriptions . push ( cancellation ) ;
873879 } ) ;
0 commit comments