23
23
// to the runFlutter function.
24
24
25
25
// @dart = 2.8
26
+ // This file is ready to transition, just uncomment /*?*/, /*!*/, and /*late*/.
26
27
27
28
import 'dart:async' ;
28
29
import 'dart:convert' ;
@@ -113,39 +114,67 @@ class Multiple extends Transition {
113
114
114
115
@override
115
116
String toString () {
117
+ if (_originalPatterns.length == patterns.length) {
118
+ return '${_originalPatterns .map (describe ).join (', ' )} (all matched)' ;
119
+ }
116
120
return '${_originalPatterns .map (describe ).join (', ' )} (matched ${_originalPatterns .length - patterns .length } so far)' ;
117
121
}
118
122
}
119
123
120
- class ProcessTestResult {
121
- const ProcessTestResult (this .exitCode, this .stdout, this .stderr);
122
- final int exitCode;
123
- final List <String > stdout;
124
- final List <String > stderr;
124
+ class LogLine {
125
+ const LogLine (this .channel, this .stamp, this .message);
126
+ final String channel;
127
+ final String stamp;
128
+ final String message;
129
+
130
+ bool get couldBeCrash => message.contains ('Oops; flutter has exited unexpectedly:' );
125
131
126
132
@override
127
- String toString () => 'exit code $exitCode \n stdout:\n ${stdout .join ('\n ' )}\n stderr:\n ${stderr .join ('\n ' )}\n ' ;
128
- }
133
+ String toString () => '$stamp $channel : $message ' ;
129
134
130
- String clarify (String line) {
131
- return line.runes.map <String >((int rune) {
132
- if (rune >= 0x20 && rune <= 0x7F ) {
133
- return String .fromCharCode (rune);
134
- }
135
- switch (rune) {
136
- case 0x00 : return '<NUL>' ;
137
- case 0x07 : return '<BEL>' ;
138
- case 0x08 : return '<TAB>' ;
139
- case 0x09 : return '<BS>' ;
140
- case 0x0A : return '<LF>' ;
141
- case 0x0D : return '<CR>' ;
142
- }
143
- return '<${rune .toRadixString (16 ).padLeft (rune <= 0xFF ? 2 : rune <= 0xFFFF ? 4 : 5 , '0' )}>' ;
144
- }).join ('' );
135
+ void printClearly () {
136
+ print ('$stamp $channel : ${clarify (message )}' );
137
+ }
138
+
139
+ static String clarify (String line) {
140
+ return line.runes.map <String >((int rune) {
141
+ if (rune >= 0x20 && rune <= 0x7F ) {
142
+ return String .fromCharCode (rune);
143
+ }
144
+ switch (rune) {
145
+ case 0x00 : return '<NUL>' ;
146
+ case 0x07 : return '<BEL>' ;
147
+ case 0x08 : return '<TAB>' ;
148
+ case 0x09 : return '<BS>' ;
149
+ case 0x0A : return '<LF>' ;
150
+ case 0x0D : return '<CR>' ;
151
+ }
152
+ return '<${rune .toRadixString (16 ).padLeft (rune <= 0xFF ? 2 : rune <= 0xFFFF ? 4 : 5 , '0' )}>' ;
153
+ }).join ('' );
154
+ }
145
155
}
146
156
147
- void printClearly (String line) {
148
- print (clarify (line));
157
+ class ProcessTestResult {
158
+ const ProcessTestResult (this .exitCode, this .logs);
159
+ final int exitCode;
160
+ final List <LogLine > logs;
161
+
162
+ List <String > get stdout {
163
+ return logs
164
+ .where ((LogLine log) => log.channel == 'stdout' )
165
+ .map <String >((LogLine log) => log.message)
166
+ .toList ();
167
+ }
168
+
169
+ List <String > get stderr {
170
+ return logs
171
+ .where ((LogLine log) => log.channel == 'stderr' )
172
+ .map <String >((LogLine log) => log.message)
173
+ .toList ();
174
+ }
175
+
176
+ @override
177
+ String toString () => 'exit code $exitCode \n logs:\n ${logs .join ('\n ' )}\n ' ;
149
178
}
150
179
151
180
Future <ProcessTestResult > runFlutter (
@@ -156,13 +185,12 @@ Future<ProcessTestResult> runFlutter(
156
185
bool logging = true ,
157
186
Duration expectedMaxDuration = const Duration (seconds: 25 ), // must be less than test timeout of 30 seconds!
158
187
}) async {
188
+ final Stopwatch clock = Stopwatch ()..start ();
159
189
final Process process = await processManager.start (
160
190
< String > [flutterBin, ...arguments],
161
191
workingDirectory: workingDirectory,
162
192
);
163
- final List <String > stdoutLog = < String > [];
164
- final List <String > stderrLog = < String > [];
165
- final List <String > stdinLog = < String > [];
193
+ final List <LogLine > logs = < LogLine > [];
166
194
int nextTransition = 0 ;
167
195
void describeStatus () {
168
196
if (transitions.isNotEmpty) {
@@ -175,13 +203,13 @@ Future<ProcessTestResult> runFlutter(
175
203
' ' } ${transitions [index ]}' );
176
204
}
177
205
}
178
- if (stdoutLog.isEmpty && stderrLog.isEmpty && stdinLog .isEmpty) {
206
+ if (logs .isEmpty) {
179
207
print ('So far nothing has been logged${ debug ? "" : "; use debug:true to print all output" }.' );
180
208
} else {
181
209
print ('Log${ debug ? "" : " (only contains logged lines; use debug:true to print all output)" }:' );
182
- stdoutLog. map < String >(( String line) => 'stdout: $ line ' ). forEach (printClearly);
183
- stderrLog. map < String >(( String line) => 'stderr: $ line ' ). forEach ( printClearly);
184
- stdinLog. map < String >(( String line) => 'stdin: $ line ' ). forEach (printClearly);
210
+ for ( final LogLine log in logs) {
211
+ log. printClearly ( );
212
+ }
185
213
}
186
214
}
187
215
bool streamingLogs = false ;
@@ -190,28 +218,30 @@ Future<ProcessTestResult> runFlutter(
190
218
if (! streamingLogs) {
191
219
streamingLogs = true ;
192
220
if (! debug) {
193
- print ('Test is taking a long time.' );
221
+ print ('Test is taking a long time (${ clock . elapsed . inSeconds } seconds so far) .' );
194
222
}
195
223
describeStatus ();
196
224
print ('(streaming all logs from this point on...)' );
197
225
} else {
198
226
print ('(taking a long time...)' );
199
227
}
200
228
}
229
+ String stamp () => '[${(clock .elapsed .inMilliseconds / 1000.0 ).toStringAsFixed (1 ).padLeft (5 , " " )}s]' ;
201
230
void processStdout (String line) {
231
+ final LogLine log = LogLine ('stdout' , stamp (), line);
202
232
if (logging) {
203
- stdoutLog .add (line );
233
+ logs .add (log );
204
234
}
205
235
if (streamingLogs) {
206
- print ( 'stdout: $ line ' );
236
+ log. printClearly ( );
207
237
}
208
238
if (nextTransition < transitions.length && transitions[nextTransition].matches (line)) {
209
239
if (streamingLogs) {
210
240
print ('(matched ${transitions [nextTransition ]})' );
211
241
}
212
242
if (transitions[nextTransition].logging != null ) {
213
243
if (! logging && transitions[nextTransition].logging/*!*/ ) {
214
- stdoutLog .add (line );
244
+ logs .add (log );
215
245
}
216
246
logging = transitions[nextTransition].logging/*!*/ ;
217
247
if (streamingLogs) {
@@ -225,9 +255,10 @@ Future<ProcessTestResult> runFlutter(
225
255
if (transitions[nextTransition].handler != null ) {
226
256
final String /*?*/ command = transitions[nextTransition].handler/*!*/ (line);
227
257
if (command != null ) {
228
- stdinLog.add (command);
258
+ final LogLine inLog = LogLine ('stdin' , stamp (), command);
259
+ logs.add (inLog);
229
260
if (streamingLogs) {
230
- print ( 'stdin: $ command ' );
261
+ inLog. printClearly ( );
231
262
}
232
263
process.stdin.write (command);
233
264
}
@@ -238,9 +269,10 @@ Future<ProcessTestResult> runFlutter(
238
269
}
239
270
}
240
271
void processStderr (String line) {
241
- stderrLog.add (line);
272
+ final LogLine log = LogLine ('stdout' , stamp (), line);
273
+ logs.add (log);
242
274
if (streamingLogs) {
243
- print ( 'stderr: $ line ' );
275
+ log. printClearly ( );
244
276
}
245
277
}
246
278
if (debug) {
@@ -251,19 +283,24 @@ Future<ProcessTestResult> runFlutter(
251
283
process.stdout.transform <String >(utf8.decoder).transform <String >(const LineSplitter ()).listen (processStdout);
252
284
process.stderr.transform <String >(utf8.decoder).transform <String >(const LineSplitter ()).listen (processStderr);
253
285
unawaited (process.exitCode.timeout (expectedMaxDuration, onTimeout: () {
254
- print ('(process is not quitting, trying to send a "q" just in case that helps)' );
286
+ print ('${ stamp ()} (process is not quitting, trying to send a "q" just in case that helps)' );
255
287
print ('(a functional test should never reach this point)' );
288
+ final LogLine inLog = LogLine ('stdin' , stamp (), 'q' );
289
+ logs.add (inLog);
290
+ if (streamingLogs) {
291
+ inLog.printClearly ();
292
+ }
256
293
process.stdin.write ('q' );
257
- return null ;
294
+ return - 1 ; // discarded
258
295
}).catchError ((Object error) { /* ignore the error here, it'll be reported on the next line */ }));
259
296
final int exitCode = await process.exitCode;
260
297
if (streamingLogs) {
261
- print ('(process terminated with exit code $exitCode )' );
298
+ print ('${ stamp ()} (process terminated with exit code $exitCode )' );
262
299
}
263
300
timeout? .cancel ();
264
301
if (nextTransition < transitions.length) {
265
302
print ('The subprocess terminated before all the expected transitions had been matched.' );
266
- if (stderrLog .any ((String line) => line.contains ( 'Oops; flutter has exited unexpectedly:' ) )) {
303
+ if (logs .any ((LogLine line) => line.couldBeCrash )) {
267
304
print ('The subprocess may in fact have crashed. Check the stderr logs below.' );
268
305
}
269
306
print ('The transition that we were hoping to see next but that we never saw was:' );
@@ -275,9 +312,9 @@ Future<ProcessTestResult> runFlutter(
275
312
throw TestFailure ('Missed some expected transitions.' );
276
313
}
277
314
if (streamingLogs) {
278
- print ('(completed execution successfully!)' );
315
+ print ('${ stamp ()} (completed execution successfully!)' );
279
316
}
280
- return ProcessTestResult (exitCode, stdoutLog, stderrLog );
317
+ return ProcessTestResult (exitCode, logs );
281
318
}
282
319
283
320
const int progressMessageWidth = 64 ;
0 commit comments