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
9e55af52
Unverified
Commit
9e55af52
authored
Feb 17, 2021
by
Jia Hao
Committed by
GitHub
Feb 17, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[flutter_tools] Decouple FlutterPlatform from Process (#74236)
parent
6a8ba743
Changes
11
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
878 additions
and
686 deletions
+878
-686
coverage_collector.dart
packages/flutter_tools/lib/src/test/coverage_collector.dart
+30
-22
event_printer.dart
packages/flutter_tools/lib/src/test/event_printer.dart
+11
-9
flutter_platform.dart
packages/flutter_tools/lib/src/test/flutter_platform.dart
+104
-404
flutter_tester_device.dart
...ges/flutter_tools/lib/src/test/flutter_tester_device.dart
+343
-0
font_config_manager.dart
packages/flutter_tools/lib/src/test/font_config_manager.dart
+48
-0
test_device.dart
packages/flutter_tools/lib/src/test/test_device.dart
+45
-0
watcher.dart
packages/flutter_tools/lib/src/test/watcher.dart
+12
-28
flutter_platform_test.dart
...utter_tools/test/general.shard/flutter_platform_test.dart
+8
-214
flutter_tester_device_test.dart
..._tools/test/general.shard/flutter_tester_device_test.dart
+265
-0
event_printer_test.dart
...ter_tools/test/general.shard/test/event_printer_test.dart
+10
-7
test_test.dart
packages/flutter_tools/test/integration.shard/test_test.dart
+2
-2
No files found.
packages/flutter_tools/lib/src/test/coverage_collector.dart
View file @
9e55af52
...
...
@@ -15,6 +15,7 @@ import '../base/utils.dart';
import
'../globals.dart'
as
globals
;
import
'../vmservice.dart'
;
import
'test_device.dart'
;
import
'watcher.dart'
;
/// A class that's used to collect coverage data during tests.
...
...
@@ -27,9 +28,9 @@ class CoverageCollector extends TestWatcher {
bool
Function
(
String
)
libraryPredicate
;
@override
Future
<
void
>
handleFinishedTest
(
ProcessEvent
event
)
async
{
_logMessage
(
'
test
${event.childIndex}
: collecting coverage
'
);
await
collectCoverage
(
event
.
process
,
event
.
observatoryUri
);
Future
<
void
>
handleFinishedTest
(
TestDevice
testDevice
)
async
{
_logMessage
(
'
Starting coverage collection
'
);
await
collectCoverage
(
testDevice
);
}
void
_logMessage
(
String
line
,
{
bool
error
=
false
})
{
...
...
@@ -81,34 +82,41 @@ class CoverageCollector extends TestWatcher {
/// has been run to completion so that all coverage data has been recorded.
///
/// The returned [Future] completes when the coverage is collected.
Future
<
void
>
collectCoverage
(
Process
process
,
Uri
observatoryUri
)
async
{
assert
(
process
!=
null
);
assert
(
observatoryUri
!=
null
);
final
int
pid
=
process
.
pid
;
_logMessage
(
'pid
$pid
: collecting coverage data from
$observatoryUri
...'
);
Future
<
void
>
collectCoverage
(
TestDevice
testDevice
)
async
{
assert
(
testDevice
!=
null
);
Map
<
String
,
dynamic
>
data
;
final
Future
<
void
>
processComplete
=
process
.
exitCode
.
then
<
void
>((
int
code
)
{
throw
Exception
(
'Failed to collect coverage, process terminated prematurely with exit code
$code
.'
);
});
final
Future
<
void
>
collectionComplete
=
collect
(
observatoryUri
,
libraryPredicate
)
final
Future
<
void
>
processComplete
=
testDevice
.
finished
.
catchError
(
(
Object
error
)
=>
throw
Exception
(
'Failed to collect coverage, test device terminated prematurely with '
'error:
${(error as TestDeviceException).message}
.'
),
test:
(
Object
error
)
=>
error
is
TestDeviceException
,
);
final
Future
<
void
>
collectionComplete
=
testDevice
.
observatoryUri
.
then
((
Uri
observatoryUri
)
{
_logMessage
(
'collecting coverage data from
$testDevice
at
$observatoryUri
...'
);
return
collect
(
observatoryUri
,
libraryPredicate
)
.
then
<
void
>((
Map
<
String
,
dynamic
>
result
)
{
if
(
result
==
null
)
{
throw
Exception
(
'Failed to collect coverage.'
);
}
_logMessage
(
'Collected coverage data.'
);
data
=
result
;
});
});
await
Future
.
any
<
void
>(<
Future
<
void
>>[
processComplete
,
collectionComplete
]);
assert
(
data
!=
null
);
_logMessage
(
'
pid
$pid
(
$observatoryUri
): collected coverage data; merging
...'
);
_logMessage
(
'
Merging coverage data
...'
);
_addHitmap
(
await
coverage
.
createHitmap
(
data
[
'coverage'
]
as
List
<
Map
<
String
,
dynamic
>>,
packagesPath:
packagesPath
,
checkIgnoredLines:
true
,
));
_logMessage
(
'
pid
$pid
(
$observatoryUri
): d
one merging coverage data into global coverage map.'
);
_logMessage
(
'
D
one merging coverage data into global coverage map.'
);
}
/// Returns a future that will complete with the formatted coverage data
...
...
@@ -188,10 +196,10 @@ class CoverageCollector extends TestWatcher {
}
@override
Future
<
void
>
handleTestCrashed
(
ProcessEvent
event
)
async
{
}
Future
<
void
>
handleTestCrashed
(
TestDevice
testDevice
)
async
{
}
@override
Future
<
void
>
handleTestTimedOut
(
ProcessEvent
event
)
async
{
}
Future
<
void
>
handleTestTimedOut
(
TestDevice
testDevice
)
async
{
}
}
Future
<
vm_service
.
VmService
>
_defaultConnect
(
Uri
serviceUri
)
{
...
...
packages/flutter_tools/lib/src/test/event_printer.dart
View file @
9e55af52
...
...
@@ -6,6 +6,8 @@
import
'../convert.dart'
;
import
'../globals.dart'
as
globals
;
import
'test_device.dart'
;
import
'watcher.dart'
;
/// Prints JSON events when running a test in --machine mode.
...
...
@@ -18,25 +20,25 @@ class EventPrinter extends TestWatcher {
final
TestWatcher
_parent
;
@override
void
handleStarted
Process
(
ProcessEvent
event
)
{
void
handleStarted
Device
(
Uri
observatoryUri
)
{
_sendEvent
(
'test.startedProcess'
,
<
String
,
dynamic
>{
'observatoryUri'
:
event
.
observatoryUri
.
toString
()});
_parent
?.
handleStarted
Process
(
event
);
<
String
,
dynamic
>{
'observatoryUri'
:
observatoryUri
.
toString
()});
_parent
?.
handleStarted
Device
(
observatoryUri
);
}
@override
Future
<
void
>
handleTestCrashed
(
ProcessEvent
event
)
async
{
return
_parent
?.
handleTestCrashed
(
event
);
Future
<
void
>
handleTestCrashed
(
TestDevice
testDevice
)
async
{
return
_parent
?.
handleTestCrashed
(
testDevice
);
}
@override
Future
<
void
>
handleTestTimedOut
(
ProcessEvent
event
)
async
{
return
_parent
?.
handleTestTimedOut
(
event
);
Future
<
void
>
handleTestTimedOut
(
TestDevice
testDevice
)
async
{
return
_parent
?.
handleTestTimedOut
(
testDevice
);
}
@override
Future
<
void
>
handleFinishedTest
(
ProcessEvent
event
)
async
{
return
_parent
?.
handleFinishedTest
(
event
);
Future
<
void
>
handleFinishedTest
(
TestDevice
testDevice
)
async
{
return
_parent
?.
handleFinishedTest
(
testDevice
);
}
void
_sendEvent
(
String
name
,
[
dynamic
params
])
{
...
...
packages/flutter_tools/lib/src/test/flutter_platform.dart
View file @
9e55af52
...
...
@@ -5,14 +5,11 @@
// @dart = 2.8
import
'dart:async'
;
import
'dart:io'
as
io
;
// ignore: dart_io_import;
import
'package:dds/dds.dart'
;
import
'package:meta/meta.dart'
;
import
'package:package_config/package_config.dart'
;
import
'package:stream_channel/stream_channel.dart'
;
import
'package:test_core/src/platform.dart'
;
// ignore: implementation_imports
import
'package:vm_service/vm_service.dart'
as
vm_service
;
import
'../base/common.dart'
;
import
'../base/file_system.dart'
;
...
...
@@ -24,9 +21,12 @@ import '../device.dart';
import
'../globals.dart'
as
globals
;
import
'../project.dart'
;
import
'../test/test_wrapper.dart'
;
import
'../vmservice.dart'
;
import
'flutter_tester_device.dart'
;
import
'font_config_manager.dart'
;
import
'test_compiler.dart'
;
import
'test_config.dart'
;
import
'test_device.dart'
;
import
'watcher.dart'
;
/// The address at which our WebSocket server resides and at which the sky_shell
...
...
@@ -50,7 +50,6 @@ FlutterPlatform installHook({
TestWatcher
watcher
,
bool
enableObservatory
=
false
,
bool
machine
=
false
,
int
port
=
0
,
String
precompiledDillPath
,
Map
<
String
,
String
>
precompiledDillFiles
,
bool
updateGoldens
=
false
,
...
...
@@ -80,7 +79,6 @@ FlutterPlatform installHook({
machine:
machine
,
enableObservatory:
enableObservatory
,
host:
_kHosts
[
serverType
],
port:
port
,
precompiledDillPath:
precompiledDillPath
,
precompiledDillFiles:
precompiledDillFiles
,
updateGoldens:
updateGoldens
,
...
...
@@ -178,17 +176,16 @@ void catchIsolateErrors() {
});
}
void
main
(
)
{
String
serverPort
=
Platform
.
environment
[
'SERVER_PORT'
]
??
''
;
String
server
=
Uri
.
decodeComponent
(
'
$encodedWebsocketUrl
:
\
$serverPort
'
);
StreamChannel
<
dynamic
>
c
hannel
=
serializeSuite
(()
{
StreamChannel
<
dynamic
>
testC
hannel
=
serializeSuite
(()
{
catchIsolateErrors
();
''');
if (flutterTestDep) {
buffer.write('''
goldenFileComparator
=
LocalFileComparator
(
Uri
.
parse
(
'
$testUrl
'
));
autoUpdateGoldenFiles
=
$updateGoldens
;
goldenFileComparator
=
LocalFileComparator
(
Uri
.
parse
(
'
$testUrl
'
));
autoUpdateGoldenFiles
=
$updateGoldens
;
''');
}
if (testConfigFile != null) {
...
...
@@ -203,18 +200,17 @@ autoUpdateGoldenFiles = $updateGoldens;
buffer.write('''
});
WebSocket
.
connect
(
server
).
then
((
WebSocket
socket
)
{
socket
.
map
((
dynamic
x
)
{
return
json
.
decode
(
x
as
String
);
}).
pipe
(
channel
.
sink
);
socket
.
addStream
(
channel
.
stream
.
map
(
json
.
encode
));
socket
.
map
((
dynamic
message
)
{
// We're only communicating with string encoded JSON.
return
json
.
decode
(
message
as
String
);
}).
pipe
(
testChannel
.
sink
);
socket
.
addStream
(
testChannel
.
stream
.
map
(
json
.
encode
));
});
}
''');
return buffer.toString();
}
enum _TestHarnessStatus { testerCrashed, finished }
typedef Finalizer = Future<void> Function();
/// The flutter test platform used to integrate with package:test.
...
...
@@ -226,7 +222,6 @@ class FlutterPlatform extends PlatformPlugin {
this.enableObservatory,
this.machine,
this.host,
this.port,
this.precompiledDillPath,
this.precompiledDillFiles,
this.updateGoldens,
...
...
@@ -242,7 +237,6 @@ class FlutterPlatform extends PlatformPlugin {
final bool enableObservatory;
final bool machine;
final InternetAddress host;
final int port;
final String precompiledDillPath;
final Map<String, String> precompiledDillFiles;
final bool updateGoldens;
...
...
@@ -251,7 +245,7 @@ class FlutterPlatform extends PlatformPlugin {
final FlutterProject flutterProject;
final String icudtlPath;
Directory fontsDirectory
;
final FontConfigManager _fontConfigManager = FontConfigManager()
;
/// The test compiler produces dill files for each test main.
///
...
...
@@ -297,6 +291,7 @@ class FlutterPlatform extends PlatformPlugin {
throwToolExit
(
'installHook() was called with a precompiled test entry-point, but then more than one test suite was run.'
);
}
}
final
int
ourTestCount
=
_testCount
;
_testCount
+=
1
;
final
StreamController
<
dynamic
>
localController
=
StreamController
<
dynamic
>();
...
...
@@ -339,16 +334,29 @@ class FlutterPlatform extends PlatformPlugin {
throw
'Failed to compile
$expression
'
;
}
/// Binds an [HttpServer] serving from `host` on `port`.
///
/// Only intended to be overridden in tests for [FlutterPlatform].
@protected
@visibleForTesting
Future
<
HttpServer
>
bind
(
InternetAddress
host
,
int
port
)
=>
HttpServer
.
bind
(
host
,
port
);
TestDevice
_createTestDevice
(
int
ourTestCount
)
{
return
FlutterTesterTestDevice
(
id:
ourTestCount
,
platform:
globals
.
platform
,
fileSystem:
globals
.
fs
,
processManager:
globals
.
processManager
,
logger:
globals
.
logger
,
shellPath:
shellPath
,
enableObservatory:
enableObservatory
,
machine:
machine
,
debuggingOptions:
debuggingOptions
,
host:
host
,
buildTestAssets:
buildTestAssets
,
flutterProject:
flutterProject
,
icudtlPath:
icudtlPath
,
compileExpression:
_compileExpressionService
,
fontConfigManager:
_fontConfigManager
);
}
Future
<
_AsyncError
>
_startTest
(
String
testPath
,
StreamChannel
<
dynamic
>
controller
,
StreamChannel
<
dynamic
>
testHarnessChannel
,
int
ourTestCount
,
)
async
{
globals
.
printTrace
(
'test
$ourTestCount
: starting test
$testPath
'
);
...
...
@@ -356,42 +364,13 @@ class FlutterPlatform extends PlatformPlugin {
_AsyncError
outOfBandError
;
// error that we couldn't send to the harness that we need to send via our future
final
List
<
Finalizer
>
finalizers
=
<
Finalizer
>[];
// Will be run in reverse order.
bool
subprocessActive
=
false
;
bool
controllerSinkClosed
=
false
;
try
{
// Callback can't throw since it's just setting a variable.
unawaited
(
controller
.
sink
.
done
.
whenComplete
(()
{
unawaited
(
testHarnessChannel
.
sink
.
done
.
whenComplete
(()
{
controllerSinkClosed
=
true
;
}));
// Prepare our WebSocket server to talk to the engine subprocess.
final
HttpServer
server
=
await
bind
(
host
,
port
);
finalizers
.
add
(()
async
{
globals
.
printTrace
(
'test
$ourTestCount
: shutting down test harness socket server'
);
await
server
.
close
(
force:
true
);
});
final
Completer
<
WebSocket
>
webSocket
=
Completer
<
WebSocket
>();
server
.
listen
(
(
HttpRequest
request
)
{
if
(!
webSocket
.
isCompleted
)
{
webSocket
.
complete
(
WebSocketTransformer
.
upgrade
(
request
));
}
},
onError:
(
dynamic
error
,
StackTrace
stack
)
{
// If you reach here, it's unlikely we're going to be able to really handle this well.
globals
.
printTrace
(
'test
$ourTestCount
: test harness socket server experienced an unexpected error:
$error
'
);
if
(!
controllerSinkClosed
)
{
controller
.
sink
.
addError
(
error
,
stack
);
controller
.
sink
.
close
();
}
else
{
globals
.
printError
(
'unexpected error from test harness socket server:
$error
'
);
}
},
cancelOnError:
true
,
);
globals
.
printTrace
(
'test
$ourTestCount
: starting shell process'
);
// If a kernel file is given, then use that to launch the test.
// If mapping is provided, look kernel file from mapping.
// If all fails, create a "listener" dart that invokes actual test.
...
...
@@ -401,7 +380,7 @@ class FlutterPlatform extends PlatformPlugin {
}
else
if
(
precompiledDillFiles
!=
null
)
{
mainDart
=
precompiledDillFiles
[
testPath
];
}
mainDart
??=
_createListenerDart
(
finalizers
,
ourTestCount
,
testPath
,
server
);
mainDart
??=
_createListenerDart
(
finalizers
,
ourTestCount
,
testPath
);
if
(
precompiledDillPath
==
null
&&
precompiledDillFiles
==
null
)
{
// Lazily instantiate compiler so it is built only if it is actually used.
...
...
@@ -409,141 +388,66 @@ class FlutterPlatform extends PlatformPlugin {
mainDart
=
await
compiler
.
compile
(
globals
.
fs
.
file
(
mainDart
).
uri
);
if
(
mainDart
==
null
)
{
controller
.
sink
.
addError
(
_getErrorMessage
(
'Compilation failed'
,
testPath
,
shellPath
)
);
testHarnessChannel
.
sink
.
addError
(
'Compilation failed for testPath=
$testPath
'
);
return
null
;
}
}
final
Process
process
=
await
_startProcess
(
shellPath
,
mainDart
,
enableObservatory:
enableObservatory
,
serverPort:
server
.
po
rt
,
globals
.
printTrace
(
'test
$ourTestCount
: starting test device'
);
final
TestDevice
testDevice
=
_createTestDevice
(
ourTestCount
);
final
Future
<
StreamChannel
<
String
>>
remoteChannelFuture
=
testDevice
.
start
(
compiledEntrypointPath:
mainDa
rt
,
);
subprocessActive
=
true
;
finalizers
.
add
(()
async
{
if
(
subprocessActive
)
{
globals
.
printTrace
(
'test
$ourTestCount
: ensuring end-of-process for shell'
);
process
.
kill
(
io
.
ProcessSignal
.
sigkill
);
final
int
exitCode
=
await
process
.
exitCode
;
subprocessActive
=
false
;
if
(!
controllerSinkClosed
&&
exitCode
!=
-
9
)
{
// ProcessSignal.SIGKILL
// We expect SIGKILL (9) because we tried to terminate it.
// It's negative because signals are returned as negative exit codes.
final
String
message
=
_getErrorMessage
(
_getExitCodeMessage
(
exitCode
),
testPath
,
shellPath
);
controller
.
sink
.
addError
(
message
);
}
}
globals
.
printTrace
(
'test
$ourTestCount
: ensuring test device is terminated.'
);
await
testDevice
.
kill
();
});
final
Completer
<
Uri
>
gotProcessObservatoryUri
=
Completer
<
Uri
>();
if
(!
enableObservatory
)
{
gotProcessObservatoryUri
.
complete
();
}
// Pipe stdout and stderr from the subprocess to our printStatus console.
// We also keep track of what observatory port the engine used, if any.
final
Uri
ddsServiceUri
=
getDdsServiceUri
();
_pipeStandardStreamsToConsole
(
process
,
reportObservatoryUri:
(
Uri
detectedUri
)
async
{
assert
(!
gotProcessObservatoryUri
.
isCompleted
);
assert
(
debuggingOptions
.
hostVmServicePort
==
null
||
debuggingOptions
.
hostVmServicePort
==
detectedUri
.
port
);
Uri
forwardingUri
;
if
(!
debuggingOptions
.
disableDds
)
{
final
DartDevelopmentService
dds
=
await
DartDevelopmentService
.
startDartDevelopmentService
(
detectedUri
,
serviceUri:
ddsServiceUri
,
enableAuthCodes:
!
debuggingOptions
.
disableServiceAuthCodes
,
ipv6:
host
.
type
==
InternetAddressType
.
IPv6
,
);
forwardingUri
=
dds
.
uri
;
globals
.
printTrace
(
'Dart Development Service started at
${dds.uri}
, forwarding to VM service at
${dds.remoteVmServiceUri}
.'
);
}
else
{
forwardingUri
=
detectedUri
;
}
{
globals
.
printTrace
(
'Connecting to service protocol:
$forwardingUri
'
);
final
Future
<
vm_service
.
VmService
>
localVmService
=
connectToVmService
(
forwardingUri
,
compileExpression:
_compileExpressionService
);
unawaited
(
localVmService
.
then
((
vm_service
.
VmService
vmservice
)
{
globals
.
printTrace
(
'Successfully connected to service protocol:
$forwardingUri
'
);
}));
}
if
(
debuggingOptions
.
startPaused
&&
!
machine
)
{
globals
.
printStatus
(
'The test process has been started.'
);
globals
.
printStatus
(
'You can now connect to it using observatory. To connect, load the following Web site in your browser:'
);
globals
.
printStatus
(
'
$forwardingUri
'
);
globals
.
printStatus
(
'You should first set appropriate breakpoints, then resume the test in the debugger.'
);
}
gotProcessObservatoryUri
.
complete
(
forwardingUri
);
},
);
// At this point, three things can happen next:
// The engine could crash, in which case process.exitCode will complete.
// The engine could connect to us, in which case webSocket.future will complete.
// The local test harness could get bored of us.
globals
.
printTrace
(
'test
$ourTestCount
: awaiting connection to result for test process at pid
${process.pid}
'
);
final
_TestHarnessStatus
testHarnessStatus
=
await
Future
.
any
<
_TestHarnessStatus
>(<
Future
<
_TestHarnessStatus
>>[
process
.
exitCode
.
then
<
_TestHarnessStatus
>((
int
exitCode
)
=>
_TestHarnessStatus
.
testerCrashed
),
gotProcessObservatoryUri
.
future
.
then
<
_TestHarnessStatus
>((
Uri
processObservatoryUri
)
{
// At this point, these things can happen:
// A. The test device could crash, in which case [testDevice.finished]
// will complete.
// B. The test device could connect to us, in which case
// [remoteChannelFuture] will complete.
globals
.
printTrace
(
'test
$ourTestCount
: awaiting connection to test device'
);
await
Future
.
any
<
void
>(<
Future
<
void
>>[
testDevice
.
finished
,
()
async
{
final
Uri
processObservatoryUri
=
await
testDevice
.
observatoryUri
;
if
(
processObservatoryUri
!=
null
)
{
globals
.
printTrace
(
'test
$ourTestCount
: Observatory uri is available at
$processObservatoryUri
'
);
}
watcher
?.
handleStartedProcess
(
ProcessEvent
(
ourTestCount
,
process
,
processObservatoryUri
));
return
webSocket
.
future
.
then
<
_TestHarnessStatus
>((
WebSocket
remoteSocket
)
async
{
globals
.
printTrace
(
'test
$ourTestCount
: connected to test harness, now awaiting test result'
);
await
_controlTests
(
controller:
controller
,
remoteSocket:
remoteSocket
,
onError:
(
dynamic
error
,
StackTrace
stackTrace
)
{
// If you reach here, it's unlikely we're going to be able to really handle this well.
globals
.
printError
(
'test:
$testPath
\n
error:
$error
'
);
if
(!
controllerSinkClosed
)
{
controller
.
sink
.
addError
(
error
,
stackTrace
);
controller
.
sink
.
close
();
}
else
{
globals
.
printError
(
'unexpected error:
$error
'
);
}
globals
.
printTrace
(
'test
$ourTestCount
: Observatory uri is not available'
);
}
watcher
?.
handleStartedDevice
(
processObservatoryUri
);
final
StreamChannel
<
String
>
remoteChannel
=
await
remoteChannelFuture
;
globals
.
printTrace
(
'test
$ourTestCount
: connected to test device, now awaiting test result'
);
await
_pipeHarnessToRemote
(
id:
ourTestCount
,
harnessChannel:
testHarnessChannel
,
remoteChannel:
remoteChannel
,
);
await
watcher
?.
handleFinishedTest
(
ProcessEvent
(
ourTestCount
,
process
,
processObservatoryUri
));
return
_TestHarnessStatus
.
finished
;
});
})
globals
.
printTrace
(
'test
$ourTestCount
: finished'
);
await
watcher
?.
handleFinishedTest
(
testDevice
);
}()
]);
if
(
testHarnessStatus
==
_TestHarnessStatus
.
testerCrashed
)
{
globals
.
printTrace
(
'test
$ourTestCount
: process with pid
${process.pid}
crashed'
);
final
int
exitCode
=
await
process
.
exitCode
;
subprocessActive
=
false
;
final
String
message
=
_getErrorMessage
(
_getExitCodeMessage
(
exitCode
),
testPath
,
shellPath
);
controller
.
sink
.
addError
(
message
);
// Awaited for with 'sink.done' below in `finally`.
unawaited
(
controller
.
sink
.
close
());
globals
.
printTrace
(
'test
$ourTestCount
: waiting for controller sink to close'
);
await
controller
.
sink
.
done
;
await
watcher
?.
handleTestCrashed
(
ProcessEvent
(
ourTestCount
,
process
));
}
on
Exception
catch
(
error
,
stackTrace
)
{
Object
reportedError
=
error
;
StackTrace
reportedStackTrace
=
stackTrace
;
if
(
error
is
TestDeviceException
)
{
reportedError
=
error
.
message
;
reportedStackTrace
=
error
.
stackTrace
;
}
}
on
Exception
catch
(
error
,
stack
)
{
globals
.
printTrace
(
'test
$ourTestCount
: error caught during test;
${controllerSinkClosed ? "reporting to console" : "sending to test framework"}
'
);
if
(!
controllerSinkClosed
)
{
controller
.
sink
.
addError
(
error
,
stack
);
testHarnessChannel
.
sink
.
addError
(
reportedError
,
reportedStackTrace
);
}
else
{
globals
.
printError
(
'unhandled error during test:
\n
$testPath
\n
$
error
\n
$stack
'
);
outOfBandError
??=
_AsyncError
(
error
,
stack
);
globals
.
printError
(
'unhandled error during test:
\n
$testPath
\n
$
reportedError
\n
$reportedStackTrace
'
);
outOfBandError
??=
_AsyncError
(
reportedError
,
reportedStackTrace
);
}
}
finally
{
globals
.
printTrace
(
'test
$ourTestCount
: cleaning up...'
);
...
...
@@ -554,7 +458,7 @@ class FlutterPlatform extends PlatformPlugin {
}
on
Exception
catch
(
error
,
stack
)
{
globals
.
printTrace
(
'test
$ourTestCount
: error while cleaning up;
${controllerSinkClosed ? "reporting to console" : "sending to test framework"}
'
);
if
(!
controllerSinkClosed
)
{
controller
.
sink
.
addError
(
error
,
stack
);
testHarnessChannel
.
sink
.
addError
(
error
,
stack
);
}
else
{
globals
.
printError
(
'unhandled error during finalization of test:
\n
$testPath
\n
$error
\n
$stack
'
);
outOfBandError
??=
_AsyncError
(
error
,
stack
);
...
...
@@ -563,12 +467,11 @@ class FlutterPlatform extends PlatformPlugin {
}
if
(!
controllerSinkClosed
)
{
// Waiting below with await.
unawaited
(
controller
.
sink
.
close
());
unawaited
(
testHarnessChannel
.
sink
.
close
());
globals
.
printTrace
(
'test
$ourTestCount
: waiting for controller sink to close'
);
await
controller
.
sink
.
done
;
await
testHarnessChannel
.
sink
.
done
;
}
}
assert
(!
subprocessActive
);
assert
(
controllerSinkClosed
);
if
(
outOfBandError
!=
null
)
{
globals
.
printTrace
(
'test
$ourTestCount
: finished with out-of-band failure'
);
...
...
@@ -582,7 +485,6 @@ class FlutterPlatform extends PlatformPlugin {
List
<
Finalizer
>
finalizers
,
int
ourTestCount
,
String
testPath
,
HttpServer
server
,
)
{
// Prepare a temporary directory to store the Dart file that will talk to us.
final
Directory
tempDir
=
globals
.
fs
.
systemTempDirectory
.
createTempSync
(
'flutter_test_listener.'
);
...
...
@@ -621,184 +523,13 @@ class FlutterPlatform extends PlatformPlugin {
);
}
File
_cachedFontConfig
;
@override
Future
<
dynamic
>
close
()
async
{
if
(
compiler
!=
null
)
{
await
compiler
.
dispose
();
compiler
=
null
;
}
if
(
fontsDirectory
!=
null
)
{
globals
.
printTrace
(
'Deleting
${fontsDirectory.path}
...'
);
fontsDirectory
.
deleteSync
(
recursive:
true
);
fontsDirectory
=
null
;
}
}
/// Returns a Fontconfig config file that limits font fallback to the
/// artifact cache directory.
File
get
_fontConfigFile
{
if
(
_cachedFontConfig
!=
null
)
{
return
_cachedFontConfig
;
}
final
StringBuffer
sb
=
StringBuffer
();
sb
.
writeln
(
'<fontconfig>'
);
sb
.
writeln
(
' <dir>
${globals.cache.getCacheArtifacts().path}
</dir>'
);
sb
.
writeln
(
' <cachedir>/var/cache/fontconfig</cachedir>'
);
sb
.
writeln
(
'</fontconfig>'
);
if
(
fontsDirectory
==
null
)
{
fontsDirectory
=
globals
.
fs
.
systemTempDirectory
.
createTempSync
(
'flutter_test_fonts.'
);
globals
.
printTrace
(
'Using this directory for fonts configuration:
${fontsDirectory.path}
'
);
}
_cachedFontConfig
=
globals
.
fs
.
file
(
'
${fontsDirectory.path}
/fonts.conf'
);
_cachedFontConfig
.
createSync
();
_cachedFontConfig
.
writeAsStringSync
(
sb
.
toString
());
return
_cachedFontConfig
;
}
Future
<
Process
>
_startProcess
(
String
executable
,
String
testPath
,
{
bool
enableObservatory
=
false
,
int
serverPort
,
})
{
assert
(
executable
!=
null
);
// Please provide the path to the shell in the SKY_SHELL environment variable.
assert
(!
debuggingOptions
.
startPaused
||
enableObservatory
);
final
int
observatoryPort
=
debuggingOptions
.
disableDds
?
debuggingOptions
.
hostVmServicePort
:
0
;
final
List
<
String
>
command
=
<
String
>[
executable
,
if
(
enableObservatory
)
...<
String
>[
// Some systems drive the _FlutterPlatform class in an unusual way, where
// only one test file is processed at a time, and the operating
// environment hands out specific ports ahead of time in a cooperative
// manner, where we're only allowed to open ports that were given to us in
// advance like this. For those esoteric systems, we have this feature
// whereby you can create _FlutterPlatform with a pair of ports.
//
// I mention this only so that you won't be tempted, as I was, to apply
// the obvious simplification to this code and remove this entire feature.
if
(
observatoryPort
!=
null
)
'--observatory-port=
$observatoryPort
'
,
if
(
debuggingOptions
.
startPaused
)
'--start-paused'
,
if
(
debuggingOptions
.
disableServiceAuthCodes
)
'--disable-service-auth-codes'
,
]
else
'--disable-observatory'
,
if
(
host
.
type
==
InternetAddressType
.
IPv6
)
'--ipv6'
,
if
(
icudtlPath
!=
null
)
'--icu-data-file-path=
$icudtlPath
'
,
'--enable-checked-mode'
,
'--verify-entry-points'
,
'--enable-software-rendering'
,
'--skia-deterministic-rendering'
,
'--enable-dart-profiling'
,
'--non-interactive'
,
'--use-test-fonts'
,
'--packages=
${debuggingOptions.buildInfo.packagesPath}
'
,
if
(
debuggingOptions
.
nullAssertions
)
'--dart-flags=--null_assertions'
,
...
debuggingOptions
.
dartEntrypointArgs
,
testPath
,
];
globals
.
printTrace
(
command
.
join
(
' '
));
// If the FLUTTER_TEST environment variable has been set, then pass it on
// for package:flutter_test to handle the value.
//
// If FLUTTER_TEST has not been set, assume from this context that this
// call was invoked by the command 'flutter test'.
final
String
flutterTest
=
globals
.
platform
.
environment
.
containsKey
(
'FLUTTER_TEST'
)
?
globals
.
platform
.
environment
[
'FLUTTER_TEST'
]
:
'true'
;
final
Map
<
String
,
String
>
environment
=
<
String
,
String
>{
'FLUTTER_TEST'
:
flutterTest
,
'FONTCONFIG_FILE'
:
_fontConfigFile
.
path
,
'SERVER_PORT'
:
serverPort
.
toString
(),
'APP_NAME'
:
flutterProject
?.
manifest
?.
appName
??
''
,
if
(
buildTestAssets
)
'UNIT_TEST_ASSETS'
:
globals
.
fs
.
path
.
join
(
flutterProject
?.
directory
?.
path
??
''
,
'build'
,
'unit_test_assets'
),
};
return
globals
.
processManager
.
start
(
command
,
environment:
environment
);
}
void
_pipeStandardStreamsToConsole
(
Process
process
,
{
Future
<
void
>
reportObservatoryUri
(
Uri
uri
),
})
{
const
String
observatoryString
=
'Observatory listening on '
;
for
(
final
Stream
<
List
<
int
>>
stream
in
<
Stream
<
List
<
int
>>>[
process
.
stderr
,
process
.
stdout
,
])
{
stream
.
transform
<
String
>(
utf8
.
decoder
)
.
transform
<
String
>(
const
LineSplitter
())
.
listen
(
(
String
line
)
async
{
if
(
line
.
startsWith
(
"error: Unable to read Dart source 'package:test/"
))
{
globals
.
printTrace
(
'Shell:
$line
'
);
globals
.
printError
(
'
\n\n
Failed to load test harness. Are you missing a dependency on flutter_test?
\n
'
);
}
else
if
(
line
.
startsWith
(
observatoryString
))
{
globals
.
printTrace
(
'Shell:
$line
'
);
try
{
final
Uri
uri
=
Uri
.
parse
(
line
.
substring
(
observatoryString
.
length
));
if
(
reportObservatoryUri
!=
null
)
{
await
reportObservatoryUri
(
uri
);
}
}
on
Exception
catch
(
error
)
{
globals
.
printError
(
'Could not parse shell observatory port message:
$error
'
);
}
}
else
if
(
line
!=
null
)
{
globals
.
printStatus
(
'Shell:
$line
'
);
}
},
onError:
(
dynamic
error
)
{
globals
.
printError
(
'shell console stream for process pid
${process.pid}
experienced an unexpected error:
$error
'
);
},
cancelOnError:
true
,
);
}
}
String
_getErrorMessage
(
String
what
,
String
testPath
,
String
shellPath
)
{
return
'
$what
\n
Test:
$testPath
\n
Shell:
$shellPath
\n\n
'
;
}
String
_getExitCodeMessage
(
int
exitCode
)
{
switch
(
exitCode
)
{
case
1
:
return
'Shell subprocess cleanly reported an error. Check the logs above for an error message.'
;
case
0
:
return
'Shell subprocess ended cleanly. Did main() call exit()?'
;
case
-
0x0f
:
// ProcessSignal.SIGTERM
return
'Shell subprocess crashed with SIGTERM (
$exitCode
).'
;
case
-
0x0b
:
// ProcessSignal.SIGSEGV
return
'Shell subprocess crashed with segmentation fault.'
;
case
-
0x06
:
// ProcessSignal.SIGABRT
return
'Shell subprocess crashed with SIGABRT (
$exitCode
).'
;
case
-
0x02
:
// ProcessSignal.SIGINT
return
'Shell subprocess terminated by ^C (SIGINT,
$exitCode
).'
;
default
:
return
'Shell subprocess crashed with unexpected exit code
$exitCode
.'
;
}
}
@visibleForTesting
@protected
Uri
getDdsServiceUri
()
{
return
Uri
(
scheme:
'http'
,
host:
(
host
.
type
==
InternetAddressType
.
IPv6
?
InternetAddress
.
loopbackIPv6
:
InternetAddress
.
loopbackIPv4
).
host
,
port:
debuggingOptions
.
hostVmServicePort
??
0
,
);
await
_fontConfigManager
.
dispose
();
}
}
...
...
@@ -862,60 +593,29 @@ class _AsyncError {
final
StackTrace
stack
;
}
/// Bridges the package:test
controller and the remote tester
.
/// Bridges the package:test
harness and the remote device
.
///
/// Sets up a that allows the package:test test [controller] to communicate with
/// a [remoteSocket] that runs the test. The returned future completes when
/// either side is closed, which also indicates when the tests have finished.
Future
<
void
>
_controlTests
({
@required
StreamChannel
<
dynamic
>
controller
,
@required
WebSocket
remoteSocket
,
@required
void
Function
(
dynamic
,
StackTrace
)
onError
,
/// The returned future completes when either side is closed, which also
/// indicates when the tests have finished.
Future
<
void
>
_pipeHarnessToRemote
({
@required
int
id
,
@required
StreamChannel
<
dynamic
>
harnessChannel
,
@required
StreamChannel
<
String
>
remoteChannel
,
})
async
{
final
Completer
<
void
>
harnessDone
=
Completer
<
void
>();
final
StreamSubscription
<
dynamic
>
harnessToTest
=
controller
.
stream
.
listen
(
(
dynamic
event
)
{
remoteSocket
.
add
(
json
.
encode
(
event
));
},
onDone:
harnessDone
.
complete
,
onError:
(
dynamic
error
,
StackTrace
stack
)
{
globals
.
printError
(
'test harness controller stream experienced an unexpected error'
);
onError
(
error
,
stack
);
},
cancelOnError:
true
,
);
final
Completer
<
void
>
testDone
=
Completer
<
void
>();
final
StreamSubscription
<
dynamic
>
testToHarness
=
remoteSocket
.
listen
(
(
dynamic
encodedEvent
)
{
assert
(
encodedEvent
is
String
);
// we shouldn't ever get binary messages
controller
.
sink
.
add
(
json
.
decode
(
encodedEvent
as
String
));
},
onDone:
testDone
.
complete
,
onError:
(
dynamic
error
,
StackTrace
stack
)
{
globals
.
printError
(
'test socket stream experienced an unexpected error'
);
onError
(
error
,
stack
);
},
cancelOnError:
true
,
);
globals
.
printTrace
(
'waiting for test harness or tests to finish'
);
globals
.
printTrace
(
'test
$id
: Waiting for test harness or tests to finish'
);
await
Future
.
any
<
void
>(<
Future
<
void
>>[
harnessDone
.
future
.
then
<
void
>((
void
value
)
{
globals
.
printTrace
(
'test process is no longer needed by test harness'
);
harnessChannel
.
stream
.
map
<
String
>(
json
.
encode
)
.
pipe
(
remoteChannel
.
sink
)
.
then
<
void
>((
void
value
)
{
globals
.
printTrace
(
'test
$id
: Test process is no longer needed by test harness'
);
}),
testDone
.
future
.
then
<
void
>((
void
value
)
{
globals
.
printTrace
(
'test harness is no longer needed by test process'
);
remoteChannel
.
stream
.
map
<
dynamic
>(
json
.
decode
)
.
pipe
(
harnessChannel
.
sink
)
.
then
<
void
>((
void
value
)
{
globals
.
printTrace
(
'test
$id
: Test harness is no longer needed by test process'
);
}),
]);
await
Future
.
wait
<
void
>(<
Future
<
void
>>[
harnessToTest
.
cancel
(),
testToHarness
.
cancel
(),
]);
}
packages/flutter_tools/lib/src/test/flutter_tester_device.dart
0 → 100644
View file @
9e55af52
// 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
'dart:io'
as
io
;
// ignore: dart_io_import;
import
'package:dds/dds.dart'
;
import
'package:meta/meta.dart'
;
import
'package:process/process.dart'
;
import
'package:stream_channel/stream_channel.dart'
;
import
'package:vm_service/vm_service.dart'
as
vm_service
;
import
'../base/common.dart'
;
import
'../base/file_system.dart'
;
import
'../base/io.dart'
;
import
'../base/logger.dart'
;
import
'../base/platform.dart'
;
import
'../convert.dart'
;
import
'../device.dart'
;
import
'../project.dart'
;
import
'../vmservice.dart'
;
import
'font_config_manager.dart'
;
import
'test_device.dart'
;
/// Implementation of [TestDevice] with the Flutter Tester over a [Process].
class
FlutterTesterTestDevice
extends
TestDevice
{
FlutterTesterTestDevice
({
@required
this
.
id
,
@required
this
.
platform
,
@required
this
.
fileSystem
,
@required
this
.
processManager
,
@required
this
.
logger
,
@required
this
.
shellPath
,
@required
this
.
debuggingOptions
,
@required
this
.
enableObservatory
,
@required
this
.
machine
,
@required
this
.
host
,
@required
this
.
buildTestAssets
,
@required
this
.
flutterProject
,
@required
this
.
icudtlPath
,
@required
this
.
compileExpression
,
@required
this
.
fontConfigManager
,
})
:
assert
(
shellPath
!=
null
),
// Please provide the path to the shell in the SKY_SHELL environment variable.
assert
(!
debuggingOptions
.
startPaused
||
enableObservatory
),
_gotProcessObservatoryUri
=
enableObservatory
?
Completer
<
Uri
>()
:
(
Completer
<
Uri
>()..
complete
(
null
));
/// Used for logging to identify the test that is currently being executed.
final
int
id
;
final
Platform
platform
;
final
FileSystem
fileSystem
;
final
ProcessManager
processManager
;
final
Logger
logger
;
final
String
shellPath
;
final
DebuggingOptions
debuggingOptions
;
final
bool
enableObservatory
;
final
bool
machine
;
final
InternetAddress
host
;
final
bool
buildTestAssets
;
final
FlutterProject
flutterProject
;
final
String
icudtlPath
;
final
CompileExpression
compileExpression
;
final
FontConfigManager
fontConfigManager
;
final
Completer
<
Uri
>
_gotProcessObservatoryUri
;
final
Completer
<
int
>
_exitCode
=
Completer
<
int
>();
Process
_process
;
HttpServer
_server
;
@override
Future
<
StreamChannel
<
String
>>
start
({
@required
String
compiledEntrypointPath
})
async
{
assert
(!
_exitCode
.
isCompleted
);
assert
(
_process
==
null
);
assert
(
_server
==
null
);
// Prepare our WebSocket server to talk to the engine subprocess.
// Let the server choose an unused port.
_server
=
await
bind
(
host
,
/*port*/
0
);
logger
.
printTrace
(
'test
$id
: test harness socket server is running at port:
${_server.port}
'
);
final
List
<
String
>
command
=
<
String
>[
shellPath
,
if
(
enableObservatory
)
...<
String
>[
// Some systems drive the _FlutterPlatform class in an unusual way, where
// only one test file is processed at a time, and the operating
// environment hands out specific ports ahead of time in a cooperative
// manner, where we're only allowed to open ports that were given to us in
// advance like this. For those esoteric systems, we have this feature
// whereby you can create _FlutterPlatform with a pair of ports.
//
// I mention this only so that you won't be tempted, as I was, to apply
// the obvious simplification to this code and remove this entire feature.
'--observatory-port=
${debuggingOptions.disableDds ? debuggingOptions.hostVmServicePort: 0}
'
,
if
(
debuggingOptions
.
startPaused
)
'--start-paused'
,
if
(
debuggingOptions
.
disableServiceAuthCodes
)
'--disable-service-auth-codes'
,
]
else
'--disable-observatory'
,
if
(
host
.
type
==
InternetAddressType
.
IPv6
)
'--ipv6'
,
if
(
icudtlPath
!=
null
)
'--icu-data-file-path=
$icudtlPath
'
,
'--enable-checked-mode'
,
'--verify-entry-points'
,
'--enable-software-rendering'
,
'--skia-deterministic-rendering'
,
'--enable-dart-profiling'
,
'--non-interactive'
,
'--use-test-fonts'
,
'--packages=
${debuggingOptions.buildInfo.packagesPath}
'
,
if
(
debuggingOptions
.
nullAssertions
)
'--dart-flags=--null_assertions'
,
...
debuggingOptions
.
dartEntrypointArgs
,
compiledEntrypointPath
,
];
// If the FLUTTER_TEST environment variable has been set, then pass it on
// for package:flutter_test to handle the value.
//
// If FLUTTER_TEST has not been set, assume from this context that this
// call was invoked by the command 'flutter test'.
final
String
flutterTest
=
platform
.
environment
.
containsKey
(
'FLUTTER_TEST'
)
?
platform
.
environment
[
'FLUTTER_TEST'
]
:
'true'
;
final
Map
<
String
,
String
>
environment
=
<
String
,
String
>{
'FLUTTER_TEST'
:
flutterTest
,
'FONTCONFIG_FILE'
:
fontConfigManager
.
fontConfigFile
.
path
,
'SERVER_PORT'
:
_server
.
port
.
toString
(),
'APP_NAME'
:
flutterProject
?.
manifest
?.
appName
??
''
,
if
(
buildTestAssets
)
'UNIT_TEST_ASSETS'
:
fileSystem
.
path
.
join
(
flutterProject
?.
directory
?.
path
??
''
,
'build'
,
'unit_test_assets'
),
};
logger
.
printTrace
(
'test
$id
: Starting flutter_tester process with command=
$command
, environment=
$environment
'
);
_process
=
await
processManager
.
start
(
command
,
environment:
environment
);
// Unawaited to update state.
unawaited
(
_process
.
exitCode
.
then
((
int
exitCode
)
{
logger
.
printTrace
(
'test
$id
: flutter_tester process at pid
${_process.pid}
exited with code=
$exitCode
'
);
_exitCode
.
complete
(
exitCode
);
}));
logger
.
printTrace
(
'test
$id
: Started flutter_tester process at pid
${_process.pid}
'
);
// Pipe stdout and stderr from the subprocess to our printStatus console.
// We also keep track of what observatory port the engine used, if any.
_pipeStandardStreamsToConsole
(
process:
_process
,
reportObservatoryUri:
(
Uri
detectedUri
)
async
{
assert
(!
_gotProcessObservatoryUri
.
isCompleted
);
assert
(
debuggingOptions
.
hostVmServicePort
==
null
||
debuggingOptions
.
hostVmServicePort
==
detectedUri
.
port
);
Uri
forwardingUri
;
if
(!
debuggingOptions
.
disableDds
)
{
logger
.
printTrace
(
'test
$id
: Starting Dart Development Service'
);
final
DartDevelopmentService
dds
=
await
startDds
(
detectedUri
);
forwardingUri
=
dds
.
uri
;
logger
.
printTrace
(
'test
$id
: Dart Development Service started at
${dds.uri}
, forwarding to VM service at
${dds.remoteVmServiceUri}
.'
);
}
else
{
forwardingUri
=
detectedUri
;
}
logger
.
printTrace
(
'Connecting to service protocol:
$forwardingUri
'
);
final
Future
<
vm_service
.
VmService
>
localVmService
=
connectToVmService
(
forwardingUri
,
compileExpression:
compileExpression
,
);
unawaited
(
localVmService
.
then
((
vm_service
.
VmService
vmservice
)
{
logger
.
printTrace
(
'test
$id
: Successfully connected to service protocol:
$forwardingUri
'
);
}));
if
(
debuggingOptions
.
startPaused
&&
!
machine
)
{
logger
.
printStatus
(
'The test process has been started.'
);
logger
.
printStatus
(
'You can now connect to it using observatory. To connect, load the following Web site in your browser:'
);
logger
.
printStatus
(
'
$forwardingUri
'
);
logger
.
printStatus
(
'You should first set appropriate breakpoints, then resume the test in the debugger.'
);
}
_gotProcessObservatoryUri
.
complete
(
forwardingUri
);
},
);
return
remoteChannel
;
}
@override
Future
<
Uri
>
get
observatoryUri
{
assert
(
_gotProcessObservatoryUri
!=
null
);
return
_gotProcessObservatoryUri
.
future
;
}
@override
Future
<
void
>
kill
()
async
{
logger
.
printTrace
(
'test
$id
: Terminating flutter_tester process'
);
_process
?.
kill
(
io
.
ProcessSignal
.
sigkill
);
logger
.
printTrace
(
'test
$id
: Shutting down test harness socket server'
);
await
_server
?.
close
(
force:
true
);
await
finished
;
}
@override
Future
<
void
>
get
finished
async
{
final
int
exitCode
=
await
_exitCode
.
future
;
// On Windows, the [exitCode] and the terminating signal have no correlation.
if
(
platform
.
isWindows
)
{
return
;
}
// ProcessSignal.SIGKILL. Negative because signals are returned as negative
// exit codes.
if
(
exitCode
==
-
9
)
{
// We expect SIGKILL (9) because we could have tried to [kill] it.
return
;
}
throw
TestDeviceException
(
_getExitCodeMessage
(
exitCode
),
StackTrace
.
current
);
}
Uri
get
_ddsServiceUri
{
return
Uri
(
scheme:
'http'
,
host:
(
host
.
type
==
InternetAddressType
.
IPv6
?
InternetAddress
.
loopbackIPv6
:
InternetAddress
.
loopbackIPv4
).
host
,
port:
debuggingOptions
.
hostVmServicePort
??
0
,
);
}
@visibleForTesting
@protected
Future
<
DartDevelopmentService
>
startDds
(
Uri
uri
)
{
return
DartDevelopmentService
.
startDartDevelopmentService
(
uri
,
serviceUri:
_ddsServiceUri
,
enableAuthCodes:
!
debuggingOptions
.
disableServiceAuthCodes
,
ipv6:
host
.
type
==
InternetAddressType
.
IPv6
,
);
}
/// Binds an [HttpServer] serving from `host` on `port`.
///
/// Only intended to be overridden in tests.
@protected
@visibleForTesting
Future
<
HttpServer
>
bind
(
InternetAddress
host
,
int
port
)
=>
HttpServer
.
bind
(
host
,
port
);
@protected
@visibleForTesting
Future
<
StreamChannel
<
String
>>
get
remoteChannel
async
{
assert
(
_server
!=
null
);
try
{
final
HttpRequest
firstRequest
=
await
_server
.
first
;
final
WebSocket
webSocket
=
await
WebSocketTransformer
.
upgrade
(
firstRequest
);
return
_webSocketToStreamChannel
(
webSocket
);
}
on
Exception
catch
(
error
,
stackTrace
)
{
throw
TestDeviceException
(
'Unable to connect to flutter_tester process:
$error
'
,
stackTrace
);
}
}
@override
String
toString
()
{
final
String
status
=
_process
!=
null
?
'pid:
${_process.pid}
,
${_exitCode.isCompleted ? 'exited' : 'running'}
'
:
'not started'
;
return
'Flutter Tester (
$status
) for test
$id
'
;
}
void
_pipeStandardStreamsToConsole
({
@required
Process
process
,
@required
Future
<
void
>
reportObservatoryUri
(
Uri
uri
),
})
{
const
String
observatoryString
=
'Observatory listening on '
;
for
(
final
Stream
<
List
<
int
>>
stream
in
<
Stream
<
List
<
int
>>>[
process
.
stderr
,
process
.
stdout
,
])
{
stream
.
transform
<
String
>(
utf8
.
decoder
)
.
transform
<
String
>(
const
LineSplitter
())
.
listen
(
(
String
line
)
async
{
logger
.
printTrace
(
'test
$id
: Shell:
$line
'
);
if
(
line
.
startsWith
(
observatoryString
))
{
try
{
final
Uri
uri
=
Uri
.
parse
(
line
.
substring
(
observatoryString
.
length
));
if
(
reportObservatoryUri
!=
null
)
{
await
reportObservatoryUri
(
uri
);
}
}
on
Exception
catch
(
error
)
{
logger
.
printError
(
'Could not parse shell observatory port message:
$error
'
);
}
}
else
if
(
line
!=
null
)
{
logger
.
printStatus
(
'Shell:
$line
'
);
}
},
onError:
(
dynamic
error
)
{
logger
.
printError
(
'shell console stream for process pid
${process.pid}
experienced an unexpected error:
$error
'
);
},
cancelOnError:
true
,
);
}
}
}
String
_getExitCodeMessage
(
int
exitCode
)
{
switch
(
exitCode
)
{
case
1
:
return
'Shell subprocess cleanly reported an error. Check the logs above for an error message.'
;
case
0
:
return
'Shell subprocess ended cleanly. Did main() call exit()?'
;
case
-
0x0f
:
// ProcessSignal.SIGTERM
return
'Shell subprocess crashed with SIGTERM (
$exitCode
).'
;
case
-
0x0b
:
// ProcessSignal.SIGSEGV
return
'Shell subprocess crashed with segmentation fault.'
;
case
-
0x06
:
// ProcessSignal.SIGABRT
return
'Shell subprocess crashed with SIGABRT (
$exitCode
).'
;
case
-
0x02
:
// ProcessSignal.SIGINT
return
'Shell subprocess terminated by ^C (SIGINT,
$exitCode
).'
;
default
:
return
'Shell subprocess crashed with unexpected exit code
$exitCode
.'
;
}
}
StreamChannel
<
String
>
_webSocketToStreamChannel
(
WebSocket
webSocket
)
{
final
StreamChannelController
<
String
>
controller
=
StreamChannelController
<
String
>();
controller
.
local
.
stream
.
map
<
dynamic
>((
String
message
)
=>
message
as
dynamic
)
.
pipe
(
webSocket
);
webSocket
// We're only communicating with string encoded JSON.
.
map
<
String
>((
dynamic
message
)
=>
message
as
String
)
.
pipe
(
controller
.
local
.
sink
);
return
controller
.
foreign
;
}
packages/flutter_tools/lib/src/test/font_config_manager.dart
0 → 100644
View file @
9e55af52
// 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
'../base/file_system.dart'
;
import
'../globals.dart'
as
globals
;
/// Manages a Font configuration that can be shared across multiple tests.
class
FontConfigManager
{
Directory
_fontsDirectory
;
File
_cachedFontConfig
;
/// Returns a Font configuration that limits font fallback to the artifact
/// cache directory.
File
get
fontConfigFile
{
if
(
_cachedFontConfig
!=
null
)
{
return
_cachedFontConfig
;
}
final
StringBuffer
sb
=
StringBuffer
();
sb
.
writeln
(
'<fontconfig>'
);
sb
.
writeln
(
' <dir>
${globals.cache.getCacheArtifacts().path}
</dir>'
);
sb
.
writeln
(
' <cachedir>/var/cache/fontconfig</cachedir>'
);
sb
.
writeln
(
'</fontconfig>'
);
if
(
_fontsDirectory
==
null
)
{
_fontsDirectory
=
globals
.
fs
.
systemTempDirectory
.
createTempSync
(
'flutter_test_fonts.'
);
globals
.
printTrace
(
'Using this directory for fonts configuration:
${_fontsDirectory.path}
'
);
}
_cachedFontConfig
=
globals
.
fs
.
file
(
'
${_fontsDirectory.path}
/fonts.conf'
);
_cachedFontConfig
.
createSync
();
_cachedFontConfig
.
writeAsStringSync
(
sb
.
toString
());
return
_cachedFontConfig
;
}
Future
<
void
>
dispose
()
async
{
if
(
_fontsDirectory
!=
null
)
{
globals
.
printTrace
(
'Deleting
${_fontsDirectory.path}
...'
);
await
_fontsDirectory
.
delete
(
recursive:
true
);
_fontsDirectory
=
null
;
}
}
}
packages/flutter_tools/lib/src/test/test_device.dart
0 → 100644
View file @
9e55af52
// 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:meta/meta.dart'
;
import
'package:stream_channel/stream_channel.dart'
;
/// A remote device where tests can be executed on.
///
/// Reusability of an instance across multiple runs is not guaranteed for all
/// implementations.
abstract
class
TestDevice
{
/// Starts the test device with the provided entrypoint.
///
/// Returns a channel that can be used to communicate with the test process.
Future
<
StreamChannel
<
String
>>
start
({
@required
String
compiledEntrypointPath
});
/// Should complete with null if the observatory is not enabled.
Future
<
Uri
>
get
observatoryUri
;
/// Terminates the test device.
///
/// A [TestDeviceException] can be thrown if it did not stop gracefully.
Future
<
void
>
kill
();
/// Waits for the test device to stop.
///
/// A [TestDeviceException] can be thrown if it did not stop gracefully.
Future
<
void
>
get
finished
;
}
/// Thrown when the device encounters a problem.
class
TestDeviceException
implements
Exception
{
TestDeviceException
(
this
.
message
,
this
.
stackTrace
);
final
String
message
;
final
StackTrace
stackTrace
;
@override
String
toString
()
=>
'TestDeviceException(
$message
)'
;
}
packages/flutter_tools/lib/src/test/watcher.dart
View file @
9e55af52
...
...
@@ -4,43 +4,27 @@
// @dart = 2.8
import
'
../base/io.dart'
show
Process
;
import
'
test_device.dart'
;
/// Callbacks for reporting progress while running tests.
abstract
class
TestWatcher
{
/// Called after
a child process
starts.
/// Called after
the test device
starts.
///
/// If startPaused was true, the caller needs to resume in Observatory to
/// start running the tests.
void
handleStarted
Process
(
ProcessEvent
event
)
{
}
void
handleStarted
Device
(
Uri
observatoryUri
)
{
}
/// Called after the tests finish but before the
process
exits.
/// Called after the tests finish but before the
test device
exits.
///
/// The
child process
won't exit until this method completes.
/// Not called if the
process
died.
Future
<
void
>
handleFinishedTest
(
ProcessEvent
event
);
/// The
test device
won't exit until this method completes.
/// Not called if the
test device
died.
Future
<
void
>
handleFinishedTest
(
TestDevice
testDevice
);
/// Called when the test process crashed before connecting to test harness.
Future
<
void
>
handleTestCrashed
(
ProcessEvent
event
);
/// Called when the test device crashed before it could be connected to the
/// test harness.
Future
<
void
>
handleTestCrashed
(
TestDevice
testDevice
);
/// Called if we timed out waiting for the test
process
to connect to test
/// Called if we timed out waiting for the test
device
to connect to test
/// harness.
Future
<
void
>
handleTestTimedOut
(
ProcessEvent
event
);
}
/// Describes a child process started during testing.
class
ProcessEvent
{
ProcessEvent
(
this
.
childIndex
,
this
.
process
,
[
this
.
observatoryUri
]);
/// The index assigned when the child process was launched.
///
/// Indexes are assigned consecutively starting from zero.
/// When debugging, there should only be one child process so this will
/// always be zero.
final
int
childIndex
;
final
Process
process
;
/// The observatory URL or null if not debugging.
final
Uri
observatoryUri
;
Future
<
void
>
handleTestTimedOut
(
TestDevice
testDevice
);
}
packages/flutter_tools/test/general.shard/flutter_platform_test.dart
View file @
9e55af52
...
...
@@ -4,16 +4,12 @@
// @dart = 2.8
import
'dart:async'
;
import
'package:file/memory.dart'
;
import
'package:flutter_tools/src/base/file_system.dart'
;
import
'package:flutter_tools/src/base/io.dart'
;
import
'package:flutter_tools/src/base/platform.dart'
;
import
'package:flutter_tools/src/build_info.dart'
;
import
'package:flutter_tools/src/device.dart'
;
import
'package:flutter_tools/src/test/flutter_platform.dart'
;
import
'package:meta/meta.dart'
;
import
'package:mockito/mockito.dart'
;
import
'package:process/process.dart'
;
import
'package:test_core/backend.dart'
;
// ignore: deprecated_member_use
...
...
@@ -41,6 +37,7 @@ void main() {
BuildInfo
.
debug
,
hostVmServicePort:
1234
,
),
enableObservatory:
false
,
);
flutterPlatform
.
loadChannel
(
'test1.dart'
,
MockSuitePlatform
());
...
...
@@ -56,6 +53,7 @@ void main() {
debuggingOptions:
DebuggingOptions
.
enabled
(
BuildInfo
.
debug
),
shellPath:
'/'
,
precompiledDillPath:
'example.dill'
,
enableObservatory:
false
,
);
flutterPlatform
.
loadChannel
(
'test1.dart'
,
MockSuitePlatform
());
...
...
@@ -65,115 +63,6 @@ void main() {
ProcessManager:
()
=>
FakeProcessManager
.
any
(),
});
group
(
'Observatory and DDS setup'
,
()
{
Platform
fakePlatform
;
ProcessManager
fakeProcessManager
;
FlutterPlatform
flutterPlatform
;
final
Map
<
Type
,
Generator
>
contextOverrides
=
<
Type
,
Generator
>{
Platform:
()
=>
fakePlatform
,
ProcessManager:
()
=>
fakeProcessManager
,
FileSystem:
()
=>
fileSystem
,
};
setUp
(()
{
fakePlatform
=
FakePlatform
(
operatingSystem:
'linux'
,
environment:
<
String
,
String
>{});
fakeProcessManager
=
FakeProcessManager
.
list
(<
FakeCommand
>[
const
FakeCommand
(
command:
<
String
>[
'/'
,
'--observatory-port=0'
,
'--ipv6'
,
'--enable-checked-mode'
,
'--verify-entry-points'
,
'--enable-software-rendering'
,
'--skia-deterministic-rendering'
,
'--enable-dart-profiling'
,
'--non-interactive'
,
'--use-test-fonts'
,
'--packages=.dart_tool/package_config.json'
,
'example.dill'
],
stdout:
'success'
,
stderr:
'failure'
,
exitCode:
0
,
)
]);
flutterPlatform
=
TestObservatoryFlutterPlatform
();
});
testUsingContext
(
'skips setting observatory port and uses the input port for for DDS instead'
,
()
async
{
flutterPlatform
.
loadChannel
(
'test1.dart'
,
MockSuitePlatform
());
final
TestObservatoryFlutterPlatform
testPlatform
=
flutterPlatform
as
TestObservatoryFlutterPlatform
;
await
testPlatform
.
ddsServiceUriFuture
().
then
((
Uri
uri
)
=>
expect
(
uri
.
port
,
1234
));
},
overrides:
contextOverrides
);
});
group
(
'The FLUTTER_TEST environment variable is passed to the test process'
,
()
{
FakePlatform
fakePlatform
;
MockProcessManager
mockProcessManager
;
FlutterPlatform
flutterPlatform
;
final
Map
<
Type
,
Generator
>
contextOverrides
=
<
Type
,
Generator
>{
Platform:
()
=>
fakePlatform
,
ProcessManager:
()
=>
mockProcessManager
,
FileSystem:
()
=>
fileSystem
,
};
setUp
(()
{
// Not Windows
fakePlatform
=
FakePlatform
(
operatingSystem:
'linux'
);
mockProcessManager
=
MockProcessManager
();
flutterPlatform
=
TestFlutterPlatform
();
});
Future
<
Map
<
String
,
String
>>
captureEnvironment
()
async
{
flutterPlatform
.
loadChannel
(
'test1.dart'
,
MockSuitePlatform
());
when
(
mockProcessManager
.
start
(
any
,
environment:
anyNamed
(
'environment'
)),
).
thenAnswer
((
_
)
{
return
Future
<
Process
>.
value
(
MockProcess
());
});
await
untilCalled
(
mockProcessManager
.
start
(
any
,
environment:
anyNamed
(
'environment'
)));
final
VerificationResult
toVerify
=
verify
(
mockProcessManager
.
start
(
any
,
environment:
captureAnyNamed
(
'environment'
),
));
expect
(
toVerify
.
captured
,
hasLength
(
1
));
expect
(
toVerify
.
captured
.
first
,
isA
<
Map
<
String
,
String
>>());
return
toVerify
.
captured
.
first
as
Map
<
String
,
String
>;
}
testUsingContext
(
'as true when not originally set'
,
()
async
{
fakePlatform
.
environment
=
<
String
,
String
>{};
final
Map
<
String
,
String
>
capturedEnvironment
=
await
captureEnvironment
();
expect
(
capturedEnvironment
[
'FLUTTER_TEST'
],
'true'
);
},
overrides:
contextOverrides
);
testUsingContext
(
'as true when set to true'
,
()
async
{
fakePlatform
.
environment
=
<
String
,
String
>{
'FLUTTER_TEST'
:
'true'
};
final
Map
<
String
,
String
>
capturedEnvironment
=
await
captureEnvironment
();
expect
(
capturedEnvironment
[
'FLUTTER_TEST'
],
'true'
);
},
overrides:
contextOverrides
);
testUsingContext
(
'as false when set to false'
,
()
async
{
fakePlatform
.
environment
=
<
String
,
String
>{
'FLUTTER_TEST'
:
'false'
};
final
Map
<
String
,
String
>
capturedEnvironment
=
await
captureEnvironment
();
expect
(
capturedEnvironment
[
'FLUTTER_TEST'
],
'false'
);
},
overrides:
contextOverrides
);
testUsingContext
(
'unchanged when set'
,
()
async
{
fakePlatform
.
environment
=
<
String
,
String
>{
'FLUTTER_TEST'
:
'neither true nor false'
};
final
Map
<
String
,
String
>
capturedEnvironment
=
await
captureEnvironment
();
expect
(
capturedEnvironment
[
'FLUTTER_TEST'
],
'neither true nor false'
);
},
overrides:
contextOverrides
);
testUsingContext
(
'as null when set to null'
,
()
async
{
fakePlatform
.
environment
=
<
String
,
String
>{
'FLUTTER_TEST'
:
null
};
final
Map
<
String
,
String
>
capturedEnvironment
=
await
captureEnvironment
();
expect
(
capturedEnvironment
[
'FLUTTER_TEST'
],
null
);
},
overrides:
contextOverrides
);
});
testUsingContext
(
'installHook creates a FlutterPlatform'
,
()
{
expect
(()
=>
installHook
(
shellPath:
'abc'
,
...
...
@@ -206,7 +95,6 @@ void main() {
),
enableObservatory:
true
,
machine:
true
,
port:
100
,
precompiledDillPath:
'def'
,
precompiledDillFiles:
expectedPrecompiledDillFiles
,
updateGoldens:
true
,
...
...
@@ -225,7 +113,6 @@ void main() {
expect
(
flutterPlatform
.
debuggingOptions
.
hostVmServicePort
,
equals
(
200
));
expect
(
flutterPlatform
.
enableObservatory
,
equals
(
true
));
expect
(
flutterPlatform
.
machine
,
equals
(
true
));
expect
(
flutterPlatform
.
port
,
equals
(
100
));
expect
(
flutterPlatform
.
host
,
InternetAddress
.
loopbackIPv6
);
expect
(
flutterPlatform
.
precompiledDillPath
,
equals
(
'def'
));
expect
(
flutterPlatform
.
precompiledDillFiles
,
expectedPrecompiledDillFiles
);
...
...
@@ -234,113 +121,20 @@ void main() {
expect
(
flutterPlatform
.
icudtlPath
,
equals
(
'ghi'
));
});
});
FakeProcessManager
fakeProcessManager
;
testUsingContext
(
'Can pass additional arguments to tester binary'
,
()
async
{
final
TestFlutterPlatform
platform
=
TestFlutterPlatform
(<
String
>[
'--foo'
,
'--bar'
]);
platform
.
loadChannel
(
'test1.dart'
,
MockSuitePlatform
());
await
null
;
expect
(
fakeProcessManager
.
hasRemainingExpectations
,
false
);
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
MemoryFileSystem
.
test
(),
ProcessManager:
()
{
return
fakeProcessManager
=
FakeProcessManager
.
list
(<
FakeCommand
>[
const
FakeCommand
(
command:
<
String
>[
'/'
,
'--disable-observatory'
,
'--ipv6'
,
'--enable-checked-mode'
,
'--verify-entry-points'
,
'--enable-software-rendering'
,
'--skia-deterministic-rendering'
,
'--enable-dart-profiling'
,
'--non-interactive'
,
'--use-test-fonts'
,
'--packages=.dart_tool/package_config.json'
,
'--foo'
,
'--bar'
,
'example.dill'
],
stdout:
'success'
,
stderr:
'failure'
,
exitCode:
0
,
)
]);
}
});
}
class
MockSuitePlatform
extends
Mock
implements
SuitePlatform
{}
class
MockProcessManager
extends
Mock
implements
ProcessManager
{}
class
MockProcess
extends
Mock
implements
Process
{}
class
MockHttpServer
extends
Mock
implements
HttpServer
{}
// A FlutterPlatform with enough fields set to load and start a test.
//
// Uses a mock HttpServer. We don't want to bind random ports in our CI hosts.
class
TestFlutterPlatform
extends
FlutterPlatform
{
TestFlutterPlatform
(
[
List
<
String
>
dartEntrypointArgs
=
const
<
String
>[]]
)
:
super
(
TestFlutterPlatform
()
:
super
(
shellPath:
'/'
,
debuggingOptions:
DebuggingOptions
.
enabled
(
const
BuildInfo
(
BuildMode
.
debug
,
''
,
treeShakeIcons:
false
,
packagesPath:
'.dart_tool/package_config.json'
),
startPaused:
false
,
disableDds:
true
,
dartEntrypointArgs:
dartEntrypointArgs
,
const
BuildInfo
(
BuildMode
.
debug
,
''
,
treeShakeIcons:
false
,
),
precompiledDillPath:
'example.dill'
,
host:
InternetAddress
.
loopbackIPv6
,
port:
0
,
updateGoldens:
false
,
enableObservatory:
false
,
buildTestAssets:
false
,
);
@override
@protected
Future
<
HttpServer
>
bind
(
InternetAddress
host
,
int
port
)
async
=>
MockHttpServer
();
}
// A FlutterPlatform that enables observatory.
//
// Uses a mock HttpServer. We don't want to bind random ports in our CI hosts.
class
TestObservatoryFlutterPlatform
extends
FlutterPlatform
{
TestObservatoryFlutterPlatform
()
:
super
(
shellPath:
'/'
,
debuggingOptions:
DebuggingOptions
.
enabled
(
const
BuildInfo
(
BuildMode
.
debug
,
''
,
treeShakeIcons:
false
,
packagesPath:
'.dart_tool/package_config.json'
),
startPaused:
false
,
disableDds:
false
,
disableServiceAuthCodes:
false
,
hostVmServicePort:
1234
,
),
precompiledDillPath:
'example.dill'
,
host:
InternetAddress
.
loopbackIPv6
,
port:
0
,
updateGoldens:
false
,
enableObservatory:
true
,
buildTestAssets:
false
,
);
final
Completer
<
Uri
>
_ddsServiceUriCompleter
=
Completer
<
Uri
>();
Future
<
Uri
>
ddsServiceUriFuture
()
{
return
_ddsServiceUriCompleter
.
future
;
}
@override
@protected
Future
<
HttpServer
>
bind
(
InternetAddress
host
,
int
port
)
async
=>
MockHttpServer
();
@override
Uri
getDdsServiceUri
()
{
final
Uri
result
=
super
.
getDdsServiceUri
();
_ddsServiceUriCompleter
.
complete
(
result
);
return
result
;
}
}
packages/flutter_tools/test/general.shard/flutter_tester_device_test.dart
0 → 100644
View file @
9e55af52
// 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:dds/dds.dart'
;
import
'package:flutter_tools/src/base/io.dart'
;
import
'package:flutter_tools/src/base/logger.dart'
;
import
'package:flutter_tools/src/build_info.dart'
;
import
'package:flutter_tools/src/device.dart'
;
import
'package:flutter_tools/src/test/font_config_manager.dart'
;
import
'package:flutter_tools/src/test/flutter_tester_device.dart'
;
import
'package:meta/meta.dart'
;
import
'package:mockito/mockito.dart'
;
import
'package:stream_channel/stream_channel.dart'
;
import
'package:file/memory.dart'
;
import
'package:flutter_tools/src/base/file_system.dart'
;
import
'package:flutter_tools/src/base/platform.dart'
;
import
'../src/common.dart'
;
import
'../src/context.dart'
;
void
main
(
)
{
FakePlatform
platform
;
FileSystem
fileSystem
;
ProcessManager
processManager
;
FlutterTesterTestDevice
device
;
setUp
(()
{
fileSystem
=
MemoryFileSystem
.
test
();
// Not Windows.
platform
=
FakePlatform
(
operatingSystem:
'linux'
,
environment:
<
String
,
String
>{},
);
processManager
=
FakeProcessManager
.
any
();
});
FlutterTesterTestDevice
createDevice
({
List
<
String
>
dartEntrypointArgs
=
const
<
String
>[],
bool
enableObservatory
=
false
,
})
=>
TestFlutterTesterDevice
(
platform:
platform
,
fileSystem:
fileSystem
,
processManager:
processManager
,
enableObservatory:
enableObservatory
,
dartEntrypointArgs:
dartEntrypointArgs
,
);
group
(
'The FLUTTER_TEST environment variable is passed to the test process'
,
()
{
setUp
(()
{
processManager
=
MockProcessManager
();
device
=
createDevice
();
fileSystem
.
file
(
'.dart_tool/package_config.json'
)
..
createSync
(
recursive:
true
)
..
writeAsStringSync
(
'{"configVersion":2,"packages":[]}'
);
});
Future
<
Map
<
String
,
String
>>
captureEnvironment
()
async
{
final
Future
<
StreamChannel
<
String
>>
deviceStarted
=
device
.
start
(
compiledEntrypointPath:
'example.dill'
,
);
when
(
processManager
.
start
(
any
,
environment:
anyNamed
(
'environment'
)),
).
thenAnswer
((
_
)
{
return
Future
<
Process
>.
value
(
MockProcess
());
});
await
untilCalled
(
processManager
.
start
(
any
,
environment:
anyNamed
(
'environment'
)));
final
VerificationResult
toVerify
=
verify
(
processManager
.
start
(
any
,
environment:
captureAnyNamed
(
'environment'
),
));
expect
(
toVerify
.
captured
,
hasLength
(
1
));
expect
(
toVerify
.
captured
.
first
,
isA
<
Map
<
String
,
String
>>());
await
deviceStarted
;
return
toVerify
.
captured
.
first
as
Map
<
String
,
String
>;
}
testUsingContext
(
'as true when not originally set'
,
()
async
{
final
Map
<
String
,
String
>
capturedEnvironment
=
await
captureEnvironment
();
expect
(
capturedEnvironment
[
'FLUTTER_TEST'
],
'true'
);
});
testUsingContext
(
'as true when set to true'
,
()
async
{
platform
.
environment
=
<
String
,
String
>{
'FLUTTER_TEST'
:
'true'
};
final
Map
<
String
,
String
>
capturedEnvironment
=
await
captureEnvironment
();
expect
(
capturedEnvironment
[
'FLUTTER_TEST'
],
'true'
);
});
testUsingContext
(
'as false when set to false'
,
()
async
{
platform
.
environment
=
<
String
,
String
>{
'FLUTTER_TEST'
:
'false'
};
final
Map
<
String
,
String
>
capturedEnvironment
=
await
captureEnvironment
();
expect
(
capturedEnvironment
[
'FLUTTER_TEST'
],
'false'
);
});
testUsingContext
(
'unchanged when set'
,
()
async
{
platform
.
environment
=
<
String
,
String
>{
'FLUTTER_TEST'
:
'neither true nor false'
};
final
Map
<
String
,
String
>
capturedEnvironment
=
await
captureEnvironment
();
expect
(
capturedEnvironment
[
'FLUTTER_TEST'
],
'neither true nor false'
);
});
testUsingContext
(
'as null when set to null'
,
()
async
{
platform
.
environment
=
<
String
,
String
>{
'FLUTTER_TEST'
:
null
};
final
Map
<
String
,
String
>
capturedEnvironment
=
await
captureEnvironment
();
expect
(
capturedEnvironment
[
'FLUTTER_TEST'
],
null
);
});
});
group
(
'Dart Entrypoint Args'
,
()
{
setUp
(()
{
processManager
=
FakeProcessManager
.
list
(<
FakeCommand
>[
const
FakeCommand
(
command:
<
String
>[
'/'
,
'--disable-observatory'
,
'--ipv6'
,
'--enable-checked-mode'
,
'--verify-entry-points'
,
'--enable-software-rendering'
,
'--skia-deterministic-rendering'
,
'--enable-dart-profiling'
,
'--non-interactive'
,
'--use-test-fonts'
,
'--packages=.dart_tool/package_config.json'
,
'--foo'
,
'--bar'
,
'example.dill'
],
stdout:
'success'
,
stderr:
'failure'
,
exitCode:
0
,
)
]);
device
=
createDevice
(
dartEntrypointArgs:
<
String
>[
'--foo'
,
'--bar'
]);
});
testUsingContext
(
'Can pass additional arguments to tester binary'
,
()
async
{
await
device
.
start
(
compiledEntrypointPath:
'example.dill'
);
expect
((
processManager
as
FakeProcessManager
).
hasRemainingExpectations
,
false
);
});
});
group
(
'DDS'
,
()
{
setUp
(()
{
processManager
=
FakeProcessManager
.
list
(<
FakeCommand
>[
const
FakeCommand
(
command:
<
String
>[
'/'
,
'--observatory-port=0'
,
'--ipv6'
,
'--enable-checked-mode'
,
'--verify-entry-points'
,
'--enable-software-rendering'
,
'--skia-deterministic-rendering'
,
'--enable-dart-profiling'
,
'--non-interactive'
,
'--use-test-fonts'
,
'--packages=.dart_tool/package_config.json'
,
'example.dill'
],
stdout:
'Observatory listening on http://localhost:1234'
,
stderr:
'failure'
,
exitCode:
0
,
)
]);
device
=
createDevice
(
enableObservatory:
true
);
});
testUsingContext
(
'skips setting observatory port and uses the input port for for DDS instead'
,
()
async
{
await
device
.
start
(
compiledEntrypointPath:
'example.dill'
);
await
device
.
observatoryUri
;
final
Uri
uri
=
await
(
device
as
TestFlutterTesterDevice
).
ddsServiceUriFuture
();
expect
(
uri
.
port
,
1234
);
});
});
}
/// A Flutter Tester device.
///
/// Uses a mock HttpServer. We don't want to bind random ports in our CI hosts.
class
TestFlutterTesterDevice
extends
FlutterTesterTestDevice
{
TestFlutterTesterDevice
({
@required
Platform
platform
,
@required
FileSystem
fileSystem
,
@required
ProcessManager
processManager
,
@required
bool
enableObservatory
,
@required
List
<
String
>
dartEntrypointArgs
,
})
:
super
(
id:
999
,
shellPath:
'/'
,
platform:
platform
,
fileSystem:
fileSystem
,
processManager:
processManager
,
logger:
MockLogger
(),
debuggingOptions:
DebuggingOptions
.
enabled
(
const
BuildInfo
(
BuildMode
.
debug
,
''
,
treeShakeIcons:
false
,
packagesPath:
'.dart_tool/package_config.json'
,
),
startPaused:
false
,
disableDds:
false
,
disableServiceAuthCodes:
false
,
hostVmServicePort:
1234
,
nullAssertions:
false
,
dartEntrypointArgs:
dartEntrypointArgs
,
),
enableObservatory:
enableObservatory
,
machine:
false
,
host:
InternetAddress
.
loopbackIPv6
,
buildTestAssets:
false
,
flutterProject:
null
,
icudtlPath:
null
,
compileExpression:
null
,
fontConfigManager:
FontConfigManager
(),
);
final
Completer
<
Uri
>
_ddsServiceUriCompleter
=
Completer
<
Uri
>();
Future
<
Uri
>
ddsServiceUriFuture
()
=>
_ddsServiceUriCompleter
.
future
;
@override
Future
<
DartDevelopmentService
>
startDds
(
Uri
uri
)
async
{
_ddsServiceUriCompleter
.
complete
(
uri
);
final
MockDartDevelopmentService
mock
=
MockDartDevelopmentService
();
when
(
mock
.
uri
).
thenReturn
(
Uri
.
parse
(
'http://localhost:
${debuggingOptions.hostVmServicePort}
'
));
return
mock
;
}
@override
Future
<
HttpServer
>
bind
(
InternetAddress
host
,
int
port
)
async
=>
MockHttpServer
();
@override
Future
<
StreamChannel
<
String
>>
get
remoteChannel
async
=>
StreamChannelController
<
String
>().
foreign
;
}
class
MockDartDevelopmentService
extends
Mock
implements
DartDevelopmentService
{}
class
MockHttpServer
extends
Mock
implements
HttpServer
{}
class
MockLogger
extends
Mock
implements
Logger
{}
class
MockProcessManager
extends
Mock
implements
ProcessManager
{}
class
MockProcess
extends
Mock
implements
Process
{
@override
Future
<
int
>
get
exitCode
async
=>
0
;
@override
Stream
<
List
<
int
>>
get
stdout
=>
const
Stream
<
List
<
int
>>.
empty
();
@override
Stream
<
List
<
int
>>
get
stderr
=>
const
Stream
<
List
<
int
>>.
empty
();
}
packages/flutter_tools/test/general.shard/test/event_printer_test.dart
View file @
9e55af52
...
...
@@ -5,19 +5,22 @@
// @dart = 2.8
import
'package:flutter_tools/src/test/event_printer.dart'
;
import
'package:flutter_tools/src/test/watcher.dart'
;
import
'package:flutter_tools/src/test/test_device.dart'
;
import
'package:mockito/mockito.dart'
;
import
'../../src/common.dart'
;
import
'../../src/fakes.dart'
;
void
main
(
)
{
testWithoutContext
(
'EventPrinter handles a null parent'
,
()
{
final
EventPrinter
eventPrinter
=
EventPrinter
(
out:
StringBuffer
());
final
ProcessEvent
processEvent
=
ProcessEvent
(
0
,
FakeProcess
());
final
_Device
device
=
_Device
();
final
Uri
observatoryUri
=
Uri
.
parse
(
'http://localhost:1234'
);
expect
(()
=>
eventPrinter
.
handleFinishedTest
(
processEvent
),
returnsNormally
);
expect
(()
=>
eventPrinter
.
handleStarted
Process
(
processEvent
),
returnsNormally
);
expect
(()
=>
eventPrinter
.
handleTestCrashed
(
processEvent
),
returnsNormally
);
expect
(()
=>
eventPrinter
.
handleTestTimedOut
(
processEvent
),
returnsNormally
);
expect
(()
=>
eventPrinter
.
handleFinishedTest
(
device
),
returnsNormally
);
expect
(()
=>
eventPrinter
.
handleStarted
Device
(
observatoryUri
),
returnsNormally
);
expect
(()
=>
eventPrinter
.
handleTestCrashed
(
device
),
returnsNormally
);
expect
(()
=>
eventPrinter
.
handleTestTimedOut
(
device
),
returnsNormally
);
});
}
class
_Device
extends
Mock
implements
TestDevice
{}
packages/flutter_tools/test/integration.shard/test_test.dart
View file @
9e55af52
...
...
@@ -168,7 +168,7 @@ void main() {
extraArguments:
const
<
String
>[
'--verbose'
]);
final
String
stdout
=
result
.
stdout
as
String
;
if
((!
stdout
.
contains
(
'+1: All tests passed'
))
||
(!
stdout
.
contains
(
'test 0:
starting shell process
'
))
||
(!
stdout
.
contains
(
'test 0:
Starting flutter_tester process with command
'
))
||
(!
stdout
.
contains
(
'test 0: deleting temporary directory'
))
||
(!
stdout
.
contains
(
'test 0: finished'
))
||
(!
stdout
.
contains
(
'test package returned with exit code 0'
)))
{
...
...
@@ -185,7 +185,7 @@ void main() {
extraArguments:
const
<
String
>[
'--verbose'
]);
final
String
stdout
=
result
.
stdout
as
String
;
if
((!
stdout
.
contains
(
'+2: All tests passed'
))
||
(!
stdout
.
contains
(
'test 0:
starting shell process
'
))
||
(!
stdout
.
contains
(
'test 0:
Starting flutter_tester process with command
'
))
||
(!
stdout
.
contains
(
'test 0: deleting temporary directory'
))
||
(!
stdout
.
contains
(
'test 0: finished'
))
||
(!
stdout
.
contains
(
'test package returned with exit code 0'
)))
{
...
...
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