desktop_device_test.dart 14 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:file/memory.dart';
8 9
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/file_system.dart';
10 11
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
12 13
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/desktop_device.dart';
14
import 'package:flutter_tools/src/devfs.dart';
15
import 'package:flutter_tools/src/device.dart';
16
import 'package:flutter_tools/src/device_port_forwarder.dart';
17
import 'package:flutter_tools/src/project.dart';
18

19
import 'package:test/fake.dart';
20 21

import '../src/common.dart';
22
import '../src/fake_process_manager.dart';
23 24 25

void main() {
  group('Basic info', () {
26 27 28
    testWithoutContext('Category is desktop', () async {
      final FakeDesktopDevice device = setUpDesktopDevice();

29 30 31
      expect(device.category, Category.desktop);
    });

32 33 34
    testWithoutContext('Not an emulator', () async {
      final FakeDesktopDevice device = setUpDesktopDevice();

35 36 37 38
      expect(await device.isLocalEmulator, false);
      expect(await device.emulatorId, null);
    });

39 40 41 42
    testWithoutContext('Uses OS name as SDK name', () async {
      final FakeDesktopDevice device = setUpDesktopDevice();

      expect(await device.sdkNameAndVersion, 'Example');
43 44 45 46
    });
  });

  group('Install', () {
47 48 49
    testWithoutContext('Install checks always return true', () async {
      final FakeDesktopDevice device = setUpDesktopDevice();

50 51
      expect(await device.isAppInstalled(FakeApplicationPackage()), true);
      expect(await device.isLatestBuildInstalled(FakeApplicationPackage()), true);
52 53 54
      expect(device.category, Category.desktop);
    });

55 56
    testWithoutContext('Install and uninstall are no-ops that report success', () async {
      final FakeDesktopDevice device = setUpDesktopDevice();
57
      final FakeApplicationPackage package = FakeApplicationPackage();
58

59 60 61 62 63 64 65 66 67 68 69 70
      expect(await device.uninstallApp(package), true);
      expect(await device.isAppInstalled(package), true);
      expect(await device.isLatestBuildInstalled(package), true);

      expect(await device.installApp(package), true);
      expect(await device.isAppInstalled(package), true);
      expect(await device.isLatestBuildInstalled(package), true);
      expect(device.category, Category.desktop);
    });
  });

  group('Starting and stopping application', () {
71 72
    testWithoutContext('Stop without start is a successful no-op', () async {
      final FakeDesktopDevice device = setUpDesktopDevice();
73
      final FakeApplicationPackage package = FakeApplicationPackage();
74 75 76 77

      expect(await device.stopApp(package), true);
    });

78 79 80 81 82
    testWithoutContext('Can run from prebuilt application', () async {
      final FileSystem fileSystem = MemoryFileSystem.test();
      final Completer<void> completer = Completer<void>();
      final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
        FakeCommand(
83
          command: const <String>['debug'],
84
          stdout: 'The Dart VM service is listening on http://127.0.0.1/0\n',
85 86 87 88
          completer: completer,
        ),
      ]);
      final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager, fileSystem: fileSystem);
89
      final String? executableName = device.executablePathForDevice(FakeApplicationPackage(), BuildMode.debug);
90
      fileSystem.file(executableName).writeAsStringSync('\n');
91
      final FakeApplicationPackage package = FakeApplicationPackage();
92 93 94 95 96
      final LaunchResult result = await device.startApp(
        package,
        prebuiltApplication: true,
        debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      );
97

98 99 100 101
      expect(result.started, true);
      expect(result.observatoryUri, Uri.parse('http://127.0.0.1/0'));
    });

102 103 104
    testWithoutContext('Null executable path fails gracefully', () async {
      final BufferLogger logger = BufferLogger.test();
      final DesktopDevice device = setUpDesktopDevice(nullExecutablePathForDevice: true, logger: logger);
105
      final FakeApplicationPackage package = FakeApplicationPackage();
106 107 108 109 110
      final LaunchResult result = await device.startApp(
        package,
        prebuiltApplication: true,
        debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      );
111

112
      expect(result.started, false);
113
      expect(logger.errorText, contains('Unable to find executable to run'));
114 115
    });

116 117 118 119
    testWithoutContext('stopApp kills process started by startApp', () async {
      final Completer<void> completer = Completer<void>();
      final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
        FakeCommand(
120
          command: const <String>['debug'],
121
          stdout: 'The Dart VM service is listening on http://127.0.0.1/0\n',
122 123 124 125
          completer: completer,
        ),
      ]);
      final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
126
      final FakeApplicationPackage package = FakeApplicationPackage();
127 128 129 130 131
      final LaunchResult result = await device.startApp(
        package,
        prebuiltApplication: true,
        debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
      );
132

133 134 135 136 137
      expect(result.started, true);
      expect(await device.stopApp(package), true);
    });
  });

