flutter_tester.dart 7.66 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 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 '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';
16
import '../bundle.dart';
17
import '../convert.dart';
18 19 20
import '../dart/package_map.dart';
import '../device.dart';
import '../globals.dart';
21
import '../project.dart';
22 23 24 25 26
import '../protocol_discovery.dart';
import '../version.dart';

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

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

34 35
  final Directory _directory;

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

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

// TODO(scheglov): This device does not currently work with full restarts.
class FlutterTesterDevice extends Device {
45 46 47 48 49 50
  FlutterTesterDevice(String deviceId) : super(
      deviceId,
      platformType: null,
      category: null,
      ephemeral: false,
  );
51 52

  Process _process;
53
  final DevicePortForwarder _portForwarder = _NoopPortForwarder();
54 55 56 57

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

58 59 60
  @override
  Future<String> get emulatorId async => null;

61 62 63 64
  @override
  String get name => 'Flutter test device';

  @override
65
  DevicePortForwarder get portForwarder => _portForwarder;
66 67 68 69 70 71 72 73 74 75 76

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

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

  @override
77
  void clearLogs() { }
78

79
  final _FlutterTesterDeviceLogReader _logReader =
80
      _FlutterTesterDeviceLogReader();
81

82
  @override
83
  DeviceLogReader getLogReader({ ApplicationPackage app }) => _logReader;
84 85 86 87 88 89 90 91 92 93 94 95 96

  @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;

97 98 99
  bool _isRunning = false;
  bool get isRunning => _isRunning;

100 101 102 103 104 105 106
  @override
  Future<LaunchResult> startApp(
    ApplicationPackage package, {
    @required String mainPath,
    String route,
    @required DebuggingOptions debuggingOptions,
    Map<String, dynamic> platformArgs,
107 108
    bool prebuiltApplication = false,
    bool ipv6 = false,
109
  }) async {
110 111 112
    final BuildInfo buildInfo = debuggingOptions.buildInfo;

    if (!buildInfo.isDebug) {
113
      printError('This device only supports debug mode.');
114
      return LaunchResult.failed();
115 116 117
    }

    final String shellPath = artifacts.getArtifactPath(Artifact.flutterTester);
118
    if (!fs.isFileSync(shellPath)) {
119
      throwToolExit('Cannot find Flutter shell at $shellPath');
120
    }
121 122 123

    final List<String> command = <String>[
      shellPath,
124
      '--run-forever',
125 126 127 128 129
      '--non-interactive',
      '--enable-dart-profiling',
      '--packages=${PackageMap.globalPackagesPath}',
    ];
    if (debuggingOptions.debuggingEnabled) {
130
      if (debuggingOptions.startPaused) {
131
        command.add('--start-paused');
132 133
      }
      if (debuggingOptions.disableServiceAuthCodes) {
134
        command.add('--disable-service-auth-codes');
135 136
      }
      if (debuggingOptions.hasObservatoryPort) {
137
        command.add('--observatory-port=${debuggingOptions.hostVmServicePort}');
138
      }
139 140
    }

141 142
    // Build assets and perform initial compilation.
    final String assetDirPath = getAssetBuildDirectory();
143
    final String applicationKernelFilePath = getKernelPathForTransformerOptions(
144 145 146
      fs.path.join(getBuildDirectory(), 'flutter-tester-app.dill'),
      trackWidgetCreation: buildInfo.trackWidgetCreation,
    );
147
    await BundleBuilder().build(
148
      buildMode: buildInfo.mode,
149 150 151
      mainPath: mainPath,
      assetDirPath: assetDirPath,
      applicationKernelFilePath: applicationKernelFilePath,
152
      precompiledSnapshot: false,
153
      trackWidgetCreation: buildInfo.trackWidgetCreation,
154
      platform: getTargetPlatformForName(getNameForHostPlatform(getCurrentHostPlatform())),
155 156
    );
    command.add('--flutter-assets-dir=$assetDirPath');
157

158
    command.add(applicationKernelFilePath);
159 160

    try {
161
      printTrace(command.join(' '));
162

163
      _isRunning = true;
164
      _process = await processManager.start(command,
165 166 167 168
        environment: <String, String>{
          'FLUTTER_TEST': 'true',
        },
      );
169
      // Setting a bool can't fail in the callback.
170
      unawaited(_process.exitCode.then<void>((_) => _isRunning = false));
171
      _process.stdout
172 173 174 175 176
        .transform<String>(utf8.decoder)
        .transform<String>(const LineSplitter())
        .listen((String line) {
          _logReader.addLine(line);
        });
177
      _process.stderr
178 179 180 181 182
        .transform<String>(utf8.decoder)
        .transform<String>(const LineSplitter())
        .listen((String line) {
          _logReader.addLine(line);
        });
183

184
      if (!debuggingOptions.debuggingEnabled) {
185
        return LaunchResult.succeeded();
186
      }
187

188
      final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory(
189
        getLogReader(),
190 191 192
        hostPort: debuggingOptions.hostVmServicePort,
        devicePort: debuggingOptions.deviceVmServicePort,
        ipv6: ipv6,
193
      );
194 195

      final Uri observatoryUri = await observatoryDiscovery.uri;
196
      return LaunchResult.succeeded(observatoryUri: observatoryUri);
197 198
    } catch (error) {
      printError('Failed to launch $package: $error');
199
      return LaunchResult.failed();
200 201 202 203 204 205 206 207 208 209 210 211
    }
  }

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

  @override
  Future<bool> uninstallApp(ApplicationPackage app) async => true;
212 213 214

  @override
  bool isSupportedForProject(FlutterProject flutterProject) => true;
215 216 217
}

class FlutterTesterDevices extends PollingDeviceDiscovery {
218 219
  FlutterTesterDevices() : super('Flutter tester');

220 221 222 223 224
  static const String kTesterDeviceId = 'flutter-tester';

  static bool showFlutterTesterDevice = false;

  final FlutterTesterDevice _testerDevice =
225
      FlutterTesterDevice(kTesterDeviceId);
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240

  @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 =
241
      StreamController<String>.broadcast();
242 243 244 245 246 247 248 249 250 251 252 253

  @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);
}
254 255 256 257 258

/// 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
259
  Future<int> forward(int devicePort, { int hostPort }) {
260
    if (hostPort != null && hostPort != devicePort) {
261
      throw 'Forwarding to a different port is not supported by flutter tester';
262
    }
263
    return Future<int>.value(devicePort);
264 265 266 267 268 269
  }

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

  @override
270
  Future<void> unforward(ForwardedPort forwardedPort) async { }
271
}