emulator_test.dart 9.18 KB
Newer Older
1 2 3 4 5
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
6
import 'dart:convert';
7

8 9 10 11
import 'package:collection/collection.dart' show ListEquality;
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/base/config.dart';
import 'package:flutter_tools/src/base/io.dart';
12
import 'package:flutter_tools/src/device.dart';
13
import 'package:flutter_tools/src/emulator.dart';
14
import 'package:flutter_tools/src/ios/ios_emulators.dart';
15
import 'package:flutter_tools/src/macos/xcode.dart';
16 17
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
18

19 20 21
import '../src/common.dart';
import '../src/context.dart';
import '../src/mocks.dart';
22 23

void main() {
24 25 26
  MockProcessManager mockProcessManager;
  MockConfig mockConfig;
  MockAndroidSdk mockSdk;
27
  MockXcode mockXcode;
28 29

  setUp(() {
30 31 32 33
    mockProcessManager = MockProcessManager();
    mockConfig = MockConfig();
    mockSdk = MockAndroidSdk();
    mockXcode = MockXcode();
34 35 36 37 38

    when(mockSdk.avdManagerPath).thenReturn('avdmanager');
    when(mockSdk.emulatorPath).thenReturn('emulator');
  });

39 40 41
  group('EmulatorManager', () {
    testUsingContext('getEmulators', () async {
      // Test that EmulatorManager.getEmulators() doesn't throw.
42 43
      final List<Emulator> emulators =
          await emulatorManager.getAllAvailableEmulators();
44 45 46 47
      expect(emulators, isList);
    });

    testUsingContext('getEmulatorsById', () async {
48
      final _MockEmulator emulator1 =
49
          _MockEmulator('Nexus_5', 'Nexus 5', 'Google');
50
      final _MockEmulator emulator2 =
51
          _MockEmulator('Nexus_5X_API_27_x86', 'Nexus 5X', 'Google');
52
      final _MockEmulator emulator3 =
53
          _MockEmulator('iOS Simulator', 'iOS Simulator', 'Apple');
54 55 56
      final List<Emulator> emulators = <Emulator>[
        emulator1,
        emulator2,
57
        emulator3,
58 59
      ];
      final TestEmulatorManager testEmulatorManager =
60
          TestEmulatorManager(emulators);
61

62
      Future<void> expectEmulator(String id, List<Emulator> expected) async {
63
        expect(await testEmulatorManager.getEmulatorsMatching(id), expected);
64
      }
65

66 67 68 69 70 71
      await expectEmulator('Nexus_5', <Emulator>[emulator1]);
      await expectEmulator('Nexus_5X', <Emulator>[emulator2]);
      await expectEmulator('Nexus_5X_API_27_x86', <Emulator>[emulator2]);
      await expectEmulator('Nexus', <Emulator>[emulator1, emulator2]);
      await expectEmulator('iOS Simulator', <Emulator>[emulator3]);
      await expectEmulator('ios', <Emulator>[emulator3]);
72
    });
73

74
    testUsingContext('create emulator with an empty name does not fail', () async {
75 76 77 78 79 80 81 82
      final CreateEmulatorResult res = await emulatorManager.createEmulator();
      expect(res.success, equals(true));
    }, overrides: <Type, Generator>{
      ProcessManager: () => mockProcessManager,
      Config: () => mockConfig,
      AndroidSdk: () => mockSdk,
    });

83
    testUsingContext('create emulator with a unique name does not throw', () async {
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
      final CreateEmulatorResult res =
          await emulatorManager.createEmulator(name: 'test');
      expect(res.success, equals(true));
    }, overrides: <Type, Generator>{
      ProcessManager: () => mockProcessManager,
      Config: () => mockConfig,
      AndroidSdk: () => mockSdk,
    });

    testUsingContext('create emulator with an existing name errors', () async {
      final CreateEmulatorResult res =
          await emulatorManager.createEmulator(name: 'existing-avd-1');
      expect(res.success, equals(false));
    }, overrides: <Type, Generator>{
      ProcessManager: () => mockProcessManager,
      Config: () => mockConfig,
      AndroidSdk: () => mockSdk,
    });

103
    testUsingContext('create emulator without a name but when default exists adds a suffix', () async {
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
      // First will get default name.
      CreateEmulatorResult res = await emulatorManager.createEmulator();
      expect(res.success, equals(true));

      final String defaultName = res.emulatorName;

      // Second...
      res = await emulatorManager.createEmulator();
      expect(res.success, equals(true));
      expect(res.emulatorName, equals('${defaultName}_2'));

      // Third...
      res = await emulatorManager.createEmulator();
      expect(res.success, equals(true));
      expect(res.emulatorName, equals('${defaultName}_3'));
    }, overrides: <Type, Generator>{
      ProcessManager: () => mockProcessManager,
      Config: () => mockConfig,
      AndroidSdk: () => mockSdk,
    });
124
  });
125 126 127 128 129 130 131 132 133 134 135

  group('ios_emulators', () {
    bool didAttemptToRunSimulator = false;
    setUp(() {
      when(mockXcode.xcodeSelectPath).thenReturn('/fake/Xcode.app/Contents/Developer');
      when(mockXcode.getSimulatorPath()).thenAnswer((_) => '/fake/simulator.app');
      when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async {
        final List<String> args = invocation.positionalArguments[0];
        if (args.length >= 3 && args[0] == 'open' && args[1] == '-a' && args[2] == '/fake/simulator.app') {
          didAttemptToRunSimulator = true;
        }
136
        return ProcessResult(101, 0, '', '');
137 138 139
      });
    });
    testUsingContext('runs correct launch commands', () async {
140
      final Emulator emulator = IOSEmulator('ios');
141 142 143 144 145 146 147 148
      await emulator.launch();
      expect(didAttemptToRunSimulator, equals(true));
    }, overrides: <Type, Generator>{
      ProcessManager: () => mockProcessManager,
      Config: () => mockConfig,
      Xcode: () => mockXcode,
    });
  });
