emulator_test.dart 13.2 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5

6

7
import 'package:file/file.dart';
8
import 'package:file/memory.dart';
9
import 'package:flutter_tools/src/android/android_sdk.dart';
10 11
import 'package:flutter_tools/src/android/android_workflow.dart';
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
import 'package:test/fake.dart';
17

18 19
import '../src/common.dart';
import '../src/context.dart';
20
import '../src/fake_process_manager.dart';
21
import '../src/fakes.dart';
22

23 24 25 26 27 28 29 30 31 32
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
33
// system images paths. This is an example of the output to use in the fake.
34 35 36 37 38 39 40 41 42 43 44 45 46
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,
);

47
void main() {
48 49 50 51
  late FakeProcessManager fakeProcessManager;
  late FakeAndroidSdk sdk;
  late FileSystem fileSystem;
  late Xcode xcode;
52 53

  setUp(() {
54
    fileSystem = MemoryFileSystem.test();
55
    fakeProcessManager = FakeProcessManager.empty();
56
    sdk = FakeAndroidSdk();
57
    xcode = Xcode.test(processManager: fakeProcessManager, fileSystem: fileSystem);
58

59 60 61 62
    sdk
      ..avdManagerPath = 'avdmanager'
      ..emulatorPath = 'emulator'
      ..adbPath = 'adb';
63 64
  });

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

86
      await expectLater(() async => emulatorManager.getAllAvailableEmulators(),
87 88 89
        returnsNormally);
    });

90 91 92
    testUsingContext('getEmulators with no Android SDK', () async {
      // Test that EmulatorManager.getEmulators() doesn't throw when there's no Android SDK.
      final EmulatorManager emulatorManager = EmulatorManager(
93
        java: FakeJava(),
94 95 96 97 98 99 100 101 102
        fileSystem: MemoryFileSystem.test(),
        logger: BufferLogger.test(),
        processManager: FakeProcessManager.list(<FakeCommand>[
          const FakeCommand(
            command: <String>['emulator', '-list-avds'],
            stdout: 'existing-avd-1',
          ),
        ]),
        androidWorkflow: AndroidWorkflow(
103
          androidSdk: sdk,
104 105 106 107
          featureFlags: TestFeatureFlags(),
        ),
      );

108
      await expectLater(() async => emulatorManager.getAllAvailableEmulators(),
109 110 111
        returnsNormally);
    });

112
    testWithoutContext('getEmulatorsById', () async {
113
      final TestEmulatorManager testEmulatorManager = TestEmulatorManager(emulators,
114
        java: FakeJava(),
115 116 117 118 119 120 121 122
        logger: BufferLogger.test(),
        processManager: fakeProcessManager,
        androidWorkflow: AndroidWorkflow(
          androidSdk: sdk,
          featureFlags: TestFeatureFlags(),
        ),
        fileSystem: fileSystem,
      );
123 124 125 126 127 128 129

      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]);
130 131
    });

132
    testUsingContext('create emulator with a missing avdmanager does not crash.', () async {
133
      sdk.avdManagerPath = null;
134
      final EmulatorManager emulatorManager = EmulatorManager(
135
        java: FakeJava(),
136 137 138 139 140 141 142 143
        fileSystem: MemoryFileSystem.test(),
        logger: BufferLogger.test(),
        processManager: FakeProcessManager.list(<FakeCommand>[
          const FakeCommand(
            command: <String>['emulator', '-list-avds'],
            stdout: 'existing-avd-1',
          ),
        ]),
144
        androidSdk: sdk,
145
        androidWorkflow: AndroidWorkflow(
146
          androidSdk: sdk,
147
          featureFlags: TestFeatureFlags(),
148 149 150 151 152 153
        ),
      );
      final CreateEmulatorResult result = await emulatorManager.createEmulator();

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

156
    // iOS discovery uses context.
157
    testUsingContext('create emulator with an empty name does not fail', () async {
158
      final EmulatorManager emulatorManager = EmulatorManager(
159
        java: FakeJava(),
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
        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',
            ],
184
          ),
185
        ]),
186
        androidSdk: sdk,
187
        androidWorkflow: AndroidWorkflow(
188
          androidSdk: sdk,
189
          featureFlags: TestFeatureFlags(),
190 191 192 193 194
        ),
      );
      final CreateEmulatorResult result = await emulatorManager.createEmulator();

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

197 198
    testWithoutContext('create emulator with a unique name does not throw', () async {
      final EmulatorManager emulatorManager = EmulatorManager(
199
        java: FakeJava(),
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
        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',
            ],
221
          ),
222
        ]),
223
        androidSdk: sdk,
224
        androidWorkflow: AndroidWorkflow(
225
          androidSdk: sdk,
226
          featureFlags: TestFeatureFlags(),
227 228 229 230 231
        ),
      );
      final CreateEmulatorResult result = await emulatorManager.createEmulator(name: 'test');

      expect(result.success, true);
232 233
    });

234 235
    testWithoutContext('create emulator with an existing name errors', () async {
      final EmulatorManager emulatorManager = EmulatorManager(
236
        java: FakeJava(),
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
        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"
259 260
              'Use --force if you want to replace it.',
          ),
261
        ]),
262
        androidSdk: sdk,
263
        androidWorkflow: AndroidWorkflow(
264
          androidSdk: sdk,
265
          featureFlags: TestFeatureFlags(),
266 267 268 269 270
        ),
      );
      final CreateEmulatorResult result = await emulatorManager.createEmulator(name: 'existing-avd-1');

      expect(result.success, false);
271 272
    });

273
    // iOS discovery uses context.
274
    testUsingContext('create emulator without a name but when default exists adds a suffix', () async {
275
      final EmulatorManager emulatorManager = EmulatorManager(
276
        java: FakeJava(),
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
        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',
            ],
302
          ),
303
        ]),
304
        androidSdk: sdk,
305
        androidWorkflow: AndroidWorkflow(
306
          androidSdk: sdk,
307
          featureFlags: TestFeatureFlags(),
308 309 310 311 312 313
        ),
      );
      final CreateEmulatorResult result = await emulatorManager.createEmulator();

      expect(result.success, true);
      expect(result.emulatorName, 'flutter_emulator_2');
314
    });
315
  });
316 317 318

  group('ios_emulators', () {
    testUsingContext('runs correct launch commands', () async {
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
      fileSystem.directory('/fake/Xcode.app/Contents/Developer/Applications/Simulator.app').createSync(recursive: true);
      fakeProcessManager.addCommands(
        <FakeCommand>[
          const FakeCommand(
            command: <String>['/usr/bin/xcode-select', '--print-path'],
            stdout: '/fake/Xcode.app/Contents/Developer',
          ),
          const FakeCommand(command: <String>[
            'open',
            '-n',
            '-a',
            '/fake/Xcode.app/Contents/Developer/Applications/Simulator.app',
          ]),
          const FakeCommand(command: <String>[
            'open',
            '-a',
            '/fake/Xcode.app/Contents/Developer/Applications/Simulator.app',
336
          ]),
337 338 339
        ],
      );

340
      const Emulator emulator = IOSEmulator('ios');
341
      await emulator.launch();
342
      expect(fakeProcessManager, hasNoRemainingExpectations);
343
    }, overrides: <Type, Generator>{
344 345 346
      ProcessManager: () => fakeProcessManager,
      Xcode: () => xcode,
      FileSystem: () => fileSystem,
347 348
    });
  });
349 350 351
}

class TestEmulatorManager extends EmulatorManager {
352
  TestEmulatorManager(this.allEmulators, {
353
    required super.java,
354 355 356 357 358
    required super.logger,
    required super.processManager,
    required super.androidWorkflow,
    required super.fileSystem,
  });
359

360 361
  final List<Emulator> allEmulators;

362
  @override
363
  Future<List<Emulator>> getAllAvailableEmulators() {
364
    return Future<List<Emulator>>.value(allEmulators);
365 366 367
  }
}

368 369
class FakeEmulator extends Emulator {
  const FakeEmulator(String id, this.name, this.manufacturer)
370
    : super(id, true);
371 372

  @override
373
  final String name;
374

375 376
  @override
  final String manufacturer;
377

378 379 380 381
  @override
  Category get category => Category.mobile;

  @override
382
  PlatformType get platformType => PlatformType.android;
383

384
  @override
385
  Future<void> launch({bool coldBoot = false}) {
386
    throw UnimplementedError('Not implemented in Mock');
387
  }
388
}
389 390 391

class FakeAndroidSdk extends Fake implements AndroidSdk {
  @override
392
  String? avdManagerPath;
393 394

  @override
395
  String? emulatorPath;
396 397

  @override
398
  String? adbPath;
399 400

  @override
401
  String? getAvdManagerPath() => avdManagerPath;
402 403 404 405

  @override
  String getAvdPath() => 'avd';
}