resident_runner_test.dart 39 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
6

7
import 'package:vm_service/vm_service.dart' as vm_service;
8
import 'package:file/memory.dart';
9
import 'package:file_testing/file_testing.dart';
10
import 'package:flutter_tools/src/artifacts.dart';
11
import 'package:flutter_tools/src/base/command_help.dart';
12
import 'package:flutter_tools/src/base/common.dart';
13 14
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart';
15
import 'package:flutter_tools/src/base/io.dart' as io;
16
import 'package:flutter_tools/src/build_info.dart';
17
import 'package:flutter_tools/src/compile.dart';
18
import 'package:flutter_tools/src/convert.dart';
19
import 'package:flutter_tools/src/devfs.dart';
20
import 'package:flutter_tools/src/device.dart';
21
import 'package:flutter_tools/src/globals.dart' as globals;
22
import 'package:flutter_tools/src/project.dart';
23
import 'package:flutter_tools/src/reporting/reporting.dart';
24
import 'package:flutter_tools/src/resident_runner.dart';
25
import 'package:flutter_tools/src/run_cold.dart';
26 27
import 'package:flutter_tools/src/run_hot.dart';
import 'package:flutter_tools/src/vmservice.dart';
28 29
import 'package:mockito/mockito.dart';

30
import '../src/common.dart';
31
import '../src/context.dart';
32
import '../src/testbed.dart';
33

