Skip to content

Commit

Permalink
Experiments should not block on telemetry. (#10940)
Browse files Browse the repository at this point in the history
* Experiments should not block on telemetry.

* Fix tests

* Fix linter issue
  • Loading branch information
karthiknadig authored Apr 3, 2020
1 parent 8afe46f commit dfbfed3
Show file tree
Hide file tree
Showing 6 changed files with 13 additions and 90 deletions.
1 change: 1 addition & 0 deletions news/2 Fixes/10008.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Experiments no longer block on telemetry.
11 changes: 7 additions & 4 deletions src/client/common/experiments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { inject, injectable, named, optional } from 'inversify';
import { parse } from 'jsonc-parser';
import * as path from 'path';
import { IConfigurationService, IHttpClient } from '../common/types';
import { isTelemetryDisabled, sendTelemetryEvent } from '../telemetry';
import { sendTelemetryEvent } from '../telemetry';
import { EventName } from '../telemetry/constants';
import { IApplicationEnvironment, IWorkspaceService } from './application/types';
import { IApplicationEnvironment } from './application/types';
import { EXTENSION_ROOT_DIR, STANDARD_OUTPUT_CHANNEL } from './constants';
import { traceDecorators, traceError } from './logger';
import { IFileSystem } from './platform/types';
Expand Down Expand Up @@ -87,7 +87,6 @@ export class ExperimentsManager implements IExperimentsManager {
private activatedOnce: boolean = false;
constructor(
@inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory,
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService,
@inject(IHttpClient) private readonly httpClient: IHttpClient,
@inject(ICryptoUtils) private readonly crypto: ICryptoUtils,
@inject(IApplicationEnvironment) private readonly appEnvironment: IApplicationEnvironment,
Expand Down Expand Up @@ -116,7 +115,7 @@ export class ExperimentsManager implements IExperimentsManager {

@swallowExceptions('Failed to activate experiments')
public async activate(): Promise<void> {
if (this.activatedOnce || isTelemetryDisabled(this.workspaceService)) {
if (this.activatedOnce) {
return;
}
this.activatedOnce = true;
Expand Down Expand Up @@ -319,6 +318,10 @@ export class ExperimentsManager implements IExperimentsManager {
}
}

public _activated(): boolean {
return this.activatedOnce;
}

/**
* You can only opt in or out of experiment groups, not control groups. So remove requests for control groups.
*/
Expand Down
82 changes: 5 additions & 77 deletions src/test/common/experiments.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@

import { assert, expect } from 'chai';
import * as sinon from 'sinon';
import { anything, instance, mock, resetCalls, verify, when } from 'ts-mockito';
import { anything, instance, mock, verify, when } from 'ts-mockito';
import * as TypeMoq from 'typemoq';
import { WorkspaceConfiguration } from 'vscode';
import { ApplicationEnvironment } from '../../client/common/application/applicationEnvironment';
import { IApplicationEnvironment, IWorkspaceService } from '../../client/common/application/types';
import { WorkspaceService } from '../../client/common/application/workspace';
import { IApplicationEnvironment } from '../../client/common/application/types';
import { PythonSettings } from '../../client/common/configSettings';
import { ConfigurationService } from '../../client/common/configuration/service';
import { CryptoUtils } from '../../client/common/crypto';
Expand Down Expand Up @@ -43,7 +41,6 @@ import { noop } from '../core';
// tslint:disable: max-func-body-length

suite('A/B experiments', () => {
let workspaceService: IWorkspaceService;
let httpClient: IHttpClient;
let crypto: ICryptoUtils;
let appEnvironment: IApplicationEnvironment;
Expand All @@ -57,7 +54,6 @@ suite('A/B experiments', () => {
let configurationService: ConfigurationService;
let experiments: TypeMoq.IMock<IExperiments>;
setup(() => {
workspaceService = mock(WorkspaceService);
httpClient = mock(HttpClient);
crypto = mock(CryptoUtils);
appEnvironment = mock(ApplicationEnvironment);
Expand Down Expand Up @@ -85,7 +81,6 @@ suite('A/B experiments', () => {
).thenReturn(downloadedExperimentsStorage.object);
expManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand Down Expand Up @@ -188,50 +183,20 @@ suite('A/B experiments', () => {
verify(httpClient.getJSON(configUri, false)).once();
});

test('If the users have opted out of telemetry, then they are opted out of AB testing ', async () => {
const workspaceConfig = TypeMoq.Mock.ofType<WorkspaceConfiguration>();
const settings = { globalValue: false };

when(workspaceService.getConfiguration('telemetry')).thenReturn(workspaceConfig.object);
workspaceConfig
.setup((c) => c.inspect<boolean>('enableTelemetry'))
.returns(() => settings as any)
.verifiable(TypeMoq.Times.once());
downloadedExperimentsStorage
.setup((n) => n.value)
.returns(() => undefined)
.verifiable(TypeMoq.Times.never());

await expManager.activate();

verify(workspaceService.getConfiguration('telemetry')).once();
workspaceConfig.verifyAll();
downloadedExperimentsStorage.verifyAll();
});

async function testEnablingExperiments(enabled: boolean) {
const updateExperimentStorage = sinon.stub(ExperimentsManager.prototype, 'updateExperimentStorage');
updateExperimentStorage.callsFake(() => Promise.resolve());
const populateUserExperiments = sinon.stub(ExperimentsManager.prototype, 'populateUserExperiments');
populateUserExperiments.callsFake(() => Promise.resolve());
const initializeInBackground = sinon.stub(ExperimentsManager.prototype, 'initializeInBackground');
initializeInBackground.callsFake(() => Promise.resolve());
const workspaceConfig = TypeMoq.Mock.ofType<WorkspaceConfiguration>();
const settings = {};
experiments
.setup((e) => e.enabled)
.returns(() => enabled)
.verifiable(TypeMoq.Times.atLeastOnce());

when(workspaceService.getConfiguration('telemetry')).thenReturn(workspaceConfig.object);
workspaceConfig
.setup((c) => c.inspect<boolean>('enableTelemetry'))
.returns(() => settings as any)
.verifiable(TypeMoq.Times.once());

expManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand All @@ -246,7 +211,6 @@ suite('A/B experiments', () => {
assert.equal(populateUserExperiments.callCount, enabled ? 1 : 0);
assert.equal(initializeInBackground.callCount, enabled ? 1 : 0);

workspaceConfig.verifyAll();
experiments.verifyAll();
}
test('Ensure experiments are not initialized when it is disabled', async () => testEnablingExperiments(false));
Expand All @@ -263,7 +227,6 @@ suite('A/B experiments', () => {

expManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand Down Expand Up @@ -295,41 +258,23 @@ suite('A/B experiments', () => {
initializeInBackground.callsFake(() => Promise.resolve());
expManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
output.object,
instance(fs),
instance(configurationService)
);
// Activate it twice and check
const workspaceConfig = TypeMoq.Mock.ofType<WorkspaceConfiguration>();
const settings = {};

when(workspaceService.getConfiguration('telemetry')).thenReturn(workspaceConfig.object);
workspaceConfig
.setup((c) => c.inspect<boolean>('enableTelemetry'))
.returns(() => settings as any)
.verifiable(TypeMoq.Times.once());

// First activation
await expManager.activate();

resetCalls(workspaceService);

// Second activation
assert.isFalse(expManager._activated());
await expManager.activate();

verify(workspaceService.getConfiguration(anything())).never();

workspaceConfig.verifyAll();
// Ensure activated flag is set
assert.isTrue(expManager._activated());
});

test('Ensure experiments are reliably downloaded in the background', async () => {
const experimentsDeferred = createDeferred<void>();
const workspaceConfig = TypeMoq.Mock.ofType<WorkspaceConfiguration>();
const settings = {};
const updateExperimentStorage = sinon.stub(ExperimentsManager.prototype, 'updateExperimentStorage');
updateExperimentStorage.callsFake(() => Promise.resolve());
const populateUserExperiments = sinon.stub(ExperimentsManager.prototype, 'populateUserExperiments');
Expand All @@ -338,7 +283,6 @@ suite('A/B experiments', () => {
initializeInBackground.callsFake(() => experimentsDeferred.promise);
expManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand All @@ -347,12 +291,6 @@ suite('A/B experiments', () => {
instance(configurationService)
);

when(workspaceService.getConfiguration('telemetry')).thenReturn(workspaceConfig.object);
workspaceConfig
.setup((c) => c.inspect<boolean>('enableTelemetry'))
.returns(() => settings as any)
.verifiable(TypeMoq.Times.once());

const promise = expManager.activate();
const deferred = createDeferredFromPromise(promise);
await sleep(1);
Expand All @@ -363,8 +301,6 @@ suite('A/B experiments', () => {
experimentsDeferred.resolve();
await sleep(1);

verify(workspaceService.getConfiguration('telemetry')).once();
workspaceConfig.verifyAll();
assert.ok(initializeInBackground.calledOnce);
});

Expand Down Expand Up @@ -396,7 +332,6 @@ suite('A/B experiments', () => {
doBestEffortToPopulateExperiments.callsFake(() => Promise.resolve(false));
expManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand Down Expand Up @@ -438,7 +373,6 @@ suite('A/B experiments', () => {
doBestEffortToPopulateExperiments.callsFake(() => Promise.resolve(true));
expManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand Down Expand Up @@ -476,7 +410,6 @@ suite('A/B experiments', () => {
doBestEffortToPopulateExperiments.callsFake(() => Promise.resolve(false));
expManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand Down Expand Up @@ -528,7 +461,6 @@ suite('A/B experiments', () => {
doBestEffortToPopulateExperiments.callsFake(() => Promise.resolve(false));
expManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand Down Expand Up @@ -583,7 +515,6 @@ suite('A/B experiments', () => {
doBestEffortToPopulateExperiments.callsFake(() => Promise.resolve(false));
expManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand Down Expand Up @@ -1022,7 +953,6 @@ suite('A/B experiments', () => {
downloadAndStoreExperiments.callsFake(() => downloadExperimentsDeferred.promise);
expManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand All @@ -1047,7 +977,6 @@ suite('A/B experiments', () => {
downloadAndStoreExperiments.callsFake(() => downloadExperimentsDeferred.promise);
expManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand All @@ -1070,7 +999,6 @@ suite('A/B experiments', () => {
downloadAndStoreExperiments.callsFake(() => Promise.reject('Kaboom'));
expManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand Down
3 changes: 0 additions & 3 deletions src/test/debugger/extension/adapter/factory.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { DebugAdapterExecutable, DebugAdapterServer, DebugConfiguration, DebugSe
import { ApplicationEnvironment } from '../../../../client/common/application/applicationEnvironment';
import { ApplicationShell } from '../../../../client/common/application/applicationShell';
import { IApplicationShell } from '../../../../client/common/application/types';
import { WorkspaceService } from '../../../../client/common/application/workspace';
import { ConfigurationService } from '../../../../client/common/configuration/service';
import { CryptoUtils } from '../../../../client/common/crypto';
import { DebugAdapterNewPtvsd } from '../../../../client/common/experimentGroups';
Expand Down Expand Up @@ -103,7 +102,6 @@ suite('Debugging - Adapter Factory', () => {
rewiremock.enable();
rewiremock('vscode-extension-telemetry').with({ default: Reporter });

const workspaceService = mock(WorkspaceService);
const httpClient = mock(HttpClient);
const crypto = mock(CryptoUtils);
const appEnvironment = mock(ApplicationEnvironment);
Expand All @@ -117,7 +115,6 @@ suite('Debugging - Adapter Factory', () => {
} as any) as IPythonSettings);
experimentsManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { DebugProtocol } from 'vscode-debugprotocol';
import { ApplicationEnvironment } from '../../../../client/common/application/applicationEnvironment';
import { ApplicationShell } from '../../../../client/common/application/applicationShell';
import { IApplicationShell } from '../../../../client/common/application/types';
import { WorkspaceService } from '../../../../client/common/application/workspace';
import { ConfigurationService } from '../../../../client/common/configuration/service';
import { CryptoUtils } from '../../../../client/common/crypto';
import { DebugAdapterNewPtvsd } from '../../../../client/common/experimentGroups';
Expand Down Expand Up @@ -49,7 +48,6 @@ suite('Debugging - Outdated Debugger Prompt tests.', () => {
};

setup(() => {
const workspaceService = mock(WorkspaceService);
const httpClient = mock(HttpClient);
const crypto = mock(CryptoUtils);
const appEnvironment = mock(ApplicationEnvironment);
Expand All @@ -63,7 +61,6 @@ suite('Debugging - Outdated Debugger Prompt tests.', () => {
} as any) as IPythonSettings);
experimentsManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import * as assert from 'assert';
import rewiremock from 'rewiremock';
import { instance, mock, spy, when } from 'ts-mockito';
import { ApplicationEnvironment } from '../../../../../client/common/application/applicationEnvironment';
import { WorkspaceService } from '../../../../../client/common/application/workspace';
import { ConfigurationService } from '../../../../../client/common/configuration/service';
import { CryptoUtils } from '../../../../../client/common/crypto';
import { DebugAdapterNewPtvsd, WebAppReload } from '../../../../../client/common/experimentGroups';
Expand Down Expand Up @@ -60,7 +59,6 @@ suite('Debugging - Config Resolver Launch Experiments', () => {
rewiremock.enable();
rewiremock('vscode-extension-telemetry').with({ default: Reporter });

const workspaceService = mock(WorkspaceService);
const httpClient = mock(HttpClient);
const crypto = mock(CryptoUtils);
const appEnvironment = mock(ApplicationEnvironment);
Expand All @@ -75,7 +73,6 @@ suite('Debugging - Config Resolver Launch Experiments', () => {
} as any) as IPythonSettings);
experimentsManager = new ExperimentsManager(
instance(persistentStateFactory),
instance(workspaceService),
instance(httpClient),
instance(crypto),
instance(appEnvironment),
Expand Down

0 comments on commit dfbfed3

Please sign in to comment.