// Copyright 2014 The Flutter 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 'dart:async';

import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:args/command_runner.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/base/net.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/run.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/version.dart';
import 'package:flutter_tools/src/web/web_runner.dart';
import 'package:mockito/mockito.dart';

import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fakes.dart';
import '../../src/mocks.dart';
import '../../src/testbed.dart';

void main() {
  group('run', () {
    MockApplicationPackageFactory mockApplicationPackageFactory;
    MockDeviceManager mockDeviceManager;
    MockFlutterVersion mockStableFlutterVersion;
    MockFlutterVersion mockUnstableFlutterVersion;

    setUpAll(() {
      Cache.disableLocking();
      mockApplicationPackageFactory = MockApplicationPackageFactory();
      mockDeviceManager = MockDeviceManager();
      mockStableFlutterVersion = MockFlutterVersion(isStable: true);
      mockUnstableFlutterVersion = MockFlutterVersion(isStable: false);
    });

    testUsingContext('fails when target not found', () async {
      final RunCommand command = RunCommand();
      applyMocksToCommand(command);
      try {
        await createTestCommandRunner(command).run(<String>['run', '-t', 'abc123', '--no-pub']);
        fail('Expect exception');
      } on ToolExit catch (e) {
        expect(e.exitCode ?? 1, 1);
      }
    });

    testUsingContext('does not support "--use-application-binary" and "--fast-start"', () async {
      globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
      globals.fs.file('pubspec.yaml').createSync();
      globals.fs.file('.packages').createSync();

      final RunCommand command = RunCommand();
      applyMocksToCommand(command);
      try {
        await createTestCommandRunner(command).run(<String>[
          'run',
          '--use-application-binary=app/bar/faz',
          '--fast-start',
          '--no-pub',
          '--show-test-device',
        ]);
        fail('Expect exception');
      } on Exception catch (e) {
        expect(e.toString(), isNot(contains('--fast-start is not supported with --use-application-binary')));
      }
    }, overrides: <Type, Generator>{
      FileSystem: () => MemoryFileSystem(),
      ProcessManager: () => FakeProcessManager.any(),
    });

    testUsingContext('Walks upward looking for a pubspec.yaml and succeeds if found', () async {
      globals.fs.file('pubspec.yaml').createSync();
      globals.fs.file('.packages')
        ..createSync()
        ..writeAsStringSync('Not a valid package');

      globals.fs.currentDirectory = globals.fs.directory(globals.fs.path.join('a', 'b', 'c'))
        ..createSync(recursive: true);

      final RunCommand command = RunCommand();
      applyMocksToCommand(command);
      try {
        await createTestCommandRunner(command).run(<String>[
          'run',
          '--fast-start',
          '--no-pub',
        ]);
        fail('Expect exception');
      } on Exception catch (e) {
        expect(e, isA<ToolExit>());
      }
      final BufferLogger bufferLogger = globals.logger as BufferLogger;
      expect(bufferLogger.statusText, contains(
        'Changing current working directory to:'
      ));
    }, overrides: <Type, Generator>{
      FileSystem: () => MemoryFileSystem(),
      ProcessManager: () => FakeProcessManager.any(),
    });

    testUsingContext('Fails with toolExit run in profile mode on emulator with machine flag', () async {
      globals.fs.file('pubspec.yaml').createSync();
      globals.fs.file('.packages').writeAsStringSync('\n');
      globals.fs.file('lib/main.dart').createSync(recursive: true);
      final FakeDevice device = FakeDevice(isLocalEmulator: true);
      when(deviceManager.getAllConnectedDevices()).thenAnswer((Invocation invocation) async {
        return <Device>[device];
      });
      when(deviceManager.getDevices()).thenAnswer((Invocation invocation) async {
        return <Device>[device];
      });
      when(deviceManager.findTargetDevices(any)).thenAnswer((Invocation invocation) async {
        return <Device>[device];
      });
      when(deviceManager.hasSpecifiedAllDevices).thenReturn(false);
      when(deviceManager.deviceDiscoverers).thenReturn(<DeviceDiscovery>[]);

      final RunCommand command = RunCommand();
      applyMocksToCommand(command);
      await expectLater(createTestCommandRunner(command).run(<String>[
        'run',
        '--no-pub',
        '--machine',
        '--profile',
      ]), throwsToolExit(message: 'not supported for emulators'));
    }, overrides: <Type, Generator>{
      FileSystem: () => MemoryFileSystem.test(),
      ProcessManager: () => FakeProcessManager.any(),
      DeviceManager: () => MockDeviceManager(),
      Stdio: () => MockStdio(),
    });

    testUsingContext('Walks upward looking for a pubspec.yaml and exits if missing', () async {
      globals.fs.currentDirectory = globals.fs.directory(globals.fs.path.join('a', 'b', 'c'))
        ..createSync(recursive: true);

      final RunCommand command = RunCommand();
      applyMocksToCommand(command);
      try {
        await createTestCommandRunner(command).run(<String>[
          'run',
          '--fast-start',
          '--no-pub',
        ]);
        fail('Expect exception');
      } on Exception catch (e) {
        expect(e, isA<ToolExit>());
        expect(e.toString(), contains('No pubspec.yaml file found'));
      }
    }, overrides: <Type, Generator>{
      FileSystem: () => MemoryFileSystem(),
      ProcessManager: () => FakeProcessManager.any(),
    });


    group('run app', () {
      MemoryFileSystem fs;
      MockArtifacts mockArtifacts;
      MockCache mockCache;
      MockProcessManager mockProcessManager;
      MockUsage mockUsage;
      Directory tempDir;

      setUpAll(() {
        mockArtifacts = MockArtifacts();
        mockCache = MockCache();
        mockUsage = MockUsage();
        fs = MemoryFileSystem();
        mockProcessManager = MockProcessManager();

        tempDir = fs.systemTempDirectory.createTempSync('flutter_run_test.');
        fs.currentDirectory = tempDir;

        tempDir.childFile('pubspec.yaml')
          .writeAsStringSync('name: flutter_app');
        tempDir.childFile('.packages')
          .writeAsStringSync('# Generated by pub on 2019-11-25 12:38:01.801784.');
        final Directory libDir = tempDir.childDirectory('lib');
        libDir.createSync();
        final File mainFile = libDir.childFile('main.dart');
        mainFile.writeAsStringSync('void main() {}');

        when(mockDeviceManager.hasSpecifiedDeviceId).thenReturn(false);
        when(mockDeviceManager.hasSpecifiedAllDevices).thenReturn(false);
      });

      testUsingContext('exits with a user message when no supported devices attached', () async {
        final RunCommand command = RunCommand();
        applyMocksToCommand(command);

        const List<Device> noDevices = <Device>[];
        when(mockDeviceManager.getDevices()).thenAnswer(
          (Invocation invocation) => Future<List<Device>>.value(noDevices)
        );
        when(mockDeviceManager.findTargetDevices(any)).thenAnswer(
          (Invocation invocation) => Future<List<Device>>.value(noDevices)
        );

        try {
          await createTestCommandRunner(command).run(<String>[
            'run',
            '--no-pub',
            '--no-hot',
          ]);
          fail('Expect exception');
        } on ToolExit catch (e) {
          expect(e.message, null);
        }

        expect(testLogger.statusText, contains(userMessages.flutterNoSupportedDevices));
      }, overrides: <Type, Generator>{
        DeviceManager: () => mockDeviceManager,
        FileSystem: () => fs,
        ProcessManager: () => mockProcessManager,
      });

      testUsingContext('updates cache before checking for devices', () async {
        final RunCommand command = RunCommand();
        applyMocksToCommand(command);

        // Called as part of requiredArtifacts()
        when(mockDeviceManager.getDevices()).thenAnswer(
          (Invocation invocation) => Future<List<Device>>.value(<Device>[])
        );
        // No devices are attached, we just want to verify update the cache
        // BEFORE checking for devices
        when(mockDeviceManager.findTargetDevices(any)).thenAnswer(
          (Invocation invocation) => Future<List<Device>>.value(<Device>[])
        );

        try {
          await createTestCommandRunner(command).run(<String>[
            'run',
            '--no-pub',
          ]);
          fail('Exception expected');
        } on ToolExit catch (e) {
          // We expect a ToolExit because no devices are attached
          expect(e.message, null);
        } on Exception catch (e) {
          fail('ToolExit expected, got $e');
        }

        verifyInOrder(<void>[
          // cache update
          mockCache.updateAll(<DevelopmentArtifact>{DevelopmentArtifact.universal}),
          // as part of gathering `requiredArtifacts`
          mockDeviceManager.getDevices(),
          // in validateCommand()
          mockDeviceManager.findTargetDevices(any),
        ]);
      }, overrides: <Type, Generator>{
        ApplicationPackageFactory: () => mockApplicationPackageFactory,
        Cache: () => mockCache,
        DeviceManager: () => mockDeviceManager,
        FileSystem: () => fs,
        ProcessManager: () => mockProcessManager,
      });

      testUsingContext('passes device target platform to usage', () async {
        final RunCommand command = RunCommand();
        applyMocksToCommand(command);
        final MockDevice mockDevice = MockDevice(TargetPlatform.ios);
        when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) => Future<bool>.value(false));
        when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(FakeDeviceLogReader());
        when(mockDevice.supportsFastStart).thenReturn(true);
        when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) => Future<String>.value('iOS 13'));
        // App fails to start because we're only interested in usage
        when(mockDevice.startApp(
          any,
          mainPath: anyNamed('mainPath'),
          debuggingOptions: anyNamed('debuggingOptions'),
          platformArgs: anyNamed('platformArgs'),
          route: anyNamed('route'),
          prebuiltApplication: anyNamed('prebuiltApplication'),
          ipv6: anyNamed('ipv6'),
        )).thenAnswer((Invocation invocation) => Future<LaunchResult>.value(LaunchResult.failed()));

        when(mockArtifacts.getArtifactPath(
          Artifact.flutterPatchedSdkPath,
          platform: anyNamed('platform'),
          mode: anyNamed('mode'),
        )).thenReturn('/path/to/sdk');

        when(mockDeviceManager.getDevices()).thenAnswer(
          (Invocation invocation) => Future<List<Device>>.value(<Device>[mockDevice])
        );

        when(mockDeviceManager.findTargetDevices(any)).thenAnswer(
          (Invocation invocation) => Future<List<Device>>.value(<Device>[mockDevice])
        );

        final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_run_test.');
        tempDir.childDirectory('ios').childFile('AppDelegate.swift').createSync(recursive: true);
        tempDir.childFile('.packages').createSync();
        tempDir.childDirectory('lib').childFile('main.dart').createSync(recursive: true);
        tempDir.childFile('pubspec.yaml')
          ..createSync()
          ..writeAsStringSync('# Hello, World');
        globals.fs.currentDirectory = tempDir;

        try {
          await createTestCommandRunner(command).run(<String>[
            'run',
            '--no-pub',
            '--no-hot',
          ]);
          fail('Exception expected');
        } on ToolExit catch (e) {
          // We expect a ToolExit because app does not start
          expect(e.message, null);
        } on Exception catch (e) {
          fail('ToolExit expected, got $e');
        }
        final List<dynamic> captures = verify(mockUsage.sendCommand(
          captureAny,
          parameters: captureAnyNamed('parameters'),
        )).captured;
        expect(captures[0], 'run');
        final Map<String, String> parameters = captures[1] as Map<String, String>;

        expect(parameters[cdKey(CustomDimensions.commandRunIsEmulator)], 'false');
        expect(parameters[cdKey(CustomDimensions.commandRunTargetName)], 'ios');
        expect(parameters[cdKey(CustomDimensions.commandRunProjectHostLanguage)], 'swift');
        expect(parameters[cdKey(CustomDimensions.commandRunTargetOsVersion)], 'iOS 13');
        expect(parameters[cdKey(CustomDimensions.commandRunModeName)], 'debug');
        expect(parameters[cdKey(CustomDimensions.commandRunProjectModule)], 'false');
        expect(parameters.containsKey(cdKey(CustomDimensions.commandRunAndroidEmbeddingVersion)), false);
      }, overrides: <Type, Generator>{
        ApplicationPackageFactory: () => mockApplicationPackageFactory,
        Artifacts: () => mockArtifacts,
        Cache: () => mockCache,
        DeviceManager: () => mockDeviceManager,
        FileSystem: () => fs,
        ProcessManager: () => mockProcessManager,
        Usage: () => mockUsage,
      });
    });

    group('dart-flags option', () {
      setUpAll(() {
        final FakeDevice fakeDevice = FakeDevice();
        when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
          return Future<List<Device>>.value(<Device>[fakeDevice]);
        });
        when(mockDeviceManager.findTargetDevices(any)).thenAnswer(
          (Invocation invocation) => Future<List<Device>>.value(<Device>[fakeDevice])
        );
      });

      RunCommand command;
      List<String> args;
      setUp(() {
        command = TestRunCommand();
        args = <String> [
          'run',
          '--dart-flags', '"--observe"',
          '--no-hot',
          '--no-pub',
        ];
      });

      testUsingContext('is not available on stable channel', () async {
        // Stable branch.
        try {
          await createTestCommandRunner(command).run(args);
          fail('Expect exception');
        // ignore: unused_catch_clause
        } on UsageException catch(e) {
          // Not available while on stable branch.
        }
      }, overrides: <Type, Generator>{
        DeviceManager: () => mockDeviceManager,
        FlutterVersion: () => mockStableFlutterVersion,
      });

      testUsingContext('is populated in debug mode', () async {
        // FakeDevice.startApp checks that --dart-flags doesn't get dropped and
        // throws ToolExit with FakeDevice.kSuccess if the flag is populated.
        try {
          await createTestCommandRunner(command).run(args);
          fail('Expect exception');
        } on ToolExit catch (e) {
          expect(e.exitCode, FakeDevice.kSuccess);
        }
      }, overrides: <Type, Generator>{
        ApplicationPackageFactory: () => mockApplicationPackageFactory,
        DeviceManager: () => mockDeviceManager,
        FlutterVersion: () => mockUnstableFlutterVersion,
      });

      testUsingContext('is populated in profile mode', () async {
        args.add('--profile');

        // FakeDevice.startApp checks that --dart-flags doesn't get dropped and
        // throws ToolExit with FakeDevice.kSuccess if the flag is populated.
        try {
          await createTestCommandRunner(command).run(args);
          fail('Expect exception');
        } on ToolExit catch (e) {
          expect(e.exitCode, FakeDevice.kSuccess);
        }
      }, overrides: <Type, Generator>{
        ApplicationPackageFactory: () => mockApplicationPackageFactory,
        DeviceManager: () => mockDeviceManager,
        FlutterVersion: () => mockUnstableFlutterVersion,
      });

      testUsingContext('is not populated in release mode', () async {
        args.add('--release');

        // FakeDevice.startApp checks that --dart-flags *does* get dropped and
        // throws ToolExit with FakeDevice.kSuccess if the flag is set to the
        // empty string.
        try {
          await createTestCommandRunner(command).run(args);
          fail('Expect exception');
        } on ToolExit catch (e) {
          expect(e.exitCode, FakeDevice.kSuccess);
        }
      }, overrides: <Type, Generator>{
        ApplicationPackageFactory: () => mockApplicationPackageFactory,
        DeviceManager: () => mockDeviceManager,
        FlutterVersion: () => mockUnstableFlutterVersion,
      });
    });

    testUsingContext('should only request artifacts corresponding to connected devices', () async {
      when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
        return Future<List<Device>>.value(<Device>[
          MockDevice(TargetPlatform.android_arm),
        ]);
      });

      expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
        DevelopmentArtifact.universal,
        DevelopmentArtifact.androidGenSnapshot,
      }));

      when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
        return Future<List<Device>>.value(<Device>[
          MockDevice(TargetPlatform.ios),
        ]);
      });

      expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
        DevelopmentArtifact.universal,
        DevelopmentArtifact.iOS,
      }));

      when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
        return Future<List<Device>>.value(<Device>[
          MockDevice(TargetPlatform.ios),
          MockDevice(TargetPlatform.android_arm),
        ]);
      });

      expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
        DevelopmentArtifact.universal,
        DevelopmentArtifact.iOS,
        DevelopmentArtifact.androidGenSnapshot,
      }));

      when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
        return Future<List<Device>>.value(<Device>[
          MockDevice(TargetPlatform.web_javascript),
        ]);
      });

      expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
        DevelopmentArtifact.universal,
        DevelopmentArtifact.web,
      }));
    }, overrides: <Type, Generator>{
      DeviceManager: () => mockDeviceManager,
    });

    group('--dart-define option', () {
      MemoryFileSystem fs;
      MockProcessManager mockProcessManager;
      MockWebRunnerFactory mockWebRunnerFactory;

      setUpAll(() {
        final FakeDevice fakeDevice = FakeDevice().._targetPlatform = TargetPlatform.web_javascript;
        when(mockDeviceManager.getDevices()).thenAnswer(
          (Invocation invocation) => Future<List<Device>>.value(<Device>[fakeDevice])
        );
        when(mockDeviceManager.findTargetDevices(any)).thenAnswer(
          (Invocation invocation) => Future<List<Device>>.value(<Device>[fakeDevice])
        );
      });

      RunCommand command;
      List<String> args;
      setUp(() {
        command = TestRunCommand();
        args = <String> [
          'run',
          '--dart-define=FOO=bar',
          '--no-hot',
          '--no-pub',
        ];
        applyMocksToCommand(command);
        fs = MemoryFileSystem();
        mockProcessManager = MockProcessManager();
        mockWebRunnerFactory = MockWebRunnerFactory();
      });

      testUsingContext('populates the environment', () async {
        final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_run_test.');
        globals.fs.currentDirectory = tempDir;

        final Directory libDir = tempDir.childDirectory('lib');
        libDir.createSync();
        final File mainFile = libDir.childFile('main.dart');
        mainFile.writeAsStringSync('void main() {}');

        final Directory webDir = tempDir.childDirectory('web');
        webDir.createSync();
        final File indexFile = libDir.childFile('index.html');
        indexFile.writeAsStringSync('<h1>Hello</h1>');

        await createTestCommandRunner(command).run(args);
        expect(mockWebRunnerFactory._dartDefines, <String>['FOO=bar']);
      }, overrides: <Type, Generator>{
        FeatureFlags: () => TestFeatureFlags(
          isWebEnabled: true,
        ),
        FileSystem: () => fs,
        ProcessManager: () => mockProcessManager,
        DeviceManager: () => mockDeviceManager,
        FlutterVersion: () => mockStableFlutterVersion,
        WebRunnerFactory: () => mockWebRunnerFactory,
      });

      testUsingContext('populates dartDefines in --machine mode', () async {
        final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_run_test.');
        globals.fs.currentDirectory = tempDir;

        final Directory libDir = tempDir.childDirectory('lib');
        libDir.createSync();
        final File mainFile = libDir.childFile('main.dart');
        mainFile.writeAsStringSync('void main() {}');

        final Directory webDir = tempDir.childDirectory('web');
        webDir.createSync();
        final File indexFile = libDir.childFile('index.html');
        indexFile.writeAsStringSync('<h1>Hello</h1>');

        when(mockDeviceManager.deviceDiscoverers).thenReturn(<DeviceDiscovery>[]);

        args.add('--machine');
        await createTestCommandRunner(command).run(args);
        expect(mockWebRunnerFactory._dartDefines, <String>['FOO=bar']);
      }, overrides: <Type, Generator>{
        FeatureFlags: () => TestFeatureFlags(
          isWebEnabled: true,
        ),
        FileSystem: () => fs,
        ProcessManager: () => mockProcessManager,
        DeviceManager: () => mockDeviceManager,
        FlutterVersion: () => mockStableFlutterVersion,
        WebRunnerFactory: () => mockWebRunnerFactory,
      });
    });
  });
}

