blob: b42194d828dee2b56f788ada1601d7a12a953b7d [file] [log] [blame]
// 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.vmServiceUri, 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': 'trace-to-file=path/to/trace.binpb',
'FLUTTER_ENGINE_SWITCH_10': 'endless-trace-buffer=true',
'FLUTTER_ENGINE_SWITCH_11': 'dump-skp-on-shader-compilation=true',
'FLUTTER_ENGINE_SWITCH_12': 'cache-sksl=true',
'FLUTTER_ENGINE_SWITCH_13': 'purge-persistent-cache=true',
'FLUTTER_ENGINE_SWITCH_14': 'enable-impeller=false',
'FLUTTER_ENGINE_SWITCH_15': 'enable-checked-mode=true',
'FLUTTER_ENGINE_SWITCH_16': 'verify-entry-points=true',
'FLUTTER_ENGINE_SWITCH_17': 'start-paused=true',
'FLUTTER_ENGINE_SWITCH_18': 'disable-service-auth-codes=true',
'FLUTTER_ENGINE_SWITCH_19': 'dart-flags=--null_assertions',
'FLUTTER_ENGINE_SWITCH_20': 'use-test-fonts=true',
'FLUTTER_ENGINE_SWITCH_21': 'verbose-logging=true',
'FLUTTER_ENGINE_SWITCHES': '21',
}
),
]);
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,
traceToFile: 'path/to/trace.binpb',
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_SWITCH_5': 'enable-impeller=false',
'FLUTTER_ENGINE_SWITCHES': '5',
}
),
]);
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'],
),
);
});
testWithoutContext('Desktop devices pass through the enable-impeller flag', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['debug'],
exitCode: -1,
environment: <String, String>{
'FLUTTER_ENGINE_SWITCH_1': 'enable-dart-profiling=true',
'FLUTTER_ENGINE_SWITCH_2': 'enable-impeller=true',
'FLUTTER_ENGINE_SWITCH_3': 'enable-checked-mode=true',
'FLUTTER_ENGINE_SWITCH_4': 'verify-entry-points=true',
'FLUTTER_ENGINE_SWITCHES': '4'
}
),
]);
final FakeDesktopDevice device = setUpDesktopDevice(
processManager: processManager,
);
final FakeApplicationPackage package = FakeApplicationPackage();
await device.startApp(
package,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(
BuildInfo.debug,
enableImpeller: ImpellerStatus.enabled,
dartEntrypointArgs: <String>[],
),
);
});
testWithoutContext('Desktop devices pass through the --no-enable-impeller flag', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['debug'],
exitCode: -1,
environment: <String, String>{
'FLUTTER_ENGINE_SWITCH_1': 'enable-dart-profiling=true',
'FLUTTER_ENGINE_SWITCH_2': 'enable-impeller=false',
'FLUTTER_ENGINE_SWITCH_3': 'enable-checked-mode=true',
'FLUTTER_ENGINE_SWITCH_4': 'verify-entry-points=true',
'FLUTTER_ENGINE_SWITCHES': '4'
}
),
]);
final FakeDesktopDevice device = setUpDesktopDevice(
processManager: processManager,
);
final FakeApplicationPackage package = FakeApplicationPackage();
await device.startApp(
package,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(
BuildInfo.debug,
enableImpeller: ImpellerStatus.disabled,
dartEntrypointArgs: <String>[],
),
);
});
}
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 buildInfo.mode.cliName;
}
}
class FakeApplicationPackage extends Fake implements ApplicationPackage { }
class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
@override
String get name => 'Example';
}