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 { ...@@ -52,6 +52,18 @@ class HotRunnerConfig {
Future<bool> setupHotRestart() async { Future<bool> setupHotRestart() async {
return true; 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 /// A hook for implementations to perform any necessary operations right
/// before the runner is about to be shut down. /// before the runner is about to be shut down.
Future<void> runPreShutdownOperations() async { Future<void> runPreShutdownOperations() async {
...@@ -546,7 +558,12 @@ class HotRunner extends ResidentRunner { ...@@ -546,7 +558,12 @@ class HotRunner extends ResidentRunner {
String reason, String reason,
}) async { }) async {
final Stopwatch restartTimer = Stopwatch()..start(); 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) { if (!updatedDevFS.success) {
for (final FlutterDevice device in flutterDevices) { for (final FlutterDevice device in flutterDevices) {
if (device.generator != null) { if (device.generator != null) {
...@@ -857,8 +874,16 @@ class HotRunner extends ResidentRunner { ...@@ -857,8 +874,16 @@ class HotRunner extends ResidentRunner {
} }
final Stopwatch reloadTimer = _stopwatchFactory.createStopwatch('reloadSources:reload')..start(); final Stopwatch reloadTimer = _stopwatchFactory.createStopwatch('reloadSources:reload')..start();
if (!(await hotRunnerConfig.setupHotReload())) {
return OperationResult(1, 'setupHotReload failed');
}
final Stopwatch devFSTimer = Stopwatch()..start(); 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. // Record time it took to synchronize to DevFS.
bool shouldReportReloadTime = true; bool shouldReportReloadTime = true;
_addBenchmarkData('hotReloadDevFSSyncMilliseconds', devFSTimer.elapsed.inMilliseconds); _addBenchmarkData('hotReloadDevFSSyncMilliseconds', devFSTimer.elapsed.inMilliseconds);
......
...@@ -157,7 +157,16 @@ void main() { ...@@ -157,7 +157,16 @@ void main() {
testUsage = TestUsage(); 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') fileSystem.file('.packages')
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('\n'); ..writeAsStringSync('\n');
...@@ -173,21 +182,58 @@ void main() { ...@@ -173,21 +182,58 @@ void main() {
).restart(fullRestart: true); ).restart(fullRestart: true);
expect(result.isOk, false); expect(result.isOk, false);
expect(result.message, 'setupHotRestart failed'); expect(result.message, 'setupHotRestart failed');
expect(failingTestingConfig.updateDevFSCompleteCalled, false);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: false), HotRunnerConfig: () => failingTestingConfig,
Artifacts: () => Artifacts.test(), Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
Platform: () => FakePlatform(operatingSystem: 'linux'), Platform: () => FakePlatform(operatingSystem: 'linux'),
ProcessManager: () => FakeProcessManager.any(), 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', () { group('shutdown hook tests', () {
TestHotRunnerConfig shutdownTestingConfig; TestHotRunnerConfig shutdownTestingConfig;
setUp(() { setUp(() {
shutdownTestingConfig = TestHotRunnerConfig( shutdownTestingConfig = TestHotRunnerConfig();
successfulSetup: true,
);
}); });
testUsingContext('shutdown hook called after signal', () async { testUsingContext('shutdown hook called after signal', () async {
...@@ -235,6 +281,13 @@ void main() { ...@@ -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 { testUsingContext('correctly tracks time spent for analytics for hot restart', () async {
final FakeDevice device = FakeDevice(); final FakeDevice device = FakeDevice();
final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device); final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
...@@ -242,7 +295,7 @@ void main() { ...@@ -242,7 +295,7 @@ void main() {
fakeFlutterDevice, fakeFlutterDevice,
]; ];
fakeFlutterDevice.updateDevFSReport = UpdateFSReport( fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport(
success: true, success: true,
invalidatedSourcesCount: 2, invalidatedSourcesCount: 2,
syncedBytes: 4, syncedBytes: 4,
...@@ -284,15 +337,24 @@ void main() { ...@@ -284,15 +337,24 @@ void main() {
hotEventScannedSourcesCount: 8, hotEventScannedSourcesCount: 8,
)), )),
]); ]);
expect(testingConfig.updateDevFSCompleteCalled, true);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true), HotRunnerConfig: () => testingConfig,
Artifacts: () => Artifacts.test(), Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
Platform: () => FakePlatform(operatingSystem: 'linux'), Platform: () => FakePlatform(operatingSystem: 'linux'),
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
Usage: () => testUsage, Usage: () => testUsage,
}); });
});
group('successful hot reload', () {
TestHotRunnerConfig testingConfig;
setUp(() {
testingConfig = TestHotRunnerConfig(
successfulHotReloadSetup: true,
);
});
testUsingContext('correctly tracks time spent for analytics for hot reload', () async { testUsingContext('correctly tracks time spent for analytics for hot reload', () async {
final FakeDevice device = FakeDevice(); final FakeDevice device = FakeDevice();
final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device); final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
...@@ -300,7 +362,7 @@ void main() { ...@@ -300,7 +362,7 @@ void main() {
fakeFlutterDevice, fakeFlutterDevice,
]; ];
fakeFlutterDevice.updateDevFSReport = UpdateFSReport( fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport(
success: true, success: true,
invalidatedSourcesCount: 6, invalidatedSourcesCount: 6,
syncedBytes: 8, syncedBytes: 8,
...@@ -378,8 +440,9 @@ void main() { ...@@ -378,8 +440,9 @@ void main() {
hotEventReloadVMTimeInMs: 512000, hotEventReloadVMTimeInMs: 512000,
)), )),
]); ]);
expect(testingConfig.updateDevFSCompleteCalled, true);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true), HotRunnerConfig: () => testingConfig,
Artifacts: () => Artifacts.test(), Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
Platform: () => FakePlatform(operatingSystem: 'linux'), Platform: () => FakePlatform(operatingSystem: 'linux'),
...@@ -388,6 +451,75 @@ void main() { ...@@ -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', () { group('hot attach', () {
FileSystem fileSystem; FileSystem fileSystem;
...@@ -420,7 +552,7 @@ void main() { ...@@ -420,7 +552,7 @@ void main() {
); );
expect(exitCode, 2); expect(exitCode, 2);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true), HotRunnerConfig: () => TestHotRunnerConfig(),
Artifacts: () => Artifacts.test(), Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
Platform: () => FakePlatform(operatingSystem: 'linux'), Platform: () => FakePlatform(operatingSystem: 'linux'),
...@@ -519,7 +651,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice { ...@@ -519,7 +651,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
FakeFlutterDevice(this.device); FakeFlutterDevice(this.device);
bool stoppedEchoingDeviceLog = false; bool stoppedEchoingDeviceLog = false;
UpdateFSReport updateDevFSReport; Future<UpdateFSReport> Function() updateDevFSReportCallback;
@override @override
final FakeDevice device; final FakeDevice device;
...@@ -552,7 +684,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice { ...@@ -552,7 +684,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
@required String dillOutputPath, @required String dillOutputPath,
@required List<Uri> invalidatedFiles, @required List<Uri> invalidatedFiles,
@required PackageConfig packageConfig, @required PackageConfig packageConfig,
}) async => updateDevFSReport; }) => updateDevFSReportCallback();
} }
class TestFlutterDevice extends FlutterDevice { class TestFlutterDevice extends FlutterDevice {
...@@ -585,13 +717,27 @@ class TestFlutterDevice extends FlutterDevice { ...@@ -585,13 +717,27 @@ class TestFlutterDevice extends FlutterDevice {
} }
class TestHotRunnerConfig extends HotRunnerConfig { class TestHotRunnerConfig extends HotRunnerConfig {
TestHotRunnerConfig({@required this.successfulSetup}); TestHotRunnerConfig({this.successfulHotRestartSetup, this.successfulHotReloadSetup});
bool successfulSetup; bool successfulHotRestartSetup;
bool successfulHotReloadSetup;
bool shutdownHookCalled = false; bool shutdownHookCalled = false;
bool updateDevFSCompleteCalled = false;
@override @override
Future<bool> setupHotRestart() async { 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 @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