// 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. // @dart = 2.8 import 'dart:async'; import 'package:dds/dds.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/test/flutter_tester_device.dart'; import 'package:flutter_tools/src/test/font_config_manager.dart'; import 'package:meta/meta.dart'; import 'package:stream_channel/stream_channel.dart'; import 'package:test/fake.dart'; import '../src/common.dart'; import '../src/context.dart'; import '../src/fake_process_manager.dart'; void main() { FakePlatform platform; FileSystem fileSystem; FakeProcessManager processManager; FlutterTesterTestDevice device; setUp(() { fileSystem = MemoryFileSystem.test(); // Not Windows. platform = FakePlatform( environment: <String, String>{}, ); processManager = FakeProcessManager.any(); }); FlutterTesterTestDevice createDevice({ List<String> dartEntrypointArgs = const <String>[], bool enableObservatory = false, }) => TestFlutterTesterDevice( platform: platform, fileSystem: fileSystem, processManager: processManager, enableObservatory: enableObservatory, dartEntrypointArgs: dartEntrypointArgs, ); testUsingContext('runs in Rosetta on arm64 Mac', () async { final FakeProcessManager processManager = FakeProcessManager.empty(); final FlutterTesterTestDevice device = TestFlutterTesterDevice( platform: FakePlatform(operatingSystem: 'macos'), fileSystem: fileSystem, processManager: processManager, enableObservatory: false, dartEntrypointArgs: const <String>[], ); processManager.addCommands(<FakeCommand>[ const FakeCommand( command: <String>[ 'which', 'sysctl', ], ), const FakeCommand( command: <String>[ 'sysctl', 'hw.optional.arm64', ], stdout: 'hw.optional.arm64: 1', ), FakeCommand(command: const <String>[ '/usr/bin/arch', '-x86_64', '/', '--disable-observatory', '--ipv6', '--enable-checked-mode', '--verify-entry-points', '--enable-software-rendering', '--skia-deterministic-rendering', '--enable-dart-profiling', '--non-interactive', '--use-test-fonts', '--disable-asset-fonts', '--packages=.dart_tool/package_config.json', 'example.dill', ], environment: <String, String>{ 'FLUTTER_TEST': 'true', 'FONTCONFIG_FILE': device.fontConfigManager.fontConfigFile.path, 'SERVER_PORT': '0', 'APP_NAME': '', }), ]); await device.start('example.dill'); expect(processManager.hasRemainingExpectations, isFalse); }); group('The FLUTTER_TEST environment variable is passed to the test process', () { setUp(() { processManager = FakeProcessManager.list(<FakeCommand>[ const FakeCommand( command: <String>[ 'uname', '-m', ], stdout: 'x86_64', ), ]); device = createDevice(); fileSystem .file('.dart_tool/package_config.json') ..createSync(recursive: true) ..writeAsStringSync('{"configVersion":2,"packages":[]}'); }); FakeCommand flutterTestCommand(String expectedFlutterTestValue) { return FakeCommand(command: const <String>[ '/', '--disable-observatory', '--ipv6', '--enable-checked-mode', '--verify-entry-points', '--enable-software-rendering', '--skia-deterministic-rendering', '--enable-dart-profiling', '--non-interactive', '--use-test-fonts', '--disable-asset-fonts', '--packages=.dart_tool/package_config.json', 'example.dill', ], environment: <String, String>{ 'FLUTTER_TEST': expectedFlutterTestValue, 'FONTCONFIG_FILE': device.fontConfigManager.fontConfigFile.path, 'SERVER_PORT': '0', 'APP_NAME': '', }); } testUsingContext('as true when not originally set', () async { processManager.addCommand(flutterTestCommand('true')); await device.start('example.dill'); expect(processManager.hasRemainingExpectations, isFalse); }); testUsingContext('as true when set to true', () async { platform.environment = <String, String>{'FLUTTER_TEST': 'true'}; processManager.addCommand(flutterTestCommand('true')); await device.start('example.dill'); expect(processManager.hasRemainingExpectations, isFalse); }); testUsingContext('as false when set to false', () async { platform.environment = <String, String>{'FLUTTER_TEST': 'false'}; processManager.addCommand(flutterTestCommand('false')); await device.start('example.dill'); expect(processManager.hasRemainingExpectations, isFalse); }); testUsingContext('unchanged when set', () async { platform.environment = <String, String>{'FLUTTER_TEST': 'neither true nor false'}; processManager.addCommand(flutterTestCommand('neither true nor false')); await device.start('example.dill'); expect(processManager.hasRemainingExpectations, isFalse); }); testUsingContext('as null when set to null', () async { platform.environment = <String, String>{'FLUTTER_TEST': null}; processManager.addCommand(flutterTestCommand(null)); await device.start('example.dill'); expect(processManager.hasRemainingExpectations, isFalse); }); }); group('Dart Entrypoint Args', () { setUp(() { processManager = FakeProcessManager.list(<FakeCommand>[ const FakeCommand( command: <String>[ 'uname', '-m', ], stdout: 'x86_64', ), const FakeCommand( command: <String>[ '/', '--disable-observatory', '--ipv6', '--enable-checked-mode', '--verify-entry-points', '--enable-software-rendering', '--skia-deterministic-rendering', '--enable-dart-profiling', '--non-interactive', '--use-test-fonts', '--disable-asset-fonts', '--packages=.dart_tool/package_config.json', '--foo', '--bar', 'example.dill', ], stdout: 'success', stderr: 'failure', ), ]); device = createDevice(dartEntrypointArgs: <String>['--foo', '--bar']); }); testUsingContext('Can pass additional arguments to tester binary', () async { await device.start('example.dill'); expect(processManager, hasNoRemainingExpectations); }); }); group('DDS', () { setUp(() { processManager = FakeProcessManager.list(<FakeCommand>[ const FakeCommand( command: <String>[ 'uname', '-m', ], stdout: 'x86_64', ), const FakeCommand( command: <String>[ '/', '--observatory-port=0', '--ipv6', '--enable-checked-mode', '--verify-entry-points', '--enable-software-rendering', '--skia-deterministic-rendering', '--enable-dart-profiling', '--non-interactive', '--use-test-fonts', '--disable-asset-fonts', '--packages=.dart_tool/package_config.json', 'example.dill', ], stdout: 'The Dart VM service is listening on http://localhost:1234', stderr: 'failure', ), ]); device = createDevice(enableObservatory: true); }); testUsingContext('skips setting observatory port and uses the input port for DDS instead', () async { await device.start('example.dill'); await device.observatoryUri; final Uri uri = await (device as TestFlutterTesterDevice).ddsServiceUriFuture(); expect(uri.port, 1234); }); }); } /// A Flutter Tester device. /// /// Uses a mock HttpServer. We don't want to bind random ports in our CI hosts. class TestFlutterTesterDevice extends FlutterTesterTestDevice { TestFlutterTesterDevice({ @required Platform platform, @required FileSystem fileSystem, @required ProcessManager processManager, @required bool enableObservatory, @required List<String> dartEntrypointArgs, }) : super( id: 999, shellPath: '/', platform: platform, fileSystem: fileSystem, processManager: processManager, logger: BufferLogger.test(), debuggingOptions: DebuggingOptions.enabled( const BuildInfo( BuildMode.debug, '', treeShakeIcons: false, ), hostVmServicePort: 1234, dartEntrypointArgs: dartEntrypointArgs, ), enableObservatory: enableObservatory, machine: false, host: InternetAddress.loopbackIPv6, testAssetDirectory: null, flutterProject: null, icudtlPath: null, compileExpression: null, fontConfigManager: FontConfigManager(), ); final Completer<Uri> _ddsServiceUriCompleter = Completer<Uri>(); Future<Uri> ddsServiceUriFuture() => _ddsServiceUriCompleter.future; @override Future<DartDevelopmentService> startDds(Uri uri) async { _ddsServiceUriCompleter.complete(uri); return FakeDartDevelopmentService(Uri.parse('http://localhost:${debuggingOptions.hostVmServicePort}'), Uri.parse('http://localhost:8080')); } @override Future<HttpServer> bind(InternetAddress host, int port) async => FakeHttpServer(); @override Future<StreamChannel<String>> get remoteChannel async => StreamChannelController<String>().foreign; } class FakeDartDevelopmentService extends Fake implements DartDevelopmentService { FakeDartDevelopmentService(this.uri, this.original); final Uri original; @override final Uri uri; @override Uri get remoteVmServiceUri => original; } class FakeHttpServer extends Fake implements HttpServer { @override int get port => 0; }