resident_runner_test.dart 42.9 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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate(
  id: '1',
  pauseEvent: vm_service.Event(
    kind: vm_service.EventKind.kResume,
    timestamp: 0
  ),
  breakpoints: <vm_service.Breakpoint>[],
  exceptionPauseMode: null,
  libraries: <vm_service.LibraryRef>[],
  livePorts: 0,
  name: 'test',
  number: '1',
  pauseOnExit: false,
  runnable: true,
  startTime: 0,
);

final vm_service.Isolate fakePausedIsolate = vm_service.Isolate(
  id: '1',
  pauseEvent: vm_service.Event(
    kind: vm_service.EventKind.kPauseException,
    timestamp: 0
  ),
  breakpoints: <vm_service.Breakpoint>[],
  exceptionPauseMode: null,
  libraries: <vm_service.LibraryRef>[],
  livePorts: 0,
  name: 'test',
  number: '1',
  pauseOnExit: false,
  runnable: true,
  startTime: 0,
);

final FlutterView fakeFlutterView = FlutterView(
  id: 'a',
  uiIsolate: fakeUnpausedIsolate,
);

73
void main() {
74 75 76 77 78 79 80
  final Uri testUri = Uri.parse('foo://bar');
  Testbed testbed;
  MockFlutterDevice mockFlutterDevice;
  MockVMService mockVMService;
  MockDevFS mockDevFS;
  ResidentRunner residentRunner;
  MockDevice mockDevice;
81
  FakeVmServiceHost fakeVmServiceHost;
82 83 84

  setUp(() {
    testbed = Testbed(setup: () {
85
      globals.fs.file('.packages').writeAsStringSync('\n');
86
      globals.fs.file(globals.fs.path.join('build', 'app.dill'))
87 88
        ..createSync(recursive: true)
        ..writeAsStringSync('ABC');
89 90 91 92 93 94 95 96 97 98 99 100
      residentRunner = HotRunner(
        <FlutterDevice>[
          mockFlutterDevice,
        ],
        stayResident: false,
        debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      );
    });
    mockFlutterDevice = MockFlutterDevice();
    mockDevice = MockDevice();
    mockVMService = MockVMService();
    mockDevFS = MockDevFS();
101

102 103 104
    // DevFS Mocks
    when(mockDevFS.lastCompiled).thenReturn(DateTime(2000));
    when(mockDevFS.sources).thenReturn(<Uri>[]);
105
    when(mockDevFS.baseUri).thenReturn(Uri());
106
    when(mockDevFS.destroy()).thenAnswer((Invocation invocation) async { });
107
    when(mockDevFS.assetPathsToEvict).thenReturn(<String>{});
108 109
    // FlutterDevice Mocks.
    when(mockFlutterDevice.updateDevFS(
110 111
      invalidatedFiles: anyNamed('invalidatedFiles'),
      mainUri: anyNamed('mainUri'),
112 113 114 115 116 117 118 119
      target: anyNamed('target'),
      bundle: anyNamed('bundle'),
      firstBuildTime: anyNamed('firstBuildTime'),
      bundleFirstUpload: anyNamed('bundleFirstUpload'),
      bundleDirty: anyNamed('bundleDirty'),
      fullRestart: anyNamed('fullRestart'),
      projectRootPath: anyNamed('projectRootPath'),
      pathToReload: anyNamed('pathToReload'),
120
      dillOutputPath: anyNamed('dillOutputPath'),
121
      packageConfig: anyNamed('packageConfig'),
122 123 124 125 126 127 128 129 130
    )).thenAnswer((Invocation invocation) async {
      return UpdateFSReport(
        success: true,
        syncedBytes: 0,
        invalidatedSourcesCount: 0,
      );
    });
    when(mockFlutterDevice.devFS).thenReturn(mockDevFS);
    when(mockFlutterDevice.views).thenReturn(<FlutterView>[
131 132
      // NB: still using mock FlutterDevice.
      fakeFlutterView,
133 134 135
    ]);
    when(mockFlutterDevice.device).thenReturn(mockDevice);
    when(mockFlutterDevice.stopEchoingDeviceLog()).thenAnswer((Invocation invocation) async { });
136
    when(mockFlutterDevice.observatoryUris).thenAnswer((_) => Stream<Uri>.value(testUri));
137 138 139
    when(mockFlutterDevice.connect(
      reloadSources: anyNamed('reloadSources'),
      restart: anyNamed('restart'),
140
      compileExpression: anyNamed('compileExpression'),
141 142 143 144
    )).thenAnswer((Invocation invocation) async { });
    when(mockFlutterDevice.setupDevFS(any, any, packagesFilePath: anyNamed('packagesFilePath')))
      .thenAnswer((Invocation invocation) async {
        return testUri;
145
      });
146 147 148
    when(mockFlutterDevice.vmService).thenAnswer((Invocation invocation) {
      return fakeVmServiceHost.vmService;
    });
149
    when(mockFlutterDevice.refreshViews()).thenAnswer((Invocation invocation) async { });
150 151 152 153 154 155 156 157 158 159 160 161 162 163
    when(mockFlutterDevice.reloadSources(any, pause: anyNamed('pause'))).thenAnswer((Invocation invocation) async {
      return <Future<vm_service.ReloadReport>>[
        Future<vm_service.ReloadReport>.value(vm_service.ReloadReport.parse(<String, dynamic>{
          'type': 'ReloadReport',
          'success': true,
          'details': <String, dynamic>{
            'loadedLibraryCount': 1,
            'finalLibraryCount': 1,
            'receivedLibraryCount': 1,
            'receivedClassesCount': 1,
            'receivedProceduresCount': 1,
          },
        })),
      ];
164 165 166
    });
  });

167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
  test('FlutterDevice can list views with a filter', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      FakeVmServiceRequest(
        id: '1',
        method: kListViewsMethod,
        args: null,
        jsonResponse: <String, Object>{
          'views': <Object>[
            fakeFlutterView.toJson(),
          ],
        },
      ),
    ]);
    final MockDevice mockDevice = MockDevice();
    final FlutterDevice flutterDevice = FlutterDevice(
      mockDevice,
      buildInfo: BuildInfo.debug,
      viewFilter: 'b', // Does not match name of `fakeFlutterView`.
    );

    flutterDevice.vmService = fakeVmServiceHost.vmService;

    await flutterDevice.refreshViews();

    expect(flutterDevice.views, isEmpty);
  }));

