Ensure that cache dirs and files have appropriate permissions (#28090)

This is a partial re-application of #24669, which was
reverted due to Fuchsia breakages.

https://github.com/flutter/flutter/issues/24413
diff --git a/packages/flutter_tools/lib/src/base/os.dart b/packages/flutter_tools/lib/src/base/os.dart
index 1cfa6ff..7286e84 100644
--- a/packages/flutter_tools/lib/src/base/os.dart
+++ b/packages/flutter_tools/lib/src/base/os.dart
@@ -27,7 +27,15 @@
   OperatingSystemUtils._private();
 
   /// Make the given file executable. This may be a no-op on some platforms.
-  ProcessResult makeExecutable(File file);
+  void makeExecutable(File file);
+
+  /// Updates the specified file system [entity] to have the file mode
+  /// bits set to the value defined by [mode], which can be specified in octal
+  /// (e.g. `644`) or symbolically (e.g. `u+x`).
+  ///
+  /// On operating systems that do not support file mode bits, this will be a
+  /// no-op.
+  void chmod(FileSystemEntity entity, String mode);
 
   /// Return the path (with symlinks resolved) to the given executable, or null
   /// if `which` was not able to locate the binary.
@@ -111,8 +119,24 @@
   _PosixUtils() : super._private();
 
   @override
-  ProcessResult makeExecutable(File file) {
-    return processManager.runSync(<String>['chmod', 'a+x', file.path]);
+  void makeExecutable(File file) {
+    chmod(file, 'a+x');
+  }
+
+  @override
+  void chmod(FileSystemEntity entity, String mode) {
+    try {
+      final ProcessResult result = processManager.runSync(<String>['chmod', mode, entity.path]);
+      if (result.exitCode != 0) {
+        printTrace(
+          'Error trying to run chmod on ${entity.absolute.path}'
+          '\nstdout: ${result.stdout}'
+          '\nstderr: ${result.stderr}',
+        );
+      }
+    } on ProcessException catch (error) {
+      printTrace('Error trying to run chmod on ${entity.absolute.path}: $error');
+    }
   }
 
   @override
@@ -185,11 +209,11 @@
 class _WindowsUtils extends OperatingSystemUtils {
   _WindowsUtils() : super._private();
 
-  // This is a no-op.
   @override
-  ProcessResult makeExecutable(File file) {
-    return ProcessResult(0, 0, null, null);
-  }
+  void makeExecutable(File file) {}
+
+  @override
+  void chmod(FileSystemEntity entity, String mode) {}
 
   @override
   List<File> _which(String execName, { bool all = false }) {
diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart
index 7c6cdbc..857f379 100644
--- a/packages/flutter_tools/lib/src/cache.dart
+++ b/packages/flutter_tools/lib/src/cache.dart
@@ -207,8 +207,10 @@
   /// Return a directory in the cache dir. For `pkg`, this will return `bin/cache/pkg`.
   Directory getCacheDir(String name) {
     final Directory dir = fs.directory(fs.path.join(getRoot().path, name));
-    if (!dir.existsSync())
+    if (!dir.existsSync()) {
       dir.createSync(recursive: true);
+      os.chmod(dir, '755');
+    }
     return dir;
   }
 
@@ -280,8 +282,10 @@
     final Directory thirdPartyDir = getArtifactDirectory('third_party');
 
     final Directory serviceDir = fs.directory(fs.path.join(thirdPartyDir.path, serviceName));
-    if (!serviceDir.existsSync())
+    if (!serviceDir.existsSync()) {
       serviceDir.createSync(recursive: true);
+      os.chmod(serviceDir, '755');
+    }
 
     final File cachedFile = fs.file(fs.path.join(serviceDir.path, url.pathSegments.last));
     if (!cachedFile.existsSync()) {
@@ -633,13 +637,16 @@
     return true;
   }
 
-
   void _makeFilesExecutable(Directory dir) {
-    for (FileSystemEntity entity in dir.listSync()) {
+    os.chmod(dir, 'a+r,a+x');
+    for (FileSystemEntity entity in dir.listSync(recursive: true)) {
       if (entity is File) {
-        final String name = fs.path.basename(entity.path);
-        if (name == 'flutter_tester')
-          os.makeExecutable(entity);
+        final FileStat stat = entity.statSync();
+        final bool isUserExecutable = ((stat.mode >> 6) & 0x1) == 1;
+        if (entity.basename == 'flutter_tester' || isUserExecutable) {
+          // Make the file readable and executable by all users.
+          os.chmod(entity, 'a+r,a+x');
+        }
       }
     }
   }
diff --git a/packages/flutter_tools/test/general.shard/artifacts_test.dart b/packages/flutter_tools/test/general.shard/artifacts_test.dart
index fc522c8..ee78164 100644
--- a/packages/flutter_tools/test/general.shard/artifacts_test.dart
+++ b/packages/flutter_tools/test/general.shard/artifacts_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:file/memory.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/platform.dart';
 import 'package:flutter_tools/src/build_info.dart';
@@ -12,102 +13,106 @@
 import '../src/context.dart';
 
 void main() {
-  group('CachedArtifacts', () {
-
+  group('Artifacts', () {
+    MemoryFileSystem memoryFileSystem;
     Directory tempDir;
-    CachedArtifacts artifacts;
 
     setUp(() {
-      tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_artifacts_test_cached.');
-      artifacts = CachedArtifacts();
+      memoryFileSystem = MemoryFileSystem();
+      tempDir = memoryFileSystem.systemTempDirectory.createTempSync('artifacts_test.');
     });
 
     tearDown(() {
       tryToDelete(tempDir);
     });
 
-    testUsingContext('getArtifactPath', () {
-      expect(
+    group('CachedArtifacts', () {
+      CachedArtifacts artifacts;
+
+      setUp(() {
+        artifacts = CachedArtifacts();
+      });
+
+      testUsingContext('getArtifactPath', () {
+        expect(
           artifacts.getArtifactPath(Artifact.flutterFramework, platform: TargetPlatform.ios, mode: BuildMode.release),
           fs.path.join(tempDir.path, 'bin', 'cache', 'artifacts', 'engine', 'ios-release', 'Flutter.framework'),
-      );
-      expect(
+        );
+        expect(
           artifacts.getArtifactPath(Artifact.flutterTester),
           fs.path.join(tempDir.path, 'bin', 'cache', 'artifacts', 'engine', 'linux-x64', 'flutter_tester'),
-      );
-    }, overrides: <Type, Generator>{
-      Cache: () => Cache(rootOverride: tempDir),
-      Platform: () => FakePlatform(operatingSystem: 'linux'),
-    });
+        );
+      }, overrides: <Type, Generator>{
+        Cache: () => Cache(rootOverride: tempDir),
+        FileSystem: () => memoryFileSystem,
+        Platform: () => FakePlatform(operatingSystem: 'linux'),
+      });
 
-    testUsingContext('getEngineType', () {
-      expect(
+      testUsingContext('getEngineType', () {
+        expect(
           artifacts.getEngineType(TargetPlatform.android_arm, BuildMode.debug),
           'android-arm',
-      );
-      expect(
+        );
+        expect(
           artifacts.getEngineType(TargetPlatform.ios, BuildMode.release),
           'ios-release',
-      );
-      expect(
+        );
+        expect(
           artifacts.getEngineType(TargetPlatform.darwin_x64),
           'darwin-x64',
-      );
-    }, overrides: <Type, Generator>{
-      Cache: () => Cache(rootOverride: tempDir),
-      Platform: () => FakePlatform(operatingSystem: 'linux'),
-    });
-  });
-
-  group('LocalEngineArtifacts', () {
-
-    Directory tempDir;
-    LocalEngineArtifacts artifacts;
-
-    setUp(() {
-      tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_artifacts_test_local.');
-      artifacts = LocalEngineArtifacts(tempDir.path,
-        fs.path.join(tempDir.path, 'out', 'android_debug_unopt'),
-        fs.path.join(tempDir.path, 'out', 'host_debug_unopt'),
-      );
+        );
+      }, overrides: <Type, Generator>{
+        Cache: () => Cache(rootOverride: tempDir),
+        FileSystem: () => memoryFileSystem,
+        Platform: () => FakePlatform(operatingSystem: 'linux'),
+      });
     });
 
-    tearDown(() {
-      tryToDelete(tempDir);
-    });
+    group('LocalEngineArtifacts', () {
+      LocalEngineArtifacts artifacts;
 
-    testUsingContext('getArtifactPath', () {
-      expect(
+      setUp(() {
+        artifacts = LocalEngineArtifacts(tempDir.path,
+          memoryFileSystem.path.join(tempDir.path, 'out', 'android_debug_unopt'),
+          memoryFileSystem.path.join(tempDir.path, 'out', 'host_debug_unopt'),
+        );
+      });
+
+      testUsingContext('getArtifactPath', () {
+        expect(
           artifacts.getArtifactPath(Artifact.flutterFramework, platform: TargetPlatform.ios, mode: BuildMode.release),
           fs.path.join(tempDir.path, 'out', 'android_debug_unopt', 'Flutter.framework'),
-      );
-      expect(
+        );
+        expect(
           artifacts.getArtifactPath(Artifact.flutterTester),
           fs.path.join(tempDir.path, 'out', 'android_debug_unopt', 'flutter_tester'),
-      );
-      expect(
-        artifacts.getArtifactPath(Artifact.engineDartSdkPath),
-        fs.path.join(tempDir.path, 'out', 'host_debug_unopt', 'dart-sdk'),
-      );
-    }, overrides: <Type, Generator>{
-      Platform: () => FakePlatform(operatingSystem: 'linux'),
-    });
+        );
+        expect(
+          artifacts.getArtifactPath(Artifact.engineDartSdkPath),
+          fs.path.join(tempDir.path, 'out', 'host_debug_unopt', 'dart-sdk'),
+        );
+      }, overrides: <Type, Generator>{
+        FileSystem: () => memoryFileSystem,
+        Platform: () => FakePlatform(operatingSystem: 'linux'),
+      });
 
-    testUsingContext('getEngineType', () {
-      expect(
+      testUsingContext('getEngineType', () {
+        expect(
           artifacts.getEngineType(TargetPlatform.android_arm, BuildMode.debug),
           'android_debug_unopt',
-      );
-      expect(
+        );
+        expect(
           artifacts.getEngineType(TargetPlatform.ios, BuildMode.release),
           'android_debug_unopt',
-      );
-      expect(
+        );
+        expect(
           artifacts.getEngineType(TargetPlatform.darwin_x64),
           'android_debug_unopt',
-      );
-    }, overrides: <Type, Generator>{
-      Platform: () => FakePlatform(operatingSystem: 'linux'),
+        );
+      }, overrides: <Type, Generator>{
+        FileSystem: () => memoryFileSystem,
+        Platform: () => FakePlatform(operatingSystem: 'linux'),
+      });
     });
   });
 }
diff --git a/packages/flutter_tools/test/general.shard/cache_test.dart b/packages/flutter_tools/test/general.shard/cache_test.dart
index ca29c69..e2fe07e 100644
--- a/packages/flutter_tools/test/general.shard/cache_test.dart
+++ b/packages/flutter_tools/test/general.shard/cache_test.dart
@@ -6,14 +6,20 @@
 
 import 'package:file/file.dart';
 import 'package:file/memory.dart';
+import 'package:file_testing/file_testing.dart';
+import 'package:meta/meta.dart';
 import 'package:mockito/mockito.dart';
 import 'package:platform/platform.dart';
 
 import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart' show InternetAddress, SocketException;
+import 'package:flutter_tools/src/base/net.dart';
+import 'package:flutter_tools/src/base/os.dart';
 
 import '../src/common.dart';
 import '../src/context.dart';
+import '../src/testbed.dart';
 
 void main() {
   group('$Cache.checkLockAcquired', () {
@@ -51,8 +57,13 @@
   });
 
   group('Cache', () {
-    final MockCache mockCache = MockCache();
-    final MemoryFileSystem fs = MemoryFileSystem();
+    MockCache mockCache;
+    MemoryFileSystem memoryFileSystem;
+
+    setUp(() {
+      mockCache = MockCache();
+      memoryFileSystem = MemoryFileSystem();
+    });
 
     testUsingContext('Gradle wrapper should not be up to date, if some cached artifact is not available', () {
       final GradleWrapper gradleWrapper = GradleWrapper(mockCache);
@@ -63,7 +74,7 @@
       expect(gradleWrapper.isUpToDateInner(), false);
     }, overrides: <Type, Generator>{
       Cache: ()=> mockCache,
-      FileSystem: () => fs,
+      FileSystem: () => memoryFileSystem,
     });
 
     testUsingContext('Gradle wrapper should be up to date, only if all cached artifact are available', () {
@@ -78,7 +89,7 @@
       expect(gradleWrapper.isUpToDateInner(), true);
     }, overrides: <Type, Generator>{
       Cache: ()=> mockCache,
-      FileSystem: () => fs,
+      FileSystem: () => memoryFileSystem,
     });
 
     test('should not be up to date, if some cached artifact is not', () {
@@ -157,6 +168,74 @@
   }, overrides: <Type, Generator>{
     FileSystem: () => MockFileSystem(),
   });
+
+  group('EngineCachedArtifact', () {
+    FakeHttpClient fakeHttpClient;
+    FakePlatform fakePlatform;
+    MemoryFileSystem memoryFileSystem;
+    MockCache mockCache;
+    MockOperatingSystemUtils mockOperatingSystemUtils;
+
+    setUp(() {
+      fakeHttpClient = FakeHttpClient();
+      fakePlatform = FakePlatform()..environment = const <String, String>{};
+      memoryFileSystem = MemoryFileSystem();
+      mockCache = MockCache();
+      mockOperatingSystemUtils = MockOperatingSystemUtils();
+      when(mockOperatingSystemUtils.verifyZip(any)).thenReturn(true);
+    });
+
+    testUsingContext('makes binary dirs readable and executable by all', () async {
+      final Directory artifactDir = fs.systemTempDirectory.createTempSync('artifact.');
+      final Directory downloadDir = fs.systemTempDirectory.createTempSync('download.');
+      when(mockCache.getArtifactDirectory(any)).thenReturn(artifactDir);
+      when(mockCache.getDownloadDir()).thenReturn(downloadDir);
+      final FakeCachedArtifact artifact = FakeCachedArtifact(
+        cache: mockCache,
+        binaryDirs: <List<String>>[
+          <String>['bin_dir', 'unused_url_path'],
+        ],
+      );
+      await artifact.updateInner();
+      final Directory dir = memoryFileSystem.systemTempDirectory
+          .listSync(recursive: true)
+          .whereType<Directory>()
+          .singleWhere((Directory directory) => directory.basename == 'bin_dir', orElse: () => null);
+      expect(dir, isNotNull);
+      expect(dir.path, artifactDir.childDirectory('bin_dir').path);
+      verify(mockOperatingSystemUtils.chmod(argThat(hasPath(dir.path)), 'a+r,a+x'));
+    }, overrides: <Type, Generator>{
+      Cache: ()=> mockCache,
+      FileSystem: () => memoryFileSystem,
+      HttpClientFactory: () => () => fakeHttpClient,
+      OperatingSystemUtils: () => mockOperatingSystemUtils,
+      Platform: () => fakePlatform,
+    });
+  });
+}
+
+class FakeCachedArtifact extends EngineCachedArtifact {
+  FakeCachedArtifact({
+    String stampName = 'STAMP',
+    @required Cache cache,
+    Set<DevelopmentArtifact> requiredArtifacts = const <DevelopmentArtifact>{},
+    this.binaryDirs = const <List<String>>[],
+    this.licenseDirs = const <String>[],
+    this.packageDirs = const <String>[],
+  }) : super(stampName, cache, requiredArtifacts);
+
+  final List<List<String>> binaryDirs;
+  final List<String> licenseDirs;
+  final List<String> packageDirs;
+
+  @override
+  List<List<String>> getBinaryDirs() => binaryDirs;
+
+  @override
+  List<String> getLicenseDirs() => licenseDirs;
+
+  @override
+  List<String> getPackageDirs() => packageDirs;
 }
 
 class MockFileSystem extends ForwardingFileSystem {
@@ -179,3 +258,4 @@
 class MockCachedArtifact extends Mock implements CachedArtifact {}
 class MockInternetAddress extends Mock implements InternetAddress {}
 class MockCache extends Mock implements Cache {}
+class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
diff --git a/packages/flutter_tools/test/general.shard/commands/create_usage_test.dart b/packages/flutter_tools/test/general.shard/commands/create_usage_test.dart
index 8ab8118..6b80557 100644
--- a/packages/flutter_tools/test/general.shard/commands/create_usage_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/create_usage_test.dart
@@ -10,9 +10,9 @@
 import 'package:flutter_tools/src/reporting/usage.dart';
 
 import '../../src/common.dart';
+import '../../src/context.dart';
 import '../../src/testbed.dart';
 
-
 void main() {
   group('usageValues', () {
     Testbed testbed;
diff --git a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart
index e63ec84..a3a0369 100644
--- a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart
+++ b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart
@@ -401,16 +401,14 @@
 
   group('fuchsia app start and stop: ', () {
     MemoryFileSystem memoryFileSystem;
-    MockOperatingSystemUtils osUtils;
+    FakeOperatingSystemUtils osUtils;
     FakeFuchsiaDeviceTools fuchsiaDeviceTools;
     MockFuchsiaSdk fuchsiaSdk;
     setUp(() {
       memoryFileSystem = MemoryFileSystem();
-      osUtils = MockOperatingSystemUtils();
+      osUtils = FakeOperatingSystemUtils();
       fuchsiaDeviceTools = FakeFuchsiaDeviceTools();
       fuchsiaSdk = MockFuchsiaSdk();
-
-      when(osUtils.findFreePort()).thenAnswer((_) => Future<int>.value(12345));
     });
 
     Future<LaunchResult> setupAndStartApp({
@@ -640,8 +638,6 @@
 
 class MockProcessResult extends Mock implements ProcessResult {}
 
-class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
-
 class MockFile extends Mock implements File {}
 
 class MockProcess extends Mock implements Process {}
diff --git a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_workflow_test.dart b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_workflow_test.dart
index 6b6c973..caf1dc7 100644
--- a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_workflow_test.dart
+++ b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_workflow_test.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'package:flutter_tools/src/base/file_system.dart';
-import 'package:flutter_tools/src/base/os.dart';
 import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart';
 import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart';
 import 'package:mockito/mockito.dart';
@@ -11,8 +10,6 @@
 import '../../src/common.dart';
 import '../../src/context.dart';
 
-class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
-
 class MockFile extends Mock implements File {}
 
 void main() {
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 0a457ea..3b181ab 100644
--- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -19,6 +19,7 @@
 import 'package:mockito/mockito.dart';
 
 import '../src/common.dart';
+import '../src/context.dart';
 import '../src/testbed.dart';
 
 void main() {
diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart
index fa28b05..eb1ed6e 100644
--- a/packages/flutter_tools/test/src/context.dart
+++ b/packages/flutter_tools/test/src/context.dart
@@ -33,8 +33,8 @@
 /// Return the test logger. This assumes that the current Logger is a BufferLogger.
 BufferLogger get testLogger => context.get<Logger>();
 
-MockDeviceManager get testDeviceManager => context.get<DeviceManager>();
-MockDoctor get testDoctor => context.get<Doctor>();
+FakeDeviceManager get testDeviceManager => context.get<DeviceManager>();
+FakeDoctor get testDoctor => context.get<Doctor>();
 
 typedef ContextInitializer = void Function(AppContext testContext);
 
@@ -71,8 +71,8 @@
         name: 'mocks',
         overrides: <Type, Generator>{
           Config: () => buildConfig(fs),
-          DeviceManager: () => MockDeviceManager(),
-          Doctor: () => MockDoctor(),
+          DeviceManager: () => FakeDeviceManager(),
+          Doctor: () => FakeDoctor(),
           FlutterVersion: () => MockFlutterVersion(),
           HttpClient: () => MockHttpClient(),
           IOSSimulatorUtils: () {
@@ -82,10 +82,10 @@
           },
           OutputPreferences: () => OutputPreferences(showColor: false),
           Logger: () => BufferLogger(),
-          OperatingSystemUtils: () => MockOperatingSystemUtils(),
+          OperatingSystemUtils: () => FakeOperatingSystemUtils(),
           SimControl: () => MockSimControl(),
-          Usage: () => MockUsage(),
-          XcodeProjectInterpreter: () => MockXcodeProjectInterpreter(),
+          Usage: () => FakeUsage(),
+          XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(),
           FileSystem: () => LocalFileSystemBlockingSetCurrentDirectory(),
           TimeoutConfiguration: () => const TimeoutConfiguration(),
         },
@@ -135,7 +135,7 @@
   }
 }
 
-class MockDeviceManager implements DeviceManager {
+class FakeDeviceManager implements DeviceManager {
   List<Device> devices = <Device>[];
 
   String _specifiedDeviceId;
@@ -198,12 +198,12 @@
   }
 }
 
-class MockAndroidLicenseValidator extends AndroidLicenseValidator {
+class FakeAndroidLicenseValidator extends AndroidLicenseValidator {
   @override
   Future<LicensesAccepted> get licensesAccepted async => LicensesAccepted.all;
 }
 
-class MockDoctor extends Doctor {
+class FakeDoctor extends Doctor {
   // True for testing.
   @override
   bool get canListAnything => true;
@@ -220,7 +220,7 @@
     final List<DoctorValidator> superValidators = super.validators;
     return superValidators.map<DoctorValidator>((DoctorValidator v) {
       if (v is AndroidLicenseValidator) {
-        return MockAndroidLicenseValidator();
+        return FakeAndroidLicenseValidator();
       }
       return v;
     }).toList();
@@ -233,11 +233,14 @@
   }
 }
 
-class MockOperatingSystemUtils implements OperatingSystemUtils {
+class FakeOperatingSystemUtils implements OperatingSystemUtils {
   @override
   ProcessResult makeExecutable(File file) => null;
 
   @override
+  void chmod(FileSystemEntity entity, String mode) { }
+
+  @override
   File which(String execName) => null;
 
   @override
@@ -273,7 +276,7 @@
 
 class MockIOSSimulatorUtils extends Mock implements IOSSimulatorUtils {}
 
-class MockUsage implements Usage {
+class FakeUsage implements Usage {
   @override
   bool get isFirstRun => false;
 
@@ -314,7 +317,7 @@
   void printWelcome() { }
 }
 
-class MockXcodeProjectInterpreter implements XcodeProjectInterpreter {
+class FakeXcodeProjectInterpreter implements XcodeProjectInterpreter {
   @override
   bool get isInstalled => true;
 
diff --git a/packages/flutter_tools/test/src/testbed.dart b/packages/flutter_tools/test/src/testbed.dart
index d01a0cd..ac4901c 100644
--- a/packages/flutter_tools/test/src/testbed.dart
+++ b/packages/flutter_tools/test/src/testbed.dart
@@ -9,6 +9,7 @@
 
 import 'package:file/memory.dart';
 import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/os.dart';
 import 'package:flutter_tools/src/base/platform.dart';
 import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
@@ -33,6 +34,7 @@
       ? FileSystemStyle.windows
       : FileSystemStyle.posix),
   Logger: () => BufferLogger(), // Allows reading logs and prevents stdout.
+  OperatingSystemUtils: () => FakeOperatingSystemUtils(),
   OutputPreferences: () => OutputPreferences(showColor: false), // configures BufferLogger to avoid color codes.
   Usage: () => NoOpUsage(), // prevent addition of analytics from burdening test mocks
   FlutterVersion: () => FakeFlutterVersion() // prevent requirement to mock git for test runner.