Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions packages/flutter_tools/lib/src/ios/core_devices.dart
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ class IOSCoreDeviceControl {
'Failed to execute code (error: EXC_BAD_ACCESS, debugger assist: not detected)',
];

static const kCoreDeviceLaunchCompleteLog = 'Waiting for the application to terminate';

/// Executes `devicectl` command to get list of devices. The command will
/// likely complete before [timeout] is reached. If [timeout] is reached,
/// the command will be stopped as a failure.
Expand Down Expand Up @@ -650,13 +652,21 @@ class IOSCoreDeviceControl {
///
/// If [attachToConsole] is true, attaches the application to the console and waits for the app
/// to terminate.
///
/// When [jsonOutputFile] is provided, devicectl will write a JSON file with the command results
/// after the command has completed. This will not have the results when using [attachToConsole]
/// until the process has exited.
///
/// When [logOutputFile] is provided, devicectl will write all logging otherwise passed to
/// stdout/stderr to the file. It will also continue to stream the logs to stdout/stderr.
List<String> _launchAppCommand({
required String deviceId,
required String bundleId,
List<String> launchArguments = const <String>[],
bool startStopped = false,
bool attachToConsole = false,
File? outputFile,
File? jsonOutputFile,
File? logOutputFile,
}) {
return <String>[
..._xcode.xcrunCommand(),
Expand All @@ -674,7 +684,8 @@ class IOSCoreDeviceControl {
// See https://github.com/llvm/llvm-project/blob/19b43e1757b4fd3d0f188cf8a08e9febb0dbec2f/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp#L1227-L1233
'{"OS_ACTIVITY_DT_MODE": "enable"}',
],
if (outputFile != null) ...<String>['--json-output', outputFile.path],
if (jsonOutputFile != null) ...<String>['--json-output', jsonOutputFile.path],
if (logOutputFile != null) ...<String>['--log-output', logOutputFile.path],
bundleId,
if (launchArguments.isNotEmpty) ...launchArguments,
];
Expand Down Expand Up @@ -703,7 +714,7 @@ class IOSCoreDeviceControl {
deviceId: deviceId,
launchArguments: launchArguments,
startStopped: startStopped,
outputFile: output,
jsonOutputFile: output,
);

try {
Expand Down Expand Up @@ -754,15 +765,19 @@ class IOSCoreDeviceControl {
return false;
}

final Directory tempDirectory = _fileSystem.systemTempDirectory.createTempSync('core_devices.');
final File output = tempDirectory.childFile('launch_log.txt')..createSync();

final launchCompleter = Completer<bool>();
final List<String> command = _launchAppCommand(
bundleId: bundleId,
deviceId: deviceId,
launchArguments: launchArguments,
startStopped: startStopped,
attachToConsole: true,
logOutputFile: output,
);

Timer? timer;
try {
final Process launchProcess = await _processUtils.start(command);
coreDeviceLogForwarder.launchProcess = launchProcess;
Expand All @@ -776,7 +791,7 @@ class IOSCoreDeviceControl {
_logger.printTrace(line);
}

if (line.contains('Waiting for the application to terminate')) {
if (!launchCompleter.isCompleted && line.contains(kCoreDeviceLaunchCompleteLog)) {
launchCompleter.complete(true);
}
});
Expand Down Expand Up @@ -805,10 +820,27 @@ class IOSCoreDeviceControl {
}
}),
);
return launchCompleter.future;

// Sometimes devicectl launch logs don't stream to stdout.
// As a workaround, we also use the log output file to check if it has finished launching.
timer = Timer.periodic(const Duration(seconds: 1), (timer) async {
if (await output.exists()) {
final String contents = await output.readAsString();
if (!launchCompleter.isCompleted && contents.contains(kCoreDeviceLaunchCompleteLog)) {
launchCompleter.complete(true);
}
}
});

// Do not return the launchCompleter.future directly, otherwise, the timer will be canceled
// prematurely.
final bool status = await launchCompleter.future;
return status;
} on ProcessException catch (err) {
_logger.printTrace('Error executing devicectl: $err');
return false;
} finally {
timer?.cancel();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,8 @@ invalid JSON
'--console',
'--environment-variables',
'{"OS_ACTIVITY_DT_MODE": "enable"}',
'--log-output',
'/.tmp_rand0/core_devices.rand0/launch_log.txt',
bundleId,
],
stdout: '''
Expand Down Expand Up @@ -1830,6 +1832,8 @@ Waiting for the application to terminate...
'--console',
'--environment-variables',
'{"OS_ACTIVITY_DT_MODE": "enable"}',
'--log-output',
'/.tmp_rand0/core_devices.rand0/launch_log.txt',
bundleId,
'--arg1',
'--arg2',
Expand Down Expand Up @@ -1872,6 +1876,8 @@ Waiting for the application to terminate...
'--console',
'--environment-variables',
'{"OS_ACTIVITY_DT_MODE": "enable"}',
'--log-output',
'/.tmp_rand0/core_devices.rand0/launch_log.txt',
bundleId,
],
stdout: '''
Expand Down Expand Up @@ -1939,6 +1945,8 @@ Waiting for the application to terminate...
'--console',
'--environment-variables',
'{"OS_ACTIVITY_DT_MODE": "enable"}',
'--log-output',
'/.tmp_rand0/core_devices.rand0/launch_log.txt',
bundleId,
],
exitCode: 1,
Expand All @@ -1961,6 +1969,54 @@ ERROR: The operation couldn?t be completed. (OSStatus error -10814.) (NSOSStatus
expect(logger.errorText, isEmpty);
expect(result, isFalse);
});

testWithoutContext('Successful launch with output in log file', () async {
final Completer<void> launchCompleter = Completer();
fakeProcessManager.addCommand(
FakeCommand(
command: const <String>[
'xcrun',
'devicectl',
'device',
'process',
'launch',
'--device',
deviceId,
'--start-stopped',
'--console',
'--environment-variables',
'{"OS_ACTIVITY_DT_MODE": "enable"}',
'--log-output',
'/.tmp_rand0/core_devices.rand0/launch_log.txt',
bundleId,
],
onRun: (command) {
fileSystem.file('/.tmp_rand0/core_devices.rand0/launch_log.txt')
..createSync(recursive: true)
..writeAsStringSync('''
10:04:12 Acquired tunnel connection to device.
10:04:12 Enabling developer disk image services.
10:04:12 Acquired usage assertion.
Launched application with com.example.my_app bundle identifier.
Waiting for the application to terminate...
''');
},
completer: launchCompleter,
),
);

final bool result = await deviceControl.launchAppAndStreamLogs(
deviceId: deviceId,
bundleId: bundleId,
coreDeviceLogForwarder: FakeIOSCoreDeviceLogForwarder(),
startStopped: true,
);
launchCompleter.complete();

expect(fakeProcessManager, hasNoRemainingExpectations);
expect(logger.errorText, isEmpty);
expect(result, isTrue);
});
});

group('terminate app', () {
Expand Down