194
  test('ResidentRunner can attach to device successfully', () => testbed.run(() async {
195
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
196 197
    final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> onAppStart = Completer<void>.sync();
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
    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 {
214
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
215
      FakeVmServiceRequest(
216
        id: '1',
217 218 219 220 221 222 223 224 225 226 227 228 229 230
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        id: '2',
        method: 'getVM',
        args: null,
        jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
      ),
      const FakeVmServiceRequest(
        id: '3',
231 232 233 234 235
        method: 'streamListen',
        args: <String, Object>{
          'streamId': 'Isolate',
        }
      ),
236 237
      FakeVmServiceRequest(
        id: '4',
238 239
        method: kRunInViewMethod,
        args: <String, Object>{
240
          'viewId': fakeFlutterView.id,
241 242 243 244 245 246 247 248 249 250 251 252
          'mainScript': 'lib/main.dart.dill',
          'assetDirectory': 'build/flutter_assets',
        }
      ),
      FakeVmServiceStreamResponse(
        streamId: 'Isolate',
        event: vm_service.Event(
          timestamp: 0,
          kind: vm_service.EventKind.kIsolateRunnable,
        )
      ),
    ]);
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
    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,
268 269 270 271 272
      debuggingOptions: DebuggingOptions.enabled(
        BuildInfo.debug,
        fastStart: true,
        startPaused: true,
      ),
273 274 275
    );
    final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> onAppStart = Completer<void>.sync();
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
    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 {
292
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
    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(
310
      mainUri: anyNamed('mainUri'),
311 312 313 314 315 316 317 318 319
      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'),
320
      dillOutputPath: anyNamed('dillOutputPath'),
321
      packageConfig: anyNamed('packageConfig'),
322
    )).thenThrow(vm_service.RPCError('something bad happened', 666, ''));
323 324 325 326

    final OperationResult result = await residentRunner.restart(fullRestart: false);
    expect(result.fatal, true);
    expect(result.code, 1);
327
    verify(globals.flutterUsage.sendEvent('hot', 'exception', parameters: <String, String>{
328 329 330 331 332
      cdKey(CustomDimensions.hotEventTargetPlatform):
        getNameForTargetPlatform(TargetPlatform.android_arm),
      cdKey(CustomDimensions.hotEventSdkName): 'Example',
      cdKey(CustomDimensions.hotEventEmulator): 'false',
      cdKey(CustomDimensions.hotEventFullRestart): 'false',
333 334 335 336 337
    })).called(1);
  }, overrides: <Type, Generator>{
    Usage: () => MockUsage(),
  }));

