resident_web_runner_test.dart 50 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:convert';
9
import 'dart:io';
10 11

import 'package:dwds/dwds.dart';
12
import 'package:file/memory.dart';
13
import 'package:flutter_tools/src/application_package.dart';
14
import 'package:flutter_tools/src/asset.dart';
15
import 'package:flutter_tools/src/base/dds.dart';
16
import 'package:flutter_tools/src/base/file_system.dart';
17
import 'package:flutter_tools/src/base/logger.dart';
18
import 'package:flutter_tools/src/base/platform.dart';
19
import 'package:flutter_tools/src/base/time.dart';
20
import 'package:flutter_tools/src/build_info.dart';
21
import 'package:flutter_tools/src/build_system/targets/shader_compiler.dart';
22 23
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/devfs.dart';
24
import 'package:flutter_tools/src/device.dart';
25
import 'package:flutter_tools/src/globals.dart' as globals;
26 27
import 'package:flutter_tools/src/isolated/devfs_web.dart';
import 'package:flutter_tools/src/isolated/resident_web_runner.dart';
28
import 'package:flutter_tools/src/project.dart';
29
import 'package:flutter_tools/src/reporting/reporting.dart';
30
import 'package:flutter_tools/src/resident_devtools_handler.dart';
31
import 'package:flutter_tools/src/resident_runner.dart';
32
import 'package:flutter_tools/src/vmservice.dart';
33
import 'package:flutter_tools/src/web/chrome.dart';
34
import 'package:flutter_tools/src/web/web_device.dart';
35 36 37
import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
import 'package:package_config/package_config_types.dart';
38
import 'package:test/fake.dart';
39
import 'package:vm_service/vm_service.dart' as vm_service;
40
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
41 42

import '../src/common.dart';
43
import '../src/context.dart';
44
import '../src/fake_vm_services.dart';
45

46 47
const List<VmServiceExpectation> kAttachLogExpectations =
    <VmServiceExpectation>[
48 49 50 51 52 53 54 55 56 57 58
  FakeVmServiceRequest(
    method: 'streamListen',
    args: <String, Object>{
      'streamId': 'Stdout',
    },
  ),
  FakeVmServiceRequest(
    method: 'streamListen',
    args: <String, Object>{
      'streamId': 'Stderr',
    },
59
  ),
60 61
];

62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
const List<VmServiceExpectation> kAttachIsolateExpectations =
    <VmServiceExpectation>[
  FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{
    'streamId': 'Isolate',
  }),
  FakeVmServiceRequest(method: 'registerService', args: <String, Object>{
    'service': 'reloadSources',
    'alias': 'Flutter Tools',
  }),
  FakeVmServiceRequest(method: 'registerService', args: <String, Object>{
    'service': 'flutterVersion',
    'alias': 'Flutter Tools',
  }),
  FakeVmServiceRequest(method: 'registerService', args: <String, Object>{
    'service': 'flutterMemoryInfo',
    'alias': 'Flutter Tools',
  }),
79 80 81 82 83 84
  FakeVmServiceRequest(
    method: 'streamListen',
    args: <String, Object>{
      'streamId': 'Extension',
    },
  ),
85 86 87 88 89 90 91
];

const List<VmServiceExpectation> kAttachExpectations = <VmServiceExpectation>[
  ...kAttachLogExpectations,
  ...kAttachIsolateExpectations,
];

