Unverified Commit c7c8a6c4 authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

[flutter_tools] Add more info to pub get failure event (#41652)

parent 517c08e8
...@@ -6,6 +6,7 @@ import 'dart:async'; ...@@ -6,6 +6,7 @@ import 'dart:async';
import '../convert.dart'; import '../convert.dart';
import '../globals.dart'; import '../globals.dart';
import 'common.dart';
import 'context.dart'; import 'context.dart';
import 'file_system.dart'; import 'file_system.dart';
import 'io.dart'; import 'io.dart';
...@@ -470,10 +471,13 @@ class _DefaultProcessUtils implements ProcessUtils { ...@@ -470,10 +471,13 @@ class _DefaultProcessUtils implements ProcessUtils {
stderrSubscription.asFuture<void>(), stderrSubscription.asFuture<void>(),
]); ]);
await waitGroup<void>(<Future<void>>[ // The streams as futures have already completed, so waiting for the
stdoutSubscription.cancel(), // potentially async stream cancellation to complete likely has no benefit.
stderrSubscription.cancel(), // Further, some Stream implementations commonly used in tests don't
]); // complete the Future returned here, which causes tests using
// mocks/FakeAsync to fail when these Futures are awaited.
unawaited(stdoutSubscription.cancel());
unawaited(stderrSubscription.cancel());
return await process.exitCode; return await process.exitCode;
} }
......
...@@ -100,12 +100,10 @@ class PackagesGetCommand extends FlutterCommand { ...@@ -100,12 +100,10 @@ class PackagesGetCommand extends FlutterCommand {
checkLastModified: false, checkLastModified: false,
); );
pubGetTimer.stop(); pubGetTimer.stop();
PubGetEvent(success: true).send(); flutterUsage.sendTiming('pub', 'get', pubGetTimer.elapsed, label: 'success');
flutterUsage.sendTiming('packages-pub-get', 'success', pubGetTimer.elapsed);
} catch (_) { } catch (_) {
pubGetTimer.stop(); pubGetTimer.stop();
PubGetEvent(success: false).send(); flutterUsage.sendTiming('pub', 'get', pubGetTimer.elapsed, label: 'failure');
flutterUsage.sendTiming('packages-pub-get', 'failure', pubGetTimer.elapsed);
rethrow; rethrow;
} }
} }
......
...@@ -15,6 +15,7 @@ import '../base/process.dart'; ...@@ -15,6 +15,7 @@ import '../base/process.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../cache.dart'; import '../cache.dart';
import '../globals.dart'; import '../globals.dart';
import '../reporting/reporting.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import 'sdk.dart'; import 'sdk.dart';
...@@ -54,6 +55,10 @@ class PubContext { ...@@ -54,6 +55,10 @@ class PubContext {
@override @override
String toString() => 'PubContext: ${_values.join(':')}'; String toString() => 'PubContext: ${_values.join(':')}';
String toAnalyticsString() {
return _values.map((String s) => s.replaceAll('_', '-')).toList().join('-');
}
} }
bool _shouldRunPubGet({ File pubSpecYaml, File dotPackages }) { bool _shouldRunPubGet({ File pubSpecYaml, File dotPackages }) {
...@@ -128,7 +133,9 @@ Future<void> pubGet({ ...@@ -128,7 +133,9 @@ Future<void> pubGet({
} }
if (dotPackages.lastModifiedSync().isBefore(pubSpecYaml.lastModifiedSync())) { if (dotPackages.lastModifiedSync().isBefore(pubSpecYaml.lastModifiedSync())) {
throwToolExit('$directory: pub did not update .packages file (pubspec.yaml timestamp: ${pubSpecYaml.lastModifiedSync()}; .packages timestamp: ${dotPackages.lastModifiedSync()}).'); throwToolExit('$directory: pub did not update .packages file '
'(pubspec.yaml timestamp: ${pubSpecYaml.lastModifiedSync()}; '
'.packages timestamp: ${dotPackages.lastModifiedSync()}).');
} }
} }
...@@ -155,6 +162,17 @@ Future<void> pub( ...@@ -155,6 +162,17 @@ Future<void> pub(
}) async { }) async {
showTraceForErrors ??= isRunningOnBot; showTraceForErrors ??= isRunningOnBot;
bool versionSolvingFailed = false;
String filterWrapper(String line) {
if (line.contains('version solving failed')) {
versionSolvingFailed = true;
}
if (filter == null) {
return line;
}
return filter(line);
}
if (showTraceForErrors) { if (showTraceForErrors) {
arguments.insert(0, '--trace'); arguments.insert(0, '--trace');
} }
...@@ -166,12 +184,13 @@ Future<void> pub( ...@@ -166,12 +184,13 @@ Future<void> pub(
code = await processUtils.stream( code = await processUtils.stream(
_pubCommand(arguments), _pubCommand(arguments),
workingDirectory: directory, workingDirectory: directory,
mapFunction: filter, mapFunction: filterWrapper,
environment: _createPubEnvironment(context), environment: _createPubEnvironment(context),
); );
if (code != 69) { // UNAVAILABLE in https://github.com/dart-lang/pub/blob/master/lib/src/exit_codes.dart if (code != 69) { // UNAVAILABLE in https://github.com/dart-lang/pub/blob/master/lib/src/exit_codes.dart
break; break;
} }
versionSolvingFailed = false;
printStatus('$failureMessage ($code) -- attempting retry $attempts in $duration second${ duration == 1 ? "" : "s"}...'); printStatus('$failureMessage ($code) -- attempting retry $attempts in $duration second${ duration == 1 ? "" : "s"}...');
await Future<void>.delayed(Duration(seconds: duration)); await Future<void>.delayed(Duration(seconds: duration));
if (duration < 64) { if (duration < 64) {
...@@ -179,6 +198,18 @@ Future<void> pub( ...@@ -179,6 +198,18 @@ Future<void> pub(
} }
} }
assert(code != null); assert(code != null);
String result = 'success';
if (versionSolvingFailed) {
result = 'version-solving-failed';
} else if (code != 0) {
result = 'failure';
}
PubResultEvent(
context: context.toAnalyticsString(),
result: result,
).send();
if (code != 0) { if (code != 0) {
throwToolExit('$failureMessage ($code)', exitCode: code); throwToolExit('$failureMessage ($code)', exitCode: code);
} }
......
...@@ -27,7 +27,7 @@ class DisabledUsage implements Usage { ...@@ -27,7 +27,7 @@ class DisabledUsage implements Usage {
void sendCommand(String command, { Map<String, String> parameters }) { } void sendCommand(String command, { Map<String, String> parameters }) { }
@override @override
void sendEvent(String category, String parameter, { Map<String, String> parameters }) { } void sendEvent(String category, String parameter, { String label, Map<String, String> parameters }) { }
@override @override
void sendTiming(String category, String variableName, Duration duration, { String label }) { } void sendTiming(String category, String variableName, Duration duration, { String label }) { }
......
...@@ -9,13 +9,16 @@ part of reporting; ...@@ -9,13 +9,16 @@ part of reporting;
/// If sending values for custom dimensions is required, extend this class as /// If sending values for custom dimensions is required, extend this class as
/// below. /// below.
class UsageEvent { class UsageEvent {
UsageEvent(this.category, this.parameter); UsageEvent(this.category, this.parameter, {
this.label,
});
final String category; final String category;
final String parameter; final String parameter;
final String label;
void send() { void send() {
flutterUsage.sendEvent(category, parameter); flutterUsage.sendEvent(category, parameter, label: label);
} }
} }
...@@ -101,7 +104,7 @@ class DoctorResultEvent extends UsageEvent { ...@@ -101,7 +104,7 @@ class DoctorResultEvent extends UsageEvent {
@override @override
void send() { void send() {
if (validator is! GroupedValidator) { if (validator is! GroupedValidator) {
flutterUsage.sendEvent(category, parameter); flutterUsage.sendEvent(category, parameter, label: label);
return; return;
} }
final GroupedValidator group = validator; final GroupedValidator group = validator;
...@@ -114,10 +117,11 @@ class DoctorResultEvent extends UsageEvent { ...@@ -114,10 +117,11 @@ class DoctorResultEvent extends UsageEvent {
} }
/// An event that reports success or failure of a pub get. /// An event that reports success or failure of a pub get.
class PubGetEvent extends UsageEvent { class PubResultEvent extends UsageEvent {
PubGetEvent({ PubResultEvent({
@required bool success, @required String context,
}) : super('packages-pub-get', success ? 'success' : 'failure'); @required String result,
}) : super('pub', context, label: result);
} }
/// An event that reports something about a build. /// An event that reports something about a build.
......
...@@ -124,6 +124,7 @@ abstract class Usage { ...@@ -124,6 +124,7 @@ abstract class Usage {
void sendEvent( void sendEvent(
String category, String category,
String parameter, { String parameter, {
String label,
Map<String, String> parameters, Map<String, String> parameters,
}); });
...@@ -262,6 +263,7 @@ class _DefaultUsage implements Usage { ...@@ -262,6 +263,7 @@ class _DefaultUsage implements Usage {
void sendEvent( void sendEvent(
String category, String category,
String parameter, { String parameter, {
String label,
Map<String, String> parameters, Map<String, String> parameters,
}) { }) {
if (suppressAnalytics) { if (suppressAnalytics) {
...@@ -273,7 +275,12 @@ class _DefaultUsage implements Usage { ...@@ -273,7 +275,12 @@ class _DefaultUsage implements Usage {
cdKey(CustomDimensions.localTime): formatDateTime(systemClock.now()), cdKey(CustomDimensions.localTime): formatDateTime(systemClock.now()),
}; };
_analytics.sendEvent(category, parameter, parameters: paramsWithLocalTime); _analytics.sendEvent(
category,
parameter,
label: label,
parameters: paramsWithLocalTime,
);
} }
@override @override
......
...@@ -225,7 +225,11 @@ void main() { ...@@ -225,7 +225,11 @@ void main() {
await doctor.diagnose(verbose: false); await doctor.diagnose(verbose: false);
expect( expect(
verify(mockUsage.sendEvent('doctorResult.PassingValidator', captureAny)).captured, verify(mockUsage.sendEvent(
'doctorResult.PassingValidator',
captureAny,
label: anyNamed('label'),
)).captured,
<dynamic>['installed', 'installed', 'installed'], <dynamic>['installed', 'installed', 'installed'],
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -238,15 +242,27 @@ void main() { ...@@ -238,15 +242,27 @@ void main() {
await FakePassingDoctor().diagnose(verbose: false); await FakePassingDoctor().diagnose(verbose: false);
expect( expect(
verify(mockUsage.sendEvent('doctorResult.PassingValidator', captureAny)).captured, verify(mockUsage.sendEvent(
'doctorResult.PassingValidator',
captureAny,
label: anyNamed('label'),
)).captured,
<dynamic>['installed', 'installed'], <dynamic>['installed', 'installed'],
); );
expect( expect(
verify(mockUsage.sendEvent('doctorResult.PartialValidatorWithHintsOnly', captureAny)).captured, verify(mockUsage.sendEvent(
'doctorResult.PartialValidatorWithHintsOnly',
captureAny,
label: anyNamed('label'),
)).captured,
<dynamic>['partial'], <dynamic>['partial'],
); );
expect( expect(
verify(mockUsage.sendEvent('doctorResult.PartialValidatorWithErrors', captureAny)).captured, verify(mockUsage.sendEvent(
'doctorResult.PartialValidatorWithErrors',
captureAny,
label: anyNamed('label'),
)).captured,
<dynamic>['partial'], <dynamic>['partial'],
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -258,23 +274,43 @@ void main() { ...@@ -258,23 +274,43 @@ void main() {
await FakeDoctor().diagnose(verbose: false); await FakeDoctor().diagnose(verbose: false);
expect( expect(
verify(mockUsage.sendEvent('doctorResult.PassingValidator', captureAny)).captured, verify(mockUsage.sendEvent(
'doctorResult.PassingValidator',
captureAny,
label: anyNamed('label'),
)).captured,
<dynamic>['installed'], <dynamic>['installed'],
); );
expect( expect(
verify(mockUsage.sendEvent('doctorResult.MissingValidator', captureAny)).captured, verify(mockUsage.sendEvent(
'doctorResult.MissingValidator',
captureAny,
label: anyNamed('label'),
)).captured,
<dynamic>['missing'], <dynamic>['missing'],
); );
expect( expect(
verify(mockUsage.sendEvent('doctorResult.NotAvailableValidator', captureAny)).captured, verify(mockUsage.sendEvent(
'doctorResult.NotAvailableValidator',
captureAny,
label: anyNamed('label'),
)).captured,
<dynamic>['notAvailable'], <dynamic>['notAvailable'],
); );
expect( expect(
verify(mockUsage.sendEvent('doctorResult.PartialValidatorWithHintsOnly', captureAny)).captured, verify(mockUsage.sendEvent(
'doctorResult.PartialValidatorWithHintsOnly',
captureAny,
label: anyNamed('label'),
)).captured,
<dynamic>['partial'], <dynamic>['partial'],
); );
expect( expect(
verify(mockUsage.sendEvent('doctorResult.PartialValidatorWithErrors', captureAny)).captured, verify(mockUsage.sendEvent(
'doctorResult.PartialValidatorWithErrors',
captureAny,
label: anyNamed('label'),
)).captured,
<dynamic>['partial'], <dynamic>['partial'],
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -286,11 +322,19 @@ void main() { ...@@ -286,11 +322,19 @@ void main() {
await FakeGroupedDoctor().diagnose(verbose: false); await FakeGroupedDoctor().diagnose(verbose: false);
expect( expect(
verify(mockUsage.sendEvent('doctorResult.PassingGroupedValidator', captureAny)).captured, verify(mockUsage.sendEvent(
'doctorResult.PassingGroupedValidator',
captureAny,
label: anyNamed('label'),
)).captured,
<dynamic>['installed', 'installed', 'installed'], <dynamic>['installed', 'installed', 'installed'],
); );
expect( expect(
verify(mockUsage.sendEvent('doctorResult.MissingGroupedValidator', captureAny)).captured, verify(mockUsage.sendEvent(
'doctorResult.MissingGroupedValidator',
captureAny,
label: anyNamed('label'),
)).captured,
<dynamic>['missing'], <dynamic>['missing'],
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
......
...@@ -3,14 +3,17 @@ ...@@ -3,14 +3,17 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:collection';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
...@@ -18,6 +21,7 @@ import 'package:quiver/testing/async.dart'; ...@@ -18,6 +21,7 @@ import 'package:quiver/testing/async.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
import '../../src/mocks.dart' as mocks;
void main() { void main() {
setUpAll(() { setUpAll(() {
...@@ -88,7 +92,7 @@ void main() { ...@@ -88,7 +92,7 @@ void main() {
ProcessManager: () => MockProcessManager(69), ProcessManager: () => MockProcessManager(69),
FileSystem: () => MockFileSystem(), FileSystem: () => MockFileSystem(),
Platform: () => FakePlatform( Platform: () => FakePlatform(
environment: <String, String>{}, environment: UnmodifiableMapView<String, String>(<String, String>{}),
), ),
}); });
...@@ -115,7 +119,7 @@ void main() { ...@@ -115,7 +119,7 @@ void main() {
ProcessManager: () => MockProcessManager(69), ProcessManager: () => MockProcessManager(69),
FileSystem: () => MockFileSystem(), FileSystem: () => MockFileSystem(),
Platform: () => FakePlatform( Platform: () => FakePlatform(
environment: <String, String>{}, environment: UnmodifiableMapView<String, String>(<String, String>{}),
), ),
}); });
...@@ -141,17 +145,78 @@ void main() { ...@@ -141,17 +145,78 @@ void main() {
ProcessManager: () => MockProcessManager(69), ProcessManager: () => MockProcessManager(69),
FileSystem: () => MockFileSystem(), FileSystem: () => MockFileSystem(),
Platform: () => FakePlatform( Platform: () => FakePlatform(
environment: <String, String>{'PUB_CACHE': 'custom/pub-cache/path'}, environment: UnmodifiableMapView<String, String>(<String, String>{
'PUB_CACHE': 'custom/pub-cache/path',
}),
), ),
}); });
testUsingContext('analytics sent on success', () async {
MockDirectory.findCache = true;
await pubGet(context: PubContext.flutterTests, checkLastModified: false);
verify(flutterUsage.sendEvent('pub', 'flutter-tests', label: 'success')).called(1);
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(0),
FileSystem: () => MockFileSystem(),
Platform: () => FakePlatform(
environment: UnmodifiableMapView<String, String>(<String, String>{
'PUB_CACHE': 'custom/pub-cache/path',
}),
),
Usage: () => MockUsage(),
});
testUsingContext('analytics sent on failure', () async {
MockDirectory.findCache = true;
try {
await pubGet(context: PubContext.flutterTests, checkLastModified: false);
} on ToolExit {
// Ignore.
}
verify(flutterUsage.sendEvent('pub', 'flutter-tests', label: 'failure')).called(1);
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(1),
FileSystem: () => MockFileSystem(),
Platform: () => FakePlatform(
environment: UnmodifiableMapView<String, String>(<String, String>{
'PUB_CACHE': 'custom/pub-cache/path',
}),
),
Usage: () => MockUsage(),
});
testUsingContext('analytics sent on failed version solve', () async {
MockDirectory.findCache = true;
try {
await pubGet(context: PubContext.flutterTests, checkLastModified: false);
} on ToolExit {
// Ignore.
}
verify(flutterUsage.sendEvent('pub', 'flutter-tests', label: 'version-solving-failed')).called(1);
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(
1,
stderr: 'version solving failed',
),
FileSystem: () => MockFileSystem(),
Platform: () => FakePlatform(
environment: UnmodifiableMapView<String, String>(<String, String>{
'PUB_CACHE': 'custom/pub-cache/path',
}),
),
Usage: () => MockUsage(),
});
} }
typedef StartCallback = void Function(List<dynamic> command); typedef StartCallback = void Function(List<dynamic> command);
class MockProcessManager implements ProcessManager { class MockProcessManager implements ProcessManager {
MockProcessManager(this.fakeExitCode); MockProcessManager(this.fakeExitCode, {
this.stderr = '',
});
final int fakeExitCode; final int fakeExitCode;
final String stderr;
String lastPubEnvironment; String lastPubEnvironment;
String lastPubCache; String lastPubCache;
...@@ -167,59 +232,16 @@ class MockProcessManager implements ProcessManager { ...@@ -167,59 +232,16 @@ class MockProcessManager implements ProcessManager {
}) { }) {
lastPubEnvironment = environment['PUB_ENVIRONMENT']; lastPubEnvironment = environment['PUB_ENVIRONMENT'];
lastPubCache = environment['PUB_CACHE']; lastPubCache = environment['PUB_CACHE'];
return Future<Process>.value(MockProcess(fakeExitCode)); return Future<Process>.value(mocks.createMockProcess(
} exitCode: fakeExitCode,
stderr: stderr,
@override ));
dynamic noSuchMethod(Invocation invocation) => null;
}
class MockProcess implements Process {
MockProcess(this.fakeExitCode);
final int fakeExitCode;
@override
Stream<List<int>> get stdout => MockStream<List<int>>();
@override
Stream<List<int>> get stderr => MockStream<List<int>>();
@override
Future<int> get exitCode => Future<int>.value(fakeExitCode);
@override
dynamic noSuchMethod(Invocation invocation) => null;
}
class MockStream<T> implements Stream<T> {
@override
Stream<S> transform<S>(StreamTransformer<T, S> streamTransformer) => MockStream<S>();
@override
Stream<T> where(bool test(T event)) => MockStream<T>();
@override
StreamSubscription<T> listen(void onData(T event), { Function onError, void onDone(), bool cancelOnError }) {
return MockStreamSubscription<T>();
} }
@override @override
dynamic noSuchMethod(Invocation invocation) => null; dynamic noSuchMethod(Invocation invocation) => null;
} }
class MockStreamSubscription<T> implements StreamSubscription<T> {
@override
Future<E> asFuture<E>([ E futureValue ]) => Future<E>.value();
@override
Future<void> cancel() async { }
@override
dynamic noSuchMethod(Invocation invocation) => null;
}
class MockFileSystem extends ForwardingFileSystem { class MockFileSystem extends ForwardingFileSystem {
MockFileSystem() : super(MemoryFileSystem()); MockFileSystem() : super(MemoryFileSystem());
...@@ -266,3 +288,5 @@ class MockDirectory implements Directory { ...@@ -266,3 +288,5 @@ class MockDirectory implements Directory {
} }
class MockRandomAccessFile extends Mock implements RandomAccessFile {} class MockRandomAccessFile extends Mock implements RandomAccessFile {}
class MockUsage extends Mock implements Usage {}
...@@ -139,8 +139,9 @@ class CrashingUsage implements Usage { ...@@ -139,8 +139,9 @@ class CrashingUsage implements Usage {
void sendEvent( void sendEvent(
String category, String category,
String parameter, { String parameter, {
String label,
Map<String, String> parameters, Map<String, String> parameters,
}) => _impl.sendEvent(category, parameter, parameters: parameters); }) => _impl.sendEvent(category, parameter, label: label, parameters: parameters);
@override @override
void sendTiming( void sendTiming(
......
...@@ -303,7 +303,7 @@ class FakeUsage implements Usage { ...@@ -303,7 +303,7 @@ class FakeUsage implements Usage {
void sendCommand(String command, { Map<String, String> parameters }) { } void sendCommand(String command, { Map<String, String> parameters }) { }
@override @override
void sendEvent(String category, String parameter, { Map<String, String> parameters }) { } void sendEvent(String category, String parameter, { String label, Map<String, String> parameters }) { }
@override @override
void sendTiming(String category, String variableName, Duration duration, { String label }) { } void sendTiming(String category, String variableName, Duration duration, { String label }) { }
......
...@@ -162,7 +162,7 @@ class NoOpUsage implements Usage { ...@@ -162,7 +162,7 @@ class NoOpUsage implements Usage {
void sendCommand(String command, {Map<String, String> parameters}) {} void sendCommand(String command, {Map<String, String> parameters}) {}
@override @override
void sendEvent(String category, String parameter,{ Map<String, String> parameters }) {} void sendEvent(String category, String parameter, { String label, Map<String, String> parameters }) {}
@override @override
void sendException(dynamic exception) {} void sendException(dynamic exception) {}
......
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