flutter/packages/flutter_tools/test/general.shard/desktop_device_test.dart
Chris Bracken d272a3ab80
Reland: [macos] add flavor options to tool commands (#119564)
* Reland: [macos] add flavor options to tool commands

Adds --flavor option to flutter run and flutter build. Running against
preexisting devicelab flavor tests for feature parity between macOS,
iOS, and Android.

This relands #118421 by alex-wallen which was reverted in #118858 due to
the following test failures:

The bail-out with "Host and target are the same. Nothing to install."
added in `packages/flutter_tools/lib/src/commands/install.dart`
triggered failures in the following tests, which unconditionally attempt
to install the built app, which is unsupported on desktop since the
host and target are the same:

* https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8791495589540422465/+/u/run_flutter_view_macos__start_up/test_stdout
* https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8791496218824259121/+/u/run_complex_layout_win_desktop__start_up/test_stdout
* https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8791496218165602641/+/u/run_flutter_gallery_win_desktop__start_up/test_stdout

Fixes #64088

* Partial revert: eliminate install check on desktop

The original flavour support patch included a check that triggered a
failure when flutter install is run on desktop OSes. This was
intentional, since the host and target devices are the same and
installation is unnecessary to launch the app on currently-supported
desktop OSes.

Note that Windows UWP apps *do* require installation to run, and we used
to have an install command for those apps, though UWP is no longer
supported.

Since that part of the change was orthogonal to flavour support itself,
I'm reverting that component of the change and we can deal with it
separately if so desired.
2023-01-31 17:37:46 +00:00

383 lines
14 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// 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:file/memory.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/desktop_device.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/device_port_forwarder.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:test/fake.dart';
import '../src/common.dart';
import '../src/fake_process_manager.dart';
void main() {
group('Basic info', () {
testWithoutContext('Category is desktop', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
expect(device.category, Category.desktop);
});
testWithoutContext('Not an emulator', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
expect(await device.isLocalEmulator, false);
expect(await device.emulatorId, null);
});
testWithoutContext('Uses OS name as SDK name', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
expect(await device.sdkNameAndVersion, 'Example');
});
});
group('Install', () {
testWithoutContext('Install checks always return true', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
expect(await device.isAppInstalled(FakeApplicationPackage()), true);
expect(await device.isLatestBuildInstalled(FakeApplicationPackage()), true);
expect(device.category, Category.desktop);
});
testWithoutContext('Install and uninstall are no-ops that report success', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
final FakeApplicationPackage package = FakeApplicationPackage();
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', () {
testWithoutContext('Stop without start is a successful no-op', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
final FakeApplicationPackage package = FakeApplicationPackage();
expect(await device.stopApp(package), true);
});
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(
command: const <String>['debug'],
stdout: 'The Dart VM service is listening on http://127.0.0.1/0\n',
completer: completer,
),
]);
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager, fileSystem: fileSystem);
final String? executableName = device.executablePathForDevice(FakeApplicationPackage(), BuildInfo.debug);
fileSystem.file(executableName).writeAsStringSync('\n');
final FakeApplicationPackage package = FakeApplicationPackage();
final LaunchResult result = await device.startApp(
package,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
);
expect(result.started, true);
expect(result.observatoryUri, Uri.parse('http://127.0.0.1/0'));
});
testWithoutContext('Null executable path fails gracefully', () async {
final BufferLogger logger = BufferLogger.test();
final DesktopDevice device = setUpDesktopDevice(nullExecutablePathForDevice: true, logger: logger);
final FakeApplicationPackage package = FakeApplicationPackage();
final LaunchResult result = await device.startApp(
package,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
);
expect(result.started, false);
expect(logger.errorText, contains('Unable to find executable to run'));
});
testWithoutContext('stopApp kills process started by startApp', () async {
final Completer<void> completer = Completer<void>();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>['debug'],
stdout: 'The Dart VM service is listening on http://127.0.0.1/0\n',
completer: completer,
),
]);
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
final FakeApplicationPackage package = FakeApplicationPackage();
final LaunchResult result = await device.startApp(
package,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
);
expect(result.started, true);
expect(await device.stopApp(package), true);
});
});
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'],
stdout: 'The Dart VM service is listening on http://127.0.0.1/0\n',
completer: completer,
environment: const <String, String>{
'FLUTTER_ENGINE_SWITCH_1': 'enable-dart-profiling=true',
'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',
}
),
]);
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
final FakeApplicationPackage package = FakeApplicationPackage();
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',
traceSkiaAllowlist: 'skia.a,skia.b',
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'],
stdout: 'The Dart VM service is listening on http://127.0.0.1/0\n',
completer: completer,
environment: const <String, String>{
'FLUTTER_ENGINE_SWITCH_1': 'enable-dart-profiling=true',
'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',
}
),
]);
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
final FakeApplicationPackage package = FakeApplicationPackage();
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);
});
testWithoutContext('Port forwarder is a no-op', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
final DevicePortForwarder portForwarder = device.portForwarder;
final int result = await portForwarder.forward(2);
expect(result, 2);
expect(portForwarder.forwardedPorts.isEmpty, true);
});
testWithoutContext('createDevFSWriter returns a LocalDevFSWriter', () {
final FakeDesktopDevice device = setUpDesktopDevice();
expect(device.createDevFSWriter(FakeApplicationPackage(), ''), isA<LocalDevFSWriter>());
});
testWithoutContext('startApp supports dartEntrypointArgs', () async {
final Completer<void> completer = Completer<void>();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>['debug', 'arg1', 'arg2'],
stdout: 'The Dart VM service is listening on http://127.0.0.1/0\n',
completer: completer,
),
]);
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
final FakeApplicationPackage package = FakeApplicationPackage();
final LaunchResult result = await device.startApp(
package,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(
BuildInfo.debug,
dartEntrypointArgs: <String>['arg1', 'arg2'],
),
);
expect(result.started, true);
});
testWithoutContext('Device logger captures all output', () async {
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();
}));
// Start looking for 'Oops' in the stream before starting the app.
expect(device.getLogReader().logLines, emits('Oops'));
final FakeApplicationPackage package = FakeApplicationPackage();
await device.startApp(
package,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(
BuildInfo.debug,
dartEntrypointArgs: <String>['arg1', 'arg2'],
),
);
});
}
FakeDesktopDevice setUpDesktopDevice({
FileSystem? fileSystem,
Logger? logger,
ProcessManager? processManager,
OperatingSystemUtils? operatingSystemUtils,
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({
required ProcessManager processManager,
required Logger logger,
required FileSystem fileSystem,
required OperatingSystemUtils operatingSystemUtils,
this.nullExecutablePathForDevice = false,
}) : super(
'dummy',
platformType: PlatformType.linux,
ephemeral: false,
processManager: processManager,
logger: logger,
fileSystem: fileSystem,
operatingSystemUtils: operatingSystemUtils,
);
/// The [mainPath] last passed to [buildForDevice].
String? lastBuiltMainPath;
/// The [buildInfo] last passed to [buildForDevice].
BuildInfo? lastBuildInfo;
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({
String? mainPath,
BuildInfo? buildInfo,
}) async {
lastBuiltMainPath = mainPath;
lastBuildInfo = buildInfo;
}
// Dummy implementation that just returns the build mode name.
@override
String? executablePathForDevice(ApplicationPackage package, BuildInfo buildInfo) {
if (nullExecutablePathForDevice) {
return null;
}
return getNameForBuildMode(buildInfo.mode);
}
}
class FakeApplicationPackage extends Fake implements ApplicationPackage { }
class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
@override
String get name => 'Example';
}