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

import 'dart:async';

7
import 'package:process/process.dart';
8 9 10 11 12

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

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

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

35 36
  final Directory _directory;

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

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

  final ProcessManager _processManager;
  final FlutterVersion _flutterVersion;
  final Logger _logger;
  final FileSystem _fileSystem;
  final Artifacts _artifacts;
71
  final OperatingSystemUtils _operatingSystemUtils;
72

73
  Process? _process;
74
  final DevicePortForwarder _portForwarder = const NoOpDevicePortForwarder();
75 76 77 78

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

79
  @override
80
  Future<String?> get emulatorId async => null;
81

82 83 84 85
  @override
  String get name => 'Flutter test device';

  @override
86
  DevicePortForwarder get portForwarder => _portForwarder;
87 88 89

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

94 95 96
  @override
  bool supportsRuntimeMode(BuildMode buildMode) => buildMode == BuildMode.debug;

97 98 99 100
  @override
  Future<TargetPlatform> get targetPlatform async => TargetPlatform.tester;

  @override
101
  void clearLogs() { }
102

103
  final DesktopLogReader _logReader = DesktopLogReader();
104

105
  @override
106
  DeviceLogReader getLogReader({
107
    ApplicationPackage? app,
108 109 110 111
    bool includePastLogs = false,
  }) {
    return _logReader;
  }
112 113

  @override
114 115
  Future<bool> installApp(
    ApplicationPackage app, {
116
    String? userIdentifier,
117
  }) async => true;
118 119

  @override
120 121
  Future<bool> isAppInstalled(
    ApplicationPackage app, {
122
    String? userIdentifier,
123
  }) async => false;
124 125 126 127 128 129 130 131 132 133

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

  @override
  bool isSupported() => true;

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

148
    final Directory assetDirectory = _fileSystem.systemTempDirectory
149
      .createTempSync('flutter_tester.');
150
    final String applicationKernelFilePath = getKernelPathForTransformerOptions(
151
      _fileSystem.path.join(assetDirectory.path, 'flutter-tester-app.dill'),
152 153
      trackWidgetCreation: buildInfo.trackWidgetCreation,
    );
154

155
    // Build assets and perform initial compilation.
156
    await BundleBuilder().build(
157
      buildInfo: buildInfo,
158 159
      mainPath: mainPath,
      applicationKernelFilePath: applicationKernelFilePath,
160
      platform: getTargetPlatformForName(getNameForHostPlatform(_operatingSystemUtils.hostPlatform)),
161
      assetDirPath: assetDirectory.path,
162
    );
163

164 165 166 167
    final List<String> command = <String>[
      _artifacts.getArtifactPath(Artifact.flutterTester),
      '--run-forever',
      '--non-interactive',
168 169
      if (debuggingOptions.enableDartProfiling)
        '--enable-dart-profiling',
170
      '--packages=${debuggingOptions.buildInfo.packagesPath}',
171
      '--flutter-assets-dir=${assetDirectory.path}',
172 173 174 175
      if (debuggingOptions.startPaused)
        '--start-paused',
      if (debuggingOptions.disableServiceAuthCodes)
        '--disable-service-auth-codes',
176
      if (debuggingOptions.hostVmServicePort != null)
177
        '--observatory-port=${debuggingOptions.hostVmServicePort}',
178
      applicationKernelFilePath,
179
    ];
180

181
    ProtocolDiscovery? observatoryDiscovery;
182
    try {
183 184
      _logger.printTrace(command.join(' '));
      _process = await _processManager.start(command,
185 186 187 188
        environment: <String, String>{
          'FLUTTER_TEST': 'true',
        },
      );
189
      if (!debuggingOptions.debuggingEnabled) {
190
        return LaunchResult.succeeded();
191
      }
192

193
      observatoryDiscovery = ProtocolDiscovery.observatory(
194
        getLogReader(),
195
        hostPort: debuggingOptions.hostVmServicePort,
196 197
        devicePort: debuggingOptions.deviceVmServicePort,
        ipv6: ipv6,
198
        logger: _logger,
199
      );
200
      _logReader.initializeProcess(_process!);
201

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

  @override
219 220
  Future<bool> stopApp(
    ApplicationPackage app, {
221
    String? userIdentifier,
222
  }) async {
223 224 225 226 227 228
    _process?.kill();
    _process = null;
    return true;
  }

  @override
229 230
  Future<bool> uninstallApp(
    ApplicationPackage app, {
231
    String? userIdentifier,
232
  }) async => true;
233 234 235

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

237 238 239
  @override
  DevFSWriter createDevFSWriter(
    covariant ApplicationPackage app,
240
    String? userIdentifier,
241 242 243 244 245 246
  ) {
    return LocalDevFSWriter(
      fileSystem: _fileSystem,
    );
  }

247 248
  @override
  Future<void> dispose() async {
249 250
    _logReader.dispose();
    await _portForwarder.dispose();
251
  }
252 253 254
}

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

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

  static bool showFlutterTesterDevice = false;

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

  @override
  bool get canListAnything => true;

  @override
  bool get supportsPlatform => true;

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

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