mirror of
https://github.com/flutter/flutter
synced 2024-10-13 03:32:55 +00:00
95217eb72a
* Add new hook for setupHotReload and after updating devFS is complete * Handle the case where updateDevFS failed
773 lines
26 KiB
Dart
773 lines
26 KiB
Dart
// 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.
|
|
|
|
// @dart = 2.8
|
|
|
|
import 'package:file/memory.dart';
|
|
import 'package:flutter_tools/src/application_package.dart';
|
|
import 'package:flutter_tools/src/artifacts.dart';
|
|
import 'package:flutter_tools/src/asset.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/compile.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:meta/meta.dart';
|
|
import 'package:package_config/package_config.dart';
|
|
import 'package:test/fake.dart';
|
|
import 'package:vm_service/vm_service.dart' as vm_service;
|
|
|
|
import '../src/common.dart';
|
|
import '../src/context.dart';
|
|
import '../src/fake_vm_services.dart';
|
|
import '../src/fakes.dart';
|
|
|
|
final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate(
|
|
id: '1',
|
|
pauseEvent: vm_service.Event(
|
|
kind: vm_service.EventKind.kResume,
|
|
timestamp: 0
|
|
),
|
|
breakpoints: <vm_service.Breakpoint>[],
|
|
exceptionPauseMode: null,
|
|
libraries: <vm_service.LibraryRef>[],
|
|
livePorts: 0,
|
|
name: 'test',
|
|
number: '1',
|
|
pauseOnExit: false,
|
|
runnable: true,
|
|
startTime: 0,
|
|
isSystemIsolate: false,
|
|
isolateFlags: <vm_service.IsolateFlag>[],
|
|
);
|
|
|
|
final FlutterView fakeFlutterView = FlutterView(
|
|
id: 'a',
|
|
uiIsolate: fakeUnpausedIsolate,
|
|
);
|
|
|
|
final FakeVmServiceRequest listViews = FakeVmServiceRequest(
|
|
method: kListViewsMethod,
|
|
jsonResponse: <String, Object>{
|
|
'views': <Object>[
|
|
fakeFlutterView.toJson(),
|
|
],
|
|
},
|
|
);
|
|
|
|
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();
|
|
FileSystem fileSystem;
|
|
TestUsage testUsage;
|
|
|
|
setUp(() {
|
|
fileSystem = MemoryFileSystem.test();
|
|
testUsage = TestUsage();
|
|
});
|
|
|
|
group('fails to setup', () {
|
|
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)..devFS = FakeDevFs(),
|
|
];
|
|
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,
|
|
Platform: () => FakePlatform(operatingSystem: 'linux'),
|
|
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,
|
|
String fastReassembleClassName,
|
|
) async => ReassembleResult(
|
|
<FlutterView, FlutterVmService>{null: null},
|
|
false,
|
|
true,
|
|
),
|
|
).restart(fullRestart: false);
|
|
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(operatingSystem: 'linux'),
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
});
|
|
});
|
|
|
|
group('shutdown hook tests', () {
|
|
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),
|
|
];
|
|
await HotRunner(
|
|
devices,
|
|
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
|
|
target: 'main.dart',
|
|
).cleanupAfterSignal();
|
|
expect(shutdownTestingConfig.shutdownHookCalled, true);
|
|
}, overrides: <Type, Generator>{
|
|
HotRunnerConfig: () => shutdownTestingConfig,
|
|
Artifacts: () => Artifacts.test(),
|
|
FileSystem: () => fileSystem,
|
|
Platform: () => FakePlatform(operatingSystem: 'linux'),
|
|
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),
|
|
];
|
|
await HotRunner(
|
|
devices,
|
|
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
|
|
target: 'main.dart',
|
|
).preExit();
|
|
expect(shutdownTestingConfig.shutdownHookCalled, true);
|
|
}, overrides: <Type, Generator>{
|
|
HotRunnerConfig: () => shutdownTestingConfig,
|
|
Artifacts: () => Artifacts.test(),
|
|
FileSystem: () => fileSystem,
|
|
Platform: () => FakePlatform(operatingSystem: 'linux'),
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
});
|
|
});
|
|
|
|
group('successful hot restart', () {
|
|
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,
|
|
).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(testingConfig.updateDevFSCompleteCalled, true);
|
|
}, overrides: <Type, Generator>{
|
|
HotRunnerConfig: () => testingConfig,
|
|
Artifacts: () => Artifacts.test(),
|
|
FileSystem: () => fileSystem,
|
|
Platform: () => FakePlatform(operatingSystem: 'linux'),
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Usage: () => testUsage,
|
|
});
|
|
});
|
|
|
|
group('successful hot reload', () {
|
|
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,
|
|
reloadSourcesHelper: (
|
|
HotRunner hotRunner,
|
|
List<FlutterDevice> flutterDevices,
|
|
bool pause,
|
|
Map<String, dynamic> firstReloadDetails,
|
|
String targetPlatform,
|
|
String sdkName,
|
|
bool emulator,
|
|
String reason,
|
|
) 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,
|
|
String fastReassembleClassName,
|
|
) async => ReassembleResult(
|
|
<FlutterView, FlutterVmService>{null: null},
|
|
false,
|
|
true,
|
|
),
|
|
).restart(fullRestart: false);
|
|
|
|
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,
|
|
Platform: () => FakePlatform(operatingSystem: 'linux'),
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Usage: () => testUsage,
|
|
});
|
|
});
|
|
|
|
group('hot restart that failed to sync dev fs', () {
|
|
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 'updateDevFS failed';
|
|
|
|
final HotRunner runner = HotRunner(
|
|
devices,
|
|
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
|
|
target: 'main.dart',
|
|
devtoolsHandler: createNoOpHandler,
|
|
);
|
|
|
|
await expectLater(runner.restart(fullRestart: true), throwsA('updateDevFS failed'));
|
|
expect(testingConfig.updateDevFSCompleteCalled, true);
|
|
}, overrides: <Type, Generator>{
|
|
HotRunnerConfig: () => testingConfig,
|
|
Artifacts: () => Artifacts.test(),
|
|
FileSystem: () => fileSystem,
|
|
Platform: () => FakePlatform(operatingSystem: 'linux'),
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Usage: () => testUsage,
|
|
});
|
|
});
|
|
|
|
group('hot reload that failed to sync dev fs', () {
|
|
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 'updateDevFS failed';
|
|
|
|
final HotRunner runner = HotRunner(
|
|
devices,
|
|
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
|
|
target: 'main.dart',
|
|
devtoolsHandler: createNoOpHandler,
|
|
);
|
|
|
|
await expectLater(runner.restart(fullRestart: false), throwsA('updateDevFS failed'));
|
|
expect(testingConfig.updateDevFSCompleteCalled, true);
|
|
}, overrides: <Type, Generator>{
|
|
HotRunnerConfig: () => testingConfig,
|
|
Artifacts: () => Artifacts.test(),
|
|
FileSystem: () => fileSystem,
|
|
Platform: () => FakePlatform(operatingSystem: 'linux'),
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Usage: () => testUsage,
|
|
});
|
|
});
|
|
});
|
|
|
|
group('hot attach', () {
|
|
FileSystem fileSystem;
|
|
|
|
setUp(() {
|
|
fileSystem = MemoryFileSystem.test();
|
|
});
|
|
|
|
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',
|
|
).attach(
|
|
enableDevTools: false,
|
|
);
|
|
expect(exitCode, 2);
|
|
}, overrides: <Type, Generator>{
|
|
HotRunnerConfig: () => TestHotRunnerConfig(),
|
|
Artifacts: () => Artifacts.test(),
|
|
FileSystem: () => fileSystem,
|
|
Platform: () => FakePlatform(operatingSystem: 'linux'),
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
});
|
|
});
|
|
|
|
group('hot cleanupAtFinish()', () {
|
|
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',
|
|
).cleanupAtFinish();
|
|
|
|
expect(device1.disposed, true);
|
|
expect(device2.disposed, true);
|
|
|
|
expect(flutterDevice1.stoppedEchoingDeviceLog, true);
|
|
expect(flutterDevice2.stoppedEchoingDeviceLog, true);
|
|
});
|
|
});
|
|
}
|
|
|
|
class FakeDevFs extends Fake implements DevFS {
|
|
@override
|
|
Future<void> destroy() async { }
|
|
|
|
@override
|
|
List<Uri> sources = <Uri>[];
|
|
|
|
@override
|
|
DateTime lastCompiled;
|
|
|
|
@override
|
|
PackageConfig lastPackageConfig;
|
|
|
|
@override
|
|
Set<String> assetPathsToEvict = <String>{};
|
|
|
|
@override
|
|
Uri baseUri;
|
|
}
|
|
|
|
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;
|
|
|
|
@override
|
|
Future<String> get sdkNameAndVersion async => 'Tester';
|
|
|
|
@override
|
|
Future<bool> get isLocalEmulator async => false;
|
|
|
|
@override
|
|
String get name => 'Fake Device';
|
|
|
|
@override
|
|
Future<bool> stopApp(
|
|
covariant ApplicationPackage app, {
|
|
String userIdentifier,
|
|
}) async {
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<void> dispose() async {
|
|
disposed = true;
|
|
}
|
|
}
|
|
|
|
class FakeFlutterDevice extends Fake implements FlutterDevice {
|
|
FakeFlutterDevice(this.device);
|
|
|
|
bool stoppedEchoingDeviceLog = false;
|
|
Future<UpdateFSReport> Function() updateDevFSReportCallback;
|
|
|
|
@override
|
|
final FakeDevice device;
|
|
|
|
@override
|
|
Future<void> stopEchoingDeviceLog() async {
|
|
stoppedEchoingDeviceLog = true;
|
|
}
|
|
|
|
@override
|
|
DevFS devFS = FakeDevFs();
|
|
|
|
@override
|
|
FlutterVmService get vmService => FakeFlutterVmService();
|
|
|
|
@override
|
|
ResidentCompiler generator;
|
|
|
|
@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,
|
|
@required String dillOutputPath,
|
|
@required List<Uri> invalidatedFiles,
|
|
@required PackageConfig packageConfig,
|
|
}) => updateDevFSReportCallback();
|
|
}
|
|
|
|
class TestFlutterDevice extends FlutterDevice {
|
|
TestFlutterDevice({
|
|
@required Device device,
|
|
@required this.exception,
|
|
@required ResidentCompiler generator,
|
|
}) : assert(exception != null),
|
|
super(device, buildInfo: BuildInfo.debug, generator: generator);
|
|
|
|
/// The exception to throw when the connect method is called.
|
|
final Exception exception;
|
|
|
|
@override
|
|
Future<void> connect({
|
|
ReloadSources reloadSources,
|
|
Restart restart,
|
|
CompileExpression compileExpression,
|
|
GetSkSLMethod getSkSLMethod,
|
|
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
|
|
bool disableServiceAuthCodes = false,
|
|
bool enableDds = true,
|
|
bool ipv6 = false,
|
|
int hostVmServicePort,
|
|
int ddsPort,
|
|
bool allowExistingDdsInstance = false,
|
|
}) async {
|
|
throw exception;
|
|
}
|
|
}
|
|
|
|
class TestHotRunnerConfig extends HotRunnerConfig {
|
|
TestHotRunnerConfig({this.successfulHotRestartSetup, this.successfulHotReloadSetup});
|
|
bool successfulHotRestartSetup;
|
|
bool successfulHotReloadSetup;
|
|
bool shutdownHookCalled = false;
|
|
bool updateDevFSCompleteCalled = false;
|
|
|
|
@override
|
|
Future<bool> setupHotRestart() async {
|
|
assert(successfulHotRestartSetup != null, 'setupHotRestart is not expected to be called in this test.');
|
|
return successfulHotRestartSetup;
|
|
}
|
|
|
|
@override
|
|
Future<bool> setupHotReload() async {
|
|
assert(successfulHotReloadSetup != null, 'setupHotReload is not expected to be called in this test.');
|
|
return successfulHotReloadSetup;
|
|
}
|
|
|
|
@override
|
|
void updateDevFSComplete() {
|
|
updateDevFSCompleteCalled = true;
|
|
}
|
|
|
|
@override
|
|
Future<void> runPreShutdownOperations() async {
|
|
shutdownHookCalled = true;
|
|
}
|
|
}
|
|
|
|
class FakeResidentCompiler extends Fake implements ResidentCompiler {
|
|
@override
|
|
void accept() {}
|
|
}
|
|
|
|
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>[];
|
|
}
|