2
2
// Use of this source code is governed by a BSD-style license that can be
3
3
// found in the LICENSE file.
4
4
5
+ import 'dart:async' ;
6
+ import 'dart:io' as io;
7
+
5
8
import 'package:file/memory.dart' ;
6
9
import 'package:flutter_tools/src/application_package.dart' ;
7
10
import 'package:flutter_tools/src/base/common.dart' ;
8
11
import 'package:flutter_tools/src/base/file_system.dart' ;
12
+ import 'package:flutter_tools/src/base/io.dart' ;
9
13
import 'package:flutter_tools/src/base/logger.dart' ;
10
14
import 'package:flutter_tools/src/base/platform.dart' ;
15
+ import 'package:flutter_tools/src/base/signals.dart' ;
11
16
import 'package:flutter_tools/src/build_info.dart' ;
12
17
import 'package:flutter_tools/src/cache.dart' ;
13
18
import 'package:flutter_tools/src/commands/drive.dart' ;
@@ -27,12 +32,14 @@ void main() {
27
32
late BufferLogger logger;
28
33
late Platform platform;
29
34
late FakeDeviceManager fakeDeviceManager;
35
+ late Signals signals;
30
36
31
37
setUp (() {
32
38
fileSystem = MemoryFileSystem .test ();
33
39
logger = BufferLogger .test ();
34
40
platform = FakePlatform ();
35
41
fakeDeviceManager = FakeDeviceManager ();
42
+ signals = FakeSignals ();
36
43
});
37
44
38
45
setUpAll (() {
@@ -44,7 +51,12 @@ void main() {
44
51
});
45
52
46
53
testUsingContext ('warns if screenshot is not supported but continues test' , () async {
47
- final DriveCommand command = DriveCommand (fileSystem: fileSystem, logger: logger, platform: platform);
54
+ final DriveCommand command = DriveCommand (
55
+ fileSystem: fileSystem,
56
+ logger: logger,
57
+ platform: platform,
58
+ signals: signals,
59
+ );
48
60
fileSystem.file ('lib/main.dart' ).createSync (recursive: true );
49
61
fileSystem.file ('test_driver/main_test.dart' ).createSync (recursive: true );
50
62
fileSystem.file ('pubspec.yaml' ).createSync ();
@@ -76,7 +88,12 @@ void main() {
76
88
});
77
89
78
90
testUsingContext ('takes screenshot and rethrows on drive exception' , () async {
79
- final DriveCommand command = DriveCommand (fileSystem: fileSystem, logger: logger, platform: platform);
91
+ final DriveCommand command = DriveCommand (
92
+ fileSystem: fileSystem,
93
+ logger: logger,
94
+ platform: platform,
95
+ signals: signals,
96
+ );
80
97
fileSystem.file ('lib/main.dart' ).createSync (recursive: true );
81
98
fileSystem.file ('test_driver/main_test.dart' ).createSync (recursive: true );
82
99
fileSystem.file ('pubspec.yaml' ).createSync ();
@@ -111,6 +128,7 @@ void main() {
111
128
fileSystem: fileSystem,
112
129
logger: logger,
113
130
platform: platform,
131
+ signals: signals,
114
132
flutterDriverFactory: FailingFakeFlutterDriverFactory (),
115
133
);
116
134
@@ -149,7 +167,13 @@ void main() {
149
167
});
150
168
151
169
testUsingContext ('drive --screenshot errors but does not fail if screenshot fails' , () async {
152
- final DriveCommand command = DriveCommand (fileSystem: fileSystem, logger: logger, platform: platform);
170
+ final DriveCommand command = DriveCommand (
171
+ fileSystem: fileSystem,
172
+ logger: logger,
173
+ platform: platform,
174
+ signals: signals,
175
+ );
176
+
153
177
fileSystem.file ('lib/main.dart' ).createSync (recursive: true );
154
178
fileSystem.file ('test_driver/main_test.dart' ).createSync (recursive: true );
155
179
fileSystem.file ('pubspec.yaml' ).createSync ();
@@ -179,8 +203,71 @@ void main() {
179
203
DeviceManager : () => fakeDeviceManager,
180
204
});
181
205
206
+ testUsingContext ('drive --screenshot takes screenshot if sent a registered signal' , () async {
207
+ final FakeProcessSignal signal = FakeProcessSignal ();
208
+ final ProcessSignal signalUnderTest = ProcessSignal (signal);
209
+ final DriveCommand command = DriveCommand (
210
+ fileSystem: fileSystem,
211
+ logger: logger,
212
+ platform: platform,
213
+ signals: Signals .test (),
214
+ flutterDriverFactory: NeverEndingFlutterDriverFactory (() {
215
+ signal.controller.add (signal);
216
+ }),
217
+ signalsToHandle: < ProcessSignal > {signalUnderTest},
218
+ );
219
+
220
+ fileSystem.file ('lib/main.dart' ).createSync (recursive: true );
221
+ fileSystem.file ('test_driver/main_test.dart' ).createSync (recursive: true );
222
+ fileSystem.file ('pubspec.yaml' ).createSync ();
223
+ fileSystem.directory ('drive_screenshots' ).createSync ();
224
+
225
+ final ScreenshotDevice screenshotDevice = ScreenshotDevice ();
226
+ fakeDeviceManager.devices = < Device > [screenshotDevice];
227
+
228
+ expect (screenshotDevice.screenshots, isEmpty);
229
+
230
+ // This command will never complete. In reality, a real signal would have
231
+ // shut down the Dart process.
232
+ unawaited (
233
+ createTestCommandRunner (command).run (
234
+ < String > [
235
+ 'drive' ,
236
+ '--no-pub' ,
237
+ '-d' ,
238
+ screenshotDevice.id,
239
+ '--use-existing-app' ,
240
+ 'http://localhost:8181' ,
241
+ '--screenshot' ,
242
+ 'drive_screenshots' ,
243
+ ],
244
+ ),
245
+ );
246
+
247
+ await screenshotDevice.firstScreenshot;
248
+ expect (
249
+ screenshotDevice.screenshots,
250
+ contains (isA <File >().having (
251
+ (File file) => file.path,
252
+ 'path' ,
253
+ 'drive_screenshots/drive_01.png' ,
254
+ )),
255
+ );
256
+ }, overrides: < Type , Generator > {
257
+ FileSystem : () => fileSystem,
258
+ ProcessManager : () => FakeProcessManager .any (),
259
+ Pub : () => FakePub (),
260
+ DeviceManager : () => fakeDeviceManager,
261
+ });
262
+
182
263
testUsingContext ('shouldRunPub is true unless user specifies --no-pub' , () async {
183
- final DriveCommand command = DriveCommand (fileSystem: fileSystem, logger: logger, platform: platform);
264
+ final DriveCommand command = DriveCommand (
265
+ fileSystem: fileSystem,
266
+ logger: logger,
267
+ platform: platform,
268
+ signals: signals,
269
+ );
270
+
184
271
fileSystem.file ('lib/main.dart' ).createSync (recursive: true );
185
272
fileSystem.file ('test_driver/main_test.dart' ).createSync (recursive: true );
186
273
fileSystem.file ('pubspec.yaml' ).createSync ();
@@ -207,7 +294,13 @@ void main() {
207
294
});
208
295
209
296
testUsingContext ('flags propagate to debugging options' , () async {
210
- final DriveCommand command = DriveCommand (fileSystem: fileSystem, logger: logger, platform: platform);
297
+ final DriveCommand command = DriveCommand (
298
+ fileSystem: fileSystem,
299
+ logger: logger,
300
+ platform: platform,
301
+ signals: signals,
302
+ );
303
+
211
304
fileSystem.file ('lib/main.dart' ).createSync (recursive: true );
212
305
fileSystem.file ('test_driver/main_test.dart' ).createSync (recursive: true );
213
306
fileSystem.file ('pubspec.yaml' ).createSync ();
@@ -270,6 +363,13 @@ class ThrowingScreenshotDevice extends ScreenshotDevice {
270
363
// Until we fix that, we have to also ignore related lints here.
271
364
// ignore: avoid_implementing_value_types
272
365
class ScreenshotDevice extends Fake implements Device {
366
+ final List <File > screenshots = < File > [];
367
+
368
+ final Completer <void > _firstScreenshotCompleter = Completer <void >();
369
+
370
+ /// A Future that completes when [takeScreenshot] is called the first time.
371
+ Future <void > get firstScreenshot => _firstScreenshotCompleter.future;
372
+
273
373
@override
274
374
final String name = 'FakeDevice' ;
275
375
@@ -299,7 +399,12 @@ class ScreenshotDevice extends Fake implements Device {
299
399
}) async => LaunchResult .succeeded ();
300
400
301
401
@override
302
- Future <void > takeScreenshot (File outputFile) async {}
402
+ Future <void > takeScreenshot (File outputFile) async {
403
+ if (! _firstScreenshotCompleter.isCompleted) {
404
+ _firstScreenshotCompleter.complete ();
405
+ }
406
+ screenshots.add (outputFile);
407
+ }
303
408
}
304
409
305
410
class FakePub extends Fake implements Pub {
@@ -331,6 +436,48 @@ class FakeDeviceManager extends Fake implements DeviceManager {
331
436
Future <List <Device >> findTargetDevices (FlutterProject ? flutterProject, {Duration ? timeout, bool promptUserToChooseDevice = true }) async => devices;
332
437
}
333
438
439
+ /// A [FlutterDriverFactory] that creates a [NeverEndingDriverService] .
440
+ class NeverEndingFlutterDriverFactory extends Fake implements FlutterDriverFactory {
441
+ NeverEndingFlutterDriverFactory (this .callback);
442
+
443
+ final void Function () callback;
444
+
445
+ @override
446
+ DriverService createDriverService (bool web) => NeverEndingDriverService (callback);
447
+ }
448
+
449
+ /// A [DriverService] that will return a Future from [startTest] that will never complete.
450
+ ///
451
+ /// This is to similate when the test will take a long time, but a signal is
452
+ /// expected to interrupt the process.
453
+ class NeverEndingDriverService extends Fake implements DriverService {
454
+ NeverEndingDriverService (this .callback);
455
+
456
+ final void Function () callback;
457
+ @override
458
+ Future <void > reuseApplication (Uri vmServiceUri, Device device, DebuggingOptions debuggingOptions, bool ipv6) async { }
459
+
460
+ @override
461
+ Future <int > startTest (
462
+ String testFile,
463
+ List <String > arguments,
464
+ Map <String , String > environment,
465
+ PackageConfig packageConfig, {
466
+ bool ? headless,
467
+ String ? chromeBinary,
468
+ String ? browserName,
469
+ bool ? androidEmulator,
470
+ int ? driverPort,
471
+ List <String >? webBrowserFlags,
472
+ List <String >? browserDimension,
473
+ String ? profileMemory,
474
+ }) async {
475
+ callback ();
476
+ // return a Future that will never complete.
477
+ return Completer <int >().future;
478
+ }
479
+ }
480
+
334
481
class FailingFakeFlutterDriverFactory extends Fake implements FlutterDriverFactory {
335
482
@override
336
483
DriverService createDriverService (bool web) => FailingFakeDriverService ();
@@ -356,3 +503,10 @@ class FailingFakeDriverService extends Fake implements DriverService {
356
503
String ? profileMemory,
357
504
}) async => 1 ;
358
505
}
506
+
507
+ class FakeProcessSignal extends Fake implements io.ProcessSignal {
508
+ final StreamController <io.ProcessSignal > controller = StreamController <io.ProcessSignal >();
509
+
510
+ @override
511
+ Stream <io.ProcessSignal > watch () => controller.stream;
512
+ }
0 commit comments