analytics_test.dart 8.37 KB
Newer Older
1 2 3 4 5
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:args/command_runner.dart';
6
import 'package:flutter_tools/src/base/config.dart';
7
import 'package:flutter_tools/src/base/time.dart';
8
import 'package:flutter_tools/src/features.dart';
9 10
import 'package:mockito/mockito.dart';

11
import 'package:flutter_tools/src/base/file_system.dart';
12
import 'package:flutter_tools/src/cache.dart';
13
import 'package:flutter_tools/src/commands/build.dart';
14 15
import 'package:flutter_tools/src/commands/config.dart';
import 'package:flutter_tools/src/commands/doctor.dart';
16
import 'package:flutter_tools/src/doctor.dart';
17
import 'package:flutter_tools/src/runner/flutter_command.dart';
18
import 'package:flutter_tools/src/reporting/usage.dart';
19
import 'package:flutter_tools/src/version.dart';
20

21 22
import '../src/common.dart';
import '../src/context.dart';
23 24 25

void main() {
  group('analytics', () {
26
    Directory tempDir;
27
    MockFlutterConfig mockFlutterConfig;
28

29 30 31 32
    setUpAll(() {
      Cache.disableLocking();
    });

33
    setUp(() {
34
      Cache.flutterRoot = '../..';
35
      tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_analytics_test.');
36
      mockFlutterConfig = MockFlutterConfig();
37 38 39
    });

    tearDown(() {
40
      tryToDelete(tempDir);
41 42 43 44 45 46 47 48
    });

    // Ensure we don't send anything when analytics is disabled.
    testUsingContext('doesn\'t send when disabled', () async {
      int count = 0;
      flutterUsage.onSend.listen((Map<String, dynamic> data) => count++);

      flutterUsage.enabled = false;
49
      await createProject(tempDir);
50 51 52
      expect(count, 0);

      flutterUsage.enabled = true;
53
      await createProject(tempDir);
54 55 56 57
      expect(count, flutterUsage.isFirstRun ? 0 : 2);

      count = 0;
      flutterUsage.enabled = false;
58
      final DoctorCommand doctorCommand = DoctorCommand();
59
      final CommandRunner<void>runner = createTestCommandRunner(doctorCommand);
60
      await runner.run(<String>['doctor']);
61
      expect(count, 0);
62
    }, overrides: <Type, Generator>{
63
      FlutterVersion: () => FlutterVersion(const SystemClock()),
64
      Usage: () => Usage(configDirOverride: tempDir.path),
65 66
    });

67
    // Ensure we don't send for the 'flutter config' command.
68 69 70 71 72
    testUsingContext('config doesn\'t send', () async {
      int count = 0;
      flutterUsage.onSend.listen((Map<String, dynamic> data) => count++);

      flutterUsage.enabled = false;
73
      final ConfigCommand command = ConfigCommand();
74
      final CommandRunner<void> runner = createTestCommandRunner(command);
75 76 77 78 79 80
      await runner.run(<String>['config']);
      expect(count, 0);

      flutterUsage.enabled = true;
      await runner.run(<String>['config']);
      expect(count, 0);
81
    }, overrides: <Type, Generator>{
82
      FlutterVersion: () => FlutterVersion(const SystemClock()),
83
      Usage: () => Usage(configDirOverride: tempDir.path),
84
    });
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122

    testUsingContext('Usage records one feature in experiment setting', () async {
      when<bool>(mockFlutterConfig.getValue(flutterWebFeature.configSetting))
          .thenReturn(true);
      final Usage usage = Usage();

      usage.suppressAnalytics = false;
      usage.enabled = true;
      final Future<Map<String, dynamic>> data = usage.onSend.first;
      usage.sendCommand('test');

      expect(await data, containsPair(enabledFlutterFeatures, 'enable-web'));
    }, overrides: <Type, Generator>{
      FlutterVersion: () => FlutterVersion(const SystemClock()),
      Usage: () => Usage(configDirOverride: tempDir.path),
      Config: () => mockFlutterConfig,
    });

    testUsingContext('Usage records multiple features in experiment setting', () async {
      when<bool>(mockFlutterConfig.getValue(flutterWebFeature.configSetting))
          .thenReturn(true);
      when<bool>(mockFlutterConfig.getValue(flutterLinuxDesktopFeature.configSetting))
          .thenReturn(true);
      when<bool>(mockFlutterConfig.getValue(flutterMacOSDesktopFeature.configSetting))
          .thenReturn(true);
      final Usage usage = Usage();

      usage.suppressAnalytics = false;
      usage.enabled = true;
      final Future<Map<String, dynamic>> data = usage.onSend.first;
      usage.sendCommand('test');

      expect(await data, containsPair(enabledFlutterFeatures, 'enable-web,enable-linux-desktop,enable-macos-desktop'));
    }, overrides: <Type, Generator>{
      FlutterVersion: () => FlutterVersion(const SystemClock()),
      Usage: () => Usage(configDirOverride: tempDir.path),
      Config: () => mockFlutterConfig,
    });
123
  });
124

125 126
  group('analytics with mocks', () {
    Usage mockUsage;
127
    SystemClock mockClock;
128
    Doctor mockDoctor;
129 130 131
    List<int> mockTimes;

    setUp(() {
132
      mockUsage = MockUsage();
133
      when(mockUsage.isFirstRun).thenReturn(false);
134 135
      mockClock = MockClock();
      mockDoctor = MockDoctor();
136
      when(mockClock.now()).thenAnswer(
137
        (Invocation _) => DateTime.fromMillisecondsSinceEpoch(mockTimes.removeAt(0))
138 139 140 141 142
      );
    });

    testUsingContext('flutter commands send timing events', () async {
      mockTimes = <int>[1000, 2000];
143
      when(mockDoctor.diagnose(androidLicenses: false, verbose: false)).thenAnswer((_) async => true);
144
      final DoctorCommand command = DoctorCommand();
145
      final CommandRunner<void> runner = createTestCommandRunner(command);
146 147 148 149 150
      await runner.run(<String>['doctor']);

      verify(mockClock.now()).called(2);

      expect(
151
        verify(mockUsage.sendTiming(captureAny, captureAny, captureAny, label: captureAnyNamed('label'))).captured,
152
        <dynamic>['flutter', 'doctor', const Duration(milliseconds: 1000), 'success'],
153 154
      );
    }, overrides: <Type, Generator>{
155
      SystemClock: () => mockClock,
156 157 158 159 160 161
      Doctor: () => mockDoctor,
      Usage: () => mockUsage,
    });

    testUsingContext('doctor fail sends warning', () async {
      mockTimes = <int>[1000, 2000];
162
      when(mockDoctor.diagnose(androidLicenses: false, verbose: false)).thenAnswer((_) async => false);
163
      final DoctorCommand command = DoctorCommand();
164
      final CommandRunner<void> runner = createTestCommandRunner(command);
165 166 167 168 169
      await runner.run(<String>['doctor']);

      verify(mockClock.now()).called(2);

      expect(
170
        verify(mockUsage.sendTiming(captureAny, captureAny, captureAny, label: captureAnyNamed('label'))).captured,
171
        <dynamic>['flutter', 'doctor', const Duration(milliseconds: 1000), 'warning'],
172 173
      );
    }, overrides: <Type, Generator>{
174
      SystemClock: () => mockClock,
175
      Doctor: () => mockDoctor,
176 177
      Usage: () => mockUsage,
    });
178 179

    testUsingContext('single command usage path', () async {
180
      final FlutterCommand doctorCommand = DoctorCommand();
181 182 183 184 185 186
      expect(await doctorCommand.usagePath, 'doctor');
    }, overrides: <Type, Generator>{
      Usage: () => mockUsage,
    });

    testUsingContext('compound command usage path', () async {
187
      final BuildCommand buildCommand = BuildCommand();
188 189 190 191 192
      final FlutterCommand buildApkCommand = buildCommand.subcommands['apk'];
      expect(await buildApkCommand.usagePath, 'build/apk');
    }, overrides: <Type, Generator>{
      Usage: () => mockUsage,
    });
193 194
  });

195
  group('analytics bots', () {
196 197
    Directory tempDir;

198
    setUp(() {
199 200 201 202 203
      tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_analytics_bots_test.');
    });

    tearDown(() {
      tryToDelete(tempDir);
204 205
    });

206 207 208 209 210 211
    testUsingContext('don\'t send on bots', () async {
      int count = 0;
      flutterUsage.onSend.listen((Map<String, dynamic> data) => count++);

      await createTestCommandRunner().run(<String>['--version']);
      expect(count, 0);
212
    }, overrides: <Type, Generator>{
213
      Usage: () => Usage(
214 215
        settingsName: 'flutter_bot_test',
        versionOverride: 'dev/unknown',
216
        configDirOverride: tempDir.path,
217 218 219 220 221 222 223 224 225 226 227
      ),
    });

    testUsingContext('don\'t send on bots even when opted in', () async {
      int count = 0;
      flutterUsage.onSend.listen((Map<String, dynamic> data) => count++);
      flutterUsage.enabled = true;

      await createTestCommandRunner().run(<String>['--version']);
      expect(count, 0);
    }, overrides: <Type, Generator>{
228
      Usage: () => Usage(
229 230
        settingsName: 'flutter_bot_test',
        versionOverride: 'dev/unknown',
231
        configDirOverride: tempDir.path,
232
      ),
233 234
    });
  });
235
}
236 237

class MockUsage extends Mock implements Usage {}
238 239

class MockDoctor extends Mock implements Doctor {}
240 241

class MockFlutterConfig extends Mock implements Config {}