Unverified Commit 4fee0079 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Inform user how to fix permissions when the observatory URL isn't found on iOS 14 (#65977)

parent b56a8c56
......@@ -310,6 +310,7 @@ class IOSDevice extends Device {
bool prebuiltApplication = false,
bool ipv6 = false,
@visibleForTesting Duration fallbackPollingDelay,
@visibleForTesting Duration fallbackThrottleTimeout,
String userIdentifier,
}) async {
String packageId;
......@@ -402,6 +403,7 @@ class IOSDevice extends Device {
hostPort: debuggingOptions.hostVmServicePort,
devicePort: debuggingOptions.deviceVmServicePort,
ipv6: ipv6,
throttleTimeout: fallbackThrottleTimeout ?? const Duration(seconds: 1),
);
}
final int installationResult = await _iosDeploy.runApp(
......@@ -441,6 +443,12 @@ class IOSDevice extends Device {
packageName: FlutterProject.current().manifest.appName,
);
if (localUri == null) {
if (majorSdkVersion >= 14) {
_logger.printError('Failed to attach to the observatory.');
_logger.printError(
'Try accepting the local network permissions popup, or enable "Settings > Privacy > Local Network" for your app.');
_logger.printError('For more information, see https://flutter.dev/docs/development/ios-14#debugging-flutter');
}
return LaunchResult.failed();
}
return LaunchResult.succeeded(observatoryUri: localUri);
......
......@@ -18,6 +18,7 @@ class ProtocolDiscovery {
this.serviceName, {
this.portForwarder,
this.throttleDuration,
this.throttleTimeout,
this.hostPort,
this.devicePort,
this.ipv6,
......@@ -34,6 +35,7 @@ class ProtocolDiscovery {
DeviceLogReader logReader, {
DevicePortForwarder portForwarder,
Duration throttleDuration = const Duration(milliseconds: 200),
Duration throttleTimeout,
@required int hostPort,
@required int devicePort,
@required bool ipv6,
......@@ -44,6 +46,7 @@ class ProtocolDiscovery {
kObservatoryService,
portForwarder: portForwarder,
throttleDuration: throttleDuration,
throttleTimeout: throttleTimeout,
hostPort: hostPort,
devicePort: devicePort,
ipv6: ipv6,
......@@ -60,6 +63,11 @@ class ProtocolDiscovery {
/// The time to wait before forwarding a new observatory URIs from [logReader].
final Duration throttleDuration;
/// The time between URIs are discovered before timing out when scraping the [logReader].
///
/// If null, log scanning will continue indefinitely.
final Duration throttleTimeout;
StreamSubscription<String> _deviceLogSubscription;
_BufferedStreamController<Uri> _uriStreamController;
......@@ -81,15 +89,21 @@ class ProtocolDiscovery {
///
/// When a new observatory URL: is available in [logReader],
/// the URLs are forwarded at most once every [throttleDuration].
/// Returns when no event has been observed for [throttleTimeout].
///
/// Port forwarding is only attempted when this is invoked,
/// for each observatory URL in the stream.
Stream<Uri> get uris {
return _uriStreamController.stream
Stream<Uri> uriStream = _uriStreamController.stream
.transform(_throttle<Uri>(
waitDuration: throttleDuration,
))
.asyncMap<Uri>(_forwardPort);
));
if (throttleTimeout != null) {
// Don't throw a TimeoutException. The URL wasn't found in time, just close the stream.
uriStream = uriStream.timeout(throttleTimeout,
onTimeout: (EventSink<Uri> sink) => sink.close());
}
return uriStream.asyncMap<Uri>(_forwardPort);
}
Future<void> cancel() => _stopScrapingLogs();
......
......@@ -266,6 +266,106 @@ void main() {
Usage: () => MockUsage(),
});
group('IOSDevice.startApp on discovery failure local network permissions', () {
// Still uses context for analytics and mDNS.
testUsingContext('is not prompted on < iOS 14', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
kDeployCommand,
kLaunchDebugCommand,
]);
final BufferLogger logger = BufferLogger.test();
final IOSDevice device = setUpIOSDevice(
sdkVersion: '13.0',
processManager: processManager,
fileSystem: fileSystem,
logger: logger,
vmServiceConnector: (String string, {Log log}) async {
throw const io.SocketException(
'OS Error: Connection refused, errno = 61, address = localhost, port '
'= 58943',
);
},
);
final IOSApp iosApp = PrebuiltIOSApp(
projectBundleId: 'app',
bundleName: 'Runner',
bundleDir: fileSystem.currentDirectory,
);
final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
device.portForwarder = const NoOpDevicePortForwarder();
device.setLogReader(iosApp, deviceLogReader);
when(MDnsObservatoryDiscovery.instance.getObservatoryUri(any, any, usesIpv6: anyNamed('usesIpv6')))
.thenAnswer((Invocation invocation) async => null);
final LaunchResult launchResult = await device.startApp(iosApp,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
platformArgs: <String, dynamic>{},
fallbackPollingDelay: Duration.zero,
fallbackThrottleTimeout: const Duration(milliseconds: 10),
);
expect(launchResult.started, false);
expect(launchResult.hasObservatory, false);
expect(logger.errorText, isEmpty);
}, overrides: <Type, Generator>{
MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(),
Usage: () => MockUsage(),
});
// Still uses context for analytics and mDNS.
testUsingContext('is prompted on >= iOS 14', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
kDeployCommand,
kLaunchDebugCommand,
]);
final BufferLogger logger = BufferLogger.test();
final IOSDevice device = setUpIOSDevice(
sdkVersion: '14.0',
processManager: processManager,
fileSystem: fileSystem,
logger: logger,
vmServiceConnector: (String string, {Log log}) async {
throw const io.SocketException(
'OS Error: Connection refused, errno = 61, address = localhost, port '
'= 58943',
);
},
);
final IOSApp iosApp = PrebuiltIOSApp(
projectBundleId: 'app',
bundleName: 'Runner',
bundleDir: fileSystem.currentDirectory,
);
final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
device.portForwarder = const NoOpDevicePortForwarder();
device.setLogReader(iosApp, deviceLogReader);
when(MDnsObservatoryDiscovery.instance.getObservatoryUri(any, any, usesIpv6: anyNamed('usesIpv6')))
.thenAnswer((Invocation invocation) async => null);
final LaunchResult launchResult = await device.startApp(iosApp,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
platformArgs: <String, dynamic>{},
fallbackPollingDelay: Duration.zero,
fallbackThrottleTimeout: const Duration(milliseconds: 10),
);
expect(launchResult.started, false);
expect(launchResult.hasObservatory, false);
expect(logger.errorText, contains('Settings > Privacy > Local Network'));
}, overrides: <Type, Generator>{
MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(),
Usage: () => MockUsage(),
});
});
// Still uses context for TimeoutConfiguration and usage
testUsingContext('IOSDevice.startApp succeeds in release mode', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
......@@ -430,7 +530,7 @@ IOSDevice setUpIOSDevice({
fileSystem: fileSystem ?? MemoryFileSystem.test(),
platform: macPlatform,
iProxy: IProxy.test(logger: logger, processManager: processManager ?? FakeProcessManager.any()),
logger: BufferLogger.test(),
logger: logger ?? BufferLogger.test(),
iosDeploy: IOSDeploy(
logger: logger ?? BufferLogger.test(),
platform: macPlatform,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment