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

5 6
// @dart = 2.8

7
import 'dart:async';
8
import 'dart:io';
9

10
import 'package:dds/dds.dart' as dds;
11
import 'package:file/memory.dart';
12
import 'package:file_testing/file_testing.dart';
13
import 'package:flutter_tools/src/application_package.dart';
14
import 'package:flutter_tools/src/artifacts.dart';
15
import 'package:flutter_tools/src/asset.dart';
16
import 'package:flutter_tools/src/base/command_help.dart';
17
import 'package:flutter_tools/src/base/common.dart';
18
import 'package:flutter_tools/src/base/dds.dart';
19
import 'package:flutter_tools/src/base/file_system.dart';
20
import 'package:flutter_tools/src/base/io.dart' as io;
21 22
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
23
import 'package:flutter_tools/src/build_info.dart';
24
import 'package:flutter_tools/src/compile.dart';
25
import 'package:flutter_tools/src/convert.dart';
26
import 'package:flutter_tools/src/devfs.dart';
27
import 'package:flutter_tools/src/device.dart';
28 29
import 'package:flutter_tools/src/device_port_forwarder.dart';
import 'package:flutter_tools/src/features.dart';
30
import 'package:flutter_tools/src/globals_null_migrated.dart' as globals;
31
import 'package:flutter_tools/src/reporting/reporting.dart';
32
import 'package:flutter_tools/src/resident_devtools_handler.dart';
33
import 'package:flutter_tools/src/resident_runner.dart';
34
import 'package:flutter_tools/src/run_cold.dart';
35
import 'package:flutter_tools/src/run_hot.dart';
36
import 'package:flutter_tools/src/version.dart';
37
import 'package:flutter_tools/src/vmservice.dart';
38 39
import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
40
import 'package:test/fake.dart';
41
import 'package:vm_service/vm_service.dart' as vm_service;
42

43
import '../src/common.dart';
44
import '../src/context.dart';
45
import '../src/fake_vm_services.dart';
46
import '../src/fakes.dart';
47
import '../src/testbed.dart';
48

49 50 51 52 53 54 55 56
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,
57
  extensionRPCs: <String>[],
58 59 60 61 62 63 64
  libraries: <vm_service.LibraryRef>[
    vm_service.LibraryRef(
      id: '1',
      uri: 'file:///hello_world/main.dart',
      name: '',
    ),
  ],
65 66 67 68 69 70
  livePorts: 0,
  name: 'test',
  number: '1',
  pauseOnExit: false,
  runnable: true,
  startTime: 0,
71
  isSystemIsolate: false,
72
  isolateFlags: <vm_service.IsolateFlag>[],
73 74 75 76 77 78 79 80
);

final vm_service.Isolate fakePausedIsolate = vm_service.Isolate(
  id: '1',
  pauseEvent: vm_service.Event(
    kind: vm_service.EventKind.kPauseException,
    timestamp: 0
  ),
81 82 83 84 85 86
  breakpoints: <vm_service.Breakpoint>[
    vm_service.Breakpoint(
      breakpointNumber: 123,
      id: 'test-breakpoint',
      location: vm_service.SourceLocation(
        tokenPos: 0,
87
        script: vm_service.ScriptRef(id: 'test-script', uri: 'foo.dart'),
88
      ),
89
      enabled: true,
90 91 92
      resolved: true,
    ),
  ],
93 94 95 96 97 98 99 100
  exceptionPauseMode: null,
  libraries: <vm_service.LibraryRef>[],
  livePorts: 0,
  name: 'test',
  number: '1',
  pauseOnExit: false,
  runnable: true,
  startTime: 0,
101
  isSystemIsolate: false,
102
  isolateFlags: <vm_service.IsolateFlag>[],
103 104
);

105 106 107 108 109 110 111 112 113 114 115
final vm_service.VM fakeVM = vm_service.VM(
  isolates: <vm_service.IsolateRef>[fakeUnpausedIsolate],
  pid: 1,
  hostCPU: '',
  isolateGroups: <vm_service.IsolateGroupRef>[],
  targetCPU: '',
  startTime: 0,
  name: 'dart',
  architectureBits: 64,
  operatingSystem: '',
  version: '',
116 117
  systemIsolateGroups: <vm_service.IsolateGroupRef>[],
  systemIsolates: <vm_service.IsolateRef>[],
118 119
);

120 121 122 123 124
final FlutterView fakeFlutterView = FlutterView(
  id: 'a',
  uiIsolate: fakeUnpausedIsolate,
);

125 126 127 128 129 130 131 132 133
final FakeVmServiceRequest listViews = FakeVmServiceRequest(
  method: kListViewsMethod,
  jsonResponse: <String, Object>{
    'views': <Object>[
      fakeFlutterView.toJson(),
    ],
  },
);

134 135 136 137 138 139 140 141 142
const FakeVmServiceRequest setAssetBundlePath = FakeVmServiceRequest(
  method: '_flutter.setAssetBundlePath',
  args: <String, Object>{
    'viewId': 'a',
    'assetDirectory': 'build/flutter_assets',
    'isolateId': '1',
  }
);

143 144 145 146 147 148 149 150
const FakeVmServiceRequest evict = FakeVmServiceRequest(
  method: 'ext.flutter.evict',
  args: <String, Object>{
    'value': 'asset',
    'isolateId': '1',
  }
);

151 152
final Uri testUri = Uri.parse('foo://bar');

153
void main() {
154
  Testbed testbed;
155 156
  FakeFlutterDevice flutterDevice;
  FakeDevFS devFS;
157
  ResidentRunner residentRunner;
158
  FakeDevice device;
159
  FakeVmServiceHost fakeVmServiceHost;
160 161 162

  setUp(() {
    testbed = Testbed(setup: () {
163 164
      globals.fs.file('.packages')
        .writeAsStringSync('\n');
165
      globals.fs.file(globals.fs.path.join('build', 'app.dill'))
166 167
        ..createSync(recursive: true)
        ..writeAsStringSync('ABC');
168 169
      residentRunner = HotRunner(
        <FlutterDevice>[
170
          flutterDevice,
171 172 173
        ],
        stayResident: false,
        debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
174
        target: 'main.dart',
175
        devtoolsHandler: createNoOpHandler,
176 177
      );
    });
178 179 180 181 182 183 184
    device = FakeDevice();
    devFS = FakeDevFS();
    flutterDevice = FakeFlutterDevice()
      ..testUri = testUri
      ..vmServiceHost = (() => fakeVmServiceHost)
      ..device = device
      .._devFS = devFS;
185 186
  });

187
  testUsingContext('ResidentRunner can attach to device successfully', () => testbed.run(() async {
188 189 190 191
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
    ]);
192 193
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
194
    final Future<int> result = residentRunner.attach(
195 196 197
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
198
    );
199
    final Future<DebugConnectionInfo> connectionInfo = futureConnectionInfo.future;
200 201

    expect(await result, 0);
202
    expect(futureConnectionInfo.isCompleted, true);
203
    expect((await connectionInfo).baseUri, 'foo://bar');
204
    expect(futureAppStart.isCompleted, true);
205
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
206
  }));
207

208
  testUsingContext('ResidentRunner suppresses errors for the initial compilation', () => testbed.run(() async {
209 210 211 212 213 214
    globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
      .createSync(recursive: true);
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
    ]);
215 216
    final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
      ..nextOutput = const CompilerOutput('foo', 0 ,<Uri>[]);
217 218
    residentRunner = HotRunner(
      <FlutterDevice>[
219
        flutterDevice,
220 221 222
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
223
      target: 'main.dart',
224
      devtoolsHandler: createNoOpHandler,
225
    );
226
    flutterDevice.generator = residentCompiler;
227

228
    expect(await residentRunner.run(enableDevTools: true), 0);
229
    expect(residentCompiler.didSuppressErrors, true);
230
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
231
  }));
232

