device_test.dart 19.6 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
// @dart = 2.8

7 8
import 'dart:async';

9
import 'package:fake_async/fake_async.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/base/terminal.dart';
13
import 'package:flutter_tools/src/base/user_messages.dart';
14
import 'package:flutter_tools/src/build_info.dart';
15
import 'package:flutter_tools/src/device.dart';
16
import 'package:flutter_tools/src/project.dart';
17
import 'package:meta/meta.dart';
18
import 'package:test/fake.dart';
19

20
import '../src/common.dart';
21
import '../src/fake_devices.dart';
22

23
void main() {
24
  group('DeviceManager', () {
25
    testWithoutContext('getDevices', () async {
26 27 28 29
      final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f');
      final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e');
      final FakeDevice device3 = FakeDevice('iPod touch', '82564b38861a9a5');
      final List<Device> devices = <Device>[device1, device2, device3];
30 31 32 33 34 35 36

      final DeviceManager deviceManager = TestDeviceManager(
        devices,
        logger: BufferLogger.test(),
        terminal: Terminal.test(),
      );

37
      expect(await deviceManager.getDevices(), devices);
38
    });
39

40
    testWithoutContext('getDeviceById exact matcher', () async {
41 42 43
      final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f');
      final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e');
      final FakeDevice device3 = FakeDevice('iPod touch', '82564b38861a9a5');
44
      final List<Device> devices = <Device>[device1, device2, device3];
45
      final BufferLogger logger = BufferLogger.test();
46 47 48 49 50 51 52 53 54

      // Include different device discoveries:
      // 1. One that never completes to prove the first exact match is
      // returned quickly.
      // 2. One that throws, to prove matches can return when some succeed
      // and others fail.
      // 3. A device discoverer that succeeds.
      final DeviceManager deviceManager = TestDeviceManager(
        devices,
55 56 57 58
        deviceDiscoveryOverrides: <DeviceDiscovery>[
          ThrowingPollingDeviceDiscovery(),
          LongPollingDeviceDiscovery(),
        ],
59 60
        logger: logger,
        terminal: Terminal.test(),
61
      );
62

63
      Future<void> expectDevice(String id, List<Device> expected) async {
64
        expect(await deviceManager.getDevicesById(id), expected);
65
      }
66
      await expectDevice('01abfc49119c410e', <Device>[device2]);
67
      expect(logger.traceText, contains('Ignored error discovering 01abfc49119c410e'));
68
      await expectDevice('Nexus 5X', <Device>[device2]);
69
      expect(logger.traceText, contains('Ignored error discovering Nexus 5X'));
70
      await expectDevice('0553790d0a4e726f', <Device>[device1]);
71 72 73
      expect(logger.traceText, contains('Ignored error discovering 0553790d0a4e726f'));
    });

74
    testWithoutContext('getDeviceById prefix matcher', () async {
75 76 77 78
      final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f');
      final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e');
      final FakeDevice device3 = FakeDevice('iPod touch', '82564b38861a9a5');
      final List<Device> devices = <Device>[device1, device2, device3];
79
      final BufferLogger logger = BufferLogger.test();
80 81 82 83 84 85 86

      // Include different device discoveries:
      // 1. One that throws, to prove matches can return when some succeed
      // and others fail.
      // 2. A device discoverer that succeeds.
      final DeviceManager deviceManager = TestDeviceManager(
        devices,
87 88 89
        deviceDiscoveryOverrides: <DeviceDiscovery>[
          ThrowingPollingDeviceDiscovery(),
        ],
90 91
        logger: logger,
        terminal: Terminal.test(),
92 93 94 95 96
      );

      Future<void> expectDevice(String id, List<Device> expected) async {
        expect(await deviceManager.getDevicesById(id), expected);
      }
97
      await expectDevice('Nexus 5', <Device>[device1]);
98
      expect(logger.traceText, contains('Ignored error discovering Nexus 5'));
99
      await expectDevice('0553790', <Device>[device1]);
100
      expect(logger.traceText, contains('Ignored error discovering 0553790'));
101
      await expectDevice('Nexus', <Device>[device1, device2]);
102
      expect(logger.traceText, contains('Ignored error discovering Nexus'));
103
    });
104

105
    testWithoutContext('getAllConnectedDevices caches', () async {
106
      final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f');
107 108 109 110 111
      final TestDeviceManager deviceManager = TestDeviceManager(
        <Device>[device1],
        logger: BufferLogger.test(),
        terminal: Terminal.test(),
      );
112 113
      expect(await deviceManager.getAllConnectedDevices(), <Device>[device1]);

114
      final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e');
115 116 117 118
      deviceManager.resetDevices(<Device>[device2]);
      expect(await deviceManager.getAllConnectedDevices(), <Device>[device1]);
    });

119
    testWithoutContext('refreshAllConnectedDevices does not cache', () async {
120
      final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f');
121 122 123 124 125
      final TestDeviceManager deviceManager = TestDeviceManager(
        <Device>[device1],
        logger: BufferLogger.test(),
        terminal: Terminal.test(),
      );
126 127
      expect(await deviceManager.refreshAllConnectedDevices(), <Device>[device1]);

128
      final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e');
129 130 131
      deviceManager.resetDevices(<Device>[device2]);
      expect(await deviceManager.refreshAllConnectedDevices(), <Device>[device2]);
    });
132
  });
133

134 135 136 137 138
  testWithoutContext('PollingDeviceDiscovery startPolling', () {
    FakeAsync().run((FakeAsync time) {
      final FakePollingDeviceDiscovery pollingDeviceDiscovery = FakePollingDeviceDiscovery();
      pollingDeviceDiscovery.startPolling();
      time.elapse(const Duration(milliseconds: 4001));
139

140 141 142
      // First check should use the default polling timeout
      // to quickly populate the list.
      expect(pollingDeviceDiscovery.lastPollingTimeout, isNull);
143

144
      time.elapse(const Duration(milliseconds: 4001));
145

146 147 148
      // Subsequent polling should be much longer.
      expect(pollingDeviceDiscovery.lastPollingTimeout, const Duration(seconds: 30));
      pollingDeviceDiscovery.stopPolling();
149
    });
150 151
  });

152
  group('Filter devices', () {
153 154 155 156 157
    final FakeDevice ephemeralOne = FakeDevice('ephemeralOne', 'ephemeralOne');
    final FakeDevice ephemeralTwo = FakeDevice('ephemeralTwo', 'ephemeralTwo');
    final FakeDevice nonEphemeralOne = FakeDevice('nonEphemeralOne', 'nonEphemeralOne', ephemeral: false);
    final FakeDevice nonEphemeralTwo = FakeDevice('nonEphemeralTwo', 'nonEphemeralTwo', ephemeral: false);
    final FakeDevice unsupported = FakeDevice('unsupported', 'unsupported', isSupported: false);
158 159 160 161 162 163
    final FakeDevice webDevice = FakeDevice('webby', 'webby')
      ..targetPlatform = Future<TargetPlatform>.value(TargetPlatform.web_javascript);
    final FakeDevice fuchsiaDevice = FakeDevice('fuchsiay', 'fuchsiay')
      ..targetPlatform = Future<TargetPlatform>.value(TargetPlatform.fuchsia_x64);

    testWithoutContext('chooses ephemeral device', () async {
164
      final List<Device> devices = <Device>[
165
        ephemeralOne,
166 167 168 169 170
        nonEphemeralOne,
        nonEphemeralTwo,
        unsupported,
      ];

171 172 173 174 175 176
      final DeviceManager deviceManager = TestDeviceManager(
        devices,
        logger: BufferLogger.test(),
        terminal: Terminal.test(),
      );
      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
177

178
      expect(filtered.single, ephemeralOne);
179 180
    });

181
    testWithoutContext('choose first non-ephemeral device', () async {
182 183 184 185
      final List<Device> devices = <Device>[
        nonEphemeralOne,
        nonEphemeralTwo,
      ];
186 187
      final FakeTerminal terminal = FakeTerminal()
        ..setPrompt(<String>['1', '2', 'q', 'Q'], '1');
188

189 190 191
      final DeviceManager deviceManager = TestDeviceManager(
        devices,
        logger: BufferLogger.test(),
192
        terminal: terminal,
193 194
      );
      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
195 196 197 198 199 200

      expect(filtered, <Device>[
        nonEphemeralOne
      ]);
    });

201
    testWithoutContext('choose second non-ephemeral device', () async {
202 203 204 205
      final List<Device> devices = <Device>[
        nonEphemeralOne,
        nonEphemeralTwo,
      ];
206 207
      final FakeTerminal terminal = FakeTerminal()
        ..setPrompt(<String>['1', '2', 'q', 'Q'], '2');
208

209 210 211
      final DeviceManager deviceManager = TestDeviceManager(
        devices,
        logger: BufferLogger.test(),
212
        terminal: terminal,
213 214
      );
      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
215 216 217 218 219 220

      expect(filtered, <Device>[
        nonEphemeralTwo
      ]);
    });

221
    testWithoutContext('choose first ephemeral device', () async {
222 223 224 225 226
      final List<Device> devices = <Device>[
        ephemeralOne,
        ephemeralTwo,
      ];

227 228
      final FakeTerminal terminal = FakeTerminal()
        ..setPrompt(<String>['1', '2', 'q', 'Q'], '1');
229

230 231 232
      final DeviceManager deviceManager = TestDeviceManager(
        devices,
        logger: BufferLogger.test(),
233
        terminal: terminal,
234 235
      );
      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
236 237 238 239 240 241

      expect(filtered, <Device>[
        ephemeralOne
      ]);
    });

242
    testWithoutContext('choose second ephemeral device', () async {
243 244 245 246
      final List<Device> devices = <Device>[
        ephemeralOne,
        ephemeralTwo,
      ];
247 248
      final FakeTerminal terminal = FakeTerminal()
        ..setPrompt(<String>['1', '2', 'q', 'Q'], '2');
249

250 251 252
      final DeviceManager deviceManager = TestDeviceManager(
        devices,
        logger: BufferLogger.test(),
253
        terminal: terminal,
254 255
      );
      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
256 257

      expect(filtered, <Device>[
258 259 260 261
        ephemeralTwo
      ]);
    });

262
    testWithoutContext('choose non-ephemeral device', () async {
263 264 265
      final List<Device> devices = <Device>[
        ephemeralOne,
        ephemeralTwo,
266 267
        nonEphemeralOne,
        nonEphemeralTwo,
268 269
      ];

270 271
      final FakeTerminal terminal = FakeTerminal()
        ..setPrompt(<String>['1', '2', '3', '4', 'q', 'Q'], '3');
272

273 274 275
      final DeviceManager deviceManager = TestDeviceManager(
        devices,
        logger: BufferLogger.test(),
276
        terminal: terminal,
277 278 279
      );

      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
280 281 282

      expect(filtered, <Device>[
        nonEphemeralOne
283 284
      ]);
    });
285

286
    testWithoutContext('exit from choose one of available devices', () async {
287 288 289 290 291
      final List<Device> devices = <Device>[
        ephemeralOne,
        ephemeralTwo,
      ];

292 293
      final FakeTerminal terminal = FakeTerminal()
        ..setPrompt(<String>['1', '2', 'q', 'Q'], 'q');
294 295 296 297

      final DeviceManager deviceManager = TestDeviceManager(
        devices,
        logger: BufferLogger.test(),
298
        terminal: terminal,
299 300
      );
      await expectLater(
301
        () async => deviceManager.findTargetDevices(FakeFlutterProject()),
302
        throwsToolExit(),
303
      );
304 305
    });

306
    testWithoutContext('Removes a single unsupported device', () async {
307 308 309 310
      final List<Device> devices = <Device>[
        unsupported,
      ];

311 312 313 314 315 316
      final DeviceManager deviceManager = TestDeviceManager(
        devices,
        logger: BufferLogger.test(),
        terminal: Terminal.test(),
      );
      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
317 318 319 320

      expect(filtered, <Device>[]);
    });

321
    testWithoutContext('Does not remove an unsupported device if FlutterProject is null', () async {
322 323 324 325
      final List<Device> devices = <Device>[
        unsupported,
      ];

326 327 328 329 330
      final DeviceManager deviceManager = TestDeviceManager(
        devices,
        logger: BufferLogger.test(),
        terminal: Terminal.test(),
      );
331 332 333 334 335
      final List<Device> filtered = await deviceManager.findTargetDevices(null);

      expect(filtered, <Device>[unsupported]);
    });

336
    testWithoutContext('Removes web and fuchsia from --all', () async {
337 338 339 340
      final List<Device> devices = <Device>[
        webDevice,
        fuchsiaDevice,
      ];
341 342 343 344 345
      final DeviceManager deviceManager = TestDeviceManager(
        devices,
        logger: BufferLogger.test(),
        terminal: Terminal.test(),
      );
346 347
      deviceManager.specifiedDeviceId = 'all';

348
      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
349 350 351 352

      expect(filtered, <Device>[]);
    });

353
    testWithoutContext('Removes unsupported devices from --all', () async {
354 355 356 357 358
      final List<Device> devices = <Device>[
        nonEphemeralOne,
        nonEphemeralTwo,
        unsupported,
      ];
359 360 361 362 363
      final DeviceManager deviceManager = TestDeviceManager(
        devices,
        logger: BufferLogger.test(),
        terminal: Terminal.test(),
      );
364 365
      deviceManager.specifiedDeviceId = 'all';

366
      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
367 368 369 370 371 372

      expect(filtered, <Device>[
        nonEphemeralOne,
        nonEphemeralTwo,
      ]);
    });
373

374
    testWithoutContext('uses DeviceManager.isDeviceSupportedForProject instead of device.isSupportedForProject', () async {
375 376 377
      final List<Device> devices = <Device>[
        unsupported,
      ];
378 379 380 381 382
      final TestDeviceManager deviceManager = TestDeviceManager(
        devices,
        logger: BufferLogger.test(),
        terminal: Terminal.test(),
      );
383 384
      deviceManager.isAlwaysSupportedOverride = true;

385
      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
386 387 388 389 390

      expect(filtered, <Device>[
        unsupported,
      ]);
    });
391

392
    testWithoutContext('does not refresh device cache without a timeout', () async {
393 394 395
      final List<Device> devices = <Device>[
        ephemeralOne,
      ];
396 397
      final MockDeviceDiscovery deviceDiscovery = MockDeviceDiscovery()
        ..deviceValues = devices;
398

399 400 401
      final DeviceManager deviceManager = TestDeviceManager(
        <Device>[],
        deviceDiscoveryOverrides: <DeviceDiscovery>[
402
          deviceDiscovery
403 404 405 406
        ],
        logger: BufferLogger.test(),
        terminal: Terminal.test(),
      );
407 408
      deviceManager.specifiedDeviceId = ephemeralOne.id;
      final List<Device> filtered = await deviceManager.findTargetDevices(
409
        FakeFlutterProject(),
410 411 412
      );

      expect(filtered.single, ephemeralOne);
413 414
      expect(deviceDiscovery.devicesCalled, 1);
      expect(deviceDiscovery.discoverDevicesCalled, 0);
415 416
    });

417
    testWithoutContext('refreshes device cache with a timeout', () async {
418 419 420 421
      final List<Device> devices = <Device>[
        ephemeralOne,
      ];
      const Duration timeout = Duration(seconds: 2);
422 423
      final MockDeviceDiscovery deviceDiscovery = MockDeviceDiscovery()
        ..deviceValues = devices;
424

425 426 427
      final DeviceManager deviceManager = TestDeviceManager(
        <Device>[],
        deviceDiscoveryOverrides: <DeviceDiscovery>[
428
          deviceDiscovery
429 430 431 432
        ],
        logger: BufferLogger.test(),
        terminal: Terminal.test(),
      );
433 434
      deviceManager.specifiedDeviceId = ephemeralOne.id;
      final List<Device> filtered = await deviceManager.findTargetDevices(
435
        FakeFlutterProject(),
436 437 438 439
        timeout: timeout,
      );

      expect(filtered.single, ephemeralOne);
440 441
      expect(deviceDiscovery.devicesCalled, 1);
      expect(deviceDiscovery.discoverDevicesCalled, 1);
442
    });
443
  });
444

445
  group('JSON encode devices', () {
446
    testWithoutContext('Consistency of JSON representation', () async {
447 448 449 450 451 452 453 454
      expect(
        // This tests that fakeDevices is a list of tuples where "second" is the
        // correct JSON representation of the "first". Actual values are irrelevant
        await Future.wait(fakeDevices.map((FakeDeviceJsonData d) => d.dev.toJson())),
        fakeDevices.map((FakeDeviceJsonData d) => d.json)
      );
    });
  });
455 456 457 458 459 460 461

  testWithoutContext('computeDartVmFlags handles various combinations of Dart VM flags and null_assertions', () {
    expect(computeDartVmFlags(DebuggingOptions.enabled(BuildInfo.debug, dartFlags: null)), '');
    expect(computeDartVmFlags(DebuggingOptions.enabled(BuildInfo.debug, dartFlags: '--foo')), '--foo');
    expect(computeDartVmFlags(DebuggingOptions.enabled(BuildInfo.debug, dartFlags: '', nullAssertions: true)), '--null_assertions');
    expect(computeDartVmFlags(DebuggingOptions.enabled(BuildInfo.debug, dartFlags: '--foo', nullAssertions: true)), '--foo,--null_assertions');
  });
462
}
463

464
class TestDeviceManager extends DeviceManager {
465
    TestDeviceManager(List<Device> allDevices, {
466
    List<DeviceDiscovery> deviceDiscoveryOverrides,
467 468 469
    @required Logger logger,
    @required Terminal terminal,
  }) : super(logger: logger, terminal: terminal, userMessages: UserMessages()) {
470 471 472
    _fakeDeviceDiscoverer = FakePollingDeviceDiscovery();
    _deviceDiscoverers = <DeviceDiscovery>[
      _fakeDeviceDiscoverer,
473 474
      if (deviceDiscoveryOverrides != null)
        ...deviceDiscoveryOverrides
475
    ];
476 477 478
    resetDevices(allDevices);
  }
  @override
479 480 481
  List<DeviceDiscovery> get deviceDiscoverers => _deviceDiscoverers;
  List<DeviceDiscovery> _deviceDiscoverers;
  FakePollingDeviceDiscovery _fakeDeviceDiscoverer;
482

483
  void resetDevices(List<Device> allDevices) {
484
    _fakeDeviceDiscoverer.setDevices(allDevices);
485
  }
486

487
  bool isAlwaysSupportedOverride;
488 489 490 491 492 493 494 495

  @override
  bool isDeviceSupportedForProject(Device device, FlutterProject flutterProject) {
    if (isAlwaysSupportedOverride != null) {
      return isAlwaysSupportedOverride;
    }
    return super.isDeviceSupportedForProject(device, flutterProject);
  }
496 497
}

