hot_test.dart 26 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

7
import 'package:file/memory.dart';
8
import 'package:flutter_tools/src/application_package.dart';
9
import 'package:flutter_tools/src/artifacts.dart';
10
import 'package:flutter_tools/src/asset.dart';
11
import 'package:flutter_tools/src/base/file_system.dart';
12
import 'package:flutter_tools/src/base/io.dart';
13
import 'package:flutter_tools/src/base/platform.dart';
14
import 'package:flutter_tools/src/build_info.dart';
15
import 'package:flutter_tools/src/build_system/targets/shader_compiler.dart';
16
import 'package:flutter_tools/src/compile.dart';
17
import 'package:flutter_tools/src/devfs.dart';
18
import 'package:flutter_tools/src/device.dart';
19
import 'package:flutter_tools/src/reporting/reporting.dart';
20
import 'package:flutter_tools/src/resident_devtools_handler.dart';
21
import 'package:flutter_tools/src/resident_runner.dart';
22
import 'package:flutter_tools/src/run_hot.dart';
23
import 'package:flutter_tools/src/vmservice.dart';
24
import 'package:package_config/package_config.dart';
25
import 'package:test/fake.dart';
26
import 'package:vm_service/vm_service.dart' as vm_service;
27

28 29
import '../src/common.dart';
import '../src/context.dart';
30
import '../src/fakes.dart';
31

32
void main() {
33 34
  group('validateReloadReport', () {
    testUsingContext('invalid', () async {
35
      expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{
36 37 38
        'type': 'ReloadReport',
        'success': false,
        'details': <String, dynamic>{},
39 40
      })), false);
      expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{
41 42 43 44 45 46
        'type': 'ReloadReport',
        'success': false,
        'details': <String, dynamic>{
          'notices': <Map<String, dynamic>>[
          ],
        },
47 48
      })), false);
      expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{
49 50 51 52 53 54 55
        'type': 'ReloadReport',
        'success': false,
        'details': <String, dynamic>{
          'notices': <String, dynamic>{
            'message': 'error',
          },
        },
56 57
      })), false);
      expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{
58 59 60 61 62
        'type': 'ReloadReport',
        'success': false,
        'details': <String, dynamic>{
          'notices': <Map<String, dynamic>>[],
        },
63 64
      })), false);
      expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{
65 66 67 68
        'type': 'ReloadReport',
        'success': false,
        'details': <String, dynamic>{
          'notices': <Map<String, dynamic>>[
69
            <String, dynamic>{'message': false},
70 71
          ],
        },
72 73
      })), false);
      expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{
74 75 76 77
        'type': 'ReloadReport',
        'success': false,
        'details': <String, dynamic>{
          'notices': <Map<String, dynamic>>[
78
            <String, dynamic>{'message': <String>['error']},
79 80
          ],
        },
81 82
      })), false);
      expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{
83 84 85 86
        'type': 'ReloadReport',
        'success': false,
        'details': <String, dynamic>{
          'notices': <Map<String, dynamic>>[
87 88
            <String, dynamic>{'message': 'error'},
            <String, dynamic>{'message': <String>['error']},
89 90
          ],
        },
91 92
      })), false);
      expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{
93 94 95 96
        'type': 'ReloadReport',
        'success': false,
        'details': <String, dynamic>{
          'notices': <Map<String, dynamic>>[
97
            <String, dynamic>{'message': 'error'},
98 99
          ],
        },
100 101
      })), false);
      expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{
102 103
        'type': 'ReloadReport',
        'success': true,
104 105 106 107 108 109 110 111 112
      })), true);
    });

    testWithoutContext('ReasonForCancelling toString has a hint for specific errors', () {
      final ReasonForCancelling reasonForCancelling = ReasonForCancelling(
        message: 'Const class cannot remove fields',
      );

      expect(reasonForCancelling.toString(), contains('Try performing a hot restart instead.'));
113 114
    });
  });