233 234 235 236 237
  // Regression test for https://github.com/flutter/flutter/issues/60613
  testUsingContext('ResidentRunner calls appFailedToStart if initial compilation fails', () => testbed.run(() async {
    globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
      .createSync(recursive: true);
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
238 239
    final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
      ..nextOutput = const CompilerOutput('foo', 1 ,<Uri>[]);
240 241
    residentRunner = HotRunner(
      <FlutterDevice>[
242
        flutterDevice,
243 244 245
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
246
      target: 'main.dart',
247
      devtoolsHandler: createNoOpHandler,
248
    );
249
    flutterDevice.generator = residentCompiler;
250 251 252 253 254 255 256 257 258 259 260 261 262

    expect(await residentRunner.run(), 1);
    // Completing this future ensures that the daemon can exit correctly.
    expect(await residentRunner.waitForAppToFinish(), 1);
  }));

  // Regression test for https://github.com/flutter/flutter/issues/60613
  testUsingContext('ResidentRunner calls appFailedToStart if initial compilation fails - cold mode', () => testbed.run(() async {
    globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
      .createSync(recursive: true);
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
    residentRunner = ColdRunner(
      <FlutterDevice>[
263
        flutterDevice,
264 265 266
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.release),
267
      target: 'main.dart',
268
      devtoolsHandler: createNoOpHandler,
269
    );
270
    flutterDevice.runColdCode = 1;
271 272 273 274 275 276 277 278 279 280 281 282 283

    expect(await residentRunner.run(), 1);
    // Completing this future ensures that the daemon can exit correctly.
    expect(await residentRunner.waitForAppToFinish(), 1);
  }));

  // Regression test for https://github.com/flutter/flutter/issues/60613
  testUsingContext('ResidentRunner calls appFailedToStart if exception is thrown - cold mode', () => testbed.run(() async {
    globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
      .createSync(recursive: true);
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
    residentRunner = ColdRunner(
      <FlutterDevice>[
284
        flutterDevice,
285 286 287
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.release),
288
      target: 'main.dart',
289
      devtoolsHandler: createNoOpHandler,
290
    );
291 292
    flutterDevice.runColdError = Exception('BAD STUFF');

293 294 295 296 297 298

    expect(await residentRunner.run(), 1);
    // Completing this future ensures that the daemon can exit correctly.
    expect(await residentRunner.waitForAppToFinish(), 1);
  }));

299
  testUsingContext('ResidentRunner does not suppressErrors if running with an applicationBinary', () => testbed.run(() async {
300 301 302 303 304 305
    globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
      .createSync(recursive: true);
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
    ]);
306 307
    final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
      ..nextOutput = const CompilerOutput('foo', 0 ,<Uri>[]);
308 309
    residentRunner = HotRunner(
      <FlutterDevice>[
310
        flutterDevice,
311 312 313 314
      ],
      applicationBinary: globals.fs.file('app.apk'),
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
315
      target: 'main.dart',
316
      devtoolsHandler: createNoOpHandler,
317
    );
318
    flutterDevice.generator = residentCompiler;
319

320
    expect(await residentRunner.run(enableDevTools: true), 0);
321
    expect(residentCompiler.didSuppressErrors, false);
322
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
323
  }));
324

325
  testUsingContext('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async {
326
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
327 328 329
      listViews,
      listViews,
      listViews,
330 331 332 333 334 335 336 337 338 339 340
      FakeVmServiceRequest(
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        method: 'getVM',
        jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
      ),
341
      listViews,
342
      const FakeVmServiceRequest(
343 344 345 346 347
        method: 'streamListen',
        args: <String, Object>{
          'streamId': 'Isolate',
        }
      ),
348
      FakeVmServiceRequest(
349 350
        method: kRunInViewMethod,
        args: <String, Object>{
351
          'viewId': fakeFlutterView.id,
352
          'mainScript': 'main.dart.dill',
353 354 355 356 357 358 359 360 361 362 363
          'assetDirectory': 'build/flutter_assets',
        }
      ),
      FakeVmServiceStreamResponse(
        streamId: 'Isolate',
        event: vm_service.Event(
          timestamp: 0,
          kind: vm_service.EventKind.kIsolateRunnable,
        )
      ),
    ]);
364 365
    residentRunner = HotRunner(
      <FlutterDevice>[
366
        flutterDevice,
367 368
      ],
      stayResident: false,
369 370 371 372 373
      debuggingOptions: DebuggingOptions.enabled(
        BuildInfo.debug,
        fastStart: true,
        startPaused: true,
      ),
374
      target: 'main.dart',
375
      devtoolsHandler: createNoOpHandler,
376
    );
377 378
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
379
    final Future<int> result = residentRunner.attach(
380 381 382
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
383
    );
384
    final Future<DebugConnectionInfo> connectionInfo = futureConnectionInfo.future;
385 386

    expect(await result, 0);
387
    expect(futureConnectionInfo.isCompleted, true);
388
    expect((await connectionInfo).baseUri, 'foo://bar');
389
    expect(futureAppStart.isCompleted, true);
390
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
391
  }));
392

393
  testUsingContext('ResidentRunner can handle an RPC exception from hot reload', () => testbed.run(() async {
394 395 396 397 398
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
      listViews,
    ]);
399 400
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
401
    unawaited(residentRunner.attach(
402 403 404
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
405
    ));
406
    await futureAppStart.future;
407
    flutterDevice.reportError = vm_service.RPCError('something bad happened', 666, '');
408 409 410 411

    final OperationResult result = await residentRunner.restart(fullRestart: false);
    expect(result.fatal, true);
    expect(result.code, 1);
412
    expect((globals.flutterUsage as TestUsage).events, contains(
413 414 415 416 417 418
      TestUsageEvent('hot', 'exception', parameters: CustomDimensions(
        hotEventTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
        hotEventSdkName: 'Android',
        hotEventEmulator: false,
        hotEventFullRestart: false,
      )),
419
    ));
420
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
421
  }, overrides: <Type, Generator>{
422
    Usage: () => TestUsage(),
423
  }));
424

425 426 427 428 429
  testUsingContext('ResidentRunner fails its operation if the device initialization is not complete', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
    ]);
430 431
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
432
    unawaited(residentRunner.attach(
433 434
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
435
    ));
436
    await futureAppStart.future;
437
    flutterDevice._devFS = null;
438 439 440 441 442 443

    final OperationResult result = await residentRunner.restart(fullRestart: false);
    expect(result.fatal, false);
    expect(result.code, 1);
    expect(result.message, contains('Device initialization has not completed.'));
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
444
  }));
445

446 447 448 449 450 451
  testUsingContext('ResidentRunner can handle an reload-barred exception from hot reload', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
      listViews,
    ]);
452 453
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
454
    unawaited(residentRunner.attach(
455 456 457
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
458
    ));
459
    await futureAppStart.future;
460
    flutterDevice.reportError = vm_service.RPCError('something bad happened', kIsolateReloadBarred, '');
461 462 463 464 465

    final OperationResult result = await residentRunner.restart(fullRestart: false);
    expect(result.fatal, true);
    expect(result.code, kIsolateReloadBarred);
    expect(result.message, contains('Unable to hot reload application due to an unrecoverable error'));
466 467

    expect((globals.flutterUsage as TestUsage).events, contains(
468 469 470 471 472 473
      TestUsageEvent('hot', 'reload-barred', parameters: CustomDimensions(
        hotEventTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
        hotEventSdkName: 'Android',
        hotEventEmulator: false,
        hotEventFullRestart: false,
      )),
474
    ));
475 476
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
  }, overrides: <Type, Generator>{
477
    Usage: () => TestUsage(),
478
  }));
479

480 481 482 483 484 485 486 487
  testUsingContext('ResidentRunner reports hot reload event with null safety analytics', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
      listViews,
    ]);
    residentRunner = HotRunner(
      <FlutterDevice>[
488
        flutterDevice,
489 490
      ],
      stayResident: false,
491
      target: 'main.dart',
492 493 494 495 496
      debuggingOptions: DebuggingOptions.enabled(const BuildInfo(
        BuildMode.debug, '', treeShakeIcons: false, extraFrontEndOptions: <String>[
        '--enable-experiment=non-nullable',
        ],
      )),
497
      devtoolsHandler: createNoOpHandler,
498
    );
499 500
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
501
    unawaited(residentRunner.attach(
502 503 504
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
505
    ));
506
    await futureAppStart.future;
507
    flutterDevice.reportError = vm_service.RPCError('something bad happened', 666, '');
508 509 510 511

    final OperationResult result = await residentRunner.restart(fullRestart: false);
    expect(result.fatal, true);
    expect(result.code, 1);
512 513

    expect((globals.flutterUsage as TestUsage).events, contains(
514 515 516 517 518 519
      TestUsageEvent('hot', 'exception', parameters: CustomDimensions(
        hotEventTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
        hotEventSdkName: 'Android',
        hotEventEmulator: false,
        hotEventFullRestart: false,
      )),
520
    ));
521 522
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
  }, overrides: <Type, Generator>{
523
    Usage: () => TestUsage(),
524 525
  }));

526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
  testUsingContext('ResidentRunner does not reload sources if no sources changed', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
     listViews,
      listViews,
      listViews,
      FakeVmServiceRequest(
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': '1',
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        method: 'ext.flutter.reassemble',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
      ),
    ]);
    residentRunner = HotRunner(
      <FlutterDevice>[
547
        flutterDevice,
548 549 550
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
551
      target: 'main.dart',
552
      devtoolsHandler: createNoOpHandler,
553
    );
554 555
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
556
    unawaited(residentRunner.attach(
557 558 559
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
560
    ));
561
    await futureAppStart.future;
562
    flutterDevice.report =  UpdateFSReport(success: true, invalidatedSourcesCount: 0);
563 564 565 566 567

    final OperationResult result = await residentRunner.restart(fullRestart: false);

    expect(result.code, 0);
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
568
  }));
569

570 571 572 573 574
  testUsingContext('ResidentRunner reports error with missing entrypoint file', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
      listViews,
575 576 577 578 579 580 581 582 583 584 585 586 587
      FakeVmServiceRequest(
        method: 'getVM',
        jsonResponse: vm_service.VM.parse(<String, Object>{
          'isolates': <Object>[
            fakeUnpausedIsolate.toJson(),
          ],
        }).toJson(),
      ),
      const FakeVmServiceRequest(
        method: 'reloadSources',
        args: <String, Object>{
          'isolateId': '1',
          'pause': false,
588
          'rootLibUri': 'main.dart.incremental.dill'
589 590 591 592 593 594 595 596 597
        },
        jsonResponse: <String, Object>{
          'type': 'ReloadReport',
          'success': true,
          'details': <String, Object>{
            'loadedLibraryCount': 1,
          },
        },
      ),
598 599 600 601 602 603 604 605 606 607 608 609 610 611
      FakeVmServiceRequest(
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': '1',
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        method: 'ext.flutter.reassemble',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
      ),
    ]);
