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
f4d26a3b
Unverified
Commit
f4d26a3b
authored
Jun 01, 2020
by
Jenn Magder
Committed by
GitHub
Jun 01, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Change iOS device discovery from polling to long-running observation (#58137)
parent
bbb95e57
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
442 additions
and
46 deletions
+442
-46
utils.dart
packages/flutter_tools/lib/src/base/utils.dart
+6
-0
daemon.dart
packages/flutter_tools/lib/src/commands/daemon.dart
+7
-5
device.dart
packages/flutter_tools/lib/src/device.dart
+17
-12
devices.dart
packages/flutter_tools/lib/src/ios/devices.dart
+63
-0
xcode.dart
packages/flutter_tools/lib/src/macos/xcode.dart
+119
-2
base_utils_test.dart
...ges/flutter_tools/test/general.shard/base_utils_test.dart
+10
-4
device_test.dart
packages/flutter_tools/test/general.shard/device_test.dart
+3
-3
devices_test.dart
...es/flutter_tools/test/general.shard/ios/devices_test.dart
+161
-19
xcode_test.dart
...es/flutter_tools/test/general.shard/macos/xcode_test.dart
+56
-1
No files found.
packages/flutter_tools/lib/src/base/utils.dart
View file @
f4d26a3b
...
@@ -104,6 +104,12 @@ class ItemListNotifier<T> {
...
@@ -104,6 +104,12 @@ class ItemListNotifier<T> {
removedItems
.
forEach
(
_removedController
.
add
);
removedItems
.
forEach
(
_removedController
.
add
);
}
}
void
removeItem
(
T
item
)
{
if
(
_items
.
remove
(
item
))
{
_removedController
.
add
(
item
);
}
}
/// Close the streams.
/// Close the streams.
void
dispose
()
{
void
dispose
()
{
_addedController
.
close
();
_addedController
.
close
();
...
...
packages/flutter_tools/lib/src/commands/daemon.dart
View file @
f4d26a3b
...
@@ -790,18 +790,20 @@ class DeviceDomain extends Domain {
...
@@ -790,18 +790,20 @@ class DeviceDomain extends Domain {
/// Enable device events.
/// Enable device events.
Future
<
void
>
enable
(
Map
<
String
,
dynamic
>
args
)
{
Future
<
void
>
enable
(
Map
<
String
,
dynamic
>
args
)
{
final
List
<
Future
<
void
>>
calls
=
<
Future
<
void
>>[];
for
(
final
PollingDeviceDiscovery
discoverer
in
_discoverers
)
{
for
(
final
PollingDeviceDiscovery
discoverer
in
_discoverers
)
{
discoverer
.
startPolling
(
);
calls
.
add
(
discoverer
.
startPolling
()
);
}
}
return
Future
<
void
>.
value
(
);
return
Future
.
wait
<
void
>(
calls
);
}
}
/// Disable device events.
/// Disable device events.
Future
<
void
>
disable
(
Map
<
String
,
dynamic
>
args
)
{
Future
<
void
>
disable
(
Map
<
String
,
dynamic
>
args
)
async
{
final
List
<
Future
<
void
>>
calls
=
<
Future
<
void
>>[];
for
(
final
PollingDeviceDiscovery
discoverer
in
_discoverers
)
{
for
(
final
PollingDeviceDiscovery
discoverer
in
_discoverers
)
{
discoverer
.
stopPolling
(
);
calls
.
add
(
discoverer
.
stopPolling
()
);
}
}
return
Future
<
void
>.
value
(
);
return
Future
.
wait
<
void
>(
calls
);
}
}
/// Forward a host port to a device port.
/// Forward a host port to a device port.
...
...
packages/flutter_tools/lib/src/device.dart
View file @
f4d26a3b
...
@@ -82,6 +82,7 @@ class DeviceManager {
...
@@ -82,6 +82,7 @@ class DeviceManager {
platform:
globals
.
platform
,
platform:
globals
.
platform
,
xcdevice:
globals
.
xcdevice
,
xcdevice:
globals
.
xcdevice
,
iosWorkflow:
globals
.
iosWorkflow
,
iosWorkflow:
globals
.
iosWorkflow
,
logger:
globals
.
logger
,
),
),
IOSSimulators
(
iosSimulatorUtils:
globals
.
iosSimulatorUtils
),
IOSSimulators
(
iosSimulatorUtils:
globals
.
iosSimulatorUtils
),
FuchsiaDevices
(
FuchsiaDevices
(
...
@@ -289,14 +290,18 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
...
@@ -289,14 +290,18 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
static
const
Duration
_pollingTimeout
=
Duration
(
seconds:
30
);
static
const
Duration
_pollingTimeout
=
Duration
(
seconds:
30
);
final
String
name
;
final
String
name
;
ItemListNotifier
<
Device
>
_items
;
@protected
@visibleForTesting
ItemListNotifier
<
Device
>
deviceNotifier
;
Timer
_timer
;
Timer
_timer
;
Future
<
List
<
Device
>>
pollingGetDevices
({
Duration
timeout
});
Future
<
List
<
Device
>>
pollingGetDevices
({
Duration
timeout
});
void
startPolling
()
{
Future
<
void
>
startPolling
()
async
{
if
(
_timer
==
null
)
{
if
(
_timer
==
null
)
{
_items
??=
ItemListNotifier
<
Device
>();
deviceNotifier
??=
ItemListNotifier
<
Device
>();
// Make initial population the default, fast polling timeout.
// Make initial population the default, fast polling timeout.
_timer
=
_initTimer
(
null
);
_timer
=
_initTimer
(
null
);
}
}
...
@@ -306,7 +311,7 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
...
@@ -306,7 +311,7 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
return
Timer
(
_pollingInterval
,
()
async
{
return
Timer
(
_pollingInterval
,
()
async
{
try
{
try
{
final
List
<
Device
>
devices
=
await
pollingGetDevices
(
timeout:
pollingTimeout
);
final
List
<
Device
>
devices
=
await
pollingGetDevices
(
timeout:
pollingTimeout
);
_items
.
updateWithNewList
(
devices
);
deviceNotifier
.
updateWithNewList
(
devices
);
}
on
TimeoutException
{
}
on
TimeoutException
{
globals
.
printTrace
(
'Device poll timed out. Will retry.'
);
globals
.
printTrace
(
'Device poll timed out. Will retry.'
);
}
}
...
@@ -315,7 +320,7 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
...
@@ -315,7 +320,7 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
});
});
}
}
void
stopPolling
()
{
Future
<
void
>
stopPolling
()
async
{
_timer
?.
cancel
();
_timer
?.
cancel
();
_timer
=
null
;
_timer
=
null
;
}
}
...
@@ -327,23 +332,23 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
...
@@ -327,23 +332,23 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
@override
@override
Future
<
List
<
Device
>>
discoverDevices
({
Duration
timeout
})
async
{
Future
<
List
<
Device
>>
discoverDevices
({
Duration
timeout
})
async
{
_items
=
null
;
deviceNotifier
=
null
;
return
_populateDevices
(
timeout:
timeout
);
return
_populateDevices
(
timeout:
timeout
);
}
}
Future
<
List
<
Device
>>
_populateDevices
({
Duration
timeout
})
async
{
Future
<
List
<
Device
>>
_populateDevices
({
Duration
timeout
})
async
{
_items
??=
ItemListNotifier
<
Device
>.
from
(
await
pollingGetDevices
(
timeout:
timeout
));
deviceNotifier
??=
ItemListNotifier
<
Device
>.
from
(
await
pollingGetDevices
(
timeout:
timeout
));
return
_items
.
items
;
return
deviceNotifier
.
items
;
}
}
Stream
<
Device
>
get
onAdded
{
Stream
<
Device
>
get
onAdded
{
_items
??=
ItemListNotifier
<
Device
>();
deviceNotifier
??=
ItemListNotifier
<
Device
>();
return
_items
.
onAdded
;
return
deviceNotifier
.
onAdded
;
}
}
Stream
<
Device
>
get
onRemoved
{
Stream
<
Device
>
get
onRemoved
{
_items
??=
ItemListNotifier
<
Device
>();
deviceNotifier
??=
ItemListNotifier
<
Device
>();
return
_items
.
onRemoved
;
return
deviceNotifier
.
onRemoved
;
}
}
void
dispose
()
=>
stopPolling
();
void
dispose
()
=>
stopPolling
();
...
...
packages/flutter_tools/lib/src/ios/devices.dart
View file @
f4d26a3b
...
@@ -16,6 +16,7 @@ import '../base/io.dart';
...
@@ -16,6 +16,7 @@ import '../base/io.dart';
import
'../base/logger.dart'
;
import
'../base/logger.dart'
;
import
'../base/platform.dart'
;
import
'../base/platform.dart'
;
import
'../base/process.dart'
;
import
'../base/process.dart'
;
import
'../base/utils.dart'
;
import
'../build_info.dart'
;
import
'../build_info.dart'
;
import
'../convert.dart'
;
import
'../convert.dart'
;
import
'../device.dart'
;
import
'../device.dart'
;
...
@@ -35,14 +36,22 @@ class IOSDevices extends PollingDeviceDiscovery {
...
@@ -35,14 +36,22 @@ class IOSDevices extends PollingDeviceDiscovery {
Platform
platform
,
Platform
platform
,
XCDevice
xcdevice
,
XCDevice
xcdevice
,
IOSWorkflow
iosWorkflow
,
IOSWorkflow
iosWorkflow
,
Logger
logger
,
})
:
_platform
=
platform
??
globals
.
platform
,
})
:
_platform
=
platform
??
globals
.
platform
,
_xcdevice
=
xcdevice
??
globals
.
xcdevice
,
_xcdevice
=
xcdevice
??
globals
.
xcdevice
,
_iosWorkflow
=
iosWorkflow
??
globals
.
iosWorkflow
,
_iosWorkflow
=
iosWorkflow
??
globals
.
iosWorkflow
,
_logger
=
logger
??
globals
.
logger
,
super
(
'iOS devices'
);
super
(
'iOS devices'
);
@override
void
dispose
()
{
_observedDeviceEventsSubscription
?.
cancel
();
}
final
Platform
_platform
;
final
Platform
_platform
;
final
XCDevice
_xcdevice
;
final
XCDevice
_xcdevice
;
final
IOSWorkflow
_iosWorkflow
;
final
IOSWorkflow
_iosWorkflow
;
final
Logger
_logger
;
@override
@override
bool
get
supportsPlatform
=>
_platform
.
isMacOS
;
bool
get
supportsPlatform
=>
_platform
.
isMacOS
;
...
@@ -50,6 +59,60 @@ class IOSDevices extends PollingDeviceDiscovery {
...
@@ -50,6 +59,60 @@ class IOSDevices extends PollingDeviceDiscovery {
@override
@override
bool
get
canListAnything
=>
_iosWorkflow
.
canListDevices
;
bool
get
canListAnything
=>
_iosWorkflow
.
canListDevices
;
StreamSubscription
<
Map
<
XCDeviceEvent
,
String
>>
_observedDeviceEventsSubscription
;
@override
Future
<
void
>
startPolling
()
async
{
if
(!
_platform
.
isMacOS
)
{
throw
UnsupportedError
(
'Control of iOS devices or simulators only supported on macOS.'
);
}
deviceNotifier
??=
ItemListNotifier
<
Device
>();
// Start by populating all currently attached devices.
deviceNotifier
.
updateWithNewList
(
await
pollingGetDevices
());
// cancel any outstanding subscriptions.
await
_observedDeviceEventsSubscription
?.
cancel
();
_observedDeviceEventsSubscription
=
_xcdevice
.
observedDeviceEvents
().
listen
(
_onDeviceEvent
,
onError:
(
dynamic
error
,
StackTrace
stack
)
{
_logger
.
printTrace
(
'Process exception running xcdevice observe:
\n
$error
\n
$stack
'
);
},
onDone:
()
{
// If xcdevice is killed or otherwise dies, polling will be stopped.
// No retry is attempted and the polling client will have to restart polling
// (restart the IDE). Avoid hammering on a process that is
// continuously failing.
_logger
.
printTrace
(
'xcdevice observe stopped'
);
},
cancelOnError:
true
,
);
}
Future
<
void
>
_onDeviceEvent
(
Map
<
XCDeviceEvent
,
String
>
event
)
async
{
final
XCDeviceEvent
eventType
=
event
.
containsKey
(
XCDeviceEvent
.
attach
)
?
XCDeviceEvent
.
attach
:
XCDeviceEvent
.
detach
;
final
String
deviceIdentifier
=
event
[
eventType
];
final
Device
knownDevice
=
deviceNotifier
.
items
.
firstWhere
((
Device
device
)
=>
device
.
id
==
deviceIdentifier
,
orElse:
()
=>
null
);
// Ignore already discovered devices (maybe populated at the beginning).
if
(
eventType
==
XCDeviceEvent
.
attach
&&
knownDevice
==
null
)
{
// There's no way to get details for an individual attached device,
// so repopulate them all.
final
List
<
Device
>
devices
=
await
pollingGetDevices
();
deviceNotifier
.
updateWithNewList
(
devices
);
}
else
if
(
eventType
==
XCDeviceEvent
.
detach
&&
knownDevice
!=
null
)
{
deviceNotifier
.
removeItem
(
knownDevice
);
}
}
@override
Future
<
void
>
stopPolling
()
async
{
await
_observedDeviceEventsSubscription
?.
cancel
();
}
@override
@override
Future
<
List
<
Device
>>
pollingGetDevices
({
Duration
timeout
})
async
{
Future
<
List
<
Device
>>
pollingGetDevices
({
Duration
timeout
})
async
{
if
(!
_platform
.
isMacOS
)
{
if
(!
_platform
.
isMacOS
)
{
...
...
packages/flutter_tools/lib/src/macos/xcode.dart
View file @
f4d26a3b
...
@@ -194,6 +194,11 @@ class Xcode {
...
@@ -194,6 +194,11 @@ class Xcode {
}
}
}
}
enum
XCDeviceEvent
{
attach
,
detach
,
}
/// A utility class for interacting with Xcode xcdevice command line tools.
/// A utility class for interacting with Xcode xcdevice command line tools.
class
XCDevice
{
class
XCDevice
{
XCDevice
({
XCDevice
({
...
@@ -218,7 +223,14 @@ class XCDevice {
...
@@ -218,7 +223,14 @@ class XCDevice {
platform:
platform
,
platform:
platform
,
processManager:
processManager
,
processManager:
processManager
,
),
),
_xcode
=
xcode
;
_xcode
=
xcode
{
_setupDeviceIdentifierByEventStream
();
}
void
dispose
()
{
_deviceObservationProcess
?.
kill
();
}
final
ProcessUtils
_processUtils
;
final
ProcessUtils
_processUtils
;
final
Logger
_logger
;
final
Logger
_logger
;
...
@@ -226,6 +238,19 @@ class XCDevice {
...
@@ -226,6 +238,19 @@ class XCDevice {
final
IOSDeploy
_iosDeploy
;
final
IOSDeploy
_iosDeploy
;
final
Xcode
_xcode
;
final
Xcode
_xcode
;
List
<
dynamic
>
_cachedListResults
;
Process
_deviceObservationProcess
;
StreamController
<
Map
<
XCDeviceEvent
,
String
>>
_deviceIdentifierByEvent
;
void
_setupDeviceIdentifierByEventStream
()
{
// _deviceIdentifierByEvent Should always be available for listeners
// in case polling needs to be stopped and restarted.
_deviceIdentifierByEvent
=
StreamController
<
Map
<
XCDeviceEvent
,
String
>>.
broadcast
(
onListen:
_startObservingTetheredIOSDevices
,
onCancel:
_stopObservingTetheredIOSDevices
,
);
}
bool
get
isInstalled
=>
_xcode
.
isInstalledAndMeetsVersionCheck
&&
xcdevicePath
!=
null
;
bool
get
isInstalled
=>
_xcode
.
isInstalledAndMeetsVersionCheck
&&
xcdevicePath
!=
null
;
String
_xcdevicePath
;
String
_xcdevicePath
;
...
@@ -287,7 +312,99 @@ class XCDevice {
...
@@ -287,7 +312,99 @@ class XCDevice {
return
null
;
return
null
;
}
}
List
<
dynamic
>
_cachedListResults
;
/// Observe identifiers (UDIDs) of devices as they attach and detach.
///
/// Each attach and detach event is a tuple of one event type
/// and identifier.
Stream
<
Map
<
XCDeviceEvent
,
String
>>
observedDeviceEvents
()
{
if
(!
isInstalled
)
{
_logger
.
printTrace
(
"Xcode not found. Run 'flutter doctor' for more information."
);
return
null
;
}
return
_deviceIdentifierByEvent
.
stream
;
}
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
// Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
final
RegExp
_observationIdentifierPattern
=
RegExp
(
r'^(\w*): (\w*)$'
);
Future
<
void
>
_startObservingTetheredIOSDevices
()
async
{
try
{
if
(
_deviceObservationProcess
!=
null
)
{
throw
Exception
(
'xcdevice observe restart failed'
);
}
// Run in interactive mode (via script) to convince
// xcdevice it has a terminal attached in order to redirect stdout.
_deviceObservationProcess
=
await
_processUtils
.
start
(
<
String
>[
'script'
,
'-t'
,
'0'
,
'/dev/null'
,
'xcrun'
,
'xcdevice'
,
'observe'
,
'--both'
,
],
);
final
StreamSubscription
<
String
>
stdoutSubscription
=
_deviceObservationProcess
.
stdout
.
transform
<
String
>(
utf8
.
decoder
)
.
transform
<
String
>(
const
LineSplitter
())
.
listen
((
String
line
)
{
// xcdevice observe example output of UDIDs:
//
// Listening for all devices, on both interfaces.
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
// Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
final
RegExpMatch
match
=
_observationIdentifierPattern
.
firstMatch
(
line
);
if
(
match
!=
null
&&
match
.
groupCount
==
2
)
{
final
String
verb
=
match
.
group
(
1
).
toLowerCase
();
final
String
identifier
=
match
.
group
(
2
);
if
(
verb
.
startsWith
(
'attach'
))
{
_deviceIdentifierByEvent
.
add
(<
XCDeviceEvent
,
String
>{
XCDeviceEvent
.
attach
:
identifier
});
}
else
if
(
verb
.
startsWith
(
'detach'
))
{
_deviceIdentifierByEvent
.
add
(<
XCDeviceEvent
,
String
>{
XCDeviceEvent
.
detach
:
identifier
});
}
}
});
final
StreamSubscription
<
String
>
stderrSubscription
=
_deviceObservationProcess
.
stderr
.
transform
<
String
>(
utf8
.
decoder
)
.
transform
<
String
>(
const
LineSplitter
())
.
listen
((
String
line
)
{
_logger
.
printTrace
(
'xcdevice observe error:
$line
'
);
});
unawaited
(
_deviceObservationProcess
.
exitCode
.
then
((
int
status
)
{
_logger
.
printTrace
(
'xcdevice exited with code
$exitCode
'
);
unawaited
(
stdoutSubscription
.
cancel
());
unawaited
(
stderrSubscription
.
cancel
());
}).
whenComplete
(()
async
{
if
(
_deviceIdentifierByEvent
.
hasListener
)
{
// Tell listeners the process died.
await
_deviceIdentifierByEvent
.
close
();
}
_deviceObservationProcess
=
null
;
// Reopen it so new listeners can resume polling.
_setupDeviceIdentifierByEventStream
();
}));
}
on
ProcessException
catch
(
exception
,
stackTrace
)
{
_deviceIdentifierByEvent
.
addError
(
exception
,
stackTrace
);
}
on
ArgumentError
catch
(
exception
,
stackTrace
)
{
_deviceIdentifierByEvent
.
addError
(
exception
,
stackTrace
);
}
}
void
_stopObservingTetheredIOSDevices
()
{
_deviceObservationProcess
?.
kill
();
}
/// [timeout] defaults to 2 seconds.
/// [timeout] defaults to 2 seconds.
Future
<
List
<
IOSDevice
>>
getAvailableIOSDevices
({
Duration
timeout
})
async
{
Future
<
List
<
IOSDevice
>>
getAvailableIOSDevices
({
Duration
timeout
})
async
{
...
...
packages/flutter_tools/test/general.shard/base_utils_test.dart
View file @
f4d26a3b
...
@@ -18,19 +18,25 @@ void main() {
...
@@ -18,19 +18,25 @@ void main() {
final
Future
<
List
<
String
>>
removedStreamItems
=
list
.
onRemoved
.
toList
();
final
Future
<
List
<
String
>>
removedStreamItems
=
list
.
onRemoved
.
toList
();
list
.
updateWithNewList
(<
String
>[
'aaa'
]);
list
.
updateWithNewList
(<
String
>[
'aaa'
]);
list
.
updateWithNewList
(<
String
>[
'aaa'
,
'bbb'
]);
list
.
removeItem
(
'bogus'
);
list
.
updateWithNewList
(<
String
>[
'bbb'
]);
list
.
updateWithNewList
(<
String
>[
'aaa'
,
'bbb'
,
'ccc'
]);
list
.
updateWithNewList
(<
String
>[
'bbb'
,
'ccc'
]);
list
.
removeItem
(
'bbb'
);
expect
(
list
.
items
,
<
String
>[
'ccc'
]);
list
.
dispose
();
list
.
dispose
();
final
List
<
String
>
addedItems
=
await
addedStreamItems
;
final
List
<
String
>
addedItems
=
await
addedStreamItems
;
final
List
<
String
>
removedItems
=
await
removedStreamItems
;
final
List
<
String
>
removedItems
=
await
removedStreamItems
;
expect
(
addedItems
.
length
,
2
);
expect
(
addedItems
.
length
,
3
);
expect
(
addedItems
.
first
,
'aaa'
);
expect
(
addedItems
.
first
,
'aaa'
);
expect
(
addedItems
[
1
],
'bbb'
);
expect
(
addedItems
[
1
],
'bbb'
);
expect
(
addedItems
[
2
],
'ccc'
);
expect
(
removedItems
.
length
,
1
);
expect
(
removedItems
.
length
,
2
);
expect
(
removedItems
.
first
,
'aaa'
);
expect
(
removedItems
.
first
,
'aaa'
);
expect
(
removedItems
[
1
],
'bbb'
);
});
});
});
});
}
}
packages/flutter_tools/test/general.shard/device_test.dart
View file @
f4d26a3b
...
@@ -66,9 +66,9 @@ void main() {
...
@@ -66,9 +66,9 @@ void main() {
group
(
'PollingDeviceDiscovery'
,
()
{
group
(
'PollingDeviceDiscovery'
,
()
{
testUsingContext
(
'startPolling'
,
()
async
{
testUsingContext
(
'startPolling'
,
()
async
{
FakeAsync
().
run
((
FakeAsync
time
)
{
FakeAsync
().
run
((
FakeAsync
time
)
async
{
final
FakePollingDeviceDiscovery
pollingDeviceDiscovery
=
FakePollingDeviceDiscovery
();
final
FakePollingDeviceDiscovery
pollingDeviceDiscovery
=
FakePollingDeviceDiscovery
();
pollingDeviceDiscovery
.
startPolling
();
await
pollingDeviceDiscovery
.
startPolling
();
time
.
elapse
(
const
Duration
(
milliseconds:
4001
));
time
.
elapse
(
const
Duration
(
milliseconds:
4001
));
time
.
flushMicrotasks
();
time
.
flushMicrotasks
();
// First check should use the default polling timeout
// First check should use the default polling timeout
...
@@ -79,7 +79,7 @@ void main() {
...
@@ -79,7 +79,7 @@ void main() {
time
.
flushMicrotasks
();
time
.
flushMicrotasks
();
// Subsequent polling should be much longer.
// Subsequent polling should be much longer.
expect
(
pollingDeviceDiscovery
.
lastPollingTimeout
,
const
Duration
(
seconds:
30
));
expect
(
pollingDeviceDiscovery
.
lastPollingTimeout
,
const
Duration
(
seconds:
30
));
pollingDeviceDiscovery
.
stopPolling
();
await
pollingDeviceDiscovery
.
stopPolling
();
});
});
});
});
});
});
...
...
packages/flutter_tools/test/general.shard/ios/devices_test.dart
View file @
f4d26a3b
...
@@ -262,15 +262,17 @@ void main() {
...
@@ -262,15 +262,17 @@ void main() {
});
});
});
});
group
(
'polling
GetDevices
'
,
()
{
group
(
'polling'
,
()
{
MockXcdevice
mockXcdevice
;
MockXcdevice
mockXcdevice
;
MockArtifacts
mockArtifacts
;
MockArtifacts
mockArtifacts
;
MockCache
mockCache
;
MockCache
mockCache
;
FakeProcessManager
fakeProcessManager
;
FakeProcessManager
fakeProcessManager
;
Logger
logger
;
Buffer
Logger
logger
;
IOSDeploy
iosDeploy
;
IOSDeploy
iosDeploy
;
IMobileDevice
iMobileDevice
;
IMobileDevice
iMobileDevice
;
IOSWorkflow
mockIosWorkflow
;
IOSWorkflow
mockIosWorkflow
;
IOSDevice
device1
;
IOSDevice
device2
;
setUp
(()
{
setUp
(()
{
mockXcdevice
=
MockXcdevice
();
mockXcdevice
=
MockXcdevice
();
...
@@ -292,15 +294,163 @@ void main() {
...
@@ -292,15 +294,163 @@ void main() {
processManager:
fakeProcessManager
,
processManager:
fakeProcessManager
,
logger:
logger
,
logger:
logger
,
);
);
device1
=
IOSDevice
(
'd83d5bc53967baa0ee18626ba87b6254b2ab5418'
,
name:
'Paired iPhone'
,
sdkVersion:
'13.3'
,
cpuArchitecture:
DarwinArch
.
arm64
,
artifacts:
mockArtifacts
,
iosDeploy:
iosDeploy
,
iMobileDevice:
iMobileDevice
,
logger:
logger
,
platform:
macPlatform
,
fileSystem:
MemoryFileSystem
.
test
(),
interfaceType:
IOSDeviceInterface
.
usb
,
);
device2
=
IOSDevice
(
'43ad2fda7991b34fe1acbda82f9e2fd3d6ddc9f7'
,
name:
'iPhone 6s'
,
sdkVersion:
'13.3'
,
cpuArchitecture:
DarwinArch
.
arm64
,
artifacts:
mockArtifacts
,
iosDeploy:
iosDeploy
,
iMobileDevice:
iMobileDevice
,
logger:
logger
,
platform:
macPlatform
,
fileSystem:
MemoryFileSystem
.
test
(),
interfaceType:
IOSDeviceInterface
.
usb
,
);
});
testWithoutContext
(
'start polling'
,
()
async
{
final
IOSDevices
iosDevices
=
IOSDevices
(
platform:
macPlatform
,
xcdevice:
mockXcdevice
,
iosWorkflow:
mockIosWorkflow
,
logger:
logger
,
);
when
(
mockXcdevice
.
isInstalled
).
thenReturn
(
true
);
int
fetchDevicesCount
=
0
;
when
(
mockXcdevice
.
getAvailableIOSDevices
())
.
thenAnswer
((
Invocation
invocation
)
{
if
(
fetchDevicesCount
==
0
)
{
// Initial time, no devices.
fetchDevicesCount
++;
return
Future
<
List
<
IOSDevice
>>.
value
(<
IOSDevice
>[]);
}
else
if
(
fetchDevicesCount
==
1
)
{
// Simulate 2 devices added later.
fetchDevicesCount
++;
return
Future
<
List
<
IOSDevice
>>.
value
(<
IOSDevice
>[
device1
,
device2
]);
}
fail
(
'Too many calls to getAvailableTetheredIOSDevices'
);
});
int
addedCount
=
0
;
final
Completer
<
void
>
added
=
Completer
<
void
>();
iosDevices
.
onAdded
.
listen
((
Device
device
)
{
addedCount
++;
// 2 devices will be added.
// Will throw over-completion if called more than twice.
if
(
addedCount
>=
2
)
{
added
.
complete
();
}
});
final
Completer
<
void
>
removed
=
Completer
<
void
>();
iosDevices
.
onRemoved
.
listen
((
Device
device
)
{
// Will throw over-completion if called more than once.
removed
.
complete
();
});
final
StreamController
<
Map
<
XCDeviceEvent
,
String
>>
eventStream
=
StreamController
<
Map
<
XCDeviceEvent
,
String
>>();
when
(
mockXcdevice
.
observedDeviceEvents
()).
thenAnswer
((
_
)
=>
eventStream
.
stream
);
await
iosDevices
.
startPolling
();
verify
(
mockXcdevice
.
getAvailableIOSDevices
()).
called
(
1
);
expect
(
iosDevices
.
deviceNotifier
.
items
,
isEmpty
);
expect
(
eventStream
.
hasListener
,
isTrue
);
eventStream
.
add
(<
XCDeviceEvent
,
String
>{
XCDeviceEvent
.
attach
:
'd83d5bc53967baa0ee18626ba87b6254b2ab5418'
});
await
added
.
future
;
expect
(
iosDevices
.
deviceNotifier
.
items
.
length
,
2
);
expect
(
iosDevices
.
deviceNotifier
.
items
,
contains
(
device1
));
expect
(
iosDevices
.
deviceNotifier
.
items
,
contains
(
device2
));
eventStream
.
add
(<
XCDeviceEvent
,
String
>{
XCDeviceEvent
.
detach
:
'd83d5bc53967baa0ee18626ba87b6254b2ab5418'
});
await
removed
.
future
;
expect
(
iosDevices
.
deviceNotifier
.
items
,
<
Device
>[
device2
]);
// Remove stream will throw over-completion if called more than once
// which proves this is ignored.
eventStream
.
add
(<
XCDeviceEvent
,
String
>{
XCDeviceEvent
.
detach
:
'bogus'
});
expect
(
addedCount
,
2
);
await
iosDevices
.
stopPolling
();
expect
(
eventStream
.
hasListener
,
isFalse
);
});
testWithoutContext
(
'polling can be restarted if stream is closed'
,
()
async
{
final
IOSDevices
iosDevices
=
IOSDevices
(
platform:
macPlatform
,
xcdevice:
mockXcdevice
,
iosWorkflow:
mockIosWorkflow
,
logger:
logger
,
);
when
(
mockXcdevice
.
isInstalled
).
thenReturn
(
true
);
when
(
mockXcdevice
.
getAvailableIOSDevices
())
.
thenAnswer
((
Invocation
invocation
)
=>
Future
<
List
<
IOSDevice
>>.
value
(<
IOSDevice
>[]));
final
StreamController
<
Map
<
XCDeviceEvent
,
String
>>
eventStream
=
StreamController
<
Map
<
XCDeviceEvent
,
String
>>();
final
StreamController
<
Map
<
XCDeviceEvent
,
String
>>
rescheduledStream
=
StreamController
<
Map
<
XCDeviceEvent
,
String
>>();
bool
reschedule
=
false
;
when
(
mockXcdevice
.
observedDeviceEvents
()).
thenAnswer
((
Invocation
invocation
)
{
if
(!
reschedule
)
{
reschedule
=
true
;
return
eventStream
.
stream
;
}
return
rescheduledStream
.
stream
;
});
await
iosDevices
.
startPolling
();
expect
(
eventStream
.
hasListener
,
isTrue
);
verify
(
mockXcdevice
.
getAvailableIOSDevices
()).
called
(
1
);
// Pretend xcdevice crashed.
await
eventStream
.
close
();
expect
(
logger
.
traceText
,
contains
(
'xcdevice observe stopped'
));
// Confirm a restart still gets streamed events.
await
iosDevices
.
startPolling
();
expect
(
eventStream
.
hasListener
,
isFalse
);
expect
(
rescheduledStream
.
hasListener
,
isTrue
);
await
iosDevices
.
stopPolling
();
expect
(
rescheduledStream
.
hasListener
,
isFalse
);
});
});
final
List
<
Platform
>
unsupportedPlatforms
=
<
Platform
>[
linuxPlatform
,
windowsPlatform
];
final
List
<
Platform
>
unsupportedPlatforms
=
<
Platform
>[
linuxPlatform
,
windowsPlatform
];
for
(
final
Platform
unsupportedPlatform
in
unsupportedPlatforms
)
{
for
(
final
Platform
unsupportedPlatform
in
unsupportedPlatforms
)
{
testWithoutContext
(
'throws Unsupported Operation exception on
${unsupportedPlatform.operatingSystem}
'
,
()
async
{
testWithoutContext
(
'
pollingGetDevices
throws Unsupported Operation exception on
${unsupportedPlatform.operatingSystem}
'
,
()
async
{
final
IOSDevices
iosDevices
=
IOSDevices
(
final
IOSDevices
iosDevices
=
IOSDevices
(
platform:
unsupportedPlatform
,
platform:
unsupportedPlatform
,
xcdevice:
mockXcdevice
,
xcdevice:
mockXcdevice
,
iosWorkflow:
mockIosWorkflow
,
iosWorkflow:
mockIosWorkflow
,
logger:
logger
,
);
);
when
(
mockXcdevice
.
isInstalled
).
thenReturn
(
false
);
when
(
mockXcdevice
.
isInstalled
).
thenReturn
(
false
);
expect
(
expect
(
...
@@ -310,43 +460,33 @@ void main() {
...
@@ -310,43 +460,33 @@ void main() {
});
});
}
}
testWithoutContext
(
'returns attached devices'
,
()
async
{
testWithoutContext
(
'
pollingGetDevices
returns attached devices'
,
()
async
{
final
IOSDevices
iosDevices
=
IOSDevices
(
final
IOSDevices
iosDevices
=
IOSDevices
(
platform:
macPlatform
,
platform:
macPlatform
,
xcdevice:
mockXcdevice
,
xcdevice:
mockXcdevice
,
iosWorkflow:
mockIosWorkflow
,
iosWorkflow:
mockIosWorkflow
,
logger:
logger
,
);
);
when
(
mockXcdevice
.
isInstalled
).
thenReturn
(
true
);
when
(
mockXcdevice
.
isInstalled
).
thenReturn
(
true
);
final
IOSDevice
device
=
IOSDevice
(
'd83d5bc53967baa0ee18626ba87b6254b2ab5418'
,
name:
'Paired iPhone'
,
sdkVersion:
'13.3'
,
cpuArchitecture:
DarwinArch
.
arm64
,
artifacts:
mockArtifacts
,
iosDeploy:
iosDeploy
,
iMobileDevice:
iMobileDevice
,
logger:
logger
,
platform:
macPlatform
,
fileSystem:
MemoryFileSystem
.
test
(),
interfaceType:
IOSDeviceInterface
.
usb
,
);
when
(
mockXcdevice
.
getAvailableIOSDevices
())
when
(
mockXcdevice
.
getAvailableIOSDevices
())
.
thenAnswer
((
Invocation
invocation
)
=>
Future
<
List
<
IOSDevice
>>.
value
(<
IOSDevice
>[
device
]));
.
thenAnswer
((
Invocation
invocation
)
=>
Future
<
List
<
IOSDevice
>>.
value
(<
IOSDevice
>[
device
1
]));
final
List
<
Device
>
devices
=
await
iosDevices
.
pollingGetDevices
();
final
List
<
Device
>
devices
=
await
iosDevices
.
pollingGetDevices
();
expect
(
devices
,
hasLength
(
1
));
expect
(
devices
,
hasLength
(
1
));
expect
(
identical
(
devices
.
first
,
device
),
isTrue
);
expect
(
identical
(
devices
.
first
,
device
1
),
isTrue
);
});
});
});
});
group
(
'getDiagnostics'
,
()
{
group
(
'getDiagnostics'
,
()
{
MockXcdevice
mockXcdevice
;
MockXcdevice
mockXcdevice
;
IOSWorkflow
mockIosWorkflow
;
IOSWorkflow
mockIosWorkflow
;
Logger
logger
;
setUp
(()
{
setUp
(()
{
mockXcdevice
=
MockXcdevice
();
mockXcdevice
=
MockXcdevice
();
mockIosWorkflow
=
MockIOSWorkflow
();
mockIosWorkflow
=
MockIOSWorkflow
();
logger
=
BufferLogger
.
test
();
});
});
final
List
<
Platform
>
unsupportedPlatforms
=
<
Platform
>[
linuxPlatform
,
windowsPlatform
];
final
List
<
Platform
>
unsupportedPlatforms
=
<
Platform
>[
linuxPlatform
,
windowsPlatform
];
...
@@ -356,6 +496,7 @@ void main() {
...
@@ -356,6 +496,7 @@ void main() {
platform:
unsupportedPlatform
,
platform:
unsupportedPlatform
,
xcdevice:
mockXcdevice
,
xcdevice:
mockXcdevice
,
iosWorkflow:
mockIosWorkflow
,
iosWorkflow:
mockIosWorkflow
,
logger:
logger
,
);
);
when
(
mockXcdevice
.
isInstalled
).
thenReturn
(
false
);
when
(
mockXcdevice
.
isInstalled
).
thenReturn
(
false
);
expect
((
await
iosDevices
.
getDiagnostics
()).
first
,
'Control of iOS devices or simulators only supported on macOS.'
);
expect
((
await
iosDevices
.
getDiagnostics
()).
first
,
'Control of iOS devices or simulators only supported on macOS.'
);
...
@@ -367,6 +508,7 @@ void main() {
...
@@ -367,6 +508,7 @@ void main() {
platform:
macPlatform
,
platform:
macPlatform
,
xcdevice:
mockXcdevice
,
xcdevice:
mockXcdevice
,
iosWorkflow:
mockIosWorkflow
,
iosWorkflow:
mockIosWorkflow
,
logger:
logger
,
);
);
when
(
mockXcdevice
.
isInstalled
).
thenReturn
(
true
);
when
(
mockXcdevice
.
isInstalled
).
thenReturn
(
true
);
when
(
mockXcdevice
.
getDiagnostics
())
when
(
mockXcdevice
.
getDiagnostics
())
...
...
packages/flutter_tools/test/general.shard/macos/xcode_test.dart
View file @
f4d26a3b
...
@@ -2,6 +2,8 @@
...
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// found in the LICENSE file.
import
'dart:async'
;
import
'package:file/memory.dart'
;
import
'package:file/memory.dart'
;
import
'package:flutter_tools/src/artifacts.dart'
;
import
'package:flutter_tools/src/artifacts.dart'
;
import
'package:flutter_tools/src/base/io.dart'
show
ProcessException
,
ProcessResult
;
import
'package:flutter_tools/src/base/io.dart'
show
ProcessException
,
ProcessResult
;
...
@@ -19,7 +21,7 @@ import '../../src/common.dart';
...
@@ -19,7 +21,7 @@ import '../../src/common.dart';
import
'../../src/context.dart'
;
import
'../../src/context.dart'
;
void
main
(
)
{
void
main
(
)
{
Logger
logger
;
Buffer
Logger
logger
;
setUp
(()
{
setUp
(()
{
logger
=
BufferLogger
.
test
();
logger
=
BufferLogger
.
test
();
...
@@ -353,6 +355,59 @@ void main() {
...
@@ -353,6 +355,59 @@ void main() {
});
});
});
});
group
(
'observe device events'
,
()
{
testWithoutContext
(
'Xcode not installed'
,
()
async
{
when
(
mockXcode
.
isInstalledAndMeetsVersionCheck
).
thenReturn
(
false
);
expect
(
xcdevice
.
observedDeviceEvents
(),
isNull
);
expect
(
logger
.
traceText
,
contains
(
"Xcode not found. Run 'flutter doctor' for more information."
));
});
testUsingContext
(
'relays events'
,
()
async
{
when
(
mockXcode
.
isInstalledAndMeetsVersionCheck
).
thenReturn
(
true
);
fakeProcessManager
.
addCommand
(
const
FakeCommand
(
command:
<
String
>[
'xcrun'
,
'--find'
,
'xcdevice'
],
stdout:
'/path/to/xcdevice'
,
));
fakeProcessManager
.
addCommand
(
const
FakeCommand
(
command:
<
String
>[
'script'
,
'-t'
,
'0'
,
'/dev/null'
,
'xcrun'
,
'xcdevice'
,
'observe'
,
'--both'
,
],
stdout:
'Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
\n
'
'Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418'
,
stderr:
'Some error'
,
));
final
Completer
<
void
>
attach
=
Completer
<
void
>();
final
Completer
<
void
>
detach
=
Completer
<
void
>();
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
// Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
xcdevice
.
observedDeviceEvents
().
listen
((
Map
<
XCDeviceEvent
,
String
>
event
)
{
expect
(
event
.
length
,
1
);
if
(
event
.
containsKey
(
XCDeviceEvent
.
attach
))
{
expect
(
event
[
XCDeviceEvent
.
attach
],
'd83d5bc53967baa0ee18626ba87b6254b2ab5418'
);
attach
.
complete
();
}
else
if
(
event
.
containsKey
(
XCDeviceEvent
.
detach
))
{
expect
(
event
[
XCDeviceEvent
.
detach
],
'd83d5bc53967baa0ee18626ba87b6254b2ab5418'
);
detach
.
complete
();
}
else
{
fail
(
'Unexpected event'
);
}
});
await
attach
.
future
;
await
detach
.
future
;
expect
(
logger
.
traceText
,
contains
(
'xcdevice observe error: Some error'
));
});
});
group
(
'available devices'
,
()
{
group
(
'available devices'
,
()
{
final
FakePlatform
macPlatform
=
FakePlatform
(
operatingSystem:
'macos'
);
final
FakePlatform
macPlatform
=
FakePlatform
(
operatingSystem:
'macos'
);
...
...
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