115 116

  group('hotRestart', () {
117
    final FakeResidentCompiler residentCompiler = FakeResidentCompiler();
118 119
    late FileSystem fileSystem;
    late TestUsage testUsage;
120 121

    setUp(() {
122
      fileSystem = MemoryFileSystem.test();
123
      testUsage = TestUsage();
124 125
    });

126
    group('fails to setup', () {
127
      late TestHotRunnerConfig failingTestingConfig;
128 129 130 131 132 133 134 135 136 137 138 139 140
      setUp(() {
        failingTestingConfig = TestHotRunnerConfig(
          successfulHotRestartSetup: false,
          successfulHotReloadSetup: false,
        );
      });

      testUsingContext('setupHotRestart function fails', () async {
        fileSystem.file('.packages')
          ..createSync(recursive: true)
          ..writeAsStringSync('\n');
        final FakeDevice device = FakeDevice();
        final List<FlutterDevice> devices = <FlutterDevice>[
141 142
          FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler())
            ..devFS = FakeDevFs(),
143 144 145 146 147 148 149 150 151 152 153 154 155 156
        ];
        final OperationResult result = await HotRunner(
          devices,
          debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
          target: 'main.dart',
          devtoolsHandler: createNoOpHandler,
        ).restart(fullRestart: true);
        expect(result.isOk, false);
        expect(result.message, 'setupHotRestart failed');
        expect(failingTestingConfig.updateDevFSCompleteCalled, false);
      }, overrides: <Type, Generator>{
        HotRunnerConfig: () => failingTestingConfig,
        Artifacts: () => Artifacts.test(),
        FileSystem: () => fileSystem,
157
        Platform: () => FakePlatform(),
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
        ProcessManager: () => FakeProcessManager.any(),
      });

      testUsingContext('setupHotReload function fails', () async {
        fileSystem.file('.packages')
          ..createSync(recursive: true)
          ..writeAsStringSync('\n');
        final FakeDevice device = FakeDevice();
        final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
        final List<FlutterDevice> devices = <FlutterDevice>[
          fakeFlutterDevice,
        ];
        final OperationResult result = await HotRunner(
          devices,
          debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
          target: 'main.dart',
          devtoolsHandler: createNoOpHandler,
          reassembleHelper: (
176 177 178
            List<FlutterDevice?> flutterDevices,
            Map<FlutterDevice?, List<FlutterView>> viewCache,
            void Function(String message)? onSlow,
179
            String reloadMessage,
180
            String? fastReassembleClassName,
181
          ) async => ReassembleResult(
182
              <FlutterView?, FlutterVmService?>{null: null},
183 184 185
              false,
              true,
            ),
186
        ).restart();
187 188 189 190 191 192 193
        expect(result.isOk, false);
        expect(result.message, 'setupHotReload failed');
        expect(failingTestingConfig.updateDevFSCompleteCalled, false);
      }, overrides: <Type, Generator>{
        HotRunnerConfig: () => failingTestingConfig,
        Artifacts: () => Artifacts.test(),
        FileSystem: () => fileSystem,
194
        Platform: () => FakePlatform(),
195 196
        ProcessManager: () => FakeProcessManager.any(),
      });
197
    });
198

199
    group('shutdown hook tests', () {
200
      late TestHotRunnerConfig shutdownTestingConfig;
201 202

      setUp(() {
203
        shutdownTestingConfig = TestHotRunnerConfig();
204 205 206
      });

      testUsingContext('shutdown hook called after signal', () async {
207
        fileSystem.file('.packages')
208 209
          ..createSync(recursive: true)
          ..writeAsStringSync('\n');
210
        final FakeDevice device = FakeDevice();
211
        final List<FlutterDevice> devices = <FlutterDevice>[
212
          FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler()),
213
        ];
214 215
        await HotRunner(
          devices,
216 217
          debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
          target: 'main.dart',
218
        ).cleanupAfterSignal();
219
        expect(shutdownTestingConfig.shutdownHookCalled, true);
220
      }, overrides: <Type, Generator>{
221
        HotRunnerConfig: () => shutdownTestingConfig,
222 223
        Artifacts: () => Artifacts.test(),
        FileSystem: () => fileSystem,
224
        Platform: () => FakePlatform(),
225
        ProcessManager: () => FakeProcessManager.any(),
226 227 228
      });

      testUsingContext('shutdown hook called after app stop', () async {
229
        fileSystem.file('.packages')
230 231
          ..createSync(recursive: true)
          ..writeAsStringSync('\n');
232
        final FakeDevice device = FakeDevice();
233
        final List<FlutterDevice> devices = <FlutterDevice>[
234
          FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler()),
235
        ];
236 237
        await HotRunner(
          devices,
238 239
          debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
          target: 'main.dart',
240
        ).preExit();
241
        expect(shutdownTestingConfig.shutdownHookCalled, true);
242
      }, overrides: <Type, Generator>{
243
        HotRunnerConfig: () => shutdownTestingConfig,
244 245
        Artifacts: () => Artifacts.test(),
        FileSystem: () => fileSystem,
246
        Platform: () => FakePlatform(),
247
        ProcessManager: () => FakeProcessManager.any(),
248 249
      });
    });
250

251
    group('successful hot restart', () {
252
      late TestHotRunnerConfig testingConfig;
253 254 255 256 257 258 259 260 261 262 263
      setUp(() {
        testingConfig = TestHotRunnerConfig(
          successfulHotRestartSetup: true,
        );
      });
      testUsingContext('correctly tracks time spent for analytics for hot restart', () async {
        final FakeDevice device = FakeDevice();
        final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
        final List<FlutterDevice> devices = <FlutterDevice>[
          fakeFlutterDevice,
        ];
264

265 266 267 268 269 270 271 272
        fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport(
          success: true,
          invalidatedSourcesCount: 2,
          syncedBytes: 4,
          scannedSourcesCount: 8,
          compileDuration: const Duration(seconds: 16),
          transferDuration: const Duration(seconds: 32),
        );
273

274 275 276 277 278 279
        final FakeStopwatchFactory fakeStopwatchFactory = FakeStopwatchFactory(
          stopwatches: <String, Stopwatch>{
            'fullRestartHelper': FakeStopwatch()..elapsed = const Duration(seconds: 64),
            'updateDevFS': FakeStopwatch()..elapsed = const Duration(seconds: 128),
          },
        );
280

281
        (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = Uri.parse('file:///base_uri');
282

283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
        final OperationResult result = await HotRunner(
          devices,
          debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
          target: 'main.dart',
          devtoolsHandler: createNoOpHandler,
          stopwatchFactory: fakeStopwatchFactory,
        ).restart(fullRestart: true);

        expect(result.isOk, true);
        expect(testUsage.events, <TestUsageEvent>[
          const TestUsageEvent('hot', 'restart', parameters: CustomDimensions(
            hotEventTargetPlatform: 'flutter-tester',
            hotEventSdkName: 'Tester',
            hotEventEmulator: false,
            hotEventFullRestart: true,
298
            fastReassemble: false,
299 300 301 302 303 304 305 306 307 308 309 310 311 312
            hotEventOverallTimeInMs: 64000,
            hotEventSyncedBytes: 4,
            hotEventInvalidatedSourcesCount: 2,
            hotEventTransferTimeInMs: 32000,
            hotEventCompileTimeInMs: 16000,
            hotEventFindInvalidatedTimeInMs: 128000,
            hotEventScannedSourcesCount: 8,
          )),
        ]);
        expect(testingConfig.updateDevFSCompleteCalled, true);
      }, overrides: <Type, Generator>{
        HotRunnerConfig: () => testingConfig,
        Artifacts: () => Artifacts.test(),
        FileSystem: () => fileSystem,
313
        Platform: () => FakePlatform(),
314 315 316
        ProcessManager: () => FakeProcessManager.any(),
        Usage: () => testUsage,
      });
317 318
    });

319
    group('successful hot reload', () {
320
      late TestHotRunnerConfig testingConfig;
321 322 323 324 325 326 327 328 329 330 331
      setUp(() {
        testingConfig = TestHotRunnerConfig(
          successfulHotReloadSetup: true,
        );
      });
      testUsingContext('correctly tracks time spent for analytics for hot reload', () async {
        final FakeDevice device = FakeDevice();
        final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
        final List<FlutterDevice> devices = <FlutterDevice>[
          fakeFlutterDevice,
        ];
332

333 334 335 336 337 338 339 340
        fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport(
          success: true,
          invalidatedSourcesCount: 6,
          syncedBytes: 8,
          scannedSourcesCount: 16,
          compileDuration: const Duration(seconds: 16),
          transferDuration: const Duration(seconds: 32),
        );
341

342 343 344 345 346 347 348 349
        final FakeStopwatchFactory fakeStopwatchFactory = FakeStopwatchFactory(
          stopwatches: <String, Stopwatch>{
            'updateDevFS': FakeStopwatch()..elapsed = const Duration(seconds: 64),
            'reloadSources:reload': FakeStopwatch()..elapsed = const Duration(seconds: 128),
            'reloadSources:reassemble': FakeStopwatch()..elapsed = const Duration(seconds: 256),
            'reloadSources:vm': FakeStopwatch()..elapsed = const Duration(seconds: 512),
          },
        );
350

351
        (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = Uri.parse('file:///base_uri');
352

353 354 355 356 357 358 359 360
        final OperationResult result = await HotRunner(
          devices,
          debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
          target: 'main.dart',
          devtoolsHandler: createNoOpHandler,
          stopwatchFactory: fakeStopwatchFactory,
          reloadSourcesHelper: (
            HotRunner hotRunner,
361 362
            List<FlutterDevice?> flutterDevices,
            bool? pause,
363
            Map<String, dynamic> firstReloadDetails,
364 365 366 367
            String? targetPlatform,
            String? sdkName,
            bool? emulator,
            String? reason,
368 369 370 371 372 373 374 375
          ) async {
            firstReloadDetails['finalLibraryCount'] = 2;
            firstReloadDetails['receivedLibraryCount'] = 3;
            firstReloadDetails['receivedClassesCount'] = 4;
            firstReloadDetails['receivedProceduresCount'] = 5;
            return OperationResult.ok;
          },
          reassembleHelper: (
376 377 378
            List<FlutterDevice?> flutterDevices,
            Map<FlutterDevice?, List<FlutterView>> viewCache,
            void Function(String message)? onSlow,
379
            String reloadMessage,
380
            String? fastReassembleClassName,
381
          ) async => ReassembleResult(
382
              <FlutterView?, FlutterVmService?>{null: null},
383 384 385
              false,
              true,
            ),
386
        ).restart();
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415

        expect(result.isOk, true);
        expect(testUsage.events, <TestUsageEvent>[
          const TestUsageEvent('hot', 'reload', parameters: CustomDimensions(
            hotEventFinalLibraryCount: 2,
            hotEventSyncedLibraryCount: 3,
            hotEventSyncedClassesCount: 4,
            hotEventSyncedProceduresCount: 5,
            hotEventSyncedBytes: 8,
            hotEventInvalidatedSourcesCount: 6,
            hotEventTransferTimeInMs: 32000,
            hotEventOverallTimeInMs: 128000,
            hotEventTargetPlatform: 'flutter-tester',
            hotEventSdkName: 'Tester',
            hotEventEmulator: false,
            hotEventFullRestart: false,
            fastReassemble: false,
            hotEventCompileTimeInMs: 16000,
            hotEventFindInvalidatedTimeInMs: 64000,
            hotEventScannedSourcesCount: 16,
            hotEventReassembleTimeInMs: 256000,
            hotEventReloadVMTimeInMs: 512000,
          )),
        ]);
        expect(testingConfig.updateDevFSCompleteCalled, true);
      }, overrides: <Type, Generator>{
        HotRunnerConfig: () => testingConfig,
        Artifacts: () => Artifacts.test(),
        FileSystem: () => fileSystem,
416
        Platform: () => FakePlatform(),
417 418 419 420 421 422
        ProcessManager: () => FakeProcessManager.any(),
        Usage: () => testUsage,
      });
    });

    group('hot restart that failed to sync dev fs', () {
423
      late TestHotRunnerConfig testingConfig;
424 425 426 427 428 429 430 431 432 433 434
      setUp(() {
        testingConfig = TestHotRunnerConfig(
          successfulHotRestartSetup: true,
        );
      });
      testUsingContext('still calls the devfs complete callback', () async {
        final FakeDevice device = FakeDevice();
        final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
        final List<FlutterDevice> devices = <FlutterDevice>[
          fakeFlutterDevice,
        ];
435
        fakeFlutterDevice.updateDevFSReportCallback = () async => throw Exception('updateDevFS failed');
436 437 438 439 440 441 442 443

        final HotRunner runner = HotRunner(
          devices,
          debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
          target: 'main.dart',
          devtoolsHandler: createNoOpHandler,
        );

444
        await expectLater(runner.restart(fullRestart: true), throwsA(isA<Exception>().having((Exception e) => e.toString(), 'message', 'Exception: updateDevFS failed')));
445 446 447 448 449
        expect(testingConfig.updateDevFSCompleteCalled, true);
      }, overrides: <Type, Generator>{
        HotRunnerConfig: () => testingConfig,
        Artifacts: () => Artifacts.test(),
        FileSystem: () => fileSystem,
450
        Platform: () => FakePlatform(),
451 452 453 454 455 456
        ProcessManager: () => FakeProcessManager.any(),
        Usage: () => testUsage,
      });
    });

    group('hot reload that failed to sync dev fs', () {
457
      late TestHotRunnerConfig testingConfig;
458 459 460 461 462 463 464 465 466 467 468
      setUp(() {
        testingConfig = TestHotRunnerConfig(
          successfulHotReloadSetup: true,
        );
      });
      testUsingContext('still calls the devfs complete callback', () async {
        final FakeDevice device = FakeDevice();
        final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
        final List<FlutterDevice> devices = <FlutterDevice>[
          fakeFlutterDevice,
        ];
469
        fakeFlutterDevice.updateDevFSReportCallback = () async => throw Exception('updateDevFS failed');
470 471 472 473 474 475 476 477

        final HotRunner runner = HotRunner(
          devices,
          debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
          target: 'main.dart',
          devtoolsHandler: createNoOpHandler,
        );

478
        await expectLater(runner.restart(), throwsA(isA<Exception>().having((Exception e) => e.toString(), 'message', 'Exception: updateDevFS failed')));
479 480 481 482 483
        expect(testingConfig.updateDevFSCompleteCalled, true);
      }, overrides: <Type, Generator>{
        HotRunnerConfig: () => testingConfig,
        Artifacts: () => Artifacts.test(),
        FileSystem: () => fileSystem,
484
        Platform: () => FakePlatform(),
485 486 487
        ProcessManager: () => FakeProcessManager.any(),
        Usage: () => testUsage,
      });
488
    });
489
  });
