1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// 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 'dart:async';
import 'package:file/file.dart';
import 'package:flutter_tools/src/base/common.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);
});
testWithoutContext('hot reload works without error', () async {
await flutter.run();
await flutter.hotReload();
});
testWithoutContext('multiple overlapping hot reload are debounced and queued', () async {
await flutter.run();
// Capture how many *real* hot reloads occur.
int numReloads = 0;
final StreamSubscription<void> subscription = flutter.stdout
.map(parseFlutterResponse)
.where(_isHotReloadCompletionEvent)
.listen((_) => numReloads++);
// To reduce tests flaking, override the debounce timer to something higher than
// the default to ensure the hot reloads that are supposed to arrive within the
// debounce period will even on slower CI machines.
const int hotReloadDebounceOverrideMs = 250;
const Duration delay = Duration(milliseconds: hotReloadDebounceOverrideMs * 2);
Future<void> doReload([void _]) =>
flutter.hotReload(debounce: true, debounceDurationOverrideMs: hotReloadDebounceOverrideMs);
try {
await Future.wait<void>(<Future<void>>[
doReload(),
doReload(),
Future<void>.delayed(delay).then(doReload),
Future<void>.delayed(delay).then(doReload),
]);
// We should only get two reloads, as the first two will have been
// merged together by the debounce, and the second two also.
expect(numReloads, equals(2));
} finally {
await subscription.cancel();
}
});
testWithoutContext('newly added code executes during hot reload', () async {
final StringBuffer stdout = StringBuffer();
final StreamSubscription<String> subscription = flutter.stdout.listen(stdout.writeln);
await flutter.run();
project.uncommentHotReloadPrint();
try {
await flutter.hotReload();
expect(stdout.toString(), contains('(((((RELOAD WORKED)))))'));
} finally {
await subscription.cancel();
}
});
testWithoutContext('hot restart works without error', () async {
await flutter.run();
await flutter.hotRestart();
});
testWithoutContext('breakpoints are hit after hot reload', () async {
Isolate isolate;
final Completer<void> sawTick1 = 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('The application is paused in the debugger on a breakpoint.')) {
expect(sawDebuggerPausedMessage.isCompleted, isFalse);
sawDebuggerPausedMessage.complete();
}
},
);
await flutter.run(withDebugger: true, startPaused: true);
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 Future<void>.delayed(const Duration(seconds: 2));
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; });
print('waiting for pause...');
isolate = await flutter.waitForPause();
expect(isolate.pauseEvent.kind, equals(EventKind.kPauseBreakpoint));
print('waiting for debugger message...');
await sawDebuggerPausedMessage.future;
expect(reloaded, isFalse);
print('waiting for resume...');
await flutter.resume();
print('waiting for reload future...');
await reloadFuture;
expect(reloaded, isTrue);
reloaded = false;
print('subscription cancel...');
await subscription.cancel();
});
testWithoutContext("hot reload doesn't reassemble if paused", () async {
final Completer<void> sawTick1 = Completer<void>();
final Completer<void> sawDebuggerPausedMessage1 = Completer<void>();
final Completer<void> sawDebuggerPausedMessage2 = Completer<void>();
final StreamSubscription<String> subscription = flutter.stdout.listen(
(String line) {
print('[LOG]:"$line"');
if (line.contains('(((TICK 1)))')) {
expect(sawTick1.isCompleted, isFalse);
sawTick1.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.run(withDebugger: true);
await Future<void>.delayed(const Duration(seconds: 1));
await sawTick1.future;
await flutter.addBreakpoint(
project.buildBreakpointUri,
project.buildBreakpointLine,
);
bool reloaded = false;
await Future<void>.delayed(const Duration(seconds: 1));
final Future<void> reloadFuture = flutter.hotReload().then((void value) { reloaded = true; });
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
await sawDebuggerPausedMessage2.future; // so we just get told that nothing is going to happen
await flutter.resume();
await subscription.cancel();
});
}
bool _isHotReloadCompletionEvent(Map<String, dynamic> event) {
return event != null &&
event['event'] == 'app.progress' &&
event['params'] != null &&
event['params']['progressId'] == 'hot.reload' &&
event['params']['finished'] == true;
}