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.
This commit is contained in:
Victoria Ashworth 2023-05-15 12:51:05 -05:00 committed by GitHub
parent 0636f5e035
commit 5d1132561f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 138 additions and 0 deletions

View file

@ -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."
);
} else {
iosDeployDebugger?.checkForSymbolsFiles(_fileSystem);
iosDeployDebugger?.pauseDumpBacktraceResume();
}
});

View file

@ -288,6 +288,9 @@ class IOSDeployDebugger {
bool get debuggerAttached => _debuggerState == _IOSDeployDebuggerState.attached;
_IOSDeployDebuggerState _debuggerState;
@visibleForTesting
String? symbolsDirectoryPath;
// (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
// This regex is to get the configurable lldb prompt. By default this prompt will be "lldb".
@ -306,6 +309,9 @@ class IOSDeployDebugger {
// (lldb) Process 6152 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.
static const String _signalStop = 'process signal SIGSTOP';
@ -362,12 +368,25 @@ class IOSDeployDebugger {
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
// 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/
if (lldbRun.hasMatch(line)) {
_logger.printTrace(line);
_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;
}
// Next line after "run" must be "success", or the attach failed.
@ -376,6 +395,7 @@ class IOSDeployDebugger {
_logger.printTrace(line);
final bool attachSuccess = line == 'success';
_debuggerState = attachSuccess ? _IOSDeployDebuggerState.attached : _IOSDeployDebuggerState.detached;
_logger.printTrace('Debugger state set to ${attachSuccess ? 'attached' : 'detached'}.');
if (!debuggerCompleter.isCompleted) {
debuggerCompleter.complete(attachSuccess);
}
@ -392,6 +412,7 @@ class IOSDeployDebugger {
// Even though we're not "detached", just stopped, mark as detached so the backtrace
// is only show in verbose.
_debuggerState = _IOSDeployDebuggerState.detached;
_logger.printTrace('Debugger state set to detached.');
// If we paused the app and are waiting to resume it, complete the completer
final Completer<void>? processResumeCompleter = _processResumeCompleter;
@ -422,6 +443,7 @@ class IOSDeployDebugger {
if (_lldbProcessDetached.hasMatch(line)) {
// The debugger has detached from the app, and there will be no more debugging messages.
// Kill the ios-deploy process.
_logger.printTrace(line);
exit();
return;
}
@ -430,6 +452,7 @@ class IOSDeployDebugger {
_logger.printTrace(line);
// we marked this detached when we received [_backTraceAll]
_debuggerState = _IOSDeployDebuggerState.attached;
_logger.printTrace('Debugger state set to attached.');
return;
}
@ -510,6 +533,33 @@ class IOSDeployDebugger {
_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 {
if (!debuggerAttached) {
return;

View file

@ -428,6 +428,93 @@ process continue
'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', () {