490 491

  group('hot attach', () {
492
    late FileSystem fileSystem;
493 494

    setUp(() {
495
      fileSystem = MemoryFileSystem.test();
496 497
    });

nt4f04uNd's avatar
nt4f04uNd committed
498
    testUsingContext('Exits with code 2 when HttpException is thrown '
499
      'during VM service connection', () async {
500
      fileSystem.file('.packages')
501 502 503
        ..createSync(recursive: true)
        ..writeAsStringSync('\n');

504
      final FakeResidentCompiler residentCompiler = FakeResidentCompiler();
505
      final FakeDevice device = FakeDevice();
506 507
      final List<FlutterDevice> devices = <FlutterDevice>[
        TestFlutterDevice(
508
          device: device,
509 510
          generator: residentCompiler,
          exception: const HttpException('Connection closed before full header was received, '
511
              'uri = http://127.0.0.1:63394/5ZmLv8A59xY=/ws'),
512 513 514 515 516
        ),
      ];

      final int exitCode = await HotRunner(devices,
        debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
517
        target: 'main.dart',
518
      ).attach(needsFullRestart: false);
519 520
      expect(exitCode, 2);
    }, overrides: <Type, Generator>{
521
      HotRunnerConfig: () => TestHotRunnerConfig(),
522 523
      Artifacts: () => Artifacts.test(),
      FileSystem: () => fileSystem,
524
      Platform: () => FakePlatform(),
525
      ProcessManager: () => FakeProcessManager.any(),
526 527
    });
  });
528 529 530

  group('hot cleanupAtFinish()', () {
    testUsingContext('disposes each device', () async {
531 532 533 534
      final FakeDevice device1 = FakeDevice();
      final FakeDevice device2 = FakeDevice();
      final FakeFlutterDevice flutterDevice1 = FakeFlutterDevice(device1);
      final FakeFlutterDevice flutterDevice2 = FakeFlutterDevice(device2);
535 536

      final List<FlutterDevice> devices = <FlutterDevice>[
537 538
        flutterDevice1,
        flutterDevice2,
539 540 541 542
      ];

      await HotRunner(devices,
        debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
543
        target: 'main.dart',
544 545
      ).cleanupAtFinish();

546 547 548 549 550
      expect(device1.disposed, true);
      expect(device2.disposed, true);

      expect(flutterDevice1.stoppedEchoingDeviceLog, true);
      expect(flutterDevice2.stoppedEchoingDeviceLog, true);
551 552
    });
  });
553 554
}

555 556 557
class FakeDevFs extends Fake implements DevFS {
  @override
  Future<void> destroy() async { }
558 559 560 561 562

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

  @override
563
  DateTime? lastCompiled;
564 565

  @override
566
  PackageConfig? lastPackageConfig;
567 568 569 570

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

571 572 573
  @override
  Set<String> shaderPathsToEvict= <String>{};

574
  @override
575
  Uri? baseUri;
576 577
}