34
void main() {
35 36 37 38 39 40 41 42 43
  final Uri testUri = Uri.parse('foo://bar');
  Testbed testbed;
  MockFlutterDevice mockFlutterDevice;
  MockVMService mockVMService;
  MockDevFS mockDevFS;
  MockFlutterView mockFlutterView;
  ResidentRunner residentRunner;
  MockDevice mockDevice;
  MockIsolate mockIsolate;
44
  FakeVmServiceHost fakeVmServiceHost;
45 46 47

  setUp(() {
    testbed = Testbed(setup: () {
48
      globals.fs.file('.packages').writeAsStringSync('\n');
49
      globals.fs.file(globals.fs.path.join('build', 'app.dill'))
50 51
        ..createSync(recursive: true)
        ..writeAsStringSync('ABC');
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
      residentRunner = HotRunner(
        <FlutterDevice>[
          mockFlutterDevice,
        ],
        stayResident: false,
        debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      );
    });
    mockFlutterDevice = MockFlutterDevice();
    mockDevice = MockDevice();
    mockVMService = MockVMService();
    mockDevFS = MockDevFS();
    mockFlutterView = MockFlutterView();
    mockIsolate = MockIsolate();
    // DevFS Mocks
    when(mockDevFS.lastCompiled).thenReturn(DateTime(2000));
    when(mockDevFS.sources).thenReturn(<Uri>[]);
69
    when(mockDevFS.baseUri).thenReturn(Uri());
70
    when(mockDevFS.destroy()).thenAnswer((Invocation invocation) async { });
71
    when(mockDevFS.assetPathsToEvict).thenReturn(<String>{});
72 73
    // FlutterDevice Mocks.
    when(mockFlutterDevice.updateDevFS(
74 75
      invalidatedFiles: anyNamed('invalidatedFiles'),
      mainUri: anyNamed('mainUri'),
76 77 78 79 80 81 82 83
      target: anyNamed('target'),
      bundle: anyNamed('bundle'),
      firstBuildTime: anyNamed('firstBuildTime'),
      bundleFirstUpload: anyNamed('bundleFirstUpload'),
      bundleDirty: anyNamed('bundleDirty'),
      fullRestart: anyNamed('fullRestart'),
      projectRootPath: anyNamed('projectRootPath'),
      pathToReload: anyNamed('pathToReload'),
84
      dillOutputPath: anyNamed('dillOutputPath'),
85
      packageConfig: anyNamed('packageConfig'),
86 87 88 89 90 91 92 93 94
    )).thenAnswer((Invocation invocation) async {
      return UpdateFSReport(
        success: true,
        syncedBytes: 0,
        invalidatedSourcesCount: 0,
      );
    });
    when(mockFlutterDevice.devFS).thenReturn(mockDevFS);
    when(mockFlutterDevice.views).thenReturn(<FlutterView>[
95
      mockFlutterView,
96 97 98 99
    ]);
    when(mockFlutterDevice.device).thenReturn(mockDevice);
    when(mockFlutterView.uiIsolate).thenReturn(mockIsolate);
    when(mockFlutterDevice.stopEchoingDeviceLog()).thenAnswer((Invocation invocation) async { });
100
    when(mockFlutterDevice.observatoryUris).thenAnswer((_) => Stream<Uri>.value(testUri));
101 102 103
    when(mockFlutterDevice.connect(
      reloadSources: anyNamed('reloadSources'),
      restart: anyNamed('restart'),
104
      compileExpression: anyNamed('compileExpression'),
105 106 107 108
    )).thenAnswer((Invocation invocation) async { });
    when(mockFlutterDevice.setupDevFS(any, any, packagesFilePath: anyNamed('packagesFilePath')))
      .thenAnswer((Invocation invocation) async {
        return testUri;
109
      });
110 111 112 113 114 115
    when(mockFlutterDevice.vmService).thenAnswer((Invocation invocation) {
      return fakeVmServiceHost.vmService;
    });
    when(mockFlutterDevice.flutterDeprecatedVmService).thenAnswer((Invocation invocation) {
      return mockVMService;
    });
116
    when(mockFlutterDevice.refreshViews()).thenAnswer((Invocation invocation) async { });
117
    when(mockFlutterDevice.getVMs()).thenAnswer((Invocation invocation) async { });
118 119
    when(mockFlutterDevice.reloadSources(any, pause: anyNamed('pause'))).thenReturn(<Future<vm_service.ReloadReport>>[
      Future<vm_service.ReloadReport>.value(vm_service.ReloadReport.parse(<String, dynamic>{
120 121 122 123 124 125 126 127 128
        'type': 'ReloadReport',
        'success': true,
        'details': <String, dynamic>{
          'loadedLibraryCount': 1,
          'finalLibraryCount': 1,
          'receivedLibraryCount': 1,
          'receivedClassesCount': 1,
          'receivedProceduresCount': 1,
        },
129
      })),
130
    ]);
131 132 133 134 135 136
    // VMService mocks.
    when(mockVMService.wsAddress).thenReturn(testUri);
    when(mockVMService.done).thenAnswer((Invocation invocation) {
      final Completer<void> result = Completer<void>.sync();
      return result.future;
    });
137 138 139
    when(mockIsolate.reload()).thenAnswer((Invocation invocation) {
      return Future<ServiceObject>.value(null);
    });
140 141 142
  });

  test('ResidentRunner can attach to device successfully', () => testbed.run(() async {
143
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
144 145
    final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> onAppStart = Completer<void>.sync();
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
    final Future<int> result = residentRunner.attach(
      appStartedCompleter: onAppStart,
      connectionInfoCompleter: onConnectionInfo,
    );
    final Future<DebugConnectionInfo> connectionInfo = onConnectionInfo.future;

    expect(await result, 0);

    verify(mockFlutterDevice.initLogReader()).called(1);

    expect(onConnectionInfo.isCompleted, true);
    expect((await connectionInfo).baseUri, 'foo://bar');
    expect(onAppStart.isCompleted, true);
  }));

  test('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async {
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      const FakeVmServiceRequest(
        id: '1',
        method: 'streamListen',
        args: <String, Object>{
          'streamId': 'Isolate',
        }
      ),
      const FakeVmServiceRequest(
        id: '2',
        method: kRunInViewMethod,
        args: <String, Object>{
          'viewId': null,
          'mainScript': 'lib/main.dart.dill',
          'assetDirectory': 'build/flutter_assets',
        }
      ),
      FakeVmServiceStreamResponse(
        streamId: 'Isolate',
        event: vm_service.Event(
          timestamp: 0,
          kind: vm_service.EventKind.kIsolateRunnable,
        )
      ),
    ]);
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
    when(mockDevice.supportsHotRestart).thenReturn(true);
    when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
      return 'Example';
    });
    when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
      return TargetPlatform.android_arm;
    });
    when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async {
      return false;
    });
    residentRunner = HotRunner(
      <FlutterDevice>[
        mockFlutterDevice,
      ],
      stayResident: false,
202 203 204 205 206
      debuggingOptions: DebuggingOptions.enabled(
        BuildInfo.debug,
        fastStart: true,
        startPaused: true,
      ),
207 208 209
    );
    final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> onAppStart = Completer<void>.sync();
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
    final Future<int> result = residentRunner.attach(
      appStartedCompleter: onAppStart,
      connectionInfoCompleter: onConnectionInfo,
    );
    final Future<DebugConnectionInfo> connectionInfo = onConnectionInfo.future;

    expect(await result, 0);

    verify(mockFlutterDevice.initLogReader()).called(1);

    expect(onConnectionInfo.isCompleted, true);
    expect((await connectionInfo).baseUri, 'foo://bar');
    expect(onAppStart.isCompleted, true);
  }));

  test('ResidentRunner can handle an RPC exception from hot reload', () => testbed.run(() async {
226
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
    when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
      return 'Example';
    });
    when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
      return TargetPlatform.android_arm;
    });
    when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async {
      return false;
    });
    final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> onAppStart = Completer<void>.sync();
    unawaited(residentRunner.attach(
      appStartedCompleter: onAppStart,
      connectionInfoCompleter: onConnectionInfo,
    ));
    await onAppStart.future;
    when(mockFlutterDevice.updateDevFS(
244
      mainUri: anyNamed('mainUri'),
245 246 247 248 249 250 251 252 253
      target: anyNamed('target'),
      bundle: anyNamed('bundle'),
      firstBuildTime: anyNamed('firstBuildTime'),
      bundleFirstUpload: anyNamed('bundleFirstUpload'),
      bundleDirty: anyNamed('bundleDirty'),
      fullRestart: anyNamed('fullRestart'),
      projectRootPath: anyNamed('projectRootPath'),
      pathToReload: anyNamed('pathToReload'),
      invalidatedFiles: anyNamed('invalidatedFiles'),
254
      dillOutputPath: anyNamed('dillOutputPath'),
255
      packageConfig: anyNamed('packageConfig'),
256
    )).thenThrow(vm_service.RPCError('something bad happened', 666, ''));
