Implement feature flag system for flutter tools (#36138)


diff --git a/packages/flutter_tools/lib/src/base/config.dart b/packages/flutter_tools/lib/src/base/config.dart
index b3a505d..0fef57d 100644
--- a/packages/flutter_tools/lib/src/base/config.dart
+++ b/packages/flutter_tools/lib/src/base/config.dart
@@ -27,7 +27,7 @@
 
   dynamic getValue(String key) => _values[key];
 
-  void setValue(String key, String value) {
+  void setValue(String key, Object value) {
     _values[key] = value;
     _flushValues();
   }
diff --git a/packages/flutter_tools/lib/src/commands/config.dart b/packages/flutter_tools/lib/src/commands/config.dart
index bc28667..61a27e5 100644
--- a/packages/flutter_tools/lib/src/commands/config.dart
+++ b/packages/flutter_tools/lib/src/commands/config.dart
@@ -7,9 +7,11 @@
 import '../android/android_sdk.dart';
 import '../android/android_studio.dart';
 import '../convert.dart';
+import '../features.dart';
 import '../globals.dart';
 import '../reporting/usage.dart';
 import '../runner/flutter_command.dart';
+import '../version.dart';
 
 class ConfigCommand extends FlutterCommand {
   ConfigCommand({ bool verboseHelp = false }) {
@@ -26,6 +28,21 @@
       negatable: false,
       hide: !verboseHelp,
       help: 'Print config values as json.');
+    for (Feature feature in allFeatures) {
+      if (feature.configSetting == null) {
+        continue;
+      }
+      argParser.addFlag(
+        feature.configSetting,
+        help: feature.generateHelpMessage(),
+        negatable: true,
+      );
+    }
+    argParser.addFlag(
+      'clear-features',
+      help: 'Remove all configured features and restore them to the default values.',
+      negatable: false,
+    );
   }
 
   @override
@@ -45,14 +62,27 @@
   bool get shouldUpdateCache => false;
 
   @override
-  Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{};
-
-  @override
   String get usageFooter {
-    // List all config settings.
-    String values = config.keys.map<String>((String key) {
-      return '  $key: ${config.getValue(key)}';
-    }).join('\n');
+    // List all config settings. for feature flags, include whether they
+    // are available.
+    final Map<String, Feature> featuresByName = <String, Feature>{};
+    final String channel = FlutterVersion.instance.channel;
+    for (Feature feature in allFeatures) {
+      if (feature.configSetting != null) {
+        featuresByName[feature.configSetting] = feature;
+      }
+    }
+    String values = config.keys
+        .map<String>((String key) {
+          String configFooter = '';
+          if (featuresByName.containsKey(key)) {
+            final FeatureChannelSetting setting = featuresByName[key].getSettingForChannel(channel);
+            if (!setting.available) {
+              configFooter = '(Unavailable)';
+            }
+          }
+          return '  $key: ${config.getValue(key)} $configFooter';
+        }).join('\n');
     if (values.isEmpty)
       values = '  No settings have been configured.';
     return
@@ -71,6 +101,15 @@
       return null;
     }
 
+    if (argResults['clear-features']) {
+      for (Feature feature in allFeatures) {
+        if (feature.configSetting != null) {
+          config.removeValue(feature.configSetting);
+        }
+      }
+      return null;
+    }
+
     if (argResults.wasParsed('analytics')) {
       final bool value = argResults['analytics'];
       flutterUsage.enabled = value;
@@ -89,6 +128,17 @@
     if (argResults.wasParsed('clear-ios-signing-cert'))
       _updateConfig('ios-signing-cert', '');
 
+    for (Feature feature in allFeatures) {
+      if (feature.configSetting == null) {
+        continue;
+      }
+      if (argResults.wasParsed(feature.configSetting)) {
+        final bool keyValue = argResults[feature.configSetting];
+        config.setValue(feature.configSetting, keyValue);
+        printStatus('Setting "${feature.configSetting}" value to "$keyValue".');
+      }
+    }
+
     if (argResults.arguments.isEmpty)
       printStatus(usage);
 
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 69fee62..0cbab98 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -28,6 +28,7 @@
 import 'device.dart';
 import 'doctor.dart';
 import 'emulator.dart';
+import 'features.dart';
 import 'fuchsia/fuchsia_device.dart' show FuchsiaDeviceTools;
 import 'fuchsia/fuchsia_sdk.dart' show FuchsiaSdk, FuchsiaArtifacts;
 import 'fuchsia/fuchsia_workflow.dart' show FuchsiaWorkflow;
@@ -79,6 +80,7 @@
       Doctor: () => const Doctor(),
       DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
       EmulatorManager: () => EmulatorManager(),
+      FeatureFlags: () => const FeatureFlags(),
       Flags: () => const EmptyFlags(),
       FlutterVersion: () => FlutterVersion(const SystemClock()),
       FuchsiaArtifacts: () => FuchsiaArtifacts.find(),
diff --git a/packages/flutter_tools/lib/src/features.dart b/packages/flutter_tools/lib/src/features.dart
new file mode 100644
index 0000000..69157a8
--- /dev/null
+++ b/packages/flutter_tools/lib/src/features.dart
@@ -0,0 +1,220 @@
+// 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:meta/meta.dart';
+
+import 'base/config.dart';
+import 'base/context.dart';
+import 'base/platform.dart';
+import 'version.dart';
+
+/// The current [FeatureFlags] implementation.
+///
+/// If not injected, a default implementation is provided.
+FeatureFlags get featureFlags => context.get<FeatureFlags>();
+
+/// The interface used to determine if a particular [Feature] is enabled.
+///
+/// The rest of the tools code should use this class instead of looking up
+/// features directly. To faciliate rolls to google3 and other clients, all
+/// flags should be provided with a default implementation here. Clients that
+/// use this class should extent instead of implement, so that new flags are
+/// picked up automatically.
+class FeatureFlags {
+  const FeatureFlags();
+
+  /// Whether flutter desktop for linux is enabled.
+  bool get isLinuxEnabled => _isEnabled(flutterLinuxDesktopFeature);
+
+  /// Whether flutter desktop for macOS is enabled.
+  bool get isMacOSEnabled => _isEnabled(flutterMacOSDesktopFeature);
+
+  /// Whether flutter web is enabled.
+  bool get isWebEnabled => _isEnabled(flutterWebFeature);
+
+  /// Whether flutter desktop for Windows is enabled.
+  bool get isWindowsEnabled => _isEnabled(flutterWindowsDesktopFeature);
+
+  // Calculate whether a particular feature is enabled for the current channel.
+  static bool _isEnabled(Feature feature) {
+    final String currentChannel = FlutterVersion.instance.channel;
+    final FeatureChannelSetting featureSetting = feature.getSettingForChannel(currentChannel);
+    if (!featureSetting.available) {
+      return false;
+    }
+    bool isEnabled = featureSetting.enabledByDefault;
+    if (feature.configSetting != null) {
+      final bool configOverride = Config.instance.getValue(feature.configSetting);
+      if (configOverride != null) {
+        isEnabled = configOverride;
+      }
+    }
+    if (feature.environmentOverride != null) {
+      if (platform.environment[feature.environmentOverride]?.toLowerCase() == 'true') {
+        isEnabled = true;
+      }
+    }
+    return isEnabled;
+  }
+}
+
+/// All current Flutter feature flags.
+const List<Feature> allFeatures = <Feature>[
+  flutterWebFeature,
+  flutterLinuxDesktopFeature,
+  flutterMacOSDesktopFeature,
+  flutterWindowsDesktopFeature,
+];
+
+/// The [Feature] for flutter web.
+const Feature flutterWebFeature = Feature(
+  name: 'Flutter Web',
+  configSetting: 'enable-web',
+  environmentOverride: 'FLUTTER_WEB',
+  master: FeatureChannelSetting(
+    available: true,
+    enabledByDefault: false,
+  ),
+  dev: FeatureChannelSetting(
+    available: true,
+    enabledByDefault: false,
+  ),
+);
+
+/// The [Feature] for macOS desktop.
+const Feature flutterMacOSDesktopFeature = Feature(
+  name: 'Flutter Desktop for macOS',
+  configSetting: 'enable-macos-desktop',
+  environmentOverride: 'ENABLE_FLUTTER_DESKTOP',
+  master: FeatureChannelSetting(
+    available: true,
+    enabledByDefault: false,
+  ),
+);
+
+/// The [Feature] for Linux desktop.
+const Feature flutterLinuxDesktopFeature = Feature(
+  name: 'Flutter Desktop for Linux',
+  configSetting: 'enable-linux-desktop',
+  environmentOverride: 'ENABLE_FLUTTER_DESKTOP',
+  master: FeatureChannelSetting(
+    available: true,
+    enabledByDefault: false,
+  ),
+);
+
+/// The [Feature] for Windows desktop.
+const Feature flutterWindowsDesktopFeature = Feature(
+  name: 'Flutter Desktop for Windows',
+  configSetting: 'enable-windows-desktop',
+  environmentOverride: 'ENABLE_FLUTTER_DESKTOP',
+  master: FeatureChannelSetting(
+    available: true,
+    enabledByDefault: false,
+  ),
+);
+
+/// A [Feature] is a process for conditionally enabling tool features.
+///
+/// All settings are optional, and if not provided will generally default to
+/// a "safe" value, such as being off.
+///
+/// The top level feature settings can be provided to apply to all channels.
+/// Otherwise, more specific settings take precidence over higher level
+/// settings.
+class Feature {
+  /// Creates a [Feature].
+  const Feature({
+    @required this.name,
+    this.environmentOverride,
+    this.configSetting,
+    this.master = const FeatureChannelSetting(),
+    this.dev = const FeatureChannelSetting(),
+    this.beta = const FeatureChannelSetting(),
+    this.stable = const FeatureChannelSetting(),
+  });
+
+  /// The user visible name for this feature.
+  final String name;
+
+  /// The settings for the master branch and other unknown channels.
+  final FeatureChannelSetting master;
+
+  /// The settings for the dev branch.
+  final FeatureChannelSetting dev;
+
+  /// The settings for the beta branch.
+  final FeatureChannelSetting beta;
+
+  /// The settings for the stable branch.
+  final FeatureChannelSetting stable;
+
+  /// The name of an environment variable that can override the setting.
+  ///
+  /// The environment variable needs to be set to the value 'true'. This is
+  /// only intended for usage by CI and not as an advertised method to enable
+  /// a feature.
+  ///
+  /// If not provided, defaults to `null` meaning there is no override.
+  final String environmentOverride;
+
+  /// The name of a setting that can be used to enable this feature.
+  ///
+  /// If not provided, defaults to `null` meaning there is no config setting.
+  final String configSetting;
+
+  /// A help message for the `flutter config` command, or null if unsupported.
+  String generateHelpMessage() {
+    if (configSetting == null) {
+      return null;
+    }
+    final StringBuffer buffer = StringBuffer('Enable or disable $name on ');
+    final List<String> channels = <String>[
+      if (master.available) 'master',
+      if (dev.available) 'dev',
+      if (beta.available) 'beta',
+      if (stable.available) 'stable',
+    ];
+    if (channels.length == 1) {
+      buffer.write('the ${channels.single} channel.');
+    } else {
+      buffer.write('${channels.join(', ')} channels.');
+    }
+    return buffer.toString();
+  }
+
+  /// Retrieve the correct setting for the provided `channel`.
+  FeatureChannelSetting getSettingForChannel(String channel) {
+    switch (channel) {
+      case 'stable':
+        return stable;
+      case 'beta':
+        return beta;
+      case 'dev':
+        return dev;
+      case 'master':
+      default:
+        return master;
+    }
+  }
+}
+
+/// A description of the conditions to enable a feature for a particular channel.
+class FeatureChannelSetting {
+  const FeatureChannelSetting({
+    this.available = false,
+    this.enabledByDefault = false,
+  });
+
+  /// Whether the feature is available on this channel.
+  ///
+  /// If not provded, defaults to `false`. This implies that the feature
+  /// cannot be enabled even by the settings below.
+  final bool available;
+
+  /// Whether the feature is enabled by default.
+  ///
+  /// If not provided, defaults to `false`.
+  final bool enabledByDefault;
+}
diff --git a/packages/flutter_tools/lib/src/reporting/usage.dart b/packages/flutter_tools/lib/src/reporting/usage.dart
index 4f4d8ba..fbd6604 100644
--- a/packages/flutter_tools/lib/src/reporting/usage.dart
+++ b/packages/flutter_tools/lib/src/reporting/usage.dart
@@ -7,11 +7,13 @@
 import 'package:meta/meta.dart';
 import 'package:usage/usage_io.dart';
 
+import '../base/config.dart';
 import '../base/context.dart';
 import '../base/file_system.dart';
 import '../base/os.dart';
 import '../base/platform.dart';
 import '../base/utils.dart';
+import '../features.dart';
 import '../globals.dart';
 import '../version.dart';
 
@@ -55,7 +57,9 @@
 const String reloadExceptionSdkName = 'cd28';
 const String reloadExceptionEmulator = 'cd29';
 const String reloadExceptionFullRestart = 'cd30';
-// Next ID: cd32
+
+const String enabledFlutterFeatures = 'cd32';
+// Next ID: cd33
 
 Usage get flutterUsage => Usage.instance;
 
@@ -72,6 +76,17 @@
     _analytics.setSessionValue(kSessionHostOsDetails, os.name);
     // Send the branch name as the "channel".
     _analytics.setSessionValue(kSessionChannelName, flutterVersion.getBranchName(redactUnknownBranches: true));
+    // For each flutter experimental feature, record a session value in a comma
+    // separated list.
+    final String enabledFeatures = allFeatures
+        .where((Feature feature) {
+          return feature.configSetting != null &&
+                 Config.instance.getValue(feature.configSetting) == true;
+        })
+        .map((Feature feature) => feature.configSetting)
+        .join(',');
+    _analytics.setSessionValue(enabledFlutterFeatures, enabledFeatures);
+
     // Record the host as the application installer ID - the context that flutter_tools is running in.
     if (platform.environment.containsKey('FLUTTER_HOST')) {
       _analytics.setSessionValue('aiid', platform.environment['FLUTTER_HOST']);
diff --git a/packages/flutter_tools/test/general.shard/commands/config_test.dart b/packages/flutter_tools/test/general.shard/commands/config_test.dart
index b95b4ed..eaaa831 100644
--- a/packages/flutter_tools/test/general.shard/commands/config_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/config_test.dart
@@ -4,11 +4,15 @@
 
 import 'dart:convert';
 
+import 'package:args/command_runner.dart';
 import 'package:flutter_tools/src/android/android_sdk.dart';
 import 'package:flutter_tools/src/android/android_studio.dart';
+import 'package:flutter_tools/src/base/config.dart';
 import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/commands/config.dart';
+import 'package:flutter_tools/src/version.dart';
 import 'package:mockito/mockito.dart';
 
 import '../../src/common.dart';
@@ -17,10 +21,16 @@
 void main() {
   MockAndroidStudio mockAndroidStudio;
   MockAndroidSdk mockAndroidSdk;
+  MockFlutterVersion mockFlutterVersion;
+
+  setUpAll(() {
+    Cache.disableLocking();
+  });
 
   setUp(() {
     mockAndroidStudio = MockAndroidStudio();
     mockAndroidSdk = MockAndroidSdk();
+    mockFlutterVersion = MockFlutterVersion();
   });
 
   group('config', () {
@@ -42,6 +52,77 @@
       AndroidStudio: () => mockAndroidStudio,
       AndroidSdk: () => mockAndroidSdk,
     });
+
+    testUsingContext('allows setting and removing feature flags', () async {
+      final ConfigCommand configCommand = ConfigCommand();
+      final CommandRunner<void> commandRunner = createTestCommandRunner(configCommand);
+
+      await commandRunner.run(<String>[
+        'config',
+        '--enable-web',
+        '--enable-linux-desktop',
+        '--enable-windows-desktop',
+        '--enable-macos-desktop'
+      ]);
+
+      expect(Config.instance.getValue('enable-web'), true);
+      expect(Config.instance.getValue('enable-linux-desktop'), true);
+      expect(Config.instance.getValue('enable-windows-desktop'), true);
+      expect(Config.instance.getValue('enable-macos-desktop'), true);
+
+      await commandRunner.run(<String>[
+        'config', '--clear-features',
+      ]);
+
+      expect(Config.instance.getValue('enable-web'), null);
+      expect(Config.instance.getValue('enable-linux-desktop'), null);
+      expect(Config.instance.getValue('enable-windows-desktop'), null);
+      expect(Config.instance.getValue('enable-macos-desktop'), null);
+
+      await commandRunner.run(<String>[
+        'config',
+        '--no-enable-web',
+        '--no-enable-linux-desktop',
+        '--no-enable-windows-desktop',
+        '--no-enable-macos-desktop'
+      ]);
+
+      expect(Config.instance.getValue('enable-web'), false);
+      expect(Config.instance.getValue('enable-linux-desktop'), false);
+      expect(Config.instance.getValue('enable-windows-desktop'), false);
+      expect(Config.instance.getValue('enable-macos-desktop'), false);
+    }, overrides: <Type, Generator>{
+      AndroidStudio: () => mockAndroidStudio,
+      AndroidSdk: () => mockAndroidSdk,
+    });
+
+    testUsingContext('displays which config settings are available on stable', () async {
+      final BufferLogger logger = context.get<Logger>();
+      when(mockFlutterVersion.channel).thenReturn('stable');
+      final ConfigCommand configCommand = ConfigCommand();
+      final CommandRunner<void> commandRunner = createTestCommandRunner(configCommand);
+
+      await commandRunner.run(<String>[
+        'config',
+        '--enable-web',
+        '--enable-linux-desktop',
+        '--enable-windows-desktop',
+        '--enable-macos-desktop'
+      ]);
+
+      await commandRunner.run(<String>[
+        'config',
+      ]);
+
+      expect(logger.statusText, contains('enable-web: true (Unavailable)'));
+      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)'));
+    }, overrides: <Type, Generator>{
+      AndroidStudio: () => mockAndroidStudio,
+      AndroidSdk: () => mockAndroidSdk,
+      FlutterVersion: () => mockFlutterVersion,
+    });
   });
 }
 
@@ -54,3 +135,5 @@
   @override
   String get directory => 'path/to/android/sdk';
 }
