• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6
7import 'package:meta/meta.dart';
8
9import '../application_package.dart';
10import '../artifacts.dart';
11import '../base/common.dart';
12import '../base/file_system.dart';
13import '../base/io.dart';
14import '../base/process_manager.dart';
15import '../build_info.dart';
16import '../bundle.dart';
17import '../convert.dart';
18import '../dart/package_map.dart';
19import '../device.dart';
20import '../globals.dart';
21import '../project.dart';
22import '../protocol_discovery.dart';
23import '../version.dart';
24
25class FlutterTesterApp extends ApplicationPackage {
26  factory FlutterTesterApp.fromCurrentDirectory() {
27    return FlutterTesterApp._(fs.currentDirectory);
28  }
29
30  FlutterTesterApp._(Directory directory)
31    : _directory = directory,
32      super(id: directory.path);
33
34  final Directory _directory;
35
36  @override
37  String get name => _directory.basename;
38
39  @override
40  File get packagesFile => _directory.childFile('.packages');
41}
42
43// TODO(scheglov): This device does not currently work with full restarts.
44class FlutterTesterDevice extends Device {
45  FlutterTesterDevice(String deviceId) : super(
46      deviceId,
47      platformType: null,
48      category: null,
49      ephemeral: false,
50  );
51
52  Process _process;
53  final DevicePortForwarder _portForwarder = _NoopPortForwarder();
54
55  @override
56  Future<bool> get isLocalEmulator async => false;
57
58  @override
59  Future<String> get emulatorId async => null;
60
61  @override
62  String get name => 'Flutter test device';
63
64  @override
65  DevicePortForwarder get portForwarder => _portForwarder;
66
67  @override
68  Future<String> get sdkNameAndVersion async {
69    final FlutterVersion flutterVersion = FlutterVersion.instance;
70    return 'Flutter ${flutterVersion.frameworkRevisionShort}';
71  }
72
73  @override
74  Future<TargetPlatform> get targetPlatform async => TargetPlatform.tester;
75
76  @override
77  void clearLogs() { }
78
79  final _FlutterTesterDeviceLogReader _logReader =
80      _FlutterTesterDeviceLogReader();
81
82  @override
83  DeviceLogReader getLogReader({ ApplicationPackage app }) => _logReader;
84
85  @override
86  Future<bool> installApp(ApplicationPackage app) async => true;
87
88  @override
89  Future<bool> isAppInstalled(ApplicationPackage app) async => false;
90
91  @override
92  Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
93
94  @override
95  bool isSupported() => true;
96
97  bool _isRunning = false;
98  bool get isRunning => _isRunning;
99
100  @override
101  Future<LaunchResult> startApp(
102    ApplicationPackage package, {
103    @required String mainPath,
104    String route,
105    @required DebuggingOptions debuggingOptions,
106    Map<String, dynamic> platformArgs,
107    bool prebuiltApplication = false,
108    bool usesTerminalUi = true,
109    bool ipv6 = false,
110  }) async {
111    final BuildInfo buildInfo = debuggingOptions.buildInfo;
112
113    if (!buildInfo.isDebug) {
114      printError('This device only supports debug mode.');
115      return LaunchResult.failed();
116    }
117
118    final String shellPath = artifacts.getArtifactPath(Artifact.flutterTester);
119    if (!fs.isFileSync(shellPath))
120      throwToolExit('Cannot find Flutter shell at $shellPath');
121
122    final List<String> command = <String>[
123      shellPath,
124      '--run-forever',
125      '--non-interactive',
126      '--enable-dart-profiling',
127      '--packages=${PackageMap.globalPackagesPath}',
128    ];
129    if (debuggingOptions.debuggingEnabled) {
130      if (debuggingOptions.startPaused)
131        command.add('--start-paused');
132      if (debuggingOptions.disableServiceAuthCodes)
133        command.add('--disable-service-auth-codes');
134      if (debuggingOptions.hasObservatoryPort)
135        command.add('--observatory-port=${debuggingOptions.observatoryPort}');
136    }
137
138    // Build assets and perform initial compilation.
139    final String assetDirPath = getAssetBuildDirectory();
140    final String applicationKernelFilePath = getKernelPathForTransformerOptions(
141      fs.path.join(getBuildDirectory(), 'flutter-tester-app.dill'),
142      trackWidgetCreation: buildInfo.trackWidgetCreation,
143    );
144    await BundleBuilder().build(
145      mainPath: mainPath,
146      assetDirPath: assetDirPath,
147      applicationKernelFilePath: applicationKernelFilePath,
148      precompiledSnapshot: false,
149      trackWidgetCreation: buildInfo.trackWidgetCreation,
150    );
151    command.add('--flutter-assets-dir=$assetDirPath');
152
153    command.add(applicationKernelFilePath);
154
155    try {
156      printTrace(command.join(' '));
157
158      _isRunning = true;
159      _process = await processManager.start(command,
160        environment: <String, String>{
161          'FLUTTER_TEST': 'true',
162        },
163      );
164      // Setting a bool can't fail in the callback.
165      unawaited(_process.exitCode.then<void>((_) => _isRunning = false));
166      _process.stdout
167        .transform<String>(utf8.decoder)
168        .transform<String>(const LineSplitter())
169        .listen((String line) {
170          _logReader.addLine(line);
171        });
172      _process.stderr
173        .transform<String>(utf8.decoder)
174        .transform<String>(const LineSplitter())
175        .listen((String line) {
176          _logReader.addLine(line);
177        });
178
179      if (!debuggingOptions.debuggingEnabled)
180        return LaunchResult.succeeded();
181
182      final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory(
183        getLogReader(),
184        hostPort: debuggingOptions.observatoryPort,
185      );
186
187      final Uri observatoryUri = await observatoryDiscovery.uri;
188      return LaunchResult.succeeded(observatoryUri: observatoryUri);
189    } catch (error) {
190      printError('Failed to launch $package: $error');
191      return LaunchResult.failed();
192    }
193  }
194
195  @override
196  Future<bool> stopApp(ApplicationPackage app) async {
197    _process?.kill();
198    _process = null;
199    return true;
200  }
201
202  @override
203  Future<bool> uninstallApp(ApplicationPackage app) async => true;
204
205  @override
206  bool isSupportedForProject(FlutterProject flutterProject) => true;
207}
208
209class FlutterTesterDevices extends PollingDeviceDiscovery {
210  FlutterTesterDevices() : super('Flutter tester');
211
212  static const String kTesterDeviceId = 'flutter-tester';
213
214  static bool showFlutterTesterDevice = false;
215
216  final FlutterTesterDevice _testerDevice =
217      FlutterTesterDevice(kTesterDeviceId);
218
219  @override
220  bool get canListAnything => true;
221
222  @override
223  bool get supportsPlatform => true;
224
225  @override
226  Future<List<Device>> pollingGetDevices() async {
227    return showFlutterTesterDevice ? <Device>[_testerDevice] : <Device>[];
228  }
229}
230
231class _FlutterTesterDeviceLogReader extends DeviceLogReader {
232  final StreamController<String> _logLinesController =
233      StreamController<String>.broadcast();
234
235  @override
236  int get appPid => 0;
237
238  @override
239  Stream<String> get logLines => _logLinesController.stream;
240
241  @override
242  String get name => 'flutter tester log reader';
243
244  void addLine(String line) => _logLinesController.add(line);
245}
246
247/// A fake port forwarder that doesn't do anything. Used by flutter tester
248/// where the VM is running on the same machine and does not need ports forwarding.
249class _NoopPortForwarder extends DevicePortForwarder {
250  @override
251  Future<int> forward(int devicePort, { int hostPort }) {
252    if (hostPort != null && hostPort != devicePort)
253      throw 'Forwarding to a different port is not supported by flutter tester';
254    return Future<int>.value(devicePort);
255  }
256
257  @override
258  List<ForwardedPort> get forwardedPorts => <ForwardedPort>[];
259
260  @override
261  Future<void> unforward(ForwardedPort forwardedPort) async { }
262}
263