flutter_tester.dart 7.15 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// Copyright 2018 The Chromium 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 'dart:convert';

import 'package:meta/meta.dart';

import '../application_package.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/process_manager.dart';
import '../build_info.dart';
17
import '../bundle.dart' as bundle;
18 19 20 21 22 23 24 25
import '../dart/package_map.dart';
import '../device.dart';
import '../globals.dart';
import '../protocol_discovery.dart';
import '../version.dart';

class FlutterTesterApp extends ApplicationPackage {
  factory FlutterTesterApp.fromCurrentDirectory() {
26
    return FlutterTesterApp._(fs.currentDirectory);
27 28
  }

29
  FlutterTesterApp._(Directory directory)
30
      : _directory = directory,
31
        super(id: directory.path);
32

33 34
  final Directory _directory;

35
  @override
36
  String get name => _directory.basename;
37 38

  @override
39
  File get packagesFile => _directory.childFile('.packages');
40 41 42 43
}

// TODO(scheglov): This device does not currently work with full restarts.
class FlutterTesterDevice extends Device {
44
  FlutterTesterDevice(String deviceId) : super(deviceId);
45 46

  Process _process;
47
  final DevicePortForwarder _portForwarder = _NoopPortForwarder();
48 49 50 51 52 53 54 55

  @override
  Future<bool> get isLocalEmulator async => false;

  @override
  String get name => 'Flutter test device';

  @override
56
  DevicePortForwarder get portForwarder => _portForwarder;
57 58 59 60 61 62 63 64 65 66 67 68 69

  @override
  Future<String> get sdkNameAndVersion async {
    final FlutterVersion flutterVersion = FlutterVersion.instance;
    return 'Flutter ${flutterVersion.frameworkRevisionShort}';
  }

  @override
  Future<TargetPlatform> get targetPlatform async => TargetPlatform.tester;

  @override
  void clearLogs() {}

70
  final _FlutterTesterDeviceLogReader _logReader =
71
      _FlutterTesterDeviceLogReader();
72

73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
  @override
  DeviceLogReader getLogReader({ApplicationPackage app}) => _logReader;

  @override
  Future<bool> installApp(ApplicationPackage app) async => true;

  @override
  Future<bool> isAppInstalled(ApplicationPackage app) async => false;

  @override
  Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;

