From 60d5c8abc5d362c2fc57d5623a58ca68cf836738 Mon Sep 17 00:00:00 2001 From: Elias Yishak <42216813+eliasyishak@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:42:52 -0500 Subject: [PATCH] Migration for the `sendTiming` events for `package:unified_analytics` (#138896) Related to tracker issue: - https://github.com/flutter/flutter/issues/128251 image The image above shows all of the instances where we have `sendTiming`. All of the call sites have been updated to use the new `Event.timing` event from `package:unified_analytics`. --- .../flutter_tools/lib/src/android/gradle.dart | 32 +- .../lib/src/commands/packages.dart | 23 +- packages/flutter_tools/lib/src/ios/mac.dart | 8 +- .../lib/src/isolated/resident_web_runner.dart | 5 + .../lib/src/linux/build_linux.dart | 18 +- .../lib/src/macos/build_macos.dart | 10 +- packages/flutter_tools/lib/src/run_hot.dart | 13 +- .../lib/src/runner/flutter_command.dart | 18 +- .../src/runner/flutter_command_runner.dart | 6 + .../flutter_tools/lib/src/web/compile.dart | 8 +- .../lib/src/windows/build_windows.dart | 18 +- packages/flutter_tools/pubspec.yaml | 8 +- .../hermetic/build_ipa_test.dart | 10 + .../hermetic/build_linux_test.dart | 34 +- .../hermetic/build_macos_test.dart | 18 + .../hermetic/build_windows_test.dart | 50 ++ .../commands.shard/hermetic/config_test.dart | 14 +- .../permeable/packages_test.dart | 16 + .../android/android_gradle_builder_test.dart | 46 +- .../test/general.shard/hot_test.dart | 562 +++++++++++------- .../ios_device_start_nonprebuilt_test.dart | 17 +- .../general.shard/resident_runner_test.dart | 8 +- .../resident_web_runner_test.dart | 14 + .../runner/flutter_command_runner_test.dart | 23 +- .../runner/flutter_command_test.dart | 54 +- .../general.shard/web/compile_web_test.dart | 11 +- packages/flutter_tools/test/src/common.dart | 36 ++ 27 files changed, 806 insertions(+), 274 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 1a54700be4..50f5ffcd96 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -490,7 +490,13 @@ class AndroidGradleBuilder implements AndroidBuilder { status.stop(); } - _usage.sendTiming('build', 'gradle', sw.elapsed); + final Duration elapsedDuration = sw.elapsed; + _usage.sendTiming('build', 'gradle', elapsedDuration); + _analytics.send(Event.timing( + workflow: 'build', + variableName: 'gradle', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + )); if (exitCode != 0) { if (detectedGradleError == null) { @@ -757,7 +763,13 @@ class AndroidGradleBuilder implements AndroidBuilder { } finally { status.stop(); } - _usage.sendTiming('build', 'gradle-aar', sw.elapsed); + final Duration elapsedDuration = sw.elapsed; + _usage.sendTiming('build', 'gradle-aar', elapsedDuration); + _analytics.send(Event.timing( + workflow: 'build', + variableName: 'gradle-aar', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + )); if (result.exitCode != 0) { _logger.printStatus(result.stdout, wrap: false); @@ -792,7 +804,13 @@ class AndroidGradleBuilder implements AndroidBuilder { project: project, ); - _usage.sendTiming('print', 'android build variants', sw.elapsed); + final Duration elapsedDuration = sw.elapsed; + _usage.sendTiming('print', 'android build variants', elapsedDuration); + _analytics.send(Event.timing( + workflow: 'print', + variableName: 'android build variants', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + )); if (result.exitCode != 0) { _logger.printStatus(result.stdout, wrap: false); @@ -828,7 +846,13 @@ class AndroidGradleBuilder implements AndroidBuilder { options: ['-q', '-PoutputPath=$outputPath'], project: project, ); - _usage.sendTiming('outputs', 'app link settings', sw.elapsed); + final Duration elapsedDuration = sw.elapsed; + _usage.sendTiming('outputs', 'app link settings', elapsedDuration); + _analytics.send(Event.timing( + workflow: 'outputs', + variableName: 'app link settings', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + )); if (result.exitCode != 0) { _logger.printStatus(result.stdout, wrap: false); diff --git a/packages/flutter_tools/lib/src/commands/packages.dart b/packages/flutter_tools/lib/src/commands/packages.dart index eb25fc1a56..bae2f4fd7e 100644 --- a/packages/flutter_tools/lib/src/commands/packages.dart +++ b/packages/flutter_tools/lib/src/commands/packages.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:args/args.dart'; +import 'package:unified_analytics/unified_analytics.dart'; import '../base/common.dart'; import '../base/os.dart'; @@ -298,7 +299,7 @@ class PackagesGetCommand extends FlutterCommand { processManager: globals.processManager, platform: globals.platform, usage: globals.flutterUsage, - analytics: globals.analytics, + analytics: analytics, projectDir: rootProject.directory, generateDartPluginRegistry: true, ); @@ -319,7 +320,7 @@ class PackagesGetCommand extends FlutterCommand { processManager: globals.processManager, platform: globals.platform, usage: globals.flutterUsage, - analytics: globals.analytics, + analytics: analytics, projectDir: rootProject.directory, generateDartPluginRegistry: true, ); @@ -354,10 +355,24 @@ class PackagesGetCommand extends FlutterCommand { command: name, touchesPackageConfig: !(isHelp || dryRun), ); - globals.flutterUsage.sendTiming('pub', 'get', timer.elapsed, label: 'success'); + final Duration elapsedDuration = timer.elapsed; + globals.flutterUsage.sendTiming('pub', 'get', elapsedDuration, label: 'success'); + analytics.send(Event.timing( + workflow: 'pub', + variableName: 'get', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + label: 'success' + )); // Not limiting to catching Exception because the exception is rethrown. } catch (_) { // ignore: avoid_catches_without_on_clauses - globals.flutterUsage.sendTiming('pub', 'get', timer.elapsed, label: 'failure'); + final Duration elapsedDuration = timer.elapsed; + globals.flutterUsage.sendTiming('pub', 'get', elapsedDuration, label: 'failure'); + analytics.send(Event.timing( + workflow: 'pub', + variableName: 'get', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + label: 'failure' + )); rethrow; } diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 175c7c3172..f6a05320ba 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -419,7 +419,13 @@ Future buildXcodeProject({ 'Xcode ${xcodeBuildActionToString(buildAction)} done.'.padRight(kDefaultStatusPadding + 1) + getElapsedAsSeconds(sw.elapsed).padLeft(5), ); - globals.flutterUsage.sendTiming(xcodeBuildActionToString(buildAction), 'xcode-ios', Duration(milliseconds: sw.elapsedMilliseconds)); + final Duration elapsedDuration = sw.elapsed; + globals.flutterUsage.sendTiming(xcodeBuildActionToString(buildAction), 'xcode-ios', elapsedDuration); + globals.analytics.send(Event.timing( + workflow: xcodeBuildActionToString(buildAction), + variableName: 'xcode-ios', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + )); if (tempDir.existsSync()) { // Display additional warning and error message from xcresult bundle. diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index d5a874325a..b3c0682db7 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -452,6 +452,11 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). // Don't track restart times for dart2js builds or web-server devices. if (debuggingOptions.buildInfo.isDebug && deviceIsDebuggable) { _usage.sendTiming('hot', 'web-incremental-restart', elapsed); + _analytics.send(Event.timing( + workflow: 'hot', + variableName: 'web-incremental-restart', + elapsedMilliseconds: elapsed.inMilliseconds, + )); final String sdkName = await device!.device!.sdkNameAndVersion; HotEvent( 'restart', diff --git a/packages/flutter_tools/lib/src/linux/build_linux.dart b/packages/flutter_tools/lib/src/linux/build_linux.dart index 1bcf07361f..808130a24f 100644 --- a/packages/flutter_tools/lib/src/linux/build_linux.dart +++ b/packages/flutter_tools/lib/src/linux/build_linux.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:unified_analytics/unified_analytics.dart'; + import '../artifacts.dart'; import '../base/analyze_size.dart'; import '../base/common.dart'; @@ -155,7 +157,13 @@ Future _runCmake(String buildModeName, Directory sourceDir, Directory buil if (result != 0) { throwToolExit('Unable to generate build files'); } - globals.flutterUsage.sendTiming('build', 'cmake-linux', Duration(milliseconds: sw.elapsedMilliseconds)); + final Duration elapsedDuration = sw.elapsed; + globals.flutterUsage.sendTiming('build', 'cmake-linux', elapsedDuration); + globals.analytics.send(Event.timing( + workflow: 'build', + variableName: 'cmake-linux', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + )); } Future _runBuild(Directory buildDir) async { @@ -185,5 +193,11 @@ Future _runBuild(Directory buildDir) async { if (result != 0) { throwToolExit('Build process failed'); } - globals.flutterUsage.sendTiming('build', 'linux-ninja', Duration(milliseconds: sw.elapsedMilliseconds)); + final Duration elapsedDuration = sw.elapsed; + globals.flutterUsage.sendTiming('build', 'linux-ninja', elapsedDuration); + globals.analytics.send(Event.timing( + workflow: 'build', + variableName: 'linux-ninja', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + )); } diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart index 4161bef7f4..40e2d52430 100644 --- a/packages/flutter_tools/lib/src/macos/build_macos.dart +++ b/packages/flutter_tools/lib/src/macos/build_macos.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:unified_analytics/unified_analytics.dart'; + import '../base/analyze_size.dart'; import '../base/common.dart'; import '../base/file_system.dart'; @@ -160,7 +162,13 @@ Future buildMacOS({ throwToolExit('Build process failed'); } await _writeCodeSizeAnalysis(buildInfo, sizeAnalyzer); - globals.flutterUsage.sendTiming('build', 'xcode-macos', Duration(milliseconds: sw.elapsedMilliseconds)); + final Duration elapsedDuration = sw.elapsed; + globals.flutterUsage.sendTiming('build', 'xcode-macos', elapsedDuration); + globals.analytics.send(Event.timing( + workflow: 'build', + variableName: 'xcode-macos', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + )); } /// Performs a size analysis of the AOT snapshot and writes to an analysis file, if configured. diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 939f14ee2d..117e0f389c 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -713,7 +713,13 @@ class HotRunner extends ResidentRunner { restartTimer.elapsed.inMilliseconds); // Send timing analytics. - globals.flutterUsage.sendTiming('hot', 'restart', restartTimer.elapsed); + final Duration elapsedDuration = restartTimer.elapsed; + globals.flutterUsage.sendTiming('hot', 'restart', elapsedDuration); + _analytics.send(Event.timing( + workflow: 'hot', + variableName: 'restart', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + )); // Toggle the main dill name after successfully uploading. _swap =! _swap; @@ -1112,6 +1118,11 @@ class HotRunner extends ResidentRunner { // Only report timings if we reloaded a single view without any errors. if ((reassembleResult.reassembleViews.length == 1) && !reassembleResult.failedReassemble && shouldReportReloadTime) { globals.flutterUsage.sendTiming('hot', 'reload', reloadDuration); + _analytics.send(Event.timing( + workflow: 'hot', + variableName: 'reload', + elapsedMilliseconds: reloadDuration.inMilliseconds, + )); } return OperationResult( reassembleResult.failedReassemble ? 1 : OperationResult.ok.code, diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index ac5b9e3862..e1509a3ddb 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -1634,16 +1634,26 @@ abstract class FlutterCommand extends Command { final String label = labels .where((String? label) => label != null && !_isBlank(label)) .join('-'); + + // If the command provides its own end time, use it. Otherwise report + // the duration of the entire execution. + final Duration elapsedDuration = (commandResult.endTimeOverride ?? endTime).difference(startTime); globals.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), + elapsedDuration, // 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, ); + analytics.send(Event.timing( + workflow: 'flutter', + variableName: name, + elapsedMilliseconds: elapsedDuration.inMilliseconds, + // 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. @@ -1707,7 +1717,7 @@ Run 'flutter -h' (or 'flutter -h') for available flutter commands and processManager: globals.processManager, platform: globals.platform, usage: globals.flutterUsage, - analytics: globals.analytics, + analytics: analytics, projectDir: project.directory, generateDartPluginRegistry: true, ); diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index 7994f3af89..dadad881bc 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -6,6 +6,7 @@ import 'package:args/args.dart'; import 'package:args/command_runner.dart'; import 'package:completion/completion.dart'; import 'package:file/file.dart'; +import 'package:unified_analytics/unified_analytics.dart'; import '../artifacts.dart'; import '../base/common.dart'; @@ -329,6 +330,11 @@ class FlutterCommandRunner extends CommandRunner { if ((topLevelResults[FlutterGlobalOptions.kVersionFlag] as bool?) ?? false) { globals.flutterUsage.sendCommand(FlutterGlobalOptions.kVersionFlag); + globals.analytics.send(Event.flutterCommandResult( + commandPath: 'version', + result: 'success', + commandHasTerminal: globals.stdio.hasTerminal, + )); final FlutterVersion version = globals.flutterVersion.fetchTagsAndGetVersion( clock: globals.systemClock, ); diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index 93629a7942..9fb7073ec0 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -150,11 +150,17 @@ class WebBuilder { settings: buildSettingsString, )); + final Duration elapsedDuration = sw.elapsed; _flutterUsage.sendTiming( 'build', compilerConfig.isWasm ? 'dart2wasm' : 'dart2js', - Duration(milliseconds: sw.elapsedMilliseconds), + elapsedDuration, ); + _analytics.send(Event.timing( + workflow: 'build', + variableName: compilerConfig.isWasm ? 'dart2wasm' : 'dart2js', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + )); } } diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart index 2a4af0408a..eed8ef17d4 100644 --- a/packages/flutter_tools/lib/src/windows/build_windows.dart +++ b/packages/flutter_tools/lib/src/windows/build_windows.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:unified_analytics/unified_analytics.dart'; + import '../artifacts.dart'; import '../base/analyze_size.dart'; import '../base/common.dart'; @@ -202,7 +204,13 @@ Future _runCmakeGeneration({ if (result != 0) { throwToolExit('Unable to generate build files'); } - globals.flutterUsage.sendTiming('build', 'windows-cmake-generation', Duration(milliseconds: sw.elapsedMilliseconds)); + final Duration elapsedDuration = sw.elapsed; + globals.flutterUsage.sendTiming('build', 'windows-cmake-generation', elapsedDuration); + globals.analytics.send(Event.timing( + workflow: 'build', + variableName: 'windows-cmake-generation', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + )); } Future _runBuild( @@ -253,7 +261,13 @@ Future _runBuild( if (result != 0) { throwToolExit('Build process failed.'); } - globals.flutterUsage.sendTiming('build', 'windows-cmake-build', Duration(milliseconds: sw.elapsedMilliseconds)); + final Duration elapsedDuration = sw.elapsed; + globals.flutterUsage.sendTiming('build', 'windows-cmake-build', elapsedDuration); + globals.analytics.send(Event.timing( + workflow: 'build', + variableName: 'windows-cmake-build', + elapsedMilliseconds: elapsedDuration.inMilliseconds, + )); } /// Writes the generated CMake file with the configuration for the given build. diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index cdc8f8084e..3a25909025 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -48,7 +48,7 @@ dependencies: http_multi_server: 3.2.1 convert: 3.1.1 async: 2.11.0 - unified_analytics: 5.5.0 + unified_analytics: 5.6.0 cli_config: 0.1.2 graphs: 2.3.1 @@ -69,7 +69,7 @@ dependencies: analyzer: 6.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" built_collection: 5.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - built_value: 8.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + built_value: 8.8.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dap: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -91,7 +91,7 @@ dependencies: source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - sse: 4.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + sse: 4.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -115,4 +115,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: e59e +# PUBSPEC CHECKSUM: b1a1 diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_ipa_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_ipa_test.dart index 9c71896457..dd59cc4b24 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_ipa_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_ipa_test.dart @@ -19,6 +19,7 @@ import 'package:flutter_tools/src/ios/plist_parser.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:test/fake.dart'; +import 'package:unified_analytics/unified_analytics.dart'; import '../../general.shard/ios/xcresult_test_data.dart'; import '../../src/common.dart'; @@ -78,6 +79,7 @@ void main() { late FakePlistUtils plistUtils; late BufferLogger logger; late Artifacts artifacts; + late FakeAnalytics fakeAnalytics; setUpAll(() { Cache.disableLocking(); @@ -94,6 +96,10 @@ void main() { processManager: fakeProcessManager, ); plistUtils = FakePlistUtils(); + fakeAnalytics = getInitializedFakeAnalyticsInstance( + fs: fileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); }); // Sets up the minimal mock project files necessary to look like a Flutter project. @@ -794,6 +800,9 @@ void main() { const TestUsageEvent('code-size-analysis', 'ios'), )); expect(fakeProcessManager, hasNoRemainingExpectations); + expect(fakeAnalytics.sentEvents, contains( + Event.codeSizeAnalysis(platform: 'ios') + )); }, overrides: { FileSystem: () => fileSystem, Logger: () => logger, @@ -801,6 +810,7 @@ void main() { Platform: () => macosPlatform, FileSystemUtils: () => FileSystemUtils(fileSystem: fileSystem, platform: macosPlatform), Usage: () => usage, + Analytics: () => fakeAnalytics, XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), }); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart index ec3bc880dd..8cae50d8ab 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart @@ -21,6 +21,7 @@ import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:test/fake.dart'; +import 'package:unified_analytics/unified_analytics.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -48,12 +49,13 @@ void main() { Cache.disableLocking(); }); - late FileSystem fileSystem; + late MemoryFileSystem fileSystem; late FakeProcessManager processManager; late ProcessUtils processUtils; late Logger logger; late TestUsage usage; late Artifacts artifacts; + late FakeAnalytics fakeAnalytics; setUp(() { fileSystem = MemoryFileSystem.test(); @@ -66,6 +68,10 @@ void main() { logger: logger, processManager: processManager, ); + fakeAnalytics = getInitializedFakeAnalyticsInstance( + fs: fileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); }); // Creates the mock files necessary to look like a Flutter project. @@ -209,12 +215,30 @@ void main() { const ['build', 'linux', '--no-pub'] ); expect(fileSystem.file('linux/flutter/ephemeral/generated_config.cmake'), exists); + + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'build', + variableName: 'cmake-linux', + ), + true, + ); + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'build', + variableName: 'linux-ninja', + ), + true, + ); }, overrides: { FileSystem: () => fileSystem, ProcessManager: () => processManager, Platform: () => linuxPlatform, FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true), OperatingSystemUtils: () => FakeOperatingSystemUtils(), + Analytics: () => fakeAnalytics, }); testUsingContext('Handles missing cmake', () async { @@ -701,6 +725,9 @@ set(BINARY_NAME "fizz_bar") expect(usage.events, contains( const TestUsageEvent('code-size-analysis', 'linux'), )); + expect(fakeAnalytics.sentEvents, contains( + Event.codeSizeAnalysis(platform: 'linux') + )); }, overrides: { FileSystem: () => fileSystem, ProcessManager: () => processManager, @@ -708,6 +735,7 @@ set(BINARY_NAME "fizz_bar") FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true), Usage: () => usage, OperatingSystemUtils: () => FakeOperatingSystemUtils(), + Analytics: () => fakeAnalytics, }); testUsingContext('Linux on ARM64 build --release passes, and check if the LinuxBuildDirectory for arm64 can be referenced correctly by using analytics', () async { @@ -754,6 +782,9 @@ set(BINARY_NAME "fizz_bar") expect(usage.events, contains( const TestUsageEvent('code-size-analysis', 'linux'), )); + expect(fakeAnalytics.sentEvents, contains( + Event.codeSizeAnalysis(platform: 'linux') + )); }, overrides: { FileSystem: () => fileSystem, ProcessManager: () => processManager, @@ -761,6 +792,7 @@ set(BINARY_NAME "fizz_bar") FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true), Usage: () => usage, OperatingSystemUtils: () => CustomFakeOperatingSystemUtils(hostPlatform: HostPlatform.linux_arm64), + Analytics: () => fakeAnalytics, }); } diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart index 172b75f272..2cd9163b5e 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart @@ -20,6 +20,7 @@ import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; +import 'package:unified_analytics/unified_analytics.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -70,6 +71,7 @@ void main() { late BufferLogger logger; late XcodeProjectInterpreter xcodeProjectInterpreter; late Artifacts artifacts; + late FakeAnalytics fakeAnalytics; setUpAll(() { Cache.disableLocking(); @@ -86,6 +88,10 @@ void main() { processManager: fakeProcessManager, ); xcodeProjectInterpreter = FakeXcodeProjectInterpreter(); + fakeAnalytics = getInitializedFakeAnalyticsInstance( + fs: fileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); }); // Sets up the minimal mock project files necessary to look like a Flutter project. @@ -194,11 +200,21 @@ STDERR STUFF await createTestCommandRunner(command).run( const ['build', 'macos', '--no-pub'] ); + + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'build', + variableName: 'xcode-macos', + ), + true, + ); }, overrides: { Platform: () => macosPlatform, FileSystem: () => fileSystem, ProcessManager: () => FakeProcessManager.any(), FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), + Analytics: () => fakeAnalytics, }); testUsingContext('macOS build fails on non-macOS platform', () async { @@ -594,6 +610,7 @@ STDERR STUFF expect(usage.events, contains( const TestUsageEvent('code-size-analysis', 'macos'), )); + expect(fakeAnalytics.sentEvents, contains(Event.codeSizeAnalysis(platform: 'macos'))); }, overrides: { FileSystem: () => fileSystem, ProcessManager: () => FakeProcessManager.list([ @@ -618,5 +635,6 @@ STDERR STUFF FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), FileSystemUtils: () => FileSystemUtils(fileSystem: fileSystem, platform: macosPlatform), Usage: () => usage, + Analytics: () => fakeAnalytics, }); } diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart index ec80512529..79fb6dee1f 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart @@ -13,6 +13,7 @@ import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/windows/visual_studio.dart'; import 'package:test/fake.dart'; +import 'package:unified_analytics/unified_analytics.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -45,6 +46,7 @@ void main() { late FileSystem fileSystem; late ProcessManager processManager; late TestUsage usage; + late FakeAnalytics fakeAnalytics; setUpAll(() { Cache.disableLocking(); @@ -55,6 +57,10 @@ void main() { fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows); Cache.flutterRoot = flutterRoot; usage = TestUsage(); + fakeAnalytics = getInitializedFakeAnalyticsInstance( + fs: fileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); }); // Creates the mock files necessary to look like a Flutter project. @@ -209,6 +215,46 @@ void main() { FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), }); + testUsingContext('Windows build sends timing events', () async { + final FakeVisualStudio fakeVisualStudio = FakeVisualStudio(); + final BuildWindowsCommand command = BuildWindowsCommand(logger: BufferLogger.test()) + ..visualStudioOverride = fakeVisualStudio; + setUpMockProjectFilesForBuild(); + + processManager = FakeProcessManager.list([ + cmakeGenerationCommand(), + buildCommand('Release'), + ]); + + await createTestCommandRunner(command).run( + const ['windows', '--no-pub'] + ); + + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'build', + variableName: 'windows-cmake-generation', + ), + true, + ); + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'build', + variableName: 'windows-cmake-build', + ), + true, + ); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + Platform: () => windowsPlatform, + FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), + Analytics: () => fakeAnalytics, + }); + + testUsingContext('Windows build extracts errors from stdout', () async { final FakeVisualStudio fakeVisualStudio = FakeVisualStudio(); final BuildWindowsCommand command = BuildWindowsCommand(logger: BufferLogger.test()) @@ -929,6 +975,9 @@ if %errorlevel% neq 0 goto :VCEnd expect(usage.events, contains( const TestUsageEvent('code-size-analysis', 'windows'), )); + expect(fakeAnalytics.sentEvents, contains( + Event.codeSizeAnalysis(platform: 'windows') + )); }, overrides: { FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), FileSystem: () => fileSystem, @@ -936,6 +985,7 @@ if %errorlevel% neq 0 goto :VCEnd Platform: () => windowsPlatform, FileSystemUtils: () => FileSystemUtils(fileSystem: fileSystem, platform: windowsPlatform), Usage: () => usage, + Analytics: () => fakeAnalytics, }); // Confirms that running for Windows in a directory with a diff --git a/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart index 4f98a5b370..88eea7f2a0 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'package:args/command_runner.dart'; +import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/android/android_studio.dart'; import 'package:flutter_tools/src/android/java.dart'; @@ -17,10 +18,11 @@ import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/version.dart'; import 'package:test/fake.dart'; +import 'package:unified_analytics/unified_analytics.dart'; import '../../src/common.dart'; import '../../src/context.dart'; -import '../../src/fakes.dart'; +import '../../src/fakes.dart' as fakes; import '../../src/test_flutter_command_runner.dart'; void main() { @@ -29,23 +31,29 @@ void main() { late FakeAndroidSdk fakeAndroidSdk; late FakeFlutterVersion fakeFlutterVersion; late TestUsage testUsage; + late FakeAnalytics fakeAnalytics; setUpAll(() { Cache.disableLocking(); }); setUp(() { - fakeJava = FakeJava(); + fakeJava = fakes.FakeJava(); fakeAndroidStudio = FakeAndroidStudio(); fakeAndroidSdk = FakeAndroidSdk(); fakeFlutterVersion = FakeFlutterVersion(); testUsage = TestUsage(); + fakeAnalytics = getInitializedFakeAnalyticsInstance( + fs: MemoryFileSystem.test(), + fakeFlutterVersion: fakes.FakeFlutterVersion(), + ); }); void verifyNoAnalytics() { expect(testUsage.commands, isEmpty); expect(testUsage.events, isEmpty); expect(testUsage.timings, isEmpty); + expect(fakeAnalytics.sentEvents, isEmpty); } group('config', () { @@ -263,6 +271,7 @@ void main() { ])); expect(testUsage.commands, isEmpty); expect(testUsage.timings, isEmpty); + expect(fakeAnalytics.sentEvents, isEmpty); }, overrides: { Usage: () => testUsage, }); @@ -285,6 +294,7 @@ void main() { ])); expect(testUsage.commands, isEmpty); expect(testUsage.timings, isEmpty); + expect(fakeAnalytics.sentEvents, isEmpty); }, overrides: { Usage: () => testUsage, }); diff --git a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart index df14535004..8b197e6a03 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart @@ -23,6 +23,7 @@ import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/packages.dart'; import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:unified_analytics/unified_analytics.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -40,9 +41,14 @@ void main() { Cache.disableLocking(); group('packages get/upgrade', () { late Directory tempDir; + late FakeAnalytics fakeAnalytics; setUp(() { tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.'); + fakeAnalytics = getInitializedFakeAnalyticsInstance( + fs: MemoryFileSystem.test(), + fakeFlutterVersion: FakeFlutterVersion(), + ); }); tearDown(() { @@ -220,6 +226,15 @@ void main() { expectDependenciesResolved(projectPath); expectZeroPluginsInjected(projectPath); + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'pub', + variableName: 'get', + label: 'success', + ), + true, + ); }, overrides: { Stdio: () => mockStdio, Pub: () => Pub.test( @@ -231,6 +246,7 @@ void main() { platform: globals.platform, stdio: mockStdio, ), + Analytics: () => fakeAnalytics, }); testUsingContext('get --offline fetches packages', () async { diff --git a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart index 95a1fd6c3e..dee0bd4094 100644 --- a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart @@ -145,11 +145,21 @@ void main() { expect( fakeAnalytics.sentEvents, - unorderedEquals([ + containsAll([ Event.flutterBuildInfo(label: 'app-not-using-android-x', buildType: 'gradle'), Event.flutterBuildInfo(label: 'gradle-random-event-label-failure', buildType: 'gradle'), ]), ); + + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'build', + variableName: 'gradle', + ), + true, + ); + }, overrides: { AndroidStudio: () => FakeAndroidStudio(), }); @@ -328,7 +338,7 @@ void main() { )); expect(testUsage.events, hasLength(4)); - expect(fakeAnalytics.sentEvents, hasLength(4)); + expect(fakeAnalytics.sentEvents, hasLength(7)); expect(fakeAnalytics.sentEvents, contains( Event.flutterBuildInfo( label: 'gradle-random-event-label-failure', @@ -431,7 +441,7 @@ void main() { )); expect(testUsage.events, hasLength(2)); - expect(fakeAnalytics.sentEvents, hasLength(2)); + expect(fakeAnalytics.sentEvents, hasLength(3)); expect(fakeAnalytics.sentEvents, contains( Event.flutterBuildInfo( label: 'gradle-random-event-label-failure', @@ -878,8 +888,18 @@ BuildVariant: paidProfile project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), ); expect(actual, ['freeDebug', 'paidDebug', 'freeRelease', 'paidRelease', 'freeProfile', 'paidProfile']); + + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'print', + variableName: 'android build variants', + ), + true, + ); }, overrides: { AndroidStudio: () => FakeAndroidStudio(), + Analytics: () => fakeAnalytics, }); testUsingContext('getBuildOptions returns empty list if gradle returns error', () async { @@ -941,10 +961,20 @@ Gradle Crashed 'freeDebug', project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), ); + + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'outputs', + variableName: 'app link settings', + ), + true, + ); }, overrides: { AndroidStudio: () => FakeAndroidStudio(), FileSystem: () => fileSystem, ProcessManager: () => processManager, + Analytics: () => fakeAnalytics, }); testUsingContext("doesn't indicate how to consume an AAR when printHowToConsumeAar is false", () async { @@ -1010,8 +1040,18 @@ Gradle Crashed isFalse, ); expect(processManager, hasNoRemainingExpectations); + + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'build', + variableName: 'gradle-aar', + ), + true, + ); }, overrides: { AndroidStudio: () => FakeAndroidStudio(), + Analytics: () => fakeAnalytics, }); testUsingContext('Verbose mode for AARs includes Gradle stacktrace and sets debug log level', () async { diff --git a/packages/flutter_tools/test/general.shard/hot_test.dart b/packages/flutter_tools/test/general.shard/hot_test.dart index 4fad1eb422..f8e5d27c5c 100644 --- a/packages/flutter_tools/test/general.shard/hot_test.dart +++ b/packages/flutter_tools/test/general.shard/hot_test.dart @@ -21,7 +21,8 @@ import 'package:flutter_tools/src/resident_devtools_handler.dart'; import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/run_hot.dart'; import 'package:flutter_tools/src/vmservice.dart'; -import 'package:native_assets_cli/native_assets_cli.dart' hide BuildMode, Target; +import 'package:native_assets_cli/native_assets_cli.dart' + hide BuildMode, Target; import 'package:native_assets_cli/native_assets_cli.dart' as native_assets_cli; import 'package:package_config/package_config.dart'; import 'package:test/fake.dart'; @@ -36,84 +37,116 @@ import 'fake_native_assets_build_runner.dart'; void main() { group('validateReloadReport', () { testUsingContext('invalid', () async { - expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse({ - 'type': 'ReloadReport', - 'success': false, - 'details': {}, - })), false); - expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse({ - 'type': 'ReloadReport', - 'success': false, - 'details': { - 'notices': >[ - ], - }, - })), false); - expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse({ - 'type': 'ReloadReport', - 'success': false, - 'details': { - 'notices': { - 'message': 'error', - }, - }, - })), false); - expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse({ - 'type': 'ReloadReport', - 'success': false, - 'details': { - 'notices': >[], - }, - })), false); - expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse({ - 'type': 'ReloadReport', - 'success': false, - 'details': { - 'notices': >[ - {'message': false}, - ], - }, - })), false); - expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse({ - 'type': 'ReloadReport', - 'success': false, - 'details': { - 'notices': >[ - {'message': ['error']}, - ], - }, - })), false); - expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse({ - 'type': 'ReloadReport', - 'success': false, - 'details': { - 'notices': >[ - {'message': 'error'}, - {'message': ['error']}, - ], - }, - })), false); - expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse({ - 'type': 'ReloadReport', - 'success': false, - 'details': { - 'notices': >[ - {'message': 'error'}, - ], - }, - })), false); - expect(HotRunner.validateReloadReport(vm_service.ReloadReport.parse({ - 'type': 'ReloadReport', - 'success': true, - })), true); + expect( + HotRunner.validateReloadReport( + vm_service.ReloadReport.parse({ + 'type': 'ReloadReport', + 'success': false, + 'details': {}, + })), + false); + expect( + HotRunner.validateReloadReport( + vm_service.ReloadReport.parse({ + 'type': 'ReloadReport', + 'success': false, + 'details': { + 'notices': >[], + }, + })), + false); + expect( + HotRunner.validateReloadReport( + vm_service.ReloadReport.parse({ + 'type': 'ReloadReport', + 'success': false, + 'details': { + 'notices': { + 'message': 'error', + }, + }, + })), + false); + expect( + HotRunner.validateReloadReport( + vm_service.ReloadReport.parse({ + 'type': 'ReloadReport', + 'success': false, + 'details': { + 'notices': >[], + }, + })), + false); + expect( + HotRunner.validateReloadReport( + vm_service.ReloadReport.parse({ + 'type': 'ReloadReport', + 'success': false, + 'details': { + 'notices': >[ + {'message': false}, + ], + }, + })), + false); + expect( + HotRunner.validateReloadReport( + vm_service.ReloadReport.parse({ + 'type': 'ReloadReport', + 'success': false, + 'details': { + 'notices': >[ + { + 'message': ['error'] + }, + ], + }, + })), + false); + expect( + HotRunner.validateReloadReport( + vm_service.ReloadReport.parse({ + 'type': 'ReloadReport', + 'success': false, + 'details': { + 'notices': >[ + {'message': 'error'}, + { + 'message': ['error'] + }, + ], + }, + })), + false); + expect( + HotRunner.validateReloadReport( + vm_service.ReloadReport.parse({ + 'type': 'ReloadReport', + 'success': false, + 'details': { + 'notices': >[ + {'message': 'error'}, + ], + }, + })), + false); + expect( + HotRunner.validateReloadReport( + vm_service.ReloadReport.parse({ + 'type': 'ReloadReport', + 'success': true, + })), + true); }); - testWithoutContext('ReasonForCancelling toString has a hint for specific errors', () { + testWithoutContext( + 'ReasonForCancelling toString has a hint for specific errors', () { final ReasonForCancelling reasonForCancelling = ReasonForCancelling( message: 'Const class cannot remove fields', ); - expect(reasonForCancelling.toString(), contains('Try performing a hot restart instead.')); + expect(reasonForCancelling.toString(), + contains('Try performing a hot restart instead.')); }); }); @@ -147,7 +180,10 @@ void main() { ..writeAsStringSync('\n'); final FakeDevice device = FakeDevice(); final List devices = [ - FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler()) + FlutterDevice(device, + generator: residentCompiler, + buildInfo: BuildInfo.debug, + developmentShaderCompiler: const FakeShaderCompiler()) ..devFS = FakeDevFs(), ]; final OperationResult result = await HotRunner( @@ -156,7 +192,6 @@ void main() { target: 'main.dart', devtoolsHandler: createNoOpHandler, analytics: fakeAnalytics, - ).restart(fullRestart: true); expect(result.isOk, false); expect(result.message, 'setupHotRestart failed'); @@ -188,12 +223,13 @@ void main() { Map> viewCache, void Function(String message)? onSlow, String reloadMessage, - ) async => ReassembleResult( - {null: null}, - false, - true, - ), - analytics: fakeAnalytics, + ) async => + ReassembleResult( + {null: null}, + false, + true, + ), + analytics: fakeAnalytics, ).restart(); expect(result.isOk, false); expect(result.message, 'setupHotReload failed'); @@ -220,7 +256,10 @@ void main() { ..writeAsStringSync('\n'); final FakeDevice device = FakeDevice(); final List devices = [ - FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler()), + FlutterDevice(device, + generator: residentCompiler, + buildInfo: BuildInfo.debug, + developmentShaderCompiler: const FakeShaderCompiler()), ]; await HotRunner( devices, @@ -243,7 +282,10 @@ void main() { ..writeAsStringSync('\n'); final FakeDevice device = FakeDevice(); final List devices = [ - FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler()), + FlutterDevice(device, + generator: residentCompiler, + buildInfo: BuildInfo.debug, + developmentShaderCompiler: const FakeShaderCompiler()), ]; await HotRunner( devices, @@ -268,30 +310,36 @@ void main() { successfulHotRestartSetup: true, ); }); - testUsingContext('correctly tracks time spent for analytics for hot restart', () async { + testUsingContext( + 'correctly tracks time spent for analytics for hot restart', + () async { final FakeDevice device = FakeDevice(); final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device); final List devices = [ fakeFlutterDevice, ]; - fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport( - success: true, - invalidatedSourcesCount: 2, - syncedBytes: 4, - scannedSourcesCount: 8, - compileDuration: const Duration(seconds: 16), - transferDuration: const Duration(seconds: 32), - ); + fakeFlutterDevice.updateDevFSReportCallback = + () async => UpdateFSReport( + success: true, + invalidatedSourcesCount: 2, + syncedBytes: 4, + scannedSourcesCount: 8, + compileDuration: const Duration(seconds: 16), + transferDuration: const Duration(seconds: 32), + ); final FakeStopwatchFactory fakeStopwatchFactory = FakeStopwatchFactory( stopwatches: { - 'fullRestartHelper': FakeStopwatch()..elapsed = const Duration(seconds: 64), - 'updateDevFS': FakeStopwatch()..elapsed = const Duration(seconds: 128), + 'fullRestartHelper': FakeStopwatch() + ..elapsed = const Duration(seconds: 64), + 'updateDevFS': FakeStopwatch() + ..elapsed = const Duration(seconds: 128), }, ); - (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = Uri.parse('file:///base_uri'); + (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = + Uri.parse('file:///base_uri'); final OperationResult result = await HotRunner( devices, @@ -304,37 +352,45 @@ void main() { expect(result.isOk, true); expect(testUsage.events, [ - const TestUsageEvent('hot', 'restart', parameters: CustomDimensions( - hotEventTargetPlatform: 'flutter-tester', - hotEventSdkName: 'Tester', - hotEventEmulator: false, - hotEventFullRestart: true, - hotEventOverallTimeInMs: 64000, - hotEventSyncedBytes: 4, - hotEventInvalidatedSourcesCount: 2, - hotEventTransferTimeInMs: 32000, - hotEventCompileTimeInMs: 16000, - hotEventFindInvalidatedTimeInMs: 128000, - hotEventScannedSourcesCount: 8, - )), + const TestUsageEvent('hot', 'restart', + parameters: CustomDimensions( + hotEventTargetPlatform: 'flutter-tester', + hotEventSdkName: 'Tester', + hotEventEmulator: false, + hotEventFullRestart: true, + hotEventOverallTimeInMs: 64000, + hotEventSyncedBytes: 4, + hotEventInvalidatedSourcesCount: 2, + hotEventTransferTimeInMs: 32000, + hotEventCompileTimeInMs: 16000, + hotEventFindInvalidatedTimeInMs: 128000, + hotEventScannedSourcesCount: 8, + )), ]); - expect(fakeAnalytics.sentEvents, contains( - Event.hotRunnerInfo( - label: 'restart', - targetPlatform: 'flutter-tester', - sdkName: 'Tester', - emulator: false, - fullRestart: true, - syncedBytes: 4, - invalidatedSourcesCount: 2, - transferTimeInMs: 32000, - overallTimeInMs: 64000, - compileTimeInMs: 16000, - findInvalidatedTimeInMs: 128000, - scannedSourcesCount: 8 - ) - )); + expect( + fakeAnalytics.sentEvents, + contains(Event.hotRunnerInfo( + label: 'restart', + targetPlatform: 'flutter-tester', + sdkName: 'Tester', + emulator: false, + fullRestart: true, + syncedBytes: 4, + invalidatedSourcesCount: 2, + transferTimeInMs: 32000, + overallTimeInMs: 64000, + compileTimeInMs: 16000, + findInvalidatedTimeInMs: 128000, + scannedSourcesCount: 8))); + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'hot', + variableName: 'restart', + ), + true, + ); expect(testingConfig.updateDevFSCompleteCalled, true); }, overrides: { HotRunnerConfig: () => testingConfig, @@ -353,32 +409,39 @@ void main() { successfulHotReloadSetup: true, ); }); - testUsingContext('correctly tracks time spent for analytics for hot reload', () async { + testUsingContext( + 'correctly tracks time spent for analytics for hot reload', () async { final FakeDevice device = FakeDevice(); final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device); final List devices = [ fakeFlutterDevice, ]; - fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport( - success: true, - invalidatedSourcesCount: 6, - syncedBytes: 8, - scannedSourcesCount: 16, - compileDuration: const Duration(seconds: 16), - transferDuration: const Duration(seconds: 32), - ); + fakeFlutterDevice.updateDevFSReportCallback = + () async => UpdateFSReport( + success: true, + invalidatedSourcesCount: 6, + syncedBytes: 8, + scannedSourcesCount: 16, + compileDuration: const Duration(seconds: 16), + transferDuration: const Duration(seconds: 32), + ); final FakeStopwatchFactory fakeStopwatchFactory = FakeStopwatchFactory( stopwatches: { - 'updateDevFS': FakeStopwatch()..elapsed = const Duration(seconds: 64), - 'reloadSources:reload': FakeStopwatch()..elapsed = const Duration(seconds: 128), - 'reloadSources:reassemble': FakeStopwatch()..elapsed = const Duration(seconds: 256), - 'reloadSources:vm': FakeStopwatch()..elapsed = const Duration(seconds: 512), + 'updateDevFS': FakeStopwatch() + ..elapsed = const Duration(seconds: 64), + 'reloadSources:reload': FakeStopwatch() + ..elapsed = const Duration(seconds: 128), + 'reloadSources:reassemble': FakeStopwatch() + ..elapsed = const Duration(seconds: 256), + 'reloadSources:vm': FakeStopwatch() + ..elapsed = const Duration(seconds: 512), }, ); - (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = Uri.parse('file:///base_uri'); + (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = + Uri.parse('file:///base_uri'); final OperationResult result = await HotRunner( devices, @@ -410,57 +473,68 @@ void main() { Map> viewCache, void Function(String message)? onSlow, String reloadMessage, - ) async => ReassembleResult( - {null: null}, - false, - true, - ), + ) async => + ReassembleResult( + {null: null}, + false, + true, + ), ).restart(); expect(result.isOk, true); expect(testUsage.events, [ - const TestUsageEvent('hot', 'reload', parameters: CustomDimensions( - hotEventFinalLibraryCount: 2, - hotEventSyncedLibraryCount: 3, - hotEventSyncedClassesCount: 4, - hotEventSyncedProceduresCount: 5, - hotEventSyncedBytes: 8, - hotEventInvalidatedSourcesCount: 6, - hotEventTransferTimeInMs: 32000, - hotEventOverallTimeInMs: 128000, - hotEventTargetPlatform: 'flutter-tester', - hotEventSdkName: 'Tester', - hotEventEmulator: false, - hotEventFullRestart: false, - hotEventCompileTimeInMs: 16000, - hotEventFindInvalidatedTimeInMs: 64000, - hotEventScannedSourcesCount: 16, - hotEventReassembleTimeInMs: 256000, - hotEventReloadVMTimeInMs: 512000, - )), + const TestUsageEvent('hot', 'reload', + parameters: CustomDimensions( + hotEventFinalLibraryCount: 2, + hotEventSyncedLibraryCount: 3, + hotEventSyncedClassesCount: 4, + hotEventSyncedProceduresCount: 5, + hotEventSyncedBytes: 8, + hotEventInvalidatedSourcesCount: 6, + hotEventTransferTimeInMs: 32000, + hotEventOverallTimeInMs: 128000, + hotEventTargetPlatform: 'flutter-tester', + hotEventSdkName: 'Tester', + hotEventEmulator: false, + hotEventFullRestart: false, + hotEventCompileTimeInMs: 16000, + hotEventFindInvalidatedTimeInMs: 64000, + hotEventScannedSourcesCount: 16, + hotEventReassembleTimeInMs: 256000, + hotEventReloadVMTimeInMs: 512000, + )), ]); - expect(fakeAnalytics.sentEvents, contains( - Event.hotRunnerInfo( - label: 'reload', - targetPlatform: 'flutter-tester', - sdkName: 'Tester', - emulator: false, - fullRestart: false, - finalLibraryCount: 2, - syncedLibraryCount: 3, - syncedClassesCount: 4, - syncedProceduresCount: 5, - syncedBytes: 8, - invalidatedSourcesCount: 6, - transferTimeInMs: 32000, - overallTimeInMs: 128000, - compileTimeInMs: 16000, - findInvalidatedTimeInMs: 64000, - scannedSourcesCount: 16, - reassembleTimeInMs: 256000, - reloadVMTimeInMs: 512000 + expect( + fakeAnalytics.sentEvents, + contains( + Event.hotRunnerInfo( + label: 'reload', + targetPlatform: 'flutter-tester', + sdkName: 'Tester', + emulator: false, + fullRestart: false, + finalLibraryCount: 2, + syncedLibraryCount: 3, + syncedClassesCount: 4, + syncedProceduresCount: 5, + syncedBytes: 8, + invalidatedSourcesCount: 6, + transferTimeInMs: 32000, + overallTimeInMs: 128000, + compileTimeInMs: 16000, + findInvalidatedTimeInMs: 64000, + scannedSourcesCount: 16, + reassembleTimeInMs: 256000, + reloadVMTimeInMs: 512000), + )); + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'hot', + variableName: 'reload', ), - )); + true, + ); expect(testingConfig.updateDevFSCompleteCalled, true); }, overrides: { HotRunnerConfig: () => testingConfig, @@ -485,7 +559,8 @@ void main() { final List devices = [ fakeFlutterDevice, ]; - fakeFlutterDevice.updateDevFSReportCallback = () async => throw Exception('updateDevFS failed'); + fakeFlutterDevice.updateDevFSReportCallback = + () async => throw Exception('updateDevFS failed'); final HotRunner runner = HotRunner( devices, @@ -495,7 +570,10 @@ void main() { analytics: fakeAnalytics, ); - await expectLater(runner.restart(fullRestart: true), throwsA(isA().having((Exception e) => e.toString(), 'message', 'Exception: updateDevFS failed'))); + await expectLater( + runner.restart(fullRestart: true), + throwsA(isA().having((Exception e) => e.toString(), + 'message', 'Exception: updateDevFS failed'))); expect(testingConfig.updateDevFSCompleteCalled, true); }, overrides: { HotRunnerConfig: () => testingConfig, @@ -520,7 +598,8 @@ void main() { final List devices = [ fakeFlutterDevice, ]; - fakeFlutterDevice.updateDevFSReportCallback = () async => throw Exception('updateDevFS failed'); + fakeFlutterDevice.updateDevFSReportCallback = + () async => throw Exception('updateDevFS failed'); final HotRunner runner = HotRunner( devices, @@ -530,7 +609,10 @@ void main() { analytics: fakeAnalytics, ); - await expectLater(runner.restart(), throwsA(isA().having((Exception e) => e.toString(), 'message', 'Exception: updateDevFS failed'))); + await expectLater( + runner.restart(), + throwsA(isA().having((Exception e) => e.toString(), + 'message', 'Exception: updateDevFS failed'))); expect(testingConfig.updateDevFSCompleteCalled, true); }, overrides: { HotRunnerConfig: () => testingConfig, @@ -555,8 +637,9 @@ void main() { ); }); - testUsingContext('Exits with code 2 when HttpException is thrown ' - 'during VM service connection', () async { + testUsingContext( + 'Exits with code 2 when HttpException is thrown ' + 'during VM service connection', () async { fileSystem.file('.packages') ..createSync(recursive: true) ..writeAsStringSync('\n'); @@ -567,12 +650,14 @@ void main() { TestFlutterDevice( device: device, generator: residentCompiler, - exception: const HttpException('Connection closed before full header was received, ' + exception: const HttpException( + 'Connection closed before full header was received, ' 'uri = http://127.0.0.1:63394/5ZmLv8A59xY=/ws'), ), ]; - final int exitCode = await HotRunner(devices, + final int exitCode = await HotRunner( + devices, debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), target: 'main.dart', analytics: fakeAnalytics, @@ -610,7 +695,8 @@ void main() { flutterDevice2, ]; - await HotRunner(devices, + await HotRunner( + devices, debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), target: 'main.dart', analytics: fakeAnalytics, @@ -647,17 +733,19 @@ void main() { ]; fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport( - success: true, - invalidatedSourcesCount: 6, - syncedBytes: 8, - scannedSourcesCount: 16, - compileDuration: const Duration(seconds: 16), - transferDuration: const Duration(seconds: 32), - ); + success: true, + invalidatedSourcesCount: 6, + syncedBytes: 8, + scannedSourcesCount: 16, + compileDuration: const Duration(seconds: 16), + transferDuration: const Duration(seconds: 32), + ); - (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = Uri.parse('file:///base_uri'); + (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = + Uri.parse('file:///base_uri'); - final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner( + final FakeNativeAssetsBuildRunner buildRunner = + FakeNativeAssetsBuildRunner( packagesWithNativeAssetsResult: [ Package('bar', fileSystem.currentDirectory.uri), ], @@ -695,28 +783,32 @@ void main() { FileSystem: () => fileSystem, Platform: () => FakePlatform(), ProcessManager: () => FakeProcessManager.empty(), - FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true, isMacOSEnabled: true), + FeatureFlags: () => + TestFeatureFlags(isNativeAssetsEnabled: true, isMacOSEnabled: true), }); testUsingContext('native assets run unsupported', () async { - final FakeDevice device = FakeDevice(targetPlatform: TargetPlatform.android_arm64); + final FakeDevice device = + FakeDevice(targetPlatform: TargetPlatform.android_arm64); final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device); final List devices = [ fakeFlutterDevice, ]; fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport( - success: true, - invalidatedSourcesCount: 6, - syncedBytes: 8, - scannedSourcesCount: 16, - compileDuration: const Duration(seconds: 16), - transferDuration: const Duration(seconds: 32), - ); + success: true, + invalidatedSourcesCount: 6, + syncedBytes: 8, + scannedSourcesCount: 16, + compileDuration: const Duration(seconds: 16), + transferDuration: const Duration(seconds: 32), + ); - (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = Uri.parse('file:///base_uri'); + (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = + Uri.parse('file:///base_uri'); - final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner( + final FakeNativeAssetsBuildRunner buildRunner = + FakeNativeAssetsBuildRunner( packagesWithNativeAssetsResult: [ Package('bar', fileSystem.currentDirectory.uri), ], @@ -741,28 +833,27 @@ void main() { analytics: fakeAnalytics, ); expect( - () => hotRunner.run(), - throwsToolExit( message: - 'Package(s) bar require the native assets feature. ' - 'This feature has not yet been implemented for `TargetPlatform.android_arm64`. ' - 'For more info see https://github.com/flutter/flutter/issues/129757.', - ) - ); - + () => hotRunner.run(), + throwsToolExit( + message: 'Package(s) bar require the native assets feature. ' + 'This feature has not yet been implemented for `TargetPlatform.android_arm64`. ' + 'For more info see https://github.com/flutter/flutter/issues/129757.', + )); }, overrides: { HotRunnerConfig: () => testingConfig, Artifacts: () => Artifacts.test(), FileSystem: () => fileSystem, Platform: () => FakePlatform(), ProcessManager: () => FakeProcessManager.empty(), - FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true, isMacOSEnabled: true), + FeatureFlags: () => + TestFeatureFlags(isNativeAssetsEnabled: true, isMacOSEnabled: true), }); }); } class FakeDevFs extends Fake implements DevFS { @override - Future destroy() async { } + Future destroy() async {} @override List sources = []; @@ -777,10 +868,10 @@ class FakeDevFs extends Fake implements DevFS { Set assetPathsToEvict = {}; @override - Set shaderPathsToEvict= {}; + Set shaderPathsToEvict = {}; @override - Set scenePathsToEvict= {}; + Set scenePathsToEvict = {}; @override Uri? baseUri; @@ -873,7 +964,8 @@ class FakeFlutterDevice extends Fake implements FlutterDevice { required String dillOutputPath, required List invalidatedFiles, required PackageConfig packageConfig, - }) => updateDevFSReportCallback(); + }) => + updateDevFSReportCallback(); @override TargetPlatform? get targetPlatform => device._targetPlatform; @@ -884,7 +976,10 @@ class TestFlutterDevice extends FlutterDevice { required Device device, required this.exception, required ResidentCompiler generator, - }) : super(device, buildInfo: BuildInfo.debug, generator: generator, developmentShaderCompiler: const FakeShaderCompiler()); + }) : super(device, + buildInfo: BuildInfo.debug, + generator: generator, + developmentShaderCompiler: const FakeShaderCompiler()); /// The exception to throw when the connect method is called. final Exception exception; @@ -910,7 +1005,8 @@ class TestFlutterDevice extends FlutterDevice { } class TestHotRunnerConfig extends HotRunnerConfig { - TestHotRunnerConfig({this.successfulHotRestartSetup, this.successfulHotReloadSetup}); + TestHotRunnerConfig( + {this.successfulHotRestartSetup, this.successfulHotReloadSetup}); bool? successfulHotRestartSetup; bool? successfulHotReloadSetup; bool shutdownHookCalled = false; @@ -918,13 +1014,15 @@ class TestHotRunnerConfig extends HotRunnerConfig { @override Future setupHotRestart() async { - assert(successfulHotRestartSetup != null, 'setupHotRestart is not expected to be called in this test.'); + assert(successfulHotRestartSetup != null, + 'setupHotRestart is not expected to be called in this test.'); return successfulHotRestartSetup; } @override Future setupHotReload() async { - assert(successfulHotReloadSetup != null, 'setupHotReload is not expected to be called in this test.'); + assert(successfulHotReloadSetup != null, + 'setupHotReload is not expected to be called in this test.'); return successfulHotReloadSetup; } @@ -949,7 +1047,9 @@ class FakeFlutterVmService extends Fake implements FlutterVmService { vm_service.VmService get service => FakeVmService(); @override - Future> getFlutterViews({bool returnEarly = false, Duration delay = const Duration(milliseconds: 50)}) async { + Future> getFlutterViews( + {bool returnEarly = false, + Duration delay = const Duration(milliseconds: 50)}) async { return []; } } @@ -971,7 +1071,7 @@ class FakeShaderCompiler implements DevelopmentShaderCompiler { void configureCompiler( TargetPlatform? platform, { required ImpellerStatus impellerStatus, - }) { } + }) {} @override Future recompileShader(DevFSContent inputShader) { diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart index ebbcb4946c..8c37ba0389 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart @@ -27,6 +27,7 @@ import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/macos/xcode.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:test/fake.dart'; +import 'package:unified_analytics/unified_analytics.dart'; import '../../src/common.dart'; import '../../src/context.dart' hide FakeXcodeProjectInterpreter; @@ -88,12 +89,13 @@ void main() { }); group('IOSDevice.startApp succeeds in release mode', () { - late FileSystem fileSystem; + late MemoryFileSystem fileSystem; late FakeProcessManager processManager; late BufferLogger logger; late Xcode xcode; late FakeXcodeProjectInterpreter fakeXcodeProjectInterpreter; late XcodeProjectInfo projectInfo; + late FakeAnalytics fakeAnalytics; setUp(() { logger = BufferLogger.test(); @@ -110,6 +112,10 @@ void main() { fileSystem.file('foo/.packages') ..createSync(recursive: true) ..writeAsStringSync('\n'); + fakeAnalytics = getInitializedFakeAnalyticsInstance( + fs: fileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); }); testUsingContext('missing TARGET_BUILD_DIR', () async { @@ -135,6 +141,14 @@ void main() { expect(launchResult.started, false); expect(logger.errorText, contains('Xcode build is missing expected TARGET_BUILD_DIR build setting')); expect(processManager, hasNoRemainingExpectations); + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'build', + variableName: 'xcode-ios', + ), + true, + ); }, overrides: { ProcessManager: () => processManager, FileSystem: () => fileSystem, @@ -145,6 +159,7 @@ void main() { 'DEVELOPMENT_TEAM': '3333CCCC33', }, projectInfo: projectInfo), Xcode: () => xcode, + Analytics: () => fakeAnalytics, }); testUsingContext('missing project info', () async { 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 a0e2b90630..a06cc1e53d 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -41,6 +41,7 @@ import 'package:native_assets_cli/native_assets_cli.dart' import 'package:native_assets_cli/native_assets_cli.dart' as native_assets_cli; import 'package:package_config/package_config.dart'; import 'package:test/fake.dart'; +import 'package:unified_analytics/src/enums.dart'; import 'package:unified_analytics/unified_analytics.dart'; import 'package:vm_service/vm_service.dart' as vm_service; @@ -959,8 +960,11 @@ void main() { expect(event.parameters?.hotEventTargetPlatform, getNameForTargetPlatform(TargetPlatform.android_arm)); expect(fakeVmServiceHost?.hasRemainingExpectations, false); - - final Event newEvent = fakeAnalytics.sentEvents.first; + // Parse out the event of interest since we may have timing events with + // the new analytics package + final List newEventList = fakeAnalytics.sentEvents.where((Event e) => e.eventName == DashEvent.hotRunnerInfo).toList(); + expect(newEventList, hasLength(1)); + final Event newEvent = newEventList.first; expect(newEvent.eventName.label, 'hot_runner_info'); expect(newEvent.eventData['label'], 'restart'); expect(newEvent.eventData['targetPlatform'], getNameForTargetPlatform(TargetPlatform.android_arm)); diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index 7eb4a3ad1e..ca4fec8cf8 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart @@ -693,6 +693,13 @@ void main() { expect(testUsage.timings, const [ TestTimingEvent('hot', 'web-incremental-restart', Duration.zero), ]); + expect(fakeAnalytics.sentEvents, contains( + Event.timing( + workflow: 'hot', + variableName: 'web-incremental-restart', + elapsedMilliseconds: 0, + ), + )); }, overrides: { Usage: () => testUsage, Analytics: () => fakeAnalytics, @@ -779,6 +786,13 @@ void main() { expect(testUsage.timings, const [ TestTimingEvent('hot', 'web-incremental-restart', Duration.zero), ]); + expect(fakeAnalytics.sentEvents, contains( + Event.timing( + workflow: 'hot', + variableName: 'web-incremental-restart', + elapsedMilliseconds: 0, + ), + )); }, overrides: { Usage: () => testUsage, Analytics: () => fakeAnalytics, diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart index 12301c7948..538a9c76c4 100644 --- a/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart @@ -10,10 +10,13 @@ import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/runner/flutter_command_runner.dart'; import 'package:flutter_tools/src/version.dart'; +import 'package:unified_analytics/unified_analytics.dart'; +import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/fakes.dart'; import '../../src/test_flutter_command_runner.dart'; @@ -26,6 +29,8 @@ void main() { group('FlutterCommandRunner', () { late MemoryFileSystem fileSystem; late Platform platform; + late TestUsage testUsage; + late FakeAnalytics fakeAnalytics; setUpAll(() { Cache.disableLocking(); @@ -36,6 +41,11 @@ void main() { fileSystem.directory(_kFlutterRoot).createSync(recursive: true); fileSystem.directory(_kProjectRoot).createSync(recursive: true); fileSystem.currentDirectory = _kProjectRoot; + testUsage = TestUsage(); + fakeAnalytics = getInitializedFakeAnalyticsInstance( + fs: fileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); platform = FakePlatform( environment: { @@ -144,14 +154,25 @@ void main() { final FakeFlutterVersion version = globals.flutterVersion as FakeFlutterVersion; await runner.run(['--version']); - expect(version.didFetchTagsAndUpdate, true); + expect(testUsage.commands, contains( + const TestUsageCommand('version'), + )); + expect(fakeAnalytics.sentEvents, contains( + Event.flutterCommandResult( + commandPath: 'version', + result: 'success', + commandHasTerminal: false, + ), + )); }, overrides: { FileSystem: () => fileSystem, ProcessManager: () => FakeProcessManager.any(), Platform: () => platform, FlutterVersion: () => FakeFlutterVersion(), OutputPreferences: () => OutputPreferences.test(), + Usage: () => testUsage, + Analytics: () => fakeAnalytics, }); testUsingContext("Doesn't crash on invalid .packages file", () async { diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart index 2a6c5a8053..596c00a205 100644 --- a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart @@ -27,6 +27,7 @@ import 'package:flutter_tools/src/project.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/src/enums.dart'; import 'package:unified_analytics/unified_analytics.dart'; import '../../src/common.dart'; @@ -229,14 +230,14 @@ void main() { value: 10, ), ]); - expect(fakeAnalytics.sentEvents, [ + expect(fakeAnalytics.sentEvents, contains( Event.flutterCommandResult( commandPath: 'dummy', result: 'success', maxRss: 10, commandHasTerminal: false, ), - ]); + )); }); testUsingCommandContext('reports command that results in warning', () async { @@ -263,14 +264,14 @@ void main() { value: 10, ), ]); - expect(fakeAnalytics.sentEvents, [ + expect(fakeAnalytics.sentEvents, contains( Event.flutterCommandResult( commandPath: 'dummy', result: 'warning', maxRss: 10, commandHasTerminal: false, ), - ]); + )); }); testUsingCommandContext('reports command that results in error', () async { @@ -299,14 +300,14 @@ void main() { value: 10, ), ]); - expect(fakeAnalytics.sentEvents, [ + expect(fakeAnalytics.sentEvents, contains( Event.flutterCommandResult( commandPath: 'dummy', result: 'fail', maxRss: 10, commandHasTerminal: false, ), - ]); + )); }); test('FlutterCommandResult.success()', () async { @@ -413,14 +414,14 @@ void main() { value: 10, ), ]); - expect(fakeAnalytics.sentEvents, [ + expect(fakeAnalytics.sentEvents, contains( Event.flutterCommandResult( commandPath: 'dummy', result: 'killed', maxRss: 10, commandHasTerminal: false, ), - ]); + )); }, overrides: { FileSystem: () => fileSystem, ProcessManager: () => processManager, @@ -485,6 +486,14 @@ void main() { Duration(milliseconds: 1000), label: 'fail', ))); + expect(fakeAnalytics.sentEvents, contains( + Event.timing( + workflow: 'flutter', + variableName: 'dummy', + elapsedMilliseconds: 1000, + label: 'fail', + ) + )); }); testUsingCommandContext('no timing report without usagePath', () async { @@ -496,6 +505,19 @@ void main() { await flutterCommand.run(); expect(usage.timings, isEmpty); + // Iterate through and count all the [Event.timing] instances + int timingEventCounts = 0; + for (final Event e in fakeAnalytics.sentEvents) { + if (e.eventName == DashEvent.timing) { + timingEventCounts += 1; + } + } + expect( + timingEventCounts, + 0, + reason: 'There should not be any timing events sent, there may ' + 'be other non-timing events', + ); }); testUsingCommandContext('report additional FlutterCommandResult data', () async { @@ -521,6 +543,14 @@ void main() { Duration(milliseconds: 500), label: 'success-blah1-blah2-blah3', ))); + expect(fakeAnalytics.sentEvents, contains( + Event.timing( + workflow: 'flutter', + variableName: 'dummy', + elapsedMilliseconds: 500, + label: 'success-blah1-blah2-blah3', + ), + )); }); testUsingCommandContext('report failed execution timing too', () async { @@ -545,6 +575,14 @@ void main() { label: 'fail', ), )); + expect(fakeAnalytics.sentEvents, contains( + Event.timing( + workflow: 'flutter', + variableName: 'dummy', + elapsedMilliseconds: 1000, + label: 'fail', + ), + )); }); testUsingContext('reports null safety analytics when reportNullSafety is true', () async { diff --git a/packages/flutter_tools/test/general.shard/web/compile_web_test.dart b/packages/flutter_tools/test/general.shard/web/compile_web_test.dart index a11a68fdb5..b3557413f8 100644 --- a/packages/flutter_tools/test/general.shard/web/compile_web_test.dart +++ b/packages/flutter_tools/test/general.shard/web/compile_web_test.dart @@ -111,7 +111,7 @@ void main() { expect( fakeAnalytics.sentEvents, - unorderedEquals([ + containsAll([ Event.flutterBuildInfo( label: 'web-compile', buildType: 'web', @@ -124,6 +124,14 @@ void main() { final TestTimingEvent timingEvent = testUsage.timings.single; expect(timingEvent.category, 'build'); expect(timingEvent.variableName, 'dart2wasm'); + expect( + analyticsTimingEventExists( + sentEvents: fakeAnalytics.sentEvents, + workflow: 'build', + variableName: 'dart2wasm', + ), + true, + ); }); testUsingContext('WebBuilder throws tool exit on failure', () async { @@ -159,5 +167,6 @@ void main() { expect(logger.errorText, contains('Target hello failed: FormatException: illegal character in input string')); expect(testUsage.timings, isEmpty); + expect(fakeAnalytics.sentEvents, isEmpty); }); } diff --git a/packages/flutter_tools/test/src/common.dart b/packages/flutter_tools/test/src/common.dart index 3f4e0178f8..b6143a9be9 100644 --- a/packages/flutter_tools/test/src/common.dart +++ b/packages/flutter_tools/test/src/common.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:args/command_runner.dart'; +import 'package:collection/collection.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/context.dart'; @@ -347,3 +348,38 @@ FakeAnalytics getInitializedFakeAnalyticsInstance({ clientIde: clientIde, ); } + +/// Returns "true" if the timing event searched for exists in [sentEvents]. +/// +/// This utility function allows us to check for an instance of +/// [Event.timing] within a [FakeAnalytics] instance. Normally, we can +/// use the equality operator for [Event] to check if the event exists, but +/// we are unable to do so for the timing event because the elapsed time +/// is variable so we cannot predict what that value will be in tests. +/// +/// This function allows us to check for the other keys that have +/// string values by removing the `elapsedMilliseconds` from the +/// [Event.eventData] map and checking for a match. +bool analyticsTimingEventExists({ + required List sentEvents, + required String workflow, + required String variableName, + String? label, +}) { + final Map lookup = { + 'workflow': workflow, + 'variableName': variableName, + if (label != null) 'label': label, + }; + + for (final Event e in sentEvents) { + final Map eventData = e.eventData; + eventData.remove('elapsedMilliseconds'); + + if (const DeepCollectionEquality().equals(lookup, eventData)) { + return true; + } + } + + return false; +}