338
  test('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async {
339 340
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      // Not all requests are present due to existing mocks
341
      FakeVmServiceRequest(
342
        id: '1',
343 344 345 346 347 348 349 350
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': '1',
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        id: '2',
351 352
        method: 'ext.flutter.reassemble',
        args: <String, Object>{
353
          'isolateId': fakeUnpausedIsolate.id,
354 355 356
        },
      ),
    ]);
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
    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);
376
    expect(verify(globals.flutterUsage.sendEvent('hot', 'reload',
377 378
                  parameters: captureAnyNamed('parameters'))).captured[0],
      containsPair(cdKey(CustomDimensions.hotEventTargetPlatform),
379
                   getNameForTargetPlatform(TargetPlatform.android_arm)),
380 381 382 383 384
    );
  }, overrides: <Type, Generator>{
    Usage: () => MockUsage(),
  }));

385
  test('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async {
386 387
     // Not all requests are present due to existing mocks
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
388
      FakeVmServiceRequest(
389
        id: '1',
390 391 392 393 394 395 396 397 398 399 400 401 402 403
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        id: '2',
        method: 'getVM',
        args: null,
        jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
      ),
      const FakeVmServiceRequest(
        id: '3',
404 405 406 407 408
        method: 'streamListen',
        args: <String, Object>{
          'streamId': 'Isolate',
        },
      ),
409 410
      FakeVmServiceRequest(
        id: '4',
411 412
        method: kRunInViewMethod,
        args: <String, Object>{
413
          'viewId': fakeFlutterView.id,
414 415 416 417 418 419 420 421 422 423 424 425
          'mainScript': 'lib/main.dart.dill',
          'assetDirectory': 'build/flutter_assets',
        },
      ),
      FakeVmServiceStreamResponse(
        streamId: 'Isolate',
        event: vm_service.Event(
          timestamp: 0,
          kind: vm_service.EventKind.kIsolateRunnable,
        )
      )
    ]);
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
    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);
446
    expect(verify(globals.flutterUsage.sendEvent('hot', 'restart',
447 448
                  parameters: captureAnyNamed('parameters'))).captured[0],
      containsPair(cdKey(CustomDimensions.hotEventTargetPlatform),
449
                   getNameForTargetPlatform(TargetPlatform.android_arm)),
450 451 452 453 454
    );
  }, overrides: <Type, Generator>{
    Usage: () => MockUsage(),
  }));

455
  test('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async {
456
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
    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(
475
      mainUri: anyNamed('mainUri'),
476 477 478 479 480 481 482 483 484
      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'),
485
      dillOutputPath: anyNamed('dillOutputPath'),
486
      packageConfig: anyNamed('packageConfig'),
487
    )).thenThrow(vm_service.RPCError('something bad happened', 666, ''));