  @override
  bool isSupported() => true;

88 89 90
  bool _isRunning = false;
  bool get isRunning => _isRunning;

91 92 93 94 95 96 97
  @override
  Future<LaunchResult> startApp(
    ApplicationPackage package, {
    @required String mainPath,
    String route,
    @required DebuggingOptions debuggingOptions,
    Map<String, dynamic> platformArgs,
98 99 100 101
    bool prebuiltApplication = false,
    bool applicationNeedsRebuild = false,
    bool usesTerminalUi = true,
    bool ipv6 = false,
102
  }) async {
103 104 105
    final BuildInfo buildInfo = debuggingOptions.buildInfo;

    if (!buildInfo.isDebug) {
106
      printError('This device only supports debug mode.');
107
      return LaunchResult.failed();
108 109 110 111 112 113 114 115
    }

    final String shellPath = artifacts.getArtifactPath(Artifact.flutterTester);
    if (!fs.isFileSync(shellPath))
      throwToolExit('Cannot find Flutter shell at $shellPath');

    final List<String> command = <String>[
      shellPath,
116
      '--run-forever',
117 118 119 120 121
      '--non-interactive',
      '--enable-dart-profiling',
      '--packages=${PackageMap.globalPackagesPath}',
    ];
    if (debuggingOptions.debuggingEnabled) {
122
      if (debuggingOptions.startPaused)
123 124
        command.add('--start-paused');
      if (debuggingOptions.hasObservatoryPort)
125
        command.add('--observatory-port=${debuggingOptions.observatoryPort}');
126 127
    }

128 129
    // Build assets and perform initial compilation.
    final String assetDirPath = getAssetBuildDirectory();
130 131 132 133
    final String applicationKernelFilePath = bundle.getKernelPathForTransformerOptions(
      fs.path.join(getBuildDirectory(), 'flutter-tester-app.dill'),
      trackWidgetCreation: buildInfo.trackWidgetCreation,
    );
134 135 136 137
    await bundle.build(
      mainPath: mainPath,
      assetDirPath: assetDirPath,
      applicationKernelFilePath: applicationKernelFilePath,
138
      precompiledSnapshot: false,
139 140 141
      trackWidgetCreation: buildInfo.trackWidgetCreation,
    );
    command.add('--flutter-assets-dir=$assetDirPath');
142

143
    command.add(applicationKernelFilePath);
144 145

    try {
146
      printTrace(command.join(' '));
147

148
      _isRunning = true;
149
      _process = await processManager.start(command,
150 151 152 153
        environment: <String, String>{
          'FLUTTER_TEST': 'true',
        },
      );
154
      // Setting a bool can't fail in the callback.
155
      _process.exitCode.then<void>((_) => _isRunning = false); // ignore: unawaited_futures
156
      _process.stdout
157 158
          .transform<String>(utf8.decoder)
          .transform<String>(const LineSplitter())
159 160 161 162
          .listen((String line) {
        _logReader.addLine(line);
      });
      _process.stderr
163 164
          .transform<String>(utf8.decoder)
          .transform<String>(const LineSplitter())
165 166 167 168 169
          .listen((String line) {
        _logReader.addLine(line);
      });

      if (!debuggingOptions.debuggingEnabled)
170
        return LaunchResult.succeeded();
171

172
      final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory(
173 174 175
        getLogReader(),
        hostPort: debuggingOptions.observatoryPort,
      );
176 177

      final Uri observatoryUri = await observatoryDiscovery.uri;
178
      return LaunchResult.succeeded(observatoryUri: observatoryUri);
179 180
    } catch (error) {
      printError('Failed to launch $package: $error');
181
      return LaunchResult.failed();
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
    }
  }

  @override
  Future<bool> stopApp(ApplicationPackage app) async {
    _process?.kill();
    _process = null;
    return true;
  }

  @override
  Future<bool> uninstallApp(ApplicationPackage app) async => true;
}

class FlutterTesterDevices extends PollingDeviceDiscovery {
197 198
  FlutterTesterDevices() : super('Flutter tester');

199 200 201 202 203
  static const String kTesterDeviceId = 'flutter-tester';

  static bool showFlutterTesterDevice = false;

  final FlutterTesterDevice _testerDevice =
204
      FlutterTesterDevice(kTesterDeviceId);
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219

  @override
  bool get canListAnything => true;

  @override
  bool get supportsPlatform => true;

  @override
  Future<List<Device>> pollingGetDevices() async {
    return showFlutterTesterDevice ? <Device>[_testerDevice] : <Device>[];
  }
}

class _FlutterTesterDeviceLogReader extends DeviceLogReader {
  final StreamController<String> _logLinesController =
220
      StreamController<String>.broadcast();
221 222 223 224 225 226 227 228 229 230 231 232

  @override
  int get appPid => 0;

  @override
  Stream<String> get logLines => _logLinesController.stream;

  @override
  String get name => 'flutter tester log reader';

  void addLine(String line) => _logLinesController.add(line);
}
233 234 235 236 237 238 239 240

/// A fake port forwarder that doesn't do anything. Used by flutter tester
/// where the VM is running on the same machine and does not need ports forwarding.
class _NoopPortForwarder extends DevicePortForwarder {
  @override
  Future<int> forward(int devicePort, {int hostPort}) {
    if (hostPort != null && hostPort != devicePort)
      throw 'Forwarding to a different port is not supported by flutter tester';
241
    return Future<int>.value(devicePort);
242 243 244 245 246 247
  }

  @override
  List<ForwardedPort> get forwardedPorts => <ForwardedPort>[];

  @override
248
  Future<void> unforward(ForwardedPort forwardedPort) async { }
249
}