Revert "Stream logging from attached debugger on iOS (#66092)" (#66367)

This reverts commit 5c8580360aa6e98c560dfe9191e0d42ad2015f9e.
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index fb7aea5..9bad5a3 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -212,9 +212,6 @@
 
   DevicePortForwarder _portForwarder;
 
-  @visibleForTesting
-  IOSDeployDebugger iosDeployDebugger;
-
   @override
   Future<bool> get isLocalEmulator async => false;
 
@@ -398,38 +395,23 @@
         timeout: timeoutConfiguration.slowOperation);
     try {
       ProtocolDiscovery observatoryDiscovery;
-      int installationResult = 1;
       if (debuggingOptions.debuggingEnabled) {
         _logger.printTrace('Debugging is enabled, connecting to observatory');
-        iosDeployDebugger = _iosDeploy.prepareDebuggerForLaunch(
-          deviceId: id,
-          bundlePath: bundle.path,
-          launchArguments: launchArguments,
-          interfaceType: interfaceType,
-        );
-
-        final DeviceLogReader deviceLogReader = getLogReader(app: package);
-        if (deviceLogReader is IOSDeviceLogReader) {
-          deviceLogReader.debuggerStream = iosDeployDebugger;
-        }
         observatoryDiscovery = ProtocolDiscovery.observatory(
-          deviceLogReader,
+          getLogReader(app: package),
           portForwarder: portForwarder,
-          throttleDuration: fallbackPollingDelay,
-          throttleTimeout: fallbackThrottleTimeout ?? const Duration(seconds: 5),
           hostPort: debuggingOptions.hostVmServicePort,
           devicePort: debuggingOptions.deviceVmServicePort,
           ipv6: ipv6,
-        );
-        installationResult = await iosDeployDebugger.launchAndAttach() ? 0 : 1;
-      } else {
-        installationResult = await _iosDeploy.launchApp(
-          deviceId: id,
-          bundlePath: bundle.path,
-          launchArguments: launchArguments,
-          interfaceType: interfaceType,
+          throttleTimeout: fallbackThrottleTimeout ?? const Duration(seconds: 1),
         );
       }
+      final int installationResult = await _iosDeploy.runApp(
+        deviceId: id,
+        bundlePath: bundle.path,
+        launchArguments: launchArguments,
+        interfaceType: interfaceType,
+      );
       if (installationResult != 0) {
         _logger.printError('Could not run ${bundle.path} on $id.');
         _logger.printError('Try launching Xcode and selecting "Product > Run" to fix the problem:');
@@ -483,11 +465,7 @@
     IOSApp app, {
     String userIdentifier,
   }) async {
-    // If the debugger is not attached, killing the ios-deploy process won't stop the app.
-    if (iosDeployDebugger!= null && iosDeployDebugger.debuggerAttached) {
-      // Avoid null.
-      return iosDeployDebugger?.exit() == true;
-    }
+    // Currently we don't have a way to stop an app running on iOS.
     return false;
   }
 
@@ -677,13 +655,6 @@
   // Matches a syslog line from any app.
   RegExp _anyLineRegex;
 
-  // Logging from native code/Flutter engine is prefixed by timestamp and process metadata:
-  // 2020-09-15 19:15:10.931434-0700 Runner[541:226276] Did finish launching.
-  // 2020-09-15 19:15:10.931434-0700 Runner[541:226276] [Category] Did finish launching.
-  //
-  // Logging from the dart code has no prefixing metadata.
-  final RegExp _debuggerLoggingRegex = RegExp(r'^\S* \S* \S*\[[0-9:]*] (.*)');
-
   StreamController<String> _linesController;
   List<StreamSubscription<void>> _loggingSubscriptions;
 
@@ -716,10 +687,6 @@
     }
 
     void logMessage(vm_service.Event event) {
-      if (_iosDeployDebugger != null && _iosDeployDebugger.debuggerAttached) {
-        // Prefer the more complete logs from the  attached debugger.
-        return;
-      }
       final String message = processVmServiceMessage(event);
       if (message.isNotEmpty) {
         _linesController.add(message);
@@ -732,26 +699,6 @@
     ]);
   }
 
-  /// Log reader will listen to [debugger.logLines] and will detach debugger on dispose.
-  set debuggerStream(IOSDeployDebugger debugger) {
-    // Logging is gathered from syslog on iOS 13 and earlier.
-    if (_majorSdkVersion < _minimumUniversalLoggingSdkVersion) {
-      return;
-    }
-    _iosDeployDebugger = debugger;
-    // Add the debugger logs to the controller created on initialization.
-    _loggingSubscriptions.add(debugger.logLines.listen(
-      (String line) => _linesController.add(_debuggerLineHandler(line)),
-      onError: _linesController.addError,
-      onDone: _linesController.close,
-      cancelOnError: true,
-    ));
-  }
-  IOSDeployDebugger _iosDeployDebugger;
-
-  // Strip off the logging metadata (leave the category), or just echo the line.
-  String _debuggerLineHandler(String line) => _debuggerLoggingRegex?.firstMatch(line)?.group(1) ?? line;
-
   void _listenToSysLog() {
     // syslog is not written on iOS 13+.
     if (_majorSdkVersion >= _minimumUniversalLoggingSdkVersion) {
@@ -811,7 +758,6 @@
       loggingSubscription.cancel();
     }
     _idevicesyslogProcess?.kill();
-    _iosDeployDebugger?.detach();
   }
 }
 
diff --git a/packages/flutter_tools/lib/src/ios/fallback_discovery.dart b/packages/flutter_tools/lib/src/ios/fallback_discovery.dart
index 42f337f..56c8147 100644
--- a/packages/flutter_tools/lib/src/ios/fallback_discovery.dart
+++ b/packages/flutter_tools/lib/src/ios/fallback_discovery.dart
@@ -82,29 +82,6 @@
     }
 
     try {
-      final Uri result = await _protocolDiscovery.uri;
-      if (result != null) {
-        UsageEvent(
-          _kEventName,
-          'log-success',
-          flutterUsage: _flutterUsage,
-        ).send();
-        return result;
-      }
-    } on ArgumentError {
-      // In the event of an invalid InternetAddress, this code attempts to catch
-      // an ArgumentError from protocol_discovery.dart
-    } on Exception catch (err) {
-      _logger.printTrace(err.toString());
-    }
-    _logger.printTrace('Failed to connect with log scanning, falling back to mDNS');
-    UsageEvent(
-      _kEventName,
-      'log-failure',
-      flutterUsage: _flutterUsage,
-    ).send();
-
-    try {
       final Uri result = await _mDnsObservatoryDiscovery.getObservatoryUri(
         packageId,
         device,
@@ -122,12 +99,35 @@
     } on Exception catch (err) {
       _logger.printTrace(err.toString());
     }
-    _logger.printTrace('Failed to connect with mDNS');
+    _logger.printTrace('Failed to connect with mDNS, falling back to log scanning');
     UsageEvent(
       _kEventName,
       'mdns-failure',
       flutterUsage: _flutterUsage,
     ).send();
+
+    try {
+      final Uri result = await _protocolDiscovery.uri;
+      if (result != null) {
+        UsageEvent(
+          _kEventName,
+          'fallback-success',
+          flutterUsage: _flutterUsage,
+        ).send();
+        return result;
+      }
+    } on ArgumentError {
+    // In the event of an invalid InternetAddress, this code attempts to catch
+    // an ArgumentError from protocol_discovery.dart
+    } on Exception catch (err) {
+      _logger.printTrace(err.toString());
+    }
+    _logger.printTrace('Failed to connect with log scanning');
+    UsageEvent(
+      _kEventName,
+      'fallback-failure',
+      flutterUsage: _flutterUsage,
+    ).send();
     return null;
   }
 