578 579 580
// 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
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597
class FakeDevice extends Fake implements Device {
  bool disposed = false;

  @override
  bool isSupported() => true;

  @override
  bool supportsHotReload = true;

  @override
  bool supportsHotRestart = true;

  @override
  bool supportsFlutterExit = true;

  @override
  Future<TargetPlatform> get targetPlatform async => TargetPlatform.tester;
598

599 600 601 602 603 604
  @override
  Future<String> get sdkNameAndVersion async => 'Tester';

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

605 606 607
  @override
  String get name => 'Fake Device';

608 609
  @override
  Future<bool> stopApp(
610 611
    covariant ApplicationPackage? app, {
    String? userIdentifier,
612 613 614 615 616 617 618
  }) async {
    return true;
  }

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

622 623 624 625
class FakeFlutterDevice extends Fake implements FlutterDevice {
  FakeFlutterDevice(this.device);

  bool stoppedEchoingDeviceLog = false;
626
  late Future<UpdateFSReport> Function() updateDevFSReportCallback;
627 628 629 630 631 632 633 634 635 636

  @override
  final FakeDevice device;

  @override
  Future<void> stopEchoingDeviceLog() async {
    stoppedEchoingDeviceLog = true;
  }

  @override
637
  DevFS? devFS = FakeDevFs();
638 639 640 641 642