612 613
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
614
    unawaited(residentRunner.attach(
615 616 617
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
618
    ));
619
    await futureAppStart.future;
620
    flutterDevice.report =  UpdateFSReport(success: true, invalidatedSourcesCount: 1);
621 622 623 624 625 626 627

    final OperationResult result = await residentRunner.restart(fullRestart: false);

    expect(globals.fs.file(globals.fs.path.join('lib', 'main.dart')), isNot(exists));
    expect(testLogger.errorText, contains('The entrypoint file (i.e. the file with main())'));
    expect(result.fatal, false);
    expect(result.code, 0);
628
  }));
629

630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
   testUsingContext('ResidentRunner resets compilation time on reload reject', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
      listViews,
      FakeVmServiceRequest(
        method: 'getVM',
        jsonResponse: vm_service.VM.parse(<String, Object>{
          'isolates': <Object>[
            fakeUnpausedIsolate.toJson(),
          ],
        }).toJson(),
      ),
      const FakeVmServiceRequest(
        method: 'reloadSources',
        args: <String, Object>{
          'isolateId': '1',
          'pause': false,
648
          'rootLibUri': 'main.dart.incremental.dill'
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
        },
        jsonResponse: <String, Object>{
          'type': 'ReloadReport',
          'success': false,
          'notices': <Object>[
            <String, Object>{
              'message': 'Failed to hot reload'
            }
          ],
          'details': <String, Object>{},
        },
      ),
      listViews,
      FakeVmServiceRequest(
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': '1',
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        method: 'ext.flutter.reassemble',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
      ),
    ]);
676 677
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
678
    unawaited(residentRunner.attach(
679 680 681
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
682
    ));
683
    await futureAppStart.future;
684
    flutterDevice.report =  UpdateFSReport(success: true, invalidatedSourcesCount: 1);
685 686 687 688 689 690

    final OperationResult result = await residentRunner.restart(fullRestart: false);

    expect(result.fatal, false);
    expect(result.message, contains('Reload rejected: Failed to hot reload')); // contains error message from reload report.
    expect(result.code, 1);
691
    expect(devFS.lastCompiled, null);
692
  }));
693

694
  testUsingContext('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async {
695
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
696 697
      listViews,
      listViews,
698
      listViews,
699 700 701 702 703 704 705 706 707 708 709 710 711
      FakeVmServiceRequest(
        method: 'getVM',
        jsonResponse: vm_service.VM.parse(<String, Object>{
          'isolates': <Object>[
            fakeUnpausedIsolate.toJson(),
          ],
        }).toJson(),
      ),
      const FakeVmServiceRequest(
        method: 'reloadSources',
        args: <String, Object>{
          'isolateId': '1',
          'pause': false,
712
          'rootLibUri': 'main.dart.incremental.dill'
713 714 715 716 717 718 719 720 721
        },
        jsonResponse: <String, Object>{
          'type': 'ReloadReport',
          'success': true,
          'details': <String, Object>{
            'loadedLibraryCount': 1,
          },
        },
      ),
722 723 724 725 726 727 728 729
      FakeVmServiceRequest(
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': '1',
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
730 731
        method: 'ext.flutter.reassemble',
        args: <String, Object>{
732
          'isolateId': fakeUnpausedIsolate.id,
733 734 735
        },
      ),
    ]);
736 737
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
738
    unawaited(residentRunner.attach(
739 740 741
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
742
    ));
743
    await futureAppStart.future;
744 745 746 747

    final OperationResult result = await residentRunner.restart(fullRestart: false);
    expect(result.fatal, false);
    expect(result.code, 0);
748 749 750 751

    final TestUsageEvent event = (globals.flutterUsage as TestUsage).events.first;
    expect(event.category, 'hot');
    expect(event.parameter, 'reload');
752
    expect(event.parameters.hotEventTargetPlatform, getNameForTargetPlatform(TargetPlatform.android_arm));
753
  }, overrides: <Type, Generator>{
754
    Usage: () => TestUsage(),
755
  }));
756

757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774
  testUsingContext('ResidentRunner can perform fast reassemble', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      FakeVmServiceRequest(
        method: 'getVM',
        jsonResponse: fakeVM.toJson(),
      ),
      listViews,
      listViews,
      FakeVmServiceRequest(
        method: 'getVM',
        jsonResponse: fakeVM.toJson(),
      ),
      const FakeVmServiceRequest(
        method: 'reloadSources',
        args: <String, Object>{
          'isolateId': '1',
          'pause': false,
775
          'rootLibUri': 'main.dart.incremental.dill',
776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
        },
        jsonResponse: <String, Object>{
          'type': 'ReloadReport',
          'success': true,
          'details': <String, Object>{
            'loadedLibraryCount': 1,
          },
        },
      ),
      FakeVmServiceRequest(
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': '1',
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        method: 'ext.flutter.fastReassemble',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
796
          'className': 'FOO',
797 798 799
        },
      ),
    ]);
800 801
    final FakeDelegateFlutterDevice flutterDevice =  FakeDelegateFlutterDevice(
      device,
802 803
      BuildInfo.debug,
      FakeResidentCompiler(),
804
      devFS,
805 806 807 808 809 810 811
    )..vmService = fakeVmServiceHost.vmService;
    residentRunner = HotRunner(
      <FlutterDevice>[
        flutterDevice,
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
812
      target: 'main.dart',
813
      devtoolsHandler: createNoOpHandler,
814
    );
815
    devFS.nextUpdateReport = UpdateFSReport(
816 817 818 819
      success: true,
      fastReassembleClassName: 'FOO',
      invalidatedSourcesCount: 1,
    );
820

821 822
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
823
    unawaited(residentRunner.attach(
824 825 826
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
827 828
    ));

829
    await futureAppStart.future;
830 831 832 833
    final OperationResult result = await residentRunner.restart(fullRestart: false);

    expect(result.fatal, false);
    expect(result.code, 0);
834 835 836 837

    final TestUsageEvent event = (globals.flutterUsage as TestUsage).events.first;
    expect(event.category, 'hot');
    expect(event.parameter, 'reload');
838
    expect(event.parameters.fastReassemble, true);
839 840 841 842
  }, overrides: <Type, Generator>{
    FileSystem: () => MemoryFileSystem.test(),
    Platform: () => FakePlatform(operatingSystem: 'linux'),
    ProjectFileInvalidator: () => FakeProjectFileInvalidator(),
843
    Usage: () => TestUsage(),
844
    FeatureFlags: () => TestFeatureFlags(isSingleWidgetReloadEnabled: true),
845
  }));
846

847
  testUsingContext('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async {
848
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
849 850 851
      listViews,
      listViews,
      listViews,
852 853 854 855 856 857 858 859 860 861 862
      FakeVmServiceRequest(
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        method: 'getVM',
        jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
      ),
863
      listViews,
864
      const FakeVmServiceRequest(
865 866 867 868 869
        method: 'streamListen',
        args: <String, Object>{
          'streamId': 'Isolate',
        },
      ),
870
      FakeVmServiceRequest(
871 872
        method: kRunInViewMethod,
        args: <String, Object>{
873
          'viewId': fakeFlutterView.id,
874
          'mainScript': 'main.dart.dill',
875 876 877 878 879 880 881 882 883 884 885
          'assetDirectory': 'build/flutter_assets',
        },
      ),
      FakeVmServiceStreamResponse(
        streamId: 'Isolate',
        event: vm_service.Event(
          timestamp: 0,
          kind: vm_service.EventKind.kIsolateRunnable,
        )
      )
    ]);
886 887
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
888
    unawaited(residentRunner.attach(
889 890 891
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
892 893 894 895 896
    ));

    final OperationResult result = await residentRunner.restart(fullRestart: true);
    expect(result.fatal, false);
    expect(result.code, 0);
897 898 899 900

    final TestUsageEvent event = (globals.flutterUsage as TestUsage).events.first;
    expect(event.category, 'hot');
    expect(event.parameter, 'restart');
901
    expect(event.parameters.hotEventTargetPlatform, getNameForTargetPlatform(TargetPlatform.android_arm));
902
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
903
  }, overrides: <Type, Generator>{
904
    Usage: () => TestUsage(),
905
  }));
906

907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946
  testUsingContext('ResidentRunner can remove breakpoints from paused isolate during hot restart', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
      listViews,
      FakeVmServiceRequest(
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
        jsonResponse: fakePausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        method: 'getVM',
        jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
      ),
      const FakeVmServiceRequest(
        method: 'removeBreakpoint',
        args: <String, String>{
          'isolateId': '1',
          'breakpointId': 'test-breakpoint',
        }
      ),
      const FakeVmServiceRequest(
        method: 'resume',
        args: <String, String>{
          'isolateId': '1',
        }
      ),
      listViews,
      const FakeVmServiceRequest(
        method: 'streamListen',
        args: <String, Object>{
          'streamId': 'Isolate',
        },
      ),
      FakeVmServiceRequest(
        method: kRunInViewMethod,
        args: <String, Object>{
          'viewId': fakeFlutterView.id,
947
          'mainScript': 'main.dart.dill',
948 949 950 951 952 953 954 955 956 957 958
          'assetDirectory': 'build/flutter_assets',
        },
      ),
      FakeVmServiceStreamResponse(
        streamId: 'Isolate',
        event: vm_service.Event(
          timestamp: 0,
          kind: vm_service.EventKind.kIsolateRunnable,
        )
      )
    ]);
