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';
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 @@ class AttachCommand extends FlutterCommand {
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 {
......
......@@ -32,11 +32,11 @@ class LogsCommand extends FlutterCommand {
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
......
......@@ -219,14 +219,21 @@ class RunCommand extends RunCommandBase {
@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;
......
......@@ -64,7 +64,7 @@ class ScreenshotCommand extends FlutterCommand {
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 @@ class ScreenshotCommand extends FlutterCommand {
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
......
......@@ -408,10 +408,9 @@ abstract class FlutterCommand extends Command<void> {
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 @@ abstract class FlutterCommand extends Command<void> {
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 @@ abstract class FlutterCommand extends Command<void> {
/// 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 @@ abstract class FlutterCommand extends Command<void> {
setupApplicationPackages();
if (commandPath != null) {
final Map<String, String> additionalUsageValues = await usageValues;
flutterUsage.sendCommand(commandPath, parameters: additionalUsageValues);
}
return await runCommand();
}
......
......@@ -47,7 +47,9 @@ const String kCommandPackagesProjectModule = 'cd21';
const String kCommandBuildBundleTargetPlatform = 'cd24';
const String kCommandBuildBundleIsModule = 'cd25';
// Next ID: cd26
const String kCommandResult = 'cd26';
// Next ID: cd27
Usage get flutterUsage => Usage.instance;
......
......@@ -49,6 +49,114 @@ void main() {
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 @@ void main() {
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>{
......
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