Fix invocations of ideviceinstaller not passing DYLD_LIBRARY_PATH (#36327)

* add failing tests

* fix tests

* be more specific with try-catch

* add further mocking to get tests to pass again

* fix analyzer failure
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 33e520d..4ed40de 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -191,15 +191,18 @@
 
   @override
   Future<bool> isAppInstalled(ApplicationPackage app) async {
+    RunResult apps;
     try {
-      final RunResult apps = await runCheckedAsync(<String>[_installerPath, '--list-apps']);
-      if (RegExp(app.id, multiLine: true).hasMatch(apps.stdout)) {
-        return true;
-      }
-    } catch (e) {
+      apps = await runCheckedAsync(
+        <String>[_installerPath, '--list-apps'],
+        environment: Map<String, String>.fromEntries(
+          <MapEntry<String, String>>[cache.dyLdLibEntry],
+        ),
+      );
+    } on ProcessException {
       return false;
     }
-    return false;
+    return RegExp(app.id, multiLine: true).hasMatch(apps.stdout);
   }
 
   @override
@@ -215,9 +218,14 @@
     }
 
     try {
-      await runCheckedAsync(<String>[_installerPath, '-i', iosApp.deviceBundlePath]);
+      await runCheckedAsync(
+        <String>[_installerPath, '-i', iosApp.deviceBundlePath],
+        environment: Map<String, String>.fromEntries(
+          <MapEntry<String, String>>[cache.dyLdLibEntry],
+        ),
+      );
       return true;
-    } catch (e) {
+    } on ProcessException {
       return false;
     }
   }
@@ -225,9 +233,14 @@
   @override
   Future<bool> uninstallApp(ApplicationPackage app) async {
     try {
-      await runCheckedAsync(<String>[_installerPath, '-U', app.id]);
+      await runCheckedAsync(
+        <String>[_installerPath, '-U', app.id],
+        environment: Map<String, String>.fromEntries(
+          <MapEntry<String, String>>[cache.dyLdLibEntry],
+        ),
+      );
       return true;
-    } catch (e) {
+    } on ProcessException {
       return false;
     }
   }
diff --git a/packages/flutter_tools/test/general.shard/ios/devices_test.dart b/packages/flutter_tools/test/general.shard/ios/devices_test.dart
index 0716212..f01894e 100644
--- a/packages/flutter_tools/test/general.shard/ios/devices_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/devices_test.dart
@@ -7,8 +7,10 @@
 import 'package:file/file.dart';
 import 'package:file/memory.dart';
 import 'package:flutter_tools/src/application_package.dart';
+import 'package:flutter_tools/src/artifacts.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/device.dart';
 import 'package:flutter_tools/src/ios/devices.dart';
 import 'package:flutter_tools/src/ios/mac.dart';
@@ -22,6 +24,11 @@
 import '../../src/context.dart';
 import '../../src/mocks.dart';
 
+class MockIOSApp extends Mock implements IOSApp {}
+class MockArtifacts extends Mock implements Artifacts {}
+class MockCache extends Mock implements Cache {}
+class MockDirectory extends Mock implements Directory {}
+class MockFileSystem extends Mock implements FileSystem {}
 class MockIMobileDevice extends Mock implements IMobileDevice {}
 class MockProcessManager extends Mock implements ProcessManager {}
 class MockXcode extends Mock implements Xcode {}
@@ -29,8 +36,96 @@
 class MockProcess extends Mock implements Process {}
 
 void main() {
-  final FakePlatform osx = FakePlatform.fromPlatform(const LocalPlatform());
-  osx.operatingSystem = 'macos';
+  final FakePlatform macPlatform = FakePlatform.fromPlatform(const LocalPlatform());
+  macPlatform.operatingSystem = 'macos';
+
+  group('Process calls', () {
+    MockIOSApp mockApp;
+    MockArtifacts mockArtifacts;
+    MockCache mockCache;
+    MockFileSystem mockFileSystem;
+    MockProcessManager mockProcessManager;
+    const String installerPath = '/path/to/ideviceinstaller';
+    const String appId = '789';
+    const MapEntry<String, String> libraryEntry = MapEntry<String, String>(
+      'DYLD_LIBRARY_PATH',
+      '/path/to/libraries'
+    );
+    final Map<String, String> env = Map<String, String>.fromEntries(
+      <MapEntry<String, String>>[libraryEntry]
+    );
+
+    setUp(() {
+      mockApp = MockIOSApp();
+      mockArtifacts = MockArtifacts();
+      mockCache = MockCache();
+      when(mockCache.dyLdLibEntry).thenReturn(libraryEntry);
+      mockFileSystem = MockFileSystem();
+      mockProcessManager = MockProcessManager();
+      when(
+        mockArtifacts.getArtifactPath(
+          Artifact.ideviceinstaller,
+          platform: anyNamed('platform'),
+        )
+      ).thenReturn(installerPath);
+    });
+
+    testUsingContext('installApp() invokes process with correct environment', () async {
+      final IOSDevice device = IOSDevice('123');
+      const String bundlePath = '/path/to/bundle';
+      final List<String> args = <String>[installerPath, '-i', bundlePath];
+      when(mockApp.deviceBundlePath).thenReturn(bundlePath);
+      final MockDirectory directory = MockDirectory();
+      when(mockFileSystem.directory(bundlePath)).thenReturn(directory);
+      when(directory.existsSync()).thenReturn(true);
+      when(mockProcessManager.run(args, environment: env))
+        .thenAnswer(
+          (_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
+        );
+      await device.installApp(mockApp);
+      verify(mockProcessManager.run(args, environment: env));
+    }, overrides: <Type, Generator>{
+      Artifacts: () => mockArtifacts,
+      Cache: () => mockCache,
+      FileSystem: () => mockFileSystem,
+      Platform: () => macPlatform,
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext('isAppInstalled() invokes process with correct environment', () async {
+      final IOSDevice device = IOSDevice('123');
+      final List<String> args = <String>[installerPath, '--list-apps'];
+      when(mockProcessManager.run(args, environment: env))
+        .thenAnswer(
+          (_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
+        );
+      when(mockApp.id).thenReturn(appId);
+      await device.isAppInstalled(mockApp);
+      verify(mockProcessManager.run(args, environment: env));
+    }, overrides: <Type, Generator>{
+      Artifacts: () => mockArtifacts,
+      Cache: () => mockCache,
+      Platform: () => macPlatform,
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext('uninstallApp() invokes process with correct environment', () async {
+      final IOSDevice device = IOSDevice('123');
+      final List<String> args = <String>[installerPath, '-U', appId];
+      when(mockApp.id).thenReturn(appId);
+      when(mockProcessManager.run(args, environment: env))
+        .thenAnswer(
+          (_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
+        );
+      await device.uninstallApp(mockApp);
+      verify(mockProcessManager.run(args, environment: env));
+    }, overrides: <Type, Generator>{
+      Artifacts: () => mockArtifacts,
+      Cache: () => mockCache,
+      Platform: () => macPlatform,
+      ProcessManager: () => mockProcessManager,
+    });
+  });
 
   group('getAttachedDevices', () {
     MockIMobileDevice mockIMobileDevice;