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 (localEngineSrcPath != null) ...<String>['--local-engine-src-path', localEngineSrcPath],
|
||||||
if (localWebSdk != null) ...<String>['--local-web-sdk', localWebSdk],
|
if (localWebSdk != null) ...<String>['--local-web-sdk', localWebSdk],
|
||||||
...options,
|
...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_cold.dart';
|
||||||
import '../run_hot.dart';
|
import '../run_hot.dart';
|
||||||
import '../runner/flutter_command.dart';
|
import '../runner/flutter_command.dart';
|
||||||
|
import '../runner/flutter_command_runner.dart';
|
||||||
import '../vmservice.dart';
|
import '../vmservice.dart';
|
||||||
|
|
||||||
/// A Flutter-command that attaches to applications that have been launched
|
/// 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,
|
ddsPort: ddsPort,
|
||||||
devToolsServerAddress: devToolsServerAddress,
|
devToolsServerAddress: devToolsServerAddress,
|
||||||
serveObservatory: serveObservatory,
|
serveObservatory: serveObservatory,
|
||||||
|
usingCISystem: usingCISystem,
|
||||||
);
|
);
|
||||||
|
|
||||||
return buildInfo.isDebug
|
return buildInfo.isDebug
|
||||||
@ -535,7 +537,7 @@ known, it can be explicitly provided to attach via the command-line, e.g.
|
|||||||
flutterDevices,
|
flutterDevices,
|
||||||
target: targetFile,
|
target: targetFile,
|
||||||
debuggingOptions: debuggingOptions,
|
debuggingOptions: debuggingOptions,
|
||||||
packagesFilePath: globalResults!['packages'] as String?,
|
packagesFilePath: globalResults![FlutterGlobalOptions.kPackagesOption] as String?,
|
||||||
projectRootPath: stringArg('project-root'),
|
projectRootPath: stringArg('project-root'),
|
||||||
dillOutputPath: stringArg('output-dill'),
|
dillOutputPath: stringArg('output-dill'),
|
||||||
ipv6: usesIpv6,
|
ipv6: usesIpv6,
|
||||||
|
@ -6,6 +6,7 @@ import '../base/common.dart';
|
|||||||
import '../cache.dart';
|
import '../cache.dart';
|
||||||
import '../globals.dart' as globals;
|
import '../globals.dart' as globals;
|
||||||
import '../runner/flutter_command.dart';
|
import '../runner/flutter_command.dart';
|
||||||
|
import '../runner/flutter_command_runner.dart';
|
||||||
import '../version.dart';
|
import '../version.dart';
|
||||||
|
|
||||||
class ChannelCommand extends FlutterCommand {
|
class ChannelCommand extends FlutterCommand {
|
||||||
@ -40,7 +41,7 @@ class ChannelCommand extends FlutterCommand {
|
|||||||
case 0:
|
case 0:
|
||||||
await _listChannels(
|
await _listChannels(
|
||||||
showAll: boolArg('all'),
|
showAll: boolArg('all'),
|
||||||
verbose: globalResults?['verbose'] == true,
|
verbose: globalResults?[FlutterGlobalOptions.kVerboseFlag] == true,
|
||||||
);
|
);
|
||||||
return FlutterCommandResult.success();
|
return FlutterCommandResult.success();
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -25,6 +25,7 @@ import '../custom_devices/custom_devices_config.dart';
|
|||||||
import '../device_port_forwarder.dart';
|
import '../device_port_forwarder.dart';
|
||||||
import '../features.dart';
|
import '../features.dart';
|
||||||
import '../runner/flutter_command.dart';
|
import '../runner/flutter_command.dart';
|
||||||
|
import '../runner/flutter_command_runner.dart';
|
||||||
|
|
||||||
/// just the function signature of the [print] function.
|
/// just the function signature of the [print] function.
|
||||||
/// The Object arg may be null.
|
/// The Object arg may be null.
|
||||||
@ -811,7 +812,7 @@ Delete a device from the config file.
|
|||||||
Future<FlutterCommandResult> runCommand() async {
|
Future<FlutterCommandResult> runCommand() async {
|
||||||
checkFeatureEnabled();
|
checkFeatureEnabled();
|
||||||
|
|
||||||
final String? id = globalResults!['device-id'] as String?;
|
final String? id = globalResults![FlutterGlobalOptions.kDeviceIdOption] as String?;
|
||||||
if (id == null || !customDevicesConfig.contains(id)) {
|
if (id == null || !customDevicesConfig.contains(id)) {
|
||||||
throwToolExit('Couldn\'t find device with id "$id" in config at "${customDevicesConfig.configPath}"');
|
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_cold.dart';
|
||||||
import '../run_hot.dart';
|
import '../run_hot.dart';
|
||||||
import '../runner/flutter_command.dart';
|
import '../runner/flutter_command.dart';
|
||||||
|
import '../runner/flutter_command_runner.dart';
|
||||||
import '../tracing.dart';
|
import '../tracing.dart';
|
||||||
import '../vmservice.dart';
|
import '../vmservice.dart';
|
||||||
import '../web/web_runner.dart';
|
import '../web/web_runner.dart';
|
||||||
@ -247,6 +248,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
|
|||||||
uninstallFirst: uninstallFirst,
|
uninstallFirst: uninstallFirst,
|
||||||
enableDartProfiling: enableDartProfiling,
|
enableDartProfiling: enableDartProfiling,
|
||||||
enableEmbedderApi: enableEmbedderApi,
|
enableEmbedderApi: enableEmbedderApi,
|
||||||
|
usingCISystem: usingCISystem,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return DebuggingOptions.enabled(
|
return DebuggingOptions.enabled(
|
||||||
@ -298,6 +300,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
|
|||||||
serveObservatory: boolArg('serve-observatory'),
|
serveObservatory: boolArg('serve-observatory'),
|
||||||
enableDartProfiling: enableDartProfiling,
|
enableDartProfiling: enableDartProfiling,
|
||||||
enableEmbedderApi: enableEmbedderApi,
|
enableEmbedderApi: enableEmbedderApi,
|
||||||
|
usingCISystem: usingCISystem,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -643,7 +646,7 @@ class RunCommand extends RunCommandBase {
|
|||||||
: globals.fs.file(applicationBinaryPath),
|
: globals.fs.file(applicationBinaryPath),
|
||||||
trackWidgetCreation: trackWidgetCreation,
|
trackWidgetCreation: trackWidgetCreation,
|
||||||
projectRootPath: stringArg('project-root'),
|
projectRootPath: stringArg('project-root'),
|
||||||
packagesFilePath: globalResults!['packages'] as String?,
|
packagesFilePath: globalResults![FlutterGlobalOptions.kPackagesOption] as String?,
|
||||||
dillOutputPath: stringArg('output-dill'),
|
dillOutputPath: stringArg('output-dill'),
|
||||||
ipv6: ipv6 ?? false,
|
ipv6: ipv6 ?? false,
|
||||||
multidexEnabled: boolArg('multidex'),
|
multidexEnabled: boolArg('multidex'),
|
||||||
|
@ -422,6 +422,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
|
|||||||
disablePortPublication: true,
|
disablePortPublication: true,
|
||||||
enableDds: enableDds,
|
enableDds: enableDds,
|
||||||
nullAssertions: boolArg(FlutterOptions.kNullAssertions),
|
nullAssertions: boolArg(FlutterOptions.kNullAssertions),
|
||||||
|
usingCISystem: usingCISystem,
|
||||||
);
|
);
|
||||||
|
|
||||||
Device? integrationTestDevice;
|
Device? integrationTestDevice;
|
||||||
|
@ -971,6 +971,7 @@ class DebuggingOptions {
|
|||||||
this.serveObservatory = false,
|
this.serveObservatory = false,
|
||||||
this.enableDartProfiling = true,
|
this.enableDartProfiling = true,
|
||||||
this.enableEmbedderApi = false,
|
this.enableEmbedderApi = false,
|
||||||
|
this.usingCISystem = false,
|
||||||
}) : debuggingEnabled = true;
|
}) : debuggingEnabled = true;
|
||||||
|
|
||||||
DebuggingOptions.disabled(this.buildInfo, {
|
DebuggingOptions.disabled(this.buildInfo, {
|
||||||
@ -993,6 +994,7 @@ class DebuggingOptions {
|
|||||||
this.uninstallFirst = false,
|
this.uninstallFirst = false,
|
||||||
this.enableDartProfiling = true,
|
this.enableDartProfiling = true,
|
||||||
this.enableEmbedderApi = false,
|
this.enableEmbedderApi = false,
|
||||||
|
this.usingCISystem = false,
|
||||||
}) : debuggingEnabled = false,
|
}) : debuggingEnabled = false,
|
||||||
useTestFonts = false,
|
useTestFonts = false,
|
||||||
startPaused = false,
|
startPaused = false,
|
||||||
@ -1069,6 +1071,7 @@ class DebuggingOptions {
|
|||||||
required this.serveObservatory,
|
required this.serveObservatory,
|
||||||
required this.enableDartProfiling,
|
required this.enableDartProfiling,
|
||||||
required this.enableEmbedderApi,
|
required this.enableEmbedderApi,
|
||||||
|
required this.usingCISystem,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool debuggingEnabled;
|
final bool debuggingEnabled;
|
||||||
@ -1109,6 +1112,7 @@ class DebuggingOptions {
|
|||||||
final bool serveObservatory;
|
final bool serveObservatory;
|
||||||
final bool enableDartProfiling;
|
final bool enableDartProfiling;
|
||||||
final bool enableEmbedderApi;
|
final bool enableEmbedderApi;
|
||||||
|
final bool usingCISystem;
|
||||||
|
|
||||||
/// Whether the tool should try to uninstall a previously installed version of the app.
|
/// Whether the tool should try to uninstall a previously installed version of the app.
|
||||||
///
|
///
|
||||||
@ -1243,6 +1247,7 @@ class DebuggingOptions {
|
|||||||
'serveObservatory': serveObservatory,
|
'serveObservatory': serveObservatory,
|
||||||
'enableDartProfiling': enableDartProfiling,
|
'enableDartProfiling': enableDartProfiling,
|
||||||
'enableEmbedderApi': enableEmbedderApi,
|
'enableEmbedderApi': enableEmbedderApi,
|
||||||
|
'usingCISystem': usingCISystem,
|
||||||
};
|
};
|
||||||
|
|
||||||
static DebuggingOptions fromJson(Map<String, Object?> json, BuildInfo buildInfo) =>
|
static DebuggingOptions fromJson(Map<String, Object?> json, BuildInfo buildInfo) =>
|
||||||
@ -1294,6 +1299,7 @@ class DebuggingOptions {
|
|||||||
serveObservatory: (json['serveObservatory'] as bool?) ?? false,
|
serveObservatory: (json['serveObservatory'] as bool?) ?? false,
|
||||||
enableDartProfiling: (json['enableDartProfiling'] as bool?) ?? true,
|
enableDartProfiling: (json['enableDartProfiling'] as bool?) ?? true,
|
||||||
enableEmbedderApi: (json['enableEmbedderApi'] as bool?) ?? false,
|
enableEmbedderApi: (json['enableEmbedderApi'] as bool?) ?? false,
|
||||||
|
usingCISystem: (json['usingCISystem'] as bool?) ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,7 +482,10 @@ class IOSDevice extends Device {
|
|||||||
int installationResult = 1;
|
int installationResult = 1;
|
||||||
if (debuggingOptions.debuggingEnabled) {
|
if (debuggingOptions.debuggingEnabled) {
|
||||||
_logger.printTrace('Debugging is enabled, connecting to vmService');
|
_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
|
// If the device supports syslog reading, prefer launching the app without
|
||||||
// attaching the debugger to avoid the overhead of the unnecessary extra running process.
|
// attaching the debugger to avoid the overhead of the unnecessary extra running process.
|
||||||
@ -629,12 +632,14 @@ class IOSDevice extends Device {
|
|||||||
DeviceLogReader getLogReader({
|
DeviceLogReader getLogReader({
|
||||||
covariant IOSApp? app,
|
covariant IOSApp? app,
|
||||||
bool includePastLogs = false,
|
bool includePastLogs = false,
|
||||||
|
bool usingCISystem = false,
|
||||||
}) {
|
}) {
|
||||||
assert(!includePastLogs, 'Past log reading not supported on iOS devices.');
|
assert(!includePastLogs, 'Past log reading not supported on iOS devices.');
|
||||||
return _logReaders.putIfAbsent(app, () => IOSDeviceLogReader.create(
|
return _logReaders.putIfAbsent(app, () => IOSDeviceLogReader.create(
|
||||||
device: this,
|
device: this,
|
||||||
app: app,
|
app: app,
|
||||||
iMobileDevice: _iMobileDevice,
|
iMobileDevice: _iMobileDevice,
|
||||||
|
usingCISystem: usingCISystem,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -749,17 +754,20 @@ class IOSDeviceLogReader extends DeviceLogReader {
|
|||||||
this._deviceId,
|
this._deviceId,
|
||||||
this.name,
|
this.name,
|
||||||
String appName,
|
String appName,
|
||||||
|
bool usingCISystem,
|
||||||
) : // Match for lines for the runner in syslog.
|
) : // Match for lines for the runner in syslog.
|
||||||
//
|
//
|
||||||
// iOS 9 format: Runner[297] <Notice>:
|
// iOS 9 format: Runner[297] <Notice>:
|
||||||
// iOS 10 format: Runner(Flutter)[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].
|
/// Create a new [IOSDeviceLogReader].
|
||||||
factory IOSDeviceLogReader.create({
|
factory IOSDeviceLogReader.create({
|
||||||
required IOSDevice device,
|
required IOSDevice device,
|
||||||
IOSApp? app,
|
IOSApp? app,
|
||||||
required IMobileDevice iMobileDevice,
|
required IMobileDevice iMobileDevice,
|
||||||
|
bool usingCISystem = false,
|
||||||
}) {
|
}) {
|
||||||
final String appName = app?.name?.replaceAll('.app', '') ?? '';
|
final String appName = app?.name?.replaceAll('.app', '') ?? '';
|
||||||
return IOSDeviceLogReader._(
|
return IOSDeviceLogReader._(
|
||||||
@ -768,6 +776,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
|
|||||||
device.id,
|
device.id,
|
||||||
device.name,
|
device.name,
|
||||||
appName,
|
appName,
|
||||||
|
usingCISystem,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -775,9 +784,17 @@ class IOSDeviceLogReader extends DeviceLogReader {
|
|||||||
factory IOSDeviceLogReader.test({
|
factory IOSDeviceLogReader.test({
|
||||||
required IMobileDevice iMobileDevice,
|
required IMobileDevice iMobileDevice,
|
||||||
bool useSyslog = true,
|
bool useSyslog = true,
|
||||||
|
bool usingCISystem = false,
|
||||||
|
int? majorSdkVersion,
|
||||||
}) {
|
}) {
|
||||||
|
final int sdkVersion;
|
||||||
|
if (majorSdkVersion != null) {
|
||||||
|
sdkVersion = majorSdkVersion;
|
||||||
|
} else {
|
||||||
|
sdkVersion = useSyslog ? 12 : 13;
|
||||||
|
}
|
||||||
return IOSDeviceLogReader._(
|
return IOSDeviceLogReader._(
|
||||||
iMobileDevice, useSyslog ? 12 : 13, '1234', 'test', 'Runner');
|
iMobileDevice, sdkVersion, '1234', 'test', 'Runner', usingCISystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -785,6 +802,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
|
|||||||
final int _majorSdkVersion;
|
final int _majorSdkVersion;
|
||||||
final String _deviceId;
|
final String _deviceId;
|
||||||
final IMobileDevice _iMobileDevice;
|
final IMobileDevice _iMobileDevice;
|
||||||
|
final bool _usingCISystem;
|
||||||
|
|
||||||
// Matches a syslog line from the runner.
|
// Matches a syslog line from the runner.
|
||||||
RegExp _runnerLineRegex;
|
RegExp _runnerLineRegex;
|
||||||
@ -810,12 +828,42 @@ class IOSDeviceLogReader extends DeviceLogReader {
|
|||||||
// Sometimes (race condition?) we try to send a log after the controller has
|
// 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
|
// been closed. See https://github.com/flutter/flutter/issues/99021 for more
|
||||||
// context.
|
// context.
|
||||||
void _addToLinesController(String message) {
|
void _addToLinesController(String message, IOSDeviceLogSource source) {
|
||||||
if (!linesController.isClosed) {
|
if (!linesController.isClosed) {
|
||||||
|
if (_excludeLog(message, source)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
linesController.add(message);
|
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>>[];
|
final List<StreamSubscription<void>> _loggingSubscriptions = <StreamSubscription<void>>[];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -835,6 +883,10 @@ class IOSDeviceLogReader extends DeviceLogReader {
|
|||||||
|
|
||||||
static const int minimumUniversalLoggingSdkVersion = 13;
|
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 {
|
Future<void> _listenToUnifiedLoggingEvents(FlutterVmService connectedVmService) async {
|
||||||
if (_majorSdkVersion < minimumUniversalLoggingSdkVersion) {
|
if (_majorSdkVersion < minimumUniversalLoggingSdkVersion) {
|
||||||
return;
|
return;
|
||||||
@ -859,7 +911,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
|
|||||||
}
|
}
|
||||||
final String message = processVmServiceMessage(event);
|
final String message = processVmServiceMessage(event);
|
||||||
if (message.isNotEmpty) {
|
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.
|
/// Log reader will listen to [debugger.logLines] and will detach debugger on dispose.
|
||||||
IOSDeployDebugger? get debuggerStream => _iosDeployDebugger;
|
IOSDeployDebugger? get debuggerStream => _iosDeployDebugger;
|
||||||
|
|
||||||
|
/// Send messages from ios-deploy debugger stream to device log reader stream.
|
||||||
set debuggerStream(IOSDeployDebugger? debugger) {
|
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) {
|
if (_majorSdkVersion < minimumUniversalLoggingSdkVersion) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -882,7 +936,10 @@ class IOSDeviceLogReader extends DeviceLogReader {
|
|||||||
}
|
}
|
||||||
// Add the debugger logs to the controller created on initialization.
|
// Add the debugger logs to the controller created on initialization.
|
||||||
_loggingSubscriptions.add(debugger.logLines.listen(
|
_loggingSubscriptions.add(debugger.logLines.listen(
|
||||||
(String line) => _addToLinesController(_debuggerLineHandler(line)),
|
(String line) => _addToLinesController(
|
||||||
|
_debuggerLineHandler(line),
|
||||||
|
IOSDeviceLogSource.iosDeploy,
|
||||||
|
),
|
||||||
onError: linesController.addError,
|
onError: linesController.addError,
|
||||||
onDone: linesController.close,
|
onDone: linesController.close,
|
||||||
cancelOnError: true,
|
cancelOnError: true,
|
||||||
@ -893,18 +950,38 @@ class IOSDeviceLogReader extends DeviceLogReader {
|
|||||||
// Strip off the logging metadata (leave the category), or just echo the line.
|
// Strip off the logging metadata (leave the category), or just echo the line.
|
||||||
String _debuggerLineHandler(String line) => _debuggerLoggingRegex.firstMatch(line)?.group(1) ?? 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() {
|
void _listenToSysLog() {
|
||||||
// syslog is not written on iOS 13+.
|
// Syslog stopped working on iOS 13 (https://github.com/flutter/flutter/issues/41133).
|
||||||
if (_majorSdkVersion >= minimumUniversalLoggingSdkVersion) {
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
_iMobileDevice.startLogger(_deviceId).then<void>((Process process) {
|
_iMobileDevice.startLogger(_deviceId).then<void>((Process process) {
|
||||||
process.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(_newSyslogLineHandler());
|
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.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(_newSyslogLineHandler());
|
||||||
process.exitCode.whenComplete(() {
|
process.exitCode.whenComplete(() {
|
||||||
if (linesController.hasListener) {
|
if (!linesController.hasListener) {
|
||||||
linesController.close();
|
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);
|
assert(idevicesyslogProcess == null);
|
||||||
idevicesyslogProcess = process;
|
idevicesyslogProcess = process;
|
||||||
@ -926,7 +1003,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
|
|||||||
return (String line) {
|
return (String line) {
|
||||||
if (printing) {
|
if (printing) {
|
||||||
if (!_anyLineRegex.hasMatch(line)) {
|
if (!_anyLineRegex.hasMatch(line)) {
|
||||||
_addToLinesController(decodeSyslog(line));
|
_addToLinesController(decodeSyslog(line), IOSDeviceLogSource.idevicesyslog);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -938,8 +1015,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
|
|||||||
if (match != null) {
|
if (match != null) {
|
||||||
final String logLine = line.substring(match.end);
|
final String logLine = line.substring(match.end);
|
||||||
// Only display the log line after the initial device and executable information.
|
// Only display the log line after the initial device and executable information.
|
||||||
_addToLinesController(decodeSyslog(logLine));
|
_addToLinesController(decodeSyslog(logLine), IOSDeviceLogSource.idevicesyslog);
|
||||||
|
|
||||||
printing = true;
|
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.
|
/// A [DevicePortForwarder] specialized for iOS usage with iproxy.
|
||||||
class IOSDevicePortForwarder extends DevicePortForwarder {
|
class IOSDevicePortForwarder extends DevicePortForwarder {
|
||||||
|
|
||||||
|
@ -36,6 +36,8 @@ import 'devfs.dart';
|
|||||||
import 'device.dart';
|
import 'device.dart';
|
||||||
import 'features.dart';
|
import 'features.dart';
|
||||||
import 'globals.dart' as globals;
|
import 'globals.dart' as globals;
|
||||||
|
import 'ios/application_package.dart';
|
||||||
|
import 'ios/devices.dart';
|
||||||
import 'project.dart';
|
import 'project.dart';
|
||||||
import 'resident_devtools_handler.dart';
|
import 'resident_devtools_handler.dart';
|
||||||
import 'run_cold.dart';
|
import 'run_cold.dart';
|
||||||
@ -391,11 +393,19 @@ class FlutterDevice {
|
|||||||
return devFS!.create();
|
return devFS!.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startEchoingDeviceLog() async {
|
Future<void> startEchoingDeviceLog(DebuggingOptions debuggingOptions) async {
|
||||||
if (_loggingSubscription != null) {
|
if (_loggingSubscription != null) {
|
||||||
return;
|
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) {
|
_loggingSubscription = logStream.listen((String line) {
|
||||||
if (!line.contains(globals.kVMServiceMessageRegExp)) {
|
if (!line.contains(globals.kVMServiceMessageRegExp)) {
|
||||||
globals.printStatus(line, wrap: false);
|
globals.printStatus(line, wrap: false);
|
||||||
@ -451,7 +461,7 @@ class FlutterDevice {
|
|||||||
'multidex': hotRunner.multidexEnabled,
|
'multidex': hotRunner.multidexEnabled,
|
||||||
};
|
};
|
||||||
|
|
||||||
await startEchoingDeviceLog();
|
await startEchoingDeviceLog(hotRunner.debuggingOptions);
|
||||||
|
|
||||||
// Start the application.
|
// Start the application.
|
||||||
final Future<LaunchResult> futureResult = device!.startApp(
|
final Future<LaunchResult> futureResult = device!.startApp(
|
||||||
@ -519,7 +529,7 @@ class FlutterDevice {
|
|||||||
platformArgs['trace-startup'] = coldRunner.traceStartup;
|
platformArgs['trace-startup'] = coldRunner.traceStartup;
|
||||||
platformArgs['multidex'] = coldRunner.multidexEnabled;
|
platformArgs['multidex'] = coldRunner.multidexEnabled;
|
||||||
|
|
||||||
await startEchoingDeviceLog();
|
await startEchoingDeviceLog(coldRunner.debuggingOptions);
|
||||||
|
|
||||||
final LaunchResult result = await device!.startApp(
|
final LaunchResult result = await device!.startApp(
|
||||||
applicationPackage,
|
applicationPackage,
|
||||||
|
@ -304,7 +304,10 @@ abstract class FlutterCommand extends Command<void> {
|
|||||||
/// Path to the Dart's package config file.
|
/// Path to the Dart's package config file.
|
||||||
///
|
///
|
||||||
/// This can be overridden by some of its subclasses.
|
/// 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.
|
/// 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]
|
/// If no flag named [name] was added to the [ArgParser], an [ArgumentError]
|
||||||
/// will be thrown.
|
/// 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`.
|
/// Gets the parsed command-line option named [name] as a `String`.
|
||||||
///
|
///
|
||||||
/// If no option named [name] was added to the [ArgParser], an [ArgumentError]
|
/// If no option named [name] was added to the [ArgParser], an [ArgumentError]
|
||||||
/// will be thrown.
|
/// 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>`.
|
/// Gets the parsed command-line option named [name] as `List<String>`.
|
||||||
List<String> stringsArg(String name) {
|
List<String> stringsArg(String name, {bool global = false}) {
|
||||||
return argResults![name]! as List<String>? ?? <String>[];
|
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 '../tester/flutter_tester.dart';
|
||||||
import '../web/web_device.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> {
|
class FlutterCommandRunner extends CommandRunner<void> {
|
||||||
FlutterCommandRunner({ bool verboseHelp = false }) : super(
|
FlutterCommandRunner({ bool verboseHelp = false }) : super(
|
||||||
'flutter',
|
'flutter',
|
||||||
@ -33,80 +57,80 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
|||||||
' flutter run [options]\n'
|
' flutter run [options]\n'
|
||||||
' Run your Flutter application on an attached device or in an emulator.',
|
' Run your Flutter application on an attached device or in an emulator.',
|
||||||
) {
|
) {
|
||||||
argParser.addFlag('verbose',
|
argParser.addFlag(FlutterGlobalOptions.kVerboseFlag,
|
||||||
abbr: 'v',
|
abbr: 'v',
|
||||||
negatable: false,
|
negatable: false,
|
||||||
help: 'Noisy logging, including all shell commands executed.\n'
|
help: 'Noisy logging, including all shell commands executed.\n'
|
||||||
'If used with "--help", shows hidden options. '
|
'If used with "--help", shows hidden options. '
|
||||||
'If used with "flutter doctor", shows additional diagnostic information. '
|
'If used with "flutter doctor", shows additional diagnostic information. '
|
||||||
'(Use "-vv" to force verbose logging in those cases.)');
|
'(Use "-vv" to force verbose logging in those cases.)');
|
||||||
argParser.addFlag('prefixed-errors',
|
argParser.addFlag(FlutterGlobalOptions.kPrefixedErrorsFlag,
|
||||||
negatable: false,
|
negatable: false,
|
||||||
help: 'Causes lines sent to stderr to be prefixed with "ERROR:".',
|
help: 'Causes lines sent to stderr to be prefixed with "ERROR:".',
|
||||||
hide: !verboseHelp);
|
hide: !verboseHelp);
|
||||||
argParser.addFlag('quiet',
|
argParser.addFlag(FlutterGlobalOptions.kQuietFlag,
|
||||||
negatable: false,
|
negatable: false,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Reduce the amount of output from some commands.');
|
help: 'Reduce the amount of output from some commands.');
|
||||||
argParser.addFlag('wrap',
|
argParser.addFlag(FlutterGlobalOptions.kWrapFlag,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Toggles output word wrapping, regardless of whether or not the output is a terminal.',
|
help: 'Toggles output word wrapping, regardless of whether or not the output is a terminal.',
|
||||||
defaultsTo: true);
|
defaultsTo: true);
|
||||||
argParser.addOption('wrap-column',
|
argParser.addOption(FlutterGlobalOptions.kWrapColumnOption,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Sets the output wrap column. If not set, uses the width of the terminal. No '
|
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 '
|
'wrapping occurs if not writing to a terminal. Use "--no-wrap" to turn off wrapping '
|
||||||
'when connected to a terminal.');
|
'when connected to a terminal.');
|
||||||
argParser.addOption('device-id',
|
argParser.addOption(FlutterGlobalOptions.kDeviceIdOption,
|
||||||
abbr: 'd',
|
abbr: 'd',
|
||||||
help: 'Target device id or name (prefixes allowed).');
|
help: 'Target device id or name (prefixes allowed).');
|
||||||
argParser.addFlag('version',
|
argParser.addFlag(FlutterGlobalOptions.kVersionFlag,
|
||||||
negatable: false,
|
negatable: false,
|
||||||
help: 'Reports the version of this tool.');
|
help: 'Reports the version of this tool.');
|
||||||
argParser.addFlag('machine',
|
argParser.addFlag(FlutterGlobalOptions.kMachineFlag,
|
||||||
negatable: false,
|
negatable: false,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'When used with the "--version" flag, outputs the information using JSON.');
|
help: 'When used with the "--version" flag, outputs the information using JSON.');
|
||||||
argParser.addFlag('color',
|
argParser.addFlag(FlutterGlobalOptions.kColorFlag,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Whether to use terminal colors (requires support for ANSI escape sequences).',
|
help: 'Whether to use terminal colors (requires support for ANSI escape sequences).',
|
||||||
defaultsTo: true);
|
defaultsTo: true);
|
||||||
argParser.addFlag('version-check',
|
argParser.addFlag(FlutterGlobalOptions.kVersionCheckFlag,
|
||||||
defaultsTo: true,
|
defaultsTo: true,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Allow Flutter to check for updates when this command runs.');
|
help: 'Allow Flutter to check for updates when this command runs.');
|
||||||
argParser.addFlag('suppress-analytics',
|
argParser.addFlag(FlutterGlobalOptions.kSuppressAnalyticsFlag,
|
||||||
negatable: false,
|
negatable: false,
|
||||||
help: 'Suppress analytics reporting for the current CLI invocation.');
|
help: 'Suppress analytics reporting for the current CLI invocation.');
|
||||||
argParser.addFlag('disable-telemetry',
|
argParser.addFlag(FlutterGlobalOptions.kDisableTelemetryFlag,
|
||||||
negatable: false,
|
negatable: false,
|
||||||
help: 'Disable telemetry reporting each time a flutter or dart '
|
help: 'Disable telemetry reporting each time a flutter or dart '
|
||||||
'command runs, until it is re-enabled.');
|
'command runs, until it is re-enabled.');
|
||||||
argParser.addFlag('enable-telemetry',
|
argParser.addFlag(FlutterGlobalOptions.kEnableTelemetryFlag,
|
||||||
negatable: false,
|
negatable: false,
|
||||||
help: 'Enable telemetry reporting each time a flutter or dart '
|
help: 'Enable telemetry reporting each time a flutter or dart '
|
||||||
'command runs.');
|
'command runs.');
|
||||||
argParser.addOption('packages',
|
argParser.addOption(FlutterGlobalOptions.kPackagesOption,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Path to your "package_config.json" file.');
|
help: 'Path to your "package_config.json" file.');
|
||||||
if (verboseHelp) {
|
if (verboseHelp) {
|
||||||
argParser.addSeparator('Local build selection options (not normally required):');
|
argParser.addSeparator('Local build selection options (not normally required):');
|
||||||
}
|
}
|
||||||
|
|
||||||
argParser.addOption('local-engine-src-path',
|
argParser.addOption(FlutterGlobalOptions.kLocalEngineSrcPathOption,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Path to your engine src directory, if you are building Flutter locally.\n'
|
help: 'Path to your engine src directory, if you are building Flutter locally.\n'
|
||||||
'Defaults to \$$kFlutterEngineEnvironmentVariableName if set, otherwise defaults to '
|
'Defaults to \$$kFlutterEngineEnvironmentVariableName if set, otherwise defaults to '
|
||||||
'the path given in your pubspec.yaml dependency_overrides for $kFlutterEnginePackageName, '
|
'the path given in your pubspec.yaml dependency_overrides for $kFlutterEnginePackageName, '
|
||||||
'if any.');
|
'if any.');
|
||||||
|
|
||||||
argParser.addOption('local-engine',
|
argParser.addOption(FlutterGlobalOptions.kLocalEngineOption,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Name of a build output within the engine out directory, if you are building Flutter locally.\n'
|
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'
|
'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).');
|
'This path is relative to "--local-engine-src-path" (see above).');
|
||||||
|
|
||||||
argParser.addOption('local-web-sdk',
|
argParser.addOption(FlutterGlobalOptions.kLocalWebSDKOption,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Name of a build output within the engine out directory, if you are building Flutter locally.\n'
|
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'
|
'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) {
|
if (verboseHelp) {
|
||||||
argParser.addSeparator('Options for testing the "flutter" tool itself:');
|
argParser.addSeparator('Options for testing the "flutter" tool itself:');
|
||||||
}
|
}
|
||||||
argParser.addFlag('show-test-device',
|
argParser.addFlag(FlutterGlobalOptions.kShowTestDeviceFlag,
|
||||||
negatable: false,
|
negatable: false,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'List the special "flutter-tester" device in device listings. '
|
help: 'List the special "flutter-tester" device in device listings. '
|
||||||
'This headless device is used to test Flutter tooling.');
|
'This headless device is used to test Flutter tooling.');
|
||||||
argParser.addFlag('show-web-server-device',
|
argParser.addFlag(FlutterGlobalOptions.kShowWebServerDeviceFlag,
|
||||||
negatable: false,
|
negatable: false,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'List the special "web-server" device in device listings.',
|
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
|
@override
|
||||||
@ -198,8 +228,8 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
|||||||
|
|
||||||
// If the flag for enabling or disabling telemetry is passed in,
|
// If the flag for enabling or disabling telemetry is passed in,
|
||||||
// we will return out
|
// we will return out
|
||||||
if (topLevelResults.wasParsed('disable-telemetry') ||
|
if (topLevelResults.wasParsed(FlutterGlobalOptions.kDisableTelemetryFlag) ||
|
||||||
topLevelResults.wasParsed('enable-telemetry')) {
|
topLevelResults.wasParsed(FlutterGlobalOptions.kEnableTelemetryFlag)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,43 +237,43 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
|||||||
// wrapping will occur at this width explicitly, and won't adapt if the
|
// wrapping will occur at this width explicitly, and won't adapt if the
|
||||||
// terminal size changes during a run.
|
// terminal size changes during a run.
|
||||||
int? wrapColumn;
|
int? wrapColumn;
|
||||||
if (topLevelResults.wasParsed('wrap-column')) {
|
if (topLevelResults.wasParsed(FlutterGlobalOptions.kWrapColumnOption)) {
|
||||||
try {
|
try {
|
||||||
wrapColumn = int.parse(topLevelResults['wrap-column'] as String);
|
wrapColumn = int.parse(topLevelResults[FlutterGlobalOptions.kWrapColumnOption] as String);
|
||||||
if (wrapColumn < 0) {
|
if (wrapColumn < 0) {
|
||||||
throwToolExit(userMessages.runnerWrapColumnInvalid(topLevelResults['wrap-column']));
|
throwToolExit(userMessages.runnerWrapColumnInvalid(topLevelResults[FlutterGlobalOptions.kWrapColumnOption]));
|
||||||
}
|
}
|
||||||
} on FormatException {
|
} 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
|
// If we're not writing to a terminal with a defined width, then don't wrap
|
||||||
// anything, unless the user explicitly said to.
|
// anything, unless the user explicitly said to.
|
||||||
final bool useWrapping = topLevelResults.wasParsed('wrap')
|
final bool useWrapping = topLevelResults.wasParsed(FlutterGlobalOptions.kWrapFlag)
|
||||||
? topLevelResults['wrap'] as bool
|
? topLevelResults[FlutterGlobalOptions.kWrapFlag] as bool
|
||||||
: globals.stdio.terminalColumns != null && topLevelResults['wrap'] as bool;
|
: globals.stdio.terminalColumns != null && topLevelResults[FlutterGlobalOptions.kWrapFlag] as bool;
|
||||||
contextOverrides[OutputPreferences] = OutputPreferences(
|
contextOverrides[OutputPreferences] = OutputPreferences(
|
||||||
wrapText: useWrapping,
|
wrapText: useWrapping,
|
||||||
showColor: topLevelResults['color'] as bool?,
|
showColor: topLevelResults[FlutterGlobalOptions.kColorFlag] as bool?,
|
||||||
wrapColumn: wrapColumn,
|
wrapColumn: wrapColumn,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (((topLevelResults['show-test-device'] as bool?) ?? false)
|
if (((topLevelResults[FlutterGlobalOptions.kShowTestDeviceFlag] as bool?) ?? false)
|
||||||
|| topLevelResults['device-id'] == FlutterTesterDevices.kTesterDeviceId) {
|
|| topLevelResults[FlutterGlobalOptions.kDeviceIdOption] == FlutterTesterDevices.kTesterDeviceId) {
|
||||||
FlutterTesterDevices.showFlutterTesterDevice = true;
|
FlutterTesterDevices.showFlutterTesterDevice = true;
|
||||||
}
|
}
|
||||||
if (((topLevelResults['show-web-server-device'] as bool?) ?? false)
|
if (((topLevelResults[FlutterGlobalOptions.kShowWebServerDeviceFlag] as bool?) ?? false)
|
||||||
|| topLevelResults['device-id'] == WebServerDevice.kWebServerDeviceId) {
|
|| topLevelResults[FlutterGlobalOptions.kDeviceIdOption] == WebServerDevice.kWebServerDeviceId) {
|
||||||
WebServerDevice.showWebServerDevice = true;
|
WebServerDevice.showWebServerDevice = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the tooling configuration.
|
// Set up the tooling configuration.
|
||||||
final EngineBuildPaths? engineBuildPaths = await globals.localEngineLocator?.findEnginePath(
|
final EngineBuildPaths? engineBuildPaths = await globals.localEngineLocator?.findEnginePath(
|
||||||
engineSourcePath: topLevelResults['local-engine-src-path'] as String?,
|
engineSourcePath: topLevelResults[FlutterGlobalOptions.kLocalEngineSrcPathOption] as String?,
|
||||||
localEngine: topLevelResults['local-engine'] as String?,
|
localEngine: topLevelResults[FlutterGlobalOptions.kLocalEngineOption] as String?,
|
||||||
localWebSdk: topLevelResults['local-web-sdk'] as String?,
|
localWebSdk: topLevelResults[FlutterGlobalOptions.kLocalWebSDKOption] as String?,
|
||||||
packagePath: topLevelResults['packages'] as String?,
|
packagePath: topLevelResults[FlutterGlobalOptions.kPackagesOption] as String?,
|
||||||
);
|
);
|
||||||
if (engineBuildPaths != null) {
|
if (engineBuildPaths != null) {
|
||||||
contextOverrides.addAll(<Type, Object?>{
|
contextOverrides.addAll(<Type, Object?>{
|
||||||
@ -256,24 +286,24 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
|||||||
return MapEntry<Type, Generator>(type, () => value);
|
return MapEntry<Type, Generator>(type, () => value);
|
||||||
}),
|
}),
|
||||||
body: () async {
|
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') {
|
if (globals.platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true') {
|
||||||
await globals.cache.lock();
|
await globals.cache.lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((topLevelResults['suppress-analytics'] as bool?) ?? false) {
|
if ((topLevelResults[FlutterGlobalOptions.kSuppressAnalyticsFlag] as bool?) ?? false) {
|
||||||
globals.flutterUsage.suppressAnalytics = true;
|
globals.flutterUsage.suppressAnalytics = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
globals.flutterVersion.ensureVersionFile();
|
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 ci = await globals.botDetector.isRunningOnBot;
|
||||||
final bool redirectedCompletion = !globals.stdio.hasTerminal &&
|
final bool redirectedCompletion = !globals.stdio.hasTerminal &&
|
||||||
(topLevelResults.command?.name ?? '').endsWith('-completion');
|
(topLevelResults.command?.name ?? '').endsWith('-completion');
|
||||||
final bool isMachine = machineFlag || ci || redirectedCompletion;
|
final bool isMachine = machineFlag || ci || redirectedCompletion;
|
||||||
final bool versionCheckFlag = topLevelResults['version-check'] as bool? ?? false;
|
final bool versionCheckFlag = topLevelResults[FlutterGlobalOptions.kVersionCheckFlag] as bool? ?? false;
|
||||||
final bool explicitVersionCheckPassed = topLevelResults.wasParsed('version-check') && versionCheckFlag;
|
final bool explicitVersionCheckPassed = topLevelResults.wasParsed(FlutterGlobalOptions.kVersionCheckFlag) && versionCheckFlag;
|
||||||
|
|
||||||
if (topLevelResults.command?.name != 'upgrade' &&
|
if (topLevelResults.command?.name != 'upgrade' &&
|
||||||
(explicitVersionCheckPassed || (versionCheckFlag && !isMachine))) {
|
(explicitVersionCheckPassed || (versionCheckFlag && !isMachine))) {
|
||||||
@ -281,13 +311,13 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// See if the user specified a specific device.
|
// 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) {
|
if (specifiedDeviceId != null) {
|
||||||
globals.deviceManager?.specifiedDeviceId = specifiedDeviceId;
|
globals.deviceManager?.specifiedDeviceId = specifiedDeviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((topLevelResults['version'] as bool?) ?? false) {
|
if ((topLevelResults[FlutterGlobalOptions.kVersionFlag] as bool?) ?? false) {
|
||||||
globals.flutterUsage.sendCommand('version');
|
globals.flutterUsage.sendCommand(FlutterGlobalOptions.kVersionFlag);
|
||||||
globals.flutterVersion.fetchTagsAndUpdate();
|
globals.flutterVersion.fetchTagsAndUpdate();
|
||||||
String status;
|
String status;
|
||||||
if (machineFlag) {
|
if (machineFlag) {
|
||||||
|
@ -1381,6 +1381,7 @@ class FakeIOSDevice extends Fake implements IOSDevice {
|
|||||||
DeviceLogReader getLogReader({
|
DeviceLogReader getLogReader({
|
||||||
IOSApp? app,
|
IOSApp? app,
|
||||||
bool includePastLogs = false,
|
bool includePastLogs = false,
|
||||||
|
bool usingCISystem = false,
|
||||||
}) {
|
}) {
|
||||||
if (onGetLogReader == null) {
|
if (onGetLogReader == null) {
|
||||||
throw UnimplementedError(
|
throw UnimplementedError(
|
||||||
|
@ -425,6 +425,7 @@ void main() {
|
|||||||
'--enable-software-rendering',
|
'--enable-software-rendering',
|
||||||
'--skia-deterministic-rendering',
|
'--skia-deterministic-rendering',
|
||||||
'--enable-embedder-api',
|
'--enable-embedder-api',
|
||||||
|
'--ci',
|
||||||
]), throwsToolExit());
|
]), throwsToolExit());
|
||||||
|
|
||||||
final DebuggingOptions options = await command.createDebuggingOptions(false);
|
final DebuggingOptions options = await command.createDebuggingOptions(false);
|
||||||
@ -440,6 +441,7 @@ void main() {
|
|||||||
expect(options.traceSystrace, true);
|
expect(options.traceSystrace, true);
|
||||||
expect(options.enableSoftwareRendering, true);
|
expect(options.enableSoftwareRendering, true);
|
||||||
expect(options.skiaDeterministicRendering, true);
|
expect(options.skiaDeterministicRendering, true);
|
||||||
|
expect(options.usingCISystem, true);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
|
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
|
||||||
FileSystem: () => MemoryFileSystem.test(),
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
@ -1096,6 +1096,7 @@ void main() {
|
|||||||
'--enable-software-rendering',
|
'--enable-software-rendering',
|
||||||
'--skia-deterministic-rendering',
|
'--skia-deterministic-rendering',
|
||||||
'--enable-embedder-api',
|
'--enable-embedder-api',
|
||||||
|
'--ci',
|
||||||
]), throwsToolExit());
|
]), throwsToolExit());
|
||||||
|
|
||||||
final DebuggingOptions options = await command.createDebuggingOptions(false);
|
final DebuggingOptions options = await command.createDebuggingOptions(false);
|
||||||
@ -1114,6 +1115,7 @@ void main() {
|
|||||||
expect(options.impellerForceGL, true);
|
expect(options.impellerForceGL, true);
|
||||||
expect(options.enableSoftwareRendering, true);
|
expect(options.enableSoftwareRendering, true);
|
||||||
expect(options.skiaDeterministicRendering, true);
|
expect(options.skiaDeterministicRendering, true);
|
||||||
|
expect(options.usingCISystem, true);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
|
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
|
||||||
FileSystem: () => MemoryFileSystem.test(),
|
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 {
|
testUsingContext('bool? safe argResults', () async {
|
||||||
final DummyFlutterCommand command = DummyFlutterCommand(
|
final DummyFlutterCommand command = DummyFlutterCommand(
|
||||||
commandFunction: () async {
|
commandFunction: () async {
|
||||||
return const FlutterCommandResult(ExitStatus.success);
|
return const FlutterCommandResult(ExitStatus.success);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true);
|
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true);
|
||||||
command.argParser.addFlag('key');
|
command.argParser.addFlag('key');
|
||||||
@ -58,9 +100,9 @@ void main() {
|
|||||||
|
|
||||||
testUsingContext('String? safe argResults', () async {
|
testUsingContext('String? safe argResults', () async {
|
||||||
final DummyFlutterCommand command = DummyFlutterCommand(
|
final DummyFlutterCommand command = DummyFlutterCommand(
|
||||||
commandFunction: () async {
|
commandFunction: () async {
|
||||||
return const FlutterCommandResult(ExitStatus.success);
|
return const FlutterCommandResult(ExitStatus.success);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true);
|
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true);
|
||||||
command.argParser.addOption('key');
|
command.argParser.addOption('key');
|
||||||
@ -80,9 +122,9 @@ void main() {
|
|||||||
|
|
||||||
testUsingContext('List<String> safe argResults', () async {
|
testUsingContext('List<String> safe argResults', () async {
|
||||||
final DummyFlutterCommand command = DummyFlutterCommand(
|
final DummyFlutterCommand command = DummyFlutterCommand(
|
||||||
commandFunction: () async {
|
commandFunction: () async {
|
||||||
return const FlutterCommandResult(ExitStatus.success);
|
return const FlutterCommandResult(ExitStatus.success);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true);
|
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true);
|
||||||
command.argParser.addMultiOption(
|
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 {
|
class FakeIOSDeployDebugger extends Fake implements IOSDeployDebugger {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user