257 258 259 260

    final OperationResult result = await residentRunner.restart(fullRestart: false);
    expect(result.fatal, true);
    expect(result.code, 1);
261
    verify(globals.flutterUsage.sendEvent('hot', 'exception', parameters: <String, String>{
262 263 264 265 266
      cdKey(CustomDimensions.hotEventTargetPlatform):
        getNameForTargetPlatform(TargetPlatform.android_arm),
      cdKey(CustomDimensions.hotEventSdkName): 'Example',
      cdKey(CustomDimensions.hotEventEmulator): 'false',
      cdKey(CustomDimensions.hotEventFullRestart): 'false',
267 268 269 270 271
    })).called(1);
  }, overrides: <Type, Generator>{
    Usage: () => MockUsage(),
  }));

272
  test('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async {
273 274 275 276 277 278 279 280 281 282
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      // Not all requests are present due to existing mocks
      const FakeVmServiceRequest(
        id: '1',
        method: 'ext.flutter.reassemble',
        args: <String, Object>{
          'isolateId': null,
        },
      ),
    ]);
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
    when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
      return 'Example';
    });
    when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
      return TargetPlatform.android_arm;
    });
    when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async {
      return false;
    });
    final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> onAppStart = Completer<void>.sync();
    unawaited(residentRunner.attach(
      appStartedCompleter: onAppStart,
      connectionInfoCompleter: onConnectionInfo,
    ));

    final OperationResult result = await residentRunner.restart(fullRestart: false);
    expect(result.fatal, false);
    expect(result.code, 0);
302
    expect(verify(globals.flutterUsage.sendEvent('hot', 'reload',
303 304
                  parameters: captureAnyNamed('parameters'))).captured[0],
      containsPair(cdKey(CustomDimensions.hotEventTargetPlatform),
305
                   getNameForTargetPlatform(TargetPlatform.android_arm)),
306 307 308 309 310
    );
  }, overrides: <Type, Generator>{
    Usage: () => MockUsage(),
  }));

311
  test('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async {
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
     // Not all requests are present due to existing mocks
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      const FakeVmServiceRequest(
        id: '1',
        method: 'streamListen',
        args: <String, Object>{
          'streamId': 'Isolate',
        },
      ),
      const FakeVmServiceRequest(
        id: '2',
        method: kRunInViewMethod,
        args: <String, Object>{
          'viewId': null,
          'mainScript': 'lib/main.dart.dill',
          'assetDirectory': 'build/flutter_assets',
        },
      ),
      FakeVmServiceStreamResponse(
        streamId: 'Isolate',
        event: vm_service.Event(
          timestamp: 0,
          kind: vm_service.EventKind.kIsolateRunnable,
        )
      )
    ]);
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
    when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
      return 'Example';
    });
    when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
      return TargetPlatform.android_arm;
    });
    when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async {
      return false;
    });
    when(mockDevice.supportsHotRestart).thenReturn(true);
    final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> onAppStart = Completer<void>.sync();
    unawaited(residentRunner.attach(
      appStartedCompleter: onAppStart,
      connectionInfoCompleter: onConnectionInfo,
    ));

    final OperationResult result = await residentRunner.restart(fullRestart: true);
    expect(result.fatal, false);
    expect(result.code, 0);
358
    expect(verify(globals.flutterUsage.sendEvent('hot', 'restart',
359 360
                  parameters: captureAnyNamed('parameters'))).captured[0],
      containsPair(cdKey(CustomDimensions.hotEventTargetPlatform),
361
                   getNameForTargetPlatform(TargetPlatform.android_arm)),
362 363 364 365 366
    );
  }, overrides: <Type, Generator>{
    Usage: () => MockUsage(),
  }));

