diff --git a/packages/flutter_tools/lib/runner.dart b/packages/flutter_tools/lib/runner.dart index 9c4e9a9759..a81493dc31 100644 --- a/packages/flutter_tools/lib/runner.dart +++ b/packages/flutter_tools/lib/runner.dart @@ -215,9 +215,8 @@ Future _doctorText() async { } Future _exit(int code) async { - if (flutterUsage.isFirstRun) { - flutterUsage.printWelcome(); - } + // Prints the welcome message if needed. + flutterUsage.printWelcome(); // Send any last analytics calls that are in progress without overly delaying // the tool's exit (we wait a maximum of 250ms). diff --git a/packages/flutter_tools/lib/src/base/config.dart b/packages/flutter_tools/lib/src/base/config.dart index e16ba3df7c..4526cef73f 100644 --- a/packages/flutter_tools/lib/src/base/config.dart +++ b/packages/flutter_tools/lib/src/base/config.dart @@ -5,11 +5,10 @@ import '../convert.dart'; import 'context.dart'; import 'file_system.dart'; -import 'platform.dart'; class Config { Config([File configFile]) { - _configFile = configFile ?? fs.file(fs.path.join(_userHomeDir(), '.flutter_settings')); + _configFile = configFile ?? fs.file(fs.path.join(userHomePath(), '.flutter_settings')); if (_configFile.existsSync()) { _values = json.decode(_configFile.readAsStringSync()); } @@ -44,8 +43,3 @@ class Config { _configFile.writeAsStringSync(json); } } - -String _userHomeDir() { - final String envKey = platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME'; - return platform.environment[envKey] ?? '.'; -} diff --git a/packages/flutter_tools/lib/src/base/file_system.dart b/packages/flutter_tools/lib/src/base/file_system.dart index 0e3d3fa8db..46f34567f8 100644 --- a/packages/flutter_tools/lib/src/base/file_system.dart +++ b/packages/flutter_tools/lib/src/base/file_system.dart @@ -185,3 +185,11 @@ class FileNotFoundException implements IOException { @override String toString() => 'File not found: $path'; } + +/// Reads the process environment to find the current user's home directory. +/// +/// If the searched environment variables are not set, '.' is returned instead. +String userHomePath() { + final String envKey = platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME'; + return platform.environment[envKey] ?? '.'; +} diff --git a/packages/flutter_tools/lib/src/commands/config.dart b/packages/flutter_tools/lib/src/commands/config.dart index c113603dcc..46a73bdd8e 100644 --- a/packages/flutter_tools/lib/src/commands/config.dart +++ b/packages/flutter_tools/lib/src/commands/config.dart @@ -117,6 +117,7 @@ class ConfigCommand extends FlutterCommand { if (argResults.wasParsed('analytics')) { final bool value = argResults['analytics']; flutterUsage.enabled = value; + AnalyticsConfigEvent(enabled: value).send(); printStatus('Analytics reporting ${value ? 'enabled' : 'disabled'}.'); } diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart index 71cc708f85..cf047e0bff 100644 --- a/packages/flutter_tools/lib/src/commands/upgrade.dart +++ b/packages/flutter_tools/lib/src/commands/upgrade.dart @@ -15,12 +15,14 @@ import '../base/process.dart'; import '../cache.dart'; import '../dart/pub.dart'; import '../globals.dart'; +import '../persistent_tool_state.dart'; import '../runner/flutter_command.dart'; import '../version.dart'; import 'channel.dart'; class UpgradeCommand extends FlutterCommand { - UpgradeCommand() { + UpgradeCommand([UpgradeCommandRunner commandRunner]) + : _commandRunner = commandRunner ?? UpgradeCommandRunner() { argParser ..addFlag( 'force', @@ -32,10 +34,14 @@ class UpgradeCommand extends FlutterCommand { 'continue', hide: true, negatable: false, - help: 'For the second half of the upgrade flow requiring the new version of Flutter. Should not be invoked manually, but re-entrantly by the standard upgrade command.', + help: 'For the second half of the upgrade flow requiring the new ' + 'version of Flutter. Should not be invoked manually, but ' + 're-entrantly by the standard upgrade command.', ); } + final UpgradeCommandRunner _commandRunner; + @override final String name = 'upgrade'; @@ -52,8 +58,7 @@ class UpgradeCommand extends FlutterCommand { @override Future runCommand() async { - final UpgradeCommandRunner upgradeCommandRunner = UpgradeCommandRunner(); - await upgradeCommandRunner.runCommand( + await _commandRunner.runCommand( argResults['force'], argResults['continue'], GitTagVersion.determine(), @@ -141,9 +146,13 @@ class UpgradeCommandRunner { // This method should only be called if the upgrade command is invoked // re-entrantly with the `--continue` flag Future runCommandSecondHalf(FlutterVersion flutterVersion) async { + // Make sure the welcome message re-display is delayed until the end. + persistentToolState.redisplayWelcomeMessage = false; await precacheArtifacts(); await updatePackages(flutterVersion); await runDoctor(); + // Force the welcome message to re-display following the upgrade. + persistentToolState.redisplayWelcomeMessage = true; } Future hasUncomittedChanges() async { diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index e210afd697..7a0998624a 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -48,6 +48,7 @@ import 'macos/macos_workflow.dart'; import 'macos/xcode.dart'; import 'macos/xcode_validator.dart'; import 'mdns_discovery.dart'; +import 'persistent_tool_state.dart'; import 'reporting/reporting.dart'; import 'run_hot.dart'; import 'version.dart'; @@ -106,9 +107,10 @@ Future runInContext( MacOSWorkflow: () => const MacOSWorkflow(), MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery(), OperatingSystemUtils: () => OperatingSystemUtils(), - Pub: () => const Pub(), + PersistentToolState: () => PersistentToolState(), ProcessInfo: () => ProcessInfo(), ProcessUtils: () => ProcessUtils(), + Pub: () => const Pub(), Signals: () => Signals(), SimControl: () => SimControl(), Stdio: () => const Stdio(), @@ -121,8 +123,8 @@ Future runInContext( WebWorkflow: () => const WebWorkflow(), WindowsWorkflow: () => const WindowsWorkflow(), Xcode: () => Xcode(), - XcodeValidator: () => const XcodeValidator(), XcodeProjectInterpreter: () => XcodeProjectInterpreter(), + XcodeValidator: () => const XcodeValidator(), }, ); } diff --git a/packages/flutter_tools/lib/src/persistent_tool_state.dart b/packages/flutter_tools/lib/src/persistent_tool_state.dart new file mode 100644 index 0000000000..71ded3a160 --- /dev/null +++ b/packages/flutter_tools/lib/src/persistent_tool_state.dart @@ -0,0 +1,41 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'base/config.dart'; +import 'base/context.dart'; +import 'base/file_system.dart'; + +PersistentToolState get persistentToolState => PersistentToolState.instance; + +/// A class that represents global (non-project-specific) internal state that +/// must persist across tool invocations. +abstract class PersistentToolState { + factory PersistentToolState([File configFile]) => + _DefaultPersistentToolState(configFile); + + static PersistentToolState get instance => context.get(); + + /// Whether the welcome message should be redisplayed. + /// + /// May give null if the value has not been set. + bool redisplayWelcomeMessage; +} + +class _DefaultPersistentToolState implements PersistentToolState { + _DefaultPersistentToolState([File configFile]) : + _config = Config(configFile ?? fs.file(fs.path.join(userHomePath(), _kFileName))); + + static const String _kFileName = '.flutter_tool_state'; + static const String _kRedisplayWelcomeMessage = 'redisplay-welcome-message'; + + final Config _config; + + @override + bool get redisplayWelcomeMessage => _config.getValue(_kRedisplayWelcomeMessage); + + @override + set redisplayWelcomeMessage(bool value) { + _config.setValue(_kRedisplayWelcomeMessage, value); + } +} diff --git a/packages/flutter_tools/lib/src/reporting/events.dart b/packages/flutter_tools/lib/src/reporting/events.dart index 4eac731f7a..3ab033cda4 100644 --- a/packages/flutter_tools/lib/src/reporting/events.dart +++ b/packages/flutter_tools/lib/src/reporting/events.dart @@ -199,3 +199,15 @@ class CommandResultEvent extends UsageEvent { } } } + +/// An event that reports on changes in the configuration of analytics. +class AnalyticsConfigEvent extends UsageEvent { + AnalyticsConfigEvent({ + /// Whether analytics reporting is being enabled (true) or disabled (false). + @required bool enabled, + }) : super( + 'analytics', + 'enabled', + label: enabled ? 'true' : 'false', + ); +} diff --git a/packages/flutter_tools/lib/src/reporting/reporting.dart b/packages/flutter_tools/lib/src/reporting/reporting.dart index 759f80809a..dd83b46250 100644 --- a/packages/flutter_tools/lib/src/reporting/reporting.dart +++ b/packages/flutter_tools/lib/src/reporting/reporting.dart @@ -21,6 +21,7 @@ import '../base/utils.dart'; import '../doctor.dart'; import '../features.dart'; import '../globals.dart'; +import '../persistent_tool_state.dart'; import '../runner/flutter_command.dart'; import '../version.dart'; diff --git a/packages/flutter_tools/lib/src/reporting/usage.dart b/packages/flutter_tools/lib/src/reporting/usage.dart index 01695e4fd0..e737439566 100644 --- a/packages/flutter_tools/lib/src/reporting/usage.dart +++ b/packages/flutter_tools/lib/src/reporting/usage.dart @@ -323,35 +323,55 @@ class _DefaultUsage implements Usage { await _analytics.waitForLastPing(timeout: const Duration(milliseconds: 250)); } - @override - void printWelcome() { - // This gets called if it's the first run by the selected command, if any, - // and on exit, in case there was no command. - if (_printedWelcome) { - return; - } - _printedWelcome = true; - + void _printWelcome() { printStatus(''); printStatus(''' ╔════════════════════════════════════════════════════════════════════════════╗ ║ Welcome to Flutter! - https://flutter.dev ║ ║ ║ - ║ The Flutter tool anonymously reports feature usage statistics and crash ║ - ║ reports to Google in order to help Google contribute improvements to ║ - ║ Flutter over time. ║ + ║ The Flutter tool uses Google Analytics to anonymously report feature usage ║ + ║ statistics and basic crash reports. This data is used to help improve ║ + ║ Flutter tools over time. ║ + ║ ║ + ║ Flutter tool analytics are not sent on the very first run. To disable ║ + ║ reporting, type 'flutter config --no-analytics'. To display the current ║ + ║ setting, type 'flutter config'. If you opt out of analytics, an opt-out ║ + ║ event will be sent, and then no further information will be sent by the ║ + ║ Flutter tool. ║ + ║ ║ + ║ By downloading the Flutter SDK, you agree to the Google Terms of Service. ║ + ║ Note: The Google Privacy Policy describes how data is handled in this ║ + ║ service. ║ + ║ ║ + ║ Moreover, Flutter includes the Dart SDK, which may send usage metrics and ║ + ║ crash reports to Google. ║ ║ ║ ║ Read about data we send with crash reports: ║ ║ https://github.com/flutter/flutter/wiki/Flutter-CLI-crash-reporting ║ ║ ║ ║ See Google's privacy policy: ║ ║ https://www.google.com/intl/en/policies/privacy/ ║ - ║ ║ - ║ Use "flutter config --no-analytics" to disable analytics and crash ║ - ║ reporting. ║ ╚════════════════════════════════════════════════════════════════════════════╝ ''', emphasis: true); } + + @override + void printWelcome() { + // Only print once per run. + if (_printedWelcome) { + return; + } + if (// Display the welcome message if this is the first run of the tool. + isFirstRun || + // Display the welcome message if we are not on master, and if the + // persistent tool state instructs that we should. + (!FlutterVersion.instance.isMaster && + (persistentToolState.redisplayWelcomeMessage ?? true))) { + _printWelcome(); + _printedWelcome = true; + persistentToolState.redisplayWelcomeMessage = false; + } + } } // An Analytics mock that logs to file. Unimplemented methods goes to stdout. diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 00087a1fd3..09e89abfa0 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -447,9 +447,8 @@ abstract class FlutterCommand extends Command { name: 'command', overrides: {FlutterCommand: () => this}, body: () async { - if (flutterUsage.isFirstRun) { - flutterUsage.printWelcome(); - } + // Prints the welcome message if needed. + flutterUsage.printWelcome(); final String commandPath = await usagePath; _registerSignalHandlers(commandPath, startTime); FlutterCommandResult commandResult; 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 ead9e5a396..d5b79b7e51 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart @@ -14,6 +14,7 @@ import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/config.dart'; +import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/version.dart'; import 'package:mockito/mockito.dart'; @@ -24,6 +25,7 @@ void main() { MockAndroidStudio mockAndroidStudio; MockAndroidSdk mockAndroidSdk; MockFlutterVersion mockFlutterVersion; + MockUsage mockUsage; setUpAll(() { Cache.disableLocking(); @@ -33,8 +35,31 @@ void main() { mockAndroidStudio = MockAndroidStudio(); mockAndroidSdk = MockAndroidSdk(); mockFlutterVersion = MockFlutterVersion(); + mockUsage = MockUsage(); + + when(mockUsage.isFirstRun).thenReturn(false); }); + void verifyNoAnalytics() { + verifyNever(mockUsage.sendCommand( + any, + parameters: anyNamed('parameters'), + )); + verifyNever(mockUsage.sendEvent( + any, + any, + label: anyNamed('label'), + value: anyNamed('value'), + parameters: anyNamed('parameters'), + )); + verifyNever(mockUsage.sendTiming( + any, + any, + any, + label: anyNamed('label'), + )); + } + group('config', () { testUsingContext('machine flag', () async { final BufferLogger logger = context.get(); @@ -50,9 +75,11 @@ void main() { expect(jsonObject.containsKey('android-sdk'), true); expect(jsonObject['android-sdk'], isNotNull); + verifyNoAnalytics(); }, overrides: { AndroidStudio: () => mockAndroidStudio, AndroidSdk: () => mockAndroidSdk, + Usage: () => mockUsage, }); testUsingContext('Can set build-dir', () async { @@ -65,6 +92,9 @@ void main() { ]); expect(getBuildDirectory(), 'foo'); + verifyNoAnalytics(); + }, overrides: { + Usage: () => mockUsage, }); testUsingContext('throws error on absolute path to build-dir', () async { @@ -75,6 +105,9 @@ void main() { 'config', '--build-dir=/foo', ]), throwsA(isInstanceOf())); + verifyNoAnalytics(); + }, overrides: { + Usage: () => mockUsage, }); testUsingContext('allows setting and removing feature flags', () async { @@ -115,9 +148,11 @@ void main() { expect(Config.instance.getValue('enable-linux-desktop'), false); expect(Config.instance.getValue('enable-windows-desktop'), false); expect(Config.instance.getValue('enable-macos-desktop'), false); + verifyNoAnalytics(); }, overrides: { AndroidStudio: () => mockAndroidStudio, AndroidSdk: () => mockAndroidSdk, + Usage: () => mockUsage, }); testUsingContext('displays which config settings are available on stable', () async { @@ -142,10 +177,86 @@ void main() { expect(logger.statusText, contains('enable-linux-desktop: true (Unavailable)')); expect(logger.statusText, contains('enable-windows-desktop: true (Unavailable)')); expect(logger.statusText, contains('enable-macos-desktop: true (Unavailable)')); + verifyNoAnalytics(); }, overrides: { AndroidStudio: () => mockAndroidStudio, AndroidSdk: () => mockAndroidSdk, FlutterVersion: () => mockFlutterVersion, + Usage: () => mockUsage, + }); + + testUsingContext('no-analytics flag flips usage flag and sends event', () async { + final ConfigCommand configCommand = ConfigCommand(); + final CommandRunner commandRunner = createTestCommandRunner(configCommand); + + await commandRunner.run([ + 'config', + '--no-analytics', + ]); + + expect(mockUsage.enabled, false); + + // Verify that we only send the analytics disable event, and no other + // info. + verifyNever(mockUsage.sendCommand( + any, + parameters: anyNamed('parameters'), + )); + verifyNever(mockUsage.sendTiming( + any, + any, + any, + label: anyNamed('label'), + )); + + expect(verify(mockUsage.sendEvent( + captureAny, + captureAny, + label: captureAnyNamed('label'), + value: anyNamed('value'), + parameters: anyNamed('parameters'), + )).captured, + ['analytics', 'enabled', 'false'], + ); + }, overrides: { + Usage: () => mockUsage, + }); + + testUsingContext('analytics flag flips usage flag and sends event', () async { + final ConfigCommand configCommand = ConfigCommand(); + final CommandRunner commandRunner = createTestCommandRunner(configCommand); + + await commandRunner.run([ + 'config', + '--analytics', + ]); + + expect(mockUsage.enabled, true); + + // Verify that we only send the analytics disable event, and no other + // info. + verifyNever(mockUsage.sendCommand( + any, + parameters: anyNamed('parameters'), + )); + verifyNever(mockUsage.sendTiming( + any, + any, + any, + label: anyNamed('label'), + )); + + expect(verify(mockUsage.sendEvent( + captureAny, + captureAny, + label: captureAnyNamed('label'), + value: anyNamed('value'), + parameters: anyNamed('parameters'), + )).captured, + ['analytics', 'enabled', 'true'], + ); + }, overrides: { + Usage: () => mockUsage, }); }); } @@ -161,3 +272,8 @@ class MockAndroidSdk extends Mock implements AndroidSdk { } class MockFlutterVersion extends Mock implements FlutterVersion {} + +class MockUsage extends Mock implements Usage { + @override + bool enabled = true; +} diff --git a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart index 49cc3b717f..d77d9846e6 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter_tools/runner.dart' as runner; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; @@ -9,6 +10,7 @@ import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/upgrade.dart'; +import 'package:flutter_tools/src/persistent_tool_state.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/version.dart'; import 'package:mockito/mockito.dart'; @@ -17,6 +19,7 @@ import 'package:process/process.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/fake_process_manager.dart'; import '../../src/mocks.dart'; void main() { @@ -184,6 +187,49 @@ void main() { ProcessManager: () => processManager, Platform: () => fakePlatform, }); + + group('full command', () { + final FakeProcessManager fakeProcessManager = FakeProcessManager.list([ + const FakeCommand(command: [ + 'git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags', + ]), + ]); + + Directory tempDir; + File flutterToolState; + + FlutterVersion mockFlutterVersion; + + setUp(() { + Cache.disableLocking(); + tempDir = fs.systemTempDirectory.createTempSync('flutter_upgrade_test.'); + flutterToolState = tempDir.childFile('.flutter_tool_state'); + mockFlutterVersion = MockFlutterVersion(isStable: true); + }); + + tearDown(() { + Cache.enableLocking(); + tryToDelete(tempDir); + }); + + testUsingContext('upgrade continue prints welcome message', () async { + final UpgradeCommand upgradeCommand = UpgradeCommand(fakeCommandRunner); + await runner.run( + [ + 'upgrade', + '--continue', + ], + [ + upgradeCommand, + ], + ); + expect(testLogger.statusText, contains('Welcome to Flutter!')); + }, overrides: { + FlutterVersion: () => mockFlutterVersion, + ProcessManager: () => fakeProcessManager, + PersistentToolState: () => PersistentToolState(flutterToolState), + }); + }); }); group('matchesGitLine', () { @@ -267,7 +313,6 @@ class FakeUpgradeCommandRunner extends UpgradeCommandRunner { Future runDoctor() async {} } -class MockFlutterVersion extends Mock implements FlutterVersion {} class MockProcess extends Mock implements Process {} class MockProcessManager extends Mock implements ProcessManager {} class FakeProcessResult implements ProcessResult { diff --git a/packages/flutter_tools/test/general.shard/persistent_tool_state_test.dart b/packages/flutter_tools/test/general.shard/persistent_tool_state_test.dart new file mode 100644 index 0000000000..5860db1772 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/persistent_tool_state_test.dart @@ -0,0 +1,31 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/persistent_tool_state.dart'; + +import '../src/common.dart'; +import '../src/testbed.dart'; + +void main() { + Testbed testbed; + + setUp(() { + testbed = Testbed(); + }); + + test('state can be set and persists', () => testbed.run(() { + final File stateFile = fs.file('.flutter_tool_state'); + final PersistentToolState state1 = PersistentToolState(stateFile); + expect(state1.redisplayWelcomeMessage, null); + state1.redisplayWelcomeMessage = true; + expect(stateFile.existsSync(), true); + expect(state1.redisplayWelcomeMessage, true); + state1.redisplayWelcomeMessage = false; + expect(state1.redisplayWelcomeMessage, false); + + final PersistentToolState state2 = PersistentToolState(stateFile); + expect(state2.redisplayWelcomeMessage, false); + })); +} diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart index 7f1fdff79d..116df60773 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.dart @@ -23,6 +23,7 @@ import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/ios/plist_parser.dart'; import 'package:flutter_tools/src/ios/simulators.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; +import 'package:flutter_tools/src/persistent_tool_state.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/version.dart'; @@ -71,12 +72,18 @@ void testUsingContext( } }); Config buildConfig(FileSystem fs) { - configDir = fs.systemTempDirectory.createTempSync('flutter_config_dir_test.'); + configDir ??= fs.systemTempDirectory.createTempSync('flutter_config_dir_test.'); final File settingsFile = fs.file( fs.path.join(configDir.path, '.flutter_settings') ); return Config(settingsFile); } + PersistentToolState buildPersistentToolState(FileSystem fs) { + configDir ??= fs.systemTempDirectory.createTempSync('flutter_config_dir_test.'); + final File toolStateFile = fs.file( + fs.path.join(configDir.path, '.flutter_tool_state')); + return PersistentToolState(toolStateFile); + } test(description, () async { await runInContext(() { @@ -96,6 +103,7 @@ void testUsingContext( OutputPreferences: () => OutputPreferences.test(), Logger: () => BufferLogger(), OperatingSystemUtils: () => FakeOperatingSystemUtils(), + PersistentToolState: () => buildPersistentToolState(fs), SimControl: () => MockSimControl(), Usage: () => FakeUsage(), XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(), diff --git a/packages/flutter_tools/test/src/testbed.dart b/packages/flutter_tools/test/src/testbed.dart index c192e680a6..98126a8fa7 100644 --- a/packages/flutter_tools/test/src/testbed.dart +++ b/packages/flutter_tools/test/src/testbed.dart @@ -64,7 +64,7 @@ final Map _testbedDefaults = { /// }); /// }) /// -/// test('Can delete a file', () => testBed.run(() { +/// test('Can delete a file', () => testbed.run(() { /// expect(fs.file('foo').existsSync(), true); /// fs.file('foo').deleteSync(); /// expect(fs.file('foo').existsSync(), false);