959 960
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
961
    unawaited(residentRunner.attach(
962 963 964
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
965 966 967 968 969 970
    ));

    final OperationResult result = await residentRunner.restart(fullRestart: true);

    expect(result.isOk, true);
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
971
  }));
972

973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999
  testUsingContext('ResidentRunner will alternative the name of the dill file uploaded for a hot restart', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
      listViews,
      FakeVmServiceRequest(
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        method: 'getVM',
        jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
      ),
      listViews,
      const FakeVmServiceRequest(
        method: 'streamListen',
        args: <String, Object>{
          'streamId': 'Isolate',
        },
      ),
      FakeVmServiceRequest(
        method: kRunInViewMethod,
        args: <String, Object>{
          'viewId': fakeFlutterView.id,
1000
          'mainScript': 'main.dart.dill',
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
          'assetDirectory': 'build/flutter_assets',
        },
      ),
      FakeVmServiceStreamResponse(
        streamId: 'Isolate',
        event: vm_service.Event(
          timestamp: 0,
          kind: vm_service.EventKind.kIsolateRunnable,
        ),
      ),
      listViews,
      FakeVmServiceRequest(
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        method: 'getVM',
        jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
      ),
      listViews,
      const FakeVmServiceRequest(
        method: 'streamListen',
        args: <String, Object>{
          'streamId': 'Isolate',
        },
      ),
      FakeVmServiceRequest(
        method: kRunInViewMethod,
        args: <String, Object>{
          'viewId': fakeFlutterView.id,
1034
          'mainScript': 'main.dart.swap.dill',
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067
          'assetDirectory': 'build/flutter_assets',
        },
      ),
      FakeVmServiceStreamResponse(
        streamId: 'Isolate',
        event: vm_service.Event(
          timestamp: 0,
          kind: vm_service.EventKind.kIsolateRunnable,
        ),
      ),
      listViews,
      FakeVmServiceRequest(
        method: 'getIsolate',
        args: <String, Object>{
          'isolateId': fakeUnpausedIsolate.id,
        },
        jsonResponse: fakeUnpausedIsolate.toJson(),
      ),
      FakeVmServiceRequest(
        method: 'getVM',
        jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
      ),
      listViews,
      const FakeVmServiceRequest(
        method: 'streamListen',
        args: <String, Object>{
          'streamId': 'Isolate',
        },
      ),
      FakeVmServiceRequest(
        method: kRunInViewMethod,
        args: <String, Object>{
          'viewId': fakeFlutterView.id,
1068
          'mainScript': 'main.dart.dill',
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
          'assetDirectory': 'build/flutter_assets',
        },
      ),
      FakeVmServiceStreamResponse(
        streamId: 'Isolate',
        event: vm_service.Event(
          timestamp: 0,
          kind: vm_service.EventKind.kIsolateRunnable,
        ),
      )
    ]);
1080 1081
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
1082
    unawaited(residentRunner.attach(
1083 1084 1085
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
1086 1087 1088 1089 1090 1091 1092
    ));

    await residentRunner.restart(fullRestart: true);
    await residentRunner.restart(fullRestart: true);
    await residentRunner.restart(fullRestart: true);

    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1093
  }));
1094

1095
  testUsingContext('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async {
1096 1097 1098 1099
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
    ]);
1100 1101
    final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
    final Completer<void> futureAppStart = Completer<void>.sync();
1102
    unawaited(residentRunner.attach(
1103 1104 1105
      appStartedCompleter: futureAppStart,
      connectionInfoCompleter: futureConnectionInfo,
      enableDevTools: true,
1106
    ));
1107
    await futureAppStart.future;
1108
    flutterDevice.reportError = vm_service.RPCError('something bad happened', 666, '');
1109 1110 1111 1112

    final OperationResult result = await residentRunner.restart(fullRestart: true);
    expect(result.fatal, true);
    expect(result.code, 1);
1113 1114

    expect((globals.flutterUsage as TestUsage).events, contains(
1115 1116 1117 1118 1119 1120
      TestUsageEvent('hot', 'exception', parameters: CustomDimensions(
        hotEventTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
        hotEventSdkName: 'Android',
        hotEventEmulator: false,
        hotEventFullRestart: true,
      )),
1121
    ));
1122
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1123
  }, overrides: <Type, Generator>{
1124
    Usage: () => TestUsage(),
1125
  }));
1126

1127
  testUsingContext('ResidentRunner uses temp directory when there is no output dill path', () => testbed.run(() {
1128
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1129
    expect(residentRunner.artifactDirectory.path, contains('flutter_tool.'));
1130 1131 1132

    final ResidentRunner otherRunner = HotRunner(
      <FlutterDevice>[
1133
        flutterDevice,
1134 1135 1136
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
1137
      dillOutputPath: globals.fs.path.join('foobar', 'app.dill'),
1138
      target: 'main.dart',
1139
      devtoolsHandler: createNoOpHandler,
1140 1141 1142 1143
    );
    expect(otherRunner.artifactDirectory.path, contains('foobar'));
  }));

1144
  testUsingContext('ResidentRunner deletes artifact directory on preExit', () => testbed.run(() async {
1145
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1146
    residentRunner.artifactDirectory.childFile('app.dill').createSync();
1147 1148
    await residentRunner.preExit();

1149
    expect(residentRunner.artifactDirectory, isNot(exists));
1150 1151
  }));

1152
  testUsingContext('ResidentRunner can run source generation', () => testbed.run(() async {
1153 1154
    final File arbFile = globals.fs.file(globals.fs.path.join('lib', 'l10n', 'app_en.arb'))
      ..createSync(recursive: true);
1155 1156
    arbFile.writeAsStringSync('''
{
1157 1158 1159 1160 1161
  "helloWorld": "Hello, World!",
  "@helloWorld": {
    "description": "Sample description"
  }
}''');
1162
    globals.fs.file('l10n.yaml').createSync();
1163
    globals.fs.file('pubspec.yaml').writeAsStringSync('flutter:\n  generate: true\n');
1164

1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184
    // Create necessary files for [DartPluginRegistrantTarget]
    final File packageConfig = globals.fs.directory('.dart_tool')
        .childFile('package_config.json');
    packageConfig.createSync(recursive: true);
    packageConfig.writeAsStringSync('''
{
  "configVersion": 2,
  "packages": [
    {
      "name": "path_provider_linux",
      "rootUri": "../../../path_provider_linux",
      "packageUri": "lib/",
      "languageVersion": "2.12"
    }
  ]
}
''');
    // Start from an empty generated_main.dart file.
    globals.fs.directory('.dart_tool').childDirectory('flutter_build').childFile('generated_main.dart').createSync(recursive: true);

1185 1186 1187
    await residentRunner.runSourceGenerators();

    expect(testLogger.errorText, isEmpty);
1188
    expect(testLogger.statusText, isEmpty);
1189 1190
  }));

1191
  testUsingContext('ResidentRunner can run source generation - generation fails', () => testbed.run(() async {
1192 1193 1194 1195
    // Intentionally define arb file with wrong name. generate_localizations defaults
    // to app_en.arb.
    final File arbFile = globals.fs.file(globals.fs.path.join('lib', 'l10n', 'foo.arb'))
      ..createSync(recursive: true);
1196 1197
    arbFile.writeAsStringSync('''
{
1198 1199 1200 1201 1202
  "helloWorld": "Hello, World!",
  "@helloWorld": {
    "description": "Sample description"
  }
}''');
1203
    globals.fs.file('l10n.yaml').createSync();
1204
    globals.fs.file('pubspec.yaml').writeAsStringSync('flutter:\n  generate: true\n');
1205 1206 1207

    await residentRunner.runSourceGenerators();

1208 1209
    expect(testLogger.errorText, allOf(contains('Exception')));
    expect(testLogger.statusText, isEmpty);
1210 1211
  }));

1212
  testUsingContext('ResidentRunner printHelpDetails hot runner', () => testbed.run(() {
1213
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1214 1215 1216

    residentRunner.printHelp(details: true);

1217 1218
    final CommandHelp commandHelp = residentRunner.commandHelp;

1219 1220 1221 1222
    // supports service protocol
    expect(residentRunner.supportsServiceProtocol, true);
    // isRunningDebug
    expect(residentRunner.isRunningDebug, true);
1223 1224
    // does support SkSL
    expect(residentRunner.supportsWriteSkSL, true);
1225 1226 1227 1228
    // commands
    expect(testLogger.statusText, equals(
        <dynamic>[
          'Flutter run key commands.',
1229 1230
          commandHelp.r,
          commandHelp.R,
1231
          commandHelp.v,
1232 1233 1234 1235 1236 1237 1238 1239
          commandHelp.s,
          commandHelp.w,
          commandHelp.t,
          commandHelp.L,
          commandHelp.S,
          commandHelp.U,
          commandHelp.i,
          commandHelp.p,
1240
          commandHelp.I,
1241
          commandHelp.o,
1242
          commandHelp.b,
1243 1244
          commandHelp.P,
          commandHelp.a,
1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280
          commandHelp.M,
          commandHelp.g,
          commandHelp.hWithDetails,
          commandHelp.c,
          commandHelp.q,
          '',
          '💪 Running with sound null safety 💪',
          '',
          'An Observatory debugger and profiler on FakeDevice is available at: null',
          '',
        ].join('\n')
    ));
  }));

  testUsingContext('ResidentRunner printHelp hot runner', () => testbed.run(() {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);

    residentRunner.printHelp(details: false);

    final CommandHelp commandHelp = residentRunner.commandHelp;

    // supports service protocol
    expect(residentRunner.supportsServiceProtocol, true);
    // isRunningDebug
    expect(residentRunner.isRunningDebug, true);
    // does support SkSL
    expect(residentRunner.supportsWriteSkSL, true);
    // commands
    expect(testLogger.statusText, equals(
        <dynamic>[
          'Flutter run key commands.',
          commandHelp.r,
          commandHelp.R,
          commandHelp.hWithoutDetails,
          commandHelp.c,
          commandHelp.q,
1281 1282 1283
          '',
          '💪 Running with sound null safety 💪',
          '',
1284
          'An Observatory debugger and profiler on FakeDevice is available at: null',
1285
          '',
1286 1287
        ].join('\n')
    ));
1288 1289
  }));

1290 1291 1292 1293
  testUsingContext('ResidentRunner printHelpDetails cold runner', () => testbed.run(() {
    fakeVmServiceHost = null;
    residentRunner = ColdRunner(
      <FlutterDevice>[
1294
        flutterDevice,
1295 1296 1297
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
1298
      target: 'main.dart',
1299
      devtoolsHandler: createNoOpHandler,
1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314
    );
    residentRunner.printHelp(details: true);

    final CommandHelp commandHelp = residentRunner.commandHelp;

    // does not supports service protocol
    expect(residentRunner.supportsServiceProtocol, false);
    // isRunningDebug
    expect(residentRunner.isRunningDebug, false);
    // does support SkSL
    expect(residentRunner.supportsWriteSkSL, false);
    // commands
    expect(testLogger.statusText, equals(
        <dynamic>[
          'Flutter run key commands.',
1315
          commandHelp.v,
1316
          commandHelp.s,
1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328
          commandHelp.hWithDetails,
          commandHelp.c,
          commandHelp.q,
          ''
        ].join('\n')
    ));
  }));

  testUsingContext('ResidentRunner printHelp cold runner', () => testbed.run(() {
    fakeVmServiceHost = null;
    residentRunner = ColdRunner(
      <FlutterDevice>[
1329
        flutterDevice,
1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
      target: 'main.dart',
      devtoolsHandler: createNoOpHandler,
    );
    residentRunner.printHelp(details: false);

    final CommandHelp commandHelp = residentRunner.commandHelp;

    // does not supports service protocol
    expect(residentRunner.supportsServiceProtocol, false);
    // isRunningDebug
    expect(residentRunner.isRunningDebug, false);
    // does support SkSL
    expect(residentRunner.supportsWriteSkSL, false);
    // commands
    expect(testLogger.statusText, equals(
        <dynamic>[
          'Flutter run key commands.',
          commandHelp.hWithoutDetails,
1351 1352 1353 1354 1355 1356 1357
          commandHelp.c,
          commandHelp.q,
          ''
        ].join('\n')
    ));
  }));

1358
  testUsingContext('ResidentRunner handles writeSkSL returning no data', () => testbed.run(() async {
1359
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
1360 1361
      listViews,
      FakeVmServiceRequest(
1362 1363
        method: kGetSkSLsMethod,
        args: <String, Object>{
1364
          'viewId': fakeFlutterView.id,
1365 1366 1367 1368
        },
        jsonResponse: <String, Object>{
          'SkSLs': <String, Object>{}
        }
1369
      ),
1370
    ]);
1371 1372
    await residentRunner.writeSkSL();

1373
    expect(testLogger.statusText, contains('No data was received'));
1374
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1375 1376
  }));

1377
  testUsingContext('ResidentRunner can write SkSL data to a unique file with engine revision, platform, and device name', () => testbed.run(() async {
1378
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
1379
      listViews,
1380
      FakeVmServiceRequest(
1381 1382
        method: kGetSkSLsMethod,
        args: <String, Object>{
1383
          'viewId': fakeFlutterView.id,
1384 1385 1386 1387 1388 1389 1390 1391
        },
        jsonResponse: <String, Object>{
          'SkSLs': <String, Object>{
            'A': 'B',
          }
        }
      )
    ]);
1392 1393
    await residentRunner.writeSkSL();

1394 1395 1396
    expect(testLogger.statusText, contains('flutter_01.sksl.json'));
    expect(globals.fs.file('flutter_01.sksl.json'), exists);
    expect(json.decode(globals.fs.file('flutter_01.sksl.json').readAsStringSync()), <String, Object>{
1397
      'platform': 'android',
1398
      'name': 'FakeDevice',
1399
      'engineRevision': 'abcdefg',
1400 1401
      'data': <String, Object>{'A': 'B'}
    });
1402
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1403 1404 1405 1406
  }, overrides: <Type, Generator>{
    FileSystemUtils: () => FileSystemUtils(
      fileSystem: globals.fs,
      platform: globals.platform,
1407 1408
    ),
    FlutterVersion: () => FakeFlutterVersion(engineRevision: 'abcdefg')
1409 1410
  }));

1411 1412 1413 1414 1415 1416 1417
  testUsingContext('ResidentRunner ignores DevtoolsLauncher when attaching with enableDevTools: false - cold mode', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
    ]);
    residentRunner = ColdRunner(
      <FlutterDevice>[
1418
        flutterDevice,
1419 1420 1421 1422
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile, vmserviceOutFile: 'foo'),
      target: 'main.dart',
1423
      devtoolsHandler: createNoOpHandler,
1424 1425 1426 1427
    );

    final Future<int> result = residentRunner.attach(enableDevTools: false);
    expect(await result, 0);
1428
  }));
1429

1430
  testUsingContext('FlutterDevice can exit from a release mode isolate with no VmService', () => testbed.run(() async {
1431
    final TestFlutterDevice flutterDevice = TestFlutterDevice(
1432
      device,
1433 1434 1435 1436
    );

    await flutterDevice.exitApps();

1437
    expect(device.appStopped, true);
1438 1439
  }));

1440 1441
  testUsingContext('FlutterDevice will exit an un-paused isolate using stopApp', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1442
    final TestFlutterDevice flutterDevice = TestFlutterDevice(
1443
      device,
1444
    );
1445
    flutterDevice.vmService = fakeVmServiceHost.vmService;
1446

1447 1448 1449
    final Future<void> exitFuture = flutterDevice.exitApps();

    await expectLater(exitFuture, completes);
1450
    expect(device.appStopped, true);
1451
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1452
  }));
1453

1454
  testUsingContext('HotRunner writes vm service file when providing debugging option', () => testbed.run(() async {
1455 1456 1457
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
1458
    ], wsAddress: testUri);
1459
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
1460 1461
    residentRunner = HotRunner(
      <FlutterDevice>[
1462
        flutterDevice,
1463 1464 1465
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, vmserviceOutFile: 'foo'),
1466
      target: 'main.dart',
1467
      devtoolsHandler: createNoOpHandler,
1468
    );
1469

1470
    await residentRunner.run(enableDevTools: true);
1471

1472
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1473
    expect(await globals.fs.file('foo').readAsString(), testUri.toString());
1474
  }));
1475

1476
  testUsingContext('HotRunner copies compiled app.dill to cache during startup', () => testbed.run(() async {
1477 1478 1479
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
1480
    ], wsAddress: testUri);
1481 1482 1483
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
    residentRunner = HotRunner(
      <FlutterDevice>[
1484
        flutterDevice,
1485 1486 1487
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
1488
      target: 'main.dart',
1489
      devtoolsHandler: createNoOpHandler,
1490 1491
    );
    residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
1492

1493
    await residentRunner.run(enableDevTools: true);
1494 1495

    expect(await globals.fs.file(globals.fs.path.join('build', 'cache.dill')).readAsString(), 'ABC');
1496
  }));
1497

1498 1499 1500 1501
  testUsingContext('HotRunner copies compiled app.dill to cache during startup with dart defines', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
1502
    ], wsAddress: testUri);
1503 1504 1505
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
    residentRunner = HotRunner(
      <FlutterDevice>[
1506
        flutterDevice,
1507 1508 1509 1510 1511 1512 1513 1514 1515 1516
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(
        const BuildInfo(
          BuildMode.debug,
          '',
          treeShakeIcons: false,
          dartDefines: <String>['a', 'b'],
        )
      ),
1517
      target: 'main.dart',
1518
      devtoolsHandler: createNoOpHandler,
1519 1520
    );
    residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
1521

1522
    await residentRunner.run(enableDevTools: true);
1523 1524 1525

    expect(await globals.fs.file(globals.fs.path.join(
      'build', '187ef4436122d1cc2f40dc2b92f0eba0.cache.dill')).readAsString(), 'ABC');
1526
  }));