367
  test('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async {
368
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
    when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
      return 'Example';
    });
    when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
      return TargetPlatform.android_arm;
    });
    when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async {
      return false;
    });
    when(mockDevice.supportsHotRestart).thenReturn(true);
    final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> onAppStart = Completer<void>.sync();
    unawaited(residentRunner.attach(
      appStartedCompleter: onAppStart,
      connectionInfoCompleter: onConnectionInfo,
    ));
    await onAppStart.future;
    when(mockFlutterDevice.updateDevFS(
387
      mainUri: anyNamed('mainUri'),
388 389 390 391 392 393 394 395 396
      target: anyNamed('target'),
      bundle: anyNamed('bundle'),
      firstBuildTime: anyNamed('firstBuildTime'),
      bundleFirstUpload: anyNamed('bundleFirstUpload'),
      bundleDirty: anyNamed('bundleDirty'),
      fullRestart: anyNamed('fullRestart'),
      projectRootPath: anyNamed('projectRootPath'),
      pathToReload: anyNamed('pathToReload'),
      invalidatedFiles: anyNamed('invalidatedFiles'),
397
      dillOutputPath: anyNamed('dillOutputPath'),
398
      packageConfig: anyNamed('packageConfig'),
399
    )).thenThrow(vm_service.RPCError('something bad happened', 666, ''));
400 401 402 403

    final OperationResult result = await residentRunner.restart(fullRestart: true);
    expect(result.fatal, true);
    expect(result.code, 1);
404
    verify(globals.flutterUsage.sendEvent('hot', 'exception', parameters: <String, String>{
405 406 407 408 409
      cdKey(CustomDimensions.hotEventTargetPlatform):
        getNameForTargetPlatform(TargetPlatform.android_arm),
      cdKey(CustomDimensions.hotEventSdkName): 'Example',
      cdKey(CustomDimensions.hotEventEmulator): 'false',
      cdKey(CustomDimensions.hotEventFullRestart): 'true',
410 411 412 413 414
    })).called(1);
  }, overrides: <Type, Generator>{
    Usage: () => MockUsage(),
  }));

415
  test('ResidentRunner uses temp directory when there is no output dill path', () => testbed.run(() {
416
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
417
    expect(residentRunner.artifactDirectory.path, contains('flutter_tool.'));
418 419 420 421 422 423 424

    final ResidentRunner otherRunner = HotRunner(
      <FlutterDevice>[
        mockFlutterDevice,
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
425
      dillOutputPath: globals.fs.path.join('foobar', 'app.dill'),
426 427 428 429
    );
    expect(otherRunner.artifactDirectory.path, contains('foobar'));
  }));

430
  test('ResidentRunner copies output dill to cache location during preExit', () => testbed.run(() async {
431
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
432 433 434 435 436 437 438 439 440
    residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('hello');
    await residentRunner.preExit();
    final File cacheDill = globals.fs.file(globals.fs.path.join(getBuildDirectory(), 'cache.dill'));

    expect(cacheDill, exists);
    expect(cacheDill.readAsStringSync(), 'hello');
  }));

  test('ResidentRunner handles output dill missing during preExit', () => testbed.run(() async {
441
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
442 443 444 445 446 447
    await residentRunner.preExit();
    final File cacheDill = globals.fs.file(globals.fs.path.join(getBuildDirectory(), 'cache.dill'));

    expect(cacheDill, isNot(exists));
  }));

448
  test('ResidentRunner printHelpDetails', () => testbed.run(() {
449
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
450 451 452 453 454
    when(mockDevice.supportsHotRestart).thenReturn(true);
    when(mockDevice.supportsScreenshot).thenReturn(true);

    residentRunner.printHelp(details: true);

455 456
    final CommandHelp commandHelp = residentRunner.commandHelp;

457 458 459 460
    // supports service protocol
    expect(residentRunner.supportsServiceProtocol, true);
    // isRunningDebug
    expect(residentRunner.isRunningDebug, true);
461 462
    // does not support CanvasKit
    expect(residentRunner.supportsCanvasKit, false);
463 464
    // does support SkSL
    expect(residentRunner.supportsWriteSkSL, true);
465 466 467 468
    // commands
    expect(testLogger.statusText, equals(
        <dynamic>[
          'Flutter run key commands.',
469 470 471
          commandHelp.r,
          commandHelp.R,
          commandHelp.h,
472
          commandHelp.c,
473 474 475 476 477 478 479 480 481 482 483
          commandHelp.q,
          commandHelp.s,
          commandHelp.w,
          commandHelp.t,
          commandHelp.L,
          commandHelp.S,
          commandHelp.U,
          commandHelp.i,
          commandHelp.p,
          commandHelp.o,
          commandHelp.z,
484
          commandHelp.M,
485
          commandHelp.v,
486 487
          commandHelp.P,
          commandHelp.a,
488 489 490 491
          'An Observatory debugger and profiler on null is available at: null',
          ''
        ].join('\n')
    ));
492 493
  }));

494
  test('ResidentRunner does support CanvasKit', () => testbed.run(() async {
495
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
496 497 498 499
    expect(() => residentRunner.toggleCanvaskit(),
      throwsA(isA<Exception>()));
  }));

500
  test('ResidentRunner handles writeSkSL returning no data', () => testbed.run(() async {
501 502 503 504 505 506 507 508 509 510 511 512
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       const FakeVmServiceRequest(
        id: '1',
        method: kGetSkSLsMethod,
        args: <String, Object>{
          'viewId': null,
        },
        jsonResponse: <String, Object>{
          'SkSLs': <String, Object>{}
        }
      )
    ]);
513 514 515 516 517 518
    await residentRunner.writeSkSL();

    expect(testLogger.statusText, contains('No data was receieved'));
  }));

  test('ResidentRunner can write SkSL data to a unique file with engine revision, platform, and device name', () => testbed.run(() async {
519 520 521 522 523 524 525 526 527 528 529 530 531 532
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      const FakeVmServiceRequest(
        id: '1',
        method: kGetSkSLsMethod,
        args: <String, Object>{
          'viewId': null,
        },
        jsonResponse: <String, Object>{
          'SkSLs': <String, Object>{
            'A': 'B',
          }
        }
      )
    ]);
533 534 535 536 537 538 539 540 541 542 543 544 545 546
    when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
      return TargetPlatform.android_arm;
    });
    when(mockDevice.name).thenReturn('test device');
    await residentRunner.writeSkSL();

    expect(testLogger.statusText, contains('flutter_01.sksl'));
    expect(globals.fs.file('flutter_01.sksl'), exists);
    expect(json.decode(globals.fs.file('flutter_01.sksl').readAsStringSync()), <String, Object>{
      'platform': 'android-arm',
      'name': 'test device',
      'engineRevision': '42.2', // From FakeFlutterVersion
      'data': <String, Object>{'A': 'B'}
    });
