@@ -23,6 +23,7 @@ import { traceInfo } from '../../../../client/logging';
2323import { UnittestTestExecutionAdapter } from '../../../../client/testing/testController/unittest/testExecutionAdapter' ;
2424import * as extapi from '../../../../client/envExt/api.internal' ;
2525import { ProjectAdapter } from '../../../../client/testing/testController/common/projectAdapter' ;
26+ import { createMockProjectAdapter } from '../testMocks' ;
2627
2728suite ( 'Unittest test execution adapter' , ( ) => {
2829 let configService : IConfigurationService ;
@@ -434,4 +435,144 @@ suite('Unittest test execution adapter', () => {
434435 typeMoq . Times . once ( ) ,
435436 ) ;
436437 } ) ;
438+
439+ test ( 'Debug mode with project should pass project.pythonProject to debug launcher' , async ( ) => {
440+ const deferred3 = createDeferred ( ) ;
441+ utilsWriteTestIdsFileStub . callsFake ( ( ) => Promise . resolve ( 'testIdPipe-mockName' ) ) ;
442+
443+ debugLauncher
444+ . setup ( ( dl ) => dl . launchDebugger ( typeMoq . It . isAny ( ) , typeMoq . It . isAny ( ) , typeMoq . It . isAny ( ) ) )
445+ . returns ( async ( _opts , callback ) => {
446+ traceInfo ( 'stubs launch debugger' ) ;
447+ if ( typeof callback === 'function' ) {
448+ deferred3 . resolve ( ) ;
449+ callback ( ) ;
450+ }
451+ } ) ;
452+
453+ const testRun = typeMoq . Mock . ofType < TestRun > ( ) ;
454+ testRun
455+ . setup ( ( t ) => t . token )
456+ . returns (
457+ ( ) =>
458+ ( {
459+ onCancellationRequested : ( ) => undefined ,
460+ } as any ) ,
461+ ) ;
462+
463+ const projectPath = path . join ( '/' , 'workspace' , 'myproject' ) ;
464+ const mockProject = createMockProjectAdapter ( {
465+ projectPath,
466+ projectName : 'myproject (Python 3.11)' ,
467+ pythonPath : '/custom/python/path' ,
468+ testProvider : 'unittest' ,
469+ } ) ;
470+
471+ const uri = Uri . file ( myTestPath ) ;
472+ adapter = new UnittestTestExecutionAdapter ( configService ) ;
473+ adapter . runTests (
474+ uri ,
475+ [ ] ,
476+ TestRunProfileKind . Debug ,
477+ testRun . object ,
478+ execFactory . object ,
479+ debugLauncher . object ,
480+ undefined ,
481+ mockProject ,
482+ ) ;
483+
484+ await deferred3 . promise ;
485+
486+ debugLauncher . verify (
487+ ( x ) =>
488+ x . launchDebugger (
489+ typeMoq . It . is < LaunchOptions > ( ( launchOptions ) => {
490+ // Project should be passed for project-based debugging
491+ assert . ok ( launchOptions . project , 'project should be defined' ) ;
492+ assert . equal ( launchOptions . project ?. name , 'myproject (Python 3.11)' ) ;
493+ assert . equal ( launchOptions . project ?. uri . fsPath , projectPath ) ;
494+ return true ;
495+ } ) ,
496+ typeMoq . It . isAny ( ) ,
497+ typeMoq . It . isAny ( ) ,
498+ ) ,
499+ typeMoq . Times . once ( ) ,
500+ ) ;
501+ } ) ;
502+
503+ test ( 'useEnvExtension mode with project should use project pythonEnvironment' , async ( ) => {
504+ // Enable the useEnvExtension path
505+ useEnvExtensionStub . returns ( true ) ;
506+
507+ utilsWriteTestIdsFileStub . callsFake ( ( ) => Promise . resolve ( 'testIdPipe-mockName' ) ) ;
508+
509+ // Store the deferredTillServerClose so we can resolve it
510+ let serverCloseDeferred : Deferred < void > | undefined ;
511+ utilsStartRunResultNamedPipeStub . callsFake ( ( _callback : unknown , deferred : Deferred < void > , _token : unknown ) => {
512+ serverCloseDeferred = deferred ;
513+ return Promise . resolve ( 'runResultPipe-mockName' ) ;
514+ } ) ;
515+
516+ const projectPath = path . join ( '/' , 'workspace' , 'myproject' ) ;
517+ const mockProject = createMockProjectAdapter ( {
518+ projectPath,
519+ projectName : 'myproject (Python 3.11)' ,
520+ pythonPath : '/custom/python/path' ,
521+ testProvider : 'unittest' ,
522+ } ) ;
523+
524+ // Stub runInBackground to capture which environment was used
525+ const runInBackgroundStub = sinon . stub ( extapi , 'runInBackground' ) ;
526+ const exitCallbacks : ( ( code : number , signal : string | null ) => void ) [ ] = [ ] ;
527+ const mockProc2 = {
528+ stdout : { on : sinon . stub ( ) } ,
529+ stderr : { on : sinon . stub ( ) } ,
530+ onExit : ( cb : ( code : number , signal : string | null ) => void ) => {
531+ exitCallbacks . push ( cb ) ;
532+ } ,
533+ kill : sinon . stub ( ) ,
534+ } ;
535+ runInBackgroundStub . resolves ( mockProc2 as any ) ;
536+
537+ const testRun = typeMoq . Mock . ofType < TestRun > ( ) ;
538+ testRun
539+ . setup ( ( t ) => t . token )
540+ . returns (
541+ ( ) =>
542+ ( {
543+ onCancellationRequested : ( ) => undefined ,
544+ } as any ) ,
545+ ) ;
546+
547+ const uri = Uri . file ( myTestPath ) ;
548+ adapter = new UnittestTestExecutionAdapter ( configService ) ;
549+ const runPromise = adapter . runTests (
550+ uri ,
551+ [ ] ,
552+ TestRunProfileKind . Run ,
553+ testRun . object ,
554+ execFactory . object ,
555+ debugLauncher . object ,
556+ undefined ,
557+ mockProject ,
558+ ) ;
559+
560+ // Wait for the runInBackground to be called
561+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
562+
563+ // Simulate process exit to complete the test
564+ exitCallbacks . forEach ( ( cb ) => cb ( 0 , null ) ) ;
565+
566+ // Resolve the server close deferred to allow the runTests to complete
567+ serverCloseDeferred ?. resolve ( ) ;
568+
569+ await runPromise ;
570+
571+ // Verify runInBackground was called with the project's Python environment
572+ sinon . assert . calledOnce ( runInBackgroundStub ) ;
573+ const envArg = runInBackgroundStub . firstCall . args [ 0 ] ;
574+ // The environment should be the project's pythonEnvironment
575+ assert . ok ( envArg , 'runInBackground should be called with an environment' ) ;
576+ assert . equal ( envArg . execInfo ?. run ?. executable , '/custom/python/path' ) ;
577+ } ) ;
437578} ) ;
0 commit comments