class MockArtifacts extends Mock implements Artifacts {}
class MockCache extends Mock implements Cache {}
class MockUsage extends Mock implements Usage {}

class MockDeviceManager extends Mock implements DeviceManager {}
class MockDevice extends Mock implements Device {
  MockDevice(this._targetPlatform);

  final TargetPlatform _targetPlatform;

  @override
  Future<TargetPlatform> get targetPlatform async => Future<TargetPlatform>.value(_targetPlatform);
}

class TestRunCommand extends RunCommand {
  @override
  // ignore: must_call_super
  Future<void> validateCommand() async {
    devices = await deviceManager.getDevices();
  }
}

class FakeDevice extends Fake implements Device {
  FakeDevice({bool isLocalEmulator = false})
   : _isLocalEmulator = isLocalEmulator;

  static const int kSuccess = 1;
  static const int kFailure = -1;
  TargetPlatform _targetPlatform = TargetPlatform.ios;
  final bool _isLocalEmulator;

  @override
  String get id => 'fake_device';

  void _throwToolExit(int code) => throwToolExit(null, exitCode: code);

  @override
  Future<bool> get isLocalEmulator => Future<bool>.value(_isLocalEmulator);

  @override
  bool get supportsHotReload => false;

  @override
  bool get supportsFastStart => false;