138 139 140 141 142
  testWithoutContext('startApp supports DebuggingOptions through FLUTTER_ENGINE_SWITCH environment variables', () async {
    final Completer<void> completer = Completer<void>();
    final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
      FakeCommand(
        command: const <String>['debug'],
143
        stdout: 'The Dart VM service is listening on http://127.0.0.1/0\n',
144 145 146
        completer: completer,
        environment: const <String, String>{
          'FLUTTER_ENGINE_SWITCH_1': 'enable-dart-profiling=true',
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
          'FLUTTER_ENGINE_SWITCH_2': 'trace-startup=true',
          'FLUTTER_ENGINE_SWITCH_3': 'enable-software-rendering=true',
          'FLUTTER_ENGINE_SWITCH_4': 'skia-deterministic-rendering=true',
          'FLUTTER_ENGINE_SWITCH_5': 'trace-skia=true',
          'FLUTTER_ENGINE_SWITCH_6': 'trace-allowlist=foo,bar',
          'FLUTTER_ENGINE_SWITCH_7': 'trace-skia-allowlist=skia.a,skia.b',
          'FLUTTER_ENGINE_SWITCH_8': 'trace-systrace=true',
          'FLUTTER_ENGINE_SWITCH_9': 'endless-trace-buffer=true',
          'FLUTTER_ENGINE_SWITCH_10': 'dump-skp-on-shader-compilation=true',
          'FLUTTER_ENGINE_SWITCH_11': 'cache-sksl=true',
          'FLUTTER_ENGINE_SWITCH_12': 'purge-persistent-cache=true',
          'FLUTTER_ENGINE_SWITCH_13': 'enable-checked-mode=true',
          'FLUTTER_ENGINE_SWITCH_14': 'verify-entry-points=true',
          'FLUTTER_ENGINE_SWITCH_15': 'start-paused=true',
          'FLUTTER_ENGINE_SWITCH_16': 'disable-service-auth-codes=true',
          'FLUTTER_ENGINE_SWITCH_17': 'dart-flags=--null_assertions',
          'FLUTTER_ENGINE_SWITCH_18': 'use-test-fonts=true',
          'FLUTTER_ENGINE_SWITCH_19': 'verbose-logging=true',
          'FLUTTER_ENGINE_SWITCHES': '19'
166 167 168 169
        }
      ),
    ]);
    final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
170
    final FakeApplicationPackage package = FakeApplicationPackage();
171 172 173 174 175 176 177 178 179 180 181 182 183 184
    final LaunchResult result = await device.startApp(
      package,
      prebuiltApplication: true,
      platformArgs: <String, Object>{
        'trace-startup': true,
      },
      debuggingOptions: DebuggingOptions.enabled(
        BuildInfo.debug,
        startPaused: true,
        disableServiceAuthCodes: true,
        enableSoftwareRendering: true,
        skiaDeterministicRendering: true,
        traceSkia: true,
        traceAllowlist: 'foo,bar',
185
        traceSkiaAllowlist: 'skia.a,skia.b',
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
        traceSystrace: true,
        endlessTraceBuffer: true,
        dumpSkpOnShaderCompilation: true,
        cacheSkSL: true,
        purgePersistentCache: true,
        useTestFonts: true,
        verboseSystemLogs: true,
        nullAssertions: true,
      ),
    );

    expect(result.started, true);
  });

  testWithoutContext('startApp supports DebuggingOptions through FLUTTER_ENGINE_SWITCH environment variables when debugging is disabled', () async {
    final Completer<void> completer = Completer<void>();
    final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
      FakeCommand(
        command: const <String>['debug'],
205
        stdout: 'The Dart VM service is listening on http://127.0.0.1/0\n',
206 207 208
        completer: completer,
        environment: const <String, String>{
          'FLUTTER_ENGINE_SWITCH_1': 'enable-dart-profiling=true',
209 210 211 212
          'FLUTTER_ENGINE_SWITCH_2': 'trace-startup=true',
          'FLUTTER_ENGINE_SWITCH_3': 'trace-allowlist=foo,bar',
          'FLUTTER_ENGINE_SWITCH_4': 'cache-sksl=true',
          'FLUTTER_ENGINE_SWITCHES': '4'
213 214 215 216
        }
      ),
    ]);
    final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
217
    final FakeApplicationPackage package = FakeApplicationPackage();
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
    final LaunchResult result = await device.startApp(
      package,
      prebuiltApplication: true,
      platformArgs: <String, Object>{
        'trace-startup': true,
      },
      debuggingOptions: DebuggingOptions.disabled(
        BuildInfo.debug,
        traceAllowlist: 'foo,bar',
        cacheSkSL: true,
      ),
    );

    expect(result.started, true);
  });

234 235
  testWithoutContext('Port forwarder is a no-op', () async {
    final FakeDesktopDevice device = setUpDesktopDevice();
236 237
    final DevicePortForwarder portForwarder = device.portForwarder;
    final int result = await portForwarder.forward(2);
238

239 240 241
    expect(result, 2);
    expect(portForwarder.forwardedPorts.isEmpty, true);
  });