547
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
548 549
  }));

550
  test('ResidentRunner can take screenshot on debug device', () => testbed.run(() async {
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      const FakeVmServiceRequest(
        id: '1',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
          'isolateId': null,
          'enabled': 'false',
        },
      ),
      const FakeVmServiceRequest(
        id: '2',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
          'isolateId': null,
          'enabled': 'true',
        },
      )
    ]);
569 570
    when(mockDevice.supportsScreenshot).thenReturn(true);
    when(mockDevice.takeScreenshot(any))
571
      .thenAnswer((Invocation invocation) async {
572
        final File file = invocation.positionalArguments.first as File;
573 574
        file.writeAsBytesSync(List<int>.generate(1024, (int i) => i));
      });
575

576
    await residentRunner.screenshot(mockFlutterDevice);
577

578
    expect(testLogger.statusText, contains('1kB'));
579
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
580
  }));
581

582 583
  test('ResidentRunner clears the screen when it should', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
584 585 586 587 588 589 590 591
    const String message = 'This should be cleared';
    expect(testLogger.statusText, equals(''));
    testLogger.printStatus(message);
    expect(testLogger.statusText, equals(message + '\n'));  // printStatus makes a newline
    residentRunner.clearScreen();
    expect(testLogger.statusText, equals(''));
  }));

592 593 594 595 596 597 598 599 600 601 602 603 604
  test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws RpcError', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      const FakeVmServiceRequest(
        id: '1',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
          'isolateId': null,
          'enabled': 'false',
        },
        // Failed response,
        errorCode: RPCErrorCodes.kInternalError,
      )
    ]);
605
    when(mockDevice.supportsScreenshot).thenReturn(true);
606
    await residentRunner.screenshot(mockFlutterDevice);
607

608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
    expect(testLogger.errorText, contains('Error'));
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
  }));

  test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner during second request', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      const FakeVmServiceRequest(
        id: '1',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
          'isolateId': null,
          'enabled': 'false',
        },
      ),
      const FakeVmServiceRequest(
        id: '2',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
          'isolateId': null,
          'enabled': 'true',
        },
        // Failed response,
        errorCode: RPCErrorCodes.kInternalError,
      )
    ]);
    when(mockDevice.supportsScreenshot).thenReturn(true);
634 635
    await residentRunner.screenshot(mockFlutterDevice);

636
    expect(testLogger.errorText, contains('Error'));
637 638 639
  }));

  test('ResidentRunner bails taking screenshot on debug device if takeScreenshot throws', () => testbed.run(() async {
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      const FakeVmServiceRequest(
        id: '1',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
          'isolateId': null,
          'enabled': 'false',
        },
      ),
      const FakeVmServiceRequest(
        id: '2',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
          'isolateId': null,
          'enabled': 'true',
        },
      ),
    ]);
658 659 660 661 662
    when(mockDevice.supportsScreenshot).thenReturn(true);
    when(mockDevice.takeScreenshot(any)).thenThrow(Exception());

    await residentRunner.screenshot(mockFlutterDevice);

