fuchsia_device_test.dart 36.3 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 10
import 'package:file/memory.dart';
import 'package:flutter_tools/src/application_package.dart';
11
import 'package:flutter_tools/src/artifacts.dart';
12
import 'package:flutter_tools/src/base/dds.dart';
13
import 'package:flutter_tools/src/base/file_system.dart';
14
import 'package:flutter_tools/src/base/io.dart';
15 16
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
17
import 'package:flutter_tools/src/base/time.dart';
18
import 'package:flutter_tools/src/build_info.dart';
19
import 'package:flutter_tools/src/cache.dart';
20
import 'package:flutter_tools/src/device.dart';
21
import 'package:flutter_tools/src/device_port_forwarder.dart';
22
import 'package:flutter_tools/src/fuchsia/fuchsia_dev_finder.dart';
23
import 'package:flutter_tools/src/fuchsia/fuchsia_device.dart';
24
import 'package:flutter_tools/src/fuchsia/fuchsia_ffx.dart';
25 26
import 'package:flutter_tools/src/fuchsia/fuchsia_kernel_compiler.dart';
import 'package:flutter_tools/src/fuchsia/fuchsia_pm.dart';
27
import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart';
28
import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart';
29
import 'package:flutter_tools/src/globals_null_migrated.dart' as globals;
30 31
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/vmservice.dart';
32
import 'package:meta/meta.dart';
33
import 'package:test/fake.dart';
34
import 'package:vm_service/vm_service.dart' as vm_service;
35

36 37
import '../../src/common.dart';
import '../../src/context.dart';
38
import '../../src/fake_vm_services.dart';
39

40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
final vm_service.Isolate fakeIsolate = vm_service.Isolate(
  id: '1',
  pauseEvent: vm_service.Event(
    kind: vm_service.EventKind.kResume,
    timestamp: 0
  ),
  breakpoints: <vm_service.Breakpoint>[],
  exceptionPauseMode: null,
  libraries: <vm_service.LibraryRef>[],
  livePorts: 0,
  name: 'wrong name',
  number: '1',
  pauseOnExit: false,
  runnable: true,
  startTime: 0,
55
  isSystemIsolate: false,
56
  isolateFlags: <vm_service.IsolateFlag>[],
57 58
);

59 60
void main() {
  group('fuchsia device', () {
61
    MemoryFileSystem memoryFileSystem;
62
    File sshConfig;
63
    FakeProcessManager processManager;
Dan Field's avatar
Dan Field committed
64

65
    setUp(() {
66 67
      memoryFileSystem = MemoryFileSystem.test();
      sshConfig = memoryFileSystem.file('ssh_config')..writeAsStringSync('\n');
68
      processManager = FakeProcessManager.empty();
69 70
    });

71
    testWithoutContext('stores the requested id and name', () {
72 73 74
      const String deviceId = 'e80::0000:a00a:f00f:2002/3';
      const String name = 'halfbaked';
      final FuchsiaDevice device = FuchsiaDevice(deviceId, name: name);
75

76 77 78 79
      expect(device.id, deviceId);
      expect(device.name, name);
    });

80 81 82 83 84 85 86 87 88 89 90
    testWithoutContext('supports all runtime modes besides jitRelease', () {
      const String deviceId = 'e80::0000:a00a:f00f:2002/3';
      const String name = 'halfbaked';
      final FuchsiaDevice device = FuchsiaDevice(deviceId, name: name);

      expect(device.supportsRuntimeMode(BuildMode.debug), true);
      expect(device.supportsRuntimeMode(BuildMode.profile), true);
      expect(device.supportsRuntimeMode(BuildMode.release), true);
      expect(device.supportsRuntimeMode(BuildMode.jitRelease), false);
    });

91
    testWithoutContext('lists nothing when workflow cannot list devices', () async {
92
      final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow(canListDevices: false);
93 94 95 96 97 98
      final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
        platform: FakePlatform(operatingSystem: 'linux'),
        fuchsiaSdk: null,
        fuchsiaWorkflow: fuchsiaWorkflow,
        logger: BufferLogger.test(),
      );
99

100 101
      expect(fuchsiaDevices.canListAnything, false);
      expect(await fuchsiaDevices.pollingGetDevices(), isEmpty);
102
    });
103

104
    testWithoutContext('can parse ffx output for single device', () async {
105 106
      final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow(shouldUseDeviceFinder: false, canListDevices: true);
      final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(devices: '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel');
107
      final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
108
        platform: FakePlatform(operatingSystem: 'linux', environment: <String, String>{},),
109 110 111 112
        fuchsiaSdk: fuchsiaSdk,
        fuchsiaWorkflow: fuchsiaWorkflow,
        logger: BufferLogger.test(),
      );
113

114 115 116 117 118 119
      final Device device = (await fuchsiaDevices.pollingGetDevices()).single;

      expect(device.name, 'paper-pulp-bush-angel');
      expect(device.id, '192.168.42.10');
    });

120
    testWithoutContext('can parse device-finder output for single device', () async {
121 122
      final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow(shouldUseDeviceFinder: true, canListDevices: true);
      final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(devFinderDevices: '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel');
123 124 125 126 127 128 129 130 131 132 133 134 135 136
      final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
        platform: FakePlatform(operatingSystem: 'linux', environment: <String, String>{'FUCHSIA_DISABLED_ffx_discovery': '1'},),
        fuchsiaSdk: fuchsiaSdk,
        fuchsiaWorkflow: fuchsiaWorkflow,
        logger: BufferLogger.test(),
      );

      final Device device = (await fuchsiaDevices.pollingGetDevices()).single;

      expect(device.name, 'paper-pulp-bush-angel');
      expect(device.id, '192.168.11.999');
    });

    testWithoutContext('can parse ffx output for multiple devices', () async {
137 138 139 140 141
      final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow(shouldUseDeviceFinder: false, canListDevices: true);
      final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(devices:
        '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel\n'
        '2001:0db8:85a3:0000:0000:8a2e:0370:7335 foo-bar-fiz-buzz'
      );
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
      final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
        platform: FakePlatform(operatingSystem: 'linux'),
        fuchsiaSdk: fuchsiaSdk,
        fuchsiaWorkflow: fuchsiaWorkflow,
        logger: BufferLogger.test(),
      );

      final List<Device> devices = await fuchsiaDevices.pollingGetDevices();

      expect(devices.first.name, 'paper-pulp-bush-angel');
      expect(devices.first.id, '192.168.42.10');
      expect(devices.last.name, 'foo-bar-fiz-buzz');
      expect(devices.last.id, '192.168.42.10');
    });

157
    testWithoutContext('can parse device-finder output for multiple devices', () async {
158 159 160 161 162
      final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow(shouldUseDeviceFinder: true, canListDevices: true);
      final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(devFinderDevices:
        '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel\n'
        '2001:0db8:85a3:0000:0000:8a2e:0370:7335 foo-bar-fiz-buzz'
      );
163 164 165 166 167 168
      final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
        platform: FakePlatform(operatingSystem: 'linux'),
        fuchsiaSdk: fuchsiaSdk,
        fuchsiaWorkflow: fuchsiaWorkflow,
        logger: BufferLogger.test(),
      );
169 170 171 172 173 174 175 176 177 178

      final List<Device> devices = await fuchsiaDevices.pollingGetDevices();

      expect(devices.first.name, 'paper-pulp-bush-angel');
      expect(devices.first.id, '192.168.11.999');
      expect(devices.last.name, 'foo-bar-fiz-buzz');
      expect(devices.last.id, '192.168.11.999');
    });

    testWithoutContext('can parse junk output from ffx', () async {
179 180
      final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow(shouldUseDeviceFinder: false, canListDevices: false);
      final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(devices: 'junk', devFinderDevices: 'junk');
181 182 183 184 185 186
      final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
        platform: FakePlatform(operatingSystem: 'linux'),
        fuchsiaSdk: fuchsiaSdk,
        fuchsiaWorkflow: fuchsiaWorkflow,
        logger: BufferLogger.test(),
      );
187 188 189 190

      final List<Device> devices = await fuchsiaDevices.pollingGetDevices();

      expect(devices, isEmpty);
191 192
    });

193
    testWithoutContext('can parse junk output from device-finder', () async {
194 195
      final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow(shouldUseDeviceFinder: true, canListDevices: true);
      final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(devices: 'junk', devFinderDevices: 'junk');
196 197 198 199 200 201 202 203 204 205 206 207
      final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
        platform: FakePlatform(operatingSystem: 'linux'),
        fuchsiaSdk: fuchsiaSdk,
        fuchsiaWorkflow: fuchsiaWorkflow,
        logger: BufferLogger.test(),
      );

      final List<Device> devices = await fuchsiaDevices.pollingGetDevices();

      expect(devices, isEmpty);
    });

208
    testUsingContext('disposing device disposes the portForwarder', () async {
209
      final FakePortForwarder portForwarder = FakePortForwarder();
210
      final FuchsiaDevice device = FuchsiaDevice('123');
211
      device.portForwarder = portForwarder;
212
      await device.dispose();
213 214

      expect(portForwarder.disposed, true);
215 216
    });

217
    testWithoutContext('default capabilities', () async {
218
      final FuchsiaDevice device = FuchsiaDevice('123');
219 220 221
      final FlutterProject project = FlutterProject.fromDirectoryTest(memoryFileSystem.currentDirectory);
      memoryFileSystem.directory('fuchsia').createSync(recursive: true);
      memoryFileSystem.file('pubspec.yaml').createSync();
222 223 224

      expect(device.supportsHotReload, true);
      expect(device.supportsHotRestart, false);
225
      expect(device.supportsFlutterExit, false);
226
      expect(device.isSupportedForProject(project), true);
227
    });
228 229 230

    test('is ephemeral', () {
      final FuchsiaDevice device = FuchsiaDevice('123');
231

232 233
      expect(device.ephemeral, true);
    });
234

235
    testWithoutContext('supported for project', () async {
236
      final FuchsiaDevice device = FuchsiaDevice('123');
237 238 239
      final FlutterProject project = FlutterProject.fromDirectoryTest(memoryFileSystem.currentDirectory);
      memoryFileSystem.directory('fuchsia').createSync(recursive: true);
      memoryFileSystem.file('pubspec.yaml').createSync();
240

241
      expect(device.isSupportedForProject(project), true);
242 243
    });

244
    testWithoutContext('not supported for project', () async {
245
      final FuchsiaDevice device = FuchsiaDevice('123');
246 247
      final FlutterProject project = FlutterProject.fromDirectoryTest(memoryFileSystem.currentDirectory);
      memoryFileSystem.file('pubspec.yaml').createSync();
248

249
      expect(device.isSupportedForProject(project), false);
250
    });
251

252 253
    testUsingContext('targetPlatform does not throw when sshConfig is missing', () async {
      final FuchsiaDevice device = FuchsiaDevice('123');
254

255 256 257
      expect(await device.targetPlatform, TargetPlatform.fuchsia_arm64);
    }, overrides: <Type, Generator>{
      FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: null),
258 259
      FuchsiaSdk: () => FakeFuchsiaSdk(),
      ProcessManager: () => processManager,
260 261
    });

262
    testUsingContext('targetPlatform arm64 works', () async {
263 264 265 266 267
      processManager.addCommand(const FakeCommand(
        command: <String>['ssh', '-F', '/ssh_config', '123', 'uname -m'],
        stdout: 'aarch64',
      ));

268 269 270 271
      final FuchsiaDevice device = FuchsiaDevice('123');
      expect(await device.targetPlatform, TargetPlatform.fuchsia_arm64);
    }, overrides: <Type, Generator>{
      FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
272 273
      FuchsiaSdk: () => FakeFuchsiaSdk(),
      ProcessManager: () => processManager,
274 275 276
    });

    testUsingContext('targetPlatform x64 works', () async {
277 278 279 280 281
      processManager.addCommand(const FakeCommand(
        command: <String>['ssh', '-F', '/ssh_config', '123', 'uname -m'],
        stdout: 'x86_64',
      ));

282 283 284 285
      final FuchsiaDevice device = FuchsiaDevice('123');
      expect(await device.targetPlatform, TargetPlatform.fuchsia_x64);
    }, overrides: <Type, Generator>{
      FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
286 287
      FuchsiaSdk: () => FakeFuchsiaSdk(),
      ProcessManager: () => processManager,
288 289 290
    });

    testUsingContext('hostAddress parsing works', () async {
291 292 293 294 295
      processManager.addCommand(const FakeCommand(
        command: <String>['ssh', '-F', '/ssh_config', 'id', r'echo $SSH_CONNECTION'],
        stdout: 'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22',
      ));

296 297 298 299
      final FuchsiaDevice device = FuchsiaDevice('id');
      expect(await device.hostAddress, 'fe80::8c6c:2fff:fe3d:c5e1%25ethp0003');
    }, overrides: <Type, Generator>{
      FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
300 301
      FuchsiaSdk: () => FakeFuchsiaSdk(),
      ProcessManager: () => processManager,
302 303 304
    });

    testUsingContext('hostAddress parsing throws tool error on failure', () async {
305 306 307 308 309
      processManager.addCommand(const FakeCommand(
        command: <String>['ssh', '-F', '/ssh_config', 'id', r'echo $SSH_CONNECTION'],
        exitCode: 1,
      ));

310
      final FuchsiaDevice device = FuchsiaDevice('id');
311
      await expectLater(() => device.hostAddress, throwsToolExit());
312 313
    }, overrides: <Type, Generator>{
      FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
314 315
      FuchsiaSdk: () => FakeFuchsiaSdk(),
      ProcessManager: () => processManager,
316 317 318
    });

    testUsingContext('hostAddress parsing throws tool error on empty response', () async {
319 320 321 322
      processManager.addCommand(const FakeCommand(
        command: <String>['ssh', '-F', '/ssh_config', 'id', r'echo $SSH_CONNECTION'],
      ));

323
      final FuchsiaDevice device = FuchsiaDevice('id');
324
      expect(() async => device.hostAddress, throwsToolExit());
325 326
    }, overrides: <Type, Generator>{
      FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
327 328
      FuchsiaSdk: () => FakeFuchsiaSdk(),
      ProcessManager: () => processManager,
329
    });