  @override
  Future<String> get sdkNameAndVersion => Future<String>.value('');

  @override
  DeviceLogReader getLogReader({
    ApplicationPackage app,
    bool includePastLogs = false,
  }) {
    return FakeDeviceLogReader();
  }

  @override
  String get name => 'FakeDevice';

  @override
  Future<TargetPlatform> get targetPlatform async => _targetPlatform;

  @override
  final PlatformType platformType = PlatformType.ios;

  @override
  Future<LaunchResult> startApp(
    ApplicationPackage package, {
    String mainPath,
    String route,
    DebuggingOptions debuggingOptions,
    Map<String, dynamic> platformArgs,
    bool prebuiltApplication = false,
    bool usesTerminalUi = true,
    bool ipv6 = false,
  }) async {
    final String dartFlags = debuggingOptions.dartFlags;
    // In release mode, --dart-flags should be set to the empty string and
    // provided flags should be dropped. In debug and profile modes,
    // --dart-flags should not be empty.
    if (debuggingOptions.buildInfo.isRelease) {
      if (dartFlags.isNotEmpty) {
        _throwToolExit(kFailure);
      }
      _throwToolExit(kSuccess);
    } else {
      if (dartFlags.isEmpty) {
        _throwToolExit(kFailure);
      }
      _throwToolExit(kSuccess);
    }
    return null;
  }
}

class MockWebRunnerFactory extends Mock implements WebRunnerFactory {
  List<String> _dartDefines;

  @override
  ResidentRunner createWebRunner(
    FlutterDevice device, {
    String target,
    bool stayResident,
    FlutterProject flutterProject,
    bool ipv6,
    DebuggingOptions debuggingOptions,
    UrlTunneller urlTunneller,
  }) {
    _dartDefines = debuggingOptions.buildInfo.dartDefines;
    return MockWebRunner();
  }
}

class MockWebRunner extends Mock implements ResidentRunner {
  @override
  bool get debuggingEnabled => false;

  @override
  Future<int> run({
    Completer<DebugConnectionInfo> connectionInfoCompleter,
    Completer<void> appStartedCompleter,
    String route,
  }) async {
    return 0;
  }

  @override
  Future<int> waitForAppToFinish() async => 0;
}