663
    expect(testLogger.errorText, contains('Error'));
664 665
  }));

666
  test("ResidentRunner can't take screenshot on device without support", () => testbed.run(() {
667
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
668 669 670
    when(mockDevice.supportsScreenshot).thenReturn(false);

    expect(() => residentRunner.screenshot(mockFlutterDevice),
Dan Field's avatar
Dan Field committed
671
        throwsAssertionError);
672 673 674
  }));

  test('ResidentRunner does not toggle banner in non-debug mode', () => testbed.run(() async {
675
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
676 677 678 679 680 681 682 683 684
    residentRunner = HotRunner(
      <FlutterDevice>[
        mockFlutterDevice,
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
    );
    when(mockDevice.supportsScreenshot).thenReturn(true);
    when(mockDevice.takeScreenshot(any))
685
      .thenAnswer((Invocation invocation) async {
686
        final File file = invocation.positionalArguments.first as File;
687 688
        file.writeAsBytesSync(List<int>.generate(1024, (int i) => i));
      });
689 690 691

    await residentRunner.screenshot(mockFlutterDevice);

692
    expect(testLogger.statusText, contains('1kB'));
693
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
694 695 696
  }));

  test('FlutterDevice will not exit a paused isolate', () => testbed.run(() async {
697
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
698 699 700 701
    final TestFlutterDevice flutterDevice = TestFlutterDevice(
      mockDevice,
      <FlutterView>[ mockFlutterView ],
    );
702
    flutterDevice.vmService = fakeVmServiceHost.vmService;
703 704 705 706 707 708 709 710
    final MockServiceEvent mockServiceEvent = MockServiceEvent();
    when(mockServiceEvent.isPauseEvent).thenReturn(true);
    when(mockIsolate.pauseEvent).thenReturn(mockServiceEvent);
    when(mockDevice.supportsFlutterExit).thenReturn(true);

    await flutterDevice.exitApps();

    verify(mockDevice.stopApp(any)).called(1);
711
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
712 713 714
  }));

  test('FlutterDevice will exit an un-paused isolate', () => testbed.run(() async {
715 716 717 718 719 720 721 722 723
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      const FakeVmServiceRequest(
        id: '1',
        method: 'ext.flutter.exit',
        args: <String, Object>{
          'isolateId': null,
        },
      )
    ]);
724 725
    final TestFlutterDevice flutterDevice = TestFlutterDevice(
      mockDevice,
726
      <FlutterView> [mockFlutterView],
727
    );
728
    flutterDevice.vmService = fakeVmServiceHost.vmService;
729 730 731 732 733 734 735

    final MockServiceEvent mockServiceEvent = MockServiceEvent();
    when(mockServiceEvent.isPauseEvent).thenReturn(false);
    when(mockIsolate.pauseEvent).thenReturn(mockServiceEvent);
    when(mockDevice.supportsFlutterExit).thenReturn(true);

    await flutterDevice.exitApps();
736
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
737
  }));
738 739

  test('ResidentRunner refreshViews calls flutter device', () => testbed.run(() async {
740
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
741 742 743 744 745 746
    await residentRunner.refreshViews();

    verify(mockFlutterDevice.refreshViews()).called(1);
  }));

  test('ResidentRunner debugDumpApp calls flutter device', () => testbed.run(() async {
747
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
748 749 750 751 752 753 754
    await residentRunner.debugDumpApp();

    verify(mockFlutterDevice.refreshViews()).called(1);
    verify(mockFlutterDevice.debugDumpApp()).called(1);
  }));

  test('ResidentRunner debugDumpRenderTree calls flutter device', () => testbed.run(() async {
755
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
756 757 758 759 760 761 762
    await residentRunner.debugDumpRenderTree();

    verify(mockFlutterDevice.refreshViews()).called(1);
    verify(mockFlutterDevice.debugDumpRenderTree()).called(1);
  }));

  test('ResidentRunner debugDumpLayerTree calls flutter device', () => testbed.run(() async {
763
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
764 765 766 767 768 769 770
    await residentRunner.debugDumpLayerTree();

    verify(mockFlutterDevice.refreshViews()).called(1);
    verify(mockFlutterDevice.debugDumpLayerTree()).called(1);
  }));

  test('ResidentRunner debugDumpSemanticsTreeInTraversalOrder calls flutter device', () => testbed.run(() async {
771
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
772 773 774 775 776 777 778
    await residentRunner.debugDumpSemanticsTreeInTraversalOrder();

    verify(mockFlutterDevice.refreshViews()).called(1);
    verify(mockFlutterDevice.debugDumpSemanticsTreeInTraversalOrder()).called(1);
  }));

  test('ResidentRunner debugDumpSemanticsTreeInInverseHitTestOrder calls flutter device', () => testbed.run(() async {
779
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
780 781 782 783 784 785 786
    await residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder();

    verify(mockFlutterDevice.refreshViews()).called(1);
    verify(mockFlutterDevice.debugDumpSemanticsTreeInInverseHitTestOrder()).called(1);
  }));

  test('ResidentRunner debugToggleDebugPaintSizeEnabled calls flutter device', () => testbed.run(() async {
787
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
788 789 790 791 792 793 794
    await residentRunner.debugToggleDebugPaintSizeEnabled();

    verify(mockFlutterDevice.refreshViews()).called(1);
    verify(mockFlutterDevice.toggleDebugPaintSizeEnabled()).called(1);
  }));

  test('ResidentRunner debugToggleDebugCheckElevationsEnabled calls flutter device', () => testbed.run(() async {
795
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
796 797 798 799 800 801
    await residentRunner.debugToggleDebugCheckElevationsEnabled();

    verify(mockFlutterDevice.refreshViews()).called(1);
    verify(mockFlutterDevice.toggleDebugCheckElevationsEnabled()).called(1);
  }));

802 803
  test('ResidentRunner debugTogglePerformanceOverlayOverride calls flutter device', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
804 805 806 807 808 809 810
    await residentRunner.debugTogglePerformanceOverlayOverride();

    verify(mockFlutterDevice.refreshViews()).called(1);
    verify(mockFlutterDevice.debugTogglePerformanceOverlayOverride()).called(1);
  }));

  test('ResidentRunner debugToggleWidgetInspector calls flutter device', () => testbed.run(() async {
811
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
812 813 814 815 816 817 818
    await residentRunner.debugToggleWidgetInspector();

    verify(mockFlutterDevice.refreshViews()).called(1);
    verify(mockFlutterDevice.toggleWidgetInspector()).called(1);
  }));

  test('ResidentRunner debugToggleProfileWidgetBuilds calls flutter device', () => testbed.run(() async {
819
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
820 821 822 823 824
    await residentRunner.debugToggleProfileWidgetBuilds();

    verify(mockFlutterDevice.refreshViews()).called(1);
    verify(mockFlutterDevice.toggleProfileWidgetBuilds()).called(1);
  }));
825 826

  test('HotRunner writes vm service file when providing debugging option', () => testbed.run(() async {
827
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
828
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
829 830 831 832 833 834 835 836 837 838 839 840 841 842 843
    residentRunner = HotRunner(
      <FlutterDevice>[
        mockFlutterDevice,
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, vmserviceOutFile: 'foo'),
    );
    when(mockFlutterDevice.runHot(
      hotRunner: anyNamed('hotRunner'),
      route: anyNamed('route'),
    )).thenAnswer((Invocation invocation) async {
      return 0;
    });
    await residentRunner.run();

844
    expect(await globals.fs.file('foo').readAsString(), testUri.toString());
845 846
  }));

847
  test('HotRunner unforwards device ports', () => testbed.run(() async {
848
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
849 850
    final MockDevicePortForwarder mockPortForwarder = MockDevicePortForwarder();
    when(mockDevice.portForwarder).thenReturn(mockPortForwarder);
851
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
    residentRunner = HotRunner(
      <FlutterDevice>[
        mockFlutterDevice,
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
    );
    when(mockFlutterDevice.runHot(
      hotRunner: anyNamed('hotRunner'),
      route: anyNamed('route'),
    )).thenAnswer((Invocation invocation) async {
      return 0;
    });

    when(mockDevice.dispose()).thenAnswer((Invocation invocation) async {
      await mockDevice.portForwarder.dispose();
    });

    await residentRunner.run();

    verify(mockPortForwarder.dispose()).called(1);
  }));

875
  test('HotRunner handles failure to write vmservice file', () => testbed.run(() async {
876
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
877
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
878 879 880 881 882 883 884 885 886 887 888 889 890 891 892
    residentRunner = HotRunner(
      <FlutterDevice>[
        mockFlutterDevice,
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, vmserviceOutFile: 'foo'),
    );
    when(mockFlutterDevice.runHot(
      hotRunner: anyNamed('hotRunner'),
      route: anyNamed('route'),
    )).thenAnswer((Invocation invocation) async {
      return 0;
    });
    await residentRunner.run();

893
    expect(testLogger.errorText, contains('Failed to write vmservice-out-file at foo'));
894 895 896 897 898 899
  }, overrides: <Type, Generator>{
    FileSystem: () => ThrowingForwardingFileSystem(MemoryFileSystem()),
  }));


  test('ColdRunner writes vm service file when providing debugging option', () => testbed.run(() async {
900
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
901
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
902 903 904 905 906 907 908 909 910 911 912 913 914 915 916
    residentRunner = ColdRunner(
      <FlutterDevice>[
        mockFlutterDevice,
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile, vmserviceOutFile: 'foo'),
    );
    when(mockFlutterDevice.runCold(
      coldRunner: anyNamed('coldRunner'),
      route: anyNamed('route'),
    )).thenAnswer((Invocation invocation) async {
      return 0;
    });
    await residentRunner.run();

917
    expect(await globals.fs.file('foo').readAsString(), testUri.toString());
918
  }));
919 920

  test('FlutterDevice uses dartdevc configuration when targeting web', () => testbed.run(() async {
921
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
922 923 924 925 926 927 928
    final MockDevice mockDevice = MockDevice();
    when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
      return TargetPlatform.web_javascript;
    });

    final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create(
      mockDevice,
929
      buildInfo: BuildInfo.debug,
930 931
      flutterProject: FlutterProject.current(),
      target: null,
932
    )).generator as DefaultResidentCompiler;
933

934 935
    expect(residentCompiler.initializeFromDill,
      globals.fs.path.join(getBuildDirectory(), 'cache.dill'));
936 937 938
    expect(residentCompiler.librariesSpec,
      globals.fs.file(globals.artifacts.getArtifactPath(Artifact.flutterWebLibrariesJson))
        .uri.toString());
939 940
    expect(residentCompiler.targetModel, TargetModel.dartdevc);
    expect(residentCompiler.sdkRoot,
941
      globals.artifacts.getArtifactPath(Artifact.flutterWebSdk, mode: BuildMode.debug) + '/');
942 943
    expect(
      residentCompiler.platformDill,
944
      globals.fs.file(globals.artifacts.getArtifactPath(Artifact.webPlatformKernelDill, mode: BuildMode.debug))
945 946
        .absolute.uri.toString(),
    );
947
  }));