498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
class MockDeviceDiscovery extends Fake implements DeviceDiscovery {
  int devicesCalled = 0;
  int discoverDevicesCalled = 0;

  @override
  bool supportsPlatform = true;

  List<Device> deviceValues = <Device>[];

  @override
  Future<List<Device>> get devices async {
    devicesCalled += 1;
    return deviceValues;
  }

  @override
  Future<List<Device>> discoverDevices({Duration timeout}) async {
    discoverDevicesCalled += 1;
    return deviceValues;
  }
}

520
class FakeFlutterProject extends Fake implements FlutterProject {}
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562

class LongPollingDeviceDiscovery extends PollingDeviceDiscovery {
  LongPollingDeviceDiscovery() : super('forever');

  final Completer<List<Device>> _completer = Completer<List<Device>>();

  @override
  Future<List<Device>> pollingGetDevices({ Duration timeout }) async {
    return _completer.future;
  }

  @override
  Future<void> stopPolling() async {
    _completer.complete();
  }

  @override
  Future<void> dispose() async {
    _completer.complete();
  }

  @override
  bool get supportsPlatform => true;

  @override
  bool get canListAnything => true;
}

class ThrowingPollingDeviceDiscovery extends PollingDeviceDiscovery {
  ThrowingPollingDeviceDiscovery() : super('throw');

  @override
  Future<List<Device>> pollingGetDevices({ Duration timeout }) async {
    throw const ProcessException('fake-discovery', <String>[]);
  }

  @override
  bool get supportsPlatform => true;

  @override
  bool get canListAnything => true;
}
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590

class FakeTerminal extends Fake implements Terminal {
  @override
  bool stdinHasTerminal = true;

  @override
  bool usesTerminalUi = true;

  void setPrompt(List<String> characters, String result) {
    _nextPrompt = characters;
    _nextResult = result;
  }

  List<String> _nextPrompt;
  String _nextResult;

  @override
  Future<String> promptForCharInput(
    List<String> acceptedCharacters, {
    Logger logger,
    String prompt,
    int defaultChoiceIndex,
    bool displayAcceptedCharacters = true,
  }) async {
    expect(acceptedCharacters, _nextPrompt);
    return _nextResult;
  }
}