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 {
);
if (localUri == null) {
await _screenshotOnFailure();
iosDeployDebugger?.detach();
return LaunchResult.failed();
}
return LaunchResult.succeeded(observatoryUri: localUri);
} on ProcessException catch (e) {
iosDeployDebugger?.detach();
_logger.printError(e.message);
return LaunchResult.failed();
} finally {
......@@ -487,7 +489,12 @@ class IOSDevice extends Device {
IOSApp app, {
String userIdentifier,
}) 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
......@@ -736,8 +743,8 @@ class IOSDeviceLogReader extends DeviceLogReader {
}
void logMessage(vm_service.Event event) {
if (_iosDeployDebugger != null) {
// Prefer the more complete logs from the debugger.
if (_iosDeployDebugger != null && _iosDeployDebugger.debuggerAttached) {
// Prefer the more complete logs from the attached debugger.
return;
}
final String message = processVmServiceMessage(event);
......@@ -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) {
// Logging is gathered from syslog on iOS 13 and earlier.
if (_majorSdkVersion < minimumUniversalLoggingSdkVersion) {
......@@ -831,6 +838,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
loggingSubscription.cancel();
}
_idevicesyslogProcess?.kill();
_iosDeployDebugger?.detach();
}
}
......
......@@ -25,9 +25,6 @@ const String noProvisioningProfileErrorTwo = 'Error 0xe8000067';
const String deviceLockedError = 'e80000e2';
const String unknownAppLaunchError = 'Error 0xe8000022';
// Another debugger instance is already attached?
const String processLaunchFailedError = 'error: process launch failed';
class IOSDeploy {
IOSDeploy({
@required Artifacts artifacts,
......@@ -118,13 +115,14 @@ class IOSDeploy {
/// Returns [IOSDeployDebugger] wrapping attached debugger logic.
///
/// 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({
@required String deviceId,
@required String bundlePath,
@required List<String> launchArguments,
@required IOSDeviceInterface interfaceType,
}) {
// Interactive debug session to support sending the lldb detach command.
final List<String> launchCommand = <String>[
'script',
'-t',
......@@ -136,7 +134,6 @@ class IOSDeploy {
'--bundle',
bundlePath,
'--debug',
'--noninteractive',
if (interfaceType != IOSDeviceInterface.network)
'--no-wifi',
if (launchArguments.isNotEmpty) ...<String>[
......@@ -224,7 +221,7 @@ enum _IOSDeployDebuggerState {
attached,
}
/// Wrapper to launch app with the debugger with ios-deploy.
/// Wrapper to launch app and attach the debugger with ios-deploy.
class IOSDeployDebugger {
IOSDeployDebugger({
@required Logger logger,
......@@ -264,16 +261,15 @@ class IOSDeployDebugger {
Stream<String> get logLines => _debuggerOutput.stream;
final StreamController<String> _debuggerOutput = StreamController<String>.broadcast();
bool get debuggerAttached => _debuggerState == _IOSDeployDebuggerState.attached;
_IOSDeployDebuggerState _debuggerState;
// (lldb) run
// 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');
// (lldb) autoexit
// hhttps://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L61
static final RegExp _lldbAutoexit = RegExp(r'\(lldb\)\s*autoexit');
// From lldb on exit.
// (lldb) run
// https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L51
static final RegExp _lldbProcessExit = RegExp(r'Process \d* exited with status =');
/// Launch the app on the device, and attach the debugger.
......@@ -296,7 +292,6 @@ class IOSDeployDebugger {
// (lldb) run
// success
// (lldb) autoexit
// 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)) {
_logger.printTrace(line);
......@@ -326,7 +321,7 @@ class IOSDeployDebugger {
}
return;
}
if (_debuggerState != _IOSDeployDebuggerState.attached || _lldbAutoexit.hasMatch(line)) {
if (_debuggerState != _IOSDeployDebuggerState.attached) {
_logger.printTrace(line);
return;
}
......@@ -380,6 +375,21 @@ class IOSDeployDebugger {
_iosDeployProcess = null;
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.
......@@ -402,13 +412,6 @@ Error launching app. Try launching from within Xcode via:
open ios/Runner.xcworkspace
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);
}
......
......@@ -3,8 +3,10 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
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/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
......@@ -48,7 +50,6 @@ void main () {
'--bundle',
'/',
'--debug',
'--noninteractive',
'--args',
<String>[
'--enable-dart-profiling',
......@@ -86,7 +87,7 @@ void main () {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
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(
......@@ -110,7 +111,7 @@ void main () {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
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(
......@@ -209,21 +210,26 @@ void main () {
await iosDeployDebugger.launchAndAttach();
expect(logger.errorText, contains('Try launching from within Xcode'));
});
});
testWithoutContext('cannot attach', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['ios-deploy'],
stdout: 'error: process launch failed: timed out waiting for app to launch',
),
]);
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
processManager: processManager,
logger: logger,
);
await iosDeployDebugger.launchAndAttach();
expect(logger.errorText, contains('Could not attach the debugger'));
});
testWithoutContext('detach', () async {
final StreamController<List<int>> stdin = StreamController<List<int>>();
final Stream<String> stdinStream = stdin.stream.transform<String>(const Utf8Decoder());
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>[
'ios-deploy',
],
stdout: '(lldb) run\nsuccess',
stdin: IOSink(stdin.sink),
),
]);
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
));
final MockIOSDeployDebugger iosDeployDebugger = MockIOSDeployDebugger();
when(iosDeployDebugger.debuggerAttached).thenReturn(true);
final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[
'Message from debugger'
......@@ -303,6 +304,24 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
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>[
'--bundle',
'/',
'--debug',
'--noninteractive',
'--no-wifi',
'--args',
'--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>[
'PATH': '/usr/bin:null',
'DYLD_LIBRARY_PATH': '/path/to/libraries',
},
stdout: '(lldb) run\nsuccess\n(lldb) autoexit',
stdout: '(lldb) run\nsuccess',
);
void main() {
......@@ -167,6 +166,7 @@ void main() {
expect(launchResult.started, true);
expect(launchResult.hasObservatory, true);
verify(globals.flutterUsage.sendEvent('ios-handshake', 'log-success')).called(1);
expect(await device.stopApp(iosApp), false);
}, overrides: <Type, Generator>{
Usage: () => MockUsage(),
});
......@@ -216,6 +216,7 @@ void main() {
expect(launchResult.started, true);
expect(launchResult.hasObservatory, true);
verify(globals.flutterUsage.sendEvent('ios-handshake', 'log-success')).called(1);
expect(await device.stopApp(iosApp), false);
}, overrides: <Type, Generator>{
Usage: () => MockUsage(),
});
......@@ -301,6 +302,7 @@ void main() {
expect(launchResult.started, true);
expect(launchResult.hasObservatory, false);
expect(await device.stopApp(iosApp), false);
expect(processManager.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
Usage: () => MockUsage(),
......@@ -323,7 +325,6 @@ void main() {
'--bundle',
'/',
'--debug',
'--noninteractive',
'--no-wifi',
// The arguments below are determined by what is passed into
// the debugging options argument to startApp.
......@@ -405,10 +406,67 @@ void main() {
);
expect(launchResult.started, true);
expect(await device.stopApp(iosApp), false);
expect(processManager.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
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({
......
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