488 489 490 491

    final OperationResult result = await residentRunner.restart(fullRestart: true);
    expect(result.fatal, true);
    expect(result.code, 1);
492
    verify(globals.flutterUsage.sendEvent('hot', 'exception', parameters: <String, String>{
493 494 495 496 497
      cdKey(CustomDimensions.hotEventTargetPlatform):
        getNameForTargetPlatform(TargetPlatform.android_arm),
      cdKey(CustomDimensions.hotEventSdkName): 'Example',
      cdKey(CustomDimensions.hotEventEmulator): 'false',
      cdKey(CustomDimensions.hotEventFullRestart): 'true',
498 499 500 501 502
    })).called(1);
  }, overrides: <Type, Generator>{
    Usage: () => MockUsage(),
  }));

503
  test('ResidentRunner uses temp directory when there is no output dill path', () => testbed.run(() {
504
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
505
    expect(residentRunner.artifactDirectory.path, contains('flutter_tool.'));
506 507 508 509 510 511 512

    final ResidentRunner otherRunner = HotRunner(
      <FlutterDevice>[
        mockFlutterDevice,
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
513
      dillOutputPath: globals.fs.path.join('foobar', 'app.dill'),
514 515 516 517
    );
    expect(otherRunner.artifactDirectory.path, contains('foobar'));
  }));

518
  test('ResidentRunner copies output dill to cache location during preExit', () => testbed.run(() async {
519
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
520 521 522 523 524 525 526 527 528
    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 {
529
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
530 531 532 533 534 535
    await residentRunner.preExit();
    final File cacheDill = globals.fs.file(globals.fs.path.join(getBuildDirectory(), 'cache.dill'));

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

536
  test('ResidentRunner printHelpDetails', () => testbed.run(() {
537
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
538 539 540 541 542
    when(mockDevice.supportsHotRestart).thenReturn(true);
    when(mockDevice.supportsScreenshot).thenReturn(true);

    residentRunner.printHelp(details: true);

543 544
    final CommandHelp commandHelp = residentRunner.commandHelp;

545 546 547 548
    // supports service protocol
    expect(residentRunner.supportsServiceProtocol, true);
    // isRunningDebug
    expect(residentRunner.isRunningDebug, true);
549 550
    // does not support CanvasKit
    expect(residentRunner.supportsCanvasKit, false);
551 552
    // does support SkSL
    expect(residentRunner.supportsWriteSkSL, true);
553 554 555 556
    // commands
    expect(testLogger.statusText, equals(
        <dynamic>[
          'Flutter run key commands.',
557 558 559
          commandHelp.r,
          commandHelp.R,
          commandHelp.h,
560
          commandHelp.c,
561 562 563 564 565 566 567 568 569 570 571
          commandHelp.q,
          commandHelp.s,
          commandHelp.w,
          commandHelp.t,
          commandHelp.L,
          commandHelp.S,
          commandHelp.U,
          commandHelp.i,
          commandHelp.p,
          commandHelp.o,
          commandHelp.z,
572
          commandHelp.M,
573
          commandHelp.v,
574 575
          commandHelp.P,
          commandHelp.a,
576 577 578 579
          'An Observatory debugger and profiler on null is available at: null',
          ''
        ].join('\n')
    ));
580 581
  }));

582
  test('ResidentRunner does support CanvasKit', () => testbed.run(() async {
583
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
584 585 586 587
    expect(() => residentRunner.toggleCanvaskit(),
      throwsA(isA<Exception>()));
  }));

588
  test('ResidentRunner handles writeSkSL returning no data', () => testbed.run(() async {
589
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
590
       FakeVmServiceRequest(
591 592 593
        id: '1',
        method: kGetSkSLsMethod,
        args: <String, Object>{
594
          'viewId': fakeFlutterView.id,
595 596 597 598 599 600
        },
        jsonResponse: <String, Object>{
          'SkSLs': <String, Object>{}
        }
      )
    ]);
601 602 603 604 605 606
    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 {
607
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
608
      FakeVmServiceRequest(
609 610 611
        id: '1',
        method: kGetSkSLsMethod,
        args: <String, Object>{
612
          'viewId': fakeFlutterView.id,
613 614 615 616 617 618 619 620
        },
        jsonResponse: <String, Object>{
          'SkSLs': <String, Object>{
            'A': 'B',
          }
        }
      )
    ]);
621 622 623 624 625 626 627 628 629
    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>{
630
      'platform': 'android',
631 632 633 634
      'name': 'test device',
      'engineRevision': '42.2', // From FakeFlutterVersion
      'data': <String, Object>{'A': 'B'}
    });
635
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
636 637
  }));

638
  test('ResidentRunner can take screenshot on debug device', () => testbed.run(() async {
639
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
640
      FakeVmServiceRequest(
641 642 643
        id: '1',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
644
          'isolateId': fakeUnpausedIsolate.id,
645 646 647
          'enabled': 'false',
        },
      ),
648
      FakeVmServiceRequest(
649 650 651
        id: '2',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
652
          'isolateId': fakeUnpausedIsolate.id,
653 654 655 656
          'enabled': 'true',
        },
      )
    ]);
657 658
    when(mockDevice.supportsScreenshot).thenReturn(true);
    when(mockDevice.takeScreenshot(any))
659
      .thenAnswer((Invocation invocation) async {
660
        final File file = invocation.positionalArguments.first as File;
661 662
        file.writeAsBytesSync(List<int>.generate(1024, (int i) => i));
      });
663

664
    await residentRunner.screenshot(mockFlutterDevice);
665

666
    expect(testLogger.statusText, contains('1kB'));
667
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
668
  }));
669

670 671
  test('ResidentRunner clears the screen when it should', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
672 673 674 675 676 677 678 679
    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(''));
  }));

680 681
  test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws RpcError', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
682
      FakeVmServiceRequest(
683 684 685
        id: '1',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
686
          'isolateId': fakeUnpausedIsolate.id,
687 688 689 690 691 692
          'enabled': 'false',
        },
        // Failed response,
        errorCode: RPCErrorCodes.kInternalError,
      )
    ]);
693
    when(mockDevice.supportsScreenshot).thenReturn(true);
694
    await residentRunner.screenshot(mockFlutterDevice);
695

696 697 698 699 700 701
    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>[
702
      FakeVmServiceRequest(
703 704 705
        id: '1',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
706
          'isolateId': fakeUnpausedIsolate.id,
707 708 709
          'enabled': 'false',
        },
      ),
710
      FakeVmServiceRequest(
711 712 713
        id: '2',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
714
          'isolateId': fakeUnpausedIsolate.id,
715 716 717 718 719 720 721
          'enabled': 'true',
        },
        // Failed response,
        errorCode: RPCErrorCodes.kInternalError,
      )
    ]);
    when(mockDevice.supportsScreenshot).thenReturn(true);
722 723
    await residentRunner.screenshot(mockFlutterDevice);

724
    expect(testLogger.errorText, contains('Error'));
725 726 727
  }));

  test('ResidentRunner bails taking screenshot on debug device if takeScreenshot throws', () => testbed.run(() async {
728
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
729
      FakeVmServiceRequest(
730 731 732
        id: '1',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
733
          'isolateId': fakeUnpausedIsolate.id,
734 735 736
          'enabled': 'false',
        },
      ),
737
      FakeVmServiceRequest(
738 739 740
        id: '2',
        method: 'ext.flutter.debugAllowBanner',
        args: <String, Object>{
741
          'isolateId': fakeUnpausedIsolate.id,
742 743 744 745
          'enabled': 'true',
        },
      ),
    ]);
746 747 748 749 750
    when(mockDevice.supportsScreenshot).thenReturn(true);
    when(mockDevice.takeScreenshot(any)).thenThrow(Exception());

    await residentRunner.screenshot(mockFlutterDevice);

751
    expect(testLogger.errorText, contains('Error'));
752 753
  }));

754
  test("ResidentRunner can't take screenshot on device without support", () => testbed.run(() {
755
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
756 757 758
    when(mockDevice.supportsScreenshot).thenReturn(false);

    expect(() => residentRunner.screenshot(mockFlutterDevice),
Dan Field's avatar
Dan Field committed
759
        throwsAssertionError);
760 761 762
  }));

  test('ResidentRunner does not toggle banner in non-debug mode', () => testbed.run(() async {
763
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
764 765 766 767 768 769 770 771 772
    residentRunner = HotRunner(
      <FlutterDevice>[
        mockFlutterDevice,
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
    );
    when(mockDevice.supportsScreenshot).thenReturn(true);
    when(mockDevice.takeScreenshot(any))
773
      .thenAnswer((Invocation invocation) async {
774
        final File file = invocation.positionalArguments.first as File;
775 776
        file.writeAsBytesSync(List<int>.generate(1024, (int i) => i));
      });
777 778 779

    await residentRunner.screenshot(mockFlutterDevice);

780
    expect(testLogger.statusText, contains('1kB'));
781
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
782 783 784
  }));

  test('FlutterDevice will not exit a paused isolate', () => testbed.run(() async {
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      FakeVmServiceRequest(
        id: '1',
        method: '_flutter.listViews',
        args: null,
        jsonResponse: <String, Object>{
          'views': <Object>[
            fakeFlutterView.toJson(),
          ],
        },
      ),
      FakeVmServiceRequest(
        id: '2',
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
        jsonResponse: fakePausedIsolate.toJson(),
      ),
    ]);
805 806
    final TestFlutterDevice flutterDevice = TestFlutterDevice(
      mockDevice,
807
      <FlutterView>[ fakeFlutterView ],
808
    );
809
    flutterDevice.vmService = fakeVmServiceHost.vmService;
810 811 812 813 814
    when(mockDevice.supportsFlutterExit).thenReturn(true);

    await flutterDevice.exitApps();

    verify(mockDevice.stopApp(any)).called(1);
815
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
816 817
  }));

818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
  test('FlutterDevice will call stopApp if the exit request times out', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      FakeVmServiceRequest(
        id: '1',
        method: '_flutter.listViews',
        args: null,
        jsonResponse: <String, Object>{
          'views': <Object>[
            fakeFlutterView.toJson(),
          ],
        },
      ),
      FakeVmServiceRequest(
        id: '2',
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        id: '3',
        method: 'ext.flutter.exit',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
        // Intentionally do not close isolate.
        close: false,
      )
    ]);
    final TestFlutterDevice flutterDevice = TestFlutterDevice(
      mockDevice,
      <FlutterView>[ fakeFlutterView ],
    );
    flutterDevice.vmService = fakeVmServiceHost.vmService;
    when(mockDevice.supportsFlutterExit).thenReturn(true);

    await flutterDevice.exitApps(
      timeoutDelay: Duration.zero,
    );

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

863
  test('FlutterDevice will exit an un-paused isolate', () => testbed.run(() async {
864
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
865
      FakeVmServiceRequest(
866
        id: '1',
867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884
        method: kListViewsMethod,
        args: null,
        jsonResponse: <String, Object>{
          'views': <Object>[
            fakeFlutterView.toJson(),
          ],
        },
      ),
      FakeVmServiceRequest(
        id: '2',
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        id: '3',
885 886
        method: 'ext.flutter.exit',
        args: <String, Object>{
887
          'isolateId': fakeUnpausedIsolate.id,
888
        },
889
        close: true,
890 891
      )
    ]);
892 893
    final TestFlutterDevice flutterDevice = TestFlutterDevice(
      mockDevice,
894
      <FlutterView> [fakeFlutterView ],
895
    );
896
    flutterDevice.vmService = fakeVmServiceHost.vmService;
897 898 899

    when(mockDevice.supportsFlutterExit).thenReturn(true);

900 901 902
    final Future<void> exitFuture = flutterDevice.exitApps();

    await expectLater(exitFuture, completes);
903
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
904
  }));
