diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index b2a03110320..fb123577392 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart @@ -334,6 +334,15 @@ class GroupedValidator extends DoctorValidator { final List subValidators; + List _subResults; + + /// Subvalidator results. + /// + /// To avoid losing information when results are merged, the subresults are + /// cached on this field when they are available. The results are in the same + /// order as the subvalidator list. + List get subResults => _subResults; + @override String get slowWarning => _currentSlowWarning; String _currentSlowWarning = 'Initializing...'; @@ -356,6 +365,7 @@ class GroupedValidator extends DoctorValidator { ValidationResult _mergeValidationResults(List results) { assert(results.isNotEmpty, 'Validation results should not be empty'); + _subResults = results; ValidationType mergedType = results[0].type; final List mergedMessages = []; String statusInfo; diff --git a/packages/flutter_tools/lib/src/reporting/events.dart b/packages/flutter_tools/lib/src/reporting/events.dart index 515314ea8ea..8a73f516360 100644 --- a/packages/flutter_tools/lib/src/reporting/events.dart +++ b/packages/flutter_tools/lib/src/reporting/events.dart @@ -90,12 +90,27 @@ class HotEvent extends UsageEvent { /// An event that reports the result of a [DoctorValidator] class DoctorResultEvent extends UsageEvent { DoctorResultEvent({ - @required DoctorValidator validator, - @required ValidationResult result - }) : super('doctorResult.${validator.runtimeType.toString()}', + @required this.validator, + @required this.result, + }) : super('doctorResult.${validator.runtimeType}', result.typeStr); - // TODO(zra): Override send() to detect a GroupedValidator and send separate - // events for each sub-validator. + + final DoctorValidator validator; + final ValidationResult result; + + @override + void send() { + if (validator is! GroupedValidator) { + flutterUsage.sendEvent(category, parameter); + return; + } + final GroupedValidator group = validator; + for (int i = 0; i < group.subValidators.length; i++) { + final DoctorValidator v = group.subValidators[i]; + final ValidationResult r = group.subResults[i]; + DoctorResultEvent(validator: v, result: r).send(); + } + } } /// An event that reports success or failure of a pub get. diff --git a/packages/flutter_tools/test/general.shard/commands/doctor_test.dart b/packages/flutter_tools/test/general.shard/commands/doctor_test.dart index 555af06d24c..38c3350a60d 100644 --- a/packages/flutter_tools/test/general.shard/commands/doctor_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/doctor_test.dart @@ -276,6 +276,22 @@ void main() { Platform: _kNoColorOutputPlatform, Usage: () => mockUsage, }); + + testUsingContext('events for grouped validators are properly decomposed', () async { + await FakeGroupedDoctor().diagnose(verbose: false); + + expect( + verify(mockUsage.sendEvent('doctorResult.PassingGroupedValidator', captureAny)).captured, + ['installed', 'installed', 'installed'], + ); + expect( + verify(mockUsage.sendEvent('doctorResult.MissingGroupedValidator', captureAny)).captured, + ['missing'], + ); + }, overrides: { + Platform: _kNoColorOutputPlatform, + Usage: () => mockUsage, + }); }); group('doctor with fake validators', () { diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index 2b17c50090d..644f828ffe1 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -52,6 +52,7 @@ void main() { // DevFS Mocks when(mockDevFS.lastCompiled).thenReturn(DateTime(2000)); when(mockDevFS.sources).thenReturn([]); + when(mockDevFS.baseUri).thenReturn(Uri()); when(mockDevFS.destroy()).thenAnswer((Invocation invocation) async { }); when(mockDevFS.assetPathsToEvict).thenReturn({}); // FlutterDevice Mocks. @@ -80,6 +81,7 @@ void main() { ]); when(mockFlutterDevice.device).thenReturn(mockDevice); when(mockFlutterView.uiIsolate).thenReturn(mockIsolate); + when(mockFlutterView.runFromSource(any, any, any)).thenAnswer((Invocation invocation) async {}); when(mockFlutterDevice.stopEchoingDeviceLog()).thenAnswer((Invocation invocation) async { }); when(mockFlutterDevice.observatoryUris).thenReturn([ testUri, @@ -189,9 +191,6 @@ void main() { Usage: () => MockUsage(), })); - - // Need one for hot restart as well. - test('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async { when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example'; @@ -221,6 +220,36 @@ void main() { Usage: () => MockUsage(), })); + test('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async { + when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { + return 'Example'; + }); + when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async { + return TargetPlatform.android_arm; + }); + when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async { + return false; + }); + when(mockDevice.supportsHotRestart).thenReturn(true); + final Completer onConnectionInfo = Completer.sync(); + final Completer onAppStart = Completer.sync(); + unawaited(residentRunner.attach( + appStartedCompleter: onAppStart, + connectionInfoCompleter: onConnectionInfo, + )); + + final OperationResult result = await residentRunner.restart(fullRestart: true); + expect(result.fatal, false); + expect(result.code, 0); + expect(verify(flutterUsage.sendEvent('hot', 'restart', + parameters: captureAnyNamed('parameters'))).captured[0], + containsPair(cdKey(CustomDimensions.hotEventTargetPlatform), + getNameForTargetPlatform(TargetPlatform.android_arm)) + ); + }, overrides: { + Usage: () => MockUsage(), + })); + test('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async { when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example';