Unverified Commit acde65d1 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Revert noninteractive lldb debugging, timeout warning (#68245)

parent 9611130e
...@@ -471,10 +471,12 @@ class IOSDevice extends Device { ...@@ -471,10 +471,12 @@ class IOSDevice extends Device {
); );
if (localUri == null) { if (localUri == null) {
await _screenshotOnFailure(); await _screenshotOnFailure();
iosDeployDebugger?.detach();
return LaunchResult.failed(); return LaunchResult.failed();
} }
return LaunchResult.succeeded(observatoryUri: localUri); return LaunchResult.succeeded(observatoryUri: localUri);
} on ProcessException catch (e) { } on ProcessException catch (e) {
iosDeployDebugger?.detach();
_logger.printError(e.message); _logger.printError(e.message);
return LaunchResult.failed(); return LaunchResult.failed();
} finally { } finally {
...@@ -487,7 +489,12 @@ class IOSDevice extends Device { ...@@ -487,7 +489,12 @@ class IOSDevice extends Device {
IOSApp app, { IOSApp app, {
String userIdentifier, String userIdentifier,
}) async { }) async {
return iosDeployDebugger?.exit(); // If the debugger is not attached, killing the ios-deploy process won't stop the app.
if (iosDeployDebugger!= null && iosDeployDebugger.debuggerAttached) {
// Avoid null.
return iosDeployDebugger?.exit() == true;
}
return false;
} }
@override @override
...@@ -736,8 +743,8 @@ class IOSDeviceLogReader extends DeviceLogReader { ...@@ -736,8 +743,8 @@ class IOSDeviceLogReader extends DeviceLogReader {
} }
void logMessage(vm_service.Event event) { void logMessage(vm_service.Event event) {
if (_iosDeployDebugger != null) { if (_iosDeployDebugger != null && _iosDeployDebugger.debuggerAttached) {
// Prefer the more complete logs from the debugger. // Prefer the more complete logs from the attached debugger.
return; return;
} }
final String message = processVmServiceMessage(event); final String message = processVmServiceMessage(event);
...@@ -752,7 +759,7 @@ class IOSDeviceLogReader extends DeviceLogReader { ...@@ -752,7 +759,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
]); ]);
} }
/// Log reader will listen to [debugger.logLines]. /// Log reader will listen to [debugger.logLines] and will detach debugger on dispose.
set debuggerStream(IOSDeployDebugger debugger) { set debuggerStream(IOSDeployDebugger debugger) {
// Logging is gathered from syslog on iOS 13 and earlier. // Logging is gathered from syslog on iOS 13 and earlier.
if (_majorSdkVersion < minimumUniversalLoggingSdkVersion) { if (_majorSdkVersion < minimumUniversalLoggingSdkVersion) {
...@@ -831,6 +838,7 @@ class IOSDeviceLogReader extends DeviceLogReader { ...@@ -831,6 +838,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
loggingSubscription.cancel(); loggingSubscription.cancel();
} }
_idevicesyslogProcess?.kill(); _idevicesyslogProcess?.kill();
_iosDeployDebugger?.detach();
} }
} }
......
...@@ -25,9 +25,6 @@ const String noProvisioningProfileErrorTwo = 'Error 0xe8000067'; ...@@ -25,9 +25,6 @@ const String noProvisioningProfileErrorTwo = 'Error 0xe8000067';
const String deviceLockedError = 'e80000e2'; const String deviceLockedError = 'e80000e2';
const String unknownAppLaunchError = 'Error 0xe8000022'; const String unknownAppLaunchError = 'Error 0xe8000022';
// Another debugger instance is already attached?
const String processLaunchFailedError = 'error: process launch failed';
class IOSDeploy { class IOSDeploy {
IOSDeploy({ IOSDeploy({
@required Artifacts artifacts, @required Artifacts artifacts,
...@@ -118,13 +115,14 @@ class IOSDeploy { ...@@ -118,13 +115,14 @@ class IOSDeploy {
/// Returns [IOSDeployDebugger] wrapping attached debugger logic. /// Returns [IOSDeployDebugger] wrapping attached debugger logic.
/// ///
/// This method does not install the app. Call [IOSDeployDebugger.launchAndAttach()] /// This method does not install the app. Call [IOSDeployDebugger.launchAndAttach()]
/// to install the specified app bundle. /// to install and attach the debugger to the specified app bundle.
IOSDeployDebugger prepareDebuggerForLaunch({ IOSDeployDebugger prepareDebuggerForLaunch({
@required String deviceId, @required String deviceId,
@required String bundlePath, @required String bundlePath,
@required List<String> launchArguments, @required List<String> launchArguments,
@required IOSDeviceInterface interfaceType, @required IOSDeviceInterface interfaceType,
}) { }) {
// Interactive debug session to support sending the lldb detach command.
final List<String> launchCommand = <String>[ final List<String> launchCommand = <String>[
'script', 'script',
'-t', '-t',
...@@ -136,7 +134,6 @@ class IOSDeploy { ...@@ -136,7 +134,6 @@ class IOSDeploy {
'--bundle', '--bundle',
bundlePath, bundlePath,
'--debug', '--debug',
'--noninteractive',
if (interfaceType != IOSDeviceInterface.network) if (interfaceType != IOSDeviceInterface.network)
'--no-wifi', '--no-wifi',
if (launchArguments.isNotEmpty) ...<String>[ if (launchArguments.isNotEmpty) ...<String>[
...@@ -224,7 +221,7 @@ enum _IOSDeployDebuggerState { ...@@ -224,7 +221,7 @@ enum _IOSDeployDebuggerState {
attached, attached,
} }
/// Wrapper to launch app with the debugger with ios-deploy. /// Wrapper to launch app and attach the debugger with ios-deploy.
class IOSDeployDebugger { class IOSDeployDebugger {
IOSDeployDebugger({ IOSDeployDebugger({
@required Logger logger, @required Logger logger,
...@@ -264,16 +261,15 @@ class IOSDeployDebugger { ...@@ -264,16 +261,15 @@ class IOSDeployDebugger {
Stream<String> get logLines => _debuggerOutput.stream; Stream<String> get logLines => _debuggerOutput.stream;
final StreamController<String> _debuggerOutput = StreamController<String>.broadcast(); final StreamController<String> _debuggerOutput = StreamController<String>.broadcast();
bool get debuggerAttached => _debuggerState == _IOSDeployDebuggerState.attached;
_IOSDeployDebuggerState _debuggerState; _IOSDeployDebuggerState _debuggerState;
// (lldb) run // (lldb) run
// https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L51 // https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L51
static final RegExp _lldbRun = RegExp(r'\(lldb\)\s*run'); static final RegExp _lldbRun = RegExp(r'\(lldb\)\s*run');
// (lldb) autoexit // (lldb) run
// hhttps://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L61 // https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L51
static final RegExp _lldbAutoexit = RegExp(r'\(lldb\)\s*autoexit');
// From lldb on exit.
static final RegExp _lldbProcessExit = RegExp(r'Process \d* exited with status ='); static final RegExp _lldbProcessExit = RegExp(r'Process \d* exited with status =');
/// Launch the app on the device, and attach the debugger. /// Launch the app on the device, and attach the debugger.
...@@ -296,7 +292,6 @@ class IOSDeployDebugger { ...@@ -296,7 +292,6 @@ class IOSDeployDebugger {
// (lldb) run // (lldb) run
// success // success
// (lldb) autoexit
// 2020-09-15 13:42:25.185474-0700 Runner[477:181141] flutter: Observatory listening on http://127.0.0.1:57782/ // 2020-09-15 13:42:25.185474-0700 Runner[477:181141] flutter: Observatory listening on http://127.0.0.1:57782/
if (_lldbRun.hasMatch(line)) { if (_lldbRun.hasMatch(line)) {
_logger.printTrace(line); _logger.printTrace(line);
...@@ -326,7 +321,7 @@ class IOSDeployDebugger { ...@@ -326,7 +321,7 @@ class IOSDeployDebugger {
} }
return; return;
} }
if (_debuggerState != _IOSDeployDebuggerState.attached || _lldbAutoexit.hasMatch(line)) { if (_debuggerState != _IOSDeployDebuggerState.attached) {
_logger.printTrace(line); _logger.printTrace(line);
return; return;
} }
...@@ -380,6 +375,21 @@ class IOSDeployDebugger { ...@@ -380,6 +375,21 @@ class IOSDeployDebugger {
_iosDeployProcess = null; _iosDeployProcess = null;
return success; return success;
} }
void detach() {
if (!debuggerAttached) {
return;
}
try {
// Detach lldb from the app process.
_iosDeployProcess?.stdin?.writeln('process detach');
_debuggerState = _IOSDeployDebuggerState.detached;
} on SocketException catch (error) {
// Best effort, try to detach, but maybe the app already exited or already detached.
_logger.printTrace('Could not detach from debugger: $error');
}
}
} }
// Maps stdout line stream. Must return original line. // Maps stdout line stream. Must return original line.
...@@ -402,13 +412,6 @@ Error launching app. Try launching from within Xcode via: ...@@ -402,13 +412,6 @@ Error launching app. Try launching from within Xcode via:
open ios/Runner.xcworkspace open ios/Runner.xcworkspace
Your Xcode version may be too old for your iOS version. Your Xcode version may be too old for your iOS version.
═══════════════════════════════════════════════════════════════════════════════════''',
emphasis: true);
} else if (stdout.contains(processLaunchFailedError)) {
logger.printError('''
═══════════════════════════════════════════════════════════════════════════════════
Could not attach the debugger.
Try uninstalling the app from your device and retrying.
═══════════════════════════════════════════════════════════════════════════════════''', ═══════════════════════════════════════════════════════════════════════════════════''',
emphasis: true); emphasis: true);
} }
......
...@@ -3,8 +3,10 @@ ...@@ -3,8 +3,10 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
...@@ -48,7 +50,6 @@ void main () { ...@@ -48,7 +50,6 @@ void main () {
'--bundle', '--bundle',
'/', '/',
'--debug', '--debug',
'--noninteractive',
'--args', '--args',
<String>[ <String>[
'--enable-dart-profiling', '--enable-dart-profiling',
...@@ -86,7 +87,7 @@ void main () { ...@@ -86,7 +87,7 @@ void main () {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand( const FakeCommand(
command: <String>['ios-deploy'], command: <String>['ios-deploy'],
stdout: '(lldb) run\r\nsuccess\r\n(lldb) autoexit\r\nsuccess\r\nLog on attach1\r\n\r\nLog on attach2\r\n\r\n\r\n\r\nPROCESS_STOPPED\r\nLog after process exit', stdout: '(lldb) run\r\nsuccess\r\nsuccess\r\nLog on attach1\r\n\r\nLog on attach2\r\n\r\n\r\n\r\nPROCESS_STOPPED\r\nLog after process exit',
), ),
]); ]);
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test( final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
...@@ -110,7 +111,7 @@ void main () { ...@@ -110,7 +111,7 @@ void main () {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand( const FakeCommand(
command: <String>['ios-deploy'], command: <String>['ios-deploy'],
stdout: '(lldb) run\r\nsuccess\r\n(lldb) autoexit\r\nLog on attach\r\nProcess 100 exited with status = 0\r\nLog after process exit', stdout: '(lldb) run\r\nsuccess\r\nLog on attach\r\nProcess 100 exited with status = 0\r\nLog after process exit',
), ),
]); ]);
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test( final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
...@@ -209,21 +210,26 @@ void main () { ...@@ -209,21 +210,26 @@ void main () {
await iosDeployDebugger.launchAndAttach(); await iosDeployDebugger.launchAndAttach();
expect(logger.errorText, contains('Try launching from within Xcode')); expect(logger.errorText, contains('Try launching from within Xcode'));
}); });
});
testWithoutContext('cannot attach', () async { testWithoutContext('detach', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ final StreamController<List<int>> stdin = StreamController<List<int>>();
const FakeCommand( final Stream<String> stdinStream = stdin.stream.transform<String>(const Utf8Decoder());
command: <String>['ios-deploy'], final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
stdout: 'error: process launch failed: timed out waiting for app to launch', FakeCommand(
), command: const <String>[
]); 'ios-deploy',
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test( ],
processManager: processManager, stdout: '(lldb) run\nsuccess',
logger: logger, stdin: IOSink(stdin.sink),
); ),
await iosDeployDebugger.launchAndAttach(); ]);
expect(logger.errorText, contains('Could not attach the debugger')); final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
}); processManager: processManager,
);
await iosDeployDebugger.launchAndAttach();
iosDeployDebugger.detach();
expect(await stdinStream.first, 'process detach');
}); });
}); });
......
...@@ -239,6 +239,7 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt ...@@ -239,6 +239,7 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
)); ));
final MockIOSDeployDebugger iosDeployDebugger = MockIOSDeployDebugger(); final MockIOSDeployDebugger iosDeployDebugger = MockIOSDeployDebugger();
when(iosDeployDebugger.debuggerAttached).thenReturn(true);
final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[ final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[
'Message from debugger' 'Message from debugger'
...@@ -303,6 +304,24 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt ...@@ -303,6 +304,24 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
await streamComplete.future; await streamComplete.future;
}); });
testWithoutContext('detaches debugger', () async {
final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
iMobileDevice: IMobileDevice(
artifacts: artifacts,
processManager: processManager,
cache: fakeCache,
logger: logger,
),
useSyslog: false,
);
final MockIOSDeployDebugger iosDeployDebugger = MockIOSDeployDebugger();
when(iosDeployDebugger.logLines).thenAnswer((Invocation invocation) => const Stream<String>.empty());
logReader.debuggerStream = iosDeployDebugger;
logReader.dispose();
verify(iosDeployDebugger.detach());
});
}); });
} }
......
...@@ -92,7 +92,6 @@ const FakeCommand kAttachDebuggerCommand = FakeCommand(command: <String>[ ...@@ -92,7 +92,6 @@ const FakeCommand kAttachDebuggerCommand = FakeCommand(command: <String>[
'--bundle', '--bundle',
'/', '/',
'--debug', '--debug',
'--noninteractive',
'--no-wifi', '--no-wifi',
'--args', '--args',
'--enable-dart-profiling --enable-service-port-fallback --disable-service-auth-codes --observatory-port=60700 --enable-checked-mode --verify-entry-points' '--enable-dart-profiling --enable-service-port-fallback --disable-service-auth-codes --observatory-port=60700 --enable-checked-mode --verify-entry-points'
...@@ -100,7 +99,7 @@ const FakeCommand kAttachDebuggerCommand = FakeCommand(command: <String>[ ...@@ -100,7 +99,7 @@ const FakeCommand kAttachDebuggerCommand = FakeCommand(command: <String>[
'PATH': '/usr/bin:null', 'PATH': '/usr/bin:null',
'DYLD_LIBRARY_PATH': '/path/to/libraries', 'DYLD_LIBRARY_PATH': '/path/to/libraries',
}, },
stdout: '(lldb) run\nsuccess\n(lldb) autoexit', stdout: '(lldb) run\nsuccess',
); );
void main() { void main() {
...@@ -167,6 +166,7 @@ void main() { ...@@ -167,6 +166,7 @@ void main() {
expect(launchResult.started, true); expect(launchResult.started, true);
expect(launchResult.hasObservatory, true); expect(launchResult.hasObservatory, true);
verify(globals.flutterUsage.sendEvent('ios-handshake', 'log-success')).called(1); verify(globals.flutterUsage.sendEvent('ios-handshake', 'log-success')).called(1);
expect(await device.stopApp(iosApp), false);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Usage: () => MockUsage(), Usage: () => MockUsage(),
}); });
...@@ -216,6 +216,7 @@ void main() { ...@@ -216,6 +216,7 @@ void main() {
expect(launchResult.started, true); expect(launchResult.started, true);
expect(launchResult.hasObservatory, true); expect(launchResult.hasObservatory, true);
verify(globals.flutterUsage.sendEvent('ios-handshake', 'log-success')).called(1); verify(globals.flutterUsage.sendEvent('ios-handshake', 'log-success')).called(1);
expect(await device.stopApp(iosApp), false);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Usage: () => MockUsage(), Usage: () => MockUsage(),
}); });
...@@ -301,6 +302,7 @@ void main() { ...@@ -301,6 +302,7 @@ void main() {
expect(launchResult.started, true); expect(launchResult.started, true);
expect(launchResult.hasObservatory, false); expect(launchResult.hasObservatory, false);
expect(await device.stopApp(iosApp), false);
expect(processManager.hasRemainingExpectations, false); expect(processManager.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Usage: () => MockUsage(), Usage: () => MockUsage(),
...@@ -323,7 +325,6 @@ void main() { ...@@ -323,7 +325,6 @@ void main() {
'--bundle', '--bundle',
'/', '/',
'--debug', '--debug',
'--noninteractive',
'--no-wifi', '--no-wifi',
// The arguments below are determined by what is passed into // The arguments below are determined by what is passed into
// the debugging options argument to startApp. // the debugging options argument to startApp.
...@@ -405,10 +406,67 @@ void main() { ...@@ -405,10 +406,67 @@ void main() {
); );
expect(launchResult.started, true); expect(launchResult.started, true);
expect(await device.stopApp(iosApp), false);
expect(processManager.hasRemainingExpectations, false); expect(processManager.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Usage: () => MockUsage(), Usage: () => MockUsage(),
}); });
// Still uses context for analytics.
testUsingContext(
'IOSDevice.startApp detaches lldb when VM service connection fails',
() async {
final FileSystem fileSystem = MemoryFileSystem.test();
final MockIOSDeploy mockIOSDeploy = MockIOSDeploy();
final MockIOSDeployDebugger mockIOSDeployDebugger = MockIOSDeployDebugger();
when(mockIOSDeploy.prepareDebuggerForLaunch(
deviceId: anyNamed('deviceId'),
bundlePath: anyNamed('bundlePath'),
launchArguments: anyNamed('launchArguments'),
interfaceType: anyNamed('interfaceType')))
.thenReturn(mockIOSDeployDebugger);
when(mockIOSDeploy.installApp(
deviceId: anyNamed('deviceId'),
bundlePath: anyNamed('bundlePath'),
launchArguments: anyNamed('launchArguments'),
interfaceType: anyNamed('interfaceType')))
.thenAnswer((_) async => 0);
when(mockIOSDeployDebugger.launchAndAttach()).thenAnswer((_) async => true);
final IOSDevice device = setUpIOSDevice(
fileSystem: fileSystem,
iosDeploy: mockIOSDeploy,
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,
);
device.portForwarder = const NoOpDevicePortForwarder();
device.setLogReader(iosApp, FakeDeviceLogReader());
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);
verify(mockIOSDeployDebugger.detach()).called(1);
}, overrides: <Type, Generator>{
Usage: () => MockUsage(),
});
} }
IOSDevice setUpIOSDevice({ IOSDevice setUpIOSDevice({
......
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