flutter_tester.dart 8.24 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11 12 13 14
// 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 '../build_info.dart';
15
import '../bundle.dart';
16
import '../convert.dart';
17 18
import '../dart/package_map.dart';
import '../device.dart';
19
import '../globals.dart' as globals;
20
import '../project.dart';
21 22 23 24 25
import '../protocol_discovery.dart';
import '../version.dart';

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

29
  FlutterTesterApp._(Directory directory)
30 31
    : _directory = directory,
      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 45 46 47 48 49
  FlutterTesterDevice(String deviceId) : super(
      deviceId,
      platformType: null,
      category: null,
      ephemeral: false,
  );
50 51

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

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

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

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

  @override
64
  DevicePortForwarder get portForwarder => _portForwarder;
65 66 67

  @override
  Future<String> get sdkNameAndVersion async {
68
    final FlutterVersion flutterVersion = globals.flutterVersion;
69 70 71 72 73 74 75
    return 'Flutter ${flutterVersion.frameworkRevisionShort}';
  }

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

  @override
76
  void clearLogs() { }
77

78
  final _FlutterTesterDeviceLogReader _logReader =
79
      _FlutterTesterDeviceLogReader();
80

81
  @override
82 83 84 85 86 87
  DeviceLogReader getLogReader({
    ApplicationPackage app,
    bool includePastLogs = false,
  }) {
    return _logReader;
  }
88 89 90 91 92 93 94 95 96 97 98 99 100

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

101 102 103
  bool _isRunning = false;
  bool get isRunning => _isRunning;

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

    if (!buildInfo.isDebug) {
117
      globals.printError('This device only supports debug mode.');
118
      return LaunchResult.failed();
119 120
    }

121 122
    final String shellPath = globals.artifacts.getArtifactPath(Artifact.flutterTester);
    if (!globals.fs.isFileSync(shellPath)) {
123
      throwToolExit('Cannot find Flutter shell at $shellPath');
124
    }
125 126 127

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

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

163
    command.add(applicationKernelFilePath);
164

165
    ProtocolDiscovery observatoryDiscovery;
166
    try {
167
      globals.printTrace(command.join(' '));
168

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

190
      if (!debuggingOptions.debuggingEnabled) {
191
        return LaunchResult.succeeded();
192
      }
193

194
      observatoryDiscovery = ProtocolDiscovery.observatory(
195
        getLogReader(),
196 197 198
        hostPort: debuggingOptions.hostVmServicePort,
        devicePort: debuggingOptions.deviceVmServicePort,
        ipv6: ipv6,
199
      );
200 201

      final Uri observatoryUri = await observatoryDiscovery.uri;
202 203 204 205 206 207 208
      if (observatoryUri != null) {
        return LaunchResult.succeeded(observatoryUri: observatoryUri);
      }
      globals.printError(
        'Failed to launch $package: '
        'The log reader failed unexpectedly.',
      );
209
    } on Exception catch (error) {
210
      globals.printError('Failed to launch $package: $error');
211 212
    } finally {
      await observatoryDiscovery?.cancel();
213
    }
214
    return LaunchResult.failed();
215 216 217 218 219 220 221 222 223 224 225
  }

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

  @override
  Future<bool> uninstallApp(ApplicationPackage app) async => true;
226 227 228

  @override
  bool isSupportedForProject(FlutterProject flutterProject) => true;
229 230 231 232 233 234

  @override
  Future<void> dispose() async {
    _logReader?.dispose();
    await _portForwarder?.dispose();
  }
235 236 237
}

class FlutterTesterDevices extends PollingDeviceDiscovery {
238 239
  FlutterTesterDevices() : super('Flutter tester');

240 241 242 243 244
  static const String kTesterDeviceId = 'flutter-tester';

  static bool showFlutterTesterDevice = false;

  final FlutterTesterDevice _testerDevice =
245
      FlutterTesterDevice(kTesterDeviceId);
246 247 248 249 250 251 252 253

  @override
  bool get canListAnything => true;

  @override
  bool get supportsPlatform => true;

  @override
254
  Future<List<Device>> pollingGetDevices({ Duration timeout }) async {
255 256 257 258 259 260
    return showFlutterTesterDevice ? <Device>[_testerDevice] : <Device>[];
  }
}

class _FlutterTesterDeviceLogReader extends DeviceLogReader {
  final StreamController<String> _logLinesController =
261
      StreamController<String>.broadcast();
262 263 264 265 266 267 268 269 270 271 272

  @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);
273 274 275

  @override
  void dispose() {}
276
}
277 278 279 280 281

/// 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
282
  Future<int> forward(int devicePort, { int hostPort }) {
283
    if (hostPort != null && hostPort != devicePort) {
284
      throw 'Forwarding to a different port is not supported by flutter tester';
285
    }
286
    return Future<int>.value(devicePort);
287 288 289 290 291 292
  }

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

  @override
293
  Future<void> unforward(ForwardedPort forwardedPort) async { }
294 295 296

  @override
  Future<void> dispose() async { }
297
}