905 906

  test('ResidentRunner refreshViews calls flutter device', () => testbed.run(() async {
907
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
908 909 910 911 912 913
    await residentRunner.refreshViews();

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

  test('ResidentRunner debugDumpApp calls flutter device', () => testbed.run(() async {
914
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
915 916 917 918 919 920 921
    await residentRunner.debugDumpApp();

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

  test('ResidentRunner debugDumpRenderTree calls flutter device', () => testbed.run(() async {
922
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
923 924 925 926 927 928 929
    await residentRunner.debugDumpRenderTree();

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

  test('ResidentRunner debugDumpLayerTree calls flutter device', () => testbed.run(() async {
930
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
931 932 933 934 935 936 937
    await residentRunner.debugDumpLayerTree();

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

  test('ResidentRunner debugDumpSemanticsTreeInTraversalOrder calls flutter device', () => testbed.run(() async {
938
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
939 940 941 942 943 944 945
    await residentRunner.debugDumpSemanticsTreeInTraversalOrder();

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

  test('ResidentRunner debugDumpSemanticsTreeInInverseHitTestOrder calls flutter device', () => testbed.run(() async {
946
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
947 948 949 950 951 952 953
    await residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder();

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

  test('ResidentRunner debugToggleDebugPaintSizeEnabled calls flutter device', () => testbed.run(() async {
954
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
955 956 957 958 959 960 961
    await residentRunner.debugToggleDebugPaintSizeEnabled();

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

  test('ResidentRunner debugToggleDebugCheckElevationsEnabled calls flutter device', () => testbed.run(() async {
962
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
963 964 965 966 967 968
    await residentRunner.debugToggleDebugCheckElevationsEnabled();

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

969 970
  test('ResidentRunner debugTogglePerformanceOverlayOverride calls flutter device', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
971 972 973 974 975 976 977
    await residentRunner.debugTogglePerformanceOverlayOverride();

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

  test('ResidentRunner debugToggleWidgetInspector calls flutter device', () => testbed.run(() async {
978
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
979 980 981 982 983 984 985
    await residentRunner.debugToggleWidgetInspector();

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

  test('ResidentRunner debugToggleProfileWidgetBuilds calls flutter device', () => testbed.run(() async {
986
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
987 988 989 990 991
    await residentRunner.debugToggleProfileWidgetBuilds();

    verify(mockFlutterDevice.refreshViews()).called(1);
    verify(mockFlutterDevice.toggleProfileWidgetBuilds()).called(1);
  }));
992 993

  test('HotRunner writes vm service file when providing debugging option', () => testbed.run(() async {
994
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
995
    setWsAddress(testUri, fakeVmServiceHost.vmService);
996
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
    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();

1012
    expect(await globals.fs.file('foo').readAsString(), testUri.toString());
1013 1014
  }));

1015
  test('HotRunner unforwards device ports', () => testbed.run(() async {
1016
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1017 1018
    final MockDevicePortForwarder mockPortForwarder = MockDevicePortForwarder();
    when(mockDevice.portForwarder).thenReturn(mockPortForwarder);
1019
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042
    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);
  }));

1043
  test('HotRunner handles failure to write vmservice file', () => testbed.run(() async {
1044
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1045
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
    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();

1061
    expect(testLogger.errorText, contains('Failed to write vmservice-out-file at foo'));
1062 1063 1064 1065 1066 1067
  }, overrides: <Type, Generator>{
    FileSystem: () => ThrowingForwardingFileSystem(MemoryFileSystem()),
  }));


  test('ColdRunner writes vm service file when providing debugging option', () => testbed.run(() async {
1068
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1069
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
1070
    setWsAddress(testUri, fakeVmServiceHost.vmService);
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085
    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();

1086
    expect(await globals.fs.file('foo').readAsString(), testUri.toString());
1087
  }));
1088 1089

  test('FlutterDevice uses dartdevc configuration when targeting web', () => testbed.run(() async {
1090
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1091 1092 1093 1094 1095 1096 1097
    final MockDevice mockDevice = MockDevice();
    when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
      return TargetPlatform.web_javascript;
    });

    final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create(
      mockDevice,
1098
      buildInfo: BuildInfo.debug,
1099 1100
      flutterProject: FlutterProject.current(),
      target: null,
1101
    )).generator as DefaultResidentCompiler;
1102

1103 1104
    expect(residentCompiler.initializeFromDill,
      globals.fs.path.join(getBuildDirectory(), 'cache.dill'));
1105 1106 1107
    expect(residentCompiler.librariesSpec,
      globals.fs.file(globals.artifacts.getArtifactPath(Artifact.flutterWebLibrariesJson))
        .uri.toString());
1108 1109
    expect(residentCompiler.targetModel, TargetModel.dartdevc);
    expect(residentCompiler.sdkRoot,
1110
      globals.artifacts.getArtifactPath(Artifact.flutterWebSdk, mode: BuildMode.debug) + '/');
1111 1112
    expect(
      residentCompiler.platformDill,
1113
      globals.fs.file(globals.artifacts.getArtifactPath(Artifact.webPlatformKernelDill, mode: BuildMode.debug))
1114 1115
        .absolute.uri.toString(),
    );
1116
  }));
1117 1118

  test('connect sets up log reader', () => testbed.run(() async {
1119
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1120 1121 1122 1123 1124 1125 1126
    final MockDevice mockDevice = MockDevice();
    final MockDeviceLogReader mockLogReader = MockDeviceLogReader();
    when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(mockLogReader);

    final TestFlutterDevice flutterDevice = TestFlutterDevice(
      mockDevice,
      <FlutterView>[],
1127
      observatoryUris: Stream<Uri>.value(testUri),
1128 1129 1130
    );

    await flutterDevice.connect();
1131
    verify(mockLogReader.connectedVMService = mockVMService);
1132
  }, overrides: <Type, Generator>{
1133 1134 1135 1136
    VMServiceConnector: () => (Uri httpUri, {
      ReloadSources reloadSources,
      Restart restart,
      CompileExpression compileExpression,
1137
      ReloadMethod reloadMethod,
1138 1139 1140
      io.CompressionOptions compression,
      Device device,
    }) async => mockVMService,
1141
  }));
1142 1143 1144 1145 1146 1147

  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
1148
    expect(() => nextPlatform('unknown', TestFeatureFlags()), throwsAssertionError);
1149
  });
1150 1151
}

1152
class MockFlutterDevice extends Mock implements FlutterDevice {}
1153
class MockVMService extends Mock implements vm_service.VmService {}
1154
class MockDevFS extends Mock implements DevFS {}
1155
class MockDevice extends Mock implements Device {}
1156
class MockDeviceLogReader extends Mock implements DeviceLogReader {}
1157
class MockDevicePortForwarder extends Mock implements DevicePortForwarder {}
1158
class MockUsage extends Mock implements Usage {}
1159
class MockProcessManager extends Mock implements ProcessManager {}
1160

1161
class TestFlutterDevice extends FlutterDevice {
1162
  TestFlutterDevice(Device device, this.views, { Stream<Uri> observatoryUris })
1163
    : super(device, buildInfo: BuildInfo.debug) {
1164 1165
    _observatoryUris = observatoryUris;
  }
1166 1167 1168

  @override
  final List<FlutterView> views;
1169 1170

  @override
1171 1172
  Stream<Uri> get observatoryUris => _observatoryUris;
  Stream<Uri> _observatoryUris;
1173 1174
}

1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185
class ThrowingForwardingFileSystem extends ForwardingFileSystem {
  ThrowingForwardingFileSystem(FileSystem delegate) : super(delegate);

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