+
+class MockFlutterVersion extends Mock implements FlutterVersion {}
\ No newline at end of file
diff --git a/packages/flutter_tools/test/general.shard/config_test.dart b/packages/flutter_tools/test/general.shard/config_test.dart
index 8217375..4e64db3 100644
--- a/packages/flutter_tools/test/general.shard/config_test.dart
+++ b/packages/flutter_tools/test/general.shard/config_test.dart
@@ -29,6 +29,13 @@
       expect(config.keys, contains('foo'));
     });
 
+    test('get set bool value', () async {
+      expect(config.getValue('foo'), null);
+      config.setValue('foo', true);
+      expect(config.getValue('foo'), true);
+      expect(config.keys, contains('foo'));
+    });
+
     test('containsKey', () async {
       expect(config.containsKey('foo'), false);
       config.setValue('foo', 'bar');
diff --git a/packages/flutter_tools/test/general.shard/features_test.dart b/packages/flutter_tools/test/general.shard/features_test.dart
new file mode 100644
index 0000000..94b336b
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/features_test.dart
@@ -0,0 +1,428 @@
+// 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/config.dart';
+import 'package:flutter_tools/src/features.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/version.dart';
+import 'package:mockito/mockito.dart';
+
+import '../src/common.dart';
+import '../src/testbed.dart';
+
+void main() {
+  group('Features', () {
+    MockFlutterVerion mockFlutterVerion;
+    MockFlutterConfig mockFlutterConfig;
+    MockPlatform mockPlatform;
+    Testbed testbed;
+
+    setUp(() {
+      mockFlutterVerion = MockFlutterVerion();
+      mockFlutterConfig = MockFlutterConfig();
+      mockPlatform = MockPlatform();
+      when<bool>(mockFlutterConfig.getValue(any)).thenReturn(false);
+      when(mockPlatform.environment).thenReturn(const <String, String>{});
+      testbed = Testbed(overrides: <Type, Generator>{
+        FlutterVersion: () => mockFlutterVerion,
+        FeatureFlags: () => const FeatureFlags(),
+        Config: () => mockFlutterConfig,
+        Platform: () => mockPlatform,
+      });
+    });
+
+    test('setting has safe defaults', () {
+      const FeatureChannelSetting featureSetting = FeatureChannelSetting();
+
+      expect(featureSetting.available, false);
+      expect(featureSetting.enabledByDefault, false);
+    });
+
+    test('has safe defaults', () {
+      const Feature feature = Feature(name: 'example');
+
+      expect(feature.name, 'example');
+      expect(feature.environmentOverride, null);
+      expect(feature.configSetting, null);
+    });
+
+    test('retrieves the correct setting for each branch', () {
+      final FeatureChannelSetting masterSetting = FeatureChannelSetting(available: nonconst(true));
+      final FeatureChannelSetting devSetting = FeatureChannelSetting(available: nonconst(true));
+      final FeatureChannelSetting betaSetting = FeatureChannelSetting(available: nonconst(true));
+      final FeatureChannelSetting stableSetting = FeatureChannelSetting(available: nonconst(true));
+      final Feature feature = Feature(
+        name: 'example',
+        master: masterSetting,
+        dev: devSetting,
+        beta: betaSetting,
+        stable: stableSetting,
+      );
+
+      expect(feature.getSettingForChannel('master'), masterSetting);
+      expect(feature.getSettingForChannel('dev'), devSetting);
+      expect(feature.getSettingForChannel('beta'), betaSetting);
+      expect(feature.getSettingForChannel('stable'), stableSetting);
+      expect(feature.getSettingForChannel('unknown'), masterSetting);
+    });
+
+    test('env variables are only enabled with "true" string', () => testbed.run(() {
+      when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_WEB': 'hello'});
+
+      expect(featureFlags.isWebEnabled, false);
+
+      when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_WEB': 'true'});
+
+      expect(featureFlags.isWebEnabled, true);
+    }));
+
+    test('flutter web help string', () {
+      expect(flutterWebFeature.generateHelpMessage(), 'Enable or disable Flutter Web on master, dev channels.');
+    });
+
+    test('flutter macOS desktop help string', () {
+      expect(flutterMacOSDesktopFeature.generateHelpMessage(), 'Enable or disable Flutter Desktop for macOS on the master channel.');
+    });
+
+    test('flutter Linux desktop help string', () {
+      expect(flutterLinuxDesktopFeature.generateHelpMessage(), 'Enable or disable Flutter Desktop for Linux on the master channel.');
+    });
+
+    test('flutter Windows desktop help string', () {
+      expect(flutterWindowsDesktopFeature.generateHelpMessage(), 'Enable or disable Flutter Desktop for Windows on the master channel.');
+    });
+
+    /// Flutter Web
+
+    test('flutter web off by default on master', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('master');
+
+      expect(featureFlags.isWebEnabled, false);
+    }));
+
+    test('flutter web enabled with config on master', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('master');
+      when<bool>(mockFlutterConfig.getValue('enable-web')).thenReturn(true);
+
+      expect(featureFlags.isWebEnabled, true);
+    }));
+
+    test('flutter web enabled with environment variable on master', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('master');
+      when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_WEB': 'true'});
+
+      expect(featureFlags.isWebEnabled, true);
+    }));
+
+    test('flutter web off by default on dev', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('dev');
+
+      expect(featureFlags.isWebEnabled, false);
+    }));
+
+    test('flutter web enabled with config on dev', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('dev');
+      when<bool>(mockFlutterConfig.getValue('enable-web')).thenReturn(true);
+
+      expect(featureFlags.isWebEnabled, true);
+    }));
+
+    test('flutter web enabled with environment variable on dev', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('dev');
+      when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_WEB': 'true'});
+
+      expect(featureFlags.isWebEnabled, true);
+    }));
+
+    test('flutter web off by default on beta', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('beta');
+
+      expect(featureFlags.isWebEnabled, false);
+    }));
+
+    test('flutter web not enabled with config on beta', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('beta');
+      when<bool>(mockFlutterConfig.getValue('enable-web')).thenReturn(true);
+
+      expect(featureFlags.isWebEnabled, false);
+    }));
+
+    test('flutter web not enabled with environment variable on beta', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('beta');
+      when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_WEB': 'true'});
+
+      expect(featureFlags.isWebEnabled, false);
+    }));
+
+    test('flutter web off by default on stable', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('stable');
+
+      expect(featureFlags.isWebEnabled, false);
+    }));
+
+    test('flutter web not enabled with config on stable', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('stable');
+      when<bool>(mockFlutterConfig.getValue('enable-web')).thenReturn(true);
+
+      expect(featureFlags.isWebEnabled, false);
+    }));
+
+    test('flutter web not enabled with environment variable on stable', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('stable');
+      when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_WEB': 'enabled'});
+
+      expect(featureFlags.isWebEnabled, false);
+    }));
+
+    /// Flutter macOS desktop.
+
+    test('flutter macos desktop off by default on master', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('master');
+
+      expect(featureFlags.isMacOSEnabled, false);
+    }));
+
+    test('flutter macos desktop enabled with config on master', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('master');
+      when<bool>(mockFlutterConfig.getValue('enable-macos-desktop')).thenReturn(true);
+
+      expect(featureFlags.isMacOSEnabled, true);
+    }));
+
+    test('flutter macos desktop enabled with environment variable on master', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('master');
+      when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+      expect(featureFlags.isMacOSEnabled, true);
+    }));
+
+    test('flutter macos desktop off by default on dev', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('dev');
+
+      expect(featureFlags.isMacOSEnabled, false);
+    }));
+
+    test('flutter macos desktop not enabled with config on dev', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('dev');
+      when<bool>(mockFlutterConfig.getValue('flutter-desktop-macos')).thenReturn(true);
+
+      expect(featureFlags.isMacOSEnabled, false);
+    }));
+
+    test('flutter macos desktop not enabled with environment variable on dev', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('dev');
+      when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+      expect(featureFlags.isMacOSEnabled, false);
+    }));
+
+    test('flutter macos desktop off by default on beta', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('beta');
+
+      expect(featureFlags.isMacOSEnabled, false);
+    }));
+
+    test('fflutter macos desktop not enabled with config on beta', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('beta');
+      when<bool>(mockFlutterConfig.getValue('flutter-desktop-macos')).thenReturn(true);
+
+      expect(featureFlags.isMacOSEnabled, false);
+    }));
+
+    test('flutter macos desktop not enabled with environment variable on beta', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('beta');
+      when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+      expect(featureFlags.isMacOSEnabled, false);
+    }));
+
+    test('flutter macos desktop off by default on stable', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('stable');
+
+      expect(featureFlags.isMacOSEnabled, false);
+    }));
+
+    test('flutter macos desktop not enabled with config on stable', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('stable');
+      when<bool>(mockFlutterConfig.getValue('flutter-desktop-macos')).thenReturn(true);
+
+      expect(featureFlags.isMacOSEnabled, false);
+    }));
+
+    test('flutter macos desktop not enabled with environment variable on stable', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('stable');
+      when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+      expect(featureFlags.isMacOSEnabled, false);
+    }));
+
+    /// Flutter Linux Desktop
+    test('flutter linux desktop off by default on master', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('master');
+
+      expect(featureFlags.isLinuxEnabled, false);
+    }));
+
+    test('flutter linux desktop enabled with config on master', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('master');
+      when<bool>(mockFlutterConfig.getValue('enable-linux-desktop')).thenReturn(true);
+
+      expect(featureFlags.isLinuxEnabled, true);
+    }));
+
+    test('flutter linux desktop enabled with environment variable on master', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('master');
+      when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+      expect(featureFlags.isLinuxEnabled, true);
+    }));
+
+    test('flutter linux desktop off by default on dev', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('dev');
+
+      expect(featureFlags.isLinuxEnabled, false);
+    }));
+
+    test('flutter linux desktop not enabled with config on dev', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('dev');
+      when<bool>(mockFlutterConfig.getValue('enable-linux-desktop')).thenReturn(true);
+
+      expect(featureFlags.isLinuxEnabled, false);
+    }));
+
+    test('flutter linux desktop not enabled with environment variable on dev', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('dev');
+      when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+      expect(featureFlags.isLinuxEnabled, false);
+    }));
+
+    test('flutter linux desktop off by default on beta', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('beta');
+
+      expect(featureFlags.isLinuxEnabled, false);
+    }));
+
+    test('fflutter linux desktop not enabled with config on beta', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('beta');
+      when<bool>(mockFlutterConfig.getValue('enable-linux-desktop')).thenReturn(true);
+
+      expect(featureFlags.isLinuxEnabled, false);
+    }));
+
+    test('flutter linux desktop not enabled with environment variable on beta', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('beta');
+      when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+      expect(featureFlags.isLinuxEnabled, false);
+    }));
+
+    test('flutter linux desktop off by default on stable', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('stable');
+
+      expect(featureFlags.isLinuxEnabled, false);
+    }));
+
+    test('flutter linux desktop not enabled with config on stable', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('stable');
+      when<bool>(mockFlutterConfig.getValue('enable-linux-desktop')).thenReturn(true);
+
+      expect(featureFlags.isLinuxEnabled, false);
+    }));
+
+    test('flutter linux desktop not enabled with environment variable on stable', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('stable');
+      when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+      expect(featureFlags.isLinuxEnabled, false);
+    }));
+
+    /// Flutter Windows desktop.
+    test('flutter windows desktop off by default on master', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('master');
+
+      expect(featureFlags.isWindowsEnabled, false);
+    }));
+
+    test('flutter windows desktop enabled with config on master', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('master');
+      when<bool>(mockFlutterConfig.getValue('enable-windows-desktop')).thenReturn(true);
+
+      expect(featureFlags.isWindowsEnabled, true);
+    }));
+
+    test('flutter windows desktop enabled with environment variable on master', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('master');
+      when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+      expect(featureFlags.isWindowsEnabled, true);
+    }));
+
+    test('flutter windows desktop off by default on dev', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('dev');
+
+      expect(featureFlags.isWindowsEnabled, false);
+    }));
+
+    test('flutter windows desktop not enabled with config on dev', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('dev');
+      when<bool>(mockFlutterConfig.getValue('enable-windows-desktop')).thenReturn(true);
+
+      expect(featureFlags.isWindowsEnabled, false);
+    }));
+
+    test('flutter windows desktop not enabled with environment variable on dev', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('dev');
+      when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+      expect(featureFlags.isWindowsEnabled, false);
+    }));
+
+    test('flutter windows desktop off by default on beta', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('beta');
+
+      expect(featureFlags.isWindowsEnabled, false);
+    }));
+
+    test('fflutter windows desktop not enabled with config on beta', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('beta');
+      when<bool>(mockFlutterConfig.getValue('enable-windows-desktop')).thenReturn(true);
+
+      expect(featureFlags.isWindowsEnabled, false);
+    }));
+
+    test('flutter windows desktop not enabled with environment variable on beta', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('beta');
+      when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+      expect(featureFlags.isWindowsEnabled, false);
+    }));
+
+    test('flutter windows desktop off by default on stable', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('stable');
+
+      expect(featureFlags.isWindowsEnabled, false);
+    }));
+
+    test('flutter windows desktop not enabled with config on stable', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('stable');
+      when<bool>(mockFlutterConfig.getValue('enable-windows-desktop')).thenReturn(true);
+
+      expect(featureFlags.isWindowsEnabled, false);
+    }));
+
+    test('flutter windows desktop not enabled with environment variable on stable', () => testbed.run(() {
+      when(mockFlutterVerion.channel).thenReturn('stable');
+      when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+      expect(featureFlags.isWindowsEnabled, false);
+    }));
+  });
+}
+
+class MockFlutterVerion extends Mock implements FlutterVersion {}
+class MockFlutterConfig extends Mock implements Config {}
+class MockPlatform extends Mock implements Platform {}
+
+T nonconst<T>(T item) => item;