1527

1528 1529 1530 1531
  testUsingContext('HotRunner copies compiled app.dill to cache during startup with null safety', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
1532
    ], wsAddress: testUri);
1533 1534 1535
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
    residentRunner = HotRunner(
      <FlutterDevice>[
1536
        flutterDevice,
1537 1538 1539 1540 1541 1542 1543
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(
        const BuildInfo(
          BuildMode.debug,
          '',
          treeShakeIcons: false,
1544
          extraFrontEndOptions: <String>['--enable-experiment=non-nullable']
1545 1546
        )
      ),
1547
      target: 'main.dart',
1548
      devtoolsHandler: createNoOpHandler,
1549 1550
    );
    residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
1551

1552
    await residentRunner.run(enableDevTools: true);
1553 1554

    expect(await globals.fs.file(globals.fs.path.join(
1555
      'build', 'cache.dill')).readAsString(), 'ABC');
1556
  }));
1557

1558
  testUsingContext('HotRunner does not copy app.dill if a dillOutputPath is given', () => testbed.run(() async {
1559 1560 1561
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
1562
    ], wsAddress: testUri);
1563 1564 1565
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
    residentRunner = HotRunner(
      <FlutterDevice>[
1566
        flutterDevice,
1567 1568 1569 1570
      ],
      stayResident: false,
      dillOutputPath: 'test',
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
1571
      target: 'main.dart',
1572
      devtoolsHandler: createNoOpHandler,
1573 1574
    );
    residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
1575

1576
    await residentRunner.run(enableDevTools: true);
1577 1578

    expect(globals.fs.file(globals.fs.path.join('build', 'cache.dill')), isNot(exists));
1579
  }));
1580

1581
  testUsingContext('HotRunner copies compiled app.dill to cache during startup with --track-widget-creation', () => testbed.run(() async {
1582 1583 1584
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
1585
    ], wsAddress: testUri);
1586 1587 1588
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
    residentRunner = HotRunner(
      <FlutterDevice>[
1589
        flutterDevice,
1590 1591 1592 1593 1594 1595 1596 1597
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(const BuildInfo(
        BuildMode.debug,
        '',
        treeShakeIcons: false,
        trackWidgetCreation: true,
      )),
1598
      target: 'main.dart',
1599
      devtoolsHandler: createNoOpHandler,
1600 1601
    );
    residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
1602

1603
    await residentRunner.run(enableDevTools: true);
1604 1605

    expect(await globals.fs.file(globals.fs.path.join('build', 'cache.dill.track.dill')).readAsString(), 'ABC');
1606
  }));
1607

1608
  testUsingContext('HotRunner calls device dispose', () => testbed.run(() async {
1609 1610 1611
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
1612
    ], wsAddress: testUri);
1613
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
1614 1615
    residentRunner = HotRunner(
      <FlutterDevice>[
1616
        flutterDevice,
1617 1618 1619
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
1620
      target: 'main.dart',
1621
      devtoolsHandler: createNoOpHandler,
1622 1623 1624
    );

    await residentRunner.run();
1625
    expect(device.disposed, true);
1626
  }));
1627

1628
  testUsingContext('HotRunner handles failure to write vmservice file', () => testbed.run(() async {
1629 1630 1631 1632
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      listViews,
    ]);
1633
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
1634 1635
    residentRunner = HotRunner(
      <FlutterDevice>[
1636
        flutterDevice,
1637 1638 1639
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, vmserviceOutFile: 'foo'),
1640
      target: 'main.dart',
1641
      devtoolsHandler: createNoOpHandler,
1642
    );
1643

1644
    await residentRunner.run(enableDevTools: true);
1645

1646
    expect(testLogger.errorText, contains('Failed to write vmservice-out-file at foo'));
1647
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1648
  }, overrides: <Type, Generator>{
1649
    FileSystem: () => ThrowingForwardingFileSystem(MemoryFileSystem.test()),
1650
  }));
1651

1652
  testUsingContext('ColdRunner writes vm service file when providing debugging option', () => testbed.run(() async {
1653 1654
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
1655
    ], wsAddress: testUri);
1656
    globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
1657 1658
    residentRunner = ColdRunner(
      <FlutterDevice>[
1659
        flutterDevice,
1660 1661 1662
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile, vmserviceOutFile: 'foo'),
1663
      target: 'main.dart',
1664
      devtoolsHandler: createNoOpHandler,
1665
    );
1666

1667
    await residentRunner.run(enableDevTools: true);
1668

1669
    expect(await globals.fs.file('foo').readAsString(), testUri.toString());
1670
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1671
  }));
1672

1673
  testUsingContext('FlutterDevice uses dartdevc configuration when targeting web', () async {
1674
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1675
    final FakeDevice device = FakeDevice(targetPlatform: TargetPlatform.web_javascript);
1676
    final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create(
1677
      device,
1678 1679 1680 1681 1682 1683
      buildInfo: const BuildInfo(
        BuildMode.debug,
        '',
        treeShakeIcons: false,
        nullSafetyMode: NullSafetyMode.unsound,
      ),
1684
      target: null,
1685
      platform: FakePlatform(operatingSystem: 'linux'),
1686
    )).generator as DefaultResidentCompiler;
1687

1688
    expect(residentCompiler.initializeFromDill,
1689
      globals.fs.path.join(getBuildDirectory(), 'fbbe6a61fb7a1de317d381f8df4814e5.cache.dill'));
1690
    expect(residentCompiler.librariesSpec,
1691
      globals.fs.file(globals.artifacts.getHostArtifact(HostArtifact.flutterWebLibrariesJson))
1692
        .uri.toString());
1693 1694
    expect(residentCompiler.targetModel, TargetModel.dartdevc);
    expect(residentCompiler.sdkRoot,
1695
      '${globals.artifacts.getHostArtifact(HostArtifact.flutterWebSdk).path}/');
1696
    expect(residentCompiler.platformDill, 'file:///HostArtifact.webPlatformKernelDill');
1697 1698 1699 1700 1701 1702 1703 1704
  }, overrides: <Type, Generator>{
    Artifacts: () => Artifacts.test(),
    FileSystem: () => MemoryFileSystem.test(),
    ProcessManager: () => FakeProcessManager.any(),
  });

  testUsingContext('FlutterDevice uses dartdevc configuration when targeting web with null-safety autodetected', () async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1705
    final FakeDevice device = FakeDevice(targetPlatform: TargetPlatform.web_javascript);
1706 1707

    final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create(
1708
      device,
1709 1710 1711 1712 1713 1714 1715
      buildInfo: const BuildInfo(
        BuildMode.debug,
        '',
        treeShakeIcons: false,
        extraFrontEndOptions: <String>['--enable-experiment=non-nullable'],
      ),
      target: null,
1716
      platform: FakePlatform(operatingSystem: 'linux'),
1717 1718 1719
    )).generator as DefaultResidentCompiler;

    expect(residentCompiler.initializeFromDill,
1720
      globals.fs.path.join(getBuildDirectory(), '80b1a4cf4e7b90e1ab5f72022a0bc624.cache.dill'));
1721
    expect(residentCompiler.librariesSpec,
1722
      globals.fs.file(globals.artifacts.getHostArtifact(HostArtifact.flutterWebLibrariesJson))
1723 1724 1725
        .uri.toString());
    expect(residentCompiler.targetModel, TargetModel.dartdevc);
    expect(residentCompiler.sdkRoot,
1726
      '${globals.artifacts.getHostArtifact(HostArtifact.flutterWebSdk).path}/');
1727
    expect(residentCompiler.platformDill, 'file:///HostArtifact.webPlatformSoundKernelDill');
1728 1729 1730 1731
  }, overrides: <Type, Generator>{
    Artifacts: () => Artifacts.test(),
    FileSystem: () => MemoryFileSystem.test(),
    ProcessManager: () => FakeProcessManager.any(),
1732
  });
1733

1734 1735
  testUsingContext('FlutterDevice passes flutter-widget-cache flag when feature is enabled', () async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1736
    final FakeDevice device = FakeDevice(targetPlatform: TargetPlatform.android_arm);
1737 1738

    final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create(
1739
      device,
1740 1741 1742 1743 1744 1745
      buildInfo: const BuildInfo(
        BuildMode.debug,
        '',
        treeShakeIcons: false,
        extraFrontEndOptions: <String>[],
      ),
1746
      target: null, platform: null,
1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757
    )).generator as DefaultResidentCompiler;

    expect(residentCompiler.extraFrontEndOptions,
      contains('--flutter-widget-cache'));
  }, overrides: <Type, Generator>{
    Artifacts: () => Artifacts.test(),
    FileSystem: () => MemoryFileSystem.test(),
    ProcessManager: () => FakeProcessManager.any(),
    FeatureFlags: () => TestFeatureFlags(isSingleWidgetReloadEnabled: true)
  });

1758
   testUsingContext('FlutterDevice passes alternative-invalidation-strategy flag', () async {
1759
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1760
    final FakeDevice device = FakeDevice(targetPlatform: TargetPlatform.android_arm);
1761

1762 1763

    final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create(
1764
      device,
1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781
      buildInfo: const BuildInfo(
        BuildMode.debug,
        '',
        treeShakeIcons: false,
        extraFrontEndOptions: <String>[],
      ),
      target: null, platform: null,
    )).generator as DefaultResidentCompiler;

    expect(residentCompiler.extraFrontEndOptions,
      contains('--enable-experiment=alternative-invalidation-strategy'));
  }, overrides: <Type, Generator>{
    Artifacts: () => Artifacts.test(),
    FileSystem: () => MemoryFileSystem.test(),
    ProcessManager: () => FakeProcessManager.any(),
  });