330 331 332
  });

  group('displays friendly error when', () {
333
    File artifactFile;
334
    FakeProcessManager processManager;
335 336

    setUp(() {
337
      processManager = FakeProcessManager.empty();
338
      artifactFile = MemoryFileSystem.test().file('artifact');
339
    });
340 341

    testUsingContext('No vmservices found', () async {
342 343 344
      processManager.addCommand(const FakeCommand(
        command: <String>['ssh', '-F', '/artifact', 'id', 'find /hub -name vmservice-port'],
      ));
345
      final FuchsiaDevice device = FuchsiaDevice('id');
346 347

      await expectLater(device.servicePorts, throwsToolExit(message: 'No Dart Observatories found. Are you running a debug build?'));
348
    }, overrides: <Type, Generator>{
349
      ProcessManager: () => processManager,
350
      FuchsiaArtifacts: () => FuchsiaArtifacts(
351 352 353 354 355
        sshConfig: artifactFile,
        devFinder: artifactFile,
        ffx: artifactFile,
      ),
      FuchsiaSdk: () => FakeFuchsiaSdk(),
356
    });
357 358 359

    group('device logs', () {
      const String exampleUtcLogs = '''
360
[2018-11-09 01:27:45][3][297950920][log] INFO: example_app.cmx(flutter): Error doing thing
361 362
[2018-11-09 01:27:58][46257][46269][foo] INFO: Using a thing
[2018-11-09 01:29:58][46257][46269][foo] INFO: Blah blah blah
363
[2018-11-09 01:29:58][46257][46269][foo] INFO: other_app.cmx(flutter): Do thing
364
[2018-11-09 01:30:02][41175][41187][bar] INFO: Invoking a bar
365
[2018-11-09 01:30:12][52580][52983][log] INFO: example_app.cmx(flutter): Did thing this time
366

367
  ''';
368
      FakeProcessManager processManager;
369
      File devFinder;
370
      File ffx;
371
      File sshConfig;
372

373
      setUp(() {
374
       processManager = FakeProcessManager.empty();
375 376
        final FileSystem memoryFileSystem = MemoryFileSystem.test();
        devFinder = memoryFileSystem.file('device-finder')..writeAsStringSync('\n');
377
        ffx = memoryFileSystem.file('ffx')..writeAsStringSync('\n');
378
        sshConfig = memoryFileSystem.file('ssh_config')..writeAsStringSync('\n');
379 380 381
      });

      testUsingContext('can be parsed for an app', () async {
382 383 384 385 386 387
        final Completer<void> lock = Completer<void>();
        processManager.addCommand(FakeCommand(
          command: const <String>['ssh', '-F', '/ssh_config', 'id', 'log_listener --clock Local'],
          stdout: exampleUtcLogs,
          completer: lock,
        ));
388
        final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
389
        final DeviceLogReader reader = device.getLogReader(app: FuchsiaModulePackage(name: 'example_app'));
390
        final List<String> logLines = <String>[];
391 392 393 394 395 396
        reader.logLines.listen((String line) {
          logLines.add(line);
          if (logLines.length == 2) {
            lock.complete();
          }
        });
397 398
        expect(logLines, isEmpty);

399
        await lock.future;
400 401 402 403 404
        expect(logLines, <String>[
          '[2018-11-09 01:27:45.000] Flutter: Error doing thing',
          '[2018-11-09 01:30:12.000] Flutter: Did thing this time',
        ]);
      }, overrides: <Type, Generator>{
405
        ProcessManager: () => processManager,
406
        SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)),
407
        FuchsiaArtifacts: () =>
408
            FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig, ffx: ffx),
409 410 411
      });

      testUsingContext('cuts off prior logs', () async {
412 413 414 415 416 417
        final Completer<void> lock = Completer<void>();
        processManager.addCommand(FakeCommand(
          command: const <String>['ssh', '-F', '/ssh_config', 'id', 'log_listener --clock Local'],
          stdout: exampleUtcLogs,
          completer: lock,
        ));
418
        final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
419
        final DeviceLogReader reader = device.getLogReader(app: FuchsiaModulePackage(name: 'example_app'));
420 421 422 423 424 425 426
        final List<String> logLines = <String>[];
        reader.logLines.listen((String line) {
          logLines.add(line);
          lock.complete();
        });
        expect(logLines, isEmpty);

427
        await lock.future.timeout(const Duration(seconds: 1));
428 429 430 431 432

        expect(logLines, <String>[
          '[2018-11-09 01:30:12.000] Flutter: Did thing this time',
        ]);
      }, overrides: <Type, Generator>{
433
        ProcessManager: () => processManager,
434
        SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 29, 45)),
435
        FuchsiaArtifacts: () => FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig, ffx: ffx),
436 437 438
      });

      testUsingContext('can be parsed for all apps', () async {
439 440 441 442 443 444
        final Completer<void> lock = Completer<void>();
        processManager.addCommand(FakeCommand(
          command: const <String>['ssh', '-F', '/ssh_config', 'id', 'log_listener --clock Local'],
          stdout: exampleUtcLogs,
          completer: lock,
        ));
445 446 447
        final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
        final DeviceLogReader reader = device.getLogReader();
        final List<String> logLines = <String>[];
448 449 450 451 452 453
        reader.logLines.listen((String line) {
          logLines.add(line);
          if (logLines.length == 3) {
            lock.complete();
          }
        });
454 455
        expect(logLines, isEmpty);

456
        await lock.future.timeout(const Duration(seconds: 1));
457 458 459 460 461 462 463

        expect(logLines, <String>[
          '[2018-11-09 01:27:45.000] Flutter: Error doing thing',
          '[2018-11-09 01:29:58.000] Flutter: Do thing',
          '[2018-11-09 01:30:12.000] Flutter: Did thing this time',
        ]);
      }, overrides: <Type, Generator>{
464
        ProcessManager: () => processManager,
465
        SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)),
466
        FuchsiaArtifacts: () => FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig, ffx: ffx),
467 468
      });
    });
469
  });
470

Dan Field's avatar
Dan Field committed
471
  group('screenshot', () {
472 473 474 475 476 477
    FakeProcessManager processManager;

    setUp(() {
      processManager = FakeProcessManager.empty();
    });

478
    testUsingContext('is supported on posix platforms', () {
479 480
      final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
      expect(device.supportsScreenshot, true);
481 482 483 484 485
    }, overrides: <Type, Generator>{
      Platform: () => FakePlatform(
        operatingSystem: 'linux',
      ),
    });
486 487 488

    testUsingContext('is not supported on Windows', () {
      final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
489

490 491 492 493 494 495 496
      expect(device.supportsScreenshot, false);
    }, overrides: <Type, Generator>{
      Platform: () => FakePlatform(
        operatingSystem: 'windows',
      ),
    });

497
    test("takeScreenshot throws if file isn't .ppm", () async {
498 499 500 501 502
      final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
      await expectLater(
        () => device.takeScreenshot(globals.fs.file('file.invalid')),
        throwsA(equals('file.invalid must be a .ppm file')),
      );
503
    });
504 505

    testUsingContext('takeScreenshot throws if screencap failed', () async {
506 507
      processManager.addCommand(const FakeCommand(
        command: <String>[
508 509 510 511 512 513
          'ssh',
          '-F',
          '/fuchsia/out/default/.ssh',
          '0.0.0.0',
          'screencap > /tmp/screenshot.ppm',
        ],
514 515 516 517
        exitCode: 1,
        stderr: '<error-message>'
      ));
      final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
518 519 520 521 522 523

      await expectLater(
        () => device.takeScreenshot(globals.fs.file('file.ppm')),
        throwsA(equals('Could not take a screenshot on device tester:\n<error-message>')),
      );
    }, overrides: <Type, Generator>{
524
      ProcessManager: () => processManager,
525
      FileSystem: () => MemoryFileSystem.test(),
526 527 528 529 530 531
      Platform: () => FakePlatform(
        environment: <String, String>{
          'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
        },
        operatingSystem: 'linux',
      ),
532
    });
533 534 535

    testUsingContext('takeScreenshot throws if scp failed', () async {
      final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
536 537
      processManager.addCommand(const FakeCommand(
        command: <String>[
538 539 540 541 542 543
          'ssh',
          '-F',
          '/fuchsia/out/default/.ssh',
          '0.0.0.0',
          'screencap > /tmp/screenshot.ppm',
        ],
544 545 546
      ));
      processManager.addCommand(const FakeCommand(
        command: <String>[
547 548 549 550 551 552
          'scp',
          '-F',
          '/fuchsia/out/default/.ssh',
          '0.0.0.0:/tmp/screenshot.ppm',
          'file.ppm',
        ],
553 554 555 556 557
        exitCode: 1,
        stderr: '<error-message>',
      ));
      processManager.addCommand(const FakeCommand(
        command: <String>[
558 559 560 561 562 563
          'ssh',
          '-F',
          '/fuchsia/out/default/.ssh',
          '0.0.0.0',
          'rm /tmp/screenshot.ppm',
        ],
564
      ));
565 566 567 568 569 570

      await expectLater(
        () => device.takeScreenshot(globals.fs.file('file.ppm')),
        throwsA(equals('Failed to copy screenshot from device:\n<error-message>')),
      );
    }, overrides: <Type, Generator>{
571
      ProcessManager: () => processManager,
572
      FileSystem: () => MemoryFileSystem.test(),
573 574 575 576 577 578
      Platform: () => FakePlatform(
        environment: <String, String>{
          'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
        },
        operatingSystem: 'linux',
      ),
579
    });
580

581
    testUsingContext("takeScreenshot prints error if can't delete file from device", () async {
582
      final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
583 584
      processManager.addCommand(const FakeCommand(
        command: <String>[
585 586 587 588 589 590
          'ssh',
          '-F',
          '/fuchsia/out/default/.ssh',
          '0.0.0.0',
          'screencap > /tmp/screenshot.ppm',
        ],
591 592 593
      ));
      processManager.addCommand(const FakeCommand(
        command: <String>[
594 595 596 597 598 599
          'scp',
          '-F',
          '/fuchsia/out/default/.ssh',
          '0.0.0.0:/tmp/screenshot.ppm',
          'file.ppm',
        ],
600 601 602
      ));
      processManager.addCommand(const FakeCommand(
        command: <String>[
603 604 605 606 607 608
          'ssh',
          '-F',
          '/fuchsia/out/default/.ssh',
          '0.0.0.0',
          'rm /tmp/screenshot.ppm',
        ],
609 610 611 612 613
        exitCode: 1,
        stderr: '<error-message>',
      ));

      await device.takeScreenshot(globals.fs.file('file.ppm'));
614 615 616 617 618
      expect(
        testLogger.errorText,
        contains('Failed to delete screenshot.ppm from the device:\n<error-message>'),
      );
    }, overrides: <Type, Generator>{
619
      ProcessManager: () => processManager,
620
      FileSystem: () => MemoryFileSystem.test(),
621 622 623 624 625 626 627 628 629 630
      Platform: () => FakePlatform(
        environment: <String, String>{
          'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
        },
        operatingSystem: 'linux',
      ),
    }, testOn: 'posix');

    testUsingContext('takeScreenshot returns', () async {
      final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
631 632
      processManager.addCommand(const FakeCommand(
        command: <String>[
633 634 635 636 637 638
          'ssh',
          '-F',
          '/fuchsia/out/default/.ssh',
          '0.0.0.0',
          'screencap > /tmp/screenshot.ppm',
        ],
639 640 641
      ));
      processManager.addCommand(const FakeCommand(
        command: <String>[
642 643 644 645 646 647
          'scp',
          '-F',
          '/fuchsia/out/default/.ssh',
          '0.0.0.0:/tmp/screenshot.ppm',
          'file.ppm',
        ],
648 649 650
      ));
      processManager.addCommand(const FakeCommand(
        command: <String>[
651 652 653 654 655 656
          'ssh',
          '-F',
          '/fuchsia/out/default/.ssh',
          '0.0.0.0',
          'rm /tmp/screenshot.ppm',
        ],
657
      ));
658

659
      expect(() => device.takeScreenshot(globals.fs.file('file.ppm')), returnsNormally);
660
    }, overrides: <Type, Generator>{
661
      ProcessManager: () => processManager,
662
      FileSystem: () => MemoryFileSystem.test(),
663 664 665 666 667 668
      Platform: () => FakePlatform(
        environment: <String, String>{
          'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
        },
        operatingSystem: 'linux',
      ),
669
    });
670 671
  });

