emulator_test.dart 12.2 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// 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
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_workflow.dart';
10
import 'package:flutter_tools/src/base/io.dart';
11
import 'package:flutter_tools/src/base/logger.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
import '../src/testbed.dart';
23

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
const FakeEmulator emulator1 = FakeEmulator('Nexus_5', 'Nexus 5', 'Google');
const FakeEmulator emulator2 = FakeEmulator('Nexus_5X_API_27_x86', 'Nexus 5X', 'Google');
const FakeEmulator emulator3 = FakeEmulator('iOS Simulator', 'iOS Simulator', 'Apple');
const List<Emulator> emulators = <Emulator>[
  emulator1,
  emulator2,
  emulator3,
];

// 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.
const String fakeCreateFailureOutput =
  '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)

const FakeCommand kListEmulatorsCommand = FakeCommand(
  command: <String>['avdmanager', 'create', 'avd', '-n', 'temp'],
  stderr: fakeCreateFailureOutput,
  exitCode: 1,
);

48
void main() {
49 50
  MockProcessManager mockProcessManager;
  MockAndroidSdk mockSdk;
51
  MockXcode mockXcode;
52 53

  setUp(() {
54 55 56
    mockProcessManager = MockProcessManager();
    mockSdk = MockAndroidSdk();
    mockXcode = MockXcode();
57 58

    when(mockSdk.avdManagerPath).thenReturn('avdmanager');
59
    when(mockSdk.getAvdManagerPath()).thenReturn('avdmanager');
60 61 62
    when(mockSdk.emulatorPath).thenReturn('emulator');
  });

63
  group('EmulatorManager', () {
64
    // iOS discovery uses context.
65 66
    testUsingContext('getEmulators', () async {
      // Test that EmulatorManager.getEmulators() doesn't throw.
67 68 69 70 71 72 73 74 75 76 77 78
      final EmulatorManager emulatorManager = EmulatorManager(
        fileSystem: MemoryFileSystem.test(),
        logger: BufferLogger.test(),
        processManager: FakeProcessManager.list(<FakeCommand>[
          const FakeCommand(
            command: <String>['emulator', '-list-avds'],
            stdout: 'existing-avd-1',
          ),
        ]),
        androidSdk: mockSdk,
        androidWorkflow: AndroidWorkflow(
          androidSdk: mockSdk,
79
          featureFlags: TestFeatureFlags(),
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
        ),
      );

      await expectLater(() async => await emulatorManager.getAllAvailableEmulators(),
        returnsNormally);
    });

    testWithoutContext('getEmulatorsById', () async {
      final TestEmulatorManager testEmulatorManager = TestEmulatorManager(emulators);

      expect(await testEmulatorManager.getEmulatorsMatching('Nexus_5'), <Emulator>[emulator1]);
      expect(await testEmulatorManager.getEmulatorsMatching('Nexus_5X'), <Emulator>[emulator2]);
      expect(await testEmulatorManager.getEmulatorsMatching('Nexus_5X_API_27_x86'),  <Emulator>[emulator2]);
      expect(await testEmulatorManager.getEmulatorsMatching('Nexus'), <Emulator>[emulator1, emulator2]);
      expect(await testEmulatorManager.getEmulatorsMatching('iOS Simulator'), <Emulator>[emulator3]);
      expect(await testEmulatorManager.getEmulatorsMatching('ios'),  <Emulator>[emulator3]);
96 97
    });

98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
    testUsingContext('create emulator with a missing avdmanager does not crash.', () async {
      when(mockSdk.avdManagerPath).thenReturn(null);
      when(mockSdk.getAvdManagerPath()).thenReturn(null);
      final EmulatorManager emulatorManager = EmulatorManager(
        fileSystem: MemoryFileSystem.test(),
        logger: BufferLogger.test(),
        processManager: FakeProcessManager.list(<FakeCommand>[
          const FakeCommand(
            command: <String>['emulator', '-list-avds'],
            stdout: 'existing-avd-1',
          ),
        ]),
        androidSdk: mockSdk,
        androidWorkflow: AndroidWorkflow(
          androidSdk: mockSdk,
113
          featureFlags: TestFeatureFlags(),
114 115 116 117 118 119
        ),
      );
      final CreateEmulatorResult result = await emulatorManager.createEmulator();

      expect(result.success, false);
      expect(result.error, contains('avdmanager is missing from the Android SDK'));
120
    });
121

122
    // iOS discovery uses context.
123
    testUsingContext('create emulator with an empty name does not fail', () async {
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
      final EmulatorManager emulatorManager = EmulatorManager(
        fileSystem: MemoryFileSystem.test(),
        logger: BufferLogger.test(),
        processManager: FakeProcessManager.list(<FakeCommand>[
          const FakeCommand(
            command: <String>['emulator', '-list-avds'],
            stdout: 'existing-avd-1',
          ),
          const FakeCommand(
            command: <String>['avdmanager', 'list', 'device', '-c'],
            stdout: 'test\ntest2\npixel\npixel-xl\n',
          ),
          kListEmulatorsCommand,
          const FakeCommand(
            command: <String>[
              'avdmanager',
              'create',
              'avd',
              '-n',
              'flutter_emulator',
              '-k',
              'system-images;android-27;google_apis_playstore;x86',
              '-d',
              'pixel',
            ],
          )
        ]),
        androidSdk: mockSdk,
        androidWorkflow: AndroidWorkflow(
          androidSdk: mockSdk,
154
          featureFlags: TestFeatureFlags(),
155 156 157 158 159
        ),
      );
      final CreateEmulatorResult result = await emulatorManager.createEmulator();

      expect(result.success, true);
160 161
    });

162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
    testWithoutContext('create emulator with a unique name does not throw', () async {
      final EmulatorManager emulatorManager = EmulatorManager(
        fileSystem: MemoryFileSystem.test(),
        logger: BufferLogger.test(),
        processManager: FakeProcessManager.list(<FakeCommand>[
          const FakeCommand(
            command: <String>['avdmanager', 'list', 'device', '-c'],
            stdout: 'test\ntest2\npixel\npixel-xl\n',
          ),
          kListEmulatorsCommand,
          const FakeCommand(
            command: <String>[
              'avdmanager',
              'create',
              'avd',
              // The specified name is given with the -n flag.
              '-n',
              'test',
              '-k',
              'system-images;android-27;google_apis_playstore;x86',
              '-d',
              'pixel',
            ],
          )
        ]),
        androidSdk: mockSdk,
        androidWorkflow: AndroidWorkflow(
          androidSdk: mockSdk,
190
          featureFlags: TestFeatureFlags(),
191 192 193 194 195
        ),
      );
      final CreateEmulatorResult result = await emulatorManager.createEmulator(name: 'test');

      expect(result.success, true);
196 197
    });

198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
    testWithoutContext('create emulator with an existing name errors', () async {
      final EmulatorManager emulatorManager = EmulatorManager(
        fileSystem: MemoryFileSystem.test(),
        logger: BufferLogger.test(),
        processManager: FakeProcessManager.list(<FakeCommand>[
          const FakeCommand(
            command: <String>['avdmanager', 'list', 'device', '-c'],
            stdout: 'test\ntest2\npixel\npixel-xl\n',
          ),
          kListEmulatorsCommand,
          const FakeCommand(
            command: <String>[
              'avdmanager',
              'create',
              'avd',
              '-n',
              'existing-avd-1',
              '-k',
              'system-images;android-27;google_apis_playstore;x86',
              '-d',
              'pixel',
            ],
            exitCode: 1,
            stderr: "Error: Android Virtual Device 'existing-avd-1' already exists.\n"
              'Use --force if you want to replace it.'
          )
        ]),
        androidSdk: mockSdk,
        androidWorkflow: AndroidWorkflow(
          androidSdk: mockSdk,
228
          featureFlags: TestFeatureFlags(),
229 230 231 232 233
        ),
      );
      final CreateEmulatorResult result = await emulatorManager.createEmulator(name: 'existing-avd-1');

      expect(result.success, false);
234 235
    });

236
    // iOS discovery uses context.
237
    testUsingContext('create emulator without a name but when default exists adds a suffix', () async {
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
      final EmulatorManager emulatorManager = EmulatorManager(
        fileSystem: MemoryFileSystem.test(),
        logger: BufferLogger.test(),
        processManager: FakeProcessManager.list(<FakeCommand>[
          const FakeCommand(
            command: <String>['emulator', '-list-avds'],
            stdout: 'existing-avd-1\nflutter_emulator',
          ),
          const FakeCommand(
            command: <String>['avdmanager', 'list', 'device', '-c'],
            stdout: 'test\ntest2\npixel\npixel-xl\n',
          ),
          kListEmulatorsCommand,
          const FakeCommand(
            command: <String>[
              'avdmanager',
              'create',
              'avd',
              // a "_2" suffix is added to disambiguate from the existing emulator.
              '-n',
              'flutter_emulator_2',
              '-k',
              'system-images;android-27;google_apis_playstore;x86',
              '-d',
              'pixel',
            ],
          )
        ]),
        androidSdk: mockSdk,
        androidWorkflow: AndroidWorkflow(
          androidSdk: mockSdk,
269
          featureFlags: TestFeatureFlags(),
270 271 272 273 274 275
        ),
      );
      final CreateEmulatorResult result = await emulatorManager.createEmulator();

      expect(result.success, true);
      expect(result.emulatorName, 'flutter_emulator_2');
276
    });
277
  });
278 279 280 281 282 283 284

  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 {
285
        final List<String> args = invocation.positionalArguments[0] as List<String>;
286 287 288
        if (args.length >= 3 && args[0] == 'open' && args[1] == '-a' && args[2] == '/fake/simulator.app') {
          didAttemptToRunSimulator = true;
        }
289
        return ProcessResult(101, 0, '', '');
290 291 292
      });
    });
    testUsingContext('runs correct launch commands', () async {
293
      const Emulator emulator = IOSEmulator('ios');
294 295 296 297 298 299 300
      await emulator.launch();
      expect(didAttemptToRunSimulator, equals(true));
    }, overrides: <Type, Generator>{
      ProcessManager: () => mockProcessManager,
      Xcode: () => mockXcode,
    });
  });
301 302 303 304 305
}

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

306 307
  final List<Emulator> allEmulators;

308
  @override
309
  Future<List<Emulator>> getAllAvailableEmulators() {
310
    return Future<List<Emulator>>.value(allEmulators);
311 312 313
  }
}

314 315
class FakeEmulator extends Emulator {
  const FakeEmulator(String id, this.name, this.manufacturer)
316
    : super(id, true);
317 318

  @override
319
  final String name;
320

321 322
  @override
  final String manufacturer;
323

324 325 326 327 328 329
  @override
  Category get category => Category.mobile;

  @override
  PlatformType get platformType => PlatformType.android;

330
  @override
331
  Future<void> launch() {
332
    throw UnimplementedError('Not implemented in Mock');
333
  }
334
}
335 336 337 338 339 340 341 342 343 344

class MockProcessManager extends Mock implements ProcessManager {

  @override
  ProcessResult runSync(
    List<dynamic> command, {
    String workingDirectory,
    Map<String, String> environment,
    bool includeParentEnvironment = true,
    bool runInShell = false,
345
    Encoding stdoutEncoding = systemEncoding,
346
    Encoding stderrEncoding = systemEncoding,
347
  }) {
348 349 350
    final String program = command[0] as String;
    final List<String> args = command.sublist(1) as List<String>;
    switch (program) {
351
      case '/usr/bin/xcode-select':
352
        throw ProcessException(program, args);
353 354
        break;
    }
355
    throw StateError('Unexpected process call: $command');
356 357
  }
}
358 359

class MockXcode extends Mock implements Xcode {}