Skip to content

Commit 6683468

Browse files
authored
Add debugging for iOS startup test flakes (#130099)
Adding debugging for flutter/flutter#129836. Takes a screenshot when startup test takes too long (10 minutes). Also, removes some old debugging and add new debugging message.
1 parent d55a7d8 commit 6683468

File tree

3 files changed

+71
-20
lines changed

3 files changed

+71
-20
lines changed

dev/devicelab/lib/tasks/perf_tests.dart

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,15 @@ class StartupTest {
771771
const int maxFailures = 3;
772772
int currentFailures = 0;
773773
for (int i = 0; i < iterations; i += 1) {
774+
// Startup should not take more than a few minutes. After 10 minutes,
775+
// take a screenshot to help debug.
776+
final Timer timer = Timer(const Duration(minutes: 10), () async {
777+
print('Startup not completed within 10 minutes. Taking a screenshot...');
778+
await _flutterScreenshot(
779+
device.deviceId,
780+
'screenshot_startup_${DateTime.now().toLocal().toIso8601String()}.png',
781+
);
782+
});
774783
final int result = await flutter('run', options: <String>[
775784
'--no-android-gradle-daemon',
776785
'--no-publish-port',
@@ -782,28 +791,19 @@ class StartupTest {
782791
device.deviceId,
783792
if (applicationBinaryPath != null)
784793
'--use-application-binary=$applicationBinaryPath',
785-
], canFail: true);
794+
], canFail: true);
795+
timer.cancel();
786796
if (result == 0) {
787797
final Map<String, dynamic> data = json.decode(
788798
file('${_testOutputDirectory(testDirectory)}/start_up_info.json').readAsStringSync(),
789799
) as Map<String, dynamic>;
790800
results.add(data);
791801
} else {
792802
currentFailures += 1;
793-
if (hostAgent.dumpDirectory != null) {
794-
await flutter(
795-
'screenshot',
796-
options: <String>[
797-
'-d',
798-
device.deviceId,
799-
'--out',
800-
hostAgent.dumpDirectory!
801-
.childFile('screenshot_startup_failure_$currentFailures.png')
802-
.path,
803-
],
804-
canFail: true,
805-
);
806-
}
803+
await _flutterScreenshot(
804+
device.deviceId,
805+
'screenshot_startup_failure_$currentFailures.png',
806+
);
807807
i -= 1;
808808
if (currentFailures == maxFailures) {
809809
return TaskResult.failure('Application failed to start $maxFailures times');
@@ -829,6 +829,23 @@ class StartupTest {
829829
]);
830830
});
831831
}
832+
833+
Future<void> _flutterScreenshot(String deviceId, String screenshotName) async {
834+
if (hostAgent.dumpDirectory != null) {
835+
await flutter(
836+
'screenshot',
837+
options: <String>[
838+
'-d',
839+
deviceId,
840+
'--out',
841+
hostAgent.dumpDirectory!
842+
.childFile(screenshotName)
843+
.path,
844+
],
845+
canFail: true,
846+
);
847+
}
848+
}
832849
}
833850

834851
/// A one-off test to verify that devtools starts in profile mode.

packages/flutter_tools/lib/src/ios/ios_deploy.dart

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,8 @@ class IOSDeployDebugger {
338338
RegExp lldbRun = RegExp(r'\(lldb\)\s*run');
339339

340340
final Completer<bool> debuggerCompleter = Completer<bool>();
341+
342+
bool receivedLogs = false;
341343
try {
342344
_iosDeployProcess = await _processUtils.start(
343345
_launchCommand,
@@ -386,8 +388,6 @@ class IOSDeployDebugger {
386388
if (lldbRun.hasMatch(line)) {
387389
_logger.printTrace(line);
388390
_debuggerState = _IOSDeployDebuggerState.launching;
389-
// TODO(vashworth): Remove all debugger state comments when https://github.com/flutter/flutter/issues/126412 is resolved.
390-
_logger.printTrace('Debugger state set to launching.');
391391
return;
392392
}
393393
// Next line after "run" must be "success", or the attach failed.
@@ -396,7 +396,6 @@ class IOSDeployDebugger {
396396
_logger.printTrace(line);
397397
final bool attachSuccess = line == 'success';
398398
_debuggerState = attachSuccess ? _IOSDeployDebuggerState.attached : _IOSDeployDebuggerState.detached;
399-
_logger.printTrace('Debugger state set to ${attachSuccess ? 'attached' : 'detached'}.');
400399
if (!debuggerCompleter.isCompleted) {
401400
debuggerCompleter.complete(attachSuccess);
402401
}
@@ -425,7 +424,6 @@ class IOSDeployDebugger {
425424
// Even though we're not "detached", just stopped, mark as detached so the backtrace
426425
// is only show in verbose.
427426
_debuggerState = _IOSDeployDebuggerState.detached;
428-
_logger.printTrace('Debugger state set to detached.');
429427

430428
// If we paused the app and are waiting to resume it, complete the completer
431429
final Completer<void>? processResumeCompleter = _processResumeCompleter;
@@ -465,7 +463,6 @@ class IOSDeployDebugger {
465463
_logger.printTrace(line);
466464
// we marked this detached when we received [_backTraceAll]
467465
_debuggerState = _IOSDeployDebuggerState.attached;
468-
_logger.printTrace('Debugger state set to attached.');
469466
return;
470467
}
471468

@@ -480,6 +477,16 @@ class IOSDeployDebugger {
480477
// This will still cause "legit" logged newlines to be doubled...
481478
} else if (!_debuggerOutput.isClosed) {
482479
_debuggerOutput.add(line);
480+
481+
// Sometimes the `ios-deploy` process does not return logs from the
482+
// application after attaching, such as the Dart VM url. In CI,
483+
// `idevicesyslog` is used as a fallback to get logs. Print a
484+
// message to indicate whether logs were received from `ios-deploy`
485+
// to help with debugging.
486+
if (!receivedLogs) {
487+
_logger.printTrace('Received logs from ios-deploy.');
488+
receivedLogs = true;
489+
}
483490
}
484491
lastLineFromDebugger = line;
485492
});

packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,33 @@ void main () {
341341
await iosDeployDebugger.launchAndAttach();
342342
expect(logger.errorText, contains('Try launching from within Xcode'));
343343
});
344+
345+
testWithoutContext('debugger attached and received logs', () async {
346+
final StreamController<List<int>> stdin = StreamController<List<int>>();
347+
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
348+
FakeCommand(
349+
command: const <String>['ios-deploy'],
350+
stdout: '(lldb) run\r\nsuccess\r\nLog on attach1\r\n\r\nLog on attach2\r\n',
351+
stdin: IOSink(stdin.sink),
352+
),
353+
]);
354+
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
355+
processManager: processManager,
356+
logger: logger,
357+
);
358+
final List<String> receivedLogLines = <String>[];
359+
final Stream<String> logLines = iosDeployDebugger.logLines
360+
..listen(receivedLogLines.add);
361+
362+
expect(iosDeployDebugger.logLines, emitsInOrder(<String>[
363+
'Log on attach1',
364+
'Log on attach2',
365+
]));
366+
expect(await iosDeployDebugger.launchAndAttach(), isTrue);
367+
await logLines.drain();
368+
369+
expect(LineSplitter.split(logger.traceText), containsOnce('Received logs from ios-deploy.'));
370+
});
344371
});
345372

346373
testWithoutContext('detach', () async {

0 commit comments

Comments
 (0)