1782 1783
   testUsingContext('FlutterDevice passes initializeFromDill parameter if specified', () async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1784
    final FakeDevice device = FakeDevice(targetPlatform: TargetPlatform.android_arm);
1785 1786

    final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create(
1787
      device,
1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804
      buildInfo: const BuildInfo(
        BuildMode.debug,
        '',
        treeShakeIcons: false,
        extraFrontEndOptions: <String>[],
        initializeFromDill: '/foo/bar.dill',
      ),
      target: null, platform: null,
    )).generator as DefaultResidentCompiler;

    expect(residentCompiler.initializeFromDill, '/foo/bar.dill');
  }, overrides: <Type, Generator>{
    Artifacts: () => Artifacts.test(),
    FileSystem: () => MemoryFileSystem.test(),
    ProcessManager: () => FakeProcessManager.any(),
  });

1805 1806
  testUsingContext('Handle existing VM service clients DDS error', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1807
    final FakeDevice device = FakeDevice()
1808
      ..dds = DartDevelopmentService();
1809
    ddsLauncherCallback = (Uri uri, {bool enableAuthCodes, bool ipv6, Uri serviceUri}) {
1810 1811 1812 1813
      expect(uri, Uri(scheme: 'foo', host: 'bar'));
      expect(enableAuthCodes, isTrue);
      expect(ipv6, isFalse);
      expect(serviceUri, Uri(scheme: 'http', host: '127.0.0.1', port: 0));
1814 1815 1816 1817 1818
      throw FakeDartDevelopmentServiceException(message:
        'Existing VM service clients prevent DDS from taking control.',
      );
    };
    final TestFlutterDevice flutterDevice = TestFlutterDevice(
1819
      device,
1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837
      observatoryUris: Stream<Uri>.value(testUri),
    );
    bool caught = false;
    final Completer<void>done = Completer<void>();
    runZonedGuarded(() {
      flutterDevice.connect(allowExistingDdsInstance: true).then((_) => done.complete());
    }, (Object e, StackTrace st) {
      expect(e is ToolExit, true);
      expect((e as ToolExit).message,
        contains('Existing VM service clients prevent DDS from taking control.',
      ));
      done.complete();
      caught = true;
    });
    await done.future;
    if (!caught) {
      fail('Expected ToolExit to be thrown.');
    }
1838 1839 1840 1841 1842 1843 1844 1845 1846
  }, overrides: <Type, Generator>{
    VMServiceConnector: () => (Uri httpUri, {
      ReloadSources reloadSources,
      Restart restart,
      CompileExpression compileExpression,
      GetSkSLMethod getSkSLMethod,
      PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
      io.CompressionOptions compression,
      Device device,
1847
      Logger logger,
1848
    }) async => FakeVmServiceHost(requests: <VmServiceExpectation>[]).vmService,
1849 1850
  }));

1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882
  testUsingContext('Host VM service ipv6 defaults', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
    final FakeDevice device = FakeDevice()
      ..dds = DartDevelopmentService();
    final Completer<void>done = Completer<void>();
    ddsLauncherCallback = (Uri uri, {bool enableAuthCodes, bool ipv6, Uri serviceUri}) async {
      expect(uri, Uri(scheme: 'foo', host: 'bar'));
      expect(enableAuthCodes, isFalse);
      expect(ipv6, isTrue);
      expect(serviceUri, Uri(scheme: 'http', host: '::1', port: 0));
      done.complete();
      return null;
    };
    final TestFlutterDevice flutterDevice = TestFlutterDevice(
      device,
      observatoryUris: Stream<Uri>.value(testUri),
    );
    await flutterDevice.connect(allowExistingDdsInstance: true, ipv6: true, disableServiceAuthCodes: true);
    await done.future;
  }, overrides: <Type, Generator>{
    VMServiceConnector: () => (Uri httpUri, {
      ReloadSources reloadSources,
      Restart restart,
      CompileExpression compileExpression,
      GetSkSLMethod getSkSLMethod,
      PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
      io.CompressionOptions compression,
      Device device,
      Logger logger,
    }) async => FakeVmServiceHost(requests: <VmServiceExpectation>[]).vmService,
  }));

1883 1884
  testUsingContext('Failed DDS start outputs error message', () => testbed.run(() async {
    // See https://github.com/flutter/flutter/issues/72385 for context.
1885
    final FakeDevice device = FakeDevice()
1886
      ..dds = DartDevelopmentService();
1887
    ddsLauncherCallback = (Uri uri, {bool enableAuthCodes, bool ipv6, Uri serviceUri}) {
1888 1889 1890 1891
      expect(uri, Uri(scheme: 'foo', host: 'bar'));
      expect(enableAuthCodes, isTrue);
      expect(ipv6, isFalse);
      expect(serviceUri, Uri(scheme: 'http', host: '127.0.0.1', port: 0));
1892 1893 1894
      throw FakeDartDevelopmentServiceException(message: 'No URI');
    };
    final TestFlutterDevice flutterDevice = TestFlutterDevice(
1895
      device,
1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914
      observatoryUris: Stream<Uri>.value(testUri),
    );
    bool caught = false;
    final Completer<void>done = Completer<void>();
    runZonedGuarded(() {
      flutterDevice.connect(allowExistingDdsInstance: true).then((_) => done.complete());
    }, (Object e, StackTrace st) {
      expect(e is StateError, true);
      expect((e as StateError).message, contains('No URI'));
      expect(testLogger.errorText, contains(
        'DDS has failed to start and there is not an existing DDS instance',
      ));
      done.complete();
      caught = true;
    });
    await done.future;
    if (!caught) {
      fail('Expected a StateError to be thrown.');
    }
1915 1916 1917 1918 1919 1920 1921 1922 1923
  }, overrides: <Type, Generator>{
    VMServiceConnector: () => (Uri httpUri, {
      ReloadSources reloadSources,
      Restart restart,
      CompileExpression compileExpression,
      GetSkSLMethod getSkSLMethod,
      PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
      io.CompressionOptions compression,
      Device device,
1924
      Logger logger,
1925
    }) async => FakeVmServiceHost(requests: <VmServiceExpectation>[]).vmService,
1926
  }));
1927

1928
  testUsingContext('nextPlatform moves through expected platforms', () {
1929 1930 1931 1932 1933
    expect(nextPlatform('android'), 'iOS');
    expect(nextPlatform('iOS'), 'fuchsia');
    expect(nextPlatform('fuchsia'), 'macOS');
    expect(nextPlatform('macOS'), 'android');
    expect(() => nextPlatform('unknown'), throwsAssertionError);
1934
  });
1935 1936 1937 1938

  testUsingContext('cleanupAtFinish shuts down resident devtools handler', () => testbed.run(() async {
    residentRunner = HotRunner(
      <FlutterDevice>[
1939
        flutterDevice,
1940 1941 1942 1943 1944 1945 1946 1947 1948 1949
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, vmserviceOutFile: 'foo'),
      target: 'main.dart',
      devtoolsHandler: createNoOpHandler,
    );
    await residentRunner.cleanupAtFinish();

    expect((residentRunner.residentDevtoolsHandler as NoOpDevtoolsHandler).wasShutdown, true);
  }));
1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015

  testUsingContext('HotRunner sets asset directory when first evict assets', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      setAssetBundlePath,
      evict,
    ]);
    residentRunner = HotRunner(
      <FlutterDevice>[
        flutterDevice,
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      target: 'main.dart',
      devtoolsHandler: createNoOpHandler,
    );

    (flutterDevice.devFS as FakeDevFS).assetPathsToEvict = <String>{'asset'};

    expect(flutterDevice.devFS.hasSetAssetDirectory, false);
    await (residentRunner as HotRunner).evictDirtyAssets();
    expect(flutterDevice.devFS.hasSetAssetDirectory, true);
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
  }));

  testUsingContext('HotRunner does not sets asset directory when no assets to evict', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
    ]);
    residentRunner = HotRunner(
      <FlutterDevice>[
        flutterDevice,
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      target: 'main.dart',
      devtoolsHandler: createNoOpHandler,
    );

    expect(flutterDevice.devFS.hasSetAssetDirectory, false);
    await (residentRunner as HotRunner).evictDirtyAssets();
    expect(flutterDevice.devFS.hasSetAssetDirectory, false);
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
  }));

  testUsingContext('HotRunner does not set asset directory if it has been set before', () => testbed.run(() async {
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      listViews,
      evict,
    ]);
    residentRunner = HotRunner(
      <FlutterDevice>[
        flutterDevice,
      ],
      stayResident: false,
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      target: 'main.dart',
      devtoolsHandler: createNoOpHandler,
    );

    (flutterDevice.devFS as FakeDevFS).assetPathsToEvict = <String>{'asset'};
    flutterDevice.devFS.hasSetAssetDirectory = true;

    await (residentRunner as HotRunner).evictDirtyAssets();
    expect(flutterDevice.devFS.hasSetAssetDirectory, true);
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
  }));