92
void main() {
93
  FakeDebugConnection debugConnection;
94
  FakeChromeDevice chromeDevice;
95 96
  FakeAppConnection appConnection;
  FakeFlutterDevice flutterDevice;
97 98
  FakeWebDevFS webDevFS;
  FakeResidentCompiler residentCompiler;
99 100
  FakeChromeConnection chromeConnection;
  FakeChromeTab chromeTab;
101 102
  FakeWebServerDevice webServerDevice;
  FakeDevice mockDevice;
103
  FakeVmServiceHost fakeVmServiceHost;
104 105
  FileSystem fileSystem;
  ProcessManager processManager;
106
  TestUsage testUsage;
107 108

  setUp(() {
109
    testUsage = TestUsage();
110 111
    fileSystem = MemoryFileSystem.test();
    processManager = FakeProcessManager.any();
112 113 114
    debugConnection = FakeDebugConnection();
    mockDevice = FakeDevice();
    appConnection = FakeAppConnection();
115 116
    webDevFS = FakeWebDevFS();
    residentCompiler = FakeResidentCompiler();
117 118
    chromeConnection = FakeChromeConnection();
    chromeTab = FakeChromeTab('index.html');
119 120 121 122 123
    webServerDevice = FakeWebServerDevice();
    flutterDevice = FakeFlutterDevice()
      .._devFS = webDevFS
      ..device = mockDevice
      ..generator = residentCompiler;
124
    fileSystem.file('.packages').writeAsStringSync('\n');
125 126
  });

127
  void setupMocks() {
128 129 130
    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('lib/main.dart').createSync(recursive: true);
    fileSystem.file('web/index.html').createSync(recursive: true);
131
    webDevFS.report = UpdateFSReport(success: true);
132
    debugConnection.fakeVmServiceHost = () => fakeVmServiceHost;
133
    webDevFS.result = ConnectionResult(
134 135 136
      appConnection,
      debugConnection,
      debugConnection.vmService,
137
    );
138
    debugConnection.uri = 'ws://127.0.0.1/abcd/';
139
    chromeConnection.tabs.add(chromeTab);
140 141
  }

142 143 144
  testUsingContext(
      'runner with web server device does not support debugging without --start-paused',
      () {
145 146
    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
    flutterDevice.device = WebServerDevice(
147
      logger: BufferLogger.test(),
148
    );
149
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
150
    final ResidentRunner profileResidentWebRunner = ResidentWebRunner(
151
      flutterDevice,
152 153
      flutterProject:
          FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
154 155
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      ipv6: true,
156 157 158 159 160
      fileSystem: fileSystem,
      logger: BufferLogger.test(),
      usage: globals.flutterUsage,
      systemClock: globals.systemClock,
    );
161 162

    expect(profileResidentWebRunner.debuggingEnabled, false);
163

164
    flutterDevice.device = FakeChromeDevice();
165

166
    expect(residentWebRunner.debuggingEnabled, true);
167
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
168 169 170 171
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
172

173 174 175
  testUsingContext(
      'runner with web server device supports debugging with --start-paused',
      () {
176
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
177
    setupMocks();
178
    flutterDevice.device = WebServerDevice(
179
      logger: BufferLogger.test(),
180
    );
181
    final ResidentRunner profileResidentWebRunner = ResidentWebRunner(
182
      flutterDevice,
183 184 185 186
      flutterProject:
          FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
      debuggingOptions:
          DebuggingOptions.enabled(BuildInfo.debug, startPaused: true),
187
      ipv6: true,
188 189 190 191
      fileSystem: fileSystem,
      logger: BufferLogger.test(),
      usage: globals.flutterUsage,
      systemClock: globals.systemClock,
192 193
    );

194
    expect(profileResidentWebRunner.uri, webDevFS.baseUri);
195
    expect(profileResidentWebRunner.debuggingEnabled, true);
196 197 198 199 200
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
  testUsingContext('profile does not supportsServiceProtocol', () {
201
    final ResidentRunner residentWebRunner = ResidentWebRunner(
202
      flutterDevice,
203 204
      flutterProject:
          FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
205 206
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      ipv6: true,
207 208 209 210 211
      fileSystem: fileSystem,
      logger: BufferLogger.test(),
      usage: globals.flutterUsage,
      systemClock: globals.systemClock,
    );
212
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
213
    flutterDevice.device = chromeDevice;
214
    final ResidentRunner profileResidentWebRunner = ResidentWebRunner(
215
      flutterDevice,
216 217
      flutterProject:
          FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
218 219
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile),
      ipv6: true,
220 221 222 223
      fileSystem: fileSystem,
      logger: BufferLogger.test(),
      usage: globals.flutterUsage,
      systemClock: globals.systemClock,
224 225 226 227
    );

    expect(profileResidentWebRunner.supportsServiceProtocol, false);
    expect(residentWebRunner.supportsServiceProtocol, true);
228 229 230 231
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
232

233
  testUsingContext('Can successfully run and connect to vmservice', () async {
234
    final BufferLogger logger = BufferLogger.test();
235 236 237 238
    final ResidentRunner residentWebRunner =
        setUpResidentRunner(flutterDevice, logger: logger);
    fakeVmServiceHost =
        FakeVmServiceHost(requests: kAttachExpectations.toList());
239
    setupMocks();
240

241 242
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
243 244 245
    unawaited(residentWebRunner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
246 247
    final DebugConnectionInfo debugConnectionInfo =
        await connectionInfoCompleter.future;
248

249
    expect(appConnection.ranMain, true);
250 251
    expect(logger.statusText,
        contains('Debug service listening on ws://127.0.0.1/abcd/'));
252
    expect(debugConnectionInfo.wsUri.toString(), 'ws://127.0.0.1/abcd/');
253
  }, overrides: <Type, Generator>{
254 255 256
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
257

258 259
  testUsingContext('WebRunner copies compiled app.dill to cache during startup',
      () async {
260 261 262
    final DebuggingOptions debuggingOptions = DebuggingOptions.enabled(
      const BuildInfo(BuildMode.debug, null, treeShakeIcons: false),
    );
263 264 265 266
    final ResidentRunner residentWebRunner =
        setUpResidentRunner(flutterDevice, debuggingOptions: debuggingOptions);
    fakeVmServiceHost =
        FakeVmServiceHost(requests: kAttachExpectations.toList());
267
    setupMocks();
268

269 270 271 272 273
    residentWebRunner.artifactDirectory
        .childFile('app.dill')
        .writeAsStringSync('ABC');
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
274 275 276 277 278
    unawaited(residentWebRunner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;

279 280 281 282 283
    expect(
        await fileSystem
            .file(fileSystem.path.join('build', 'cache.dill'))
            .readAsString(),
        'ABC');
284 285 286 287
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
288

289 290 291
  testUsingContext(
      'WebRunner copies compiled app.dill to cache during startup with track-widget-creation',
      () async {
292
    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
293 294
    fakeVmServiceHost =
        FakeVmServiceHost(requests: kAttachExpectations.toList());
295
    setupMocks();
296

297 298 299 300 301
    residentWebRunner.artifactDirectory
        .childFile('app.dill')
        .writeAsStringSync('ABC');
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
302 303 304 305 306
    unawaited(residentWebRunner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;

307 308 309 310 311
    expect(
        await fileSystem
            .file(fileSystem.path.join('build', 'cache.dill.track.dill'))
            .readAsString(),
        'ABC');
312 313 314 315 316
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });

317
  // Regression test for https://github.com/flutter/flutter/issues/60613
318 319 320 321 322
  testUsingContext(
      'ResidentWebRunner calls appFailedToStart if initial compilation fails',
      () async {
    fakeVmServiceHost =
        FakeVmServiceHost(requests: kAttachExpectations.toList());
323
    setupMocks();
324
    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
325 326 327
    fileSystem
        .file(globals.fs.path.join('lib', 'main.dart'))
        .createSync(recursive: true);
328
    webDevFS.report = UpdateFSReport();
329 330 331 332 333 334 335 336 337

    expect(await residentWebRunner.run(), 1);
    // Completing this future ensures that the daemon can exit correctly.
    expect(await residentWebRunner.waitForAppToFinish(), 1);
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });

338 339 340
  testUsingContext(
      'Can successfully run without an index.html including status warning',
      () async {
341
    final BufferLogger logger = BufferLogger.test();
342 343
    fakeVmServiceHost =
        FakeVmServiceHost(requests: kAttachExpectations.toList());
344
    setupMocks();
345
    fileSystem.file(fileSystem.path.join('web', 'index.html')).deleteSync();
346
    final ResidentWebRunner residentWebRunner = ResidentWebRunner(
347
      flutterDevice,
348 349
      flutterProject:
          FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
350 351 352
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      ipv6: true,
      stayResident: false,
353 354 355 356 357
      fileSystem: fileSystem,
      logger: logger,
      usage: globals.flutterUsage,
      systemClock: globals.systemClock,
    );
358 359

    expect(await residentWebRunner.run(), 0);
360
    expect(logger.statusText,
361
        contains('This application is not configured to build on the web'));
362 363 364 365
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
366

367 368 369 370
  testUsingContext('Can successfully run and disconnect with --no-resident',
      () async {
    fakeVmServiceHost =
        FakeVmServiceHost(requests: kAttachExpectations.toList());
371
    setupMocks();
372
    final ResidentRunner residentWebRunner = ResidentWebRunner(
373
      flutterDevice,
374 375
      flutterProject:
          FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
376 377 378
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      ipv6: true,
      stayResident: false,
379 380 381 382 383
      fileSystem: fileSystem,
      logger: BufferLogger.test(),
      usage: globals.flutterUsage,
      systemClock: globals.systemClock,
    );
384 385

    expect(await residentWebRunner.run(), 0);
386 387 388 389
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
390

391 392
  testUsingContext('Listens to stdout and stderr streams before running main',
      () async {
393
    final BufferLogger logger = BufferLogger.test();
394 395
    final ResidentRunner residentWebRunner =
        setUpResidentRunner(flutterDevice, logger: logger);
396 397 398 399 400
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      ...kAttachLogExpectations,
      FakeVmServiceStreamResponse(
        streamId: 'Stdout',
        event: vm_service.Event(
401 402 403
            timestamp: 0,
            kind: vm_service.EventStreams.kStdout,
            bytes: base64.encode(utf8.encode('THIS MESSAGE IS IMPORTANT'))),
404 405 406 407
      ),
      FakeVmServiceStreamResponse(
        streamId: 'Stderr',
        event: vm_service.Event(
408 409 410
            timestamp: 0,
            kind: vm_service.EventStreams.kStderr,
            bytes: base64.encode(utf8.encode('SO IS THIS'))),
411 412 413
      ),
      ...kAttachIsolateExpectations,
    ]);
414
    setupMocks();
415 416
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
417 418 419 420 421
    unawaited(residentWebRunner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;

422 423
    expect(logger.statusText, contains('THIS MESSAGE IS IMPORTANT'));
    expect(logger.statusText, contains('SO IS THIS'));
424 425 426 427
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
428

429 430 431 432
  testUsingContext('Listens to extension events with structured errors',
      () async {
    final ResidentRunner residentWebRunner =
        setUpResidentRunner(flutterDevice, logger: testLogger);
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
    final Map<String, String> extensionData = <String, String>{
      'test': 'data',
      'renderedErrorText': 'error text',
    };
    final Map<String, String> emptyExtensionData = <String, String>{
      'test': 'data',
      'renderedErrorText': '',
    };
    final Map<String, String> nonStructuredErrorData = <String, String>{
      'other': 'other stuff',
    };
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      ...kAttachExpectations,
      FakeVmServiceStreamResponse(
        streamId: 'Extension',
        event: vm_service.Event(
          timestamp: 0,
          extensionKind: 'Flutter.Error',
          extensionData: vm_service.ExtensionData.parse(extensionData),
          kind: vm_service.EventStreams.kExtension,
        ),
      ),
      // Empty error text should not break anything.
      FakeVmServiceStreamResponse(
        streamId: 'Extension',
        event: vm_service.Event(
          timestamp: 0,
          extensionKind: 'Flutter.Error',
          extensionData: vm_service.ExtensionData.parse(emptyExtensionData),
          kind: vm_service.EventStreams.kExtension,
        ),
      ),
      // This is not Flutter.Error kind data, so it should not be logged.
      FakeVmServiceStreamResponse(
        streamId: 'Extension',
        event: vm_service.Event(
          timestamp: 0,
          extensionKind: 'Other',
          extensionData: vm_service.ExtensionData.parse(nonStructuredErrorData),
          kind: vm_service.EventStreams.kExtension,
        ),
      ),
    ]);

477
    setupMocks();
478 479
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
480 481 482 483 484 485 486 487
    unawaited(residentWebRunner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;
    await null;

    expect(testLogger.statusText, contains('\nerror text'));
    expect(testLogger.statusText, isNot(contains('other stuff')));
488 489 490 491
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
492

493
  testUsingContext('Does not run main with --start-paused', () async {
494
    final ResidentRunner residentWebRunner = ResidentWebRunner(
495
      flutterDevice,
496 497 498 499
      flutterProject:
          FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
      debuggingOptions:
          DebuggingOptions.enabled(BuildInfo.debug, startPaused: true),
500
      ipv6: true,
501 502 503 504 505
      fileSystem: fileSystem,
      logger: BufferLogger.test(),
      usage: globals.flutterUsage,
      systemClock: globals.systemClock,
    );
506 507
    fakeVmServiceHost =
        FakeVmServiceHost(requests: kAttachExpectations.toList());
508
    setupMocks();
509 510
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
511

512 513 514 515 516
    unawaited(residentWebRunner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;

517
    expect(appConnection.ranMain, false);
518 519 520 521
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
522

523
  testUsingContext('Can hot reload after attaching', () async {
524 525
    final BufferLogger logger = BufferLogger.test();
    final ResidentRunner residentWebRunner = setUpResidentRunner(
526
      flutterDevice,
527
      logger: logger,
528
      systemClock: SystemClock.fixed(DateTime(2001)),
529
    );
530 531 532
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      ...kAttachExpectations,
      const FakeVmServiceRequest(
533 534 535 536
          method: 'hotRestart',
          jsonResponse: <String, Object>{
            'type': 'Success',
          }),
537 538 539 540 541 542
      const FakeVmServiceRequest(
        method: 'streamListen',
        args: <String, Object>{
          'streamId': 'Isolate',
        },
      ),
543
    ]);
544
    setupMocks();
545
    final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher();
546 547
    final Chromium chrome =
        Chromium(1, chromeConnection, chromiumLauncher: chromiumLauncher);
548
    chromiumLauncher.setInstance(chrome);
549

550
    flutterDevice.device = GoogleChromeDevice(
551
      fileSystem: fileSystem,
552
      chromiumLauncher: chromiumLauncher,
553
      logger: BufferLogger.test(),
554
      platform: FakePlatform(),
555
      processManager: FakeProcessManager.any(),
556
    );
557 558
    webDevFS.report = UpdateFSReport(success: true);

559 560
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
561 562 563
    unawaited(residentWebRunner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
564 565
    final DebugConnectionInfo debugConnectionInfo =
        await connectionInfoCompleter.future;
566 567 568

    expect(debugConnectionInfo, isNotNull);

569
    final OperationResult result = await residentWebRunner.restart();
570

571
    expect(logger.statusText, contains('Restarted application in'));
572
    expect(result.code, 0);
573
    expect(webDevFS.mainUri.toString(), contains('entrypoint.dart'));
574 575

    // ensure that analytics are sent.
576
    expect(testUsage.events, <TestUsageEvent>[
577 578 579 580 581 582 583 584 585
      TestUsageEvent('hot', 'restart',
          parameters: CustomDimensions.fromMap(<String, String>{
            'cd27': 'web-javascript',
            'cd28': '',
            'cd29': 'false',
            'cd30': 'true',
            'cd13': '0',
            'cd48': 'false'
          })),
586 587 588 589
    ]);
    expect(testUsage.timings, const <TestTimingEvent>[
      TestTimingEvent('hot', 'web-incremental-restart', Duration.zero),
    ]);
590
  }, overrides: <Type, Generator>{
591
    Usage: () => testUsage,
592 593 594
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
595

596
  testUsingContext('Can hot restart after attaching', () async {
597 598
    final BufferLogger logger = BufferLogger.test();
    final ResidentRunner residentWebRunner = setUpResidentRunner(
599
      flutterDevice,
600
      logger: logger,
601
      systemClock: SystemClock.fixed(DateTime(2001)),
602
    );
603 604 605
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      ...kAttachExpectations,
      const FakeVmServiceRequest(
606 607 608 609
          method: 'hotRestart',
          jsonResponse: <String, Object>{
            'type': 'Success',
          }),
610
    ]);
611
    setupMocks();
612
    final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher();
613 614
    final Chromium chrome =
        Chromium(1, chromeConnection, chromiumLauncher: chromiumLauncher);
615
    chromiumLauncher.setInstance(chrome);
616

617
    flutterDevice.device = GoogleChromeDevice(
618
      fileSystem: fileSystem,
619
      chromiumLauncher: chromiumLauncher,
620
      logger: BufferLogger.test(),
621
      platform: FakePlatform(),
622
      processManager: FakeProcessManager.any(),
623
    );
624 625
    webDevFS.report = UpdateFSReport(success: true);

626 627
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
628 629 630 631
    unawaited(residentWebRunner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;
632 633
    final OperationResult result =
        await residentWebRunner.restart(fullRestart: true);
634

635
    // Ensure that generated entrypoint is generated correctly.
636
    expect(webDevFS.mainUri, isNotNull);
637 638
    final String entrypointContents =
        fileSystem.file(webDevFS.mainUri).readAsStringSync();
639 640
    expect(entrypointContents, contains('// Flutter web bootstrap script'));
    expect(entrypointContents, contains("import 'dart:ui' as ui;"));
641
    expect(entrypointContents, contains('await ui.webOnlyWarmupEngine('));
642

643
    expect(logger.statusText, contains('Restarted application in'));
644
    expect(result.code, 0);
645

646
    // ensure that analytics are sent.
647
    expect(testUsage.events, <TestUsageEvent>[
648 649 650 651 652 653 654 655 656
      TestUsageEvent('hot', 'restart',
          parameters: CustomDimensions.fromMap(<String, String>{
            'cd27': 'web-javascript',
            'cd28': '',
            'cd29': 'false',
            'cd30': 'true',
            'cd13': '0',
            'cd48': 'false'
          })),
657 658 659 660
    ]);
    expect(testUsage.timings, const <TestTimingEvent>[
      TestTimingEvent('hot', 'web-incremental-restart', Duration.zero),
    ]);
661
  }, overrides: <Type, Generator>{
662
    Usage: () => testUsage,
663 664 665
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
666

667 668
  testUsingContext('Can hot restart after attaching with web-server device',
      () async {
669 670
    final BufferLogger logger = BufferLogger.test();
    final ResidentRunner residentWebRunner = setUpResidentRunner(
671
      flutterDevice,
672
      logger: logger,
673
      systemClock: SystemClock.fixed(DateTime(2001)),
674
    );
675
    fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations);
676
    setupMocks();
677
    flutterDevice.device = webServerDevice;
678 679
    webDevFS.report = UpdateFSReport(success: true);

680 681
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
682 683 684 685
    unawaited(residentWebRunner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;
686 687
    final OperationResult result =
        await residentWebRunner.restart(fullRestart: true);
688

689
    expect(logger.statusText, contains('Restarted application in'));
690
    expect(result.code, 0);
691

692
    // web-server device does not send restart analytics
693 694
    expect(testUsage.events, isEmpty);
    expect(testUsage.timings, isEmpty);
695
  }, overrides: <Type, Generator>{
696
    Usage: () => testUsage,
697 698 699
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
700

701
  testUsingContext('web resident runner is debuggable', () {
702
    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
703 704
    fakeVmServiceHost =
        FakeVmServiceHost(requests: kAttachExpectations.toList());
705

706
    expect(residentWebRunner.debuggingEnabled, true);
707 708 709 710
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
711

712
  testUsingContext('Exits when initial compile fails', () async {
713
    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
714
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
715
    setupMocks();
716
    webDevFS.report = UpdateFSReport();
717

718 719
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
720
    unawaited(residentWebRunner.run(
721 722 723
      connectionInfoCompleter: connectionInfoCompleter,
    ));

724
    expect(await residentWebRunner.run(), 1);
725 726
    expect(testUsage.events, isEmpty);
    expect(testUsage.timings, isEmpty);
727
  }, overrides: <Type, Generator>{
728
    Usage: () => testUsage,
729 730 731
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
732

733 734 735
  testUsingContext(
      'Faithfully displays stdout messages with leading/trailing spaces',
      () async {
736
    final BufferLogger logger = BufferLogger.test();
737 738
    final ResidentRunner residentWebRunner =
        setUpResidentRunner(flutterDevice, logger: logger);
739 740 741 742 743 744 745 746
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      ...kAttachLogExpectations,
      FakeVmServiceStreamResponse(
        streamId: 'Stdout',
        event: vm_service.Event(
          timestamp: 0,
          kind: vm_service.EventStreams.kStdout,
          bytes: base64.encode(
747 748
            utf8.encode(
                '    This is a message with 4 leading and trailing spaces    '),
749 750 751 752 753
          ),
        ),
      ),
      ...kAttachIsolateExpectations,
    ]);
754
    setupMocks();
755 756
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
757 758 759 760 761
    unawaited(residentWebRunner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;

762 763 764 765
    expect(
        logger.statusText,
        contains(
            '    This is a message with 4 leading and trailing spaces    '));
766
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
767 768 769 770
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
771

772
  testUsingContext('Fails on compilation errors in hot restart', () async {
773
    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
774 775
    fakeVmServiceHost =
        FakeVmServiceHost(requests: kAttachExpectations.toList());
776
    setupMocks();
777 778
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
779
    unawaited(residentWebRunner.run(
780 781 782
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;
783
    webDevFS.report = UpdateFSReport();
784

785 786
    final OperationResult result =
        await residentWebRunner.restart(fullRestart: true);
787 788

    expect(result.code, 1);
789
    expect(result.message, contains('Failed to recompile application.'));
790 791
    expect(testUsage.events, isEmpty);
    expect(testUsage.timings, isEmpty);
792
  }, overrides: <Type, Generator>{
793
    Usage: () => testUsage,
794 795 796
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
797

798 799 800
  testUsingContext(
      'Fails non-fatally on vmservice response error for hot restart',
      () async {
801
    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
802 803 804 805 806 807
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      ...kAttachExpectations,
      const FakeVmServiceRequest(
        method: 'hotRestart',
        jsonResponse: <String, Object>{
          'type': 'Failed',
808 809
        },
      ),
810
    ]);
811
    setupMocks();
812 813
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
814 815 816 817 818
    unawaited(residentWebRunner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;

819
    final OperationResult result = await residentWebRunner.restart();
820

821
    expect(result.code, 0);
822 823 824 825
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
826

827
  testUsingContext('Fails fatally on Vm Service error response', () async {
828
    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
829 830 831 832 833 834 835 836
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      ...kAttachExpectations,
      const FakeVmServiceRequest(
        method: 'hotRestart',
        // Failed response,
        errorCode: RPCErrorCodes.kInternalError,
      ),
    ]);
837
    setupMocks();
838 839
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
840 841 842 843
    unawaited(residentWebRunner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;
844
    final OperationResult result = await residentWebRunner.restart();
845 846

    expect(result.code, 1);
847
    expect(result.message, contains(RPCErrorCodes.kInternalError.toString()));
848 849 850 851
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
852

853 854
  testUsingContext('printHelp without details shows hot restart help message',
      () async {
855
    final BufferLogger logger = BufferLogger.test();
856 857
    final ResidentRunner residentWebRunner =
        setUpResidentRunner(flutterDevice, logger: logger);
858 859 860
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
    residentWebRunner.printHelp(details: false);

861
    expect(logger.statusText, contains('To hot restart changes'));
862 863 864 865 866
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });

867 868
  testUsingContext('cleanup of resources is safe to call multiple times',
      () async {
869
    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
870
    mockDevice.dds = DartDevelopmentService();
871 872 873
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      ...kAttachExpectations,
    ]);
874
    setupMocks();
875 876
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
877
    unawaited(residentWebRunner.run(
878 879 880 881 882 883
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;

    await residentWebRunner.exit();
    await residentWebRunner.exit();
884

885
    expect(debugConnection.didClose, false);
886
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
887 888 889 890
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
891

892
  testUsingContext('cleans up Chrome if tab is closed', () async {
893
    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
894 895 896
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      ...kAttachExpectations,
    ]);
897
    setupMocks();
898 899
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
900 901 902 903
    final Future<int> result = residentWebRunner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    );
    await connectionInfoCompleter.future;
904
    debugConnection.completer.complete();
905 906

    await result;
907
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
908 909 910 911
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
912

913
  testUsingContext('Prints target and device name on run', () async {
914
    final BufferLogger logger = BufferLogger.test();
915 916
    final ResidentRunner residentWebRunner =
        setUpResidentRunner(flutterDevice, logger: logger);
917 918 919
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      ...kAttachExpectations,
    ]);
920
    setupMocks();
921
    mockDevice.name = 'Chromez';
922 923
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
924
    unawaited(residentWebRunner.run(
925 926 927 928
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;

929 930 931 932 933 934
    expect(
        logger.statusText,
        contains(
          'Launching ${fileSystem.path.join('lib', 'main.dart')} on '
          'Chromez in debug mode',
        ));
935
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
936 937 938 939
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
940

941 942
  testUsingContext('Sends launched app.webLaunchUrl event for Chrome device',
      () async {
943
    final BufferLogger logger = BufferLogger.test();
944 945
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
      ...kAttachLogExpectations,
946
      ...kAttachIsolateExpectations,
947
    ]);
948
    setupMocks();
949
    final FakeChromeConnection chromeConnection = FakeChromeConnection();
950
    final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher();
951 952
    final Chromium chrome =
        Chromium(1, chromeConnection, chromiumLauncher: chromiumLauncher);
953
    chromiumLauncher.setInstance(chrome);
954

955
    flutterDevice.device = GoogleChromeDevice(
956
      fileSystem: fileSystem,
957
      chromiumLauncher: chromiumLauncher,
958
      logger: logger,
959
      platform: FakePlatform(),
960
      processManager: FakeProcessManager.any(),
961
    );
962 963
    webDevFS.baseUri = Uri.parse('http://localhost:8765/app/');

964 965
    final FakeChromeTab chromeTab = FakeChromeTab('index.html');
    chromeConnection.tabs.add(chromeTab);
966

967
    final ResidentWebRunner runner = ResidentWebRunner(
968
      flutterDevice,
969 970
      flutterProject:
          FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
971 972
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      ipv6: true,
973 974 975 976 977
      fileSystem: fileSystem,
      logger: logger,
      usage: globals.flutterUsage,
      systemClock: globals.systemClock,
    );
978

979 980
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
981 982 983 984 985 986
    unawaited(runner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;

    // Ensure we got the URL and that it was already launched.
987 988 989 990 991 992 993 994 995 996 997
    expect(
        logger.eventText,
        contains(json.encode(
          <String, Object>{
            'name': 'app.webLaunchUrl',
            'args': <String, Object>{
              'url': 'http://localhost:8765/app/',
              'launched': true,
            },
          },
        )));
998
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
999
  }, overrides: <Type, Generator>{
1000 1001 1002
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
1003

1004 1005 1006
  testUsingContext(
      'Sends unlaunched app.webLaunchUrl event for Web Server device',
      () async {
1007
    final BufferLogger logger = BufferLogger.test();
1008
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1009
    setupMocks();
1010
    flutterDevice.device = WebServerDevice(
1011
      logger: logger,
1012
    );
1013
    webDevFS.baseUri = Uri.parse('http://localhost:8765/app/');
1014

1015
    final ResidentWebRunner runner = ResidentWebRunner(
1016
      flutterDevice,
1017 1018
      flutterProject:
          FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
1019 1020
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      ipv6: true,
1021 1022 1023 1024 1025
      fileSystem: fileSystem,
      logger: logger,
      usage: globals.flutterUsage,
      systemClock: globals.systemClock,
    );
1026

1027 1028
    final Completer<DebugConnectionInfo> connectionInfoCompleter =
        Completer<DebugConnectionInfo>();
1029 1030 1031 1032 1033 1034
    unawaited(runner.run(
      connectionInfoCompleter: connectionInfoCompleter,
    ));
    await connectionInfoCompleter.future;

    // Ensure we got the URL and that it was not already launched.
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
    expect(
        logger.eventText,
        contains(json.encode(
          <String, Object>{
            'name': 'app.webLaunchUrl',
            'args': <String, Object>{
              'url': 'http://localhost:8765/app/',
              'launched': false,
            },
          },
        )));
1046
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1047
  }, overrides: <Type, Generator>{
1048 1049 1050
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
1051

1052 1053
  // While this file should be ignored on web, generating it here will cause a
  // perf regression in hot restart.
1054
  testUsingContext('Does not generate dart_plugin_registrant.dart', () async {
1055
    // Create necessary files for [DartPluginRegistrantTarget]
1056 1057
    final File packageConfig =
        globals.fs.directory('.dart_tool').childFile('package_config.json');
1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071
    packageConfig.createSync(recursive: true);
    packageConfig.writeAsStringSync('''
{
  "configVersion": 2,
  "packages": [
    {
      "name": "path_provider_linux",
      "rootUri": "../../../path_provider_linux",
      "packageUri": "lib/",
      "languageVersion": "2.12"
    }
  ]
}
''');
1072
    // Start with a dart_plugin_registrant.dart file.
1073 1074 1075 1076 1077
    globals.fs
        .directory('.dart_tool')
        .childDirectory('flutter_build')
        .childFile('dart_plugin_registrant.dart')
        .createSync(recursive: true);
1078

1079 1080
    final FlutterProject project =
        FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
1081 1082 1083 1084

    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
    await residentWebRunner.runSourceGenerators();

1085
    // dart_plugin_registrant.dart should be untouched, indicating that its
1086 1087 1088 1089 1090 1091 1092 1093 1094
    // generation didn't run. If it had run, the file would have been removed as
    // there are no plugins in the project.
    expect(project.dartPluginRegistrant.existsSync(), true);
    expect(project.dartPluginRegistrant.readAsStringSync(), '');
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });

1095 1096
  testUsingContext('Successfully turns WebSocketException into ToolExit',
      () async {
1097
    final BufferLogger logger = BufferLogger.test();
1098 1099
    final ResidentRunner residentWebRunner =
        setUpResidentRunner(flutterDevice, logger: logger);
1100
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1101
    setupMocks();
1102
    webDevFS.exception = const WebSocketException();
1103

1104
    await expectLater(residentWebRunner.run, throwsToolExit());
1105
    expect(logger.errorText, contains('WebSocketException'));
1106
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1107 1108 1109 1110
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
1111

1112 1113
  testUsingContext('Successfully turns AppConnectionException into ToolExit',
      () async {
1114
    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
1115
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1116
    setupMocks();
1117
    webDevFS.exception = AppConnectionException('');
1118

1119
    await expectLater(residentWebRunner.run, throwsToolExit());
1120
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1121 1122 1123 1124
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
1125

1126 1127
  testUsingContext('Successfully turns ChromeDebugError into ToolExit',
      () async {
1128
    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
1129
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1130
    setupMocks();
1131

1132
    webDevFS.exception = ChromeDebugException(<String, dynamic>{});
1133

1134
    await expectLater(residentWebRunner.run, throwsToolExit());
1135
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1136 1137 1138 1139
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
1140

1141
  testUsingContext('Rethrows unknown Exception type from dwds', () async {
1142
    final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
1143
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1144
    setupMocks();
1145
    webDevFS.exception = Exception();
1146

1147
    await expectLater(residentWebRunner.run, throwsException);
1148
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1149 1150 1151 1152
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
1153

1154
  testUsingContext('Rethrows unknown Error type from dwds tooling', () async {
1155
    final BufferLogger logger = BufferLogger.test();
1156 1157
    final ResidentRunner residentWebRunner =
        setUpResidentRunner(flutterDevice, logger: logger);
1158
    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
1159
    setupMocks();
1160
    webDevFS.exception = StateError('');
1161

1162
    await expectLater(residentWebRunner.run, throwsStateError);
1163
    expect(fakeVmServiceHost.hasRemainingExpectations, false);
1164
  }, overrides: <Type, Generator>{
1165 1166 1167 1168 1169
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
  });
}

1170 1171
ResidentRunner setUpResidentRunner(
  FlutterDevice flutterDevice, {
1172 1173
  Logger logger,
  SystemClock systemClock,
1174
  DebuggingOptions debuggingOptions,
1175 1176
}) {
  return ResidentWebRunner(
1177
    flutterDevice,
1178 1179 1180 1181
    flutterProject:
        FlutterProject.fromDirectoryTest(globals.fs.currentDirectory),
    debuggingOptions:
        debuggingOptions ?? DebuggingOptions.enabled(BuildInfo.debug),
1182
    ipv6: true,
1183 1184 1185 1186
    usage: globals.flutterUsage,
    systemClock: systemClock ?? SystemClock.fixed(DateTime.now()),
    fileSystem: globals.fs,
    logger: logger ?? BufferLogger.test(),
1187
    devtoolsHandler: createNoOpHandler,
1188
  );
1189 1190
}

1191 1192 1193
// Unfortunately Device, despite not being immutable, has an `operator ==`.
// Until we fix that, we have to also ignore related lints here.
// ignore: avoid_implementing_value_types
1194
class FakeWebServerDevice extends FakeDevice implements WebServerDevice {}
1195

1196 1197 1198
// Unfortunately Device, despite not being immutable, has an `operator ==`.
// Until we fix that, we have to also ignore related lints here.
// ignore: avoid_implementing_value_types
1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241
class FakeDevice extends Fake implements Device {
  @override
  String name;

  int count = 0;

  @override
  Future<String> get sdkNameAndVersion async => 'SDK Name and Version';

  @override
  DartDevelopmentService dds;

  @override
  Future<LaunchResult> startApp(
    covariant ApplicationPackage package, {
    String mainPath,
    String route,
    DebuggingOptions debuggingOptions,
    Map<String, dynamic> platformArgs,
    bool prebuiltApplication = false,
    bool ipv6 = false,
    String userIdentifier,
  }) async {
    return LaunchResult.succeeded();
  }

  @override
  Future<bool> stopApp(
    covariant ApplicationPackage app, {
    String userIdentifier,
  }) async {
    if (count > 0) {
      throw StateError('stopApp called more than once.');
    }
    count += 1;
    return true;
  }
}

class FakeDebugConnection extends Fake implements DebugConnection {
  FakeVmServiceHost Function() fakeVmServiceHost;

  @override
1242 1243
  vm_service.VmService get vmService =>
      fakeVmServiceHost.call().vmService.service;
1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267

  @override
  String uri;

  final Completer<void> completer = Completer<void>();
  bool didClose = false;

  @override
  Future<void> get onDone => completer.future;

  @override
  Future<void> close() async {
    didClose = true;
  }
}

class FakeAppConnection extends Fake implements AppConnection {
  bool ranMain = false;

  @override
  void runMain() {
    ranMain = true;
  }
}
1268

1269 1270 1271
// Unfortunately Device, despite not being immutable, has an `operator ==`.
// Until we fix that, we have to also ignore related lints here.
// ignore: avoid_implementing_value_types
1272
class FakeChromeDevice extends Fake implements ChromiumDevice {}
1273

1274
class FakeWipDebugger extends Fake implements WipDebugger {}
1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285

class FakeResidentCompiler extends Fake implements ResidentCompiler {
  @override
  Future<CompilerOutput> recompile(
    Uri mainUri,
    List<Uri> invalidatedFiles, {
    @required String outputPath,
    @required PackageConfig packageConfig,
    @required String projectRootPath,
    @required FileSystem fs,
    bool suppressErrors = false,
1286
    bool checkDartPluginRegistry = false,
1287
    File dartPluginRegistrant,
1288 1289 1290 1291 1292
  }) async {
    return const CompilerOutput('foo.dill', 0, <Uri>[]);
  }

  @override
1293
  void accept() {}
1294 1295

  @override
1296
  void reset() {}
1297 1298 1299 1300 1301 1302 1303

  @override
  Future<CompilerOutput> reject() async {
    return const CompilerOutput('foo.dill', 0, <Uri>[]);
  }

  @override
1304
  void addFileSystemRoot(String root) {}
1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339
}

class FakeWebDevFS extends Fake implements WebDevFS {
  Object exception;
  ConnectionResult result;
  UpdateFSReport report;

  Uri mainUri;

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

  @override
  Uri baseUri = Uri.parse('http://localhost:12345');

  @override
  DateTime lastCompiled = DateTime.now();

  @override
  PackageConfig lastPackageConfig = PackageConfig.empty;

  @override
  Future<Uri> create() async {
    return baseUri;
  }

  @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,
1340
    @required DevelopmentShaderCompiler shaderCompiler,
1341 1342 1343 1344 1345 1346 1347
    DevFSWriter devFSWriter,
    String target,
    AssetBundle bundle,
    DateTime firstBuildTime,
    bool bundleFirstUpload = false,
    bool fullRestart = false,
    String projectRootPath,
1348
    File dartPluginRegistrant,
1349 1350 1351 1352 1353 1354
  }) async {
    this.mainUri = mainUri;
    return report;
  }

  @override
1355
  Future<ConnectionResult> connect(bool useDebugExtension, {VmServiceFactory vmServiceFactory = createVmServiceDelegate}) async {
1356
    if (exception != null) {
1357 1358
      assert(exception is Exception || exception is Error);
      // ignore: only_throw_errors, exception is either Error or Exception here.
1359 1360 1361 1362 1363
      throw exception;
    }
    return result;
  }
}
1364 1365 1366 1367 1368

class FakeChromeConnection extends Fake implements ChromeConnection {
  final List<ChromeTab> tabs = <ChromeTab>[];

  @override
1369 1370
  Future<ChromeTab> getTab(bool Function(ChromeTab tab) accept,
      {Duration retryFor}) async {
1371 1372
    return tabs.firstWhere(accept);
  }
1373 1374 1375 1376 1377

  @override
  Future<List<ChromeTab>> getTabs({Duration retryFor}) async {
    return tabs;
  }
1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397
}

class FakeChromeTab extends Fake implements ChromeTab {
  FakeChromeTab(this.url);

  @override
  final String url;
  final FakeWipConnection connection = FakeWipConnection();

  @override
  Future<WipConnection> connect() async {
    return connection;
  }
}

class FakeWipConnection extends Fake implements WipConnection {
  @override
  final WipDebugger debugger = FakeWipDebugger();
}

1398 1399 1400 1401
/// A test implementation of the [ChromiumLauncher] that launches a fixed instance.
class TestChromiumLauncher implements ChromiumLauncher {
  TestChromiumLauncher();

1402 1403
  bool _hasInstance = false;
  void setInstance(Chromium chromium) {
1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427
    _hasInstance = true;
    currentCompleter.complete(chromium);
  }

  @override
  Completer<Chromium> currentCompleter = Completer<Chromium>();

  @override
  bool canFindExecutable() {
    return true;
  }

  @override
  Future<Chromium> get connectedInstance => currentCompleter.future;

  @override
  String findExecutable() {
    return 'chrome';
  }

  @override
  bool get hasChromeInstance => _hasInstance;

  @override
1428 1429 1430 1431 1432 1433 1434 1435
  Future<Chromium> launch(
    String url, {
    bool headless = false,
    int debugPort,
    bool skipCheck = false,
    Directory cacheDir,
    List<String> webBrowserFlags = const <String>[],
  }) async {
1436 1437
    return currentCompleter.future;
  }
1438 1439 1440 1441 1442

  @override
  Future<Chromium> connect(Chromium chrome, bool skipCheck) {
    return currentCompleter.future;
  }
1443
}
1444 1445 1446 1447 1448 1449 1450

class FakeFlutterDevice extends Fake implements FlutterDevice {
  Uri testUri;
  UpdateFSReport report = UpdateFSReport(
    success: true,
    invalidatedSourcesCount: 1,
  );
1451
  Exception reportError;
1452 1453 1454 1455 1456 1457 1458

  @override
  ResidentCompiler generator;

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

1459 1460 1461
  @override
  DevelopmentShaderCompiler get developmentShaderCompiler => const FakeShaderCompiler();

1462 1463 1464 1465 1466 1467 1468 1469 1470
  @override
  FlutterVmService vmService;

  DevFS _devFS;

  @override
  DevFS get devFS => _devFS;

  @override
1471
  set devFS(DevFS value) {}
1472 1473 1474 1475 1476

  @override
  Device device;

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

  @override
1480
  Future<void> initLogReader() async {}
1481 1482 1483 1484 1485 1486 1487

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

  @override
1488 1489
  Future<void> exitApps(
      {Duration timeoutDelay = const Duration(seconds: 10)}) async {}
1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501

  @override
  Future<void> connect({
    ReloadSources reloadSources,
    Restart restart,
    CompileExpression compileExpression,
    GetSkSLMethod getSkSLMethod,
    PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
    int hostVmServicePort,
    int ddsPort,
    bool disableServiceAuthCodes = false,
    bool enableDds = true,
1502
    bool cacheStartupProfile = false,
1503 1504
    @required bool allowExistingDdsInstance,
    bool ipv6 = false,
1505
  }) async {}
1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520

  @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,
1521
    File dartPluginRegistrant,
1522 1523 1524 1525 1526 1527 1528 1529
  }) async {
    if (reportError != null) {
      throw reportError;
    }
    return report;
  }

  @override
1530
  Future<void> updateReloadStatus(bool wasReloadSuccessful) async {}
1531
}
1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543

class FakeShaderCompiler implements DevelopmentShaderCompiler {
  const FakeShaderCompiler();

  @override
  void configureCompiler(TargetPlatform platform, { @required bool enableImpeller }) { }

  @override
  Future<DevFSContent> recompileShader(DevFSContent inputShader) {
    throw UnimplementedError();
  }
}