@@ -270,6 +270,356 @@ suite('Debugging - Config Resolver', () => {
270270 expect ( config ) . to . have . property ( 'debugLauncherPython' , pythonPath ) ;
271271 } ) ;
272272
273+ // Tests for prioritization of python path configuration
274+ suite ( 'resolveAndUpdatePythonPath prioritization tests' , ( ) => {
275+ test ( 'When pythonPath is a concrete path and python is undefined, python should be set to pythonPath value' , async ( ) => {
276+ const expectedPath = path . join ( 'path' , 'to' , 'custom' , 'python' ) ;
277+ const config = {
278+ pythonPath : expectedPath ,
279+ python : undefined ,
280+ } ;
281+
282+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
283+
284+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
285+ expect ( config ) . to . have . property ( 'python' , expectedPath ) ;
286+ } ) ;
287+
288+ test ( 'When pythonPath is a concrete path and python is a different concrete path, python should take precedence' , async ( ) => {
289+ const pythonPathValue = path . join ( 'path' , 'to' , 'pythonPath' , 'python' ) ;
290+ const pythonValue = path . join ( 'path' , 'to' , 'python' , 'python' ) ;
291+ const config = {
292+ pythonPath : pythonPathValue ,
293+ python : pythonValue ,
294+ } ;
295+
296+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
297+
298+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
299+ expect ( config ) . to . have . property ( 'python' , pythonValue ) ;
300+ } ) ;
301+
302+ test ( 'When pythonPath is ${command:python.interpreterPath} and python is a concrete path, python should take precedence' , async ( ) => {
303+ const pythonValue = path . join ( 'path' , 'to' , 'python' , 'python' ) ;
304+ const interpreterPath = path . join ( 'path' , 'from' , 'interpreter' ) ;
305+ const config = {
306+ pythonPath : '${command:python.interpreterPath}' ,
307+ python : pythonValue ,
308+ } ;
309+
310+ getInterpreterDetailsStub . resolves ( { path : [ interpreterPath ] } as unknown as PythonEnvironment ) ;
311+
312+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
313+
314+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
315+ expect ( config ) . to . have . property ( 'python' , pythonValue ) ;
316+ } ) ;
317+
318+ test ( 'When pythonPath is a concrete path and python is ${command:python.interpreterPath}, python should resolve from interpreter' , async ( ) => {
319+ const pythonPathValue = path . join ( 'path' , 'to' , 'pythonPath' , 'python' ) ;
320+ const interpreterPath = path . join ( 'path' , 'from' , 'interpreter' ) ;
321+ const config = {
322+ pythonPath : pythonPathValue ,
323+ python : '${command:python.interpreterPath}' ,
324+ } ;
325+
326+ getInterpreterDetailsStub . resolves ( { path : [ interpreterPath ] } as unknown as PythonEnvironment ) ;
327+
328+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
329+
330+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
331+ expect ( config ) . to . have . property ( 'python' , interpreterPath ) ;
332+ } ) ;
333+
334+ test ( 'When both pythonPath and python are ${command:python.interpreterPath}, both should resolve to interpreter path' , async ( ) => {
335+ const interpreterPath = path . join ( 'path' , 'from' , 'interpreter' ) ;
336+ const config = {
337+ pythonPath : '${command:python.interpreterPath}' ,
338+ python : '${command:python.interpreterPath}' ,
339+ } ;
340+
341+ getInterpreterDetailsStub . resolves ( { path : [ interpreterPath ] } as unknown as PythonEnvironment ) ;
342+
343+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
344+
345+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
346+ expect ( config ) . to . have . property ( 'python' , interpreterPath ) ;
347+ } ) ;
348+
349+ test ( 'When pythonPath is not set and python is not set, both should resolve from interpreter' , async ( ) => {
350+ const interpreterPath = path . join ( 'path' , 'from' , 'interpreter' ) ;
351+ const config = { } ;
352+
353+ getInterpreterDetailsStub . resolves ( { path : [ interpreterPath ] } as unknown as PythonEnvironment ) ;
354+
355+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
356+
357+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
358+ expect ( config ) . to . have . property ( 'python' , interpreterPath ) ;
359+ } ) ;
360+
361+ test ( 'debugAdapterPython should use pythonPath when neither debugAdapterPython nor python are set' , async ( ) => {
362+ const pythonPathValue = path . join ( 'path' , 'to' , 'custom' , 'python' ) ;
363+ const config = {
364+ pythonPath : pythonPathValue ,
365+ } ;
366+
367+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
368+
369+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
370+ expect ( config ) . to . have . property ( 'python' , pythonPathValue ) ;
371+ expect ( config ) . to . have . property ( 'debugAdapterPython' , pythonPathValue ) ;
372+ } ) ;
373+
374+ test ( 'debugAdapterPython should use python when pythonPath is set but python has different value' , async ( ) => {
375+ const pythonPathValue = path . join ( 'path' , 'to' , 'pythonPath' , 'python' ) ;
376+ const pythonValue = path . join ( 'path' , 'to' , 'python' , 'python' ) ;
377+ const config = {
378+ pythonPath : pythonPathValue ,
379+ python : pythonValue ,
380+ } ;
381+
382+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
383+
384+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
385+ expect ( config ) . to . have . property ( 'python' , pythonValue ) ;
386+ expect ( config ) . to . have . property ( 'debugAdapterPython' , pythonValue ) ;
387+ } ) ;
388+
389+ test ( 'debugAdapterPython should prefer explicitly set debugAdapterPython over pythonPath' , async ( ) => {
390+ const pythonPathValue = path . join ( 'path' , 'to' , 'pythonPath' , 'python' ) ;
391+ const debugAdapterValue = path . join ( 'path' , 'to' , 'debugAdapter' , 'python' ) ;
392+ const config = {
393+ pythonPath : pythonPathValue ,
394+ debugAdapterPython : debugAdapterValue ,
395+ } ;
396+
397+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
398+
399+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
400+ expect ( config ) . to . have . property ( 'python' , pythonPathValue ) ;
401+ expect ( config ) . to . have . property ( 'debugAdapterPython' , debugAdapterValue ) ;
402+ } ) ;
403+
404+ test ( 'debugLauncherPython should use pythonPath when neither debugLauncherPython nor python are set' , async ( ) => {
405+ const pythonPathValue = path . join ( 'path' , 'to' , 'custom' , 'python' ) ;
406+ const config = {
407+ pythonPath : pythonPathValue ,
408+ } ;
409+
410+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
411+
412+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
413+ expect ( config ) . to . have . property ( 'python' , pythonPathValue ) ;
414+ expect ( config ) . to . have . property ( 'debugLauncherPython' , pythonPathValue ) ;
415+ } ) ;
416+
417+ test ( 'debugLauncherPython should use python when pythonPath is set but python has different value' , async ( ) => {
418+ const pythonPathValue = path . join ( 'path' , 'to' , 'pythonPath' , 'python' ) ;
419+ const pythonValue = path . join ( 'path' , 'to' , 'python' , 'python' ) ;
420+ const config = {
421+ pythonPath : pythonPathValue ,
422+ python : pythonValue ,
423+ } ;
424+
425+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
426+
427+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
428+ expect ( config ) . to . have . property ( 'python' , pythonValue ) ;
429+ expect ( config ) . to . have . property ( 'debugLauncherPython' , pythonValue ) ;
430+ } ) ;
431+
432+ test ( 'debugLauncherPython should prefer explicitly set debugLauncherPython over pythonPath' , async ( ) => {
433+ const pythonPathValue = path . join ( 'path' , 'to' , 'pythonPath' , 'python' ) ;
434+ const debugLauncherValue = path . join ( 'path' , 'to' , 'debugLauncher' , 'python' ) ;
435+ const config = {
436+ pythonPath : pythonPathValue ,
437+ debugLauncherPython : debugLauncherValue ,
438+ } ;
439+
440+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
441+
442+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
443+ expect ( config ) . to . have . property ( 'python' , pythonPathValue ) ;
444+ expect ( config ) . to . have . property ( 'debugLauncherPython' , debugLauncherValue ) ;
445+ } ) ;
446+
447+ test ( 'All three debug python fields can have different values when explicitly set' , async ( ) => {
448+ const pythonValue = path . join ( 'path' , 'to' , 'python' , 'python' ) ;
449+ const debugAdapterValue = path . join ( 'path' , 'to' , 'debugAdapter' , 'python' ) ;
450+ const debugLauncherValue = path . join ( 'path' , 'to' , 'debugLauncher' , 'python' ) ;
451+ const config = {
452+ python : pythonValue ,
453+ debugAdapterPython : debugAdapterValue ,
454+ debugLauncherPython : debugLauncherValue ,
455+ } ;
456+
457+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
458+
459+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
460+ expect ( config ) . to . have . property ( 'python' , pythonValue ) ;
461+ expect ( config ) . to . have . property ( 'debugAdapterPython' , debugAdapterValue ) ;
462+ expect ( config ) . to . have . property ( 'debugLauncherPython' , debugLauncherValue ) ;
463+ } ) ;
464+
465+ test ( 'When debugAdapterPython is ${command:python.interpreterPath}, it should fallback to resolved pythonPath' , async ( ) => {
466+ const interpreterPath = path . join ( 'path' , 'from' , 'interpreter' ) ;
467+ const config = {
468+ pythonPath : '${command:python.interpreterPath}' ,
469+ debugAdapterPython : '${command:python.interpreterPath}' ,
470+ } ;
471+
472+ getInterpreterDetailsStub . resolves ( { path : [ interpreterPath ] } as unknown as PythonEnvironment ) ;
473+
474+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
475+
476+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
477+ expect ( config ) . to . have . property ( 'python' , interpreterPath ) ;
478+ expect ( config ) . to . have . property ( 'debugAdapterPython' , interpreterPath ) ;
479+ } ) ;
480+
481+ test ( 'When debugLauncherPython is ${command:python.interpreterPath}, it should fallback to resolved pythonPath' , async ( ) => {
482+ const interpreterPath = path . join ( 'path' , 'from' , 'interpreter' ) ;
483+ const config = {
484+ pythonPath : '${command:python.interpreterPath}' ,
485+ debugLauncherPython : '${command:python.interpreterPath}' ,
486+ } ;
487+
488+ getInterpreterDetailsStub . resolves ( { path : [ interpreterPath ] } as unknown as PythonEnvironment ) ;
489+
490+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
491+
492+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
493+ expect ( config ) . to . have . property ( 'python' , interpreterPath ) ;
494+ expect ( config ) . to . have . property ( 'debugLauncherPython' , interpreterPath ) ;
495+ } ) ;
496+
497+ test ( 'Complex scenario: pythonPath set, python differs, debugAdapterPython and debugLauncherPython both set differently' , async ( ) => {
498+ const pythonPathValue = path . join ( 'path' , 'to' , 'pythonPath' , 'python' ) ;
499+ const pythonValue = path . join ( 'path' , 'to' , 'python' , 'python' ) ;
500+ const debugAdapterValue = path . join ( 'path' , 'to' , 'debugAdapter' , 'python' ) ;
501+ const debugLauncherValue = path . join ( 'path' , 'to' , 'debugLauncher' , 'python' ) ;
502+ const config = {
503+ pythonPath : pythonPathValue ,
504+ python : pythonValue ,
505+ debugAdapterPython : debugAdapterValue ,
506+ debugLauncherPython : debugLauncherValue ,
507+ } ;
508+
509+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
510+
511+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
512+ expect ( config ) . to . have . property ( 'python' , pythonValue ) ;
513+ expect ( config ) . to . have . property ( 'debugAdapterPython' , debugAdapterValue ) ;
514+ expect ( config ) . to . have . property ( 'debugLauncherPython' , debugLauncherValue ) ;
515+ } ) ;
516+
517+ test ( 'When pythonPath is undefined and python is concrete path, debugAdapter and debugLauncher should use python' , async ( ) => {
518+ const pythonValue = path . join ( 'path' , 'to' , 'python' , 'python' ) ;
519+ const config = {
520+ python : pythonValue ,
521+ } ;
522+
523+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
524+
525+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
526+ expect ( config ) . to . have . property ( 'python' , pythonValue ) ;
527+ expect ( config ) . to . have . property ( 'debugAdapterPython' , pythonValue ) ;
528+ expect ( config ) . to . have . property ( 'debugLauncherPython' , pythonValue ) ;
529+ } ) ;
530+
531+ test ( 'When pythonPath is empty string, it should be treated as not set and resolve from interpreter' , async ( ) => {
532+ const interpreterPath = path . join ( 'path' , 'from' , 'interpreter' ) ;
533+ const config = {
534+ pythonPath : '' ,
535+ } ;
536+
537+ getInterpreterDetailsStub . resolves ( { path : [ interpreterPath ] } as unknown as PythonEnvironment ) ;
538+
539+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
540+
541+ expect ( config ) . to . not . have . property ( 'pythonPath' ) ;
542+ expect ( config ) . to . have . property ( 'python' , interpreterPath ) ;
543+ } ) ;
544+
545+ // Tests for pythonPathSource field
546+ test ( 'pythonPathSource should be settingsJson when python is ${command:python.interpreterPath}' , async ( ) => {
547+ const interpreterPath = path . join ( 'path' , 'from' , 'interpreter' ) ;
548+ const config = {
549+ python : '${command:python.interpreterPath}' ,
550+ } ;
551+
552+ getInterpreterDetailsStub . resolves ( { path : [ interpreterPath ] } as unknown as PythonEnvironment ) ;
553+
554+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
555+
556+ expect ( resolver . getPythonPathSource ( ) ) . to . equal ( PythonPathSource . settingsJson ) ;
557+ } ) ;
558+
559+ test ( 'pythonPathSource should be settingsJson when python is undefined' , async ( ) => {
560+ const interpreterPath = path . join ( 'path' , 'from' , 'interpreter' ) ;
561+ const config = {
562+ pythonPath : interpreterPath ,
563+ python : undefined ,
564+ } ;
565+
566+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
567+
568+ expect ( resolver . getPythonPathSource ( ) ) . to . equal ( PythonPathSource . settingsJson ) ;
569+ } ) ;
570+
571+ test ( 'pythonPathSource should be launchJson when python is explicitly set to a concrete path' , async ( ) => {
572+ const pythonValue = path . join ( 'path' , 'to' , 'python' , 'python' ) ;
573+ const config = {
574+ python : pythonValue ,
575+ } ;
576+
577+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
578+
579+ expect ( resolver . getPythonPathSource ( ) ) . to . equal ( PythonPathSource . launchJson ) ;
580+ } ) ;
581+
582+ test ( 'pythonPathSource should be launchJson when python is a concrete path even if pythonPath is ${command:python.interpreterPath}' , async ( ) => {
583+ const pythonValue = path . join ( 'path' , 'to' , 'python' , 'python' ) ;
584+ const interpreterPath = path . join ( 'path' , 'from' , 'interpreter' ) ;
585+ const config = {
586+ pythonPath : '${command:python.interpreterPath}' ,
587+ python : pythonValue ,
588+ } ;
589+
590+ getInterpreterDetailsStub . resolves ( { path : [ interpreterPath ] } as unknown as PythonEnvironment ) ;
591+
592+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
593+
594+ expect ( resolver . getPythonPathSource ( ) ) . to . equal ( PythonPathSource . launchJson ) ;
595+ } ) ;
596+
597+ test ( 'pythonPathSource should be settingsJson when both pythonPath and python are ${command:python.interpreterPath}' , async ( ) => {
598+ const interpreterPath = path . join ( 'path' , 'from' , 'interpreter' ) ;
599+ const config = {
600+ pythonPath : '${command:python.interpreterPath}' ,
601+ python : '${command:python.interpreterPath}' ,
602+ } ;
603+
604+ getInterpreterDetailsStub . resolves ( { path : [ interpreterPath ] } as unknown as PythonEnvironment ) ;
605+
606+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
607+
608+ expect ( resolver . getPythonPathSource ( ) ) . to . equal ( PythonPathSource . settingsJson ) ;
609+ } ) ;
610+
611+ test ( 'pythonPathSource should be settingsJson when neither pythonPath nor python are set' , async ( ) => {
612+ const interpreterPath = path . join ( 'path' , 'from' , 'interpreter' ) ;
613+ const config = { } ;
614+
615+ getInterpreterDetailsStub . resolves ( { path : [ interpreterPath ] } as unknown as PythonEnvironment ) ;
616+
617+ await resolver . resolveAndUpdatePythonPath ( undefined , config as LaunchRequestArguments ) ;
618+
619+ expect ( resolver . getPythonPathSource ( ) ) . to . equal ( PythonPathSource . settingsJson ) ;
620+ } ) ;
621+ } ) ;
622+
273623 const localHostTestMatrix : Record < string , boolean > = {
274624 localhost : true ,
275625 '127.0.0.1' : true ,
0 commit comments