Unverified Commit afdc783c authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Report commands that resulted in success or failure (#34288)

This is added as a dimension cd26
parent 3b42341a
...@@ -26,7 +26,6 @@ import '../resident_runner.dart'; ...@@ -26,7 +26,6 @@ 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 '../usage.dart';
/// A Flutter-command that attaches to applications that have been launched /// A Flutter-command that attaches to applications that have been launched
/// without `flutter run`. /// without `flutter run`.
...@@ -316,10 +315,7 @@ class AttachCommand extends FlutterCommand { ...@@ -316,10 +315,7 @@ class AttachCommand extends FlutterCommand {
result = await runner.attach(); result = await runner.attach();
assert(result != null); assert(result != null);
} }
if (result == 0) { if (result != 0) {
flutterUsage.sendEvent('attach', 'success');
} else {
flutterUsage.sendEvent('attach', 'failure');
throwToolExit(null, exitCode: result); throwToolExit(null, exitCode: result);
} }
} finally { } finally {
......
...@@ -32,11 +32,11 @@ class LogsCommand extends FlutterCommand { ...@@ -32,11 +32,11 @@ class LogsCommand extends FlutterCommand {
Device device; Device device;
@override @override
Future<FlutterCommandResult> verifyThenRunCommand(String commandPath) async { Future<FlutterCommandResult> verifyThenRunCommand() async {
device = await findTargetDevice(); device = await findTargetDevice();
if (device == null) if (device == null)
throwToolExit(null); throwToolExit(null);
return super.verifyThenRunCommand(commandPath); return super.verifyThenRunCommand();
} }
@override @override
......
...@@ -219,14 +219,21 @@ class RunCommand extends RunCommandBase { ...@@ -219,14 +219,21 @@ class RunCommand extends RunCommandBase {
@override @override
Future<Map<String, String>> get usageValues async { Future<Map<String, String>> get usageValues async {
final bool isEmulator = await devices[0].isLocalEmulator;
String deviceType, deviceOsVersion; 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); deviceType = getNameForTargetPlatform(await devices[0].targetPlatform);
deviceOsVersion = await devices[0].sdkNameAndVersion; deviceOsVersion = await devices[0].sdkNameAndVersion;
isEmulator = await devices[0].isLocalEmulator;
} else { } else {
deviceType = 'multiple'; deviceType = 'multiple';
deviceOsVersion = 'multiple'; deviceOsVersion = 'multiple';
isEmulator = false;
} }
final String modeName = getBuildInfo().modeName; final String modeName = getBuildInfo().modeName;
final AndroidProject androidProject = FlutterProject.current().android; final AndroidProject androidProject = FlutterProject.current().android;
......
...@@ -64,7 +64,7 @@ class ScreenshotCommand extends FlutterCommand { ...@@ -64,7 +64,7 @@ class ScreenshotCommand extends FlutterCommand {
Device device; Device device;
@override @override
Future<FlutterCommandResult> verifyThenRunCommand(String commandPath) async { Future<FlutterCommandResult> verifyThenRunCommand() async {
device = await findTargetDevice(); device = await findTargetDevice();
if (device == null) if (device == null)
throwToolExit('Must have a connected device'); throwToolExit('Must have a connected device');
...@@ -72,7 +72,7 @@ class ScreenshotCommand extends FlutterCommand { ...@@ -72,7 +72,7 @@ class ScreenshotCommand extends FlutterCommand {
throwToolExit('Screenshot not supported for ${device.name}.'); throwToolExit('Screenshot not supported for ${device.name}.');
if (argResults[_kType] != _kDeviceType && argResults[_kObservatoryUri] == null) if (argResults[_kType] != _kDeviceType && argResults[_kObservatoryUri] == null)
throwToolExit('Observatory URI must be specified for screenshot type ${argResults[_kType]}'); throwToolExit('Observatory URI must be specified for screenshot type ${argResults[_kType]}');
return super.verifyThenRunCommand(commandPath); return super.verifyThenRunCommand();
} }
@override @override
......
...@@ -408,10 +408,9 @@ abstract class FlutterCommand extends Command<void> { ...@@ -408,10 +408,9 @@ abstract class FlutterCommand extends Command<void> {
body: () async { body: () async {
if (flutterUsage.isFirstRun) if (flutterUsage.isFirstRun)
flutterUsage.printWelcome(); flutterUsage.printWelcome();
final String commandPath = await usagePath;
FlutterCommandResult commandResult; FlutterCommandResult commandResult;
try { try {
commandResult = await verifyThenRunCommand(commandPath); commandResult = await verifyThenRunCommand();
} on ToolExit { } on ToolExit {
commandResult = const FlutterCommandResult(ExitStatus.fail); commandResult = const FlutterCommandResult(ExitStatus.fail);
rethrow; rethrow;
...@@ -419,32 +418,68 @@ abstract class FlutterCommand extends Command<void> { ...@@ -419,32 +418,68 @@ abstract class FlutterCommand extends Command<void> {
final DateTime endTime = systemClock.now(); final DateTime endTime = systemClock.now();
printTrace(userMessages.flutterElapsedTime(name, getElapsedAsMilliseconds(endTime.difference(startTime)))); printTrace(userMessages.flutterElapsedTime(name, getElapsedAsMilliseconds(endTime.difference(startTime))));
printTrace('"flutter $name" took ${getElapsedAsMilliseconds(endTime.difference(startTime))}.'); printTrace('"flutter $name" took ${getElapsedAsMilliseconds(endTime.difference(startTime))}.');
if (commandPath != null) {
final List<String> labels = <String>[]; await _sendUsage(commandResult, startTime, endTime);
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,
);
}
} }
}, },
); );
} }
/// 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. /// Perform validation then call [runCommand] to execute the command.
/// Return a [Future] that completes with an exit code /// Return a [Future] that completes with an exit code
/// indicating whether execution was successful. /// indicating whether execution was successful.
...@@ -453,7 +488,7 @@ abstract class FlutterCommand extends Command<void> { ...@@ -453,7 +488,7 @@ abstract class FlutterCommand extends Command<void> {
/// then call this method to execute the command /// then call this method to execute the command
/// rather than calling [runCommand] directly. /// rather than calling [runCommand] directly.
@mustCallSuper @mustCallSuper
Future<FlutterCommandResult> verifyThenRunCommand(String commandPath) async { Future<FlutterCommandResult> verifyThenRunCommand() async {
await validateCommand(); await validateCommand();
// Populate the cache. We call this before pub get below so that the sky_engine // Populate the cache. We call this before pub get below so that the sky_engine
...@@ -470,11 +505,6 @@ abstract class FlutterCommand extends Command<void> { ...@@ -470,11 +505,6 @@ abstract class FlutterCommand extends Command<void> {
setupApplicationPackages(); setupApplicationPackages();
if (commandPath != null) {
final Map<String, String> additionalUsageValues = await usageValues;
flutterUsage.sendCommand(commandPath, parameters: additionalUsageValues);
}
return await runCommand(); return await runCommand();
} }
......
...@@ -47,7 +47,9 @@ const String kCommandPackagesProjectModule = 'cd21'; ...@@ -47,7 +47,9 @@ const String kCommandPackagesProjectModule = 'cd21';
const String kCommandBuildBundleTargetPlatform = 'cd24'; const String kCommandBuildBundleTargetPlatform = 'cd24';
const String kCommandBuildBundleIsModule = 'cd25'; const String kCommandBuildBundleIsModule = 'cd25';
// Next ID: cd26
const String kCommandResult = 'cd26';
// Next ID: cd27
Usage get flutterUsage => Usage.instance; Usage get flutterUsage => Usage.instance;
......
...@@ -49,6 +49,114 @@ void main() { ...@@ -49,6 +49,114 @@ void main() {
Cache: () => cache, 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 { testUsingContext('report execution timing by default', () async {
// Crash if called a third time which is unexpected. // Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000]; mockTimes = <int>[1000, 2000];
...@@ -61,7 +169,12 @@ void main() { ...@@ -61,7 +169,12 @@ void main() {
verify(usage.sendTiming( verify(usage.sendTiming(
captureAny, captureAny, captureAny, captureAny, captureAny, captureAny,
label: captureAnyNamed('label'))).captured, label: captureAnyNamed('label'))).captured,
<dynamic>['flutter', 'dummy', const Duration(milliseconds: 1000), null], <dynamic>[
'flutter',
'dummy',
const Duration(milliseconds: 1000),
null
],
); );
}, },
overrides: <Type, Generator>{ overrides: <Type, Generator>{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment