Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opting out of telemetry correctly opts out of A/B testing #6272

Merged
merged 3 commits into from
Jun 21, 2019
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
1 change: 1 addition & 0 deletions news/2 Fixes/6270.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Opting out of telemetry correctly opts out of A/B testing
8 changes: 3 additions & 5 deletions src/client/common/experiments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export class ExperimentsManager implements IExperimentsManager {

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

/**
* Downloads experiments and updates storage given following conditions are met
* * Telemetry is not disabled
* * Previously downloaded experiments are no longer valid
* Downloads experiments and updates storage given previously downloaded experiments are no longer valid
*/
@traceDecorators.error('Failed to initialize experiments')
public async initializeInBackground() {
if (isTelemetryDisabled(this.workspaceService) || this.isDownloadedStorageValid.value) {
if (this.isDownloadedStorageValid.value) {
return;
}
const downloadedExperiments = await this.httpClient.getJSON<ABExperiments>(configUri, false);
Expand Down
71 changes: 41 additions & 30 deletions src/test/common/experiments.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// tslint:disable:no-any

import { assert, expect } from 'chai';
import { anything, instance, mock, verify, when } from 'ts-mockito';
import { anything, instance, mock, resetCalls, verify, when } from 'ts-mockito';
import * as TypeMoq from 'typemoq';
import { WorkspaceConfiguration } from 'vscode';
import { ApplicationEnvironment } from '../../client/common/application/applicationEnvironment';
Expand Down Expand Up @@ -53,15 +53,9 @@ suite('A/B experiments', () => {
});

async function testInitialization(
settings: { globalValue?: boolean } = {},
downloadError: boolean = false,
experimentsDownloaded?: any
) {
const workspaceConfig = TypeMoq.Mock.ofType<WorkspaceConfiguration>();
when(workspaceService.getConfiguration('telemetry')).thenReturn(workspaceConfig.object);
workspaceConfig.setup(c => c.inspect<boolean>('enableTelemetry'))
.returns(() => settings as any)
.verifiable(TypeMoq.Times.once());
if (downloadError) {
when(httpClient.getJSON(configUri, false)).thenReject(new Error('Kaboom'));
} else {
Expand All @@ -77,19 +71,10 @@ suite('A/B experiments', () => {
// tslint:disable-next-line:no-empty
} catch { }

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

test('If the users have opted out of telemetry, then they are opted out of AB testing ', async () => {
isDownloadedStorageValid.setup(n => n.value).returns(() => false).verifiable(TypeMoq.Times.never());

// settings = { globalValue: false }
await testInitialization({ globalValue: false });
});

test('Initializing experiments does not download experiments if storage is valid and contains experiments', async () => {
isDownloadedStorageValid.setup(n => n.value).returns(() => true).verifiable(TypeMoq.Times.once());

Expand All @@ -113,8 +98,8 @@ suite('A/B experiments', () => {
.returns(() => Promise.resolve(undefined))
.verifiable(TypeMoq.Times.never());

// settings = {}, downloadError = false, experimentsDownloaded = experiments
await testInitialization({}, false, experiments);
// downloadError = false, experimentsDownloaded = experiments
await testInitialization(false, experiments);
kimadeline marked this conversation as resolved.
Show resolved Hide resolved

verify(httpClient.getJSON(configUri, false)).once();
});
Expand All @@ -134,49 +119,75 @@ suite('A/B experiments', () => {
isDownloadedStorageValid.setup(n => n.updateValue(true)).returns(() => Promise.resolve(undefined)).verifiable(TypeMoq.Times.never());
downloadedExperimentsStorage.setup(n => n.updateValue(anything())).returns(() => Promise.resolve(undefined)).verifiable(TypeMoq.Times.never());

// settings = {}, downloadError = true
await testInitialization({}, true);
// downloadError = true
await testInitialization(true);

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();
});

test('Ensure experiments can only be activated once', async () => {
// 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());

downloadedExperimentsStorage
.setup(n => n.value)
.returns(() => undefined)
.verifiable(TypeMoq.Times.once());
when(fs.fileExists(anything())).thenResolve(false);
when(
fs.fileExists(anything())
).thenResolve(false);
experimentStorage.setup(n => n.value).returns(() => undefined)
.verifiable(TypeMoq.Times.exactly(2));
isDownloadedStorageValid
.setup(n => n.value)
.returns(() => true)
.verifiable(TypeMoq.Times.once());
when(workspaceService.getConfiguration('telemetry')).thenReturn(workspaceConfig.object);
workspaceConfig.setup(c => c.inspect<boolean>('enableTelemetry'))
.returns(() => settings as any);

// First activation
await expManager.activate();

downloadedExperimentsStorage.reset();
downloadedExperimentsStorage
.setup(n => n.value)
.returns(() => undefined)
.verifiable(TypeMoq.Times.never());
resetCalls(workspaceService);

// Second activation
await expManager.activate();

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

workspaceConfig.verifyAll();
verify(fs.fileExists(anything())).once();
isDownloadedStorageValid.verifyAll();
experimentStorage.verifyAll();
downloadedExperimentsStorage.verifyAll();
});

test('Ensure experiments are reliably initialized in the background', async () => {
Expand Down