@@ -148,7 +148,7 @@
       assumedWsUri = Uri.parse('ws://localhost:$hostPort/ws');
     } on Exception catch (err) {
       _logger.printTrace(err.toString());
-      _logger.printTrace('Failed to connect directly, falling back to log scanning');
+      _logger.printTrace('Failed to connect directly, falling back to mDNS');
       _sendFailureEvent(err, assumedDevicePort);
       return null;
     }
diff --git a/packages/flutter_tools/lib/src/ios/ios_deploy.dart b/packages/flutter_tools/lib/src/ios/ios_deploy.dart
index 3945bdc..c1e90a7 100644
--- a/packages/flutter_tools/lib/src/ios/ios_deploy.dart
+++ b/packages/flutter_tools/lib/src/ios/ios_deploy.dart
@@ -2,20 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:async';
-
 import 'package:meta/meta.dart';
 import 'package:process/process.dart';
 
 import '../artifacts.dart';
-import '../base/common.dart';
-import '../base/io.dart';
 import '../base/logger.dart';
 import '../base/platform.dart';
 import '../base/process.dart';
 import '../build_info.dart';
 import '../cache.dart';
-import '../convert.dart';
 import 'code_signing.dart';
 import 'devices.dart';
 
@@ -112,47 +107,10 @@
     );
   }
 
-  /// Returns [IOSDeployDebugger] wrapping attached debugger logic.
-  ///
-  /// This method does not install the app. Call [IOSDeployDebugger.launchAndAttach()]
-  /// to install and attach the debugger to the specified app bundle.
-  IOSDeployDebugger prepareDebuggerForLaunch({
-    @required String deviceId,
-    @required String bundlePath,
-    @required List<String> launchArguments,
-    @required IOSDeviceInterface interfaceType,
-  }) {
-    // Interactive debug session to support sending the lldb detach command.
-    final List<String> launchCommand = <String>[
-      'script',
-      '-t',
-      '0',
-      '/dev/null',
-      _binaryPath,
-      '--id',
-      deviceId,
-      '--bundle',
-      bundlePath,
-      '--debug',
-      if (interfaceType != IOSDeviceInterface.network)
-        '--no-wifi',
-      if (launchArguments.isNotEmpty) ...<String>[
-        '--args',
-        launchArguments.join(' '),
-      ],
-    ];
-    return IOSDeployDebugger(
-      launchCommand: launchCommand,
-      logger: _logger,
-      processUtils: _processUtils,
-      iosDeployEnv: iosDeployEnv,
-    );
-  }
-
   /// Installs and then runs the specified app bundle.
   ///
   /// Uses ios-deploy and returns the exit code.
