22// Use of this source code is governed by a BSD-style license that can be
33// found in the LICENSE file.
44
5+ import 'dart:async' ;
6+ import 'dart:io' as io;
7+
58import 'package:file/memory.dart' ;
69import 'package:flutter_tools/src/application_package.dart' ;
710import 'package:flutter_tools/src/base/common.dart' ;
811import 'package:flutter_tools/src/base/file_system.dart' ;
12+ import 'package:flutter_tools/src/base/io.dart' ;
913import 'package:flutter_tools/src/base/logger.dart' ;
1014import 'package:flutter_tools/src/base/platform.dart' ;
15+ import 'package:flutter_tools/src/base/signals.dart' ;
1116import 'package:flutter_tools/src/build_info.dart' ;
1217import 'package:flutter_tools/src/cache.dart' ;
1318import 'package:flutter_tools/src/commands/drive.dart' ;
@@ -27,12 +32,14 @@ void main() {
2732 late BufferLogger logger;
2833 late Platform platform;
2934 late FakeDeviceManager fakeDeviceManager;
35+ late Signals signals;
3036
3137 setUp (() {
3238 fileSystem = MemoryFileSystem .test ();
3339 logger = BufferLogger .test ();
3440 platform = FakePlatform ();
3541 fakeDeviceManager = FakeDeviceManager ();
42+ signals = FakeSignals ();
3643 });
3744
3845 setUpAll (() {
@@ -44,7 +51,12 @@ void main() {
4451 });
4552
4653 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+ );
4860 fileSystem.file ('lib/main.dart' ).createSync (recursive: true );
4961 fileSystem.file ('test_driver/main_test.dart' ).createSync (recursive: true );
5062 fileSystem.file ('pubspec.yaml' ).createSync ();
@@ -76,7 +88,12 @@ void main() {
7688 });
7789
7890 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+ );
8097 fileSystem.file ('lib/main.dart' ).createSync (recursive: true );
8198 fileSystem.file ('test_driver/main_test.dart' ).createSync (recursive: true );
8299 fileSystem.file ('pubspec.yaml' ).createSync ();
@@ -111,6 +128,7 @@ void main() {
111128 fileSystem: fileSystem,
112129 logger: logger,
113130 platform: platform,
131+ signals: signals,
114132 flutterDriverFactory: FailingFakeFlutterDriverFactory (),
115133 );
116134
@@ -149,7 +167,13 @@ void main() {
149167 });
150168
151169 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+
153177 fileSystem.file ('lib/main.dart' ).createSync (recursive: true );
154178 fileSystem.file ('test_driver/main_test.dart' ).createSync (recursive: true );
155179 fileSystem.file ('pubspec.yaml' ).createSync ();
@@ -179,8 +203,71 @@ void main() {
179203 DeviceManager : () => fakeDeviceManager,
180204 });
181205
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+
182263 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+
184271 fileSystem.file ('lib/main.dart' ).createSync (recursive: true );
185272 fileSystem.file ('test_driver/main_test.dart' ).createSync (recursive: true );
186273 fileSystem.file ('pubspec.yaml' ).createSync ();
@@ -207,7 +294,13 @@ void main() {
207294 });
208295
209296 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+
211304 fileSystem.file ('lib/main.dart' ).createSync (recursive: true );
212305 fileSystem.file ('test_driver/main_test.dart' ).createSync (recursive: true );
213306 fileSystem.file ('pubspec.yaml' ).createSync ();
@@ -270,6 +363,13 @@ class ThrowingScreenshotDevice extends ScreenshotDevice {
270363// Until we fix that, we have to also ignore related lints here.
271364// ignore: avoid_implementing_value_types
272365class 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+
273373 @override
274374 final String name = 'FakeDevice' ;
275375
@@ -299,7 +399,12 @@ class ScreenshotDevice extends Fake implements Device {
299399 }) async => LaunchResult .succeeded ();
300400
301401 @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+ }
303408}
304409
305410class FakePub extends Fake implements Pub {
@@ -331,6 +436,48 @@ class FakeDeviceManager extends Fake implements DeviceManager {
331436 Future <List <Device >> findTargetDevices (FlutterProject ? flutterProject, {Duration ? timeout, bool promptUserToChooseDevice = true }) async => devices;
332437}
333438
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+
334481class FailingFakeFlutterDriverFactory extends Fake implements FlutterDriverFactory {
335482 @override
336483 DriverService createDriverService (bool web) => FailingFakeDriverService ();
@@ -356,3 +503,10 @@ class FailingFakeDriverService extends Fake implements DriverService {
356503 String ? profileMemory,
357504 }) async => 1 ;
358505}
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