672
  group('portForwarder', () {
673
    FakeProcessManager processManager;
674
    File sshConfig;
675 676

    setUp(() {
677
      processManager = FakeProcessManager.empty();
678
      sshConfig = MemoryFileSystem.test().file('irrelevant')..writeAsStringSync('\n');
679 680 681 682
    });

    testUsingContext('`unforward` prints stdout and stderr if ssh command failed', () async {
      final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
683 684 685 686 687 688
      processManager.addCommand(const FakeCommand(
        command: <String>['ssh', '-F', '/irrelevant', '-O', 'cancel', '-vvv', '-L', '0:127.0.0.1:1', 'id'],
        exitCode: 1,
        stdout: '<stdout>',
        stderr: '<stderr>',
      ));
689 690 691 692 693 694

      await expectLater(
        () => device.portForwarder.unforward(ForwardedPort(/*hostPort=*/ 0, /*devicePort=*/ 1)),
        throwsToolExit(message: 'Unforward command failed:\nstdout: <stdout>\nstderr: <stderr>'),
      );
    }, overrides: <Type, Generator>{
695
      ProcessManager: () => processManager,
696 697 698 699
      FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
    });
  });

Dan Field's avatar
Dan Field committed
700

701 702
  group('FuchsiaIsolateDiscoveryProtocol', () {
    Future<Uri> findUri(List<FlutterView> views, String expectedIsolateName) async {
703 704 705 706 707 708 709 710 711 712 713 714
      final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
        requests: <VmServiceExpectation>[
          FakeVmServiceRequest(
            method: kListViewsMethod,
            jsonResponse: <String, Object>{
              'views': <Object>[
                for (FlutterView view in views)
                  view.toJson()
              ],
            },
          ),
        ],
715
        httpAddress: Uri.parse('example'),
716
      );
717
      final MockFuchsiaDevice fuchsiaDevice = MockFuchsiaDevice('123', const NoOpDevicePortForwarder(), false);
718
      final FuchsiaIsolateDiscoveryProtocol discoveryProtocol =
719
        FuchsiaIsolateDiscoveryProtocol(
720 721
        fuchsiaDevice,
        expectedIsolateName,
722
        (Uri uri) async => fakeVmServiceHost.vmService,
723
        (Device device, Uri uri, bool enableServiceAuthCodes) => null,
724
        true, // only poll once.
725
      );
726
      return discoveryProtocol.uri;
727
    }
728

729
    testUsingContext('can find flutter view with matching isolate name', () async {
730
      const String expectedIsolateName = 'foobar';
731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749
      final Uri uri = await findUri(<FlutterView>[
        // no ui isolate.
        FlutterView(id: '1', uiIsolate: null),
        // wrong name.
        FlutterView(
          id: '2',
          uiIsolate: vm_service.Isolate.parse(<String, dynamic>{
            ...fakeIsolate.toJson(),
            'name': 'Wrong name',
          }),
        ),
        // matching name.
        FlutterView(
          id: '3',
          uiIsolate: vm_service.Isolate.parse(<String, dynamic>{
             ...fakeIsolate.toJson(),
            'name': expectedIsolateName,
          }),
        ),
750
      ], expectedIsolateName);
751

752
      expect(uri.toString(), 'http://${InternetAddress.loopbackIPv4.address}:0/');
753 754
    });

755
    testUsingContext('can handle flutter view without matching isolate name', () async {
756
      const String expectedIsolateName = 'foobar';
757 758 759 760 761 762 763 764
      final Future<Uri> uri = findUri(<FlutterView>[
        // no ui isolate.
        FlutterView(id: '1', uiIsolate: null),
        // wrong name.
        FlutterView(id: '2', uiIsolate: vm_service.Isolate.parse(<String, Object>{
           ...fakeIsolate.toJson(),
          'name': 'wrong name',
        })),
765
      ], expectedIsolateName);
766

767 768 769 770 771
      expect(uri, throwsException);
    });

    testUsingContext('can handle non flutter view', () async {
      const String expectedIsolateName = 'foobar';
772 773
      final Future<Uri> uri = findUri(<FlutterView>[
        FlutterView(id: '1', uiIsolate: null), // no ui isolate.
774
      ], expectedIsolateName);
775

776 777 778
      expect(uri, throwsException);
    });
  });
779

780
  testUsingContext('Correct flutter runner', () async {
781 782 783
    final Cache cache = Cache.test(
      processManager: FakeProcessManager.any(),
    );
784 785 786 787 788
    final FileSystem fileSystem = MemoryFileSystem.test();
    final CachedArtifacts artifacts = CachedArtifacts(
      cache: cache,
      fileSystem: fileSystem,
      platform: FakePlatform(operatingSystem: 'linux'),
789
      operatingSystemUtils: globals.os,
790 791
    );
    expect(artifacts.getArtifactPath(
792 793 794 795 796 797
        Artifact.fuchsiaFlutterRunner,
        platform: TargetPlatform.fuchsia_x64,
        mode: BuildMode.debug,
      ),
      contains('flutter_jit_runner'),
    );
798
    expect(artifacts.getArtifactPath(
799 800 801 802 803 804
        Artifact.fuchsiaFlutterRunner,
        platform: TargetPlatform.fuchsia_x64,
        mode: BuildMode.profile,
      ),
      contains('flutter_aot_runner'),
    );
805
    expect(artifacts.getArtifactPath(
806 807 808 809 810 811
        Artifact.fuchsiaFlutterRunner,
        platform: TargetPlatform.fuchsia_x64,
        mode: BuildMode.release,
      ),
      contains('flutter_aot_product_runner'),
    );
812
    expect(artifacts.getArtifactPath(
813 814 815 816 817 818 819 820
        Artifact.fuchsiaFlutterRunner,
        platform: TargetPlatform.fuchsia_x64,
        mode: BuildMode.jitRelease,
      ),
      contains('flutter_jit_product_runner'),
    );
  });

821
  group('sdkNameAndVersion: ', () {
822
    File sshConfig;
823
    FakeProcessManager processManager;
824 825

    setUp(() {
826
      sshConfig = MemoryFileSystem.test().file('ssh_config')..writeAsStringSync('\n');
827
      processManager = FakeProcessManager.empty();
828 829
    });

830
    testUsingContext('does not throw on non-existent ssh config', () async {
831
      final FuchsiaDevice device = FuchsiaDevice('123');
832

833 834
      expect(await device.sdkNameAndVersion, equals('Fuchsia'));
    }, overrides: <Type, Generator>{
835
      ProcessManager: () => processManager,
836
      FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: null),
837
      FuchsiaSdk: () => FakeFuchsiaSdk(),
838 839
    });

840
    testUsingContext('returns what we get from the device on success', () async {
841 842 843 844
      processManager.addCommand(const FakeCommand(
        command: <String>['ssh', '-F', '/ssh_config', '123', 'cat /pkgfs/packages/build-info/0/data/version'],
        stdout: 'version'
      ));
845
      final FuchsiaDevice device = FuchsiaDevice('123');
846

847 848
      expect(await device.sdkNameAndVersion, equals('Fuchsia version'));
    }, overrides: <Type, Generator>{
849
      ProcessManager: () => processManager,
850
      FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
851
      FuchsiaSdk: () => FakeFuchsiaSdk(),
852 853 854
    });

    testUsingContext('returns "Fuchsia" when device command fails', () async {
855 856 857 858
      processManager.addCommand(const FakeCommand(
        command: <String>['ssh', '-F', '/ssh_config', '123', 'cat /pkgfs/packages/build-info/0/data/version'],
        exitCode: 1,
      ));
859
      final FuchsiaDevice device = FuchsiaDevice('123');
860

861 862
      expect(await device.sdkNameAndVersion, equals('Fuchsia'));
    }, overrides: <Type, Generator>{
863
      ProcessManager: () => processManager,
864
      FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
865
      FuchsiaSdk: () => FakeFuchsiaSdk(),
866 867 868
    });

    testUsingContext('returns "Fuchsia" when device gives an empty result', () async {
869 870 871
      processManager.addCommand(const FakeCommand(
        command: <String>['ssh', '-F', '/ssh_config', '123', 'cat /pkgfs/packages/build-info/0/data/version'],
      ));
872
      final FuchsiaDevice device = FuchsiaDevice('123');
873

874 875
      expect(await device.sdkNameAndVersion, equals('Fuchsia'));
    }, overrides: <Type, Generator>{
876
      ProcessManager: () => processManager,
877
      FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
878
      FuchsiaSdk: () => FakeFuchsiaSdk(),
879 880
    });
  });
881 882 883 884 885 886 887
}

class FuchsiaModulePackage extends ApplicationPackage {
  FuchsiaModulePackage({@required this.name}) : super(id: name);

  @override
  final String name;
888
}
889