948 949

  test('connect sets up log reader', () => testbed.run(() async {
950
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
951 952 953 954 955 956 957
    final MockDevice mockDevice = MockDevice();
    final MockDeviceLogReader mockLogReader = MockDeviceLogReader();
    when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(mockLogReader);

    final TestFlutterDevice flutterDevice = TestFlutterDevice(
      mockDevice,
      <FlutterView>[],
958
      observatoryUris: Stream<Uri>.value(testUri),
959 960 961
    );

    await flutterDevice.connect();
962
    verify(mockLogReader.connectedVMService = mockVMService);
963
  }, overrides: <Type, Generator>{
964 965 966 967
    VMServiceConnector: () => (Uri httpUri, {
      ReloadSources reloadSources,
      Restart restart,
      CompileExpression compileExpression,
968
      ReloadMethod reloadMethod,
969 970 971
      io.CompressionOptions compression,
      Device device,
    }) async => mockVMService,
972
  }));
973 974 975 976 977 978

  test('nextPlatform moves through expected platforms', () {
    expect(nextPlatform('android', TestFeatureFlags()), 'iOS');
    expect(nextPlatform('iOS', TestFeatureFlags()), 'fuchsia');
    expect(nextPlatform('fuchsia', TestFeatureFlags()), 'android');
    expect(nextPlatform('fuchsia', TestFeatureFlags(isMacOSEnabled: true)), 'macOS');
Dan Field's avatar
Dan Field committed
979
    expect(() => nextPlatform('unknown', TestFeatureFlags()), throwsAssertionError);
980
  });
981 982
}

983
class MockFlutterDevice extends Mock implements FlutterDevice {}
984 985 986
class MockFlutterView extends Mock implements FlutterView {}
class MockVMService extends Mock implements VMService {}
class MockDevFS extends Mock implements DevFS {}
987 988
class MockIsolate extends Mock implements Isolate {}
class MockDevice extends Mock implements Device {}
989
class MockDeviceLogReader extends Mock implements DeviceLogReader {}
990
class MockDevicePortForwarder extends Mock implements DevicePortForwarder {}
991
class MockUsage extends Mock implements Usage {}
992
class MockProcessManager extends Mock implements ProcessManager {}
993
class MockServiceEvent extends Mock implements ServiceEvent {}
994
class MockVM extends Mock implements VM {}
995
class TestFlutterDevice extends FlutterDevice {
996
  TestFlutterDevice(Device device, this.views, { Stream<Uri> observatoryUris })
997
    : super(device, buildInfo: BuildInfo.debug) {
998 999
    _observatoryUris = observatoryUris;
  }
1000 1001 1002

  @override
  final List<FlutterView> views;
1003 1004

  @override
1005 1006
  Stream<Uri> get observatoryUris => _observatoryUris;
  Stream<Uri> _observatoryUris;
1007 1008
}

1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019
class ThrowingForwardingFileSystem extends ForwardingFileSystem {
  ThrowingForwardingFileSystem(FileSystem delegate) : super(delegate);

  @override
  File file(dynamic path) {
    if (path == 'foo') {
      throw const FileSystemException();
    }
    return delegate.file(path);
  }
}