  @override
  FlutterVmService get vmService => FakeFlutterVmService();

  @override
643
  ResidentCompiler? generator;
644 645 646

  @override
  Future<UpdateFSReport> updateDevFS({
647 648 649 650
    Uri? mainUri,
    String? target,
    AssetBundle? bundle,
    DateTime? firstBuildTime,
651 652 653
    bool bundleFirstUpload = false,
    bool bundleDirty = false,
    bool fullRestart = false,
654 655 656 657 658
    String? projectRootPath,
    String? pathToReload,
    required String dillOutputPath,
    required List<Uri> invalidatedFiles,
    required PackageConfig packageConfig,
659
  }) => updateDevFSReportCallback();
660
}
661

662 663
class TestFlutterDevice extends FlutterDevice {
  TestFlutterDevice({
664 665 666
    required Device device,
    required this.exception,
    required ResidentCompiler generator,
667
  })  : assert(exception != null),
668
        super(device, buildInfo: BuildInfo.debug, generator: generator, developmentShaderCompiler: const FakeShaderCompiler());
669 670 671 672 673 674

  /// The exception to throw when the connect method is called.
  final Exception exception;

  @override
  Future<void> connect({
675 676 677 678 679
    ReloadSources? reloadSources,
    Restart? restart,
    CompileExpression? compileExpression,
    GetSkSLMethod? getSkSLMethod,
    PrintStructuredErrorLogMethod? printStructuredErrorLogMethod,
680
    bool disableServiceAuthCodes = false,
681
    bool enableDds = true,
682
    bool cacheStartupProfile = false,
683 684 685
    bool? ipv6 = false,
    int? hostVmServicePort,
    int? ddsPort,
686
    bool allowExistingDdsInstance = false,
687 688 689 690 691
  }) async {
    throw exception;
  }
}

692
class TestHotRunnerConfig extends HotRunnerConfig {
693
  TestHotRunnerConfig({this.successfulHotRestartSetup, this.successfulHotReloadSetup});
694 695
  bool? successfulHotRestartSetup;
  bool? successfulHotReloadSetup;
696
  bool shutdownHookCalled = false;
697
  bool updateDevFSCompleteCalled = false;
698

699
  @override
700
  Future<bool?> setupHotRestart() async {
701 702 703 704 705
    assert(successfulHotRestartSetup != null, 'setupHotRestart is not expected to be called in this test.');
    return successfulHotRestartSetup;
  }

  @override
706
  Future<bool?> setupHotReload() async {
707 708 709 710 711 712 713
    assert(successfulHotReloadSetup != null, 'setupHotReload is not expected to be called in this test.');
    return successfulHotReloadSetup;
  }

  @override
  void updateDevFSComplete() {
    updateDevFSCompleteCalled = true;
714
  }
715 716 717 718 719

  @override
  Future<void> runPreShutdownOperations() async {
    shutdownHookCalled = true;
  }
720
}
721 722 723 724 725

class FakeResidentCompiler extends Fake implements ResidentCompiler {
  @override
  void accept() {}
}
726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745

class FakeFlutterVmService extends Fake implements FlutterVmService {
  @override
  vm_service.VmService get service => FakeVmService();

  @override
  Future<List<FlutterView>> getFlutterViews({bool returnEarly = false, Duration delay = const Duration(milliseconds: 50)}) async {
    return <FlutterView>[];
  }
}

class FakeVmService extends Fake implements vm_service.VmService {
  @override
  Future<vm_service.VM> getVM() async => FakeVm();
}

class FakeVm extends Fake implements vm_service.VM {
  @override
  List<vm_service.IsolateRef> get isolates => <vm_service.IsolateRef>[];
}
746 747 748 749 750

class FakeShaderCompiler implements DevelopmentShaderCompiler {
  const FakeShaderCompiler();

  @override
751
  void configureCompiler(TargetPlatform? platform, { required bool enableImpeller }) { }
752 753 754 755 756 757

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