2016 2017
}

2018
class FakeDartDevelopmentServiceException implements dds.DartDevelopmentServiceException {
2019 2020
  FakeDartDevelopmentServiceException({this.message = defaultMessage});

2021 2022 2023 2024
  @override
  final int errorCode = dds.DartDevelopmentServiceException.existingDdsInstanceError;

  @override
2025 2026
  final String message;
  static const String defaultMessage = 'A DDS instance is already connected at http://localhost:8181';
2027 2028
}

2029
class TestFlutterDevice extends FlutterDevice {
2030
  TestFlutterDevice(Device device, { Stream<Uri> observatoryUris })
2031
    : super(device, buildInfo: BuildInfo.debug) {
2032 2033
    _observatoryUris = observatoryUris;
  }
2034

2035
  @override
2036 2037
  Stream<Uri> get observatoryUris => _observatoryUris;
  Stream<Uri> _observatoryUris;
2038 2039
}

2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050
class ThrowingForwardingFileSystem extends ForwardingFileSystem {
  ThrowingForwardingFileSystem(FileSystem delegate) : super(delegate);

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

2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150
class FakeFlutterDevice extends Fake implements FlutterDevice {
  FakeVmServiceHost Function() vmServiceHost;
  Uri testUri;
  UpdateFSReport report = UpdateFSReport(
    success: true,
    syncedBytes: 0,
    invalidatedSourcesCount: 1,
  );
  Object reportError;
  Object runColdError;
  int runHotCode = 0;
  int runColdCode = 0;

  @override
  ResidentCompiler generator;

  @override
  Stream<Uri> get observatoryUris => Stream<Uri>.value(testUri);

  @override
  FlutterVmService get vmService => vmServiceHost?.call()?.vmService;

  DevFS _devFS;

  @override
  DevFS get devFS => _devFS;

  @override
  set devFS(DevFS value) { }

  @override
  Device device;

  @override
  Future<void> stopEchoingDeviceLog() async { }

  @override
  Future<void> initLogReader() async { }

  @override
  Future<Uri> setupDevFS(String fsName, Directory rootDirectory) async {
    return testUri;
  }

  @override
  Future<int> runHot({HotRunner hotRunner, String route}) async {
    return runHotCode;
  }

  @override
  Future<int> runCold({ColdRunner coldRunner, String route}) async {
    if (runColdError != null) {
      throw runColdError;
    }
    return runColdCode;
  }

  @override
  Future<void> connect({
    ReloadSources reloadSources,
    Restart restart,
    CompileExpression compileExpression,
    GetSkSLMethod getSkSLMethod,
    PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
    int hostVmServicePort,
    int ddsPort,
    bool disableServiceAuthCodes = false,
    bool enableDds = true,
    @required bool allowExistingDdsInstance,
    bool ipv6 = false,
  }) async { }

  @override
  Future<UpdateFSReport> updateDevFS({
    Uri mainUri,
    String target,
    AssetBundle bundle,
    DateTime firstBuildTime,
    bool bundleFirstUpload = false,
    bool bundleDirty = false,
    bool fullRestart = false,
    String projectRootPath,
    String pathToReload,
    String dillOutputPath,
    List<Uri> invalidatedFiles,
    PackageConfig packageConfig,
  }) async {
    if (reportError != null) {
      throw reportError;
    }
    return report;
  }

  @override
  Future<void> updateReloadStatus(bool wasReloadSuccessful) async { }
}

class FakeDelegateFlutterDevice extends FlutterDevice {
  FakeDelegateFlutterDevice(
2151 2152 2153 2154
    Device device,
    BuildInfo buildInfo,
    ResidentCompiler residentCompiler,
    this.fakeDevFS,
2155
  ) : super(device, buildInfo: buildInfo, generator: residentCompiler);
2156 2157 2158 2159 2160

  @override
  Future<void> connect({
    ReloadSources reloadSources,
    Restart restart,
2161
    bool enableDds = true,
2162
    bool disableServiceAuthCodes = false,
2163
    bool ipv6 = false,
2164 2165
    CompileExpression compileExpression,
    GetSkSLMethod getSkSLMethod,
2166 2167
    int hostVmServicePort,
    int ddsPort,
2168
    PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
2169
    bool allowExistingDdsInstance = false,
2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182
  }) async { }


  final DevFS fakeDevFS;

  @override
  DevFS get devFS => fakeDevFS;

  @override
  set devFS(DevFS value) {}
}

class FakeResidentCompiler extends Fake implements ResidentCompiler {
2183 2184 2185
  CompilerOutput nextOutput;
  bool didSuppressErrors = false;

2186 2187 2188 2189 2190 2191
  @override
  Future<CompilerOutput> recompile(
    Uri mainUri,
    List<Uri> invalidatedFiles, {
    @required String outputPath,
    @required PackageConfig packageConfig,
2192 2193
    @required String projectRootPath,
    @required FileSystem fs,
2194 2195
    bool suppressErrors = false,
  }) async {
2196 2197
    didSuppressErrors = suppressErrors;
    return nextOutput ?? const CompilerOutput('foo.dill', 0, <Uri>[]);
2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221
  }

  @override
  void accept() { }

  @override
  void reset() { }
}

class FakeProjectFileInvalidator extends Fake implements ProjectFileInvalidator {
  @override
  Future<InvalidationResult> findInvalidated({
    @required DateTime lastCompiled,
    @required List<Uri> urisToMonitor,
    @required String packagesPath,
    @required PackageConfig packageConfig,
    bool asyncScanning = false,
  }) async {
    return InvalidationResult(
      packageConfig: packageConfig ?? PackageConfig.empty,
      uris: <Uri>[Uri.parse('file:///hello_world/main.dart'),
    ]);
  }
}
2222 2223 2224 2225 2226 2227 2228

class FakeDevice extends Fake implements Device {
  FakeDevice({
    String sdkNameAndVersion = 'Android',
    TargetPlatform targetPlatform = TargetPlatform.android_arm,
    bool isLocalEmulator = false,
    this.supportsHotRestart = true,
2229 2230
    this.supportsScreenshot = true,
    this.supportsFlutterExit = true,
2231 2232 2233 2234 2235 2236 2237 2238
  }) : _isLocalEmulator = isLocalEmulator,
       _targetPlatform = targetPlatform,
       _sdkNameAndVersion = sdkNameAndVersion;

  final bool _isLocalEmulator;
  final TargetPlatform _targetPlatform;
  final String _sdkNameAndVersion;

2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251
  bool disposed = false;
  bool appStopped = false;
  bool failScreenshot = false;

  @override
  bool supportsHotRestart;

  @override
  bool supportsScreenshot;

  @override
  bool supportsFlutterExit;

2252
  @override
2253 2254 2255
  PlatformType get platformType => _targetPlatform == TargetPlatform.web_javascript
    ? PlatformType.web
    : PlatformType.android;
2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269

  @override
  Future<String> get sdkNameAndVersion async => _sdkNameAndVersion;

  @override
  Future<TargetPlatform> get targetPlatform async => _targetPlatform;

  @override
  Future<bool> get isLocalEmulator async => _isLocalEmulator;

  @override
  String get name => 'FakeDevice';

  @override
2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298
  DartDevelopmentService dds;

  @override
  Future<void> dispose() async {
    disposed = true;
  }

  @override
  Future<bool> stopApp(covariant ApplicationPackage app, {String userIdentifier}) async {
    appStopped = true;
    return true;
  }

  @override
  Future<void> takeScreenshot(File outputFile) async {
    if (failScreenshot) {
      throw Exception();
    }
    outputFile.writeAsBytesSync(List<int>.generate(1024, (int i) => i));
  }

  @override
  FutureOr<DeviceLogReader> getLogReader({
    covariant ApplicationPackage app,
    bool includePastLogs = false,
  }) => NoOpDeviceLogReader(name);

  @override
  DevicePortForwarder portForwarder = const NoOpDevicePortForwarder();
2299
}
2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321

class FakeDevFS extends Fake implements DevFS {
  @override
  DateTime lastCompiled = DateTime(2000);

  @override
  PackageConfig lastPackageConfig = PackageConfig.empty;

  @override
  List<Uri> sources = <Uri>[];

  @override
  Uri baseUri = Uri();

  @override
  Future<void> destroy() async { }

  @override
  Set<String> assetPathsToEvict = <String>{};

  UpdateFSReport nextUpdateReport = UpdateFSReport(success: true);

2322 2323 2324
  @override
  bool hasSetAssetDirectory = false;

2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354
  @override
  Future<Uri> create() async {
    return Uri();
  }

  @override
  void resetLastCompiled() {
    lastCompiled = null;
  }

  @override
  Future<UpdateFSReport> update({
    @required Uri mainUri,
    @required ResidentCompiler generator,
    @required bool trackWidgetCreation,
    @required String pathToReload,
    @required List<Uri> invalidatedFiles,
    @required PackageConfig packageConfig,
    @required String dillOutputPath,
    DevFSWriter devFSWriter,
    String target,
    AssetBundle bundle,
    DateTime firstBuildTime,
    bool bundleFirstUpload = false,
    bool fullRestart = false,
    String projectRootPath,
  }) async {
    return nextUpdateReport;
  }
}