Unverified Commit 95217eb7 authored by Lau Ching Jun's avatar Lau Ching Jun Committed by GitHub

Add new hook for setupHotReload and after updating devFS is complete (#87532)

* Add new hook for setupHotReload and after updating devFS is complete

* Handle the case where updateDevFS failed
parent 788aea4b
......@@ -52,6 +52,18 @@ class HotRunnerConfig {
Future<bool> setupHotRestart() async {
return true;
}
/// A hook for implementations to perform any necessary initialization prior
/// to a hot reload. Should return true if the hot restart should continue.
Future<bool> setupHotReload() async {
return true;
}
/// A hook for implementations to perform any necessary cleanup after the
/// devfs sync is complete. At this point the flutter_tools no longer needs to
/// access the source files and assets.
void updateDevFSComplete() {}
/// A hook for implementations to perform any necessary operations right
/// before the runner is about to be shut down.
Future<void> runPreShutdownOperations() async {
......@@ -546,7 +558,12 @@ class HotRunner extends ResidentRunner {
String reason,
}) async {
final Stopwatch restartTimer = Stopwatch()..start();
final UpdateFSReport updatedDevFS = await _updateDevFS(fullRestart: true);
UpdateFSReport updatedDevFS;
try {
updatedDevFS = await _updateDevFS(fullRestart: true);
} finally {
hotRunnerConfig.updateDevFSComplete();
}
if (!updatedDevFS.success) {
for (final FlutterDevice device in flutterDevices) {
if (device.generator != null) {
......@@ -857,8 +874,16 @@ class HotRunner extends ResidentRunner {
}
final Stopwatch reloadTimer = _stopwatchFactory.createStopwatch('reloadSources:reload')..start();
if (!(await hotRunnerConfig.setupHotReload())) {
return OperationResult(1, 'setupHotReload failed');
}
final Stopwatch devFSTimer = Stopwatch()..start();
final UpdateFSReport updatedDevFS = await _updateDevFS();
UpdateFSReport updatedDevFS;
try {
updatedDevFS= await _updateDevFS();
} finally {
hotRunnerConfig.updateDevFSComplete();
}
// Record time it took to synchronize to DevFS.
bool shouldReportReloadTime = true;
_addBenchmarkData('hotReloadDevFSSyncMilliseconds', devFSTimer.elapsed.inMilliseconds);
......
......@@ -157,7 +157,16 @@ void main() {
testUsage = TestUsage();
});
testUsingContext('setup function fails', () async {
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');
......@@ -173,21 +182,58 @@ void main() {
).restart(fullRestart: true);
expect(result.isOk, false);
expect(result.message, 'setupHotRestart failed');
expect(failingTestingConfig.updateDevFSCompleteCalled, false);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: false),
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(
successfulSetup: true,
);
shutdownTestingConfig = TestHotRunnerConfig();
});
testUsingContext('shutdown hook called after signal', () async {
......@@ -235,6 +281,13 @@ void main() {
});
});
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);
......@@ -242,7 +295,7 @@ void main() {
fakeFlutterDevice,
];
fakeFlutterDevice.updateDevFSReport = UpdateFSReport(
fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport(
success: true,
invalidatedSourcesCount: 2,
syncedBytes: 4,
......@@ -284,15 +337,24 @@ void main() {
hotEventScannedSourcesCount: 8,
)),
]);
expect(testingConfig.updateDevFSCompleteCalled, true);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
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);
......@@ -300,7 +362,7 @@ void main() {
fakeFlutterDevice,
];
fakeFlutterDevice.updateDevFSReport = UpdateFSReport(
fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport(
success: true,
invalidatedSourcesCount: 6,
syncedBytes: 8,
......@@ -378,8 +440,9 @@ void main() {
hotEventReloadVMTimeInMs: 512000,
)),
]);
expect(testingConfig.updateDevFSCompleteCalled, true);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
HotRunnerConfig: () => testingConfig,
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(operatingSystem: 'linux'),
......@@ -388,6 +451,75 @@ void main() {
});
});
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;
......@@ -420,7 +552,7 @@ void main() {
);
expect(exitCode, 2);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
HotRunnerConfig: () => TestHotRunnerConfig(),
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(operatingSystem: 'linux'),
......@@ -519,7 +651,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
FakeFlutterDevice(this.device);
bool stoppedEchoingDeviceLog = false;
UpdateFSReport updateDevFSReport;
Future<UpdateFSReport> Function() updateDevFSReportCallback;
@override
final FakeDevice device;
......@@ -552,7 +684,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
@required String dillOutputPath,
@required List<Uri> invalidatedFiles,
@required PackageConfig packageConfig,
}) async => updateDevFSReport;
}) => updateDevFSReportCallback();
}
class TestFlutterDevice extends FlutterDevice {
......@@ -585,13 +717,27 @@ class TestFlutterDevice extends FlutterDevice {
}
class TestHotRunnerConfig extends HotRunnerConfig {
TestHotRunnerConfig({@required this.successfulSetup});
bool successfulSetup;
TestHotRunnerConfig({this.successfulHotRestartSetup, this.successfulHotReloadSetup});
bool successfulHotRestartSetup;
bool successfulHotReloadSetup;
bool shutdownHookCalled = false;
bool updateDevFSCompleteCalled = false;
@override
Future<bool> setupHotRestart() async {
return successfulSetup;
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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment