| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:file/memory.dart'; |
| import 'package:flutter_tools/src/artifacts.dart'; |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:flutter_tools/src/base/io.dart'; |
| import 'package:flutter_tools/src/base/platform.dart'; |
| import 'package:flutter_tools/src/build_info.dart'; |
| import 'package:flutter_tools/src/devfs.dart'; |
| import 'package:flutter_tools/src/device.dart'; |
| import 'package:flutter_tools/src/reporting/reporting.dart'; |
| import 'package:flutter_tools/src/resident_devtools_handler.dart'; |
| import 'package:flutter_tools/src/resident_runner.dart'; |
| import 'package:flutter_tools/src/run_hot.dart'; |
| import 'package:flutter_tools/src/vmservice.dart'; |
| import 'package:unified_analytics/unified_analytics.dart'; |
| import 'package:vm_service/vm_service.dart' as vm_service; |
| |
| import '../src/common.dart'; |
| import '../src/context.dart'; |
| import '../src/fakes.dart'; |
| import 'hot_shared.dart'; |
| |
| void main() { |
| group('validateReloadReport', () { |
| testUsingContext('invalid', () async { |
| expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{ |
| 'type': 'ReloadReport', |
| 'success': false, |
| 'details': <String, dynamic>{}, |
| })), false); |
| expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{ |
| 'type': 'ReloadReport', |
| 'success': false, |
| 'details': <String, dynamic>{ |
| 'notices': <Map<String, dynamic>>[ |
| ], |
| }, |
| })), false); |
| expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{ |
| 'type': 'ReloadReport', |
| 'success': false, |
| 'details': <String, dynamic>{ |
| 'notices': <String, dynamic>{ |
| 'message': 'error', |
| }, |
| }, |
| })), false); |
| expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{ |
| 'type': 'ReloadReport', |
| 'success': false, |
| 'details': <String, dynamic>{ |
| 'notices': <Map<String, dynamic>>[], |
| }, |
| })), false); |
| expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{ |
| 'type': 'ReloadReport', |
| 'success': false, |
| 'details': <String, dynamic>{ |
| 'notices': <Map<String, dynamic>>[ |
| <String, dynamic>{'message': false}, |
| ], |
| }, |
| })), false); |
| expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{ |
| 'type': 'ReloadReport', |
| 'success': false, |
| 'details': <String, dynamic>{ |
| 'notices': <Map<String, dynamic>>[ |
| <String, dynamic>{'message': <String>['error']}, |
| ], |
| }, |
| })), false); |
| expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{ |
| 'type': 'ReloadReport', |
| 'success': false, |
| 'details': <String, dynamic>{ |
| 'notices': <Map<String, dynamic>>[ |
| <String, dynamic>{'message': 'error'}, |
| <String, dynamic>{'message': <String>['error']}, |
| ], |
| }, |
| })), false); |
| expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{ |
| 'type': 'ReloadReport', |
| 'success': false, |
| 'details': <String, dynamic>{ |
| 'notices': <Map<String, dynamic>>[ |
| <String, dynamic>{'message': 'error'}, |
| ], |
| }, |
| })), false); |
| expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse(<String, dynamic>{ |
| 'type': 'ReloadReport', |
| 'success': true, |
| })), 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.')); |
| }); |
| }); |
| |
| group('hotRestart', () { |
| final FakeResidentCompiler residentCompiler = FakeResidentCompiler(); |
| late FileSystem fileSystem; |
| late TestUsage testUsage; |
| late FakeAnalytics fakeAnalytics; |
| |
| setUp(() { |
| fileSystem = MemoryFileSystem.test(); |
| testUsage = TestUsage(); |
| fakeAnalytics = getInitializedFakeAnalyticsInstance( |
| fs: fileSystem, |
| fakeFlutterVersion: FakeFlutterVersion(), |
| ); |
| }); |
| |
| group('fails to setup', () { |
| late TestHotRunnerConfig failingTestingConfig; |
| 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>[ |
| FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler()) |
| ..devFS = FakeDevFs(), |
| ]; |
| final OperationResult result = await HotRunner( |
| devices, |
| debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug), |
| target: 'main.dart', |
| devtoolsHandler: createNoOpHandler, |
| analytics: fakeAnalytics, |
| |
| ).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, |
| Platform: () => FakePlatform(), |
| 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: ( |
| List<FlutterDevice?> flutterDevices, |
| Map<FlutterDevice?, List<FlutterView>> viewCache, |
| void Function(String message)? onSlow, |
| String reloadMessage, |
| ) async => ReassembleResult( |
| <FlutterView?, FlutterVmService?>{null: null}, |
| false, |
| true, |
| ), |
| analytics: fakeAnalytics, |
| ).restart(); |
| expect(result.isOk, false); |
| expect(result.message, 'setupHotReload failed'); |
| expect(failingTestingConfig.updateDevFSCompleteCalled, false); |
| }, overrides: <Type, Generator>{ |
| HotRunnerConfig: () => failingTestingConfig, |
| Artifacts: () => Artifacts.test(), |
| FileSystem: () => fileSystem, |
| Platform: () => FakePlatform(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| }); |
| |
| group('shutdown hook tests', () { |
| late TestHotRunnerConfig shutdownTestingConfig; |
| |
| setUp(() { |
| shutdownTestingConfig = TestHotRunnerConfig(); |
| }); |
| |
| testUsingContext('shutdown hook called after signal', () async { |
| fileSystem.file('.packages') |
| ..createSync(recursive: true) |
| ..writeAsStringSync('\n'); |
| final FakeDevice device = FakeDevice(); |
| final List<FlutterDevice> devices = <FlutterDevice>[ |
| FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler()), |
| ]; |
| await HotRunner( |
| devices, |
| debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug), |
| target: 'main.dart', |
| analytics: fakeAnalytics, |
| ).cleanupAfterSignal(); |
| expect(shutdownTestingConfig.shutdownHookCalled, true); |
| }, overrides: <Type, Generator>{ |
| HotRunnerConfig: () => shutdownTestingConfig, |
| Artifacts: () => Artifacts.test(), |
| FileSystem: () => fileSystem, |
| Platform: () => FakePlatform(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('shutdown hook called after app stop', () async { |
| fileSystem.file('.packages') |
| ..createSync(recursive: true) |
| ..writeAsStringSync('\n'); |
| final FakeDevice device = FakeDevice(); |
| final List<FlutterDevice> devices = <FlutterDevice>[ |
| FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler()), |
| ]; |
| await HotRunner( |
| devices, |
| debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug), |
| target: 'main.dart', |
| analytics: fakeAnalytics, |
| ).preExit(); |
| expect(shutdownTestingConfig.shutdownHookCalled, true); |
| }, overrides: <Type, Generator>{ |
| HotRunnerConfig: () => shutdownTestingConfig, |
| Artifacts: () => Artifacts.test(), |
| FileSystem: () => fileSystem, |
| Platform: () => FakePlatform(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| }); |
| |
| group('successful hot restart', () { |
| late TestHotRunnerConfig testingConfig; |
| 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, |
| ]; |
| |
| fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport( |
| success: true, |
| invalidatedSourcesCount: 2, |
| syncedBytes: 4, |
| scannedSourcesCount: 8, |
| compileDuration: const Duration(seconds: 16), |
| transferDuration: const Duration(seconds: 32), |
| ); |
| |
| final FakeStopwatchFactory fakeStopwatchFactory = FakeStopwatchFactory( |
| stopwatches: <String, Stopwatch>{ |
| 'fullRestartHelper': FakeStopwatch()..elapsed = const Duration(seconds: 64), |
| 'updateDevFS': FakeStopwatch()..elapsed = const Duration(seconds: 128), |
| }, |
| ); |
| |
| (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = Uri.parse('file:///base_uri'); |
| |
| final OperationResult result = await HotRunner( |
| devices, |
| debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug), |
| target: 'main.dart', |
| devtoolsHandler: createNoOpHandler, |
| stopwatchFactory: fakeStopwatchFactory, |
| analytics: fakeAnalytics, |
| ).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, |
| hotEventOverallTimeInMs: 64000, |
| hotEventSyncedBytes: 4, |
| hotEventInvalidatedSourcesCount: 2, |
| hotEventTransferTimeInMs: 32000, |
| hotEventCompileTimeInMs: 16000, |
| hotEventFindInvalidatedTimeInMs: 128000, |
| hotEventScannedSourcesCount: 8, |
| )), |
| ]); |
| |
| expect(fakeAnalytics.sentEvents, contains( |
| Event.hotRunnerInfo( |
| label: 'restart', |
| targetPlatform: 'flutter-tester', |
| sdkName: 'Tester', |
| emulator: false, |
| fullRestart: true, |
| syncedBytes: 4, |
| invalidatedSourcesCount: 2, |
| transferTimeInMs: 32000, |
| overallTimeInMs: 64000, |
| compileTimeInMs: 16000, |
| findInvalidatedTimeInMs: 128000, |
| scannedSourcesCount: 8 |
| ) |
| )); |
| expect(testingConfig.updateDevFSCompleteCalled, true); |
| }, overrides: <Type, Generator>{ |
| HotRunnerConfig: () => testingConfig, |
| Artifacts: () => Artifacts.test(), |
| FileSystem: () => fileSystem, |
| Platform: () => FakePlatform(), |
| ProcessManager: () => FakeProcessManager.any(), |
| Usage: () => testUsage, |
| }); |
| }); |
| |
| group('successful hot reload', () { |
| late TestHotRunnerConfig testingConfig; |
| 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, |
| ]; |
| |
| fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport( |
| success: true, |
| invalidatedSourcesCount: 6, |
| syncedBytes: 8, |
| scannedSourcesCount: 16, |
| compileDuration: const Duration(seconds: 16), |
| transferDuration: const Duration(seconds: 32), |
| ); |
| |
| 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), |
| }, |
| ); |
| |
| (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = Uri.parse('file:///base_uri'); |
| |
| final OperationResult result = await HotRunner( |
| devices, |
| debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug), |
| target: 'main.dart', |
| devtoolsHandler: createNoOpHandler, |
| stopwatchFactory: fakeStopwatchFactory, |
| analytics: fakeAnalytics, |
| reloadSourcesHelper: ( |
| HotRunner hotRunner, |
| List<FlutterDevice?> flutterDevices, |
| bool? pause, |
| Map<String, dynamic> firstReloadDetails, |
| String? targetPlatform, |
| String? sdkName, |
| bool? emulator, |
| String? reason, |
| Usage usage, |
| Analytics? analytics, |
| ) async { |
| firstReloadDetails['finalLibraryCount'] = 2; |
| firstReloadDetails['receivedLibraryCount'] = 3; |
| firstReloadDetails['receivedClassesCount'] = 4; |
| firstReloadDetails['receivedProceduresCount'] = 5; |
| return OperationResult.ok; |
| }, |
| reassembleHelper: ( |
| List<FlutterDevice?> flutterDevices, |
| Map<FlutterDevice?, List<FlutterView>> viewCache, |
| void Function(String message)? onSlow, |
| String reloadMessage, |
| ) async => ReassembleResult( |
| <FlutterView?, FlutterVmService?>{null: null}, |
| false, |
| true, |
| ), |
| ).restart(); |
| |
| 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, |
| hotEventCompileTimeInMs: 16000, |
| hotEventFindInvalidatedTimeInMs: 64000, |
| hotEventScannedSourcesCount: 16, |
| hotEventReassembleTimeInMs: 256000, |
| hotEventReloadVMTimeInMs: 512000, |
| )), |
| ]); |
| expect(fakeAnalytics.sentEvents, contains( |
| Event.hotRunnerInfo( |
| label: 'reload', |
| targetPlatform: 'flutter-tester', |
| sdkName: 'Tester', |
| emulator: false, |
| fullRestart: false, |
| finalLibraryCount: 2, |
| syncedLibraryCount: 3, |
| syncedClassesCount: 4, |
| syncedProceduresCount: 5, |
| syncedBytes: 8, |
| invalidatedSourcesCount: 6, |
| transferTimeInMs: 32000, |
| overallTimeInMs: 128000, |
| compileTimeInMs: 16000, |
| findInvalidatedTimeInMs: 64000, |
| scannedSourcesCount: 16, |
| reassembleTimeInMs: 256000, |
| reloadVMTimeInMs: 512000 |
| ), |
| )); |
| expect(testingConfig.updateDevFSCompleteCalled, true); |
| }, overrides: <Type, Generator>{ |
| HotRunnerConfig: () => testingConfig, |
| Artifacts: () => Artifacts.test(), |
| FileSystem: () => fileSystem, |
| Platform: () => FakePlatform(), |
| ProcessManager: () => FakeProcessManager.any(), |
| Usage: () => testUsage, |
| }); |
| }); |
| |
| group('hot restart that failed to sync dev fs', () { |
| late TestHotRunnerConfig testingConfig; |
| 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, |
| ]; |
| fakeFlutterDevice.updateDevFSReportCallback = () async => throw Exception('updateDevFS failed'); |
| |
| final HotRunner runner = HotRunner( |
| devices, |
| debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug), |
| target: 'main.dart', |
| devtoolsHandler: createNoOpHandler, |
| analytics: fakeAnalytics, |
| ); |
| |
| await expectLater(runner.restart(fullRestart: true), throwsA(isA<Exception>().having((Exception e) => e.toString(), 'message', 'Exception: updateDevFS failed'))); |
| expect(testingConfig.updateDevFSCompleteCalled, true); |
| }, overrides: <Type, Generator>{ |
| HotRunnerConfig: () => testingConfig, |
| Artifacts: () => Artifacts.test(), |
| FileSystem: () => fileSystem, |
| Platform: () => FakePlatform(), |
| ProcessManager: () => FakeProcessManager.any(), |
| Usage: () => testUsage, |
| }); |
| }); |
| |
| group('hot reload that failed to sync dev fs', () { |
| late TestHotRunnerConfig testingConfig; |
| 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, |
| ]; |
| fakeFlutterDevice.updateDevFSReportCallback = () async => throw Exception('updateDevFS failed'); |
| |
| final HotRunner runner = HotRunner( |
| devices, |
| debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug), |
| target: 'main.dart', |
| devtoolsHandler: createNoOpHandler, |
| analytics: fakeAnalytics, |
| ); |
| |
| await expectLater(runner.restart(), throwsA(isA<Exception>().having((Exception e) => e.toString(), 'message', 'Exception: updateDevFS failed'))); |
| expect(testingConfig.updateDevFSCompleteCalled, true); |
| }, overrides: <Type, Generator>{ |
| HotRunnerConfig: () => testingConfig, |
| Artifacts: () => Artifacts.test(), |
| FileSystem: () => fileSystem, |
| Platform: () => FakePlatform(), |
| ProcessManager: () => FakeProcessManager.any(), |
| Usage: () => testUsage, |
| }); |
| }); |
| }); |
| |
| group('hot attach', () { |
| late FileSystem fileSystem; |
| late FakeAnalytics fakeAnalytics; |
| |
| setUp(() { |
| fileSystem = MemoryFileSystem.test(); |
| fakeAnalytics = getInitializedFakeAnalyticsInstance( |
| fs: fileSystem, |
| fakeFlutterVersion: FakeFlutterVersion(), |
| ); |
| }); |
| |
| testUsingContext('Exits with code 2 when HttpException is thrown ' |
| 'during VM service connection', () async { |
| fileSystem.file('.packages') |
| ..createSync(recursive: true) |
| ..writeAsStringSync('\n'); |
| |
| final FakeResidentCompiler residentCompiler = FakeResidentCompiler(); |
| final FakeDevice device = FakeDevice(); |
| final List<FlutterDevice> devices = <FlutterDevice>[ |
| TestFlutterDevice( |
| device: device, |
| generator: residentCompiler, |
| exception: const HttpException('Connection closed before full header was received, ' |
| 'uri = http://127.0.0.1:63394/5ZmLv8A59xY=/ws'), |
| ), |
| ]; |
| |
| final int exitCode = await HotRunner(devices, |
| debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), |
| target: 'main.dart', |
| analytics: fakeAnalytics, |
| ).attach(needsFullRestart: false); |
| expect(exitCode, 2); |
| }, overrides: <Type, Generator>{ |
| HotRunnerConfig: () => TestHotRunnerConfig(), |
| Artifacts: () => Artifacts.test(), |
| FileSystem: () => fileSystem, |
| Platform: () => FakePlatform(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| }); |
| |
| group('hot cleanupAtFinish()', () { |
| late FileSystem fileSystem; |
| late FakeAnalytics fakeAnalytics; |
| |
| setUp(() { |
| fileSystem = MemoryFileSystem.test(); |
| fakeAnalytics = getInitializedFakeAnalyticsInstance( |
| fs: fileSystem, |
| fakeFlutterVersion: FakeFlutterVersion(), |
| ); |
| }); |
| |
| testUsingContext('disposes each device', () async { |
| final FakeDevice device1 = FakeDevice(); |
| final FakeDevice device2 = FakeDevice(); |
| final FakeFlutterDevice flutterDevice1 = FakeFlutterDevice(device1); |
| final FakeFlutterDevice flutterDevice2 = FakeFlutterDevice(device2); |
| |
| final List<FlutterDevice> devices = <FlutterDevice>[ |
| flutterDevice1, |
| flutterDevice2, |
| ]; |
| |
| await HotRunner(devices, |
| debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), |
| target: 'main.dart', |
| analytics: fakeAnalytics, |
| ).cleanupAtFinish(); |
| |
| expect(device1.disposed, true); |
| expect(device2.disposed, true); |
| |
| expect(flutterDevice1.stoppedEchoingDeviceLog, true); |
| expect(flutterDevice2.stoppedEchoingDeviceLog, true); |
| }); |
| }); |
| } |