flutter_command_test.dart 8.6 KB
Newer Older
1 2 3 4
// Copyright 2017 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.

5
import 'package:flutter_tools/src/base/common.dart';
6
import 'package:flutter_tools/src/base/io.dart';
7 8 9
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
10
import 'package:flutter_tools/src/runner/flutter_command.dart';
11
import 'package:flutter_tools/src/version.dart';
12 13
import 'package:mockito/mockito.dart';

14 15
import '../../src/common.dart';
import '../../src/context.dart';
16
import 'utils.dart';
17

18
void main() {
19
  group('Flutter Command', () {
20 21
    MockitoCache cache;
    MockitoUsage usage;
22
    MockClock clock;
23
    MockProcessInfo mockProcessInfo;
24
    List<int> mockTimes;
25 26

    setUp(() {
27 28
      cache = MockitoCache();
      usage = MockitoUsage();
29
      clock = MockClock();
30 31
      mockProcessInfo = MockProcessInfo();

32 33
      when(usage.isFirstRun).thenReturn(false);
      when(clock.now()).thenAnswer(
34
        (Invocation _) => DateTime.fromMillisecondsSinceEpoch(mockTimes.removeAt(0))
35
      );
36
      when(mockProcessInfo.maxRss).thenReturn(10);
37 38 39
    });

    testUsingContext('honors shouldUpdateCache false', () async {
40
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(shouldUpdateCache: false);
41 42 43 44 45 46 47 48
      await flutterCommand.run();
      verifyZeroInteractions(cache);
    },
    overrides: <Type, Generator>{
      Cache: () => cache,
    });

    testUsingContext('honors shouldUpdateCache true', () async {
49
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(shouldUpdateCache: true);
50
      await flutterCommand.run();
51
      verify(cache.updateAll(any)).called(1);
52 53 54 55
    },
    overrides: <Type, Generator>{
      Cache: () => cache,
    });
56

57 58 59 60 61 62 63 64 65
    void testUsingCommandContext(String testName, Function testBody) {
      testUsingContext(testName, testBody, overrides: <Type, Generator>{
        ProcessInfo: () => mockProcessInfo,
        SystemClock: () => clock,
        Usage: () => usage,
      });
    }

    testUsingCommandContext('reports command that results in success', () async {
66 67 68 69 70 71 72 73 74 75
      // Crash if called a third time which is unexpected.
      mockTimes = <int>[1000, 2000];

      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
        commandFunction: () async {
          return const FlutterCommandResult(ExitStatus.success);
        }
      );
      await flutterCommand.run();

76
      verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters')));
77
      verify(usage.sendEvent(captureAny, 'success', parameters: captureAnyNamed('parameters')));
78 79
    });

80
    testUsingCommandContext('reports command that results in warning', () async {
81 82 83 84 85 86 87 88 89 90
      // Crash if called a third time which is unexpected.
      mockTimes = <int>[1000, 2000];

      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
        commandFunction: () async {
          return const FlutterCommandResult(ExitStatus.warning);
        }
      );
      await flutterCommand.run();

91
      verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters')));
92
      verify(usage.sendEvent(captureAny, 'warning', parameters: captureAnyNamed('parameters')));
93 94
    });

95
    testUsingCommandContext('reports command that results in failure', () async {
96 97 98 99 100 101 102 103 104 105 106 107
      // Crash if called a third time which is unexpected.
      mockTimes = <int>[1000, 2000];

      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
        commandFunction: () async {
          return const FlutterCommandResult(ExitStatus.fail);
        }
      );

      try {
        await flutterCommand.run();
      } on ToolExit {
108
        verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters')));
109
        verify(usage.sendEvent(captureAny, 'fail', parameters: captureAnyNamed('parameters')));
110 111 112
      }
    });

113
    testUsingCommandContext('reports command that results in error', () async {
114 115 116 117 118 119 120 121 122 123 124 125 126 127
      // Crash if called a third time which is unexpected.
      mockTimes = <int>[1000, 2000];

      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
        commandFunction: () async {
          throwToolExit('fail');
          return null; // unreachable
        }
      );

      try {
        await flutterCommand.run();
        fail('Mock should make this fail');
      } on ToolExit {
128
        verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters')));
129
        verify(usage.sendEvent(captureAny, 'fail', parameters: captureAnyNamed('parameters')));
130 131 132
      }
    });

