Unverified Commit cd18c8c0 authored by Victoria Ashworth's avatar Victoria Ashworth Committed by GitHub

Workaround for Dart VM timeout (#127875)

Workaround solution for: https://github.com/flutter/flutter/issues/121231
See https://github.com/flutter/flutter/issues/120808#issuecomment-1551826299 Error Case 2 for more information.

Sometimes the `ios-deploy` process does not return the logs from the application. We've been unable to figure out why. This is a solution to workaround that by using `idevicesyslog` alongside `ios-deploy` as a backup in getting the log for the Dart VM url. As explained in https://github.com/flutter/flutter/issues/120808#issuecomment-1551826299, when error case 2 happens, the `idevicesyslog` does successfully find the Dart VM.

Also, in the comments of the code it mentions `syslog` is not written on iOS 13+, this was added in response to this issue: https://github.com/flutter/flutter/issues/41133.

However, `idevicesyslog` does in fact work (at least for iOS 16), we use it to collect device logs for our CI tests already: https://github.com/flutter/flutter/blob/1dc26f80f04c1c1091a83c5fead9b3aa83483292/dev/devicelab/lib/framework/devices.dart#L998-L1006
parent 95cd3c03
......@@ -471,6 +471,10 @@ List<String> _flutterCommandArgs(String command, List<String> options) {
if (localEngineSrcPath != null) ...<String>['--local-engine-src-path', localEngineSrcPath],
if (localWebSdk != null) ...<String>['--local-web-sdk', localWebSdk],
...options,
// Use CI flag when running devicelab tests, except for `packages`/`pub` commands.
// `packages`/`pub` commands effectively runs the `pub` tool, which does not have
// the same allowed args.
if (!command.startsWith('packages') && !command.startsWith('pub')) '--ci',
];
}
......
......@@ -33,6 +33,7 @@ import '../resident_runner.dart';
import '../run_cold.dart';
import '../run_hot.dart';
import '../runner/flutter_command.dart';
import '../runner/flutter_command_runner.dart';
import '../vmservice.dart';
/// A Flutter-command that attaches to applications that have been launched
......@@ -528,6 +529,7 @@ known, it can be explicitly provided to attach via the command-line, e.g.
ddsPort: ddsPort,
devToolsServerAddress: devToolsServerAddress,
serveObservatory: serveObservatory,
usingCISystem: usingCISystem,
);
return buildInfo.isDebug
......@@ -535,7 +537,7 @@ known, it can be explicitly provided to attach via the command-line, e.g.
flutterDevices,
target: targetFile,
debuggingOptions: debuggingOptions,
packagesFilePath: globalResults!['packages'] as String?,
packagesFilePath: globalResults![FlutterGlobalOptions.kPackagesOption] as String?,
projectRootPath: stringArg('project-root'),
dillOutputPath: stringArg('output-dill'),
ipv6: usesIpv6,
......
......@@ -6,6 +6,7 @@ import '../base/common.dart';
import '../cache.dart';
import '../globals.dart' as globals;
import '../runner/flutter_command.dart';
import '../runner/flutter_command_runner.dart';
import '../version.dart';
class ChannelCommand extends FlutterCommand {
......@@ -40,7 +41,7 @@ class ChannelCommand extends FlutterCommand {
case 0:
await _listChannels(
showAll: boolArg('all'),
verbose: globalResults?['verbose'] == true,
verbose: globalResults?[FlutterGlobalOptions.kVerboseFlag] == true,
);
return FlutterCommandResult.success();
case 1:
......
......@@ -25,6 +25,7 @@ import '../custom_devices/custom_devices_config.dart';
import '../device_port_forwarder.dart';
import '../features.dart';
import '../runner/flutter_command.dart';
import '../runner/flutter_command_runner.dart';
/// just the function signature of the [print] function.
/// The Object arg may be null.
......@@ -811,7 +812,7 @@ Delete a device from the config file.
Future<FlutterCommandResult> runCommand() async {
checkFeatureEnabled();
final String? id = globalResults!['device-id'] as String?;
final String? id = globalResults![FlutterGlobalOptions.kDeviceIdOption] as String?;
if (id == null || !customDevicesConfig.contains(id)) {
throwToolExit('Couldn\'t find device with id "$id" in config at "${customDevicesConfig.configPath}"');
}
......
......@@ -26,6 +26,7 @@ import '../resident_runner.dart';
import '../run_cold.dart';
import '../run_hot.dart';
import '../runner/flutter_command.dart';
import '../runner/flutter_command_runner.dart';
import '../tracing.dart';
import '../vmservice.dart';
import '../web/web_runner.dart';
......@@ -247,6 +248,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
uninstallFirst: uninstallFirst,
enableDartProfiling: enableDartProfiling,
enableEmbedderApi: enableEmbedderApi,
usingCISystem: usingCISystem,
);
} else {
return DebuggingOptions.enabled(
......@@ -298,6 +300,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
serveObservatory: boolArg('serve-observatory'),
enableDartProfiling: enableDartProfiling,
enableEmbedderApi: enableEmbedderApi,
usingCISystem: usingCISystem,
);
}
}
......@@ -643,7 +646,7 @@ class RunCommand extends RunCommandBase {
: globals.fs.file(applicationBinaryPath),
trackWidgetCreation: trackWidgetCreation,
projectRootPath: stringArg('project-root'),
packagesFilePath: globalResults!['packages'] as String?,
packagesFilePath: globalResults![FlutterGlobalOptions.kPackagesOption] as String?,
dillOutputPath: stringArg('output-dill'),
ipv6: ipv6 ?? false,
multidexEnabled: boolArg('multidex'),
......
......@@ -422,6 +422,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
disablePortPublication: true,
enableDds: enableDds,
nullAssertions: boolArg(FlutterOptions.kNullAssertions),
usingCISystem: usingCISystem,
);
Device? integrationTestDevice;
......
......@@ -971,6 +971,7 @@ class DebuggingOptions {
this.serveObservatory = false,
this.enableDartProfiling = true,
this.enableEmbedderApi = false,
this.usingCISystem = false,
}) : debuggingEnabled = true;
DebuggingOptions.disabled(this.buildInfo, {
......@@ -993,6 +994,7 @@ class DebuggingOptions {
this.uninstallFirst = false,
this.enableDartProfiling = true,
this.enableEmbedderApi = false,
this.usingCISystem = false,
}) : debuggingEnabled = false,
useTestFonts = false,
startPaused = false,
......@@ -1069,6 +1071,7 @@ class DebuggingOptions {
required this.serveObservatory,
required this.enableDartProfiling,
required this.enableEmbedderApi,
required this.usingCISystem,
});
final bool debuggingEnabled;
......@@ -1109,6 +1112,7 @@ class DebuggingOptions {
final bool serveObservatory;
final bool enableDartProfiling;
final bool enableEmbedderApi;
final bool usingCISystem;
/// Whether the tool should try to uninstall a previously installed version of the app.
///
......@@ -1243,6 +1247,7 @@ class DebuggingOptions {
'serveObservatory': serveObservatory,
'enableDartProfiling': enableDartProfiling,
'enableEmbedderApi': enableEmbedderApi,
'usingCISystem': usingCISystem,
};
static DebuggingOptions fromJson(Map<String, Object?> json, BuildInfo buildInfo) =>
......@@ -1294,6 +1299,7 @@ class DebuggingOptions {
serveObservatory: (json['serveObservatory'] as bool?) ?? false,
enableDartProfiling: (json['enableDartProfiling'] as bool?) ?? true,
enableEmbedderApi: (json['enableEmbedderApi'] as bool?) ?? false,
usingCISystem: (json['usingCISystem'] as bool?) ?? false,
);
}
......
......@@ -482,7 +482,10 @@ class IOSDevice extends Device {
int installationResult = 1;
if (debuggingOptions.debuggingEnabled) {
_logger.printTrace('Debugging is enabled, connecting to vmService');
final DeviceLogReader deviceLogReader = getLogReader(app: package);
final DeviceLogReader deviceLogReader = getLogReader(
app: package,
usingCISystem: debuggingOptions.usingCISystem,
);
// If the device supports syslog reading, prefer launching the app without
// attaching the debugger to avoid the overhead of the unnecessary extra running process.
......@@ -629,12 +632,14 @@ class IOSDevice extends Device {
DeviceLogReader getLogReader({
covariant IOSApp? app,
bool includePastLogs = false,
bool usingCISystem = false,
}) {
assert(!includePastLogs, 'Past log reading not supported on iOS devices.');
return _logReaders.putIfAbsent(app, () => IOSDeviceLogReader.create(
device: this,
app: app,
iMobileDevice: _iMobileDevice,
usingCISystem: usingCISystem,
));
}
......@@ -749,17 +754,20 @@ class IOSDeviceLogReader extends DeviceLogReader {
this._deviceId,
this.name,
String appName,
bool usingCISystem,
) : // Match for lines for the runner in syslog.
//
// iOS 9 format: Runner[297] <Notice>:
// iOS 10 format: Runner(Flutter)[297] <Notice>:
_runnerLineRegex = RegExp(appName + r'(\(Flutter\))?\[[\d]+\] <[A-Za-z]+>: ');
_runnerLineRegex = RegExp(appName + r'(\(Flutter\))?\[[\d]+\] <[A-Za-z]+>: '),
_usingCISystem = usingCISystem;
/// Create a new [IOSDeviceLogReader].
factory IOSDeviceLogReader.create({
required IOSDevice device,
IOSApp? app,
required IMobileDevice iMobileDevice,
bool usingCISystem = false,
}) {
final String appName = app?.name?.replaceAll('.app', '') ?? '';
return IOSDeviceLogReader._(
......@@ -768,6 +776,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
device.id,
device.name,
appName,
usingCISystem,
);
}
......@@ -775,9 +784,17 @@ class IOSDeviceLogReader extends DeviceLogReader {
factory IOSDeviceLogReader.test({
required IMobileDevice iMobileDevice,
bool useSyslog = true,
bool usingCISystem = false,
int? majorSdkVersion,
}) {
final int sdkVersion;
if (majorSdkVersion != null) {
sdkVersion = majorSdkVersion;
} else {
sdkVersion = useSyslog ? 12 : 13;
}
return IOSDeviceLogReader._(
iMobileDevice, useSyslog ? 12 : 13, '1234', 'test', 'Runner');
iMobileDevice, sdkVersion, '1234', 'test', 'Runner', usingCISystem);
}
@override
......@@ -785,6 +802,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
final int _majorSdkVersion;
final String _deviceId;
final IMobileDevice _iMobileDevice;
final bool _usingCISystem;
// Matches a syslog line from the runner.
RegExp _runnerLineRegex;
......@@ -810,12 +828,42 @@ class IOSDeviceLogReader extends DeviceLogReader {
// Sometimes (race condition?) we try to send a log after the controller has
// been closed. See https://github.com/flutter/flutter/issues/99021 for more
// context.
void _addToLinesController(String message) {
void _addToLinesController(String message, IOSDeviceLogSource source) {
if (!linesController.isClosed) {
if (_excludeLog(message, source)) {
return;
}
linesController.add(message);
}
}
/// Used to track messages prefixed with "flutter:" when [useBothLogDeviceReaders]
/// is true.
final List<String> _streamFlutterMessages = <String>[];
/// When using both `idevicesyslog` and `ios-deploy`, exclude logs with the
/// "flutter:" prefix if they have already been added to the stream. This is
/// to prevent duplicates from being printed.
///
/// If a message does not have the prefix, exclude it if the message's
/// source is `idevicesyslog`. This is done because `ios-deploy` and
/// `idevicesyslog` often have different prefixes on non-flutter messages
/// and are often not critical for CI tests.
bool _excludeLog(String message, IOSDeviceLogSource source) {
if (!useBothLogDeviceReaders) {
return false;
}
if (message.startsWith('flutter:')) {
if (_streamFlutterMessages.contains(message)) {
return true;
}
_streamFlutterMessages.add(message);
} else if (source == IOSDeviceLogSource.idevicesyslog) {
return true;
}
return false;
}
final List<StreamSubscription<void>> _loggingSubscriptions = <StreamSubscription<void>>[];
@override
......@@ -835,6 +883,10 @@ class IOSDeviceLogReader extends DeviceLogReader {
static const int minimumUniversalLoggingSdkVersion = 13;
/// Listen to Dart VM for logs on iOS 13 or greater.
///
/// Only send logs to stream if [_iosDeployDebugger] is null or
/// the [_iosDeployDebugger] debugger is not attached.
Future<void> _listenToUnifiedLoggingEvents(FlutterVmService connectedVmService) async {
if (_majorSdkVersion < minimumUniversalLoggingSdkVersion) {
return;
......@@ -859,7 +911,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
}
final String message = processVmServiceMessage(event);
if (message.isNotEmpty) {
_addToLinesController(message);
_addToLinesController(message, IOSDeviceLogSource.unifiedLogging);
}
}
......@@ -871,8 +923,10 @@ class IOSDeviceLogReader extends DeviceLogReader {
/// Log reader will listen to [debugger.logLines] and will detach debugger on dispose.
IOSDeployDebugger? get debuggerStream => _iosDeployDebugger;
/// Send messages from ios-deploy debugger stream to device log reader stream.
set debuggerStream(IOSDeployDebugger? debugger) {
// Logging is gathered from syslog on iOS 13 and earlier.
// Logging is gathered from syslog on iOS earlier than 13.
if (_majorSdkVersion < minimumUniversalLoggingSdkVersion) {
return;
}
......@@ -882,7 +936,10 @@ class IOSDeviceLogReader extends DeviceLogReader {
}
// Add the debugger logs to the controller created on initialization.
_loggingSubscriptions.add(debugger.logLines.listen(
(String line) => _addToLinesController(_debuggerLineHandler(line)),
(String line) => _addToLinesController(
_debuggerLineHandler(line),
IOSDeviceLogSource.iosDeploy,
),
onError: linesController.addError,
onDone: linesController.close,
cancelOnError: true,
......@@ -893,18 +950,38 @@ class IOSDeviceLogReader extends DeviceLogReader {
// Strip off the logging metadata (leave the category), or just echo the line.
String _debuggerLineHandler(String line) => _debuggerLoggingRegex.firstMatch(line)?.group(1) ?? line;
/// Use both logs from `idevicesyslog` and `ios-deploy` when debugging from CI system
/// since sometimes `ios-deploy` does not return the device logs:
/// https://github.com/flutter/flutter/issues/121231
@visibleForTesting
bool get useBothLogDeviceReaders {
return _usingCISystem && _majorSdkVersion >= 16;
}
/// Start and listen to idevicesyslog to get device logs for iOS versions
/// prior to 13 or if [useBothLogDeviceReaders] is true.
void _listenToSysLog() {
// syslog is not written on iOS 13+.
if (_majorSdkVersion >= minimumUniversalLoggingSdkVersion) {
// Syslog stopped working on iOS 13 (https://github.com/flutter/flutter/issues/41133).
// However, from at least iOS 16, it has began working again. It's unclear
// why it started working again so only use syslogs for iOS versions prior
// to 13 unless [useBothLogDeviceReaders] is true.
if (!useBothLogDeviceReaders && _majorSdkVersion >= minimumUniversalLoggingSdkVersion) {
return;
}
_iMobileDevice.startLogger(_deviceId).then<void>((Process process) {
process.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(_newSyslogLineHandler());
process.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(_newSyslogLineHandler());
process.exitCode.whenComplete(() {
if (linesController.hasListener) {
linesController.close();
if (!linesController.hasListener) {
return;
}
// When using both log readers, do not close the stream on exit.
// This is to allow ios-deploy to be the source of authority to close
// the stream.
if (useBothLogDeviceReaders && debuggerStream != null) {
return;
}
linesController.close();
});
assert(idevicesyslogProcess == null);
idevicesyslogProcess = process;
......@@ -926,7 +1003,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
return (String line) {
if (printing) {
if (!_anyLineRegex.hasMatch(line)) {
_addToLinesController(decodeSyslog(line));
_addToLinesController(decodeSyslog(line), IOSDeviceLogSource.idevicesyslog);
return;
}
......@@ -938,8 +1015,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
if (match != null) {
final String logLine = line.substring(match.end);
// Only display the log line after the initial device and executable information.
_addToLinesController(decodeSyslog(logLine));
_addToLinesController(decodeSyslog(logLine), IOSDeviceLogSource.idevicesyslog);
printing = true;
}
};
......@@ -955,6 +1031,15 @@ class IOSDeviceLogReader extends DeviceLogReader {
}
}
enum IOSDeviceLogSource {
/// Gets logs from ios-deploy debugger.
iosDeploy,
/// Gets logs from idevicesyslog.
idevicesyslog,
/// Gets logs from the Dart VM Service.
unifiedLogging,
}
/// A [DevicePortForwarder] specialized for iOS usage with iproxy.
class IOSDevicePortForwarder extends DevicePortForwarder {
......
......@@ -36,6 +36,8 @@ import 'devfs.dart';
import 'device.dart';
import 'features.dart';
import 'globals.dart' as globals;
import 'ios/application_package.dart';
import 'ios/devices.dart';
import 'project.dart';
import 'resident_devtools_handler.dart';
import 'run_cold.dart';
......@@ -391,11 +393,19 @@ class FlutterDevice {
return devFS!.create();
}
Future<void> startEchoingDeviceLog() async {
Future<void> startEchoingDeviceLog(DebuggingOptions debuggingOptions) async {
if (_loggingSubscription != null) {
return;
}
final Stream<String> logStream = (await device!.getLogReader(app: package)).logLines;
final Stream<String> logStream;
if (device is IOSDevice) {
logStream = (device! as IOSDevice).getLogReader(
app: package as IOSApp?,
usingCISystem: debuggingOptions.usingCISystem,
).logLines;
} else {
logStream = (await device!.getLogReader(app: package)).logLines;
}
_loggingSubscription = logStream.listen((String line) {
if (!line.contains(globals.kVMServiceMessageRegExp)) {
globals.printStatus(line, wrap: false);
......@@ -451,7 +461,7 @@ class FlutterDevice {
'multidex': hotRunner.multidexEnabled,
};
await startEchoingDeviceLog();
await startEchoingDeviceLog(hotRunner.debuggingOptions);
// Start the application.
final Future<LaunchResult> futureResult = device!.startApp(
......@@ -519,7 +529,7 @@ class FlutterDevice {
platformArgs['trace-startup'] = coldRunner.traceStartup;
platformArgs['multidex'] = coldRunner.multidexEnabled;
await startEchoingDeviceLog();
await startEchoingDeviceLog(coldRunner.debuggingOptions);
final LaunchResult result = await device!.startApp(
applicationPackage,
......
......@@ -304,7 +304,10 @@ abstract class FlutterCommand extends Command<void> {
/// Path to the Dart's package config file.
///
/// This can be overridden by some of its subclasses.
String? get packagesPath => globalResults?['packages'] as String?;
String? get packagesPath => stringArg(FlutterGlobalOptions.kPackagesOption, global: true);
/// Whether flutter is being run from our CI.
bool get usingCISystem => boolArg(FlutterGlobalOptions.kContinuousIntegrationFlag, global: true);
/// The value of the `--filesystem-scheme` argument.
///
......@@ -1634,17 +1637,30 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and
///
/// If no flag named [name] was added to the [ArgParser], an [ArgumentError]
/// will be thrown.
bool boolArg(String name) => argResults![name] as bool;
bool boolArg(String name, {bool global = false}) {
if (global) {
return globalResults![name] as bool;
}
return argResults![name] as bool;
}
/// Gets the parsed command-line option named [name] as a `String`.
///
/// If no option named [name] was added to the [ArgParser], an [ArgumentError]
/// will be thrown.
String? stringArg(String name) => argResults![name] as String?;
String? stringArg(String name, {bool global = false}) {
if (global) {
return globalResults![name] as String?;
}
return argResults![name] as String?;
}
/// Gets the parsed command-line option named [name] as `List<String>`.
List<String> stringsArg(String name) {
return argResults![name]! as List<String>? ?? <String>[];
List<String> stringsArg(String name, {bool global = false}) {
if (global) {
return globalResults![name] as List<String>;
}
return argResults![name] as List<String>;
}
}
......
......@@ -1381,6 +1381,7 @@ class FakeIOSDevice extends Fake implements IOSDevice {
DeviceLogReader getLogReader({
IOSApp? app,
bool includePastLogs = false,
bool usingCISystem = false,
}) {
if (onGetLogReader == null) {
throw UnimplementedError(
......
......@@ -425,6 +425,7 @@ void main() {
'--enable-software-rendering',
'--skia-deterministic-rendering',
'--enable-embedder-api',
'--ci',
]), throwsToolExit());
final DebuggingOptions options = await command.createDebuggingOptions(false);
......@@ -440,6 +441,7 @@ void main() {
expect(options.traceSystrace, true);
expect(options.enableSoftwareRendering, true);
expect(options.skiaDeterministicRendering, true);
expect(options.usingCISystem, true);
}, overrides: <Type, Generator>{
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => MemoryFileSystem.test(),
......
......@@ -1096,6 +1096,7 @@ void main() {
'--enable-software-rendering',
'--skia-deterministic-rendering',
'--enable-embedder-api',
'--ci',
]), throwsToolExit());
final DebuggingOptions options = await command.createDebuggingOptions(false);
......@@ -1114,6 +1115,7 @@ void main() {
expect(options.impellerForceGL, true);
expect(options.enableSoftwareRendering, true);
expect(options.skiaDeterministicRendering, true);
expect(options.usingCISystem, true);
}, overrides: <Type, Generator>{
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => MemoryFileSystem.test(),
......
......@@ -30,11 +30,53 @@ void main() {
}
}));
testUsingContext('Global arg results are available in FlutterCommands', () async {
final DummyFlutterCommand command = DummyFlutterCommand(
commandFunction: () async {
return const FlutterCommandResult(ExitStatus.success);
},
);
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true);
runner.addCommand(command);
await runner.run(<String>['dummy', '--${FlutterGlobalOptions.kContinuousIntegrationFlag}']);
expect(command.globalResults, isNotNull);
expect(command.boolArg(FlutterGlobalOptions.kContinuousIntegrationFlag, global: true), true);
});
testUsingContext('Global arg results are available in FlutterCommands sub commands', () async {
final DummyFlutterCommand command = DummyFlutterCommand(
commandFunction: () async {
return const FlutterCommandResult(ExitStatus.success);
},
);
final DummyFlutterCommand subcommand = DummyFlutterCommand(
name: 'sub',
commandFunction: () async {
return const FlutterCommandResult(ExitStatus.success);
},
);
command.addSubcommand(subcommand);
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true);
runner.addCommand(command);
runner.addCommand(subcommand);
await runner.run(<String>['dummy', 'sub', '--${FlutterGlobalOptions.kContinuousIntegrationFlag}']);
expect(subcommand.globalResults, isNotNull);
expect(subcommand.boolArg(FlutterGlobalOptions.kContinuousIntegrationFlag, global: true), true);
});
testUsingContext('bool? safe argResults', () async {
final DummyFlutterCommand command = DummyFlutterCommand(
commandFunction: () async {
return const FlutterCommandResult(ExitStatus.success);
}
commandFunction: () async {
return const FlutterCommandResult(ExitStatus.success);
},
);
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true);
command.argParser.addFlag('key');
......@@ -58,9 +100,9 @@ void main() {
testUsingContext('String? safe argResults', () async {
final DummyFlutterCommand command = DummyFlutterCommand(
commandFunction: () async {
return const FlutterCommandResult(ExitStatus.success);
}
commandFunction: () async {
return const FlutterCommandResult(ExitStatus.success);
},
);
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true);
command.argParser.addOption('key');
......@@ -80,9 +122,9 @@ void main() {
testUsingContext('List<String> safe argResults', () async {
final DummyFlutterCommand command = DummyFlutterCommand(
commandFunction: () async {
return const FlutterCommandResult(ExitStatus.success);
}
commandFunction: () async {
return const FlutterCommandResult(ExitStatus.success);
},
);
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true);
command.argParser.addMultiOption(
......
......@@ -348,6 +348,165 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
);
});
});
group('both syslog and debugger stream', () {
testWithoutContext('useBothLogDeviceReaders is true when CI option is true and sdk is at least 16', () {
final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
iMobileDevice: IMobileDevice(
artifacts: artifacts,
processManager: processManager,
cache: fakeCache,
logger: logger,
),
usingCISystem: true,
majorSdkVersion: 16,
);
expect(logReader.useBothLogDeviceReaders, isTrue);
});
testWithoutContext('useBothLogDeviceReaders is false when sdk is less than 16', () {
final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
iMobileDevice: IMobileDevice(
artifacts: artifacts,
processManager: processManager,
cache: fakeCache,
logger: logger,
),
usingCISystem: true,
majorSdkVersion: 15,
);
expect(logReader.useBothLogDeviceReaders, isFalse);
});
testWithoutContext('useBothLogDeviceReaders is false when CI option is false', () {
final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
iMobileDevice: IMobileDevice(
artifacts: artifacts,
processManager: processManager,
cache: fakeCache,
logger: logger,
),
majorSdkVersion: 16,
);
expect(logReader.useBothLogDeviceReaders, isFalse);
});
testWithoutContext('syslog only sends flutter messages to stream when useBothLogDeviceReaders is true', () async {
processManager.addCommand(
FakeCommand(
command: <String>[
ideviceSyslogPath, '-u', '1234',
],
stdout: '''
Runner(Flutter)[297] <Notice>: A is for ari
Runner(Flutter)[297] <Notice>: I is for ichigo
May 30 13:56:28 Runner(Flutter)[2037] <Notice>: flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/
May 30 13:56:28 Runner(Flutter)[2037] <Notice>: flutter: This is a test
May 30 13:56:28 Runner(Flutter)[2037] <Notice>: [VERBOSE-2:FlutterDarwinContextMetalImpeller.mm(39)] Using the Impeller rendering backend.
'''
),
);
final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
iMobileDevice: IMobileDevice(
artifacts: artifacts,
processManager: processManager,
cache: fakeCache,
logger: logger,
),
usingCISystem: true,
majorSdkVersion: 16,
);
final List<String> lines = await logReader.logLines.toList();
expect(logReader.useBothLogDeviceReaders, isTrue);
expect(processManager, hasNoRemainingExpectations);
expect(lines, <String>[
'flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/',
'flutter: This is a test'
]);
});
testWithoutContext('IOSDeviceLogReader uses both syslog and ios-deploy debugger', () async {
processManager.addCommand(
FakeCommand(
command: <String>[
ideviceSyslogPath, '-u', '1234',
],
stdout: '''
May 30 13:56:28 Runner(Flutter)[2037] <Notice>: flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/
May 30 13:56:28 Runner(Flutter)[2037] <Notice>: flutter: Check for duplicate
May 30 13:56:28 Runner(Flutter)[2037] <Notice>: [VERBOSE-2:FlutterDarwinContextMetalImpeller.mm(39)] Using the Impeller rendering backend.
'''
),
);
final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[
'2023-06-01 12:49:01.445093-0500 Runner[2225:533240] flutter: Check for duplicate',
'(lldb) 2023-05-30 13:48:52.461894-0500 Runner[2019:1101495] [VERBOSE-2:FlutterDarwinContextMetalImpeller.mm(39)] Using the Impeller rendering backend.',
]);
final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
iMobileDevice: IMobileDevice(
artifacts: artifacts,
processManager: processManager,
cache: fakeCache,
logger: logger,
),
usingCISystem: true,
majorSdkVersion: 16,
);
final FakeIOSDeployDebugger iosDeployDebugger = FakeIOSDeployDebugger();
iosDeployDebugger.logLines = debuggingLogs;
logReader.debuggerStream = iosDeployDebugger;
final Future<List<String>> logLines = logReader.logLines.toList();
final List<String> lines = await logLines;
expect(logReader.useBothLogDeviceReaders, isTrue);
expect(processManager, hasNoRemainingExpectations);
expect(lines.length, 3);
expect(lines, containsAll(<String>[
'(lldb) 2023-05-30 13:48:52.461894-0500 Runner[2019:1101495] [VERBOSE-2:FlutterDarwinContextMetalImpeller.mm(39)] Using the Impeller rendering backend.',
'flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/',
'flutter: Check for duplicate',
]));
});
testWithoutContext('IOSDeviceLogReader only uses ios-deploy debugger when useBothLogDeviceReaders is false', () async {
final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[
'(lldb) 2023-05-30 13:48:52.461894-0500 Runner[2019:1101495] [VERBOSE-2:FlutterDarwinContextMetalImpeller.mm(39)] Using the Impeller rendering backend.',
'',
]);
final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
iMobileDevice: IMobileDevice(
artifacts: artifacts,
processManager: processManager,
cache: fakeCache,
logger: logger,
),
majorSdkVersion: 16,
);
final FakeIOSDeployDebugger iosDeployDebugger = FakeIOSDeployDebugger();
iosDeployDebugger.logLines = debuggingLogs;
logReader.debuggerStream = iosDeployDebugger;
final Future<List<String>> logLines = logReader.logLines.toList();
final List<String> lines = await logLines;
expect(logReader.useBothLogDeviceReaders, isFalse);
expect(processManager, hasNoRemainingExpectations);
expect(
lines.contains(
'(lldb) 2023-05-30 13:48:52.461894-0500 Runner[2019:1101495] [VERBOSE-2:FlutterDarwinContextMetalImpeller.mm(39)] Using the Impeller rendering backend.',
),
isTrue,
);
});
});
}
class FakeIOSDeployDebugger extends Fake implements IOSDeployDebugger {
......
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