890
class MockFuchsiaDevice extends Fake implements FuchsiaDevice {
891 892 893
  MockFuchsiaDevice(this.id, this.portForwarder, this._ipv6);

  final bool _ipv6;
894 895

  @override
896
  bool get ipv6 => _ipv6;
897

898 899
  @override
  final String id;
900

901 902 903 904
  @override
  final DevicePortForwarder portForwarder;

  @override
905 906 907
  Future<TargetPlatform> get targetPlatform async => TargetPlatform.fuchsia_arm64;

  @override
908
  String get name => 'fuchsia';
909 910

  @override
911
  Future<List<int>> servicePorts() async => <int>[1];
912 913

  @override
914
  DartDevelopmentService get dds => FakeDartDevelopmentService();
915 916
}

917 918
class FakePortForwarder extends Fake implements DevicePortForwarder {
  bool disposed = false;
919 920

  @override
921 922
  Future<void> dispose() async {
    disposed = true;
923
  }
924 925
}

926 927
class FuchsiaDeviceWithFakeDiscovery extends FuchsiaDevice {
  FuchsiaDeviceWithFakeDiscovery(String id, {String name}) : super(id, name: name);
928 929

  @override
930 931
  FuchsiaIsolateDiscoveryProtocol getIsolateDiscoveryProtocol(String isolateName) {
    return FakeFuchsiaIsolateDiscoveryProtocol();
932 933 934
  }

  @override
935
  Future<TargetPlatform> get targetPlatform async => TargetPlatform.fuchsia_arm64;
936 937
}

938
class FakeFuchsiaIsolateDiscoveryProtocol implements FuchsiaIsolateDiscoveryProtocol {
939
  @override
940
  FutureOr<Uri> get uri => Uri.parse('http://[::1]:37');
941 942

  @override
943
  void dispose() {}
944 945
}

946
class FakeFuchsiaDevFinder implements FuchsiaDevFinder {
947
  @override
948 949
  Future<List<String>> list({ Duration timeout }) async {
    return <String>['192.168.11.999 scare-cable-device-finder'];
950 951 952
  }

  @override
953 954
  Future<String> resolve(String deviceName) async {
    return '192.168.11.999';
955 956 957
  }
}

958
class FakeFuchsiaFfx implements FuchsiaFfx {
959
  @override
960 961
  Future<List<String>> list({Duration timeout}) async {
    return <String>['192.168.42.172 scare-cable-skip-ffx'];
962 963 964
  }

  @override
965 966
  Future<String> resolve(String deviceName) async {
    return '192.168.42.10';
967 968 969
  }
}

970 971 972 973 974 975 976 977 978 979 980 981 982 983
class FakeFuchsiaSdk extends Fake implements FuchsiaSdk {
  FakeFuchsiaSdk({
    FuchsiaPM pm,
    FuchsiaKernelCompiler compiler,
    FuchsiaDevFinder devFinder,
    FuchsiaFfx ffx,
    String devices,
    String devFinderDevices,
  }) : fuchsiaPM = pm,
       fuchsiaKernelCompiler = compiler,
       fuchsiaDevFinder = devFinder ?? FakeFuchsiaDevFinder(),
       fuchsiaFfx = ffx ?? FakeFuchsiaFfx(),
       _devices = devices,
       _devFinderDevices = devFinderDevices;
984 985

  @override
986
  final FuchsiaPM fuchsiaPM;
987 988

  @override
989
  final FuchsiaKernelCompiler fuchsiaKernelCompiler;
990 991

  @override
992
  final FuchsiaDevFinder fuchsiaDevFinder;
993 994

  @override
995
  final FuchsiaFfx fuchsiaFfx;
996

997 998
  final String _devices;
  final String _devFinderDevices;
999

1000
  @override
1001 1002 1003 1004 1005
  Future<String> listDevices({Duration timeout, bool useDeviceFinder = false}) async {
    if (useDeviceFinder) {
      return _devFinderDevices;
    }
    return _devices;
1006 1007 1008
  }
}

1009
class FakeDartDevelopmentService extends Fake implements DartDevelopmentService {
1010
  @override
1011 1012 1013 1014 1015 1016 1017
  Future<void> startDartDevelopmentService(
    Uri observatoryUri, {
    @required Logger logger,
    int hostPort,
    bool ipv6,
    bool disableServiceAuthCodes,
  }) async {}
1018 1019

  @override
1020
  Uri get uri => Uri.parse('example');
1021 1022
}

1023 1024 1025 1026 1027 1028 1029 1030
class FakeFuchsiaWorkflow implements FuchsiaWorkflow {
  FakeFuchsiaWorkflow({
    this.appliesToHostPlatform = true,
    this.canLaunchDevices = true,
    this.canListDevices = true,
    this.canListEmulators = true,
    this.shouldUseDeviceFinder = true,
  });
1031 1032

  @override
1033
  bool appliesToHostPlatform;
1034

1035
  @override
1036
  bool canLaunchDevices;
1037 1038

  @override
1039
  bool canListDevices;
1040 1041

  @override
1042
  bool canListEmulators;
1043 1044

  @override
1045
  bool shouldUseDeviceFinder;
1046
}