Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
* Fix issues related to debugging Django templagtes.
1. Update the Python language server to 0.2.44.

### Code Health

1. Capture telemetry to track switching to and from the Language Server.
([#5162](https://github.com/Microsoft/vscode-python/issues/5162))


## 2019.3.2 (2 April 2019)

### Fixes
Expand Down
24 changes: 20 additions & 4 deletions src/client/activation/activationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { IDiagnosticsService } from '../application/diagnostics/types';
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types';
import { STANDARD_OUTPUT_CHANNEL } from '../common/constants';
import '../common/extensions';
import { IConfigurationService, IDisposableRegistry, IOutputChannel, IPythonSettings, Resource } from '../common/types';
import { IConfigurationService, IDisposableRegistry, IOutputChannel, IPersistentStateFactory, IPythonSettings, Resource } from '../common/types';
import { swallowExceptions } from '../common/utils/decorators';
import { IServiceContainer } from '../ioc/types';
import { sendTelemetryEvent } from '../telemetry';
import { EventName } from '../telemetry/constants';
Expand All @@ -31,7 +32,8 @@ export class LanguageServerExtensionActivationService implements IExtensionActiv
private readonly lsNotSupportedDiagnosticService: IDiagnosticsService;
private resource!: Resource;

constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer,
@inject(IPersistentStateFactory) private stateFactory: IPersistentStateFactory) {
this.workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
this.output = this.serviceContainer.get<OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);
this.appShell = this.serviceContainer.get<IApplicationShell>(IApplicationShell);
Expand Down Expand Up @@ -98,7 +100,19 @@ export class LanguageServerExtensionActivationService implements IExtensionActiv
this.currentActivator.activator.dispose();
}
}

