mirror of
https://github.com/flutter/flutter
synced 2024-08-27 12:00:58 +00:00
Unified analytics events for doctor validators (#136647)
Related to tracking issue: - https://github.com/flutter/flutter/issues/128251 This PR sends analytic events for each of the doctor validators. This PR below will need to land first in `dart-lang/tools` before this merges.
This commit is contained in:
parent
6c81009bcb
commit
56ae555992
|
@ -105,6 +105,9 @@ Future<int> run(
|
|||
globals.flutterUsage.enabled = true;
|
||||
globals.printStatus('Analytics reporting enabled.');
|
||||
|
||||
// TODO(eliasyishak): Set the telemetry for the unified_analytics
|
||||
// package as well, the above will be removed once we have
|
||||
// fully transitioned to using the new package
|
||||
await globals.analytics.setTelemetry(true);
|
||||
}
|
||||
|
||||
|
|
|
@ -623,6 +623,12 @@ Future<int> exitWithHooks(int code, {required ShutdownHooks shutdownHooks}) asyn
|
|||
|
||||
final Completer<void> completer = Completer<void>();
|
||||
|
||||
// Allow any pending analytics events to send and close the http connection
|
||||
//
|
||||
// By default, we will wait 250 ms before canceling any pending events, we
|
||||
// can change the [delayDuration] in the close method if it needs to be changed
|
||||
await globals.analytics.close();
|
||||
|
||||
// Give the task / timer queue one cycle through before we hard exit.
|
||||
Timer.run(() {
|
||||
try {
|
||||
|
|
|
@ -218,7 +218,10 @@ Future<T> runInContext<T>(
|
|||
logger: globals.logger,
|
||||
botDetector: globals.botDetector,
|
||||
),
|
||||
Doctor: () => Doctor(logger: globals.logger),
|
||||
Doctor: () => Doctor(
|
||||
logger: globals.logger,
|
||||
clock: globals.systemClock,
|
||||
),
|
||||
DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
|
||||
EmulatorManager: () => EmulatorManager(
|
||||
java: globals.java,
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:process/process.dart';
|
||||
import 'package:unified_analytics/unified_analytics.dart';
|
||||
|
||||
import 'android/android_studio_validator.dart';
|
||||
import 'android/android_workflow.dart';
|
||||
|
@ -19,6 +20,7 @@ import 'base/net.dart';
|
|||
import 'base/os.dart';
|
||||
import 'base/platform.dart';
|
||||
import 'base/terminal.dart';
|
||||
import 'base/time.dart';
|
||||
import 'base/user_messages.dart';
|
||||
import 'base/utils.dart';
|
||||
import 'cache.dart';
|
||||
|
@ -229,9 +231,15 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
|
|||
class Doctor {
|
||||
Doctor({
|
||||
required Logger logger,
|
||||
}) : _logger = logger;
|
||||
required SystemClock clock,
|
||||
Analytics? analytics,
|
||||
}) : _logger = logger,
|
||||
_clock = clock,
|
||||
_analytics = analytics ?? globals.analytics;
|
||||
|
||||
final Logger _logger;
|
||||
final SystemClock _clock;
|
||||
final Analytics _analytics;
|
||||
|
||||
List<DoctorValidator> get validators {
|
||||
return DoctorValidatorsProvider._instance.validators;
|
||||
|
@ -375,6 +383,10 @@ class Doctor {
|
|||
bool doctorResult = true;
|
||||
int issues = 0;
|
||||
|
||||
// This timestamp will be used on the backend of GA4 to group each of the events that
|
||||
// were sent for each doctor validator and its result
|
||||
final int analyticsTimestamp = _clock.now().millisecondsSinceEpoch;
|
||||
|
||||
for (final ValidatorTask validatorTask in startedValidatorTasks ?? startValidatorTasks()) {
|
||||
final DoctorValidator validator = validatorTask.validator;
|
||||
final Status status = _logger.startSpinner(
|
||||
|
@ -404,6 +416,37 @@ class Doctor {
|
|||
break;
|
||||
}
|
||||
if (sendEvent) {
|
||||
if (validator is GroupedValidator) {
|
||||
for (int i = 0; i < validator.subValidators.length; i++) {
|
||||
final DoctorValidator subValidator = validator.subValidators[i];
|
||||
|
||||
// Ensure that all of the subvalidators in the group have
|
||||
// a corresponding subresult incase a validator crashed
|
||||
final ValidationResult subResult;
|
||||
try {
|
||||
subResult = validator.subResults[i];
|
||||
} on RangeError {
|
||||
continue;
|
||||
}
|
||||
|
||||
_analytics.send(Event.doctorValidatorResult(
|
||||
validatorName: subValidator.title,
|
||||
result: subResult.typeStr,
|
||||
statusInfo: subResult.statusInfo,
|
||||
partOfGroupedValidator: true,
|
||||
doctorInvocationId: analyticsTimestamp,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
_analytics.send(Event.doctorValidatorResult(
|
||||
validatorName: validator.title,
|
||||
result: result.typeStr,
|
||||
statusInfo: result.statusInfo,
|
||||
partOfGroupedValidator: false,
|
||||
doctorInvocationId: analyticsTimestamp,
|
||||
));
|
||||
}
|
||||
// TODO(eliasyishak): remove this after migrating from package:usage
|
||||
DoctorResultEvent(validator: validator, result: result).send();
|
||||
}
|
||||
|
||||
|
@ -727,8 +770,10 @@ class DeviceValidator extends DoctorValidator {
|
|||
class DoctorText {
|
||||
DoctorText(
|
||||
BufferLogger logger, {
|
||||
SystemClock? clock,
|
||||
@visibleForTesting Doctor? doctor,
|
||||
}) : _doctor = doctor ?? Doctor(logger: logger), _logger = logger;
|
||||
}) : _doctor = doctor ?? Doctor(logger: logger, clock: clock ?? globals.systemClock),
|
||||
_logger = logger;
|
||||
|
||||
final BufferLogger _logger;
|
||||
final Doctor _doctor;
|
||||
|
|
|
@ -304,6 +304,7 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
|||
|
||||
if ((topLevelResults[FlutterGlobalOptions.kSuppressAnalyticsFlag] as bool?) ?? false) {
|
||||
globals.flutterUsage.suppressAnalytics = true;
|
||||
globals.analytics.suppressTelemetry();
|
||||
}
|
||||
|
||||
globals.flutterVersion.ensureVersionFile();
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'package:flutter_tools/src/base/file_system.dart';
|
|||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/base/terminal.dart';
|
||||
import 'package:flutter_tools/src/base/time.dart';
|
||||
import 'package:flutter_tools/src/base/user_messages.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
|
@ -26,6 +27,7 @@ import 'package:flutter_tools/src/vscode/vscode.dart';
|
|||
import 'package:flutter_tools/src/vscode/vscode_validator.dart';
|
||||
import 'package:flutter_tools/src/web/workflow.dart';
|
||||
import 'package:test/fake.dart';
|
||||
import 'package:unified_analytics/unified_analytics.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
|
@ -166,7 +168,7 @@ void main() {
|
|||
|
||||
group('doctor with overridden validators', () {
|
||||
testUsingContext('validate non-verbose output format for run without issues', () async {
|
||||
final Doctor doctor = Doctor(logger: logger);
|
||||
final Doctor doctor = Doctor(logger: logger, clock: const SystemClock());
|
||||
expect(await doctor.diagnose(verbose: false), isTrue);
|
||||
expect(logger.statusText, equals(
|
||||
'Doctor summary (to see all details, run flutter doctor -v):\n'
|
||||
|
@ -190,7 +192,7 @@ void main() {
|
|||
});
|
||||
|
||||
testUsingContext('contains installed', () async {
|
||||
final Doctor doctor = Doctor(logger: logger);
|
||||
final Doctor doctor = Doctor(logger: logger, clock: const SystemClock());
|
||||
await doctor.diagnose(verbose: false);
|
||||
|
||||
expect(testUsage.events.length, 3);
|
||||
|
@ -508,7 +510,7 @@ void main() {
|
|||
|
||||
testUsingContext('PII separated, events only sent once', () async {
|
||||
final Doctor fakeDoctor = FakePiiDoctor(logger);
|
||||
final DoctorText doctorText = DoctorText(logger, doctor: fakeDoctor);
|
||||
final DoctorText doctorText = DoctorText(logger,doctor: fakeDoctor);
|
||||
const String expectedPiiText = '[✓] PII Validator\n'
|
||||
' • Contains PII path/to/username\n'
|
||||
'\n'
|
||||
|
@ -816,6 +818,183 @@ void main() {
|
|||
}, overrides: <Type, Generator>{
|
||||
AndroidWorkflow: () => FakeAndroidWorkflow(appliesToHostPlatform: false),
|
||||
});
|
||||
|
||||
group('Doctor events with unified_analytics', () {
|
||||
late FakeAnalytics fakeAnalytics;
|
||||
final FakeFlutterVersion fakeFlutterVersion = FakeFlutterVersion();
|
||||
final DateTime fakeDate = DateTime(1995, 3, 3);
|
||||
final SystemClock fakeSystemClock = SystemClock.fixed(fakeDate);
|
||||
|
||||
setUp(() {
|
||||
fakeAnalytics = getInitializedFakeAnalyticsInstance(
|
||||
fakeFlutterVersion: fakeFlutterVersion,
|
||||
fs: fs,
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext('ensure fake is being used and initialized', () {
|
||||
expect(fakeAnalytics.sentEvents.length, 0);
|
||||
expect(fakeAnalytics.okToSend, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
Analytics: () => fakeAnalytics,
|
||||
});
|
||||
|
||||
testUsingContext('contains installed', () async {
|
||||
final Doctor doctor = Doctor(logger: logger, clock: fakeSystemClock, analytics: fakeAnalytics);
|
||||
await doctor.diagnose(verbose: false);
|
||||
|
||||
expect(fakeAnalytics.sentEvents.length, 3);
|
||||
|
||||
// The event that should have been fired off during the doctor invocation
|
||||
final Event eventToFind = Event.doctorValidatorResult(
|
||||
validatorName: 'Passing Validator',
|
||||
result: 'installed',
|
||||
partOfGroupedValidator: false,
|
||||
doctorInvocationId: DateTime(1995, 3, 3).millisecondsSinceEpoch,
|
||||
statusInfo: 'with statusInfo',
|
||||
);
|
||||
expect(fakeAnalytics.sentEvents, contains(eventToFind));
|
||||
}, overrides: <Type, Generator>{
|
||||
DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(),
|
||||
});
|
||||
|
||||
testUsingContext('contains installed and partial', () async {
|
||||
await FakePassingDoctor(logger, clock: fakeSystemClock).diagnose(verbose: false);
|
||||
|
||||
expect(fakeAnalytics.sentEvents, hasLength(4));
|
||||
expect(fakeAnalytics.sentEvents, unorderedEquals(<Event>[
|
||||
Event.doctorValidatorResult(
|
||||
validatorName: 'Passing Validator',
|
||||
result: 'installed',
|
||||
partOfGroupedValidator: false,
|
||||
doctorInvocationId: fakeDate.millisecondsSinceEpoch,
|
||||
statusInfo: 'with statusInfo',
|
||||
),
|
||||
Event.doctorValidatorResult(
|
||||
validatorName: 'Partial Validator with only a Hint',
|
||||
result: 'partial',
|
||||
partOfGroupedValidator: false,
|
||||
doctorInvocationId: fakeDate.millisecondsSinceEpoch,
|
||||
),
|
||||
Event.doctorValidatorResult(
|
||||
validatorName: 'Partial Validator with Errors',
|
||||
result: 'partial',
|
||||
partOfGroupedValidator: false,
|
||||
doctorInvocationId: fakeDate.millisecondsSinceEpoch,
|
||||
),
|
||||
Event.doctorValidatorResult(
|
||||
validatorName: 'Another Passing Validator',
|
||||
result: 'installed',
|
||||
partOfGroupedValidator: false,
|
||||
doctorInvocationId: fakeDate.millisecondsSinceEpoch,
|
||||
statusInfo: 'with statusInfo',
|
||||
),
|
||||
]));
|
||||
}, overrides: <Type, Generator>{
|
||||
DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(),
|
||||
Analytics: () => fakeAnalytics,
|
||||
});
|
||||
|
||||
testUsingContext('contains installed, missing and partial', () async {
|
||||
await FakeDoctor(logger, clock: fakeSystemClock).diagnose(verbose: false);
|
||||
|
||||
expect(fakeAnalytics.sentEvents, hasLength(5));
|
||||
expect(fakeAnalytics.sentEvents, unorderedEquals(<Event>[
|
||||
Event.doctorValidatorResult(
|
||||
validatorName: 'Passing Validator',
|
||||
result: 'installed',
|
||||
partOfGroupedValidator: false,
|
||||
doctorInvocationId: fakeDate.millisecondsSinceEpoch,
|
||||
statusInfo: 'with statusInfo',
|
||||
),
|
||||
Event.doctorValidatorResult(
|
||||
validatorName: 'Missing Validator',
|
||||
result: 'missing',
|
||||
partOfGroupedValidator: false,
|
||||
doctorInvocationId: fakeDate.millisecondsSinceEpoch,
|
||||
),
|
||||
Event.doctorValidatorResult(
|
||||
validatorName: 'Not Available Validator',
|
||||
result: 'notAvailable',
|
||||
partOfGroupedValidator: false,
|
||||
doctorInvocationId: fakeDate.millisecondsSinceEpoch,
|
||||
),
|
||||
Event.doctorValidatorResult(
|
||||
validatorName: 'Partial Validator with only a Hint',
|
||||
result: 'partial',
|
||||
partOfGroupedValidator: false,
|
||||
doctorInvocationId: fakeDate.millisecondsSinceEpoch,
|
||||
),
|
||||
Event.doctorValidatorResult(
|
||||
validatorName: 'Partial Validator with Errors',
|
||||
result: 'partial',
|
||||
partOfGroupedValidator: false,
|
||||
doctorInvocationId: fakeDate.millisecondsSinceEpoch,
|
||||
),
|
||||
]));
|
||||
}, overrides: <Type, Generator>{
|
||||
DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(),
|
||||
Analytics: () => fakeAnalytics,
|
||||
});
|
||||
|
||||
testUsingContext('events for grouped validators are properly decomposed', () async {
|
||||
await FakeGroupedDoctor(logger, clock: fakeSystemClock).diagnose(verbose: false);
|
||||
|
||||
expect(fakeAnalytics.sentEvents, hasLength(4));
|
||||
expect(fakeAnalytics.sentEvents, unorderedEquals(<Event>[
|
||||
Event.doctorValidatorResult(
|
||||
validatorName: 'Category 1',
|
||||
result: 'installed',
|
||||
partOfGroupedValidator: true,
|
||||
doctorInvocationId: fakeDate.millisecondsSinceEpoch,
|
||||
),
|
||||
Event.doctorValidatorResult(
|
||||
validatorName: 'Category 1',
|
||||
result: 'installed',
|
||||
partOfGroupedValidator: true,
|
||||
doctorInvocationId: fakeDate.millisecondsSinceEpoch,
|
||||
),
|
||||
Event.doctorValidatorResult(
|
||||
validatorName: 'Category 2',
|
||||
result: 'installed',
|
||||
partOfGroupedValidator: true,
|
||||
doctorInvocationId: fakeDate.millisecondsSinceEpoch,
|
||||
),
|
||||
Event.doctorValidatorResult(
|
||||
validatorName: 'Category 2',
|
||||
result: 'missing',
|
||||
partOfGroupedValidator: true,
|
||||
doctorInvocationId: fakeDate.millisecondsSinceEpoch,
|
||||
),
|
||||
]));
|
||||
}, overrides: <Type, Generator>{
|
||||
DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(),
|
||||
Analytics: () => fakeAnalytics,
|
||||
});
|
||||
|
||||
testUsingContext('grouped validator subresult and subvalidators different lengths', () async {
|
||||
final FakeGroupedDoctorWithCrash fakeDoctor = FakeGroupedDoctorWithCrash(logger, clock: fakeSystemClock);
|
||||
await fakeDoctor.diagnose(verbose: false);
|
||||
|
||||
expect(fakeDoctor.validators, hasLength(1));
|
||||
expect(fakeDoctor.validators.first.runtimeType == FakeGroupedValidatorWithCrash, true);
|
||||
expect(fakeAnalytics.sentEvents, hasLength(0));
|
||||
|
||||
// Attempt to send a random event to ensure that the
|
||||
// analytics package is still working, despite not sending
|
||||
// above (as expected)
|
||||
final Event testEvent = Event.analyticsCollectionEnabled(status: true);
|
||||
fakeAnalytics.send(testEvent);
|
||||
expect(fakeAnalytics.sentEvents, hasLength(1));
|
||||
expect(fakeAnalytics.sentEvents, contains(testEvent));
|
||||
}, overrides: <Type, Generator>{Analytics: () => fakeAnalytics});
|
||||
|
||||
testUsingContext('sending events can be skipped', () async {
|
||||
await FakePassingDoctor(logger).diagnose(verbose: false, sendEvent: false);
|
||||
expect(fakeAnalytics.sentEvents, isEmpty);
|
||||
}
|
||||
,overrides: <Type, Generator>{Analytics: () => fakeAnalytics});
|
||||
});
|
||||
}
|
||||
|
||||
class FakeAndroidWorkflow extends Fake implements AndroidWorkflow {
|
||||
|
@ -952,7 +1131,8 @@ class AsyncCrashingValidator extends DoctorValidator {
|
|||
|
||||
/// A doctor that fails with a missing [ValidationResult].
|
||||
class FakeDoctor extends Doctor {
|
||||
FakeDoctor(Logger logger) : super(logger: logger);
|
||||
FakeDoctor(Logger logger, {super.clock = const SystemClock()})
|
||||
: super(logger: logger);
|
||||
|
||||
@override
|
||||
late final List<DoctorValidator> validators = <DoctorValidator>[
|
||||
|
@ -966,7 +1146,8 @@ class FakeDoctor extends Doctor {
|
|||
|
||||
/// A doctor that should pass, but still has issues in some categories.
|
||||
class FakePassingDoctor extends Doctor {
|
||||
FakePassingDoctor(Logger logger) : super(logger: logger);
|
||||
FakePassingDoctor(Logger logger, {super.clock = const SystemClock()})
|
||||
: super(logger: logger);
|
||||
|
||||
@override
|
||||
late final List<DoctorValidator> validators = <DoctorValidator>[
|
||||
|
@ -980,7 +1161,8 @@ class FakePassingDoctor extends Doctor {
|
|||
/// A doctor that should pass, but still has 1 issue to test the singular of
|
||||
/// categories.
|
||||
class FakeSinglePassingDoctor extends Doctor {
|
||||
FakeSinglePassingDoctor(Logger logger) : super(logger: logger);
|
||||
FakeSinglePassingDoctor(Logger logger, {super.clock = const SystemClock()})
|
||||
: super(logger: logger);
|
||||
|
||||
@override
|
||||
late final List<DoctorValidator> validators = <DoctorValidator>[
|
||||
|
@ -990,7 +1172,8 @@ class FakeSinglePassingDoctor extends Doctor {
|
|||
|
||||
/// A doctor that passes and has no issues anywhere.
|
||||
class FakeQuietDoctor extends Doctor {
|
||||
FakeQuietDoctor(Logger logger) : super(logger: logger);
|
||||
FakeQuietDoctor(Logger logger, {super.clock = const SystemClock()})
|
||||
: super(logger: logger);
|
||||
|
||||
@override
|
||||
late final List<DoctorValidator> validators = <DoctorValidator>[
|
||||
|
@ -1003,7 +1186,8 @@ class FakeQuietDoctor extends Doctor {
|
|||
|
||||
/// A doctor that passes and contains PII that can be hidden.
|
||||
class FakePiiDoctor extends Doctor {
|
||||
FakePiiDoctor(Logger logger) : super(logger: logger);
|
||||
FakePiiDoctor(Logger logger, {super.clock = const SystemClock()})
|
||||
: super(logger: logger);
|
||||
|
||||
@override
|
||||
late final List<DoctorValidator> validators = <DoctorValidator>[
|
||||
|
@ -1013,7 +1197,8 @@ class FakePiiDoctor extends Doctor {
|
|||
|
||||
/// A doctor with a validator that throws an exception.
|
||||
class FakeCrashingDoctor extends Doctor {
|
||||
FakeCrashingDoctor(Logger logger) : super(logger: logger);
|
||||
FakeCrashingDoctor(Logger logger, {super.clock = const SystemClock()})
|
||||
: super(logger: logger);
|
||||
|
||||
@override
|
||||
late final List<DoctorValidator> validators = <DoctorValidator>[
|
||||
|
@ -1027,7 +1212,8 @@ class FakeCrashingDoctor extends Doctor {
|
|||
|
||||
/// A doctor with a validator that will never finish.
|
||||
class FakeAsyncStuckDoctor extends Doctor {
|
||||
FakeAsyncStuckDoctor(Logger logger) : super(logger: logger);
|
||||
FakeAsyncStuckDoctor(Logger logger, {super.clock = const SystemClock()})
|
||||
: super(logger: logger);
|
||||
|
||||
@override
|
||||
late final List<DoctorValidator> validators = <DoctorValidator>[
|
||||
|
@ -1041,7 +1227,9 @@ class FakeAsyncStuckDoctor extends Doctor {
|
|||
|
||||
/// A doctor with a validator that throws an exception.
|
||||
class FakeAsyncCrashingDoctor extends Doctor {
|
||||
FakeAsyncCrashingDoctor(this._time, Logger logger) : super(logger: logger);
|
||||
FakeAsyncCrashingDoctor(this._time, Logger logger,
|
||||
{super.clock = const SystemClock()})
|
||||
: super(logger: logger);
|
||||
|
||||
final FakeAsync _time;
|
||||
|
||||
|
@ -1121,7 +1309,8 @@ class PassingGroupedValidatorWithStatus extends DoctorValidator {
|
|||
|
||||
/// A doctor that has two groups of two validators each.
|
||||
class FakeGroupedDoctor extends Doctor {
|
||||
FakeGroupedDoctor(Logger logger) : super(logger: logger);
|
||||
FakeGroupedDoctor(Logger logger, {super.clock = const SystemClock()})
|
||||
: super(logger: logger);
|
||||
|
||||
@override
|
||||
late final List<DoctorValidator> validators = <DoctorValidator>[
|
||||
|
@ -1136,8 +1325,41 @@ class FakeGroupedDoctor extends Doctor {
|
|||
];
|
||||
}
|
||||
|
||||
/// Fake grouped doctor that is intended to be used with [FakeGroupedValidatorWithCrash].
|
||||
class FakeGroupedDoctorWithCrash extends Doctor {
|
||||
FakeGroupedDoctorWithCrash(Logger logger, {super.clock = const SystemClock()})
|
||||
: super(logger: logger);
|
||||
|
||||
@override
|
||||
late final List<DoctorValidator> validators = <DoctorValidator>[
|
||||
FakeGroupedValidatorWithCrash(<DoctorValidator>[
|
||||
PassingGroupedValidator('Category 1'),
|
||||
PassingGroupedValidator('Category 1'),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/// This extended grouped validator will have a list of sub validators
|
||||
/// provided in the constructor, but it will have no [subResults] in the
|
||||
/// list which simulates what happens if a validator crashes.
|
||||
///
|
||||
/// Usually, the grouped validators have 2 lists, a [subValidators] and
|
||||
/// a [subResults] list, and if nothing crashes, those 2 lists will have the
|
||||
/// same length. This fake is simulating what happens when the validators
|
||||
/// crash and results in no results getting returned.
|
||||
class FakeGroupedValidatorWithCrash extends GroupedValidator {
|
||||
FakeGroupedValidatorWithCrash(super.subValidators);
|
||||
|
||||
@override
|
||||
List<ValidationResult> get subResults => <ValidationResult>[];
|
||||
|
||||
}
|
||||
|
||||
class FakeGroupedDoctorWithStatus extends Doctor {
|
||||
FakeGroupedDoctorWithStatus(Logger logger) : super(logger: logger);
|
||||
FakeGroupedDoctorWithStatus(Logger logger,
|
||||
{super.clock = const SystemClock()})
|
||||
: super(logger: logger);
|
||||
|
||||
@override
|
||||
late final List<DoctorValidator> validators = <DoctorValidator>[
|
||||
|
@ -1151,7 +1373,9 @@ class FakeGroupedDoctorWithStatus extends Doctor {
|
|||
/// A doctor that takes any two validators. Used to check behavior when
|
||||
/// merging ValidationTypes (installed, missing, partial).
|
||||
class FakeSmallGroupDoctor extends Doctor {
|
||||
FakeSmallGroupDoctor(Logger logger, DoctorValidator val1, DoctorValidator val2)
|
||||
FakeSmallGroupDoctor(
|
||||
Logger logger, DoctorValidator val1, DoctorValidator val2,
|
||||
{super.clock = const SystemClock()})
|
||||
: validators = <DoctorValidator>[GroupedValidator(<DoctorValidator>[val1, val2])],
|
||||
super(logger: logger);
|
||||
|
||||
|
|
|
@ -18,12 +18,12 @@ import 'package:flutter_tools/src/globals.dart' as globals;
|
|||
import 'package:flutter_tools/src/reporting/crash_reporting.dart';
|
||||
import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||
import 'package:flutter_tools/src/runner/flutter_command.dart';
|
||||
import 'package:test/fake.dart';
|
||||
import 'package:unified_analytics/unified_analytics.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/fake_http_client.dart';
|
||||
import '../../src/fakes.dart';
|
||||
|
||||
const String kCustomBugInstructions = 'These are instructions to report with a custom bug tracker.';
|
||||
|
||||
|
@ -317,13 +317,24 @@ void main() {
|
|||
});
|
||||
|
||||
group('unified_analytics', () {
|
||||
late FakeAnalytics fakeAnalytics;
|
||||
late MemoryFileSystem fs;
|
||||
|
||||
setUp(() {
|
||||
fs = MemoryFileSystem.test();
|
||||
|
||||
fakeAnalytics = getInitializedFakeAnalyticsInstance(
|
||||
fs: fs,
|
||||
fakeFlutterVersion: FakeFlutterVersion(),
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext(
|
||||
'runner disable telemetry with flag',
|
||||
() async {
|
||||
io.setExitFunctionForTests((int exitCode) {});
|
||||
|
||||
expect(globals.analytics.telemetryEnabled, true);
|
||||
expect(globals.analytics.shouldShowMessage, true);
|
||||
|
||||
await runner.run(
|
||||
<String>['--disable-analytics'],
|
||||
|
@ -336,7 +347,7 @@ void main() {
|
|||
expect(globals.analytics.telemetryEnabled, false);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
Analytics: () => FakeAnalytics(),
|
||||
Analytics: () => fakeAnalytics,
|
||||
FileSystem: () => MemoryFileSystem.test(),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
},
|
||||
|
@ -347,8 +358,17 @@ void main() {
|
|||
() async {
|
||||
io.setExitFunctionForTests((int exitCode) {});
|
||||
|
||||
expect(globals.analytics.telemetryEnabled, true);
|
||||
|
||||
await runner.run(
|
||||
<String>['--disable-analytics'],
|
||||
() => <FlutterCommand>[],
|
||||
// This flutterVersion disables crash reporting.
|
||||
flutterVersion: '[user-branch]/',
|
||||
shutdownHooks: ShutdownHooks(),
|
||||
);
|
||||
|
||||
expect(globals.analytics.telemetryEnabled, false);
|
||||
expect(globals.analytics.shouldShowMessage, false);
|
||||
|
||||
await runner.run(
|
||||
<String>['--enable-analytics'],
|
||||
|
@ -361,7 +381,7 @@ void main() {
|
|||
expect(globals.analytics.telemetryEnabled, true);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
Analytics: () => FakeAnalytics(fakeTelemetryStatusOverride: false),
|
||||
Analytics: () => fakeAnalytics,
|
||||
FileSystem: () => MemoryFileSystem.test(),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
},
|
||||
|
@ -373,7 +393,6 @@ void main() {
|
|||
io.setExitFunctionForTests((int exitCode) {});
|
||||
|
||||
expect(globals.analytics.telemetryEnabled, true);
|
||||
expect(globals.analytics.shouldShowMessage, true);
|
||||
|
||||
final int exitCode = await runner.run(
|
||||
<String>[
|
||||
|
@ -392,7 +411,7 @@ void main() {
|
|||
reason: 'Should not have changed from initialization');
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
Analytics: () => FakeAnalytics(),
|
||||
Analytics: () => fakeAnalytics,
|
||||
FileSystem: () => MemoryFileSystem.test(),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
},
|
||||
|
@ -536,38 +555,3 @@ class WaitingCrashReporter implements CrashReporter {
|
|||
return _future;
|
||||
}
|
||||
}
|
||||
|
||||
/// A fake [Analytics] that will be used to test
|
||||
/// the --disable-analytics flag
|
||||
class FakeAnalytics extends Fake implements Analytics {
|
||||
|
||||
FakeAnalytics({bool fakeTelemetryStatusOverride = true})
|
||||
: _fakeTelemetryStatus = fakeTelemetryStatusOverride,
|
||||
_fakeShowMessage = fakeTelemetryStatusOverride;
|
||||
|
||||
// Both of the members below can be initialized with [fakeTelemetryStatusOverride]
|
||||
// because if we pass in false for the status, that means we can also
|
||||
// assume the message has been shown before
|
||||
bool _fakeTelemetryStatus;
|
||||
bool _fakeShowMessage;
|
||||
|
||||
@override
|
||||
String get getConsentMessage => 'message';
|
||||
|
||||
@override
|
||||
bool get shouldShowMessage => _fakeShowMessage;
|
||||
|
||||
@override
|
||||
void clientShowedMessage() {
|
||||
_fakeShowMessage = false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setTelemetry(bool reportingBool) {
|
||||
_fakeTelemetryStatus = reportingBool;
|
||||
return Future<void>.value();
|
||||
}
|
||||
|
||||
@override
|
||||
bool get telemetryEnabled => _fakeTelemetryStatus;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/reporting/unified_analytics.dart';
|
||||
import 'package:unified_analytics/src/enums.dart';
|
||||
import 'package:unified_analytics/unified_analytics.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
|
@ -13,42 +12,17 @@ import '../src/fakes.dart';
|
|||
|
||||
void main() {
|
||||
const String userBranch = 'abc123';
|
||||
const String homeDirectoryName = 'home';
|
||||
const DashTool tool = DashTool.flutterTool;
|
||||
|
||||
late FileSystem fs;
|
||||
late Directory home;
|
||||
late FakeAnalytics analyticsOverride;
|
||||
|
||||
setUp(() {
|
||||
fs = MemoryFileSystem.test();
|
||||
home = fs.directory(homeDirectoryName);
|
||||
|
||||
// Prepare the tests by "onboarding" the tool into the package
|
||||
// by invoking the [clientShowedMessage] method for the provided
|
||||
// [tool]
|
||||
final FakeAnalytics initialAnalytics = FakeAnalytics(
|
||||
tool: tool,
|
||||
homeDirectory: home,
|
||||
dartVersion: '3.0.0',
|
||||
platform: DevicePlatform.macos,
|
||||
analyticsOverride = getInitializedFakeAnalyticsInstance(
|
||||
fs: fs,
|
||||
surveyHandler: SurveyHandler(
|
||||
homeDirectory: home,
|
||||
fs: fs,
|
||||
),
|
||||
);
|
||||
initialAnalytics.clientShowedMessage();
|
||||
|
||||
analyticsOverride = FakeAnalytics(
|
||||
tool: tool,
|
||||
homeDirectory: home,
|
||||
dartVersion: '3.0.0',
|
||||
platform: DevicePlatform.macos,
|
||||
fs: fs,
|
||||
surveyHandler: SurveyHandler(
|
||||
homeDirectory: home,
|
||||
fs: fs,
|
||||
fakeFlutterVersion: FakeFlutterVersion(
|
||||
branch: userBranch,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
@ -135,5 +109,17 @@ void main() {
|
|||
);
|
||||
expect(analytics, isA<NoOpAnalytics>());
|
||||
});
|
||||
|
||||
testWithoutContext('Suppression prevents events from being sent', () {
|
||||
expect(analyticsOverride.okToSend, true);
|
||||
analyticsOverride.send(Event.surveyShown(surveyId: 'surveyId'));
|
||||
expect(analyticsOverride.sentEvents, hasLength(1));
|
||||
|
||||
analyticsOverride.suppressTelemetry();
|
||||
expect(analyticsOverride.okToSend, false);
|
||||
analyticsOverride.send(Event.surveyShown(surveyId: 'surveyId'));
|
||||
|
||||
expect(analyticsOverride.sentEvents, hasLength(1));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,6 +16,10 @@ import 'package:meta/meta.dart';
|
|||
import 'package:path/path.dart' as path; // flutter_ignore: package_path_import
|
||||
import 'package:test/test.dart' as test_package show test;
|
||||
import 'package:test/test.dart' hide test;
|
||||
import 'package:unified_analytics/src/enums.dart';
|
||||
import 'package:unified_analytics/unified_analytics.dart';
|
||||
|
||||
import 'fakes.dart';
|
||||
|
||||
export 'package:path/path.dart' show Context; // flutter_ignore: package_path_import
|
||||
export 'package:test/test.dart' hide isInstanceOf, test;
|
||||
|
@ -305,3 +309,38 @@ class FileExceptionHandler {
|
|||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
/// This method is required to fetch an instance of [FakeAnalytics]
|
||||
/// because there is initialization logic that is required. An initial
|
||||
/// instance will first be created and will let package:unified_analytics
|
||||
/// know that the consent message has been shown. After confirming on the first
|
||||
/// instance, then a second instance will be generated and returned. This second
|
||||
/// instance will be cleared to send events.
|
||||
FakeAnalytics getInitializedFakeAnalyticsInstance({
|
||||
required FileSystem fs,
|
||||
required FakeFlutterVersion fakeFlutterVersion,
|
||||
}) {
|
||||
final Directory homeDirectory = fs.directory('/');
|
||||
final FakeAnalytics initialAnalytics = FakeAnalytics(
|
||||
tool: DashTool.flutterTool,
|
||||
homeDirectory: homeDirectory,
|
||||
dartVersion: fakeFlutterVersion.dartSdkVersion,
|
||||
platform: DevicePlatform.linux,
|
||||
fs: fs,
|
||||
surveyHandler: SurveyHandler(homeDirectory: homeDirectory, fs: fs),
|
||||
flutterChannel: fakeFlutterVersion.channel,
|
||||
flutterVersion: fakeFlutterVersion.getVersionString(),
|
||||
);
|
||||
initialAnalytics.clientShowedMessage();
|
||||
|
||||
return FakeAnalytics(
|
||||
tool: DashTool.flutterTool,
|
||||
homeDirectory: homeDirectory,
|
||||
dartVersion: fakeFlutterVersion.dartSdkVersion,
|
||||
platform: DevicePlatform.linux,
|
||||
fs: fs,
|
||||
surveyHandler: SurveyHandler(homeDirectory: homeDirectory, fs: fs),
|
||||
flutterChannel: fakeFlutterVersion.channel,
|
||||
flutterVersion: fakeFlutterVersion.getVersionString(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'package:flutter_tools/src/base/process.dart';
|
|||
import 'package:flutter_tools/src/base/signals.dart';
|
||||
import 'package:flutter_tools/src/base/template.dart';
|
||||
import 'package:flutter_tools/src/base/terminal.dart';
|
||||
import 'package:flutter_tools/src/base/time.dart';
|
||||
import 'package:flutter_tools/src/base/version.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/context_runner.dart';
|
||||
|
@ -291,7 +292,8 @@ class FakeAndroidLicenseValidator extends Fake implements AndroidLicenseValidato
|
|||
}
|
||||
|
||||
class FakeDoctor extends Doctor {
|
||||
FakeDoctor(Logger logger) : super(logger: logger);
|
||||
FakeDoctor(Logger logger, {super.clock = const SystemClock()})
|
||||
: super(logger: logger);
|
||||
|
||||
// True for testing.
|
||||
@override
|
||||
|
|
Loading…
Reference in a new issue