133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
    testUsingCommandContext('reports maxRss', () async {
      // Crash if called a third time which is unexpected.
      mockTimes = <int>[1000, 2000];

      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
        commandFunction: () async {
          return const FlutterCommandResult(ExitStatus.success);
        }
      );
      await flutterCommand.run();

      verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters')));
      expect(verify(usage.sendEvent(
          any,
          'success',
          parameters: captureAnyNamed('parameters'),
        )).captured[0],
        containsPair(cdKey(CustomDimensions.commandResultEventMaxRss),
            mockProcessInfo.maxRss.toString()));
    });

    testUsingCommandContext('report execution timing by default', () async {
155 156 157
      // Crash if called a third time which is unexpected.
      mockTimes = <int>[1000, 2000];

158
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
159 160 161 162
      await flutterCommand.run();
      verify(clock.now()).called(2);

      expect(
163
        verify(usage.sendTiming(
164
                captureAny, captureAny, captureAny,
165
                label: captureAnyNamed('label'))).captured,
166 167 168 169
        <dynamic>[
          'flutter',
          'dummy',
          const Duration(milliseconds: 1000),
170
          null,
171
        ],
172 173 174
      );
    });

175
    testUsingCommandContext('no timing report without usagePath', () async {
176 177 178
      // Crash if called a third time which is unexpected.
      mockTimes = <int>[1000, 2000];

179
      final DummyFlutterCommand flutterCommand =
180
          DummyFlutterCommand(noUsagePath: true);
181 182
      await flutterCommand.run();
      verify(clock.now()).called(2);
183
      verifyNever(usage.sendTiming(
184 185
                   any, any, any,
                   label: anyNamed('label')));
186
    });
187

188
    testUsingCommandContext('report additional FlutterCommandResult data', () async {
189 190 191
      // Crash if called a third time which is unexpected.
      mockTimes = <int>[1000, 2000];

192
      final FlutterCommandResult commandResult = FlutterCommandResult(
193
        ExitStatus.success,
194
        // nulls should be cleaned up.
195
        timingLabelParts: <String> ['blah1', 'blah2', null, 'blah3'],
196
        endTimeOverride: DateTime.fromMillisecondsSinceEpoch(1500),
197 198
      );

199
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
200 201
        commandFunction: () async => commandResult
      );
202 203 204
      await flutterCommand.run();
      verify(clock.now()).called(2);
      expect(
205
        verify(usage.sendTiming(
206
                captureAny, captureAny, captureAny,
207
                label: captureAnyNamed('label'))).captured,
208
        <dynamic>[
209 210
          'flutter',
          'dummy',
211
          const Duration(milliseconds: 500), // FlutterCommandResult's end time used instead.
212
          'success-blah1-blah2-blah3',
213
        ],
214 215 216
      );
    });

217
    testUsingCommandContext('report failed execution timing too', () async {
218 219 220
      // Crash if called a third time which is unexpected.
      mockTimes = <int>[1000, 2000];

221
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
222 223 224 225 226
        commandFunction: () async {
          throwToolExit('fail');
          return null; // unreachable
        },
      );
227 228 229 230 231 232 233 234 235

      try {
        await flutterCommand.run();
        fail('Mock should make this fail');
      } on ToolExit {
        // Should have still checked time twice.
        verify(clock.now()).called(2);

        expect(
236
          verify(usage.sendTiming(
237
                  captureAny, captureAny, captureAny,
238 239 240 241 242
                  label: captureAnyNamed('label'))).captured,
          <dynamic>[
            'flutter',
            'dummy',
            const Duration(milliseconds: 1000),
243 244
            'fail',
          ],
245 246
        );
      }
247
    });
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
  });
}


class FakeCommand extends FlutterCommand {
  @override
  String get description => null;

  @override
  String get name => 'fake';

  @override
  Future<FlutterCommandResult> runCommand() async {
    return null;
  }
263
}
264 265

class MockVersion extends Mock implements FlutterVersion {}
266
class MockProcessInfo extends Mock implements ProcessInfo {}