flutter_tester.dart 8.28 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7 8 9
import 'dart:async';

import 'package:meta/meta.dart';
10
import 'package:process/process.dart';
11 12 13 14 15

import '../application_package.dart';
import '../artifacts.dart';
import '../base/file_system.dart';
import '../base/io.dart';
16
import '../base/logger.dart';
17
import '../base/os.dart';
18
import '../build_info.dart';
19
import '../bundle.dart';
20
import '../bundle_builder.dart';
21
import '../desktop_device.dart';
22
import '../devfs.dart';
23
import '../device.dart';
24
import '../device_port_forwarder.dart';
25
import '../project.dart';
26 27 28 29
import '../protocol_discovery.dart';
import '../version.dart';

class FlutterTesterApp extends ApplicationPackage {
30 31
  factory FlutterTesterApp.fromCurrentDirectory(FileSystem fileSystem) {
    return FlutterTesterApp._(fileSystem.currentDirectory);
32 33
  }

34
  FlutterTesterApp._(Directory directory)
35 36
    : _directory = directory,
      super(id: directory.path);
37

38 39
  final Directory _directory;

40
  @override
41
  String get name => _directory.basename;
42 43

  @override
44
  File get packagesFile => _directory.childFile('.packages');
45 46
}

47 48 49 50 51
/// 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.
52
class FlutterTesterDevice extends Device {
53
  FlutterTesterDevice(String deviceId, {
54 55 56 57 58
    @required ProcessManager processManager,
    @required FlutterVersion flutterVersion,
    @required Logger logger,
    @required FileSystem fileSystem,
    @required Artifacts artifacts,
59
    @required OperatingSystemUtils operatingSystemUtils,
60 61 62 63 64
  }) : _processManager = processManager,
       _flutterVersion = flutterVersion,
       _logger = logger,
       _fileSystem = fileSystem,
       _artifacts = artifacts,
65
       _operatingSystemUtils = operatingSystemUtils,
66 67 68 69 70 71 72 73 74 75 76 77
       super(
        deviceId,
        platformType: null,
        category: null,
        ephemeral: false,
      );

  final ProcessManager _processManager;
  final FlutterVersion _flutterVersion;
  final Logger _logger;
  final FileSystem _fileSystem;
  final Artifacts _artifacts;
78
  final OperatingSystemUtils _operatingSystemUtils;
79 80

  Process _process;
81
  final DevicePortForwarder _portForwarder = const NoOpDevicePortForwarder();
82 83 84 85

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

86 87 88
  @override
  Future<String> get emulatorId async => null;

89 90 91 92
  @override
  String get name => 'Flutter test device';

  @override
93
  DevicePortForwarder get portForwarder => _portForwarder;
94 95 96

  @override
  Future<String> get sdkNameAndVersion async {
97
    final FlutterVersion flutterVersion = _flutterVersion;
98 99 100
    return 'Flutter ${flutterVersion.frameworkRevisionShort}';
  }

101 102 103
  @override
  bool supportsRuntimeMode(BuildMode buildMode) => buildMode == BuildMode.debug;

104 105 106 107
  @override
  Future<TargetPlatform> get targetPlatform async => TargetPlatform.tester;

  @override
108
  void clearLogs() { }
109

110
  final DesktopLogReader _logReader = DesktopLogReader();
111

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

  @override
121 122 123 124
  Future<bool> installApp(
    ApplicationPackage app, {
    String userIdentifier,
  }) async => true;
125 126

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

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

  @override
  bool isSupported() => true;

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

155
    final Directory assetDirectory = _fileSystem.systemTempDirectory
156
      .createTempSync('flutter_tester.');
157
    final String applicationKernelFilePath = getKernelPathForTransformerOptions(
158
      _fileSystem.path.join(assetDirectory.path, 'flutter-tester-app.dill'),
159 160
      trackWidgetCreation: buildInfo.trackWidgetCreation,
    );
161

162
    // Build assets and perform initial compilation.
163
    await BundleBuilder().build(
164
      buildInfo: buildInfo,
165 166
      mainPath: mainPath,
      applicationKernelFilePath: applicationKernelFilePath,
167
      platform: getTargetPlatformForName(getNameForHostPlatform(_operatingSystemUtils.hostPlatform)),
168
      assetDirPath: assetDirectory.path,
169
    );
170

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

187
    ProtocolDiscovery observatoryDiscovery;
188
    try {
189 190
      _logger.printTrace(command.join(' '));
      _process = await _processManager.start(command,
191 192 193 194
        environment: <String, String>{
          'FLUTTER_TEST': 'true',
        },
      );
195
      if (!debuggingOptions.debuggingEnabled) {
196
        return LaunchResult.succeeded();
197
      }
198

199
      observatoryDiscovery = ProtocolDiscovery.observatory(
200
        getLogReader(),
201
        hostPort: debuggingOptions.hostVmServicePort,
202 203
        devicePort: debuggingOptions.deviceVmServicePort,
        ipv6: ipv6,
204
        logger: _logger,
205
      );
206
      _logReader.initializeProcess(_process);
207 208

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

  @override
225 226 227 228
  Future<bool> stopApp(
    ApplicationPackage app, {
    String userIdentifier,
  }) async {
229 230 231 232 233 234
    _process?.kill();
    _process = null;
    return true;
  }

  @override
235 236 237 238
  Future<bool> uninstallApp(
    ApplicationPackage app, {
    String userIdentifier,
  }) async => true;
239 240 241

  @override
  bool isSupportedForProject(FlutterProject flutterProject) => true;
242

243 244 245 246 247 248 249 250 251 252
  @override
  DevFSWriter createDevFSWriter(
    covariant ApplicationPackage app,
    String userIdentifier,
  ) {
    return LocalDevFSWriter(
      fileSystem: _fileSystem,
    );
  }

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

class FlutterTesterDevices extends PollingDeviceDiscovery {
261
  FlutterTesterDevices({
262 263 264 265 266
    @required FileSystem fileSystem,
    @required Artifacts artifacts,
    @required ProcessManager processManager,
    @required Logger logger,
    @required FlutterVersion flutterVersion,
267
    @required OperatingSystemUtils operatingSystemUtils,
268
  }) : _testerDevice = FlutterTesterDevice(
269
        kTesterDeviceId,
270 271 272 273 274
        fileSystem: fileSystem,
        artifacts: artifacts,
        processManager: processManager,
        logger: logger,
        flutterVersion: flutterVersion,
275
        operatingSystemUtils: operatingSystemUtils,
276 277
      ),
       super('Flutter tester');
278

279 280 281 282
  static const String kTesterDeviceId = 'flutter-tester';

  static bool showFlutterTesterDevice = false;

283
  final FlutterTesterDevice _testerDevice;
284 285 286 287 288 289 290 291

  @override
  bool get canListAnything => true;

  @override
  bool get supportsPlatform => true;

  @override
292
  Future<List<Device>> pollingGetDevices({ Duration timeout }) async {
293 294
    return showFlutterTesterDevice ? <Device>[_testerDevice] : <Device>[];
  }
295 296 297

  @override
  List<String> get wellKnownIds => const <String>[kTesterDeviceId];
298
}