Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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('Switch Language Server')
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