Report commands that resulted in success or failure (#34288)
This is added as a dimension cd26
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart
index fcb6924..e06d1c9 100644
--- a/packages/flutter_tools/lib/src/commands/attach.dart
+++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -26,7 +26,6 @@
import '../run_cold.dart';
import '../run_hot.dart';
import '../runner/flutter_command.dart';
-import '../usage.dart';
/// A Flutter-command that attaches to applications that have been launched
/// without `flutter run`.
@@ -316,10 +315,7 @@
result = await runner.attach();
assert(result != null);
}
- if (result == 0) {
- flutterUsage.sendEvent('attach', 'success');
- } else {
- flutterUsage.sendEvent('attach', 'failure');
+ if (result != 0) {
throwToolExit(null, exitCode: result);
}
} finally {
diff --git a/packages/flutter_tools/lib/src/commands/logs.dart b/packages/flutter_tools/lib/src/commands/logs.dart
index c97c62c..e3f8d4b 100644
--- a/packages/flutter_tools/lib/src/commands/logs.dart
+++ b/packages/flutter_tools/lib/src/commands/logs.dart
@@ -32,11 +32,11 @@
Device device;
@override
- Future<FlutterCommandResult> verifyThenRunCommand(String commandPath) async {
+ Future<FlutterCommandResult> verifyThenRunCommand() async {
device = await findTargetDevice();
if (device == null)
throwToolExit(null);
- return super.verifyThenRunCommand(commandPath);
+ return super.verifyThenRunCommand();
}
@override
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 3fb4bf8..a3603ad 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -219,14 +219,21 @@
@override
Future<Map<String, String>> get usageValues async {
- final bool isEmulator = await devices[0].isLocalEmulator;
String deviceType, deviceOsVersion;
- if (devices.length == 1) {
+ bool isEmulator;
+
+ if (devices == null || devices.isEmpty) {
+ deviceType = 'none';
+ deviceOsVersion = 'none';
+ isEmulator = false;
+ } else if (devices.length == 1) {
deviceType = getNameForTargetPlatform(await devices[0].targetPlatform);
deviceOsVersion = await devices[0].sdkNameAndVersion;
+ isEmulator = await devices[0].isLocalEmulator;
} else {
deviceType = 'multiple';
deviceOsVersion = 'multiple';
+ isEmulator = false;
}
final String modeName = getBuildInfo().modeName;
final AndroidProject androidProject = FlutterProject.current().android;
diff --git a/packages/flutter_tools/lib/src/commands/screenshot.dart b/packages/flutter_tools/lib/src/commands/screenshot.dart
index 38552dd..63e454b 100644
--- a/packages/flutter_tools/lib/src/commands/screenshot.dart
+++ b/packages/flutter_tools/lib/src/commands/screenshot.dart
@@ -64,7 +64,7 @@
Device device;
@override
- Future<FlutterCommandResult> verifyThenRunCommand(String commandPath) async {
+ Future<FlutterCommandResult> verifyThenRunCommand() async {
device = await findTargetDevice();
if (device == null)
throwToolExit('Must have a connected device');
@@ -72,7 +72,7 @@
throwToolExit('Screenshot not supported for ${device.name}.');
if (argResults[_kType] != _kDeviceType && argResults[_kObservatoryUri] == null)
throwToolExit('Observatory URI must be specified for screenshot type ${argResults[_kType]}');
- return super.verifyThenRunCommand(commandPath);
+ return super.verifyThenRunCommand();
}
@override
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 634d4a8..af65951 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -408,10 +408,9 @@
body: () async {
if (flutterUsage.isFirstRun)
flutterUsage.printWelcome();
- final String commandPath = await usagePath;
FlutterCommandResult commandResult;
try {
- commandResult = await verifyThenRunCommand(commandPath);
+ commandResult = await verifyThenRunCommand();
} on ToolExit {
commandResult = const FlutterCommandResult(ExitStatus.fail);
rethrow;
@@ -419,32 +418,68 @@
final DateTime endTime = systemClock.now();
printTrace(userMessages.flutterElapsedTime(name, getElapsedAsMilliseconds(endTime.difference(startTime))));
printTrace('"flutter $name" took ${getElapsedAsMilliseconds(endTime.difference(startTime))}.');
- if (commandPath != null) {
- final List<String> labels = <String>[];
- if (commandResult?.exitStatus != null)
- labels.add(getEnumName(commandResult.exitStatus));
- if (commandResult?.timingLabelParts?.isNotEmpty ?? false)
- labels.addAll(commandResult.timingLabelParts);
- final String label = labels
- .where((String label) => !isBlank(label))
- .join('-');
- flutterUsage.sendTiming(
- 'flutter',
- name,
- // If the command provides its own end time, use it. Otherwise report
- // the duration of the entire execution.
- (commandResult?.endTimeOverride ?? endTime).difference(startTime),
- // Report in the form of `success-[parameter1-parameter2]`, all of which
- // can be null if the command doesn't provide a FlutterCommandResult.
- label: label == '' ? null : label,
- );
- }
+ await _sendUsage(commandResult, startTime, endTime);
}
},
);
}
+ /// Logs data about this command.
+ ///
+ /// For example, the command path (e.g. `build/apk`) and the result,
+ /// as well as the time spent running it.
+ Future<void> _sendUsage(FlutterCommandResult commandResult, DateTime startTime, DateTime endTime) async {
+ final String commandPath = await usagePath;
+
+ if (commandPath == null) {
+ return;
+ }
+
+ // Send screen.
+ final Map<String, String> additionalUsageValues = <String, String>{};
+ final Map<String, String> currentUsageValues = await usageValues;
+
+ if (currentUsageValues != null) {
+ additionalUsageValues.addAll(currentUsageValues);
+ }
+ if (commandResult != null) {
+ switch (commandResult.exitStatus) {
+ case ExitStatus.success:
+ additionalUsageValues[kCommandResult] = 'success';
+ break;
+ case ExitStatus.warning:
+ additionalUsageValues[kCommandResult] = 'warning';
+ break;
+ case ExitStatus.fail:
+ additionalUsageValues[kCommandResult] = 'fail';
+ break;
+ }
+ }
+ flutterUsage.sendCommand(commandPath, parameters: additionalUsageValues);
+
+ // Send timing.
+ final List<String> labels = <String>[];
+ if (commandResult?.exitStatus != null)
+ labels.add(getEnumName(commandResult.exitStatus));
+ if (commandResult?.timingLabelParts?.isNotEmpty ?? false)
+ labels.addAll(commandResult.timingLabelParts);
+
+ final String label = labels
+ .where((String label) => !isBlank(label))
+ .join('-');
+ flutterUsage.sendTiming(
+ 'flutter',
+ name,
+ // If the command provides its own end time, use it. Otherwise report
+ // the duration of the entire execution.
+ (commandResult?.endTimeOverride ?? endTime).difference(startTime),
+ // Report in the form of `success-[parameter1-parameter2]`, all of which
+ // can be null if the command doesn't provide a FlutterCommandResult.
+ label: label == '' ? null : label,
+ );
+ }
+
/// Perform validation then call [runCommand] to execute the command.
/// Return a [Future] that completes with an exit code
/// indicating whether execution was successful.
@@ -453,7 +488,7 @@
/// then call this method to execute the command
/// rather than calling [runCommand] directly.
@mustCallSuper
- Future<FlutterCommandResult> verifyThenRunCommand(String commandPath) async {
+ Future<FlutterCommandResult> verifyThenRunCommand() async {
await validateCommand();
// Populate the cache. We call this before pub get below so that the sky_engine
@@ -470,11 +505,6 @@
setupApplicationPackages();
- if (commandPath != null) {
- final Map<String, String> additionalUsageValues = await usageValues;
- flutterUsage.sendCommand(commandPath, parameters: additionalUsageValues);
- }
-
return await runCommand();
}
diff --git a/packages/flutter_tools/lib/src/usage.dart b/packages/flutter_tools/lib/src/usage.dart
index d74437b..63d8d7f 100644
--- a/packages/flutter_tools/lib/src/usage.dart
+++ b/packages/flutter_tools/lib/src/usage.dart
@@ -47,7 +47,9 @@
const String kCommandBuildBundleTargetPlatform = 'cd24';
const String kCommandBuildBundleIsModule = 'cd25';
-// Next ID: cd26
+
+const String kCommandResult = 'cd26';
+// Next ID: cd27
Usage get flutterUsage => Usage.instance;
diff --git a/packages/flutter_tools/test/runner/flutter_command_test.dart b/packages/flutter_tools/test/runner/flutter_command_test.dart
index e91b95b..a402b1d 100644
--- a/packages/flutter_tools/test/runner/flutter_command_test.dart
+++ b/packages/flutter_tools/test/runner/flutter_command_test.dart
@@ -49,6 +49,114 @@
Cache: () => cache,
});
+ testUsingContext('reports command that results in success', () async {
+ // Crash if called a third time which is unexpected.
+ mockTimes = <int>[1000, 2000];
+
+ final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
+ commandFunction: () async {
+ return const FlutterCommandResult(ExitStatus.success);
+ }
+ );
+ await flutterCommand.run();
+
+ expect(
+ verify(usage.sendCommand(captureAny,
+ parameters: captureAnyNamed('parameters'))).captured,
+ <dynamic>[
+ 'dummy',
+ const <String, String>{'cd26': 'success'}
+ ],
+ );
+ },
+ overrides: <Type, Generator>{
+ SystemClock: () => clock,
+ Usage: () => usage,
+ });
+
+ testUsingContext('reports command that results in warning', () async {
+ // Crash if called a third time which is unexpected.
+ mockTimes = <int>[1000, 2000];
+
+ final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
+ commandFunction: () async {
+ return const FlutterCommandResult(ExitStatus.warning);
+ }
+ );
+ await flutterCommand.run();
+
+ expect(
+ verify(usage.sendCommand(captureAny,
+ parameters: captureAnyNamed('parameters'))).captured,
+ <dynamic>[
+ 'dummy',
+ const <String, String>{'cd26': 'warning'}
+ ],
+ );
+ },
+ overrides: <Type, Generator>{
+ SystemClock: () => clock,
+ Usage: () => usage,
+ });
+
+ testUsingContext('reports command that results in failure', () async {
+ // Crash if called a third time which is unexpected.
+ mockTimes = <int>[1000, 2000];
+
+ final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
+ commandFunction: () async {
+ return const FlutterCommandResult(ExitStatus.fail);
+ }
+ );
+
+ try {
+ await flutterCommand.run();
+ } on ToolExit {
+ expect(
+ verify(usage.sendCommand(captureAny,
+ parameters: captureAnyNamed('parameters'))).captured,
+ <dynamic>[
+ 'dummy',
+ const <String, String>{'cd26': 'fail'}
+ ],
+ );
+ }
+ },
+ overrides: <Type, Generator>{
+ SystemClock: () => clock,
+ Usage: () => usage,
+ });
+
+ testUsingContext('reports command that results in error', () async {
+ // Crash if called a third time which is unexpected.
+ mockTimes = <int>[1000, 2000];
+
+ final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
+ commandFunction: () async {
+ throwToolExit('fail');
+ return null; // unreachable
+ }
+ );
+
+ try {
+ await flutterCommand.run();
+ fail('Mock should make this fail');
+ } on ToolExit {
+ expect(
+ verify(usage.sendCommand(captureAny,
+ parameters: captureAnyNamed('parameters'))).captured,
+ <dynamic>[
+ 'dummy',
+ const <String, String>{'cd26': 'fail'}
+ ],
+ );
+ }
+ },
+ overrides: <Type, Generator>{
+ SystemClock: () => clock,
+ Usage: () => usage,
+ });
+
testUsingContext('report execution timing by default', () async {
// Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000];
@@ -61,7 +169,12 @@
verify(usage.sendTiming(
captureAny, captureAny, captureAny,
label: captureAnyNamed('label'))).captured,
- <dynamic>['flutter', 'dummy', const Duration(milliseconds: 1000), null],
+ <dynamic>[
+ 'flutter',
+ 'dummy',
+ const Duration(milliseconds: 1000),
+ null
+ ],
);
},
overrides: <Type, Generator>{