@swallowExceptions('Swtich Language Server')
Comment thread
DonJayamanne marked this conversation as resolved.
Outdated
public async trackLangaugeServerSwitch(jediEnabled: boolean): Promise<void> {
const state = this.stateFactory.createGlobalPersistentState<boolean | undefined>('SWITCH_LS', undefined);
if (typeof state.value !== 'boolean') {
await state.updateValue(jediEnabled);
return;
}
if (state.value !== jediEnabled) {
await state.updateValue(jediEnabled);
const message = jediEnabled ? 'Switch to Jedi from LS' : 'Switch to LS from Jedi';
sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_SWITCHED, undefined, { change: message });
}
}
protected onWorkspaceFoldersChanged() {
//If an activated workspace folder was removed, dispose its activator
const workspaceKeys = this.workspaceService.workspaceFolders!.map(workspaceFolder => this.getWorkspacePathKey(workspaceFolder.uri));
Expand Down Expand Up @@ -141,7 +155,9 @@ export class LanguageServerExtensionActivationService implements IExtensionActiv
}
private useJedi(): boolean {
const configurationService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
return configurationService.getSettings(this.resource).jediEnabled;
const enabled = configurationService.getSettings(this.resource).jediEnabled;
this.trackLangaugeServerSwitch(enabled).ignoreErrors();
return enabled;
}
private getWorkspacePathKey(resource: Resource): string {
return this.workspaceService.getWorkspaceFolderIdentifier(resource, workspacePathNameForGlobalWorkspaces);
Expand Down
1 change: 1 addition & 0 deletions src/client/telemetry/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export enum EventName {
UNITTEST_NAVIGATE_TEST_FUNCTION = 'UNITTEST.NAVIGATE.TEST_FUNCTION',
UNITTEST_NAVIGATE_TEST_SUITE = 'UNITTEST.NAVIGATE.TEST_SUITE',
UNITTEST_EXPLORER_WORK_SPACE_COUNT = 'UNITTEST.TEST_EXPLORER.WORK_SPACE_COUNT',
PYTHON_LANGUAGE_SERVER_SWITCHED = 'PYTHON_LANGUAGE_SERVER.SWITCHED',
PYTHON_LANGUAGE_SERVER_ANALYSISTIME = 'PYTHON_LANGUAGE_SERVER.ANALYSIS_TIME',
PYTHON_LANGUAGE_SERVER_ENABLED = 'PYTHON_LANGUAGE_SERVER.ENABLED',
PYTHON_LANGUAGE_SERVER_EXTRACTED = 'PYTHON_LANGUAGE_SERVER.EXTRACTED',
Expand Down
1 change: 1 addition & 0 deletions src/client/telemetry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ export interface IEventNamePropertyMapping {
[EventName.PYTHON_INTERPRETER_ACTIVATION_FOR_TERMINAL]: InterpreterActivation;
[EventName.PYTHON_INTERPRETER_AUTO_SELECTION]: InterpreterAutoSelection;
[EventName.PYTHON_INTERPRETER_DISCOVERY]: InterpreterDiscovery;
[EventName.PYTHON_LANGUAGE_SERVER_SWITCHED]: { change: 'Switch to Jedi from LS' | 'Switch to LS from Jedi' };
[EventName.PYTHON_LANGUAGE_SERVER_ANALYSISTIME]: { success: boolean };
[EventName.PYTHON_LANGUAGE_SERVER_DOWNLOADED]: LanguageServerVersionTelemetry;
[EventName.PYTHON_LANGUAGE_SERVER_ENABLED]: never | undefined;
Expand Down
67 changes: 53 additions & 14 deletions src/test/activation/activationService.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import { LSNotSupportedDiagnosticServiceId } from '../../client/application/diag
import { IDiagnostic, IDiagnosticsService } from '../../client/application/diagnostics/types';
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../client/common/application/types';
import { IPlatformService } from '../../client/common/platform/types';
import { IConfigurationService, IDisposable, IDisposableRegistry, IOutputChannel, IPythonSettings, Resource } from '../../client/common/types';
import { IConfigurationService, IDisposable, IDisposableRegistry, IOutputChannel, IPersistentState, IPersistentStateFactory, IPythonSettings, Resource } from '../../client/common/types';
import { IServiceContainer } from '../../client/ioc/types';

// tslint:disable:no-any

suite('Activation - ActivationService', () => {
[true, false].forEach(jediIsEnabled => {
suite(`Jedi is ${jediIsEnabled ? 'enabled' : 'disabled'}`, () => {
Expand All @@ -34,12 +36,16 @@ suite('Activation - ActivationService', () => {
let workspaceService: TypeMoq.IMock<IWorkspaceService>;
let platformService: TypeMoq.IMock<IPlatformService>;
let lsNotSupportedDiagnosticService: TypeMoq.IMock<IDiagnosticsService>;
let stateFactory: TypeMoq.IMock<IPersistentStateFactory>;
let state: TypeMoq.IMock<IPersistentState<boolean | undefined>>;
setup(() => {
serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
appShell = TypeMoq.Mock.ofType<IApplicationShell>();
workspaceService = TypeMoq.Mock.ofType<IWorkspaceService>();
cmdManager = TypeMoq.Mock.ofType<ICommandManager>();
platformService = TypeMoq.Mock.ofType<IPlatformService>();
stateFactory = TypeMoq.Mock.ofType<IPersistentStateFactory>();
state = TypeMoq.Mock.ofType<IPersistentState<boolean | undefined>>();
const configService = TypeMoq.Mock.ofType<IConfigurationService>();
pythonSettings = TypeMoq.Mock.ofType<IPythonSettings>();
const langFolderServiceMock = TypeMoq.Mock.ofType<ILanguageServerFolderService>();
Expand All @@ -54,7 +60,10 @@ suite('Activation - ActivationService', () => {
langFolderServiceMock
.setup(l => l.getCurrentLanguageServerDirectory())
.returns(() => Promise.resolve(folderVer));

stateFactory.setup(f => f.createGlobalPersistentState(TypeMoq.It.isValue('SWITCH_LS'), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns(() => state.object);
state.setup(s => s.value).returns(() => undefined);
state.setup(s => s.updateValue(TypeMoq.It.isAny())).returns(() => Promise.resolve());
const output = TypeMoq.Mock.ofType<IOutputChannel>();
serviceContainer
.setup(c => c.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isAny()))
Expand Down Expand Up @@ -101,7 +110,7 @@ suite('Activation - ActivationService', () => {
if (lsSupported && !jediIsEnabled) {
activatorName = LanguageServerActivator.DotNet;
}
let diagnostics : IDiagnostic[];
let diagnostics: IDiagnostic[];
if (!lsSupported && !jediIsEnabled) {
diagnostics = [TypeMoq.It.isAny()];
} else {
Expand All @@ -127,29 +136,29 @@ suite('Activation - ActivationService', () => {
test('LS is supported', async () => {
pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabled);
const activator = TypeMoq.Mock.ofType<ILanguageServerActivator>();
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object);
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);

await testActivation(activationService, activator, true);
});
test('LS is not supported', async () => {
pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabled);
const activator = TypeMoq.Mock.ofType<ILanguageServerActivator>();
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object);
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);

await testActivation(activationService, activator, false);
});

test('Activatory must be activated', async () => {
pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabled);
const activator = TypeMoq.Mock.ofType<ILanguageServerActivator>();
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object);
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);

await testActivation(activationService, activator);
});
test('Activatory must be deactivated', async () => {
pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabled);
const activator = TypeMoq.Mock.ofType<ILanguageServerActivator>();
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object);
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);

await testActivation(activationService, activator);

Expand All @@ -171,7 +180,7 @@ suite('Activation - ActivationService', () => {

pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabledValueInSetting);
const activator = TypeMoq.Mock.ofType<ILanguageServerActivator>();
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object);
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);

workspaceService.verifyAll();
await testActivation(activationService, activator);
Expand Down Expand Up @@ -208,7 +217,7 @@ suite('Activation - ActivationService', () => {

pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabledValueInSetting);
const activator = TypeMoq.Mock.ofType<ILanguageServerActivator>();
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object);
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);

workspaceService.verifyAll();
await testActivation(activationService, activator);
Expand Down Expand Up @@ -244,7 +253,7 @@ suite('Activation - ActivationService', () => {

pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabled);
const activator = TypeMoq.Mock.ofType<ILanguageServerActivator>();
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object);
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);

workspaceService.verifyAll();
await testActivation(activationService, activator);
Expand Down Expand Up @@ -279,7 +288,7 @@ suite('Activation - ActivationService', () => {

pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabled);
const activator = TypeMoq.Mock.ofType<ILanguageServerActivator>();
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object);
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);

workspaceService.verifyAll();
await testActivation(activationService, activator);
Expand All @@ -304,12 +313,42 @@ suite('Activation - ActivationService', () => {
appShell.verifyAll();
cmdManager.verifyAll();
});
test('Track current LS usage for first usage', async () => {
state.reset();
state.setup(s => s.value).returns(() => undefined).verifiable(TypeMoq.Times.once());
state.setup(s => s.updateValue(TypeMoq.It.isValue(true))).returns(() => Promise.resolve()).verifiable(TypeMoq.Times.once());

const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);
await activationService.trackLangaugeServerSwitch(true);

state.verifyAll();
});
test('Track switch to LS', async () => {
state.reset();
state.setup(s => s.value).returns(() => true).verifiable(TypeMoq.Times.once());
state.setup(s => s.updateValue(TypeMoq.It.isValue(false))).returns(() => Promise.resolve()).verifiable(TypeMoq.Times.once());

const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);
await activationService.trackLangaugeServerSwitch(false);

state.verify(s => s.updateValue(TypeMoq.It.isValue(false)), TypeMoq.Times.once());
});
test('Track switch to Jedi', async () => {
state.reset();
state.setup(s => s.value).returns(() => false).verifiable(TypeMoq.Times.once());
state.setup(s => s.updateValue(TypeMoq.It.isValue(true))).returns(() => Promise.resolve()).verifiable(TypeMoq.Times.once());

const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);
await activationService.trackLangaugeServerSwitch(true);

state.verify(s => s.updateValue(TypeMoq.It.isValue(true)), TypeMoq.Times.once());
});
if (!jediIsEnabled) {
test('Revert to jedi when LS activation fails', async () => {
pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabled);
const activatorDotNet = TypeMoq.Mock.ofType<ILanguageServerActivator>();
const activatorJedi = TypeMoq.Mock.ofType<ILanguageServerActivator>();
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object);
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);
const diagnostics: IDiagnostic[] = [];
lsNotSupportedDiagnosticService
.setup(l => l.diagnose(undefined))
Expand Down Expand Up @@ -388,7 +427,7 @@ suite('Activation - ActivationService', () => {
.callback(cb => (workspaceFoldersChangedHandler = cb))
.returns(() => TypeMoq.Mock.ofType<IDisposable>().object)
.verifiable(TypeMoq.Times.once());
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object);
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);
workspaceService.verifyAll();
expect(workspaceFoldersChangedHandler).not.to.be.equal(undefined, 'Handler not set');
const folder1 = { name: 'one', uri: Uri.parse('one'), index: 1 };
Expand Down Expand Up @@ -430,7 +469,7 @@ suite('Activation - ActivationService', () => {
test('Jedi is only activated once', async () => {
pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabled);
const activator1 = TypeMoq.Mock.ofType<ILanguageServerActivator>();
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object);
const activationService = new LanguageServerExtensionActivationService(serviceContainer.object, stateFactory.object);
const folder1 = { name: 'one', uri: Uri.parse('one'), index: 1 };
const folder2 = { name: 'two', uri: Uri.parse('two'), index: 2 };
serviceContainer
Expand Down