242

243
  testWithoutContext('createDevFSWriter returns a LocalDevFSWriter', () {
244 245
    final FakeDesktopDevice device = setUpDesktopDevice();

246
    expect(device.createDevFSWriter(FakeApplicationPackage(), ''), isA<LocalDevFSWriter>());
247
  });
248 249 250 251 252 253

  testWithoutContext('startApp supports dartEntrypointArgs', () async {
    final Completer<void> completer = Completer<void>();
    final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
      FakeCommand(
        command: const <String>['debug', 'arg1', 'arg2'],
254
        stdout: 'The Dart VM service is listening on http://127.0.0.1/0\n',
255
        completer: completer,
256 257 258 259 260 261 262 263 264
      ),
    ]);
    final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
    final FakeApplicationPackage package = FakeApplicationPackage();
    final LaunchResult result = await device.startApp(
      package,
      prebuiltApplication: true,
      debuggingOptions: DebuggingOptions.enabled(
        BuildInfo.debug,
265
        dartEntrypointArgs: <String>['arg1', 'arg2'],
266 267 268 269 270
      ),
    );

    expect(result.started, true);
  });
271

272
  testWithoutContext('Device logger captures all output', () async {
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
    final Completer<void> exitCompleter = Completer<void>();
    final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
      FakeCommand(
        command: const <String>['debug', 'arg1', 'arg2'],
        exitCode: -1,
        stderr: 'Oops\n',
        completer: exitCompleter,
        outputFollowsExit: true,
      ),
    ]);
    final FakeDesktopDevice device = setUpDesktopDevice(
      processManager: processManager,
    );
    unawaited(Future<void>(() {
      exitCompleter.complete();
    }));
289 290 291 292

    // Start looking for 'Oops' in the stream before starting the app.
    expect(device.getLogReader().logLines, emits('Oops'));

293 294 295 296 297 298 299 300 301 302
    final FakeApplicationPackage package = FakeApplicationPackage();
    await device.startApp(
      package,
      prebuiltApplication: true,
      debuggingOptions: DebuggingOptions.enabled(
        BuildInfo.debug,
        dartEntrypointArgs: <String>['arg1', 'arg2'],
      ),
    );
  });
303
}
304 305

FakeDesktopDevice setUpDesktopDevice({
306 307 308 309
  FileSystem? fileSystem,
  Logger? logger,
  ProcessManager? processManager,
  OperatingSystemUtils? operatingSystemUtils,
310 311 312 313 314 315 316 317 318 319 320 321 322 323
  bool nullExecutablePathForDevice = false,
}) {
  return FakeDesktopDevice(
    fileSystem: fileSystem ?? MemoryFileSystem.test(),
    logger: logger ?? BufferLogger.test(),
    processManager: processManager ?? FakeProcessManager.any(),
    operatingSystemUtils: operatingSystemUtils ?? FakeOperatingSystemUtils(),
    nullExecutablePathForDevice: nullExecutablePathForDevice,
  );
}

/// A trivial subclass of DesktopDevice for testing the shared functionality.
class FakeDesktopDevice extends DesktopDevice {
  FakeDesktopDevice({
324 325 326 327 328
    required ProcessManager processManager,
    required Logger logger,
    required FileSystem fileSystem,
    required OperatingSystemUtils operatingSystemUtils,
    this.nullExecutablePathForDevice = false,
329 330 331 332 333 334 335 336 337 338 339
  }) : super(
      'dummy',
      platformType: PlatformType.linux,
      ephemeral: false,
      processManager: processManager,
      logger: logger,
      fileSystem: fileSystem,
      operatingSystemUtils: operatingSystemUtils,
  );

  /// The [mainPath] last passed to [buildForDevice].
340
  String? lastBuiltMainPath;
341 342

  /// The [buildInfo] last passed to [buildForDevice].
343
  BuildInfo? lastBuildInfo;
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361

  final bool nullExecutablePathForDevice;

  @override
  String get name => 'dummy';

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

  @override
  bool isSupported() => true;

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

  @override
  Future<void> buildForDevice(
    ApplicationPackage package, {
362 363
    String? mainPath,
    BuildInfo? buildInfo,
364 365 366 367 368 369 370
  }) async {
    lastBuiltMainPath = mainPath;
    lastBuildInfo = buildInfo;
  }

  // Dummy implementation that just returns the build mode name.
  @override
371
  String? executablePathForDevice(ApplicationPackage package, BuildMode buildMode) {
372 373 374 375 376 377 378
    if (nullExecutablePathForDevice) {
      return null;
    }
    return buildMode == null ? 'null' : getNameForBuildMode(buildMode);
  }
}

379
class FakeApplicationPackage extends Fake implements ApplicationPackage { }
380 381 382 383
class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
  @override
  String get name => 'Example';
}