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: 1dc26f80f0/dev/devicelab/lib/framework/devices.dart (L998-L1006)
This commit is contained in:
parent
95cd3c0340
commit
cd18c8c02f
@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,30 @@ import '../globals.dart' as globals;
|
||||
import '../tester/flutter_tester.dart';
|
||||
import '../web/web_device.dart';
|
||||
|
||||
/// Common flutter command line options.
|
||||
abstract final class FlutterGlobalOptions {
|
||||
static const String kColorFlag = 'color';
|
||||
static const String kContinuousIntegrationFlag = 'ci';
|
||||
static const String kDeviceIdOption = 'device-id';
|
||||
static const String kDisableTelemetryFlag = 'disable-telemetry';
|
||||
static const String kEnableTelemetryFlag = 'enable-telemetry';
|
||||
static const String kLocalEngineOption = 'local-engine';
|
||||
static const String kLocalEngineSrcPathOption = 'local-engine-src-path';
|
||||
static const String kLocalWebSDKOption = 'local-web-sdk';
|
||||
static const String kMachineFlag = 'machine';
|
||||
static const String kPackagesOption = 'packages';
|
||||
static const String kPrefixedErrorsFlag = 'prefixed-errors';
|
||||
static const String kQuietFlag = 'quiet';
|
||||
static const String kShowTestDeviceFlag = 'show-test-device';
|
||||
static const String kShowWebServerDeviceFlag = 'show-web-server-device';
|
||||
static const String kSuppressAnalyticsFlag = 'suppress-analytics';
|
||||
static const String kVerboseFlag = 'verbose';
|
||||
static const String kVersionCheckFlag = 'version-check';
|
||||
static const String kVersionFlag = 'version';
|
||||
static const String kWrapColumnOption = 'wrap-column';
|
||||
static const String kWrapFlag = 'wrap';
|
||||
}
|
||||
|
||||
class FlutterCommandRunner extends CommandRunner<void> {
|
||||
FlutterCommandRunner({ bool verboseHelp = false }) : super(
|
||||
'flutter',
|
||||
@ -33,80 +57,80 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
||||
' flutter run [options]\n'
|
||||
' Run your Flutter application on an attached device or in an emulator.',
|
||||
) {
|
||||
argParser.addFlag('verbose',
|
||||
argParser.addFlag(FlutterGlobalOptions.kVerboseFlag,
|
||||
abbr: 'v',
|
||||
negatable: false,
|
||||
help: 'Noisy logging, including all shell commands executed.\n'
|
||||
'If used with "--help", shows hidden options. '
|
||||
'If used with "flutter doctor", shows additional diagnostic information. '
|
||||
'(Use "-vv" to force verbose logging in those cases.)');
|
||||
argParser.addFlag('prefixed-errors',
|
||||
argParser.addFlag(FlutterGlobalOptions.kPrefixedErrorsFlag,
|
||||
negatable: false,
|
||||
help: 'Causes lines sent to stderr to be prefixed with "ERROR:".',
|
||||
hide: !verboseHelp);
|
||||
argParser.addFlag('quiet',
|
||||
argParser.addFlag(FlutterGlobalOptions.kQuietFlag,
|
||||
negatable: false,
|
||||
hide: !verboseHelp,
|
||||
help: 'Reduce the amount of output from some commands.');
|
||||
argParser.addFlag('wrap',
|
||||
argParser.addFlag(FlutterGlobalOptions.kWrapFlag,
|
||||
hide: !verboseHelp,
|
||||
help: 'Toggles output word wrapping, regardless of whether or not the output is a terminal.',
|
||||
defaultsTo: true);
|
||||
argParser.addOption('wrap-column',
|
||||
argParser.addOption(FlutterGlobalOptions.kWrapColumnOption,
|
||||
hide: !verboseHelp,
|
||||
help: 'Sets the output wrap column. If not set, uses the width of the terminal. No '
|
||||
'wrapping occurs if not writing to a terminal. Use "--no-wrap" to turn off wrapping '
|
||||
'when connected to a terminal.');
|
||||
argParser.addOption('device-id',
|
||||
argParser.addOption(FlutterGlobalOptions.kDeviceIdOption,
|
||||
abbr: 'd',
|
||||
help: 'Target device id or name (prefixes allowed).');
|
||||
argParser.addFlag('version',
|
||||
argParser.addFlag(FlutterGlobalOptions.kVersionFlag,
|
||||
negatable: false,
|
||||
help: 'Reports the version of this tool.');
|
||||
argParser.addFlag('machine',
|
||||
argParser.addFlag(FlutterGlobalOptions.kMachineFlag,
|
||||
negatable: false,
|
||||
hide: !verboseHelp,
|
||||
help: 'When used with the "--version" flag, outputs the information using JSON.');
|
||||
argParser.addFlag('color',
|
||||
argParser.addFlag(FlutterGlobalOptions.kColorFlag,
|
||||
hide: !verboseHelp,
|
||||
help: 'Whether to use terminal colors (requires support for ANSI escape sequences).',
|
||||
defaultsTo: true);
|
||||
argParser.addFlag('version-check',
|
||||
argParser.addFlag(FlutterGlobalOptions.kVersionCheckFlag,
|
||||
defaultsTo: true,
|
||||
hide: !verboseHelp,
|
||||
help: 'Allow Flutter to check for updates when this command runs.');
|
||||
argParser.addFlag('suppress-analytics',
|
||||
argParser.addFlag(FlutterGlobalOptions.kSuppressAnalyticsFlag,
|
||||
negatable: false,
|
||||
help: 'Suppress analytics reporting for the current CLI invocation.');
|
||||
argParser.addFlag('disable-telemetry',
|
||||
argParser.addFlag(FlutterGlobalOptions.kDisableTelemetryFlag,
|
||||
negatable: false,
|
||||
help: 'Disable telemetry reporting each time a flutter or dart '
|
||||
'command runs, until it is re-enabled.');
|
||||
argParser.addFlag('enable-telemetry',
|
||||
argParser.addFlag(FlutterGlobalOptions.kEnableTelemetryFlag,
|
||||
negatable: false,
|
||||
help: 'Enable telemetry reporting each time a flutter or dart '
|
||||
'command runs.');
|
||||
argParser.addOption('packages',
|
||||
argParser.addOption(FlutterGlobalOptions.kPackagesOption,
|
||||
hide: !verboseHelp,
|
||||
help: 'Path to your "package_config.json" file.');
|
||||
if (verboseHelp) {
|
||||
argParser.addSeparator('Local build selection options (not normally required):');
|
||||
}
|
||||
|
||||
argParser.addOption('local-engine-src-path',
|
||||
argParser.addOption(FlutterGlobalOptions.kLocalEngineSrcPathOption,
|
||||
hide: !verboseHelp,
|
||||
help: 'Path to your engine src directory, if you are building Flutter locally.\n'
|
||||
'Defaults to \$$kFlutterEngineEnvironmentVariableName if set, otherwise defaults to '
|
||||
'the path given in your pubspec.yaml dependency_overrides for $kFlutterEnginePackageName, '
|
||||
'if any.');
|
||||
|
||||
argParser.addOption('local-engine',
|
||||
argParser.addOption(FlutterGlobalOptions.kLocalEngineOption,
|
||||
hide: !verboseHelp,
|
||||
help: 'Name of a build output within the engine out directory, if you are building Flutter locally.\n'
|
||||
'Use this to select a specific version of the engine if you have built multiple engine targets.\n'
|
||||
'This path is relative to "--local-engine-src-path" (see above).');
|
||||
|
||||
argParser.addOption('local-web-sdk',
|
||||
argParser.addOption(FlutterGlobalOptions.kLocalWebSDKOption,
|
||||
hide: !verboseHelp,
|
||||
help: 'Name of a build output within the engine out directory, if you are building Flutter locally.\n'
|
||||
'Use this to select a specific version of the web sdk if you have built multiple engine targets.\n'
|
||||
@ -115,16 +139,22 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
||||
if (verboseHelp) {
|
||||
argParser.addSeparator('Options for testing the "flutter" tool itself:');
|
||||
}
|
||||
argParser.addFlag('show-test-device',
|
||||
argParser.addFlag(FlutterGlobalOptions.kShowTestDeviceFlag,
|
||||
negatable: false,
|
||||
hide: !verboseHelp,
|
||||
help: 'List the special "flutter-tester" device in device listings. '
|
||||
'This headless device is used to test Flutter tooling.');
|
||||
argParser.addFlag('show-web-server-device',
|
||||
argParser.addFlag(FlutterGlobalOptions.kShowWebServerDeviceFlag,
|
||||
negatable: false,
|
||||
hide: !verboseHelp,
|
||||
help: 'List the special "web-server" device in device listings.',
|
||||
);
|
||||
argParser.addFlag(
|
||||
FlutterGlobalOptions.kContinuousIntegrationFlag,
|
||||
negatable: false,
|
||||
help: 'Enable a set of CI-specific test debug settings.',
|
||||
hide: !verboseHelp,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -198,8 +228,8 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
||||
|
||||
// If the flag for enabling or disabling telemetry is passed in,
|
||||
// we will return out
|
||||
if (topLevelResults.wasParsed('disable-telemetry') ||
|
||||
topLevelResults.wasParsed('enable-telemetry')) {
|
||||
if (topLevelResults.wasParsed(FlutterGlobalOptions.kDisableTelemetryFlag) ||
|
||||
topLevelResults.wasParsed(FlutterGlobalOptions.kEnableTelemetryFlag)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -207,43 +237,43 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
||||
// wrapping will occur at this width explicitly, and won't adapt if the
|
||||
// terminal size changes during a run.
|
||||
int? wrapColumn;
|
||||
if (topLevelResults.wasParsed('wrap-column')) {
|
||||
if (topLevelResults.wasParsed(FlutterGlobalOptions.kWrapColumnOption)) {
|
||||
try {
|
||||
wrapColumn = int.parse(topLevelResults['wrap-column'] as String);
|
||||
wrapColumn = int.parse(topLevelResults[FlutterGlobalOptions.kWrapColumnOption] as String);
|
||||
if (wrapColumn < 0) {
|
||||
throwToolExit(userMessages.runnerWrapColumnInvalid(topLevelResults['wrap-column']));
|
||||
throwToolExit(userMessages.runnerWrapColumnInvalid(topLevelResults[FlutterGlobalOptions.kWrapColumnOption]));
|
||||
}
|
||||
} on FormatException {
|
||||
throwToolExit(userMessages.runnerWrapColumnParseError(topLevelResults['wrap-column']));
|
||||
throwToolExit(userMessages.runnerWrapColumnParseError(topLevelResults[FlutterGlobalOptions.kWrapColumnOption]));
|
||||
}
|
||||
}
|
||||
|
||||
// If we're not writing to a terminal with a defined width, then don't wrap
|
||||
// anything, unless the user explicitly said to.
|
||||
final bool useWrapping = topLevelResults.wasParsed('wrap')
|
||||
? topLevelResults['wrap'] as bool
|
||||
: globals.stdio.terminalColumns != null && topLevelResults['wrap'] as bool;
|
||||
final bool useWrapping = topLevelResults.wasParsed(FlutterGlobalOptions.kWrapFlag)
|
||||
? topLevelResults[FlutterGlobalOptions.kWrapFlag] as bool
|
||||
: globals.stdio.terminalColumns != null && topLevelResults[FlutterGlobalOptions.kWrapFlag] as bool;
|
||||
contextOverrides[OutputPreferences] = OutputPreferences(
|
||||
wrapText: useWrapping,
|
||||
showColor: topLevelResults['color'] as bool?,
|
||||
showColor: topLevelResults[FlutterGlobalOptions.kColorFlag] as bool?,
|
||||
wrapColumn: wrapColumn,
|
||||
);
|
||||
|
||||
if (((topLevelResults['show-test-device'] as bool?) ?? false)
|
||||
|| topLevelResults['device-id'] == FlutterTesterDevices.kTesterDeviceId) {
|
||||
if (((topLevelResults[FlutterGlobalOptions.kShowTestDeviceFlag] as bool?) ?? false)
|
||||
|| topLevelResults[FlutterGlobalOptions.kDeviceIdOption] == FlutterTesterDevices.kTesterDeviceId) {
|
||||
FlutterTesterDevices.showFlutterTesterDevice = true;
|
||||
}
|
||||
if (((topLevelResults['show-web-server-device'] as bool?) ?? false)
|
||||
|| topLevelResults['device-id'] == WebServerDevice.kWebServerDeviceId) {
|
||||
if (((topLevelResults[FlutterGlobalOptions.kShowWebServerDeviceFlag] as bool?) ?? false)
|
||||
|| topLevelResults[FlutterGlobalOptions.kDeviceIdOption] == WebServerDevice.kWebServerDeviceId) {
|
||||
WebServerDevice.showWebServerDevice = true;
|
||||
}
|
||||
|
||||
// Set up the tooling configuration.
|
||||
final EngineBuildPaths? engineBuildPaths = await globals.localEngineLocator?.findEnginePath(
|
||||
engineSourcePath: topLevelResults['local-engine-src-path'] as String?,
|
||||
localEngine: topLevelResults['local-engine'] as String?,
|
||||
localWebSdk: topLevelResults['local-web-sdk'] as String?,
|
||||
packagePath: topLevelResults['packages'] as String?,
|
||||
engineSourcePath: topLevelResults[FlutterGlobalOptions.kLocalEngineSrcPathOption] as String?,
|
||||
localEngine: topLevelResults[FlutterGlobalOptions.kLocalEngineOption] as String?,
|
||||
localWebSdk: topLevelResults[FlutterGlobalOptions.kLocalWebSDKOption] as String?,
|
||||
packagePath: topLevelResults[FlutterGlobalOptions.kPackagesOption] as String?,
|
||||
);
|
||||
if (engineBuildPaths != null) {
|
||||
contextOverrides.addAll(<Type, Object?>{
|
||||
@ -256,24 +286,24 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
||||
return MapEntry<Type, Generator>(type, () => value);
|
||||
}),
|
||||
body: () async {
|
||||
globals.logger.quiet = (topLevelResults['quiet'] as bool?) ?? false;
|
||||
globals.logger.quiet = (topLevelResults[FlutterGlobalOptions.kQuietFlag] as bool?) ?? false;
|
||||
|
||||
if (globals.platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true') {
|
||||
await globals.cache.lock();
|
||||
}
|
||||
|
||||
if ((topLevelResults['suppress-analytics'] as bool?) ?? false) {
|
||||
if ((topLevelResults[FlutterGlobalOptions.kSuppressAnalyticsFlag] as bool?) ?? false) {
|
||||
globals.flutterUsage.suppressAnalytics = true;
|
||||
}
|
||||
|
||||
globals.flutterVersion.ensureVersionFile();
|
||||
final bool machineFlag = topLevelResults['machine'] as bool? ?? false;
|
||||
final bool machineFlag = topLevelResults[FlutterGlobalOptions.kMachineFlag] as bool? ?? false;
|
||||
final bool ci = await globals.botDetector.isRunningOnBot;
|
||||
final bool redirectedCompletion = !globals.stdio.hasTerminal &&
|
||||
(topLevelResults.command?.name ?? '').endsWith('-completion');
|
||||
final bool isMachine = machineFlag || ci || redirectedCompletion;
|
||||
final bool versionCheckFlag = topLevelResults['version-check'] as bool? ?? false;
|
||||
final bool explicitVersionCheckPassed = topLevelResults.wasParsed('version-check') && versionCheckFlag;
|
||||
final bool versionCheckFlag = topLevelResults[FlutterGlobalOptions.kVersionCheckFlag] as bool? ?? false;
|
||||
final bool explicitVersionCheckPassed = topLevelResults.wasParsed(FlutterGlobalOptions.kVersionCheckFlag) && versionCheckFlag;
|
||||
|
||||
if (topLevelResults.command?.name != 'upgrade' &&
|
||||
(explicitVersionCheckPassed || (versionCheckFlag && !isMachine))) {
|
||||
@ -281,13 +311,13 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
||||
}
|
||||
|
||||
// See if the user specified a specific device.
|
||||
final String? specifiedDeviceId = topLevelResults['device-id'] as String?;
|
||||
final String? specifiedDeviceId = topLevelResults[FlutterGlobalOptions.kDeviceIdOption] as String?;
|
||||
if (specifiedDeviceId != null) {
|
||||
globals.deviceManager?.specifiedDeviceId = specifiedDeviceId;
|
||||
}
|
||||
|
||||
if ((topLevelResults['version'] as bool?) ?? false) {
|
||||
globals.flutterUsage.sendCommand('version');
|
||||
if ((topLevelResults[FlutterGlobalOptions.kVersionFlag] as bool?) ?? false) {
|
||||
globals.flutterUsage.sendCommand(FlutterGlobalOptions.kVersionFlag);
|
||||
globals.flutterVersion.fetchTagsAndUpdate();
|
||||
String status;
|
||||
if (machineFlag) {
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
);
|
||||
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true);
|
||||
command.argParser.addFlag('key');
|
||||
@ -60,7 +102,7 @@ void main() {
|
||||
final DummyFlutterCommand command = DummyFlutterCommand(
|
||||
commandFunction: () async {
|
||||
return const FlutterCommandResult(ExitStatus.success);
|
||||
}
|
||||
},
|
||||
);
|
||||
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true);
|
||||
command.argParser.addOption('key');
|
||||
@ -82,7 +124,7 @@ void main() {
|
||||
final DummyFlutterCommand command = DummyFlutterCommand(
|
||||
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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user