Skip to content

Commit 97d921f

Browse files
kimadelinekarthiknadig
authored andcommitted
Restart LS if python env info changes (#19080)
* Restart LS if python env info changes * Fix exit condition, add test case for no change
1 parent a976ba6 commit 97d921f

File tree

2 files changed

+230
-2
lines changed

2 files changed

+230
-2
lines changed

src/client/languageServer/watcher.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ export class LanguageServerWatcher
8383
this.workspaceService.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders.bind(this)),
8484
);
8585

86+
this.interpreterService.onDidChangeInterpreterInformation(
87+
this.onDidChangeInterpreterInformation,
88+
this,
89+
disposables,
90+
);
91+
8692
if (this.workspaceService.isTrusted) {
8793
disposables.push(this.interpreterPathService.onDidChange(this.onDidChangeInterpreter.bind(this)));
8894
}
@@ -272,6 +278,28 @@ export class LanguageServerWatcher
272278
return this.activate(event.uri);
273279
}
274280

281+
// Watch for interpreter information changes.
282+
private async onDidChangeInterpreterInformation(info: PythonEnvironment): Promise<void> {
283+
const iterator = this.workspaceInterpreters.entries();
284+
285+
let result = iterator.next();
286+
let done = result.done || false;
287+
288+
while (!done) {
289+
const [resourcePath, interpreter] = result.value as [string, PythonEnvironment | undefined];
290+
const resource = Uri.parse(resourcePath);
291+
292+
// Restart the language server if the interpreter path changed (#18995).
293+
if (info.envPath === interpreter?.envPath && info.path !== interpreter?.path) {
294+
await this.activate(resource);
295+
done = true;
296+
} else {
297+
result = iterator.next();
298+
done = result.done || false;
299+
}
300+
}
301+
}
302+
275303
// Watch for extension changes.
276304
private async extensionsChangeHandler(): Promise<void> {
277305
const languageServerType = this.configurationService.getSettings().languageServer;

src/test/languageServer/watcher.unit.test.ts

Lines changed: 202 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { NoneLSExtensionManager } from '../../client/languageServer/noneLSExtens
2424
import { PylanceLSExtensionManager } from '../../client/languageServer/pylanceLSExtensionManager';
2525
import { LanguageServerWatcher } from '../../client/languageServer/watcher';
2626
import * as Logging from '../../client/logging';
27+
import { PythonEnvironment } from '../../client/pythonEnvironments/info';
2728

2829
suite('Language server watcher', () => {
2930
let watcher: LanguageServerWatcher;
@@ -47,6 +48,9 @@ suite('Language server watcher', () => {
4748
} as unknown) as IInterpreterPathService,
4849
({
4950
getActiveInterpreter: () => 'python',
51+
onDidChangeInterpreterInformation: () => {
52+
/* do nothing */
53+
},
5054
} as unknown) as IInterpreterService,
5155
{} as IEnvironmentVariablesProvider,
5256
({
@@ -95,7 +99,11 @@ suite('Language server watcher', () => {
9599
/* do nothing */
96100
},
97101
} as unknown) as IInterpreterPathService,
98-
{} as IInterpreterService,
102+
({
103+
onDidChangeInterpreterInformation: () => {
104+
/* do nothing */
105+
},
106+
} as unknown) as IInterpreterService,
99107
{} as IEnvironmentVariablesProvider,
100108
({
101109
isTrusted: true,
@@ -138,7 +146,11 @@ suite('Language server watcher', () => {
138146
/* do nothing */
139147
},
140148
} as unknown) as IInterpreterPathService,
141-
{} as IInterpreterService,
149+
({
150+
onDidChangeInterpreterInformation: () => {
151+
/* do nothing */
152+
},
153+
} as unknown) as IInterpreterService,
142154
{} as IEnvironmentVariablesProvider,
143155
({
144156
isTrusted: false,
@@ -180,6 +192,9 @@ suite('Language server watcher', () => {
180192

181193
const interpreterService = ({
182194
getActiveInterpreter: getActiveInterpreterStub,
195+
onDidChangeInterpreterInformation: () => {
196+
/* do nothing */
197+
},
183198
} as unknown) as IInterpreterService;
184199

185200
watcher = new LanguageServerWatcher(
@@ -277,6 +292,9 @@ suite('Language server watcher', () => {
277292
} as unknown) as IInterpreterPathService,
278293
({
279294
getActiveInterpreter: () => 'python',
295+
onDidChangeInterpreterInformation: () => {
296+
/* do nothing */
297+
},
280298
} as unknown) as IInterpreterService,
281299
{} as IEnvironmentVariablesProvider,
282300
({
@@ -360,6 +378,9 @@ suite('Language server watcher', () => {
360378
} as unknown) as IInterpreterPathService,
361379
({
362380
getActiveInterpreter: () => 'python',
381+
onDidChangeInterpreterInformation: () => {
382+
/* do nothing */
383+
},
363384
} as unknown) as IInterpreterService,
364385
{} as IEnvironmentVariablesProvider,
365386
workspaceService,
@@ -426,6 +447,9 @@ suite('Language server watcher', () => {
426447
} as unknown) as IInterpreterPathService,
427448
({
428449
getActiveInterpreter: () => 'python',
450+
onDidChangeInterpreterInformation: () => {
451+
/* do nothing */
452+
},
429453
} as unknown) as IInterpreterService,
430454
{} as IEnvironmentVariablesProvider,
431455
workspaceService,
@@ -477,6 +501,9 @@ suite('Language server watcher', () => {
477501
} as unknown) as IInterpreterPathService,
478502
({
479503
getActiveInterpreter: () => ({ version: { major: 2, minor: 7 } }),
504+
onDidChangeInterpreterInformation: () => {
505+
/* do nothing */
506+
},
480507
} as unknown) as IInterpreterService,
481508
{} as IEnvironmentVariablesProvider,
482509
({
@@ -535,6 +562,9 @@ suite('Language server watcher', () => {
535562
} as unknown) as IInterpreterPathService,
536563
({
537564
getActiveInterpreter: () => ({ version: { major: 2, minor: 7 } }),
565+
onDidChangeInterpreterInformation: () => {
566+
/* do nothing */
567+
},
538568
} as unknown) as IInterpreterService,
539569
{} as IEnvironmentVariablesProvider,
540570
({
@@ -590,6 +620,9 @@ suite('Language server watcher', () => {
590620
} as unknown) as IInterpreterPathService,
591621
({
592622
getActiveInterpreter: () => ({ version: { major: 2, minor: 7 } }),
623+
onDidChangeInterpreterInformation: () => {
624+
/* do nothing */
625+
},
593626
} as unknown) as IInterpreterService,
594627
{} as IEnvironmentVariablesProvider,
595628
({
@@ -673,6 +706,9 @@ suite('Language server watcher', () => {
673706
} as unknown) as IInterpreterPathService,
674707
({
675708
getActiveInterpreter: getActiveInterpreterStub,
709+
onDidChangeInterpreterInformation: () => {
710+
/* do nothing */
711+
},
676712
} as unknown) as IInterpreterService,
677713
{} as IEnvironmentVariablesProvider,
678714
({
@@ -757,6 +793,9 @@ suite('Language server watcher', () => {
757793
} as unknown) as IInterpreterPathService,
758794
({
759795
getActiveInterpreter: () => ({ version: { major: 3, minor: 7 } }),
796+
onDidChangeInterpreterInformation: () => {
797+
/* do nothing */
798+
},
760799
} as unknown) as IInterpreterService,
761800
{} as IEnvironmentVariablesProvider,
762801
workspaceService,
@@ -792,4 +831,165 @@ suite('Language server watcher', () => {
792831
assert.ok(stopLanguageServerStub.notCalled === !multiLS);
793832
});
794833
});
834+
835+
test('The language server should be restarted if the interpreter info changed', async () => {
836+
const info = ({
837+
envPath: 'foo',
838+
path: 'path/to/foo/bin/python',
839+
} as unknown) as PythonEnvironment;
840+
841+
let onDidChangeInfoListener: (event: PythonEnvironment) => Promise<void> = () => Promise.resolve();
842+
843+
const interpreterService = ({
844+
onDidChangeInterpreterInformation: (
845+
listener: (event: PythonEnvironment) => Promise<void>,
846+
thisArg: unknown,
847+
): void => {
848+
onDidChangeInfoListener = listener.bind(thisArg);
849+
},
850+
getActiveInterpreter: () => ({
851+
envPath: 'foo',
852+
path: 'path/to/foo',
853+
}),
854+
} as unknown) as IInterpreterService;
855+
856+
watcher = new LanguageServerWatcher(
857+
({
858+
get: () => {
859+
/* do nothing */
860+
},
861+
} as unknown) as IServiceContainer,
862+
{} as ILanguageServerOutputChannel,
863+
{
864+
getSettings: () => ({ languageServer: LanguageServerType.None }),
865+
} as IConfigurationService,
866+
{} as IExperimentService,
867+
({
868+
getActiveWorkspaceUri: () => undefined,
869+
} as unknown) as IInterpreterHelper,
870+
({
871+
onDidChange: () => {
872+
/* do nothing */
873+
},
874+
} as unknown) as IInterpreterPathService,
875+
interpreterService,
876+
({
877+
onDidEnvironmentVariablesChange: () => {
878+
/* do nothing */
879+
},
880+
} as unknown) as IEnvironmentVariablesProvider,
881+
({
882+
isTrusted: true,
883+
getWorkspaceFolder: (uri: Uri) => ({ uri }),
884+
onDidChangeConfiguration: () => {
885+
/* do nothing */
886+
},
887+
onDidChangeWorkspaceFolders: () => {
888+
/* do nothing */
889+
},
890+
} as unknown) as IWorkspaceService,
891+
({
892+
registerCommand: () => {
893+
/* do nothing */
894+
},
895+
} as unknown) as ICommandManager,
896+
{} as IFileSystem,
897+
({
898+
getExtension: () => undefined,
899+
onDidChange: () => {
900+
/* do nothing */
901+
},
902+
} as unknown) as IExtensions,
903+
{} as IApplicationShell,
904+
[] as Disposable[],
905+
);
906+
907+
const startLanguageServerSpy = sandbox.spy(watcher, 'startLanguageServer');
908+
909+
await watcher.startLanguageServer(LanguageServerType.None);
910+
911+
await onDidChangeInfoListener(info);
912+
913+
// Check that startLanguageServer was called twice: Once above, and once after the interpreter info changed.
914+
assert.ok(startLanguageServerSpy.calledTwice);
915+
});
916+
917+
test('The language server should not be restarted if the interpreter info did not change', async () => {
918+
const info = ({
919+
envPath: 'foo',
920+
path: 'path/to/foo',
921+
} as unknown) as PythonEnvironment;
922+
923+
let onDidChangeInfoListener: (event: PythonEnvironment) => Promise<void> = () => Promise.resolve();
924+
925+
const interpreterService = ({
926+
onDidChangeInterpreterInformation: (
927+
listener: (event: PythonEnvironment) => Promise<void>,
928+
thisArg: unknown,
929+
): void => {
930+
onDidChangeInfoListener = listener.bind(thisArg);
931+
},
932+
getActiveInterpreter: () => info,
933+
} as unknown) as IInterpreterService;
934+
935+
watcher = new LanguageServerWatcher(
936+
({
937+
get: () => {
938+
/* do nothing */
939+
},
940+
} as unknown) as IServiceContainer,
941+
{} as ILanguageServerOutputChannel,
942+
{
943+
getSettings: () => ({ languageServer: LanguageServerType.None }),
944+
} as IConfigurationService,
945+
{} as IExperimentService,
946+
({
947+
getActiveWorkspaceUri: () => undefined,
948+
} as unknown) as IInterpreterHelper,
949+
({
950+
onDidChange: () => {
951+
/* do nothing */
952+
},
953+
} as unknown) as IInterpreterPathService,
954+
interpreterService,
955+
({
956+
onDidEnvironmentVariablesChange: () => {
957+
/* do nothing */
958+
},
959+
} as unknown) as IEnvironmentVariablesProvider,
960+
({
961+
isTrusted: true,
962+
getWorkspaceFolder: (uri: Uri) => ({ uri }),
963+
onDidChangeConfiguration: () => {
964+
/* do nothing */
965+
},
966+
onDidChangeWorkspaceFolders: () => {
967+
/* do nothing */
968+
},
969+
} as unknown) as IWorkspaceService,
970+
({
971+
registerCommand: () => {
972+
/* do nothing */
973+
},
974+
} as unknown) as ICommandManager,
975+
{} as IFileSystem,
976+
({
977+
getExtension: () => undefined,
978+
onDidChange: () => {
979+
/* do nothing */
980+
},
981+
} as unknown) as IExtensions,
982+
{} as IApplicationShell,
983+
[] as Disposable[],
984+
);
985+
986+
const startLanguageServerSpy = sandbox.spy(watcher, 'startLanguageServer');
987+
988+
await watcher.startLanguageServer(LanguageServerType.None);
989+
990+
await onDidChangeInfoListener(info);
991+
992+
// Check that startLanguageServer was called once: Only when startLanguageServer() was called above.
993+
assert.ok(startLanguageServerSpy.calledOnce);
994+
});
795995
});

0 commit comments

Comments
 (0)