-  Future<int> launchApp({
+  Future<int> runApp({
     @required String deviceId,
     @required String bundlePath,
     @required List<String> launchArguments,
@@ -211,202 +169,30 @@
     return true;
   }
 
-  String _monitorFailure(String stdout) => _monitorIOSDeployFailure(stdout, _logger);
-}
-
-/// lldb attach state flow.
-enum _IOSDeployDebuggerState {
-  detached,
-  launching,
-  attached,
-}
-
-/// Wrapper to launch app and attach the debugger with ios-deploy.
-class IOSDeployDebugger {
-  IOSDeployDebugger({
-    @required Logger logger,
-    @required ProcessUtils processUtils,
-    @required List<String> launchCommand,
-    @required Map<String, String> iosDeployEnv,
-  }) : _processUtils = processUtils,
-        _logger = logger,
-        _launchCommand = launchCommand,
-        _iosDeployEnv = iosDeployEnv,
-        _debuggerState = _IOSDeployDebuggerState.detached;
-
-  /// Create a [IOSDeployDebugger] for testing.
-  ///
-  /// Sets the command to "ios-deploy" and environment to an empty map.
-  @visibleForTesting
-  factory IOSDeployDebugger.test({
-    @required ProcessManager processManager,
-    Logger logger,
-  }) {
-    final Logger debugLogger = logger ?? BufferLogger.test();
-    return IOSDeployDebugger(
-      logger: debugLogger,
-      processUtils: ProcessUtils(logger: debugLogger, processManager: processManager),
-      launchCommand: <String>['ios-deploy'],
-      iosDeployEnv: <String, String>{},
-    );
-  }
-
-  final Logger _logger;
-  final ProcessUtils _processUtils;
-  final List<String> _launchCommand;
-  final Map<String, String> _iosDeployEnv;
-
-  Process _iosDeployProcess;
-
-  Stream<String> get logLines => _debuggerOutput.stream;
-  final StreamController<String> _debuggerOutput = StreamController<String>.broadcast();
-
-  bool get debuggerAttached => _debuggerState == _IOSDeployDebuggerState.attached;
-  _IOSDeployDebuggerState _debuggerState;
-
-  // (lldb)     run
-  // https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L51
-  static final RegExp _lldbRun = RegExp(r'\(lldb\)\s*run');
-
-  /// Launch the app on the device, and attach the debugger.
-  ///
-  /// Returns whether or not the debugger successfully attached.
-  Future<bool> launchAndAttach() async {
-    // Return when the debugger attaches, or the ios-deploy process exits.
-    final Completer<bool> debuggerCompleter = Completer<bool>();
-    try {
-      _iosDeployProcess = await _processUtils.start(
-        _launchCommand,
-        environment: _iosDeployEnv,
-      );
-      String lastLineFromDebugger;
-      final StreamSubscription<String> stdoutSubscription = _iosDeployProcess.stdout
-          .transform<String>(utf8.decoder)
-          .transform<String>(const LineSplitter())
-          .listen((String line) {
-        _monitorIOSDeployFailure(line, _logger);
-
-        // (lldb)     run
-        // success
-        // 2020-09-15 13:42:25.185474-0700 Runner[477:181141] flutter: Observatory listening on http://127.0.0.1:57782/
-        if (_lldbRun.hasMatch(line)) {
-          _logger.printTrace(line);
-          _debuggerState = _IOSDeployDebuggerState.launching;
-          return;
-        }
-        // Next line after "run" must be "success", or the attach failed.
-        // Example: "error: process launch failed"
-        if (_debuggerState == _IOSDeployDebuggerState.launching) {
-          _logger.printTrace(line);
-          final bool attachSuccess = line == 'success';
-          _debuggerState = attachSuccess ? _IOSDeployDebuggerState.attached : _IOSDeployDebuggerState.detached;
-          if (!debuggerCompleter.isCompleted) {
-            debuggerCompleter.complete(attachSuccess);
-          }
-          return;
-        }
-        if (line.contains('PROCESS_STOPPED') ||
-            line.contains('PROCESS_EXITED')) {
-          // The app exited or crashed, so stop echoing the output.
-          // Don't pass any further ios-deploy debugging messages to the log reader after it exits.
-          _debuggerState = _IOSDeployDebuggerState.detached;
-          _logger.printTrace(line);
-          return;
-        }
-        if (_debuggerState != _IOSDeployDebuggerState.attached) {
-          _logger.printTrace(line);
-          return;
-        }
-        if (lastLineFromDebugger != null && lastLineFromDebugger.isNotEmpty && line.isEmpty) {
-          // The lldb console stream from ios-deploy is separated lines by an extra \r\n.
-          // To avoid all lines being double spaced, if the last line from the
-          // debugger was not an empty line, skip this empty line.
-          // This will still cause "legit" logged newlines to be doubled...
-        } else {
-          _debuggerOutput.add(line);
-        }
-        lastLineFromDebugger = line;
-      });
-      final StreamSubscription<String> stderrSubscription = _iosDeployProcess.stderr
-          .transform<String>(utf8.decoder)
-          .transform<String>(const LineSplitter())
-          .listen((String line) {
-        _monitorIOSDeployFailure(line, _logger);
-        _logger.printTrace(line);
-      });
-      unawaited(_iosDeployProcess.exitCode.then((int status) {
-        _logger.printTrace('ios-deploy exited with code $exitCode');
-        _debuggerState = _IOSDeployDebuggerState.detached;
-        unawaited(stdoutSubscription.cancel());
-        unawaited(stderrSubscription.cancel());
-      }).whenComplete(() async {
-        if (_debuggerOutput.hasListener) {
-          // Tell listeners the process died.
-          await _debuggerOutput.close();
-        }
-        if (!debuggerCompleter.isCompleted) {
-          debuggerCompleter.complete(false);
-        }
-        _iosDeployProcess = null;
-      }));
-    } on ProcessException catch (exception, stackTrace) {
-      _logger.printTrace('ios-deploy failed: $exception');
-      _debuggerState = _IOSDeployDebuggerState.detached;
-      _debuggerOutput.addError(exception, stackTrace);
-    } on ArgumentError catch (exception, stackTrace) {
-      _logger.printTrace('ios-deploy failed: $exception');
-      _debuggerState = _IOSDeployDebuggerState.detached;
-      _debuggerOutput.addError(exception, stackTrace);
-    }
-    // Wait until the debugger attaches, or the attempt fails.
-    return debuggerCompleter.future;
-  }
-
-  bool exit() {
-    final bool success = (_iosDeployProcess == null) || _iosDeployProcess.kill();
-    _iosDeployProcess = null;
-    return success;
-  }
-
-  void detach() {
-    if (!debuggerAttached) {
-      return;
-    }
-
-    try {
-      // Detach lldb from the app process.
-      _iosDeployProcess?.stdin?.writeln('process detach');
-      _debuggerState = _IOSDeployDebuggerState.detached;
-    } on SocketException catch (error) {
-      // Best effort, try to detach, but maybe the app already exited or already detached.
-      _logger.printTrace('Could not detach from debugger: $error');
-    }
-  }
-}
-
-// Maps stdout line stream. Must return original line.
-String _monitorIOSDeployFailure(String stdout, Logger logger) {
-  // Installation issues.
-  if (stdout.contains(noProvisioningProfileErrorOne) || stdout.contains(noProvisioningProfileErrorTwo)) {
-    logger.printError(noProvisioningProfileInstruction, emphasis: true);
+  // Maps stdout line stream. Must return original line.
+  String _monitorFailure(String stdout) {
+    // Installation issues.
+    if (stdout.contains(noProvisioningProfileErrorOne) || stdout.contains(noProvisioningProfileErrorTwo)) {
+      _logger.printError(noProvisioningProfileInstruction, emphasis: true);
 
     // Launch issues.
-  } else if (stdout.contains(deviceLockedError)) {
-    logger.printError('''
+    } else if (stdout.contains(deviceLockedError)) {
+      _logger.printError('''
 ═══════════════════════════════════════════════════════════════════════════════════
 Your device is locked. Unlock your device first before running.
 ═══════════════════════════════════════════════════════════════════════════════════''',
-        emphasis: true);
-  } else if (stdout.contains(unknownAppLaunchError)) {
-    logger.printError('''
+      emphasis: true);
+    } else if (stdout.contains(unknownAppLaunchError)) {
+      _logger.printError('''
 ═══════════════════════════════════════════════════════════════════════════════════
 Error launching app. Try launching from within Xcode via:
     open ios/Runner.xcworkspace
 
 Your Xcode version may be too old for your iOS version.
 ═══════════════════════════════════════════════════════════════════════════════════''',
-        emphasis: true);
-  }
+      emphasis: true);
+    }
 
-  return stdout;
+    return stdout;
+  }
 }
diff --git a/packages/flutter_tools/lib/src/protocol_discovery.dart b/packages/flutter_tools/lib/src/protocol_discovery.dart
index 3d1bc3d..367bccd 100644
--- a/packages/flutter_tools/lib/src/protocol_discovery.dart
+++ b/packages/flutter_tools/lib/src/protocol_discovery.dart
@@ -34,7 +34,7 @@
   factory ProtocolDiscovery.observatory(
     DeviceLogReader logReader, {
     DevicePortForwarder portForwarder,
-    Duration throttleDuration,
+    Duration throttleDuration = const Duration(milliseconds: 200),
     Duration throttleTimeout,
     @required int hostPort,
     @required int devicePort,
@@ -45,7 +45,7 @@
       logReader,
       kObservatoryService,
       portForwarder: portForwarder,
-      throttleDuration: throttleDuration ?? const Duration(milliseconds: 200),
+      throttleDuration: throttleDuration,
       throttleTimeout: throttleTimeout,
       hostPort: hostPort,
       devicePort: devicePort,
@@ -225,7 +225,7 @@
 ///
 /// For example, consider a `waitDuration` of `10ms`, and list of event names
 /// and arrival times: `a (0ms), b (5ms), c (11ms), d (21ms)`.
-/// The events `a`, `c`, and `d` will be produced as a result.
+/// The events `c` and `d` will be produced as a result.
 StreamTransformer<S, S> _throttle<S>({
   @required Duration waitDuration,
 }) {
@@ -240,13 +240,10 @@
       handleData: (S value, EventSink<S> sink) {
         latestLine = value;
 
-        final bool isFirstMessage = lastExecution == null;
         final int currentTime = DateTime.now().millisecondsSinceEpoch;
         lastExecution ??= currentTime;
         final int remainingTime = currentTime - lastExecution;
-
-        // Always send the first event immediately.
-        final int nextExecutionTime = isFirstMessage || remainingTime > waitDuration.inMilliseconds
+        final int nextExecutionTime = remainingTime > waitDuration.inMilliseconds
           ? 0
           : waitDuration.inMilliseconds - remainingTime;
 
diff --git a/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart
index e36d466..121c4f9 100644
--- a/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart
@@ -2,15 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:async';
-import 'dart:convert';
-
 import 'package:flutter_tools/src/artifacts.dart';
-import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/base/platform.dart';
 import 'package:flutter_tools/src/cache.dart';
-import 'package:flutter_tools/src/ios/devices.dart';
 import 'package:flutter_tools/src/ios/ios_deploy.dart';
 import 'package:mockito/mockito.dart';
 import 'package:process/process.dart';
@@ -26,230 +21,50 @@
     expect(environment['PATH'], startsWith('/usr/bin'));
   });
 
-  group('IOSDeploy.prepareDebuggerForLaunch', () {
-    testWithoutContext('calls ios-deploy with correct arguments and returns when debugger attaches', () async {
-      final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-        FakeCommand(
-          command: <String>[
-            'script',
-            '-t',
-            '0',
-            '/dev/null',
-            'ios-deploy',
-            '--id',
-            '123',
-            '--bundle',
-            '/',
-            '--debug',
-            '--args',
-            <String>[
-              '--enable-dart-profiling',
-            ].join(' '),
-          ], environment: const <String, String>{
-            'PATH': '/usr/bin:/usr/local/bin:/usr/bin',
-            'DYLD_LIBRARY_PATH': '/path/to/libs',
-          },
-          stdout: '(lldb)     run\nsuccess\nDid finish launching.',
-        ),
-      ]);
-      final IOSDeploy iosDeploy = setUpIOSDeploy(processManager);
-      final IOSDeployDebugger iosDeployDebugger = iosDeploy.prepareDebuggerForLaunch(
-        deviceId: '123',
-        bundlePath: '/',
-        launchArguments: <String>['--enable-dart-profiling'],
-        interfaceType: IOSDeviceInterface.network,
-      );
+  testWithoutContext('IOSDeploy.uninstallApp calls ios-deploy with correct arguments and returns 0 on success', () async {
+    const String deviceId = '123';
+    const String bundleId = 'com.example.app';
+    final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
+      const FakeCommand(command: <String>[
+        'ios-deploy',
+        '--id',
+        deviceId,
+        '--uninstall_only',
+        '--bundle_id',
+        bundleId,
+      ])
+    ]);
+    final IOSDeploy iosDeploy = setUpIOSDeploy(processManager);
+    final int exitCode = await iosDeploy.uninstallApp(
+      deviceId: deviceId,
+      bundleId: bundleId,
+    );
 
-      expect(await iosDeployDebugger.launchAndAttach(), isTrue);
-      expect(await iosDeployDebugger.logLines.toList(), <String>['Did finish launching.']);
-      expect(processManager.hasRemainingExpectations, false);
-    });
+    expect(exitCode, 0);
+    expect(processManager.hasRemainingExpectations, false);
   });
 
-  group('IOSDeployDebugger', () {
-    group('launch', () {
-      BufferLogger logger;
+  testWithoutContext('IOSDeploy.uninstallApp returns non-zero exit code when ios-deploy does the same', () async {
+    const String deviceId = '123';
+    const String bundleId = 'com.example.app';
+    final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
+      const FakeCommand(command: <String>[
+        'ios-deploy',
+        '--id',
+        deviceId,
+        '--uninstall_only',
+        '--bundle_id',
+        bundleId,
+      ], exitCode: 1)
+    ]);
+    final IOSDeploy iosDeploy = setUpIOSDeploy(processManager);
+    final int exitCode = await iosDeploy.uninstallApp(
+      deviceId: deviceId,
+      bundleId: bundleId,
+    );
 
-      setUp(() {
-        logger = BufferLogger.test();
-      });
-
-      testWithoutContext('debugger attached', () async {
-        final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-          const FakeCommand(
-            command: <String>['ios-deploy'],
-            stdout: '(lldb)     run\r\nsuccess\r\nsuccess\r\nLog on attach1\r\n\r\nLog on attach2\r\n\r\n\r\n\r\nPROCESS_STOPPED\r\nLog after process exit',
-          ),
-        ]);
-        final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
-          processManager: processManager,
-          logger: logger,
-        );
-        final List<String> receivedLogLines = <String>[];
-        final Stream<String> logLines = iosDeployDebugger.logLines
-          ..listen(receivedLogLines.add);
-
-        expect(await iosDeployDebugger.launchAndAttach(), isTrue);
-        await logLines.toList();
-        expect(receivedLogLines, <String>[
-          'success', // ignore first "success" from lldb, but log subsequent ones from real logging.
-          'Log on attach1',
-          'Log on attach2',
-          '', '']);
-      });
-
-      testWithoutContext('attach failed', () async {
-        final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-          const FakeCommand(
-            command: <String>['ios-deploy'],
-            // A success after an error should never happen, but test that we're handling random "successes" anyway.
-            stdout: '(lldb)     run\r\nerror: process launch failed\r\nsuccess\r\nLog on attach1',
-          ),
-        ]);
-        final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
-          processManager: processManager,
-          logger: logger,
-        );
-        final List<String> receivedLogLines = <String>[];
-        final Stream<String> logLines = iosDeployDebugger.logLines
-          ..listen(receivedLogLines.add);
-
-        expect(await iosDeployDebugger.launchAndAttach(), isFalse);
-        await logLines.toList();
-        // Debugger lines are double spaced, separated by an extra \r\n. Skip the extra lines.
-        // Still include empty lines other than the extra added newlines.
-        expect(receivedLogLines, isEmpty);
-      });
-
-      testWithoutContext('no provisioning profile 1, stdout', () async {
-        final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-          const FakeCommand(
-            command: <String>['ios-deploy'],
-            stdout: 'Error 0xe8008015',
-          ),
-        ]);
-        final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
-          processManager: processManager,
-          logger: logger,
-        );
-
-        await iosDeployDebugger.launchAndAttach();
-        expect(logger.errorText, contains('No Provisioning Profile was found'));
-      });
-
-      testWithoutContext('no provisioning profile 2, stderr', () async {
-        final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-          const FakeCommand(
-            command: <String>['ios-deploy'],
-            stderr: 'Error 0xe8000067',
-          ),
-        ]);
-        final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
-          processManager: processManager,
-          logger: logger,
-        );
-        await iosDeployDebugger.launchAndAttach();
-        expect(logger.errorText, contains('No Provisioning Profile was found'));
-      });
-
-      testWithoutContext('device locked', () async {
-        final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-          const FakeCommand(
-            command: <String>['ios-deploy'],
-            stdout: 'e80000e2',
-          ),
-        ]);
-        final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
-          processManager: processManager,
-          logger: logger,
-        );
-        await iosDeployDebugger.launchAndAttach();
-        expect(logger.errorText, contains('Your device is locked.'));
-      });
-
-      testWithoutContext('device locked', () async {
-        final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-          const FakeCommand(
-            command: <String>['ios-deploy'],
-            stdout: 'Error 0xe8000022',
-          ),
-        ]);
-        final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
-          processManager: processManager,
-          logger: logger,
-        );
-        await iosDeployDebugger.launchAndAttach();
-        expect(logger.errorText, contains('Try launching from within Xcode'));
-      });
-    });
-
-    testWithoutContext('detach', () async {
-      final StreamController<List<int>> stdin = StreamController<List<int>>();
-      final Stream<String> stdinStream = stdin.stream.transform<String>(const Utf8Decoder());
-      final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-        FakeCommand(
-          command: const <String>[
-            'ios-deploy',
-          ],
-          stdout: '(lldb)     run\nsuccess',
-          stdin: IOSink(stdin.sink),
-        ),
-      ]);
-      final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
-        processManager: processManager,
-      );
-      await iosDeployDebugger.launchAndAttach();
-      iosDeployDebugger.detach();
-      expect(await stdinStream.first, 'process detach');
-    });
-  });
-
-  group('IOSDeploy.uninstallApp', () {
-    testWithoutContext('calls ios-deploy with correct arguments and returns 0 on success', () async {
-      const String deviceId = '123';
-      const String bundleId = 'com.example.app';
-      final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-        const FakeCommand(command: <String>[
-          'ios-deploy',
-          '--id',
-          deviceId,
-          '--uninstall_only',
-          '--bundle_id',
-          bundleId,
-        ])
-      ]);
-      final IOSDeploy iosDeploy = setUpIOSDeploy(processManager);
-      final int exitCode = await iosDeploy.uninstallApp(
-        deviceId: deviceId,
-        bundleId: bundleId,
-      );
-
-      expect(exitCode, 0);
-      expect(processManager.hasRemainingExpectations, false);
-    });
-
-    testWithoutContext('returns non-zero exit code when ios-deploy does the same', () async {
-      const String deviceId = '123';
-      const String bundleId = 'com.example.app';
-      final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-        const FakeCommand(command: <String>[
-          'ios-deploy',
-          '--id',
-          deviceId,
-          '--uninstall_only',
-          '--bundle_id',
-          bundleId,
-        ], exitCode: 1)
-      ]);
-      final IOSDeploy iosDeploy = setUpIOSDeploy(processManager);
-      final int exitCode = await iosDeploy.uninstallApp(
-        deviceId: deviceId,
-        bundleId: bundleId,
-      );
-
-      expect(exitCode, 1);
-      expect(processManager.hasRemainingExpectations, false);
-    });
+    expect(exitCode, 1);
+    expect(processManager.hasRemainingExpectations, false);
   });
 }
 
diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart
index 1bdfa8a..bd95e87 100644
--- a/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart
@@ -10,7 +10,6 @@
 import 'package:flutter_tools/src/convert.dart';
 import 'package:flutter_tools/src/device.dart';
 import 'package:flutter_tools/src/ios/devices.dart';
-import 'package:flutter_tools/src/ios/ios_deploy.dart';
 import 'package:flutter_tools/src/ios/mac.dart';
 import 'package:mockito/mockito.dart';
 import 'package:vm_service/vm_service.dart';
@@ -31,299 +30,167 @@
     artifacts = MockArtifacts();
     logger = BufferLogger.test();
     when(artifacts.getArtifactPath(Artifact.idevicesyslog, platform: TargetPlatform.ios))
-        .thenReturn('idevice-syslog');
+      .thenReturn('idevice-syslog');
   });
 
-  group('syslog stream', () {
-    testWithoutContext('decodeSyslog decodes a syslog-encoded line', () {
-      final String decoded = decodeSyslog(
-          r'I \M-b\M^]\M-$\M-o\M-8\M^O syslog \M-B\M-/\'
-          r'134_(\M-c\M^C\M^D)_/\M-B\M-/ \M-l\M^F\240!');
+  testWithoutContext('decodeSyslog decodes a syslog-encoded line', () {
+    final String decoded = decodeSyslog(
+      r'I \M-b\M^]\M-$\M-o\M-8\M^O syslog \M-B\M-/\'
+      r'134_(\M-c\M^C\M^D)_/\M-B\M-/ \M-l\M^F\240!');
 
-      expect(decoded, r'I ❤️ syslog ¯\_(ツ)_/¯ 솠!');
-    });
+    expect(decoded, r'I ❤️ syslog ¯\_(ツ)_/¯ 솠!');
+  });
 
-    testWithoutContext('decodeSyslog passes through un-decodeable lines as-is', () {
-      final String decoded = decodeSyslog(r'I \M-b\M^O syslog!');
+  testWithoutContext('decodeSyslog passes through un-decodeable lines as-is', () {
+    final String decoded = decodeSyslog(r'I \M-b\M^O syslog!');
 
-      expect(decoded, r'I \M-b\M^O syslog!');
-    });
+    expect(decoded, r'I \M-b\M^O syslog!');
+  });
 
-    testWithoutContext('IOSDeviceLogReader suppresses non-Flutter lines from output with syslog', () async {
-      processManager.addCommand(
-        const FakeCommand(
-            command: <String>[
-              'idevice-syslog', '-u', '1234',
-            ],
-            stdout: '''
+  testWithoutContext('IOSDeviceLogReader suppresses non-Flutter lines from output with syslog', () async {
+    processManager.addCommand(
+      const FakeCommand(
+        command: <String>[
+          'idevice-syslog', '-u', '1234',
+        ],
+        stdout: '''
 Runner(Flutter)[297] <Notice>: A is for ari
 Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt MobileGestaltSupport.m:153: pid 123 (Runner) does not have sandbox access for frZQaeyWLUvLjeuEK43hmg and IS NOT appropriately entitled
 Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt MobileGestalt.c:550: no access to InverseDeviceID (see <rdar://problem/11744455>)
 Runner(Flutter)[297] <Notice>: I is for ichigo
 Runner(UIKit)[297] <Notice>: E is for enpitsu"
 '''
-        ),
-      );
-      final DeviceLogReader logReader = IOSDeviceLogReader.test(
-        iMobileDevice: IMobileDevice(
-          artifacts: artifacts,
-          processManager: processManager,
-          cache: fakeCache,
-          logger: logger,
-        ),
-      );
-      final List<String> lines = await logReader.logLines.toList();
+      ),
+    );
+    final DeviceLogReader logReader = IOSDeviceLogReader.test(
+      iMobileDevice: IMobileDevice(
+        artifacts: artifacts,
+        processManager: processManager,
+        cache: fakeCache,
+        logger: logger,
+      ),
+    );
+    final List<String> lines = await logReader.logLines.toList();
 
-      expect(lines, <String>['A is for ari', 'I is for ichigo']);
-    });
+    expect(lines, <String>['A is for ari', 'I is for ichigo']);
+  });
 
-    testWithoutContext('IOSDeviceLogReader includes multi-line Flutter logs in the output with syslog', () async {
-      processManager.addCommand(
-        const FakeCommand(
-            command: <String>[
-              'idevice-syslog', '-u', '1234',
-            ],
-            stdout: '''
+  testWithoutContext('IOSDeviceLogReader includes multi-line Flutter logs in the output with syslog', () async {
+    processManager.addCommand(
+      const FakeCommand(
+        command: <String>[
+          'idevice-syslog', '-u', '1234',
+        ],
+        stdout: '''
 Runner(Flutter)[297] <Notice>: This is a multi-line message,
   with another Flutter message following it.
 Runner(Flutter)[297] <Notice>: This is a multi-line message,
   with a non-Flutter log message following it.
 Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
 '''
-        ),
-      );
-      final DeviceLogReader logReader = IOSDeviceLogReader.test(
-        iMobileDevice: IMobileDevice(
-          artifacts: artifacts,
-          processManager: processManager,
-          cache: fakeCache,
-          logger: logger,
-        ),
-      );
-      final List<String> lines = await logReader.logLines.toList();
+      ),
+    );
+    final DeviceLogReader logReader = IOSDeviceLogReader.test(
+      iMobileDevice: IMobileDevice(
+        artifacts: artifacts,
+        processManager: processManager,
+        cache: fakeCache,
+        logger: logger,
+      ),
+    );
+    final List<String> lines = await logReader.logLines.toList();
 
-      expect(lines, <String>[
-        'This is a multi-line message,',
-        '  with another Flutter message following it.',
-        'This is a multi-line message,',
-        '  with a non-Flutter log message following it.',
-      ]);
-    });
+    expect(lines, <String>[
+      'This is a multi-line message,',
+      '  with another Flutter message following it.',
+      'This is a multi-line message,',
+      '  with a non-Flutter log message following it.',
+    ]);
+  });
 
-    testWithoutContext('includes multi-line Flutter logs in the output', () async {
-      processManager.addCommand(
-        const FakeCommand(
-          command: <String>[
-            'idevice-syslog', '-u', '1234',
-          ],
-          stdout: '''
+  testWithoutContext('includes multi-line Flutter logs in the output', () async {
+    processManager.addCommand(
+      const FakeCommand(
+        command: <String>[
+          'idevice-syslog', '-u', '1234',
+        ],
+        stdout: '''
 Runner(Flutter)[297] <Notice>: This is a multi-line message,
   with another Flutter message following it.
 Runner(Flutter)[297] <Notice>: This is a multi-line message,
   with a non-Flutter log message following it.
 Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
 ''',
-        ),
-      );
+      ),
+    );
 
-      final DeviceLogReader logReader = IOSDeviceLogReader.test(
-        iMobileDevice: IMobileDevice(
-          artifacts: artifacts,
-          processManager: processManager,
-          cache: fakeCache,
-          logger: logger,
-        ),
-      );
-      final List<String> lines = await logReader.logLines.toList();
+    final DeviceLogReader logReader = IOSDeviceLogReader.test(
+      iMobileDevice: IMobileDevice(
+        artifacts: artifacts,
+        processManager: processManager,
+        cache: fakeCache,
+        logger: logger,
+      ),
+    );
+    final List<String> lines = await logReader.logLines.toList();
 
-      expect(lines, <String>[
-        'This is a multi-line message,',
-        '  with another Flutter message following it.',
-        'This is a multi-line message,',
-        '  with a non-Flutter log message following it.',
-      ]);
-    });
+    expect(lines, <String>[
+      'This is a multi-line message,',
+      '  with another Flutter message following it.',
+      'This is a multi-line message,',
+      '  with a non-Flutter log message following it.',
+    ]);
   });
 
-  group('VM service', () {
-    testWithoutContext('IOSDeviceLogReader can listen to VM Service logs', () async {
-      final MockVmService vmService = MockVmService();
-      final DeviceLogReader logReader = IOSDeviceLogReader.test(
-        useSyslog: false,
-        iMobileDevice: IMobileDevice(
-          artifacts: artifacts,
-          processManager: processManager,
-          cache: fakeCache,
-          logger: logger,
-        ),
-      );
-      final StreamController<Event> stdoutController = StreamController<Event>();
-      final StreamController<Event> stderController = StreamController<Event>();
-      final Completer<Success> stdoutCompleter = Completer<Success>();
-      final Completer<Success> stderrCompleter = Completer<Success>();
-      when(vmService.streamListen('Stdout')).thenAnswer((Invocation invocation) {
-        return stdoutCompleter.future;
-      });
-      when(vmService.streamListen('Stderr')).thenAnswer((Invocation invocation) {
-        return stderrCompleter.future;
-      });
-      when(vmService.onStdoutEvent).thenAnswer((Invocation invocation) {
-        return stdoutController.stream;
-      });
-      when(vmService.onStderrEvent).thenAnswer((Invocation invocation) {
-        return stderController.stream;
-      });
-      logReader.connectedVMService = vmService;
-
-      stdoutCompleter.complete(Success());
-      stderrCompleter.complete(Success());
-      stdoutController.add(Event(
-        kind: 'Stdout',
-        timestamp: 0,
-        bytes: base64.encode(utf8.encode('  This is a message ')),
-      ));
-      stderController.add(Event(
-        kind: 'Stderr',
-        timestamp: 0,
-        bytes: base64.encode(utf8.encode('  And this is an error ')),
-      ));
-
-      // Wait for stream listeners to fire.
-      await expectLater(logReader.logLines, emitsInAnyOrder(<Matcher>[
-        equals('  This is a message '),
-        equals('  And this is an error '),
-      ]));
+  testWithoutContext('IOSDeviceLogReader can listen to VM Service logs', () async {
+    final MockVmService vmService = MockVmService();
+    final DeviceLogReader logReader = IOSDeviceLogReader.test(
+      useSyslog: false,
+      iMobileDevice: IMobileDevice(
+        artifacts: artifacts,
+        processManager: processManager,
+        cache: fakeCache,
+        logger: logger,
+      ),
+    );
+    final StreamController<Event> stdoutController = StreamController<Event>();
+    final StreamController<Event> stderController = StreamController<Event>();
+    final Completer<Success> stdoutCompleter = Completer<Success>();
+    final Completer<Success> stderrCompleter = Completer<Success>();
+    when(vmService.streamListen('Stdout')).thenAnswer((Invocation invocation) {
+      return stdoutCompleter.future;
     });
-
-    testWithoutContext('IOSDeviceLogReader ignores VM Service logs when attached to debugger', () async {
-      final MockVmService vmService = MockVmService();
-      final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
-        useSyslog: false,
-        iMobileDevice: IMobileDevice(
-          artifacts: artifacts,
-          processManager: processManager,
-          cache: fakeCache,
-          logger: logger,
-        ),
-      );
-      final StreamController<Event> stdoutController = StreamController<Event>();
-      final StreamController<Event> stderController = StreamController<Event>();
-      final Completer<Success> stdoutCompleter = Completer<Success>();
-      final Completer<Success> stderrCompleter = Completer<Success>();
-      when(vmService.streamListen('Stdout')).thenAnswer((Invocation invocation) {
-        return stdoutCompleter.future;
-      });
-      when(vmService.streamListen('Stderr')).thenAnswer((Invocation invocation) {
-        return stderrCompleter.future;
-      });
-      when(vmService.onStdoutEvent).thenAnswer((Invocation invocation) {
-        return stdoutController.stream;
-      });
-      when(vmService.onStderrEvent).thenAnswer((Invocation invocation) {
-        return stderController.stream;
-      });
-      logReader.connectedVMService = vmService;
-
-      stdoutCompleter.complete(Success());
-      stderrCompleter.complete(Success());
-      stdoutController.add(Event(
-        kind: 'Stdout',
-        timestamp: 0,
-        bytes: base64.encode(utf8.encode('  This is a message ')),
-      ));
-      stderController.add(Event(
-        kind: 'Stderr',
-        timestamp: 0,
-        bytes: base64.encode(utf8.encode('  And this is an error ')),
-      ));
-
-      final MockIOSDeployDebugger iosDeployDebugger = MockIOSDeployDebugger();
-      when(iosDeployDebugger.debuggerAttached).thenReturn(true);
-
-      final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[
-        'Message from debugger'
-      ]);
-      when(iosDeployDebugger.logLines).thenAnswer((Invocation invocation) => debuggingLogs);
-      logReader.debuggerStream = iosDeployDebugger;
-
-      // Wait for stream listeners to fire.
-      await expectLater(logReader.logLines, emitsInAnyOrder(<Matcher>[
-        equals('Message from debugger'),
-      ]));
+    when(vmService.streamListen('Stderr')).thenAnswer((Invocation invocation) {
+      return stderrCompleter.future;
     });
-  });
-
-  group('debugger stream', () {
-    testWithoutContext('IOSDeviceLogReader removes metadata prefix from lldb output', () async {
-      final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[
-        '2020-09-15 19:15:10.931434-0700 Runner[541:226276] Did finish launching.',
-        '2020-09-15 19:15:10.931434-0700 Runner[541:226276] [Category] Did finish launching from logging category.',
-        'stderr from dart',
-        '',
-      ]);
-
-      final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
-        iMobileDevice: IMobileDevice(
-          artifacts: artifacts,
-          processManager: processManager,
-          cache: fakeCache,
-          logger: logger,
-        ),
-        useSyslog: false,
-      );
-      final MockIOSDeployDebugger iosDeployDebugger = MockIOSDeployDebugger();
-      when(iosDeployDebugger.logLines).thenAnswer((Invocation invocation) => debuggingLogs);
-      logReader.debuggerStream = iosDeployDebugger;
-      final Future<List<String>> logLines = logReader.logLines.toList();
-
-      expect(await logLines, <String>[
-        'Did finish launching.',
-        '[Category] Did finish launching from logging category.',
-        'stderr from dart',
-        '',
-      ]);
+    when(vmService.onStdoutEvent).thenAnswer((Invocation invocation) {
+      return stdoutController.stream;
     });
-
-    testWithoutContext('errors on debugger stream closes log stream', () async {
-      final Stream<String> debuggingLogs = Stream<String>.error('ios-deploy error');
-      final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
-        iMobileDevice: IMobileDevice(
-          artifacts: artifacts,
-          processManager: processManager,
-          cache: fakeCache,
-          logger: logger,
-        ),
-        useSyslog: false,
-      );
-      final Completer<void> streamComplete = Completer<void>();
-      final MockIOSDeployDebugger iosDeployDebugger = MockIOSDeployDebugger();
-      when(iosDeployDebugger.logLines).thenAnswer((Invocation invocation) => debuggingLogs);
-      logReader.logLines.listen(null, onError: (Object error) => streamComplete.complete());
-      logReader.debuggerStream = iosDeployDebugger;
-
-      await streamComplete.future;
+    when(vmService.onStderrEvent).thenAnswer((Invocation invocation) {
+      return stderController.stream;
     });
+    logReader.connectedVMService = vmService;
 
-    testWithoutContext('detaches debugger', () async {
-      final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
-        iMobileDevice: IMobileDevice(
-          artifacts: artifacts,
-          processManager: processManager,
-          cache: fakeCache,
-          logger: logger,
-        ),
-        useSyslog: false,
-      );
-      final MockIOSDeployDebugger iosDeployDebugger = MockIOSDeployDebugger();
-      when(iosDeployDebugger.logLines).thenAnswer((Invocation invocation) => const Stream<String>.empty());
-      logReader.debuggerStream = iosDeployDebugger;
+    stdoutCompleter.complete(Success());
+    stderrCompleter.complete(Success());
+    stdoutController.add(Event(
+      kind: 'Stdout',
+      timestamp: 0,
+      bytes: base64.encode(utf8.encode('  This is a message ')),
+    ));
+    stderController.add(Event(
+      kind: 'Stderr',
+      timestamp: 0,
+      bytes: base64.encode(utf8.encode('  And this is an error ')),
+    ));
 
-      logReader.dispose();
-      verify(iosDeployDebugger.detach());
-    });
+    // Wait for stream listeners to fire.
+    await expectLater(logReader.logLines, emitsInAnyOrder(<Matcher>[
+      equals('  This is a message '),
+      equals('  And this is an error '),
+    ]));
   });
 }
 
 class MockArtifacts extends Mock implements Artifacts {}
 class MockVmService extends Mock implements VmService {}
-class MockIOSDeployDebugger extends Mock implements IOSDeployDebugger {}
diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart
index 806f7ff..12c8777 100644
--- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart
@@ -67,25 +67,19 @@
 
 // The command used to actually launch the app with args in debug.
 const FakeCommand kLaunchDebugCommand = FakeCommand(command: <String>[
-  'script',
-  '-t',
-  '0',
-  '/dev/null',
   'ios-deploy',
   '--id',
   '123',
   '--bundle',
   '/',
-  '--debug',
   '--no-wifi',
+  '--justlaunch',
   '--args',
   '--enable-dart-profiling --enable-service-port-fallback --disable-service-auth-codes --observatory-port=60700 --enable-checked-mode --verify-entry-points'
 ], environment: <String, String>{
   'PATH': '/usr/bin:null',
   'DYLD_LIBRARY_PATH': '/path/to/libraries',
-},
-stdout: '(lldb)     run\nsuccess',
-);
+});
 
 void main() {
   // TODO(jonahwilliams): This test doesn't really belong here but
@@ -108,7 +102,7 @@
   });
 
   // Still uses context for analytics and mDNS.
-  testUsingContext('IOSDevice.startApp succeeds in debug mode via mDNS discovery when log reading fails', () async {
+  testUsingContext('IOSDevice.startApp succeeds in debug mode via mDNS discovery', () async {
     final FileSystem fileSystem = MemoryFileSystem.test();
     final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
       kDeployCommand,
@@ -151,7 +145,6 @@
       debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
       platformArgs: <String, dynamic>{},
       fallbackPollingDelay: Duration.zero,
-      fallbackThrottleTimeout: const Duration(milliseconds: 10),
     );
 
     verify(globals.flutterUsage.sendEvent('ios-handshake', 'mdns-success')).called(1);
@@ -164,58 +157,7 @@
   });
 
   // Still uses context for analytics and mDNS.
-  testUsingContext('IOSDevice.startApp succeeds in debug mode via log reading', () async {
-    final FileSystem fileSystem = MemoryFileSystem.test();
-    final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-      kDeployCommand,
-      kLaunchDebugCommand,
-    ]);
-    final IOSDevice device = setUpIOSDevice(
-      processManager: processManager,
-      fileSystem: fileSystem,
-      vmServiceConnector: (String string, {Log log}) async {
-        throw const io.SocketException(
-          'OS Error: Connection refused, errno = 61, address = localhost, port '
-          '= 58943',
-        );
-      },
-    );
-    final IOSApp iosApp = PrebuiltIOSApp(
-      projectBundleId: 'app',
-      bundleName: 'Runner',
-      bundleDir: fileSystem.currentDirectory,
-    );
-    final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
-
-    device.portForwarder = const NoOpDevicePortForwarder();
-    device.setLogReader(iosApp, deviceLogReader);
-
-    // Start writing messages to the log reader.
-    Timer.run(() {
-      deviceLogReader.addLine('Foo');
-      deviceLogReader.addLine('Observatory listening on http://127.0.0.1:456');
-    });
-
-    final LaunchResult launchResult = await device.startApp(iosApp,
-      prebuiltApplication: true,
-      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
-      platformArgs: <String, dynamic>{},
-      fallbackPollingDelay: Duration.zero,
-      fallbackThrottleTimeout: const Duration(milliseconds: 10),
-    );
-
-    expect(launchResult.started, true);
-    expect(launchResult.hasObservatory, true);
-    verify(globals.flutterUsage.sendEvent('ios-handshake', 'log-success')).called(1);
-    verifyNever(globals.flutterUsage.sendEvent('ios-handshake', 'mdns-failure'));
-    expect(await device.stopApp(iosApp), false);
-  }, overrides: <Type, Generator>{
-    MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(),
-    Usage: () => MockUsage(),
-  });
-
-  // Still uses context for analytics and mDNS.
-  testUsingContext('IOSDevice.startApp fails in debug mode when Observatory URI is malformed', () async {
+  testUsingContext('IOSDevice.startApp succeeds in debug mode when mDNS fails', () async {
     final FileSystem fileSystem = MemoryFileSystem.test();
     final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
       kDeployCommand,
@@ -244,15 +186,69 @@
     // Now that the reader is used, start writing messages to it.
     Timer.run(() {
       deviceLogReader.addLine('Foo');
-      deviceLogReader.addLine('Observatory listening on http://127.0.0.1:456abc');
+      deviceLogReader.addLine('Observatory listening on http://127.0.0.1:456');
     });
+    when(MDnsObservatoryDiscovery.instance.getObservatoryUri(any, any, usesIpv6: anyNamed('usesIpv6')))
+      .thenAnswer((Invocation invocation) async => null);
 
     final LaunchResult launchResult = await device.startApp(iosApp,
       prebuiltApplication: true,
       debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
       platformArgs: <String, dynamic>{},
       fallbackPollingDelay: Duration.zero,
-      // fallbackThrottleTimeout: const Duration(milliseconds: 10),
+    );
+
+    expect(launchResult.started, true);
+    expect(launchResult.hasObservatory, true);
+    verify(globals.flutterUsage.sendEvent('ios-handshake', 'mdns-failure')).called(1);
+    verify(globals.flutterUsage.sendEvent('ios-handshake', 'fallback-success')).called(1);
+    expect(await device.stopApp(iosApp), false);
+  }, overrides: <Type, Generator>{
+    Usage: () => MockUsage(),
+    MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(),
+  });
+
+  // Still uses context for analytics and mDNS.
+  testUsingContext('IOSDevice.startApp fails in debug mode when mDNS fails and '
+    'when Observatory URI is malformed', () async {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
+      kDeployCommand,
+      kLaunchDebugCommand,
+    ]);
+    final IOSDevice device = setUpIOSDevice(
+      processManager: processManager,
+      fileSystem: fileSystem,
+      vmServiceConnector: (String string, {Log log}) async {
+        throw const io.SocketException(
+          'OS Error: Connection refused, errno = 61, address = localhost, port '
+          '= 58943',
+        );
+      },
+    );
+    final IOSApp iosApp = PrebuiltIOSApp(
+      projectBundleId: 'app',
+      bundleName: 'Runner',
+      bundleDir: fileSystem.currentDirectory,
+    );
+    final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
+
+    device.portForwarder = const NoOpDevicePortForwarder();
+    device.setLogReader(iosApp, deviceLogReader);
+
+    // Now that the reader is used, start writing messages to it.
+    Timer.run(() {
+      deviceLogReader.addLine('Foo');
+      deviceLogReader.addLine('Observatory listening on http:/:/127.0.0.1:456');
+    });
+    when(MDnsObservatoryDiscovery.instance.getObservatoryUri(any, any, usesIpv6: anyNamed('usesIpv6')))
+      .thenAnswer((Invocation invocation) async => null);
+
+    final LaunchResult launchResult = await device.startApp(iosApp,
+      prebuiltApplication: true,
+      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
+      platformArgs: <String, dynamic>{},
+      fallbackPollingDelay: Duration.zero,
     );
 
     expect(launchResult.started, false);
@@ -263,8 +259,8 @@
       label: anyNamed('label'),
       value: anyNamed('value'),
     )).called(1);
-    verify(globals.flutterUsage.sendEvent('ios-handshake', 'log-failure')).called(1);
     verify(globals.flutterUsage.sendEvent('ios-handshake', 'mdns-failure')).called(1);
+    verify(globals.flutterUsage.sendEvent('ios-handshake', 'fallback-failure')).called(1);
     }, overrides: <Type, Generator>{
       MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(),
       Usage: () => MockUsage(),
@@ -392,7 +388,6 @@
       debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
       platformArgs: <String, dynamic>{},
       fallbackPollingDelay: Duration.zero,
-      fallbackThrottleTimeout: const Duration(milliseconds: 10),
     );
 
     expect(launchResult.started, true);
@@ -410,17 +405,13 @@
       kDeployCommand,
       FakeCommand(
         command: <String>[
-          'script',
-          '-t',
-          '0',
-          '/dev/null',
           'ios-deploy',
           '--id',
           '123',
           '--bundle',
           '/',
-          '--debug',
           '--no-wifi',
+          '--justlaunch',
           // The arguments below are determined by what is passed into
           // the debugging options argument to startApp.
           '--args',
@@ -445,8 +436,7 @@
         ], environment: const <String, String>{
           'PATH': '/usr/bin:null',
           'DYLD_LIBRARY_PATH': '/path/to/libraries',
-        },
-        stdout: '(lldb)     run\nsuccess',
+        }
       )
     ]);
     final IOSDevice device = setUpIOSDevice(
@@ -465,15 +455,22 @@
       bundleName: 'Runner',
       bundleDir: fileSystem.currentDirectory,
     );
-    final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
+    final Uri uri = Uri(
+      scheme: 'http',
+      host: '127.0.0.1',
+      port: 1234,
+      path: 'observatory',
+    );
 
+    device.setLogReader(iosApp, FakeDeviceLogReader());
     device.portForwarder = const NoOpDevicePortForwarder();
-    device.setLogReader(iosApp, deviceLogReader);
 
-    // Start writing messages to the log reader.
-    Timer.run(() {
-      deviceLogReader.addLine('Observatory listening on http://127.0.0.1:1234');
-    });
+    when(MDnsObservatoryDiscovery.instance.getObservatoryUri(
+      any,
+      any,
+      usesIpv6: anyNamed('usesIpv6'),
+      hostVmservicePort: anyNamed('hostVmservicePort')
+    )).thenAnswer((Invocation invocation) async => uri);
 
     final LaunchResult launchResult = await device.startApp(iosApp,
       prebuiltApplication: true,
@@ -495,7 +492,6 @@
       ),
       platformArgs: <String, dynamic>{},
       fallbackPollingDelay: Duration.zero,
-      fallbackThrottleTimeout: const Duration(milliseconds: 10),
     );
 
     expect(launchResult.started, true);