Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
d40f6d1e
Unverified
Commit
d40f6d1e
authored
Oct 27, 2022
by
Christopher Fujino
Committed by
GitHub
Oct 27, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[flutter_tools] allow flutter drive to take screenshots when sent a terminating signal (#114118)
parent
80d4e5a0
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
193 additions
and
7 deletions
+193
-7
executable.dart
packages/flutter_tools/lib/executable.dart
+1
-0
drive.dart
packages/flutter_tools/lib/src/commands/drive.dart
+32
-1
drive_test.dart
...lutter_tools/test/commands.shard/hermetic/drive_test.dart
+160
-6
No files found.
packages/flutter_tools/lib/executable.dart
View file @
d40f6d1e
...
...
@@ -189,6 +189,7 @@ List<FlutterCommand> generateCommands({
fileSystem:
globals
.
fs
,
logger:
globals
.
logger
,
platform:
globals
.
platform
,
signals:
globals
.
signals
,
),
EmulatorsCommand
(),
FormatCommand
(
verboseHelp:
verboseHelp
),
...
...
packages/flutter_tools/lib/src/commands/drive.dart
View file @
d40f6d1e
...
...
@@ -12,8 +12,10 @@ import '../application_package.dart';
import
'../artifacts.dart'
;
import
'../base/common.dart'
;
import
'../base/file_system.dart'
;
import
'../base/io.dart'
;
import
'../base/logger.dart'
;
import
'../base/platform.dart'
;
import
'../base/signals.dart'
;
import
'../build_info.dart'
;
import
'../dart/package_map.dart'
;
import
'../device.dart'
;
...
...
@@ -48,9 +50,11 @@ class DriveCommand extends RunCommandBase {
DriveCommand
({
bool
verboseHelp
=
false
,
@visibleForTesting
FlutterDriverFactory
?
flutterDriverFactory
,
@visibleForTesting
this
.
signalsToHandle
=
const
<
ProcessSignal
>{
ProcessSignal
.
sigint
,
ProcessSignal
.
sigterm
},
required
FileSystem
fileSystem
,
required
Logger
?
logger
,
required
Platform
platform
,
required
this
.
signals
,
})
:
_flutterDriverFactory
=
flutterDriverFactory
,
_fileSystem
=
fileSystem
,
_logger
=
logger
,
...
...
@@ -149,6 +153,11 @@ class DriveCommand extends RunCommandBase {
valueHelp:
'profile_memory.json'
);
}
final
Signals
signals
;
/// The [ProcessSignal]s that will lead to a screenshot being taken (if the option is provided).
final
Set
<
ProcessSignal
>
signalsToHandle
;
// `pub` must always be run due to the test script running from source,
// even if an application binary is used. Default to true unless the user explicitly
// specified not to.
...
...
@@ -270,7 +279,7 @@ class DriveCommand extends RunCommandBase {
);
}
final
int
testResult
=
await
driverService
.
startTest
(
final
Future
<
int
>
testResultFuture
=
driverService
.
startTest
(
testFile
,
stringsArg
(
'test-arguments'
),
<
String
,
String
>{},
...
...
@@ -286,6 +295,13 @@ class DriveCommand extends RunCommandBase {
androidEmulator:
boolArgDeprecated
(
'android-emulator'
),
profileMemory:
stringArgDeprecated
(
'profile-memory'
),
);
// If the test is sent a signal, take a screenshot before exiting
final
Map
<
ProcessSignal
,
Object
>
screenshotTokens
=
_registerScreenshotCallbacks
((
ProcessSignal
signal
)
async
{
_logger
!.
printError
(
'Caught
$signal
'
);
await
_takeScreenshot
(
device
);
});
final
int
testResult
=
await
testResultFuture
;
_unregisterScreenshotCallbacks
(
screenshotTokens
);
if
(
testResult
!=
0
&&
screenshot
!=
null
)
{
// Take a screenshot while the app is still running.
await
_takeScreenshot
(
device
);
...
...
@@ -315,6 +331,21 @@ class DriveCommand extends RunCommandBase {
return
FlutterCommandResult
.
success
();
}
Map
<
ProcessSignal
,
Object
>
_registerScreenshotCallbacks
(
Function
(
ProcessSignal
)
callback
)
{
_logger
!.
printTrace
(
'Registering signal handlers...'
);
final
Map
<
ProcessSignal
,
Object
>
tokens
=
<
ProcessSignal
,
Object
>{};
for
(
final
ProcessSignal
signal
in
signalsToHandle
)
{
tokens
[
signal
]
=
signals
.
addHandler
(
signal
,
callback
);
}
return
tokens
;
}
void
_unregisterScreenshotCallbacks
(
Map
<
ProcessSignal
,
Object
>
tokens
)
{
_logger
!.
printTrace
(
'Unregistering signal handlers...'
);
for
(
final
MapEntry
<
ProcessSignal
,
Object
>
entry
in
tokens
.
entries
)
{
signals
.
removeHandler
(
entry
.
key
,
entry
.
value
);
}
}
String
?
_getTestFile
()
{
if
(
argResults
![
'driver'
]
!=
null
)
{
return
stringArgDeprecated
(
'driver'
);
...
...
packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
View file @
d40f6d1e
...
...
@@ -2,12 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'dart:io'
as
io
;
import
'package:file/memory.dart'
;
import
'package:flutter_tools/src/application_package.dart'
;
import
'package:flutter_tools/src/base/common.dart'
;
import
'package:flutter_tools/src/base/file_system.dart'
;
import
'package:flutter_tools/src/base/io.dart'
;
import
'package:flutter_tools/src/base/logger.dart'
;
import
'package:flutter_tools/src/base/platform.dart'
;
import
'package:flutter_tools/src/base/signals.dart'
;
import
'package:flutter_tools/src/build_info.dart'
;
import
'package:flutter_tools/src/cache.dart'
;
import
'package:flutter_tools/src/commands/drive.dart'
;
...
...
@@ -27,12 +32,14 @@ void main() {
late
BufferLogger
logger
;
late
Platform
platform
;
late
FakeDeviceManager
fakeDeviceManager
;
late
Signals
signals
;
setUp
(()
{
fileSystem
=
MemoryFileSystem
.
test
();
logger
=
BufferLogger
.
test
();
platform
=
FakePlatform
();
fakeDeviceManager
=
FakeDeviceManager
();
signals
=
FakeSignals
();
});
setUpAll
(()
{
...
...
@@ -44,7 +51,12 @@ void main() {
});
testUsingContext
(
'warns if screenshot is not supported but continues test'
,
()
async
{
final
DriveCommand
command
=
DriveCommand
(
fileSystem:
fileSystem
,
logger:
logger
,
platform:
platform
);
final
DriveCommand
command
=
DriveCommand
(
fileSystem:
fileSystem
,
logger:
logger
,
platform:
platform
,
signals:
signals
,
);
fileSystem
.
file
(
'lib/main.dart'
).
createSync
(
recursive:
true
);
fileSystem
.
file
(
'test_driver/main_test.dart'
).
createSync
(
recursive:
true
);
fileSystem
.
file
(
'pubspec.yaml'
).
createSync
();
...
...
@@ -76,7 +88,12 @@ void main() {
});
testUsingContext
(
'takes screenshot and rethrows on drive exception'
,
()
async
{
final
DriveCommand
command
=
DriveCommand
(
fileSystem:
fileSystem
,
logger:
logger
,
platform:
platform
);
final
DriveCommand
command
=
DriveCommand
(
fileSystem:
fileSystem
,
logger:
logger
,
platform:
platform
,
signals:
signals
,
);
fileSystem
.
file
(
'lib/main.dart'
).
createSync
(
recursive:
true
);
fileSystem
.
file
(
'test_driver/main_test.dart'
).
createSync
(
recursive:
true
);
fileSystem
.
file
(
'pubspec.yaml'
).
createSync
();
...
...
@@ -111,6 +128,7 @@ void main() {
fileSystem:
fileSystem
,
logger:
logger
,
platform:
platform
,
signals:
signals
,
flutterDriverFactory:
FailingFakeFlutterDriverFactory
(),
);
...
...
@@ -149,7 +167,13 @@ void main() {
});
testUsingContext
(
'drive --screenshot errors but does not fail if screenshot fails'
,
()
async
{
final
DriveCommand
command
=
DriveCommand
(
fileSystem:
fileSystem
,
logger:
logger
,
platform:
platform
);
final
DriveCommand
command
=
DriveCommand
(
fileSystem:
fileSystem
,
logger:
logger
,
platform:
platform
,
signals:
signals
,
);
fileSystem
.
file
(
'lib/main.dart'
).
createSync
(
recursive:
true
);
fileSystem
.
file
(
'test_driver/main_test.dart'
).
createSync
(
recursive:
true
);
fileSystem
.
file
(
'pubspec.yaml'
).
createSync
();
...
...
@@ -179,8 +203,71 @@ void main() {
DeviceManager:
()
=>
fakeDeviceManager
,
});
testUsingContext
(
'drive --screenshot takes screenshot if sent a registered signal'
,
()
async
{
final
FakeProcessSignal
signal
=
FakeProcessSignal
();
final
ProcessSignal
signalUnderTest
=
ProcessSignal
(
signal
);
final
DriveCommand
command
=
DriveCommand
(
fileSystem:
fileSystem
,
logger:
logger
,
platform:
platform
,
signals:
Signals
.
test
(),
flutterDriverFactory:
NeverEndingFlutterDriverFactory
(()
{
signal
.
controller
.
add
(
signal
);
}),
signalsToHandle:
<
ProcessSignal
>{
signalUnderTest
},
);
fileSystem
.
file
(
'lib/main.dart'
).
createSync
(
recursive:
true
);
fileSystem
.
file
(
'test_driver/main_test.dart'
).
createSync
(
recursive:
true
);
fileSystem
.
file
(
'pubspec.yaml'
).
createSync
();
fileSystem
.
directory
(
'drive_screenshots'
).
createSync
();
final
ScreenshotDevice
screenshotDevice
=
ScreenshotDevice
();
fakeDeviceManager
.
devices
=
<
Device
>[
screenshotDevice
];
expect
(
screenshotDevice
.
screenshots
,
isEmpty
);
// This command will never complete. In reality, a real signal would have
// shut down the Dart process.
unawaited
(
createTestCommandRunner
(
command
).
run
(
<
String
>[
'drive'
,
'--no-pub'
,
'-d'
,
screenshotDevice
.
id
,
'--use-existing-app'
,
'http://localhost:8181'
,
'--screenshot'
,
'drive_screenshots'
,
],
),
);
await
screenshotDevice
.
firstScreenshot
;
expect
(
screenshotDevice
.
screenshots
,
contains
(
isA
<
File
>().
having
(
(
File
file
)
=>
file
.
path
,
'path'
,
'drive_screenshots/drive_01.png'
,
)),
);
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
fileSystem
,
ProcessManager:
()
=>
FakeProcessManager
.
any
(),
Pub:
()
=>
FakePub
(),
DeviceManager:
()
=>
fakeDeviceManager
,
});
testUsingContext
(
'shouldRunPub is true unless user specifies --no-pub'
,
()
async
{
final
DriveCommand
command
=
DriveCommand
(
fileSystem:
fileSystem
,
logger:
logger
,
platform:
platform
);
final
DriveCommand
command
=
DriveCommand
(
fileSystem:
fileSystem
,
logger:
logger
,
platform:
platform
,
signals:
signals
,
);
fileSystem
.
file
(
'lib/main.dart'
).
createSync
(
recursive:
true
);
fileSystem
.
file
(
'test_driver/main_test.dart'
).
createSync
(
recursive:
true
);
fileSystem
.
file
(
'pubspec.yaml'
).
createSync
();
...
...
@@ -207,7 +294,13 @@ void main() {
});
testUsingContext
(
'flags propagate to debugging options'
,
()
async
{
final
DriveCommand
command
=
DriveCommand
(
fileSystem:
fileSystem
,
logger:
logger
,
platform:
platform
);
final
DriveCommand
command
=
DriveCommand
(
fileSystem:
fileSystem
,
logger:
logger
,
platform:
platform
,
signals:
signals
,
);
fileSystem
.
file
(
'lib/main.dart'
).
createSync
(
recursive:
true
);
fileSystem
.
file
(
'test_driver/main_test.dart'
).
createSync
(
recursive:
true
);
fileSystem
.
file
(
'pubspec.yaml'
).
createSync
();
...
...
@@ -270,6 +363,13 @@ class ThrowingScreenshotDevice extends ScreenshotDevice {
// Until we fix that, we have to also ignore related lints here.
// ignore: avoid_implementing_value_types
class
ScreenshotDevice
extends
Fake
implements
Device
{
final
List
<
File
>
screenshots
=
<
File
>[];
final
Completer
<
void
>
_firstScreenshotCompleter
=
Completer
<
void
>();
/// A Future that completes when [takeScreenshot] is called the first time.
Future
<
void
>
get
firstScreenshot
=>
_firstScreenshotCompleter
.
future
;
@override
final
String
name
=
'FakeDevice'
;
...
...
@@ -299,7 +399,12 @@ class ScreenshotDevice extends Fake implements Device {
})
async
=>
LaunchResult
.
succeeded
();
@override
Future
<
void
>
takeScreenshot
(
File
outputFile
)
async
{}
Future
<
void
>
takeScreenshot
(
File
outputFile
)
async
{
if
(!
_firstScreenshotCompleter
.
isCompleted
)
{
_firstScreenshotCompleter
.
complete
();
}
screenshots
.
add
(
outputFile
);
}
}
class
FakePub
extends
Fake
implements
Pub
{
...
...
@@ -331,6 +436,48 @@ class FakeDeviceManager extends Fake implements DeviceManager {
Future
<
List
<
Device
>>
findTargetDevices
(
FlutterProject
?
flutterProject
,
{
Duration
?
timeout
,
bool
promptUserToChooseDevice
=
true
})
async
=>
devices
;
}
/// A [FlutterDriverFactory] that creates a [NeverEndingDriverService].
class
NeverEndingFlutterDriverFactory
extends
Fake
implements
FlutterDriverFactory
{
NeverEndingFlutterDriverFactory
(
this
.
callback
);
final
void
Function
()
callback
;
@override
DriverService
createDriverService
(
bool
web
)
=>
NeverEndingDriverService
(
callback
);
}
/// A [DriverService] that will return a Future from [startTest] that will never complete.
///
/// This is to similate when the test will take a long time, but a signal is
/// expected to interrupt the process.
class
NeverEndingDriverService
extends
Fake
implements
DriverService
{
NeverEndingDriverService
(
this
.
callback
);
final
void
Function
()
callback
;
@override
Future
<
void
>
reuseApplication
(
Uri
vmServiceUri
,
Device
device
,
DebuggingOptions
debuggingOptions
,
bool
ipv6
)
async
{
}
@override
Future
<
int
>
startTest
(
String
testFile
,
List
<
String
>
arguments
,
Map
<
String
,
String
>
environment
,
PackageConfig
packageConfig
,
{
bool
?
headless
,
String
?
chromeBinary
,
String
?
browserName
,
bool
?
androidEmulator
,
int
?
driverPort
,
List
<
String
>?
webBrowserFlags
,
List
<
String
>?
browserDimension
,
String
?
profileMemory
,
})
async
{
callback
();
// return a Future that will never complete.
return
Completer
<
int
>().
future
;
}
}
class
FailingFakeFlutterDriverFactory
extends
Fake
implements
FlutterDriverFactory
{
@override
DriverService
createDriverService
(
bool
web
)
=>
FailingFakeDriverService
();
...
...
@@ -356,3 +503,10 @@ class FailingFakeDriverService extends Fake implements DriverService {
String
?
profileMemory
,
})
async
=>
1
;
}
class
FakeProcessSignal
extends
Fake
implements
io
.
ProcessSignal
{
final
StreamController
<
io
.
ProcessSignal
>
controller
=
StreamController
<
io
.
ProcessSignal
>();
@override
Stream
<
io
.
ProcessSignal
>
watch
()
=>
controller
.
stream
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment