| // 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 'dart:async'; |
| |
| import 'package:file/file.dart'; |
| import 'package:flutter_tools/src/base/common.dart'; |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:vm_service/vm_service.dart'; |
| |
| import '../src/common.dart'; |
| import 'test_data/hot_reload_project.dart'; |
| import 'test_driver.dart'; |
| import 'test_utils.dart'; |
| |
| void main() { |
| Directory tempDir; |
| final HotReloadProject _project = HotReloadProject(); |
| FlutterRunTestDriver _flutter; |
| |
| setUp(() async { |
| tempDir = createResolvedTempDirectorySync('hot_reload_test.'); |
| await _project.setUpIn(tempDir); |
| _flutter = FlutterRunTestDriver(tempDir); |
| }); |
| |
| tearDown(() async { |
| await _flutter?.stop(); |
| tryToDelete(tempDir); |
| }); |
| |
| test('hot reload works without error', () async { |
| await _flutter.run(); |
| await _flutter.hotReload(); |
| }); |
| |
| test('newly added code executes during hot reload', () async { |
| await _flutter.run(); |
| _project.uncommentHotReloadPrint(); |
| final StringBuffer stdout = StringBuffer(); |
| final StreamSubscription<String> subscription = _flutter.stdout.listen(stdout.writeln); |
| try { |
| await _flutter.hotReload(); |
| expect(stdout.toString(), contains('(((((RELOAD WORKED)))))')); |
| } finally { |
| await subscription.cancel(); |
| } |
| }); |
| |
| test('reloadMethod triggers hot reload behavior', () async { |
| await _flutter.run(); |
| _project.uncommentHotReloadPrint(); |
| final StringBuffer stdout = StringBuffer(); |
| final StreamSubscription<String> subscription = _flutter.stdout.listen(stdout.writeln); |
| try { |
| final String libraryId = _project.buildBreakpointUri.toString(); |
| await _flutter.reloadMethod(libraryId: libraryId, classId: 'MyApp'); |
| // reloadMethod does not wait for the next frame, to allow scheduling a new |
| // update while the previous update was pending. |
| await Future<void>.delayed(const Duration(seconds: 1)); |
| expect(stdout.toString(), contains('(((((RELOAD WORKED)))))')); |
| } finally { |
| await subscription.cancel(); |
| } |
| }); |
| |
| test('hot restart works without error', () async { |
| await _flutter.run(); |
| await _flutter.hotRestart(); |
| }); |
| |
| test('breakpoints are hit after hot reload', () async { |
| Isolate isolate; |
| await _flutter.run(withDebugger: true, startPaused: true); |
| final Completer<void> sawTick1 = Completer<void>(); |
| final Completer<void> sawTick3 = Completer<void>(); |
| final Completer<void> sawDebuggerPausedMessage = Completer<void>(); |
| final StreamSubscription<String> subscription = _flutter.stdout.listen( |
| (String line) { |
| if (line.contains('((((TICK 1))))')) { |
| expect(sawTick1.isCompleted, isFalse); |
| sawTick1.complete(); |
| } |
| if (line.contains('((((TICK 3))))')) { |
| expect(sawTick3.isCompleted, isFalse); |
| sawTick3.complete(); |
| } |
| if (line.contains('The application is paused in the debugger on a breakpoint.')) { |
| expect(sawDebuggerPausedMessage.isCompleted, isFalse); |
| sawDebuggerPausedMessage.complete(); |
| } |
| }, |
| ); |
| await _flutter.resume(); // we start paused so we can set up our TICK 1 listener before the app starts |
| unawaited(sawTick1.future.timeout( |
| const Duration(seconds: 5), |
| onTimeout: () { print('The test app is taking longer than expected to print its synchronization line...'); }, |
| )); |
| await sawTick1.future; // after this, app is in steady state |
| await _flutter.addBreakpoint( |
| _project.scheduledBreakpointUri, |
| _project.scheduledBreakpointLine, |
| ); |
| await _flutter.hotReload(); // reload triggers code which eventually hits the breakpoint |
| isolate = await _flutter.waitForPause(); |
| expect(isolate.pauseEvent.kind, equals(EventKind.kPauseBreakpoint)); |
| await _flutter.resume(); |
| await _flutter.addBreakpoint( |
| _project.buildBreakpointUri, |
| _project.buildBreakpointLine, |
| ); |
| bool reloaded = false; |
| final Future<void> reloadFuture = _flutter.hotReload().then((void value) { reloaded = true; }); |
| await sawTick3.future; // this should happen before it pauses |
| isolate = await _flutter.waitForPause(); |
| expect(isolate.pauseEvent.kind, equals(EventKind.kPauseBreakpoint)); |
| await sawDebuggerPausedMessage.future; |
| expect(reloaded, isFalse); |
| await _flutter.resume(); |
| await reloadFuture; |
| expect(reloaded, isTrue); |
| reloaded = false; |
| await subscription.cancel(); |
| }); |
| |
| test('hot reload doesn\'t reassemble if paused', () async { |
| await _flutter.run(withDebugger: true); |
| final Completer<void> sawTick2 = Completer<void>(); |
| final Completer<void> sawTick3 = Completer<void>(); |
| final Completer<void> sawDebuggerPausedMessage1 = Completer<void>(); |
| final Completer<void> sawDebuggerPausedMessage2 = Completer<void>(); |
| final StreamSubscription<String> subscription = _flutter.stdout.listen( |
| (String line) { |
| if (line.contains('((((TICK 2))))')) { |
| expect(sawTick2.isCompleted, isFalse); |
| sawTick2.complete(); |
| } |
| if (line.contains('The application is paused in the debugger on a breakpoint.')) { |
| expect(sawDebuggerPausedMessage1.isCompleted, isFalse); |
| sawDebuggerPausedMessage1.complete(); |
| } |
| if (line.contains('The application is paused in the debugger on a breakpoint; interface might not update.')) { |
| expect(sawDebuggerPausedMessage2.isCompleted, isFalse); |
| sawDebuggerPausedMessage2.complete(); |
| } |
| }, |
| ); |
| await _flutter.addBreakpoint( |
| _project.buildBreakpointUri, |
| _project.buildBreakpointLine, |
| ); |
| bool reloaded = false; |
| final Future<void> reloadFuture = _flutter.hotReload().then((void value) { reloaded = true; }); |
| await sawTick2.future; // this should happen before it pauses |
| final Isolate isolate = await _flutter.waitForPause(); |
| expect(isolate.pauseEvent.kind, equals(EventKind.kPauseBreakpoint)); |
| expect(reloaded, isFalse); |
| await sawDebuggerPausedMessage1.future; // this is the one where it say "uh, you broke into the debugger while reloading" |
| await reloadFuture; // this is the one where it times out because you're in the debugger |
| expect(reloaded, isTrue); |
| await _flutter.hotReload(); // now we're already paused |
| expect(sawTick3.isCompleted, isFalse); |
| await sawDebuggerPausedMessage2.future; // so we just get told that nothing is going to happen |
| await _flutter.resume(); |
| await subscription.cancel(); |
| }); |
| } |