Unverified Commit b926c7b6 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] rewrite Usage.test to capture objects instead of print logs (#74829)

parent cf54d6a2
......@@ -296,7 +296,7 @@ class XcodeProjectInterpreter {
fileSystem: MemoryFileSystem.test(),
platform: platform,
processManager: processManager,
usage: Usage.test(),
usage: TestUsage(),
logger: BufferLogger.test(),
terminal: Terminal.test(),
majorVersion: majorVersion,
......
......@@ -91,8 +91,6 @@ abstract class Usage {
runningOnBot: runningOnBot,
firstRunMessenger: firstRunMessenger);
factory Usage.test() => _DefaultUsage.test();
/// Uses the global [Usage] instance to send a 'command' to analytics.
static void command(String command, {
Map<CustomDimensions, Object> parameters,
......@@ -276,12 +274,6 @@ class _DefaultUsage implements Usage {
_analytics.analyticsOpt = AnalyticsOpt.optOut;
}
_DefaultUsage.test() :
_suppressAnalytics = false,
_analytics = AnalyticsMock(true),
firstRunMessenger = null,
_clock = SystemClock.fixed(DateTime(2020, 10, 8));
Analytics _analytics;
final FirstRunMessenger firstRunMessenger;
......@@ -469,3 +461,163 @@ class LogToFileAnalytics extends AnalyticsMock {
_sessionValues[param] = value.toString();
}
}
/// Create a testing Usage instance.
///
/// All sent events, exceptions, timings, and pages are
/// buffered on the object and can be inspected later.
@visibleForTesting
class TestUsage implements Usage {
final List<TestUsageCommand> commands = <TestUsageCommand>[];
final List<TestUsageEvent> events = <TestUsageEvent>[];
final List<dynamic> exceptions = <dynamic>[];
final List<TestTimingEvent> timings = <TestTimingEvent>[];
@override
bool enabled = true;
@override
bool suppressAnalytics = false;
@override
String get clientId => 'test-client';
@override
Future<void> ensureAnalyticsSent() {
throw UnimplementedError();
}
@override
Stream<Map<String, dynamic>> get onSend => throw UnimplementedError();
@override
void printWelcome() { }
@override
void sendCommand(String command, {Map<String, String> parameters}) {
commands.add(TestUsageCommand(command, parameters: parameters));
}
@override
void sendEvent(String category, String parameter, {String label, int value, Map<String, String> parameters}) {
events.add(TestUsageEvent(category, parameter, label: label, value: value, parameters: parameters));
}
@override
void sendException(dynamic exception) {
exceptions.add(exception);
}
@override
void sendTiming(String category, String variableName, Duration duration, {String label}) {
timings.add(TestTimingEvent(category, variableName, duration, label: label));
}
}
@visibleForTesting
@immutable
class TestUsageCommand {
const TestUsageCommand(this.command, {this.parameters});
final String command;
final Map<String, String> parameters;
@override
bool operator ==(Object other) {
return other is TestUsageCommand &&
other.command == command &&
_mapsEqual(other.parameters, parameters);
}
@override
int get hashCode => command.hashCode ^ parameters.hashCode;
@override
String toString() => 'TestUsageCommand($command, parameters:$parameters)';
}
@visibleForTesting
@immutable
class TestUsageEvent {
const TestUsageEvent(this.category, this.parameter, {this.label, this.value, this.parameters});
final String category;
final String parameter;
final String label;
final int value;
final Map<String, String> parameters;
@override
bool operator ==(Object other) {
return other is TestUsageEvent &&
other.category == category &&
other.parameter == parameter &&
other.label == label &&
other.value == value &&
_mapsEqual(other.parameters, parameters);
}
@override
int get hashCode => category.hashCode ^
parameter.hashCode ^
label.hashCode ^
value.hashCode ^
parameters.hashCode;
@override
String toString() => 'TestUsageEvent($category, $parameter, label:$label, value:$value, parameters:$parameters)';
}
@visibleForTesting
@immutable
class TestTimingEvent {
const TestTimingEvent(this.category, this.variableName, this.duration, {this.label});
final String category;
final String variableName;
final Duration duration;
final String label;
@override
bool operator ==(Object other) {
return other is TestTimingEvent &&
other.category == category &&
other.variableName == variableName &&
other.duration == duration &&
other.label == label;
}
@override
int get hashCode => category.hashCode ^
variableName.hashCode ^
duration.hashCode ^
label.hashCode;
@override
String toString() => 'TestTimingEvent($category, $variableName, $duration, label:$label)';
}
bool _mapsEqual(Map<dynamic, dynamic> a, Map<dynamic, dynamic> b) {
if (a == b) {
return true;
}
if (a == null || b == null) {
return false;
}
if (a.length != b.length) {
return false;
}
for (final dynamic k in a.keys) {
final dynamic bValue = b[k];
if (bValue == null && !b.containsKey(k)) {
return false;
}
if (bValue != a[k]) {
return false;
}
}
return true;
}
......@@ -48,7 +48,7 @@ final Platform notMacosPlatform = FakePlatform(
void main() {
FileSystem fileSystem;
Usage usage;
TestUsage usage;
BufferLogger logger;
setUpAll(() {
......@@ -57,7 +57,7 @@ void main() {
setUp(() {
fileSystem = MemoryFileSystem.test();
usage = Usage.test();
usage = TestUsage();
logger = BufferLogger.test();
});
......@@ -242,16 +242,15 @@ void main() {
..createSync(recursive: true)
..writeAsBytesSync(List<int>.generate(10000, (int index) => 0));
// Capture Usage.test() events.
final StringBuffer buffer = await capturedConsolePrint(() =>
createTestCommandRunner(command).run(
const <String>['build', 'ipa', '--no-pub', '--analyze-size']
)
await createTestCommandRunner(command).run(
const <String>['build', 'ipa', '--no-pub', '--analyze-size']
);
expect(testLogger.statusText, contains('A summary of your iOS bundle analysis can be found at'));
expect(testLogger.statusText, contains('flutter pub global activate devtools; flutter pub global run devtools --appSizeBase='));
expect(buffer.toString(), contains('event {category: code-size-analysis, action: ios, label: null, value: null, cd33: '));
expect(usage.events, contains(
const TestUsageEvent('code-size-analysis', 'ios'),
));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
......
......@@ -47,12 +47,12 @@ void main() {
FileSystem fileSystem;
ProcessManager processManager;
Usage usage;
TestUsage usage;
setUp(() {
fileSystem = MemoryFileSystem.test();
Cache.flutterRoot = _kTestFlutterRoot;
usage = Usage.test();
usage = TestUsage();
});
// Creates the mock files necessary to look like a Flutter project.
......@@ -457,16 +457,15 @@ set(BINARY_NAME "fizz_bar")
..createSync(recursive: true)
..writeAsBytesSync(List<int>.filled(10000, 0));
// Capture Usage.test() events.
final StringBuffer buffer = await capturedConsolePrint(() =>
createTestCommandRunner(command).run(
const <String>['build', 'linux', '--no-pub', '--analyze-size']
)
await createTestCommandRunner(command).run(
const <String>['build', 'linux', '--no-pub', '--analyze-size']
);
expect(testLogger.statusText, contains('A summary of your Linux bundle analysis can be found at'));
expect(testLogger.statusText, contains('flutter pub global activate devtools; flutter pub global run devtools --appSizeBase='));
expect(buffer.toString(), contains('event {category: code-size-analysis, action: linux, label: null, value: null, cd33:'));
expect(usage.events, contains(
const TestUsageEvent('code-size-analysis', 'linux'),
));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
......
......@@ -54,7 +54,7 @@ final Platform notMacosPlatform = FakePlatform(
void main() {
FileSystem fileSystem;
Usage usage;
TestUsage usage;
setUpAll(() {
Cache.disableLocking();
......@@ -62,7 +62,7 @@ void main() {
setUp(() {
fileSystem = MemoryFileSystem.test();
usage = Usage.test();
usage = TestUsage();
});
// Sets up the minimal mock project files necessary to look like a Flutter project.
......@@ -338,16 +338,15 @@ void main() {
..createSync(recursive: true)
..writeAsBytesSync(List<int>.generate(10000, (int index) => 0));
// Capture Usage.test() events.
final StringBuffer buffer = await capturedConsolePrint(() =>
createTestCommandRunner(command).run(
const <String>['build', 'macos', '--no-pub', '--analyze-size']
)
await createTestCommandRunner(command).run(
const <String>['build', 'macos', '--no-pub', '--analyze-size']
);
expect(testLogger.statusText, contains('A summary of your macOS bundle analysis can be found at'));
expect(testLogger.statusText, contains('flutter pub global activate devtools; flutter pub global run devtools --appSizeBase='));
expect(buffer.toString(), contains('event {category: code-size-analysis, action: macos, label: null, value: null, cd33:'));
expect(usage.events, contains(
const TestUsageEvent('code-size-analysis', 'macos'),
));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
......
......@@ -45,7 +45,7 @@ void main() {
ProcessManager processManager;
MockVisualStudio mockVisualStudio;
Usage usage;
TestUsage usage;
setUpAll(() {
Cache.disableLocking();
......@@ -55,7 +55,7 @@ void main() {
fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
Cache.flutterRoot = flutterRoot;
mockVisualStudio = MockVisualStudio();
usage = Usage.test();
usage = TestUsage();
});
// Creates the mock files necessary to look like a Flutter project.
......@@ -401,16 +401,15 @@ C:\foo\windows\runner\main.cpp(17,1): error C2065: 'Baz': undeclared identifier
}),
]);
// Capture Usage.test() events.
final StringBuffer buffer = await capturedConsolePrint(() =>
createTestCommandRunner(command).run(
const <String>['windows', '--no-pub', '--analyze-size']
)
await createTestCommandRunner(command).run(
const <String>['windows', '--no-pub', '--analyze-size']
);
expect(testLogger.statusText, contains('A summary of your Windows bundle analysis can be found at'));
expect(testLogger.statusText, contains('flutter pub global activate devtools; flutter pub global run devtools --appSizeBase='));
expect(buffer.toString(), contains('event {category: code-size-analysis, action: windows, label: null, value: null, cd33:'));
expect(usage.events, contains(
const TestUsageEvent('code-size-analysis', 'windows'),
));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
FileSystem: () => fileSystem,
......
......@@ -141,13 +141,13 @@ void main() {
Artifacts artifacts;
MockCache mockCache;
MockProcessManager mockProcessManager;
Usage usage;
TestUsage usage;
Directory tempDir;
setUp(() {
artifacts = Artifacts.test();
mockCache = MockCache();
usage = Usage.test();
usage = TestUsage();
fs = MemoryFileSystem.test();
mockProcessManager = MockProcessManager();
......@@ -368,19 +368,18 @@ void main() {
..writeAsStringSync('# Hello, World');
globals.fs.currentDirectory = tempDir;
// Capture Usage.test() events.
final StringBuffer buffer = await capturedConsolePrint(() =>
expectToolExitLater(createTestCommandRunner(command).run(<String>[
'run',
'--no-pub',
'--no-hot',
]), isNull)
);
// Allow any CustomDimensions.localTime (cd33) timestamp.
final RegExp usageRegexp = RegExp(
'screenView {cd3: false, cd4: ios, cd22: iOS 13, cd23: debug, cd18: false, cd15: swift, cd31: false, cd33: .*, viewName: run'
);
expect(buffer.toString(), matches(usageRegexp));
await expectToolExitLater(createTestCommandRunner(command).run(<String>[
'run',
'--no-pub',
'--no-hot',
]), isNull);
expect(usage.commands, contains(
const TestUsageCommand('run', parameters: <String, String>{
'cd3': 'false', 'cd4': 'ios', 'cd22': 'iOS 13',
'cd23': 'debug', 'cd18': 'false', 'cd15': 'swift', 'cd31': 'false',
}
)));
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
Cache: () => mockCache,
......
......@@ -226,7 +226,7 @@ void main() {
platform: platform,
processManager: fakeProcessManager,
terminal: terminal,
usage: Usage.test(),
usage: TestUsage(),
);
fileSystem.file(xcodebuild).deleteSync();
......@@ -430,7 +430,7 @@ void main() {
platform: platform,
processManager: fakeProcessManager,
terminal: terminal,
usage: Usage.test(),
usage: TestUsage(),
);
expect(await xcodeProjectInterpreter.getInfo(workingDirectory), isNotNull);
......@@ -457,7 +457,7 @@ void main() {
platform: platform,
processManager: fakeProcessManager,
terminal: terminal,
usage: Usage.test(),
usage: TestUsage(),
);
expect(
......
......@@ -34,7 +34,7 @@ void main() {
CocoaPods cocoaPodsUnderTest;
InvokeProcess resultOfPodVersion;
BufferLogger logger;
Usage usage;
TestUsage usage;
void pretendPodVersionFails() {
resultOfPodVersion = () async => exitsWithError();
......@@ -71,7 +71,7 @@ void main() {
projectUnderTest = FlutterProject.fromDirectory(fileSystem.directory('project'));
projectUnderTest.ios.xcodeProject.createSync(recursive: true);
projectUnderTest.macos.xcodeProject.createSync(recursive: true);
usage = Usage.test();
usage = TestUsage();
cocoaPodsUnderTest = CocoaPods(
fileSystem: fileSystem,
processManager: mockProcessManager,
......@@ -486,21 +486,18 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
'LoadError - dlsym(0x7fbbeb6837d0, Init_ffi_c): symbol not found - /Library/Ruby/Gems/2.6.0/gems/ffi-1.13.1/lib/ffi_c.bundle',
));
// Capture Usage.test() events.
final StringBuffer buffer =
await capturedConsolePrint(() => expectToolExitLater(
cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
),
equals('Error running pod install'),
));
await expectToolExitLater(
cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
),
equals('Error running pod install'),
);
expect(
logger.errorText,
contains('set up CocoaPods for ARM macOS'),
);
expect(buffer.toString(),
contains('event {category: pod-install-failure, action: arm-ffi'));
expect(usage.events, contains(const TestUsageEvent('pod-install-failure', 'arm-ffi')));
});
testWithoutContext('ffi failure on x86 macOS does not prompt gem install', () async {
......
......@@ -64,7 +64,7 @@ void main() {
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
mdnsClient: client,
logger: BufferLogger.test(),
flutterUsage: Usage.test(),
flutterUsage: TestUsage(),
);
final int port = (await portDiscovery.query())?.port;
expect(port, isNull);
......@@ -76,7 +76,7 @@ void main() {
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
mdnsClient: client,
logger: logger,
flutterUsage: Usage.test(),
flutterUsage: TestUsage(),
);
final Uri uri = await portDiscovery.getObservatoryUri(
'',
......@@ -101,7 +101,7 @@ void main() {
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
mdnsClient: client,
logger: BufferLogger.test(),
flutterUsage: Usage.test(),
flutterUsage: TestUsage(),
);
final int port = (await portDiscovery.query())?.port;
expect(port, 123);
......@@ -127,7 +127,7 @@ void main() {
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
mdnsClient: client,
logger: BufferLogger.test(),
flutterUsage: Usage.test(),
flutterUsage: TestUsage(),
);
final MDnsObservatoryDiscoveryResult result = await portDiscovery.query();
expect(result?.port, 123);
......@@ -153,7 +153,7 @@ void main() {
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
mdnsClient: client,
logger: BufferLogger.test(),
flutterUsage: Usage.test(),
flutterUsage: TestUsage(),
);
expect(portDiscovery.query, throwsToolExit());
});
......@@ -177,7 +177,7 @@ void main() {
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
mdnsClient: client,
logger: BufferLogger.test(),
flutterUsage: Usage.test(),
flutterUsage: TestUsage(),
);
final int port = (await portDiscovery.query(applicationId: 'fiz'))?.port;
expect(port, 321);
......@@ -204,7 +204,7 @@ void main() {
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
mdnsClient: client,
logger: BufferLogger.test(),
flutterUsage: Usage.test(),
flutterUsage: TestUsage(),
);
final int port = (await portDiscovery.query(applicationId: 'bar'))?.port;
expect(port, 1234);
......@@ -219,7 +219,7 @@ void main() {
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
mdnsClient: client,
logger: BufferLogger.test(),
flutterUsage: Usage.test(),
flutterUsage: TestUsage(),
);
final int port = (await portDiscovery.query(applicationId: 'bar'))?.port;
expect(port, isNull);
......@@ -234,7 +234,7 @@ void main() {
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
mdnsClient: client,
logger: BufferLogger.test(),
flutterUsage: Usage.test(),
flutterUsage: TestUsage(),
);
expect(
() async => await portDiscovery.query(),
......@@ -259,7 +259,7 @@ void main() {
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
mdnsClient: client,
logger: BufferLogger.test(),
flutterUsage: Usage.test(),
flutterUsage: TestUsage(),
);
final Uri uri = await portDiscovery.getObservatoryUri('bar', mockDevice, hostVmservicePort: 0);
expect(uri.toString(), 'http://127.0.0.1:123/');
......
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