Unverified Commit 5d113256 authored by Victoria Ashworth's avatar Victoria Ashworth Committed by GitHub

Add debugging for Dart VM timeout flake (#126437)

Check what is available in the device's iOS DeviceSupport folder to check if symbols were properly fetched. Also, add some logging to track what status the debugger is in.

Debugging for https://github.com/flutter/flutter/issues/121231.
parent 0636f5e0
...@@ -546,6 +546,7 @@ class IOSDevice extends Device { ...@@ -546,6 +546,7 @@ class IOSDevice extends Device {
"If you don't see your app in the Settings, uninstall the app and rerun to see the prompt again." "If you don't see your app in the Settings, uninstall the app and rerun to see the prompt again."
); );
} else { } else {
iosDeployDebugger?.checkForSymbolsFiles(_fileSystem);
iosDeployDebugger?.pauseDumpBacktraceResume(); iosDeployDebugger?.pauseDumpBacktraceResume();
} }
}); });
......
...@@ -288,6 +288,9 @@ class IOSDeployDebugger { ...@@ -288,6 +288,9 @@ class IOSDeployDebugger {
bool get debuggerAttached => _debuggerState == _IOSDeployDebuggerState.attached; bool get debuggerAttached => _debuggerState == _IOSDeployDebuggerState.attached;
_IOSDeployDebuggerState _debuggerState; _IOSDeployDebuggerState _debuggerState;
@visibleForTesting
String? symbolsDirectoryPath;
// (lldb) platform select remote-'ios' --sysroot // (lldb) platform select remote-'ios' --sysroot
// https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L33 // https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L33
// This regex is to get the configurable lldb prompt. By default this prompt will be "lldb". // This regex is to get the configurable lldb prompt. By default this prompt will be "lldb".
...@@ -306,6 +309,9 @@ class IOSDeployDebugger { ...@@ -306,6 +309,9 @@ class IOSDeployDebugger {
// (lldb) Process 6152 resuming // (lldb) Process 6152 resuming
static final RegExp _lldbProcessResuming = RegExp(r'Process \d+ resuming'); static final RegExp _lldbProcessResuming = RegExp(r'Process \d+ resuming');
// Symbol Path: /Users/swarming/Library/Developer/Xcode/iOS DeviceSupport/16.2 (20C65) arm64e/Symbols
static final RegExp _symbolsPathPattern = RegExp(r'.*Symbol Path: ');
// Send signal to stop (pause) the app. Used before a backtrace dump. // Send signal to stop (pause) the app. Used before a backtrace dump.
static const String _signalStop = 'process signal SIGSTOP'; static const String _signalStop = 'process signal SIGSTOP';
...@@ -362,12 +368,25 @@ class IOSDeployDebugger { ...@@ -362,12 +368,25 @@ class IOSDeployDebugger {
return; return;
} }
// Symbol Path: /Users/swarming/Library/Developer/Xcode/iOS DeviceSupport/16.2 (20C65) arm64e/Symbols
if (_symbolsPathPattern.hasMatch(line)) {
_logger.printTrace('Detected path to iOS debug symbols: "$line"');
final String prefix = _symbolsPathPattern.stringMatch(line) ?? '';
if (prefix.isEmpty) {
return;
}
symbolsDirectoryPath = line.substring(prefix.length);
return;
}
// (lldb) run // (lldb) run
// success // success
// 2020-09-15 13:42:25.185474-0700 Runner[477:181141] flutter: The Dart VM service is listening on http://127.0.0.1:57782/ // 2020-09-15 13:42:25.185474-0700 Runner[477:181141] flutter: The Dart VM service is listening on http://127.0.0.1:57782/
if (lldbRun.hasMatch(line)) { if (lldbRun.hasMatch(line)) {
_logger.printTrace(line); _logger.printTrace(line);
_debuggerState = _IOSDeployDebuggerState.launching; _debuggerState = _IOSDeployDebuggerState.launching;
// TODO(vashworth): Remove all debugger state comments when https://github.com/flutter/flutter/issues/126412 is resolved.
_logger.printTrace('Debugger state set to launching.');
return; return;
} }
// Next line after "run" must be "success", or the attach failed. // Next line after "run" must be "success", or the attach failed.
...@@ -376,6 +395,7 @@ class IOSDeployDebugger { ...@@ -376,6 +395,7 @@ class IOSDeployDebugger {
_logger.printTrace(line); _logger.printTrace(line);
final bool attachSuccess = line == 'success'; final bool attachSuccess = line == 'success';
_debuggerState = attachSuccess ? _IOSDeployDebuggerState.attached : _IOSDeployDebuggerState.detached; _debuggerState = attachSuccess ? _IOSDeployDebuggerState.attached : _IOSDeployDebuggerState.detached;
_logger.printTrace('Debugger state set to ${attachSuccess ? 'attached' : 'detached'}.');
if (!debuggerCompleter.isCompleted) { if (!debuggerCompleter.isCompleted) {
debuggerCompleter.complete(attachSuccess); debuggerCompleter.complete(attachSuccess);
} }
...@@ -392,6 +412,7 @@ class IOSDeployDebugger { ...@@ -392,6 +412,7 @@ class IOSDeployDebugger {
// Even though we're not "detached", just stopped, mark as detached so the backtrace // Even though we're not "detached", just stopped, mark as detached so the backtrace
// is only show in verbose. // is only show in verbose.
_debuggerState = _IOSDeployDebuggerState.detached; _debuggerState = _IOSDeployDebuggerState.detached;
_logger.printTrace('Debugger state set to detached.');
// If we paused the app and are waiting to resume it, complete the completer // If we paused the app and are waiting to resume it, complete the completer
final Completer<void>? processResumeCompleter = _processResumeCompleter; final Completer<void>? processResumeCompleter = _processResumeCompleter;
...@@ -422,6 +443,7 @@ class IOSDeployDebugger { ...@@ -422,6 +443,7 @@ class IOSDeployDebugger {
if (_lldbProcessDetached.hasMatch(line)) { if (_lldbProcessDetached.hasMatch(line)) {
// The debugger has detached from the app, and there will be no more debugging messages. // The debugger has detached from the app, and there will be no more debugging messages.
// Kill the ios-deploy process. // Kill the ios-deploy process.
_logger.printTrace(line);
exit(); exit();
return; return;
} }
...@@ -430,6 +452,7 @@ class IOSDeployDebugger { ...@@ -430,6 +452,7 @@ class IOSDeployDebugger {
_logger.printTrace(line); _logger.printTrace(line);
// we marked this detached when we received [_backTraceAll] // we marked this detached when we received [_backTraceAll]
_debuggerState = _IOSDeployDebuggerState.attached; _debuggerState = _IOSDeployDebuggerState.attached;
_logger.printTrace('Debugger state set to attached.');
return; return;
} }
...@@ -510,6 +533,33 @@ class IOSDeployDebugger { ...@@ -510,6 +533,33 @@ class IOSDeployDebugger {
_iosDeployProcess?.stdin.writeln(_processResume); _iosDeployProcess?.stdin.writeln(_processResume);
} }
/// Check what files are found in the device's iOS DeviceSupport directory.
///
/// Expected files include Symbols (directory), Info.plist, and .finalized.
///
/// If any of the expected files are missing or there are additional files
/// (such as .copying_lock or .processing_lock), this may indicate the
/// symbols may still be fetching or something went wrong when fetching them.
///
/// Used for debugging test flakes: https://github.com/flutter/flutter/issues/121231
Future<void> checkForSymbolsFiles(FileSystem fileSystem) async {
if (symbolsDirectoryPath == null) {
_logger.printTrace('No path provided for Symbols directory.');
return;
}
final Directory symbolsDirectory = fileSystem.directory(symbolsDirectoryPath);
if (!symbolsDirectory.existsSync()) {
_logger.printTrace('Unable to find Symbols directory at $symbolsDirectoryPath');
return;
}
final Directory currentDeviceSupportDir = symbolsDirectory.parent;
final List<FileSystemEntity> symbolStatusFiles = currentDeviceSupportDir.listSync();
_logger.printTrace('Symbol files:');
for (final FileSystemEntity file in symbolStatusFiles) {
_logger.printTrace(' ${file.basename}');
}
}
Future<void> stopAndDumpBacktrace() async { Future<void> stopAndDumpBacktrace() async {
if (!debuggerAttached) { if (!debuggerAttached) {
return; return;
......
...@@ -428,6 +428,93 @@ process continue ...@@ -428,6 +428,93 @@ process continue
'process detach', 'process detach',
]); ]);
}); });
group('Check for symbols', () {
late String symbolsDirectoryPath;
setUp(() {
fileSystem = MemoryFileSystem.test();
symbolsDirectoryPath = '/Users/swarming/Library/Developer/Xcode/iOS DeviceSupport/16.2 (20C65) arm64e/Symbols';
});
testWithoutContext('and no path provided', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>[
'ios-deploy',
],
stdout:
'(lldb) Process 6156 stopped',
),
]);
final BufferLogger logger = BufferLogger.test();
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
processManager: processManager,
logger: logger,
);
await iosDeployDebugger.launchAndAttach();
await iosDeployDebugger.checkForSymbolsFiles(fileSystem);
expect(iosDeployDebugger.symbolsDirectoryPath, isNull);
expect(logger.traceText, contains('No path provided for Symbols directory.'));
});
testWithoutContext('and unable to find directory', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>[
'ios-deploy',
],
stdout:
'[ 95%] Developer disk image mounted successfully\n'
'Symbol Path: $symbolsDirectoryPath\n'
'[100%] Connecting to remote debug server',
),
]);
final BufferLogger logger = BufferLogger.test();
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
processManager: processManager,
logger: logger,
);
await iosDeployDebugger.launchAndAttach();
await iosDeployDebugger.checkForSymbolsFiles(fileSystem);
expect(iosDeployDebugger.symbolsDirectoryPath, symbolsDirectoryPath);
expect(logger.traceText, contains('Unable to find Symbols directory'));
});
testWithoutContext('and find status', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>[
'ios-deploy',
],
stdout:
'[ 95%] Developer disk image mounted successfully\n'
'Symbol Path: $symbolsDirectoryPath\n'
'[100%] Connecting to remote debug server',
),
]);
final BufferLogger logger = BufferLogger.test();
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
processManager: processManager,
logger: logger,
);
final Directory symbolsDirectory = fileSystem.directory(symbolsDirectoryPath);
symbolsDirectory.createSync(recursive: true);
final File copyingStatusFile = symbolsDirectory.parent.childFile('.copying_lock');
copyingStatusFile.createSync();
final File processingStatusFile = symbolsDirectory.parent.childFile('.processing_lock');
processingStatusFile.createSync();
await iosDeployDebugger.launchAndAttach();
await iosDeployDebugger.checkForSymbolsFiles(fileSystem);
expect(iosDeployDebugger.symbolsDirectoryPath, symbolsDirectoryPath);
expect(logger.traceText, contains('Symbol files:'));
expect(logger.traceText, contains('.copying_lock'));
expect(logger.traceText, contains('.processing_lock'));
});
});
}); });
group('IOSDeploy.uninstallApp', () { group('IOSDeploy.uninstallApp', () {
......
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