integration_test_device.dart 4.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'package:stream_channel/stream_channel.dart';
import 'package:vm_service/vm_service.dart' as vm_service;

import '../application_package.dart';
import '../build_info.dart';
import '../device.dart';
13
import '../globals.dart' as globals;
14 15 16 17 18 19 20 21 22
import '../vmservice.dart';
import 'test_device.dart';

const String kIntegrationTestExtension = 'Flutter.IntegrationTest';
const String kIntegrationTestData = 'data';
const String kIntegrationTestMethod = 'ext.flutter.integrationTest';

class IntegrationTestTestDevice implements TestDevice {
  IntegrationTestTestDevice({
23 24 25 26
    required this.id,
    required this.device,
    required this.debuggingOptions,
    required this.userIdentifier,
27
    required this.compileExpression,
28 29 30 31 32
  });

  final int id;
  final Device device;
  final DebuggingOptions debuggingOptions;
33
  final String? userIdentifier;
34
  final CompileExpression? compileExpression;
35

36
  ApplicationPackage? _applicationPackage;
37
  final Completer<void> _finished = Completer<void>();
38
  final Completer<Uri> _gotProcessVmServiceUri = Completer<Uri>();
39 40 41

  /// Starts the device.
  ///
42
  /// [entrypointPath] must be a path to an un-compiled source file.
43 44 45
  @override
  Future<StreamChannel<String>> start(String entrypointPath) async {
    final TargetPlatform targetPlatform = await device.targetPlatform;
46
    _applicationPackage = await ApplicationPackageFactory.instance?.getPackageForPlatform(
47 48 49
      targetPlatform,
      buildInfo: debuggingOptions.buildInfo,
    );
50 51
    final ApplicationPackage? package = _applicationPackage;
    if (package == null) {
52 53
      throw TestDeviceException('No application found for $targetPlatform.', StackTrace.current);
    }
54 55

    final LaunchResult launchResult = await device.startApp(
56
      package,
57 58 59 60 61 62 63 64
      mainPath: entrypointPath,
      platformArgs: <String, dynamic>{},
      debuggingOptions: debuggingOptions,
      userIdentifier: userIdentifier,
    );
    if (!launchResult.started) {
      throw TestDeviceException('Unable to start the app on the device.', StackTrace.current);
    }
65 66 67
    final Uri? vmServiceUri = launchResult.vmServiceUri;
    if (vmServiceUri == null) {
      throw TestDeviceException('The VM Service is not available on the test device.', StackTrace.current);
68 69 70 71 72
    }

    // No need to set up the log reader because the logs are captured and
    // streamed to the package:test_core runner.

73
    _gotProcessVmServiceUri.complete(vmServiceUri);
74 75

    globals.printTrace('test $id: Connecting to vm service');
76
    final FlutterVmService vmService = await connectToVmService(
77
      vmServiceUri,
78 79 80
      logger: globals.logger,
      compileExpression: compileExpression,
    ).timeout(
81 82 83 84 85
      const Duration(seconds: 5),
      onTimeout: () => throw TimeoutException('Connecting to the VM Service timed out.'),
    );

    globals.printTrace('test $id: Finding the correct isolate with the integration test service extension');
86 87 88
    final vm_service.IsolateRef isolateRef = await vmService.findExtensionIsolate(
      kIntegrationTestMethod,
    );
89 90 91 92

    await vmService.service.streamListen(vm_service.EventStreams.kExtension);
    final Stream<String> remoteMessages = vmService.service.onExtensionEvent
        .where((vm_service.Event e) => e.extensionKind == kIntegrationTestExtension)
93
        .map((vm_service.Event e) => e.extensionData!.data[kIntegrationTestData] as String);
94 95 96 97 98 99 100 101 102 103 104 105 106

    final StreamChannelController<String> controller = StreamChannelController<String>();

    controller.local.stream.listen((String event) {
      vmService.service.callServiceExtension(
        kIntegrationTestMethod,
        isolateId: isolateRef.id,
        args: <String, String>{
          kIntegrationTestData: event,
        },
      );
    });

107 108 109 110 111 112 113 114
    remoteMessages.listen(
      (String s) => controller.local.sink.add(s),
      onError: (Object error, StackTrace stack) => controller.local.sink.addError(error, stack),
    );
    unawaited(vmService.service.onDone.whenComplete(
      () => controller.local.sink.close(),
    ));

115 116 117 118
    return controller.foreign;
  }

  @override
119
  Future<Uri> get vmServiceUri => _gotProcessVmServiceUri.future;
120 121 122

  @override
  Future<void> kill() async {
123 124 125 126 127 128 129 130
    final ApplicationPackage? applicationPackage = _applicationPackage;
    if (applicationPackage != null) {
      if (!await device.stopApp(applicationPackage, userIdentifier: userIdentifier)) {
        globals.printTrace('Could not stop the Integration Test app.');
      }
      if (!await device.uninstallApp(applicationPackage, userIdentifier: userIdentifier)) {
        globals.printTrace('Could not uninstall the Integration Test app.');
      }
131 132 133 134 135 136 137 138 139
    }

    await device.dispose();
    _finished.complete();
  }

  @override
  Future<void> get finished => _finished.future;
}