@@ -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,147 @@ 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+ // Promise that resolves when the production code registers its onExit handler
528+ const onExitRegistered = createDeferred < void > ( ) ;
529+ const mockProc2 = {
530+ stdout : { on : sinon . stub ( ) } ,
531+ stderr : { on : sinon . stub ( ) } ,
532+ onExit : ( cb : ( code : number , signal : string | null ) => void ) => {
533+ exitCallbacks . push ( cb ) ;
534+ onExitRegistered . resolve ( ) ;
535+ } ,
536+ kill : sinon . stub ( ) ,
537+ } ;
538+ runInBackgroundStub . callsFake ( ( ) => Promise . resolve ( mockProc2 as any ) ) ;
539+
540+ const testRun = typeMoq . Mock . ofType < TestRun > ( ) ;
541+ testRun
542+ . setup ( ( t ) => t . token )
543+ . returns (
544+ ( ) =>
545+ ( {
546+ onCancellationRequested : ( ) => undefined ,
547+ } as any ) ,
548+ ) ;
549+
550+ const uri = Uri . file ( myTestPath ) ;
551+ adapter = new UnittestTestExecutionAdapter ( configService ) ;
552+ const runPromise = adapter . runTests (
553+ uri ,
554+ [ ] ,
555+ TestRunProfileKind . Run ,
556+ testRun . object ,
557+ execFactory . object ,
558+ debugLauncher . object ,
559+ undefined ,
560+ mockProject ,
561+ ) ;
562+
563+ // Wait for production code to register its onExit handler
564+ await onExitRegistered . promise ;
565+
566+ // Simulate process exit to complete the test
567+ exitCallbacks . forEach ( ( cb ) => cb ( 0 , null ) ) ;
568+
569+ // Resolve the server close deferred to allow the runTests to complete
570+ serverCloseDeferred ?. resolve ( ) ;
571+
572+ await runPromise ;
573+
574+ // Verify runInBackground was called with the project's Python environment
575+ sinon . assert . calledOnce ( runInBackgroundStub ) ;
576+ const envArg = runInBackgroundStub . firstCall . args [ 0 ] ;
577+ // The environment should be the project's pythonEnvironment
578+ assert . ok ( envArg , 'runInBackground should be called with an environment' ) ;
579+ assert . equal ( envArg . execInfo ?. run ?. executable , '/custom/python/path' ) ;
580+ } ) ;
437581} ) ;
0 commit comments