flutter_tester.dart 8.67 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
// 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';
8
import 'package:process/process.dart';
9 10 11

import '../application_package.dart';
import '../artifacts.dart';
12
import '../base/config.dart';
13 14
import '../base/file_system.dart';
import '../base/io.dart';
15
import '../base/logger.dart';
16
import '../build_info.dart';
17
import '../bundle.dart';
18
import '../convert.dart';
19
import '../device.dart';
20
import '../project.dart';
21 22 23 24
import '../protocol_discovery.dart';
import '../version.dart';

class FlutterTesterApp extends ApplicationPackage {
25 26
  factory FlutterTesterApp.fromCurrentDirectory(FileSystem fileSystem) {
    return FlutterTesterApp._(fileSystem.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 44 45 46
/// The device interface for running on the flutter_tester shell.
///
/// Normally this is only used as the runner for `flutter test`, but it can
/// also be used as a regular device when `--show-test-device` is provided
/// to the flutter command.
47 48
// TODO(scheglov): This device does not currently work with full restarts.
class FlutterTesterDevice extends Device {
49
  FlutterTesterDevice(String deviceId, {
50 51 52 53 54 55 56 57 58 59 60 61
    @required ProcessManager processManager,
    @required FlutterVersion flutterVersion,
    @required Logger logger,
    @required String buildDirectory,
    @required FileSystem fileSystem,
    @required Artifacts artifacts,
  }) : _processManager = processManager,
       _flutterVersion = flutterVersion,
       _logger = logger,
       _buildDirectory = buildDirectory,
       _fileSystem = fileSystem,
       _artifacts = artifacts,
62 63 64 65 66 67 68 69 70 71 72 73 74
       super(
        deviceId,
        platformType: null,
        category: null,
        ephemeral: false,
      );

  final ProcessManager _processManager;
  final FlutterVersion _flutterVersion;
  final Logger _logger;
  final String _buildDirectory;
  final FileSystem _fileSystem;
  final Artifacts _artifacts;
75 76

  Process _process;
77
  final DevicePortForwarder _portForwarder = const NoOpDevicePortForwarder();
78 79 80 81

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

82 83 84
  @override
  Future<String> get emulatorId async => null;

85 86 87 88
  @override
  String get name => 'Flutter test device';

  @override
89
  DevicePortForwarder get portForwarder => _portForwarder;
90 91 92

  @override
  Future<String> get sdkNameAndVersion async {
93
    final FlutterVersion flutterVersion = _flutterVersion;
94 95 96
    return 'Flutter ${flutterVersion.frameworkRevisionShort}';
  }

97 98 99
  @override
  bool supportsRuntimeMode(BuildMode buildMode) => buildMode == BuildMode.debug;

100 101 102 103
  @override
  Future<TargetPlatform> get targetPlatform async => TargetPlatform.tester;

  @override
104
  void clearLogs() { }
105

106
  final _FlutterTesterDeviceLogReader _logReader =
107
      _FlutterTesterDeviceLogReader();
108

109
  @override
110 111 112 113 114 115
  DeviceLogReader getLogReader({
    ApplicationPackage app,
    bool includePastLogs = false,
  }) {
    return _logReader;
  }
116 117

  @override
118 119 120 121
  Future<bool> installApp(
    ApplicationPackage app, {
    String userIdentifier,
  }) async => true;
122 123

  @override
124 125 126 127
  Future<bool> isAppInstalled(
    ApplicationPackage app, {
    String userIdentifier,
  }) async => false;
128 129 130 131 132 133 134 135 136 137 138 139

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

  @override
  bool isSupported() => true;

  @override
  Future<LaunchResult> startApp(
    ApplicationPackage package, {
    @required String mainPath,
    String route,
140
    DebuggingOptions debuggingOptions,
141
    Map<String, dynamic> platformArgs,
142 143
    bool prebuiltApplication = false,
    bool ipv6 = false,
144
    String userIdentifier,
145
  }) async {
146 147
    final BuildInfo buildInfo = debuggingOptions.buildInfo;
    if (!buildInfo.isDebug) {
148
      _logger.printError('This device only supports debug mode.');
149
      return LaunchResult.failed();
150 151
    }

152
    final String assetDirPath = _fileSystem.path.join(_buildDirectory, 'flutter_assets');
153
    final String applicationKernelFilePath = getKernelPathForTransformerOptions(
154
      _fileSystem.path.join(_buildDirectory, 'flutter-tester-app.dill'),
155 156
      trackWidgetCreation: buildInfo.trackWidgetCreation,
    );
157
    // Build assets and perform initial compilation.
158
    await BundleBuilder().build(
159
      buildInfo: buildInfo,
160 161 162
      mainPath: mainPath,
      assetDirPath: assetDirPath,
      applicationKernelFilePath: applicationKernelFilePath,
163
      precompiledSnapshot: false,
164
      trackWidgetCreation: buildInfo.trackWidgetCreation,
165
      platform: getTargetPlatformForName(getNameForHostPlatform(getCurrentHostPlatform())),
166
      treeShakeIcons: buildInfo.treeShakeIcons,
167
    );
168

169 170 171 172 173 174 175 176 177 178 179 180
    final List<String> command = <String>[
      _artifacts.getArtifactPath(Artifact.flutterTester),
      '--run-forever',
      '--non-interactive',
      '--enable-dart-profiling',
      '--packages=${debuggingOptions.buildInfo.packagesPath}',
      '--flutter-assets-dir=$assetDirPath',
      if (debuggingOptions.startPaused)
        '--start-paused',
      if (debuggingOptions.disableServiceAuthCodes)
        '--disable-service-auth-codes',
      if (debuggingOptions.hasObservatoryPort)
181
        '--observatory-port=${debuggingOptions.hostVmServicePort}',
182 183
      applicationKernelFilePath
    ];
184

185
    ProtocolDiscovery observatoryDiscovery;
186
    try {
187 188
      _logger.printTrace(command.join(' '));
      _process = await _processManager.start(command,
189 190 191 192
        environment: <String, String>{
          'FLUTTER_TEST': 'true',
        },
      );
193
      _process.stdout
194 195
        .transform<String>(utf8.decoder)
        .transform<String>(const LineSplitter())
196
        .listen(_logReader.addLine);
197
      _process.stderr
198 199
        .transform<String>(utf8.decoder)
        .transform<String>(const LineSplitter())
200
        .listen(_logReader.addLine);
201

202
      if (!debuggingOptions.debuggingEnabled) {
203
        return LaunchResult.succeeded();
204
      }
205

206
      observatoryDiscovery = ProtocolDiscovery.observatory(
207
        getLogReader(),
208
        hostPort: debuggingOptions.hostVmServicePort,
209 210
        devicePort: debuggingOptions.deviceVmServicePort,
        ipv6: ipv6,
211
      );
212 213

      final Uri observatoryUri = await observatoryDiscovery.uri;
214 215 216
      if (observatoryUri != null) {
        return LaunchResult.succeeded(observatoryUri: observatoryUri);
      }
217
      _logger.printError(
218 219 220
        'Failed to launch $package: '
        'The log reader failed unexpectedly.',
      );
221
    } on Exception catch (error) {
222
      _logger.printError('Failed to launch $package: $error');
223 224
    } finally {
      await observatoryDiscovery?.cancel();
225
    }
226
    return LaunchResult.failed();
227 228 229
  }

  @override
230 231 232 233
  Future<bool> stopApp(
    ApplicationPackage app, {
    String userIdentifier,
  }) async {
234 235 236 237 238 239
    _process?.kill();
    _process = null;
    return true;
  }

  @override
240 241 242 243
  Future<bool> uninstallApp(
    ApplicationPackage app, {
    String userIdentifier,
  }) async => true;
244 245 246

  @override
  bool isSupportedForProject(FlutterProject flutterProject) => true;
247 248 249 250 251 252

  @override
  Future<void> dispose() async {
    _logReader?.dispose();
    await _portForwarder?.dispose();
  }
253 254 255
}

class FlutterTesterDevices extends PollingDeviceDiscovery {
256
  FlutterTesterDevices({
257 258 259 260 261 262 263
    @required FileSystem fileSystem,
    @required Artifacts artifacts,
    @required ProcessManager processManager,
    @required Logger logger,
    @required FlutterVersion flutterVersion,
    @required Config config,
  }) : _testerDevice = FlutterTesterDevice(
264
        kTesterDeviceId,
265 266 267 268 269 270
        fileSystem: fileSystem,
        artifacts: artifacts,
        processManager: processManager,
        buildDirectory: getBuildDirectory(config, fileSystem),
        logger: logger,
        flutterVersion: flutterVersion,
271 272
      ),
       super('Flutter tester');
273

274 275 276 277
  static const String kTesterDeviceId = 'flutter-tester';

  static bool showFlutterTesterDevice = false;

278
  final FlutterTesterDevice _testerDevice;
279 280 281 282 283 284 285 286

  @override
  bool get canListAnything => true;

  @override
  bool get supportsPlatform => true;

  @override
287
  Future<List<Device>> pollingGetDevices({ Duration timeout }) async {
288 289 290 291 292 293
    return showFlutterTesterDevice ? <Device>[_testerDevice] : <Device>[];
  }
}

class _FlutterTesterDeviceLogReader extends DeviceLogReader {
  final StreamController<String> _logLinesController =
294
      StreamController<String>.broadcast();
295 296 297 298 299 300 301 302 303 304 305

  @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);
306 307 308

  @override
  void dispose() {}
309
}