149 150 151 152 153
}

class TestEmulatorManager extends EmulatorManager {
  TestEmulatorManager(this.allEmulators);

154 155
  final List<Emulator> allEmulators;

156
  @override
157
  Future<List<Emulator>> getAllAvailableEmulators() {
158
    return Future<List<Emulator>>.value(allEmulators);
159 160 161 162
  }
}

class _MockEmulator extends Emulator {
163
  _MockEmulator(String id, this.name, this.manufacturer)
164
    : super(id, true);
165 166

  @override
167
  final String name;
168

169 170
  @override
  final String manufacturer;
171

172 173 174 175 176 177
  @override
  Category get category => Category.mobile;

  @override
  PlatformType get platformType => PlatformType.android;

178
  @override
179
  Future<void> launch() {
180
    throw UnimplementedError('Not implemented in Mock');
181
  }
182
}
183 184 185 186 187 188 189 190 191 192 193 194 195

class MockConfig extends Mock implements Config {}

class MockProcessManager extends Mock implements ProcessManager {
  /// We have to send a command that fails in order to get the list of valid
  /// system images paths. This is an example of the output to use in the mock.
  static const String mockCreateFailureOutput =
      'Error: Package path (-k) not specified. Valid system image paths are:\n'
      'system-images;android-27;google_apis;x86\n'
      'system-images;android-P;google_apis;x86\n'
      'system-images;android-27;google_apis_playstore;x86\n'
      'null\n'; // Yep, these really end with null (on dantup's machine at least)

196
  static const ListEquality<String> _equality = ListEquality<String>();
197 198 199 200 201 202 203 204 205
  final List<String> _existingAvds = <String>['existing-avd-1'];

  @override
  ProcessResult runSync(
    List<dynamic> command, {
    String workingDirectory,
    Map<String, String> environment,
    bool includeParentEnvironment = true,
    bool runInShell = false,
206
    Encoding stdoutEncoding = systemEncoding,
207
    Encoding stderrEncoding = systemEncoding,
208 209 210 211 212
  }) {
    final String program = command[0];
    final List<String> args = command.sublist(1);
    switch (command[0]) {
      case '/usr/bin/xcode-select':
213
        throw ProcessException(program, args);
214 215 216 217 218 219
        break;
      case 'emulator':
        return _handleEmulator(args);
      case 'avdmanager':
        return _handleAvdManager(args);
    }
220
    throw StateError('Unexpected process call: $command');
221 222 223 224
  }

  ProcessResult _handleEmulator(List<String> args) {
    if (_equality.equals(args, <String>['-list-avds'])) {
225
      return ProcessResult(101, 0, '${_existingAvds.join('\n')}\n', '');
226
    }
227
    throw ProcessException('emulator', args);
228 229 230 231
  }

  ProcessResult _handleAvdManager(List<String> args) {
    if (_equality.equals(args, <String>['list', 'device', '-c'])) {
232
      return ProcessResult(101, 0, 'test\ntest2\npixel\npixel-xl\n', '');
233 234
    }
    if (_equality.equals(args, <String>['create', 'avd', '-n', 'temp'])) {
235
      return ProcessResult(101, 1, '', mockCreateFailureOutput);
236 237 238 239 240 241 242 243 244 245 246
    }
    if (args.length == 8 &&
        _equality.equals(args,
            <String>['create', 'avd', '-n', args[3], '-k', args[5], '-d', args[7]])) {
      // In order to support testing auto generation of names we need to support
      // tracking any created emulators and reject when they already exist so this
      // mock will compare the name of the AVD being created with the fake existing
      // list and either reject if it exists, or add it to the list and return success.
      final String name = args[3];
      // Error if this AVD already existed
      if (_existingAvds.contains(name)) {
247
        return ProcessResult(
248 249 250 251 252 253 254
            101,
            1,
            '',
            "Error: Android Virtual Device '$name' already exists.\n"
            'Use --force if you want to replace it.');
      } else {
        _existingAvds.add(name);
255
        return ProcessResult(101, 0, '', '');
256 257
      }
    }
258
    throw ProcessException('emulator', args);
259 260
  }
}
261 262

class MockXcode extends Mock implements Xcode {}