Unverified Commit f2761b6b authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] refactor Chrome launch logic to remove globals/statics (#55160)

parent 033b07c7
......@@ -125,6 +125,7 @@ class WebAssetServer implements AssetReader {
/// Unhandled exceptions will throw a [ToolExit] with the error and stack
/// trace.
static Future<WebAssetServer> start(
ChromiumLauncher chromiumLauncher,
String hostname,
int port,
UrlTunneller urlTunneller,
......@@ -208,8 +209,8 @@ class WebAssetServer implements AssetReader {
enableDebugExtension: true,
buildResults: const Stream<BuildResult>.empty(),
chromeConnection: () async {
final Chrome chrome = await ChromeLauncher.connectedInstance;
return chrome.chromeConnection;
final Chromium chromium = await chromiumLauncher.connectedInstance;
return chromium.chromeConnection;
},
hostname: hostname,
urlEncoder: urlTunneller,
......@@ -562,6 +563,7 @@ class WebDevFS implements DevFS {
@required this.enableDwds,
@required this.entrypoint,
@required this.expressionCompiler,
@required this.chromiumLauncher,
this.testMode = false,
});
......@@ -575,6 +577,7 @@ class WebDevFS implements DevFS {
final bool enableDwds;
final bool testMode;
final ExpressionCompiler expressionCompiler;
final ChromiumLauncher chromiumLauncher;
WebAssetServer webAssetServer;
......@@ -632,6 +635,7 @@ class WebDevFS implements DevFS {
@override
Future<Uri> create() async {
webAssetServer = await WebAssetServer.start(
chromiumLauncher,
hostname,
port,
urlTunneller,
......
......@@ -114,6 +114,7 @@ abstract class ResidentWebRunner extends ResidentRunner {
StreamSubscription<vmservice.Event> _stdErrSub;
bool _exited = false;
WipConnection _wipConnection;
ChromiumLauncher _chromiumLauncher;
vmservice.VmService get _vmService =>
_connectionResult?.debugConnection?.vmService;
......@@ -158,10 +159,6 @@ abstract class ResidentWebRunner extends ResidentRunner {
'Failed to clean up temp directory: ${_generatedEntrypointDirectory.path}',
);
}
if (ChromeLauncher.hasChromeInstance) {
final Chrome chrome = await ChromeLauncher.connectedInstance;
await chrome.close();
}
_exited = true;
}
......@@ -394,6 +391,10 @@ class _ResidentWebRunner extends ResidentWebRunner {
? await globals.os.findFreePort()
: int.tryParse(debuggingOptions.port);
if (device.device is ChromiumDevice) {
_chromiumLauncher = (device.device as ChromiumDevice).chromeLauncher;
}
try {
return await asyncGuard(() async {
// Ensure dwds resources are cached. If the .packages file is missing then
......@@ -419,6 +420,7 @@ class _ResidentWebRunner extends ResidentWebRunner {
enableDwds: _enableDwds,
entrypoint: globals.fs.file(target).uri,
expressionCompiler: expressionCompiler,
chromiumLauncher: _chromiumLauncher,
);
final Uri url = await device.devFS.create();
if (debuggingOptions.buildInfo.isDebug) {
......@@ -645,8 +647,8 @@ class _ResidentWebRunner extends ResidentWebRunner {
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
}) async {
if (device.device is ChromeDevice) {
final Chrome chrome = await ChromeLauncher.connectedInstance;
if (_chromiumLauncher != null) {
final Chromium chrome = await _chromiumLauncher.connectedInstance;
final ChromeTab chromeTab = await chrome.chromeConnection.getTab((ChromeTab chromeTab) {
return !chromeTab.url.startsWith('chrome-extension');
});
......
......@@ -14,12 +14,10 @@ import '../runner/flutter_command.dart';
class DevicesCommand extends FlutterCommand {
DevicesCommand() {
argParser.addFlag('machine',
negatable: false,
help: 'Output device information in machine readable structured JSON format',
);
argParser.addOption(
'timeout',
abbr: 't',
......@@ -60,7 +58,9 @@ class DevicesCommand extends FlutterCommand {
final List<Device> devices = await deviceManager.refreshAllConnectedDevices(timeout: timeout);
if (devices.isEmpty) {
if (boolArg('machine')) {
await printDevicesAsJson(devices);
} else if (devices.isEmpty) {
final StringBuffer status = StringBuffer('No devices detected.');
status.writeln();
status.writeln();
......@@ -80,8 +80,6 @@ class DevicesCommand extends FlutterCommand {
globals.printStatus('• $diagnostic', hangingIndent: 2);
}
}
} else if (boolArg('machine')) {
await printDevicesAsJson(devices);
} else {
globals.printStatus('${devices.length} connected ${pluralize('device', devices.length)}:\n');
await Device.printDevices(devices);
......@@ -97,5 +95,4 @@ class DevicesCommand extends FlutterCommand {
)
);
}
}
......@@ -45,7 +45,6 @@ import 'persistent_tool_state.dart';
import 'reporting/reporting.dart';
import 'run_hot.dart';
import 'version.dart';
import 'web/chrome.dart';
import 'web/workflow.dart';
import 'windows/visual_studio.dart';
import 'windows/visual_studio_validator.dart';
......@@ -99,13 +98,6 @@ Future<T> runInContext<T>(
logger: globals.logger,
platform: globals.platform,
),
ChromeLauncher: () => ChromeLauncher(
fileSystem: globals.fs,
processManager: globals.processManager,
logger: globals.logger,
operatingSystemUtils: globals.os,
platform: globals.platform,
),
CocoaPods: () => CocoaPods(
fileSystem: globals.fs,
processManager: globals.processManager,
......
......@@ -90,7 +90,13 @@ class DeviceManager {
featureFlags: featureFlags,
),
WindowsDevices(),
WebDevices(),
WebDevices(
featureFlags: featureFlags,
fileSystem: globals.fs,
logger: globals.logger,
platform: globals.platform,
processManager: globals.processManager,
),
]);
String _specifiedDeviceId;
......
......@@ -35,6 +35,7 @@ import 'reporting/reporting.dart';
import 'tester/flutter_tester.dart';
import 'version.dart';
import 'vscode/vscode_validator.dart';
import 'web/chrome.dart';
import 'web/web_validator.dart';
import 'web/workflow.dart';
import 'windows/visual_studio_validator.dart';
......@@ -82,10 +83,16 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
if (globals.iosWorkflow.appliesToHostPlatform || macOSWorkflow.appliesToHostPlatform)
GroupedValidator(<DoctorValidator>[XcodeValidator(xcode: globals.xcode, userMessages: userMessages), cocoapodsValidator]),
if (webWorkflow.appliesToHostPlatform)
WebValidator(
chromeLauncher: globals.chromeLauncher,
platform: globals.platform,
ChromeValidator(
chromiumLauncher: ChromiumLauncher(
browserFinder: findChromeExecutable,
fileSystem: globals.fs,
logger: globals.logger,
operatingSystemUtils: globals.os,
platform: globals.platform,
processManager: globals.processManager,
),
platform: globals.platform,
),
if (linuxWorkflow.appliesToHostPlatform)
LinuxDoctorValidator(
......
......@@ -34,7 +34,6 @@ import 'persistent_tool_state.dart';
import 'project.dart';
import 'reporting/reporting.dart';
import 'version.dart';
import 'web/chrome.dart';
Artifacts get artifacts => context.get<Artifacts>();
BuildSystem get buildSystem => context.get<BuildSystem>();
......@@ -183,8 +182,5 @@ PlistParser get plistParser => context.get<PlistParser>() ?? (
));
PlistParser _plistInstance;
/// The [ChromeLauncher] instance.
ChromeLauncher get chromeLauncher => context.get<ChromeLauncher>();
/// The global template renderer
TemplateRenderer get templateRenderer => context.get<TemplateRenderer>();
......@@ -555,7 +555,7 @@ class BrowserManager {
}
/// The browser instance that this is connected to via [_channel].
final Chrome _browser;
final Chromium _browser;
// TODO(nweiz): Consider removing the duplication between this and
// [_browser.name].
......@@ -623,8 +623,16 @@ class BrowserManager {
bool debug = false,
bool headless = true,
}) async {
final Chrome chrome =
await globals.chromeLauncher.launch(url.toString(), headless: headless);
final ChromiumLauncher chromiumLauncher = ChromiumLauncher(
browserFinder: findChromeExecutable,
fileSystem: globals.fs,
operatingSystemUtils: globals.os,
logger: globals.logger,
platform: globals.platform,
processManager: globals.processManager,
);
final Chromium chrome =
await chromiumLauncher.launch(url.toString(), headless: headless);
final Completer<BrowserManager> completer = Completer<BrowserManager>();
......
......@@ -15,11 +15,13 @@ import '../base/io.dart';
import '../base/logger.dart';
import '../base/os.dart';
import '../convert.dart';
import '../globals.dart' as globals;
/// An environment variable used to override the location of chrome.
/// An environment variable used to override the location of Google Chrome.
const String kChromeEnvironment = 'CHROME_EXECUTABLE';
/// An environment variable used to override the location of Microsoft Edge.
const String kEdgeEnvironment = 'EDGE_ENVIRONMENT';
/// The expected executable name on linux.
const String kLinuxExecutable = 'google-chrome';
......@@ -27,9 +29,14 @@ const String kLinuxExecutable = 'google-chrome';
const String kMacOSExecutable =
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
/// The expected executable name on Windows.
/// The expected Chrome executable name on Windows.
const String kWindowsExecutable = r'Google\Chrome\Application\chrome.exe';
/// The expected Edge executable name on Windows.
const String kWindowsEdgeExecutable = r'Microsoft\Edge\Application\msedge.exe';
typedef BrowserFinder = String Function(Platform, FileSystem);
/// Find the chrome executable on the current platform.
///
/// Does not verify whether the executable exists.
......@@ -63,43 +70,73 @@ String findChromeExecutable(Platform platform, FileSystem fileSystem) {
return null;
}
@visibleForTesting
void resetChromeForTesting() {
ChromeLauncher._currentCompleter = Completer<Chrome>();
}
@visibleForTesting
void launchChromeInstance(Chrome chrome) {
ChromeLauncher._currentCompleter.complete(chrome);
/// Find the Microsoft Edge executable on the current platform.
///
/// Does not verify whether the executable exists.
String findEdgeExecutable(Platform platform, FileSystem fileSystem) {
if (platform.environment.containsKey(kEdgeEnvironment)) {
return platform.environment[kEdgeEnvironment];
}
if (platform.isWindows) {
/// The possible locations where the Edge executable can be located on windows.
final List<String> kWindowsPrefixes = <String>[
platform.environment['LOCALAPPDATA'],
platform.environment['PROGRAMFILES'],
platform.environment['PROGRAMFILES(X86)'],
];
final String windowsPrefix = kWindowsPrefixes.firstWhere((String prefix) {
if (prefix == null) {
return false;
}
final String path = fileSystem.path.join(prefix, kWindowsEdgeExecutable);
return fileSystem.file(path).existsSync();
}, orElse: () => '.');
return fileSystem.path.join(windowsPrefix, kWindowsEdgeExecutable);
}
// Not yet supported for macOS and Linux.
return '';
}
/// Responsible for launching chrome with devtools configured.
class ChromeLauncher {
const ChromeLauncher({
/// A launcher for Chromium browsers with devtools configured.
class ChromiumLauncher {
ChromiumLauncher({
@required FileSystem fileSystem,
@required Platform platform,
@required ProcessManager processManager,
@required OperatingSystemUtils operatingSystemUtils,
@required Logger logger,
@required BrowserFinder browserFinder,
}) : _fileSystem = fileSystem,
_platform = platform,
_processManager = processManager,
_operatingSystemUtils = operatingSystemUtils,
_logger = logger;
_logger = logger,
_browserFinder = browserFinder,
_fileSystemUtils = FileSystemUtils(
fileSystem: fileSystem,
platform: platform,
);
final FileSystem _fileSystem;
final Platform _platform;
final ProcessManager _processManager;
final OperatingSystemUtils _operatingSystemUtils;
final Logger _logger;
final BrowserFinder _browserFinder;
final FileSystemUtils _fileSystemUtils;
static bool get hasChromeInstance => _currentCompleter.isCompleted;
bool get hasChromeInstance => _currentCompleter.isCompleted;
static Completer<Chrome> _currentCompleter = Completer<Chrome>();
Completer<Chromium> _currentCompleter = Completer<Chromium>();
@visibleForTesting
void testLaunchChromium(Chromium chromium) {
_currentCompleter.complete(chromium);
}
/// Whether we can locate the chrome executable.
bool canFindChrome() {
final String chrome = findChromeExecutable(_platform, _fileSystem);
bool canFindExecutable() {
final String chrome = _browserFinder(_platform, _fileSystem);
try {
return _processManager.canRun(chrome);
} on ArgumentError {
......@@ -107,7 +144,10 @@ class ChromeLauncher {
}
}
/// Launch the chrome browser to a particular `host` page.
/// The executable this launcher will use.
String findExecutable() => _browserFinder(_platform, _fileSystem);
/// Launch a Chromium browser to a particular `host` page.
///
/// `headless` defaults to false, and controls whether we open a headless or
/// a `headfull` browser.
......@@ -116,13 +156,19 @@ class ChromeLauncher {
/// port is picked automatically.
///
/// `skipCheck` does not attempt to make a devtools connection before returning.
Future<Chrome> launch(String url, { bool headless = false, int debugPort, bool skipCheck = false, Directory cacheDir }) async {
Future<Chromium> launch(String url, {
bool headless = false,
int debugPort,
bool skipCheck = false,
Directory cacheDir,
}) async {
if (_currentCompleter.isCompleted) {
throwToolExit('Only one instance of chrome can be started.');
}
final String chromeExecutable = findChromeExecutable(_platform, _fileSystem);
final Directory userDataDir = _fileSystem.systemTempDirectory.createTempSync('flutter_tools_chrome_device.');
final String chromeExecutable = _browserFinder(_platform, _fileSystem);
final Directory userDataDir = _fileSystem.systemTempDirectory
.createTempSync('flutter_tools_chrome_device.');
if (cacheDir != null) {
// Seed data dir with previous state.
......@@ -148,7 +194,12 @@ class ChromeLauncher {
'--disable-default-apps',
'--disable-translate',
if (headless)
...<String>['--headless', '--disable-gpu', '--no-sandbox', '--window-size=2400,1800'],
...<String>[
'--headless',
'--disable-gpu',
'--no-sandbox',
'--window-size=2400,1800',
],
url,
];
......@@ -180,12 +231,13 @@ class ChromeLauncher {
return 'Failed to spawn stderr';
});
final Uri remoteDebuggerUri = await _getRemoteDebuggerUrl(Uri.parse('http://localhost:$port'));
return _connect(Chrome._(
return _connect(Chromium._(
port,
ChromeConnection('localhost', port),
url: url,
process: process,
remoteDebuggerUri: remoteDebuggerUri,
chromiumLauncher: this,
), skipCheck);
}
......@@ -217,7 +269,7 @@ class ChromeLauncher {
if (sourceLocalStorageDir.existsSync()) {
targetLocalStorageDir.createSync(recursive: true);
globals.fsUtils.copyDirectorySync(sourceLocalStorageDir, targetLocalStorageDir);
_fileSystemUtils.copyDirectorySync(sourceLocalStorageDir, targetLocalStorageDir);
}
}
......@@ -236,11 +288,11 @@ class ChromeLauncher {
if (sourceLocalStorageDir.existsSync()) {
targetLocalStorageDir.createSync(recursive: true);
globals.fsUtils.copyDirectorySync(sourceLocalStorageDir, targetLocalStorageDir);
_fileSystemUtils.copyDirectorySync(sourceLocalStorageDir, targetLocalStorageDir);
}
}
static Future<Chrome> _connect(Chrome chrome, bool skipCheck) async {
Future<Chromium> _connect(Chromium chrome, bool skipCheck) async {
// The connection is lazy. Try a simple call to make sure the provided
// connection is valid.
if (!skipCheck) {
......@@ -256,7 +308,7 @@ class ChromeLauncher {
return chrome;
}
static Future<Chrome> get connectedInstance => _currentCompleter.future;
Future<Chromium> get connectedInstance => _currentCompleter.future;
/// Returns the full URL of the Chrome remote debugger for the main page.
///
......@@ -281,27 +333,30 @@ class ChromeLauncher {
}
}
/// A class for managing an instance of Chrome.
class Chrome {
Chrome._(
/// A class for managing an instance of a Chromium browser.
class Chromium {
Chromium._(
this.debugPort,
this.chromeConnection, {
this.url,
Process process,
this.remoteDebuggerUri,
}) : _process = process;
@required ChromiumLauncher chromiumLauncher,
}) : _process = process,
_chromiumLauncher = chromiumLauncher;
final String url;
final int debugPort;
final Process _process;
final ChromeConnection chromeConnection;
final Uri remoteDebuggerUri;
final ChromiumLauncher _chromiumLauncher;
Future<int> get onExit => _process.exitCode;
Future<void> close() async {
if (ChromeLauncher.hasChromeInstance) {
ChromeLauncher._currentCompleter = Completer<Chrome>();
if (_chromiumLauncher.hasChromeInstance) {
_chromiumLauncher._currentCompleter = Completer<Chromium>();
}
chromeConnection.close();
_process?.kill();
......
......@@ -3,14 +3,17 @@
// found in the LICENSE file.
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import '../application_package.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/os.dart';
import '../build_info.dart';
import '../device.dart';
import '../features.dart';
import '../globals.dart' as globals;
import '../project.dart';
import 'chrome.dart';
......@@ -26,16 +29,29 @@ class WebApplicationPackage extends ApplicationPackage {
Directory get webSourcePath => flutterProject.directory.childDirectory('web');
}
class ChromeDevice extends Device {
ChromeDevice() : super(
'chrome',
/// A web device that supports a chromium browser.
abstract class ChromiumDevice extends Device {
ChromiumDevice({
@required String name,
@required this.chromeLauncher,
@required FileSystem fileSystem,
@required Logger logger,
}) : _fileSystem = fileSystem,
_logger = logger,
super(
name,
category: Category.web,
platformType: PlatformType.web,
ephemeral: false,
);
final ChromiumLauncher chromeLauncher;
final FileSystem _fileSystem;
final Logger _logger;
/// The active chrome instance.
Chrome _chrome;
Chromium _chrome;
// TODO(jonahwilliams): this is technically false, but requires some refactoring
// to allow hot mode restart only devices.
......@@ -83,47 +99,11 @@ class ChromeDevice extends Device {
Future<String> get emulatorId async => null;
@override
bool isSupported() => featureFlags.isWebEnabled && globals.chromeLauncher.canFindChrome();
@override
String get name => 'Chrome';
bool isSupported() => chromeLauncher.canFindExecutable();
@override
DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder();
@override
Future<String> get sdkNameAndVersion async => _sdkNameAndVersion ??= await _computeSdkNameAndVersion();
String _sdkNameAndVersion;
Future<String> _computeSdkNameAndVersion() async {
if (!isSupported()) {
return 'unknown';
}
// See https://bugs.chromium.org/p/chromium/issues/detail?id=158372
String version = 'unknown';
if (globals.platform.isWindows) {
final ProcessResult result = await globals.processManager.run(<String>[
r'reg', 'query', r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon', '/v', 'version',
]);
if (result.exitCode == 0) {
final List<String> parts = (result.stdout as String).split(RegExp(r'\s+'));
if (parts.length > 2) {
version = 'Google Chrome ' + parts[parts.length - 2];
}
}
} else {
final String chrome = findChromeExecutable(globals.platform, globals.fs);
final ProcessResult result = await globals.processManager.run(<String>[
chrome,
'--version',
]);
if (result.exitCode == 0) {
version = result.stdout as String;
}
}
return version.trim();
}
@override
Future<LaunchResult> startApp(
covariant WebApplicationPackage package, {
......@@ -139,9 +119,9 @@ class ChromeDevice extends Device {
final String url = platformArgs['uri'] as String;
final bool launchChrome = platformArgs['no-launch-chrome'] != true;
if (launchChrome) {
_chrome = await globals.chromeLauncher.launch(
_chrome = await chromeLauncher.launch(
url,
cacheDir: globals.fs.currentDirectory
cacheDir: _fileSystem.currentDirectory
.childDirectory('.dart_tool')
.childDirectory('chrome-device'),
headless: debuggingOptions.webRunHeadless,
......@@ -149,7 +129,7 @@ class ChromeDevice extends Device {
);
}
globals.logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': launchChrome});
_logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': launchChrome});
return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null);
}
......@@ -177,27 +157,140 @@ class ChromeDevice extends Device {
}
}
/// The Google Chrome browser based on Chromium.
class GoogleChromeDevice extends ChromiumDevice {
GoogleChromeDevice({
@required Platform platform,
@required ProcessManager processManager,
@required ChromiumLauncher chromiumLauncher,
@required Logger logger,
@required FileSystem fileSystem,
}) : _platform = platform,
_processManager = processManager,
super(
name: 'chrome',
chromeLauncher: chromiumLauncher,
logger: logger,
fileSystem: fileSystem,
);
final Platform _platform;
final ProcessManager _processManager;
@override
String get name => 'Chrome';
@override
Future<String> get sdkNameAndVersion async => _sdkNameAndVersion ??= await _computeSdkNameAndVersion();
String _sdkNameAndVersion;
Future<String> _computeSdkNameAndVersion() async {
if (!isSupported()) {
return 'unknown';
}
// See https://bugs.chromium.org/p/chromium/issues/detail?id=158372
String version = 'unknown';
if (_platform.isWindows) {
final ProcessResult result = await _processManager.run(<String>[
r'reg', 'query', r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon', '/v', 'version',
]);
if (result.exitCode == 0) {
final List<String> parts = (result.stdout as String).split(RegExp(r'\s+'));
if (parts.length > 2) {
version = 'Google Chrome ' + parts[parts.length - 2];
}
}
} else {
final String chrome = chromeLauncher.findExecutable();
final ProcessResult result = await _processManager.run(<String>[
chrome,
'--version',
]);
if (result.exitCode == 0) {
version = result.stdout as String;
}
}
return version.trim();
}
}
/// The Microsoft Edge browser based on Chromium.
// This is not currently used, see https://github.com/flutter/flutter/issues/55322
class MicrosoftEdgeDevice extends ChromiumDevice {
MicrosoftEdgeDevice({
@required ChromiumLauncher chromiumLauncher,
@required Logger logger,
@required FileSystem fileSystem,
}) : super(
name: 'edge',
chromeLauncher: chromiumLauncher,
logger: logger,
fileSystem: fileSystem,
);
@override
String get name => 'Edge';
@override
Future<String> get sdkNameAndVersion async => '<?>';
}
class WebDevices extends PollingDeviceDiscovery {
WebDevices() : super('chrome');
WebDevices({
@required FileSystem fileSystem,
@required Logger logger,
@required Platform platform,
@required ProcessManager processManager,
@required FeatureFlags featureFlags,
}) : _featureFlags = featureFlags,
super('Chrome') {
final OperatingSystemUtils operatingSystemUtils = OperatingSystemUtils(
fileSystem: fileSystem,
platform: platform,
logger: logger,
processManager: processManager,
);
_chromeDevice = GoogleChromeDevice(
fileSystem: fileSystem,
logger: logger,
platform: platform,
processManager: processManager,
chromiumLauncher: ChromiumLauncher(
browserFinder: findChromeExecutable,
fileSystem: fileSystem,
logger: logger,
platform: platform,
processManager: processManager,
operatingSystemUtils: operatingSystemUtils,
),
);
_webServerDevice = WebServerDevice(
logger: logger,
);
}
final bool _chromeIsAvailable = globals.chromeLauncher.canFindChrome();
final ChromeDevice _webDevice = ChromeDevice();
final WebServerDevice _webServerDevice = WebServerDevice();
GoogleChromeDevice _chromeDevice;
WebServerDevice _webServerDevice;
final FeatureFlags _featureFlags;
@override
bool get canListAnything => featureFlags.isWebEnabled;
@override
Future<List<Device>> pollingGetDevices({ Duration timeout }) async {
if (!_featureFlags.isWebEnabled) {
return <Device>[];
}
return <Device>[
if (_chromeIsAvailable)
_webDevice,
_webServerDevice,
if (_chromeDevice.isSupported())
_chromeDevice,
];
}
@override
bool get supportsPlatform => featureFlags.isWebEnabled;
bool get supportsPlatform => _featureFlags.isWebEnabled;
}
@visibleForTesting
......@@ -208,13 +301,18 @@ String parseVersionForWindows(String input) {
/// A special device type to allow serving for arbitrary browsers.
class WebServerDevice extends Device {
WebServerDevice() : super(
WebServerDevice({
@required Logger logger,
}) : _logger = logger,
super(
'web-server',
platformType: PlatformType.web,
category: Category.web,
ephemeral: false,
);
final Logger _logger;
@override
void clearLogs() { }
......@@ -244,7 +342,7 @@ class WebServerDevice extends Device {
Future<bool> get isLocalEmulator async => false;
@override
bool isSupported() => featureFlags.isWebEnabled;
bool isSupported() => true;
@override
bool isSupportedForProject(FlutterProject flutterProject) {
......@@ -271,11 +369,11 @@ class WebServerDevice extends Device {
}) async {
final String url = platformArgs['uri'] as String;
if (debuggingOptions.startPaused) {
globals.printStatus('Waiting for connection from Dart debug extension at $url', emphasis: true);
_logger.printStatus('Waiting for connection from Dart debug extension at $url', emphasis: true);
} else {
globals.printStatus('$mainPath is being served at $url', emphasis: true);
_logger.printStatus('$mainPath is being served at $url', emphasis: true);
}
globals.logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': false});
_logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': false});
return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null);
}
......
......@@ -5,47 +5,39 @@
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import '../base/file_system.dart';
import '../doctor.dart';
import 'chrome.dart';
/// A validator that checks whether chrome is installed and can run.
class WebValidator extends DoctorValidator {
const WebValidator({
@required Platform platform,
@required ChromeLauncher chromeLauncher,
@required FileSystem fileSystem,
}) : _platform = platform,
_chromeLauncher = chromeLauncher,
_fileSystem = fileSystem,
super('Chrome - develop for the web');
/// A validator for Chromium-based brosers.
abstract class ChromiumValidator extends DoctorValidator {
const ChromiumValidator(String title) : super(title);
final Platform _platform;
final ChromeLauncher _chromeLauncher;
final FileSystem _fileSystem;
Platform get _platform;
ChromiumLauncher get _chromiumLauncher;
String get _name;
@override
Future<ValidationResult> validate() async {
final String chrome = findChromeExecutable(_platform, _fileSystem);
final bool canRunChrome = _chromeLauncher.canFindChrome();
final bool canRunChromium = _chromiumLauncher.canFindExecutable();
final String chromimSearchLocation = _chromiumLauncher.findExecutable();
final List<ValidationMessage> messages = <ValidationMessage>[
if (_platform.environment.containsKey(kChromeEnvironment))
if (!canRunChrome)
ValidationMessage.hint('$chrome is not executable.')
if (!canRunChromium)
ValidationMessage.hint('$chromimSearchLocation is not executable.')
else
ValidationMessage('$kChromeEnvironment = $chrome')
ValidationMessage('$kChromeEnvironment = $chromimSearchLocation')
else
if (!canRunChrome)
const ValidationMessage.hint('Cannot find Chrome. Try setting '
'$kChromeEnvironment to a Chrome executable.')
if (!canRunChromium)
ValidationMessage.hint('Cannot find $_name. Try setting '
'$kChromeEnvironment to a $_name executable.')
else
ValidationMessage('Chrome at $chrome'),
ValidationMessage('$_name at $chromimSearchLocation'),
];
if (!canRunChrome) {
if (!canRunChromium) {
return ValidationResult(
ValidationType.missing,
messages,
statusInfo: 'Cannot find chrome executable at $chrome',
statusInfo: 'Cannot find $_name executable at $chromimSearchLocation',
);
}
return ValidationResult(
......@@ -54,3 +46,42 @@ class WebValidator extends DoctorValidator {
);
}
}
/// A validator that checks whether Chrome is installed and can run.
class ChromeValidator extends ChromiumValidator {
const ChromeValidator({
@required Platform platform,
@required ChromiumLauncher chromiumLauncher,
}) : _platform = platform,
_chromiumLauncher = chromiumLauncher,
super('Chrome - develop for the web');
@override
final Platform _platform;
@override
final ChromiumLauncher _chromiumLauncher;
@override
String get _name => 'Chrome';
}
/// A validator that checks whethere Edge is installed and can run.
// This is not currently used, see https://github.com/flutter/flutter/issues/55322
class EdgeValidator extends ChromiumValidator {
const EdgeValidator({
@required Platform platform,
@required ChromiumLauncher chromiumLauncher,
}) : _platform = platform,
_chromiumLauncher = chromiumLauncher,
super('Edge - develop for the web');
@override
final Platform _platform;
@override
final ChromiumLauncher _chromiumLauncher;
@override
String get _name => 'Edge';
}
......@@ -4,54 +4,55 @@
import 'dart:convert';
import 'package:file/file.dart';
import 'package:args/command_runner.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/web/chrome.dart';
import 'package:flutter_tools/src/commands/devices.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/config.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/web/web_device.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/testbed.dart';
void main() {
group('devices', () {
Directory configDir;
Config config;
tearDown(() {
if (configDir != null) {
tryToDelete(configDir);
configDir = null;
}
});
setUpAll(() {
Cache.disableLocking();
configDir ??= globals.fs.systemTempDirectory.createTempSync(
'flutter_config_dir_test.',
});
testUsingContext('devices can display no connected devices with the --machine flag', () async {
final BufferLogger logger = context.get<Logger>() as BufferLogger;
final DevicesCommand command = DevicesCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['devices', '--machine']);
expect(
json.decode(logger.statusText),
isEmpty,
);
config = Config.test(
Config.kFlutterSettings,
directory: configDir,
logger: globals.logger,
)..setValue('enable-web', true);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWebEnabled: false),
});
testUsingContext('devices can display via the --machine flag', () async {
when(deviceManager.refreshAllConnectedDevices()).thenAnswer((Invocation invocation) async {
return <Device>[
WebServerDevice(logger: BufferLogger.test()),
];
});
// Test assumes no devices connected.
// Should return only `web-server` device
testUsingContext('Test the --machine flag', () async {
final BufferLogger logger = context.get<Logger>() as BufferLogger;
final DevicesCommand command = DevicesCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['devices', '--machine']);
expect(
json.decode(logger.statusText),
<Map<String,Object>>[
contains(equals(
<String, Object>{
'name': 'Web Server',
'id': 'web-server',
......@@ -69,27 +70,14 @@ void main() {
'startPaused': true
}
}
]
)),
);
},
overrides: <Type, Generator>{
DeviceManager: () => DeviceManager(),
Config: () => config,
ChromeLauncher: () => _DisabledChromeLauncher(),
});
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
DeviceManager: () => MockDeviceManager(),
});
}
// Without ChromeLauncher DeviceManager constructor fails with noSuchMethodError
// trying to call canFindChrome on null
// Also, Chrome may have different versions on different machines and
// JSON will not match, because the `sdk` field of the Device contains version number
// Mock the launcher to make it appear that we don't have Chrome.
class _DisabledChromeLauncher implements ChromeLauncher {
@override
bool canFindChrome() => false;
class MockDeviceManager extends Mock implements DeviceManager {
@override
Future<Chrome> launch(String url, {bool headless = false, int debugPort, bool skipCheck = false, Directory cacheDir})
=> Future<Chrome>.error('Chrome disabled');
}
\ No newline at end of file
......@@ -44,7 +44,7 @@ void main() {
test('doctor validators includes web when feature is enabled', () => testbed.run(() {
expect(DoctorValidatorsProvider.defaultInstance.validators,
contains(isA<WebValidator>()));
contains(isA<ChromiumValidator>()));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(
isWebEnabled: true,
......@@ -53,7 +53,7 @@ void main() {
test('doctor validators does not include web when feature is disabled', () => testbed.run(() {
expect(DoctorValidatorsProvider.defaultInstance.validators,
isNot(contains(isA<WebValidator>())));
isNot(contains(isA<ChromiumValidator>())));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(
isWebEnabled: false,
......
......@@ -131,14 +131,18 @@ void main() {
final MockChromeConnection mockChromeConnection = MockChromeConnection();
final MockChromeTab mockChromeTab = MockChromeTab();
final MockWipConnection mockWipConnection = MockWipConnection();
final MockChromiumLauncher chromiumLauncher = MockChromiumLauncher();
when(mockChromeConnection.getTab(any)).thenAnswer((Invocation invocation) async {
return mockChromeTab;
});
when(mockChromeTab.connect()).thenAnswer((Invocation invocation) async {
return mockWipConnection;
});
when(chromiumLauncher.connectedInstance).thenAnswer((Invocation invocation) async {
return chrome;
});
when(chrome.chromeConnection).thenReturn(mockChromeConnection);
launchChromeInstance(chrome);
when(chromeDevice.chromeLauncher).thenReturn(chromiumLauncher);
when(mockFlutterDevice.device).thenReturn(chromeDevice);
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
......@@ -163,10 +167,11 @@ class MockDebugConnection extends Mock implements DebugConnection {}
class MockVmService extends Mock implements VmService {}
class MockStatus extends Mock implements Status {}
class MockFlutterDevice extends Mock implements FlutterDevice {}
class MockChromeDevice extends Mock implements ChromeDevice {}
class MockChrome extends Mock implements Chrome {}
class MockChromeDevice extends Mock implements ChromiumDevice {}
class MockChrome extends Mock implements Chromium {}
class MockChromeConnection extends Mock implements ChromeConnection {}
class MockChromeTab extends Mock implements ChromeTab {}
class MockWipConnection extends Mock implements WipConnection {}
class MockBuildSystem extends Mock implements BuildSystem {}
class MockPub extends Mock implements Pub {}
class MockChromiumLauncher extends Mock implements ChromiumLauncher {}
......@@ -93,7 +93,6 @@ void main() {
FakeVmServiceHost fakeVmServiceHost;
setUp(() {
resetChromeForTesting();
mockDebugConnection = MockDebugConnection();
mockDevice = MockDevice();
mockAppConnection = MockAppConnection();
......@@ -173,8 +172,10 @@ void main() {
}
test('runner with web server device does not support debugging without --start-paused', () => testbed.run(() {
when(mockFlutterDevice.device).thenReturn(WebServerDevice(
logger: BufferLogger.test(),
));
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
when(mockFlutterDevice.device).thenReturn(WebServerDevice());
final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
flutterProject: FlutterProject.current(),
......@@ -194,7 +195,9 @@ void main() {
test('runner with web server device supports debugging with --start-paused', () => testbed.run(() {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
_setupMocks();
when(mockFlutterDevice.device).thenReturn(WebServerDevice());
when(mockFlutterDevice.device).thenReturn(WebServerDevice(
logger: BufferLogger.test(),
));
final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
flutterProject: FlutterProject.current(),
......@@ -355,7 +358,23 @@ void main() {
),
]);
_setupMocks();
launchChromeInstance(mockChrome);
final ChromiumLauncher chromiumLauncher = MockChromeLauncher();
when(chromiumLauncher.launch(any, cacheDir: anyNamed('cacheDir')))
.thenAnswer((Invocation invocation) async {
return mockChrome;
});
when(chromiumLauncher.connectedInstance).thenAnswer((Invocation invocation) async {
return mockChrome;
});
when(mockFlutterDevice.device).thenReturn(GoogleChromeDevice(
fileSystem: globals.fs,
chromiumLauncher: chromiumLauncher,
logger: globals.logger,
platform: FakePlatform(operatingSystem: 'linux'),
processManager: FakeProcessManager.any(),
));
when(chromiumLauncher.canFindExecutable()).thenReturn(true);
chromiumLauncher.testLaunchChromium(mockChrome);
when(mockWebDevFS.update(
mainUri: anyNamed('mainUri'),
target: anyNamed('target'),
......@@ -395,7 +414,7 @@ void main() {
expect(config, allOf(<Matcher>[
containsPair('cd27', 'web-javascript'),
containsPair('cd28', null),
containsPair('cd28', ''),
containsPair('cd29', 'false'),
containsPair('cd30', 'true'),
]));
......@@ -417,7 +436,23 @@ void main() {
),
]);
_setupMocks();
launchChromeInstance(mockChrome);
final ChromiumLauncher chromiumLauncher = MockChromeLauncher();
when(chromiumLauncher.launch(any, cacheDir: anyNamed('cacheDir')))
.thenAnswer((Invocation invocation) async {
return mockChrome;
});
when(chromiumLauncher.connectedInstance).thenAnswer((Invocation invocation) async {
return mockChrome;
});
when(chromiumLauncher.canFindExecutable()).thenReturn(true);
when(mockFlutterDevice.device).thenReturn(GoogleChromeDevice(
fileSystem: globals.fs,
chromiumLauncher: chromiumLauncher,
logger: globals.logger,
platform: FakePlatform(operatingSystem: 'linux'),
processManager: FakeProcessManager.any(),
));
chromiumLauncher.testLaunchChromium(mockChrome);
Uri entrypointFileUri;
when(mockWebDevFS.update(
mainUri: anyNamed('mainUri'),
......@@ -461,7 +496,7 @@ void main() {
expect(config, allOf(<Matcher>[
containsPair('cd27', 'web-javascript'),
containsPair('cd28', null),
containsPair('cd28', ''),
containsPair('cd29', 'false'),
containsPair('cd30', 'true'),
]));
......@@ -1062,7 +1097,22 @@ void main() {
)
]);
_setupMocks();
when(mockFlutterDevice.device).thenReturn(ChromeDevice());
final ChromiumLauncher chromiumLauncher = MockChromeLauncher();
when(chromiumLauncher.launch(any, cacheDir: anyNamed('cacheDir')))
.thenAnswer((Invocation invocation) async {
return mockChrome;
});
when(chromiumLauncher.connectedInstance).thenAnswer((Invocation invocation) async {
return mockChrome;
});
when(mockFlutterDevice.device).thenReturn(GoogleChromeDevice(
fileSystem: globals.fs,
chromiumLauncher: chromiumLauncher,
logger: globals.logger,
platform: FakePlatform(operatingSystem: 'linux'),
processManager: FakeProcessManager.any(),
));
when(chromiumLauncher.canFindExecutable()).thenReturn(true);
when(mockWebDevFS.create()).thenAnswer((Invocation invocation) async {
return Uri.parse('http://localhost:8765/app/');
});
......@@ -1077,7 +1127,7 @@ void main() {
return mockWipConnection;
});
when(chrome.chromeConnection).thenReturn(mockChromeConnection);
launchChromeInstance(chrome);
chromiumLauncher.testLaunchChromium(chrome);
final DelegateLogger delegateLogger = globals.logger as DelegateLogger;
final MockStatus mockStatus = MockStatus();
......@@ -1110,13 +1160,14 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
Logger: () => DelegateLogger(BufferLogger.test()),
ChromeLauncher: () => MockChromeLauncher(),
}));
test('Sends unlaunched app.webLaunchUrl event for Web Server device', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
_setupMocks();
when(mockFlutterDevice.device).thenReturn(WebServerDevice());
when(mockFlutterDevice.device).thenReturn(WebServerDevice(
logger: globals.logger,
));
when(mockWebDevFS.create()).thenAnswer((Invocation invocation) async {
return Uri.parse('http://localhost:8765/app/');
});
......@@ -1219,9 +1270,9 @@ void main() {
}));
}
class MockChromeLauncher extends Mock implements ChromeLauncher {}
class MockChromeLauncher extends Mock implements ChromiumLauncher {}
class MockFlutterUsage extends Mock implements Usage {}
class MockChromeDevice extends Mock implements ChromeDevice {}
class MockChromeDevice extends Mock implements ChromiumDevice {}
class MockDebugConnection extends Mock implements DebugConnection {}
class MockAppConnection extends Mock implements AppConnection {}
class MockVmService extends Mock implements VmService {}
......@@ -1229,7 +1280,7 @@ class MockStatus extends Mock implements Status {}
class MockFlutterDevice extends Mock implements FlutterDevice {}
class MockWebDevFS extends Mock implements WebDevFS {}
class MockResidentCompiler extends Mock implements ResidentCompiler {}
class MockChrome extends Mock implements Chrome {}
class MockChrome extends Mock implements Chromium {}
class MockChromeConnection extends Mock implements ChromeConnection {}
class MockChromeTab extends Mock implements ChromeTab {}
class MockWipConnection extends Mock implements WipConnection {}
......
......@@ -5,7 +5,7 @@
import 'dart:async';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:file_testing/file_testing.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';
......@@ -16,7 +16,7 @@ import 'package:platform/platform.dart';
import '../../src/common.dart';
import '../../src/context.dart';
const List<String> _kChromeArgs = <String>[
const List<String> kChromeArgs = <String>[
'--disable-background-timer-throttling',
'--disable-extensions',
'--disable-popup-blocking',
......@@ -30,7 +30,7 @@ const List<String> _kChromeArgs = <String>[
const String kDevtoolsStderr = '\n\nDevTools listening\n\n';
void main() {
ChromeLauncher chromeLauncher;
ChromiumLauncher chromeLauncher;
FileSystem fileSystem;
Platform platform;
FakeProcessManager processManager;
......@@ -49,66 +49,91 @@ void main() {
});
fileSystem = MemoryFileSystem.test();
processManager = FakeProcessManager.list(<FakeCommand>[]);
chromeLauncher = ChromeLauncher(
chromeLauncher = ChromiumLauncher(
fileSystem: fileSystem,
platform: platform,
processManager: processManager,
operatingSystemUtils: operatingSystemUtils,
logger: logger,
browserFinder: findChromeExecutable,
);
});
tearDown(() {
resetChromeForTesting();
testWithoutContext('can launch chrome and connect to the devtools', () async {
expect(
() async => await testLaunchChrome(
'/.tmp_rand0/flutter_tools_chrome_device.rand0',
processManager,
chromeLauncher,
),
returnsNormally,
);
});
test('can launch chrome and connect to the devtools', () async {
await testLaunchChrome('/.tmp_rand0/flutter_tools_chrome_device.rand0', processManager, chromeLauncher);
});
testWithoutContext('cannot have two concurrent instances of chrome', () async {
await testLaunchChrome(
'/.tmp_rand0/flutter_tools_chrome_device.rand0',
processManager,
chromeLauncher,
);
test('cannot have two concurrent instances of chrome', () async {
await testLaunchChrome('/.tmp_rand0/flutter_tools_chrome_device.rand0', processManager, chromeLauncher);
bool pass = false;
try {
await testLaunchChrome('/.tmp_rand0/flutter_tools_chrome_device.rand1', processManager, chromeLauncher);
} on ToolExit catch (_) {
pass = true;
}
expect(pass, isTrue);
expect(
() async => await testLaunchChrome(
'/.tmp_rand0/flutter_tools_chrome_device.rand1',
processManager,
chromeLauncher,
),
throwsToolExit(),
);
});
test('can launch new chrome after stopping a previous chrome', () async {
final Chrome chrome = await testLaunchChrome('/.tmp_rand0/flutter_tools_chrome_device.rand0', processManager, chromeLauncher);
testWithoutContext('can launch new chrome after stopping a previous chrome', () async {
final Chromium chrome = await testLaunchChrome(
'/.tmp_rand0/flutter_tools_chrome_device.rand0',
processManager,
chromeLauncher,
);
await chrome.close();
await testLaunchChrome('/.tmp_rand0/flutter_tools_chrome_device.rand1', processManager, chromeLauncher);
expect(
() async => await testLaunchChrome(
'/.tmp_rand0/flutter_tools_chrome_device.rand1',
processManager,
chromeLauncher,
),
returnsNormally,
);
});
test('can launch chrome with a custom debug port', () async {
testWithoutContext('can launch chrome with a custom debug port', () async {
processManager.addCommand(const FakeCommand(
command: <String>[
'example_chrome',
'--user-data-dir=/.tmp_rand1/flutter_tools_chrome_device.rand1',
'--remote-debugging-port=10000',
..._kChromeArgs,
...kChromeArgs,
'example_url',
],
stderr: kDevtoolsStderr,
));
await chromeLauncher.launch(
expect(
() async => await chromeLauncher.launch(
'example_url',
skipCheck: true,
debugPort: 10000,
),
returnsNormally,
);
});
test('can launch chrome headless', () async {
testWithoutContext('can launch chrome headless', () async {
processManager.addCommand(const FakeCommand(
command: <String>[
'example_chrome',
'--user-data-dir=/.tmp_rand1/flutter_tools_chrome_device.rand1',
'--remote-debugging-port=1234',
..._kChromeArgs,
...kChromeArgs,
'--headless',
'--disable-gpu',
'--no-sandbox',
......@@ -118,14 +143,17 @@ void main() {
stderr: kDevtoolsStderr,
));
await chromeLauncher.launch(
expect(
() async => await chromeLauncher.launch(
'example_url',
skipCheck: true,
headless: true,
),
returnsNormally,
);
});
test('can seed chrome temp directory with existing session data', () async {
testWithoutContext('can seed chrome temp directory with existing session data', () async {
final Completer<void> exitCompleter = Completer<void>.sync();
final Directory dataDir = fileSystem.directory('chrome-stuff');
......@@ -148,7 +176,7 @@ void main() {
'example_chrome',
'--user-data-dir=/.tmp_rand1/flutter_tools_chrome_device.rand1',
'--remote-debugging-port=1234',
..._kChromeArgs,
...kChromeArgs,
'example_url',
], completer: exitCompleter));
......@@ -183,23 +211,23 @@ void main() {
expect(storageDir.existsSync(), true);
expect(storageDir.childFile('LOCK').existsSync(), true);
expect(storageDir.childFile('LOCK'), exists);
expect(storageDir.childFile('LOCK').readAsBytesSync(), hasLength(0));
expect(storageDir.childFile('LOG').existsSync(), true);
expect(storageDir.childFile('LOG'), exists);
expect(storageDir.childFile('LOG').readAsStringSync(), 'contents');
});
}
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
Future<Chrome> testLaunchChrome(String userDataDir, FakeProcessManager processManager, ChromeLauncher chromeLauncher) {
Future<Chromium> testLaunchChrome(String userDataDir, FakeProcessManager processManager, ChromiumLauncher chromeLauncher) {
processManager.addCommand(FakeCommand(
command: <String>[
'example_chrome',
'--user-data-dir=$userDataDir',
'--remote-debugging-port=1234',
..._kChromeArgs,
...kChromeArgs,
'example_url',
],
stderr: kDevtoolsStderr,
......
......@@ -376,6 +376,7 @@ void main() {
entrypoint: Uri.base,
testMode: true,
expressionCompiler: null,
chromiumLauncher: null,
);
webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true);
......@@ -469,6 +470,7 @@ void main() {
entrypoint: Uri.base,
testMode: true,
expressionCompiler: null,
chromiumLauncher: null,
);
webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true);
......@@ -482,6 +484,7 @@ void main() {
test('Launches DWDS with the correct arguments', () => testbed.run(() async {
globals.fs.file('.packages').writeAsStringSync('\n');
final WebAssetServer server = await WebAssetServer.start(
null,
'any',
8123,
(String url) => null,
......
......@@ -2,36 +2,42 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_tools/src/base/io.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/web/chrome.dart';
import 'package:flutter_tools/src/web/web_device.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import 'package:platform/platform.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/testbed.dart';
void main() {
MockChromeLauncher mockChromeLauncher;
MockPlatform mockPlatform;
MockProcessManager mockProcessManager;
MockWebApplicationPackage mockWebApplicationPackage;
setUp(() async {
mockWebApplicationPackage = MockWebApplicationPackage();
mockProcessManager = MockProcessManager();
mockChromeLauncher = MockChromeLauncher();
mockPlatform = MockPlatform();
when(mockChromeLauncher.launch(any)).thenAnswer((Invocation invocation) async {
return null;
});
when(mockWebApplicationPackage.name).thenReturn('test');
testWithoutContext('No web devices listed if feature is disabled', () async {
final WebDevices webDevices = WebDevices(
featureFlags: TestFeatureFlags(isWebEnabled: false),
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
platform: FakePlatform(
operatingSystem: 'linux',
environment: <String, String>{}
),
processManager: FakeProcessManager.any(),
);
expect(await webDevices.pollingGetDevices(), isEmpty);
});
test('Chrome defaults', () async {
final ChromeDevice chromeDevice = ChromeDevice();
testWithoutContext('GoogleChromeDevice defaults', () async {
final GoogleChromeDevice chromeDevice = GoogleChromeDevice(
chromiumLauncher: null,
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
platform: FakePlatform(operatingSystem: 'linux'),
processManager: FakeProcessManager.any(),
);
expect(chromeDevice.name, 'Chrome');
expect(chromeDevice.id, 'chrome');
......@@ -41,13 +47,35 @@ void main() {
expect(chromeDevice.supportsFlutterExit, true);
expect(chromeDevice.supportsScreenshot, false);
expect(await chromeDevice.isLocalEmulator, false);
expect(chromeDevice.getLogReader(app: mockWebApplicationPackage), isA<NoOpDeviceLogReader>());
expect(chromeDevice.getLogReader(), isA<NoOpDeviceLogReader>());
expect(chromeDevice.getLogReader(), isA<NoOpDeviceLogReader>());
expect(await chromeDevice.portForwarder.forward(1), 1);
});
testWithoutContext('MicrosoftEdge defaults', () async {
final MicrosoftEdgeDevice chromeDevice = MicrosoftEdgeDevice(
chromiumLauncher: null,
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
);
expect(chromeDevice.name, 'Edge');
expect(chromeDevice.id, 'edge');
expect(chromeDevice.supportsHotReload, true);
expect(chromeDevice.supportsHotRestart, true);
expect(chromeDevice.supportsStartPaused, true);
expect(chromeDevice.supportsFlutterExit, true);
expect(chromeDevice.supportsScreenshot, false);
expect(await chromeDevice.isLocalEmulator, false);
expect(chromeDevice.getLogReader(), isA<NoOpDeviceLogReader>());
expect(chromeDevice.getLogReader(), isA<NoOpDeviceLogReader>());
expect(await chromeDevice.portForwarder.forward(1), 1);
});
test('Server defaults', () async {
final WebServerDevice device = WebServerDevice();
testWithoutContext('Server defaults', () async {
final WebServerDevice device = WebServerDevice(
logger: BufferLogger.test(),
);
expect(device.name, 'Web Server');
expect(device.id, 'web-server');
......@@ -57,95 +85,130 @@ void main() {
expect(device.supportsFlutterExit, true);
expect(device.supportsScreenshot, false);
expect(await device.isLocalEmulator, false);
expect(device.getLogReader(app: mockWebApplicationPackage), isA<NoOpDeviceLogReader>());
expect(device.getLogReader(), isA<NoOpDeviceLogReader>());
expect(device.getLogReader(), isA<NoOpDeviceLogReader>());
expect(await device.portForwarder.forward(1), 1);
});
testUsingContext('Chrome device is listed when Chrome is available', () async {
when(mockChromeLauncher.canFindChrome()).thenReturn(true);
final WebDevices deviceDiscoverer = WebDevices();
final List<Device> devices = await deviceDiscoverer.pollingGetDevices();
expect(devices, contains(isA<ChromeDevice>()));
}, overrides: <Type, Generator>{
ChromeLauncher: () => mockChromeLauncher,
testWithoutContext('Chrome device is listed when Chrome can be run', () async {
final WebDevices webDevices = WebDevices(
featureFlags: TestFeatureFlags(isWebEnabled: true),
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
platform: FakePlatform(
operatingSystem: 'linux',
environment: <String, String>{}
),
processManager: FakeProcessManager.any(),
);
expect(await webDevices.pollingGetDevices(),
contains(isA<GoogleChromeDevice>()));
});
testUsingContext('Chrome device is not listed when Chrome is not available', () async {
when(mockChromeLauncher.canFindChrome()).thenReturn(false);
final WebDevices deviceDiscoverer = WebDevices();
final List<Device> devices = await deviceDiscoverer.pollingGetDevices();
expect(devices, isNot(contains(isA<ChromeDevice>())));
}, overrides: <Type, Generator>{
ChromeLauncher: () => mockChromeLauncher,
testWithoutContext('Chrome device is not listed when Chrome cannot be run', () async {
final MockProcessManager processManager = MockProcessManager();
when(processManager.canRun(any)).thenReturn(false);
final WebDevices webDevices = WebDevices(
featureFlags: TestFeatureFlags(isWebEnabled: true),
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
platform: FakePlatform(
operatingSystem: 'linux',
environment: <String, String>{}
),
processManager: processManager,
);
expect(await webDevices.pollingGetDevices(),
isNot(contains(isA<GoogleChromeDevice>())));
});
testUsingContext('Web Server device is listed even when Chrome is not available', () async {
when(mockChromeLauncher.canFindChrome()).thenReturn(false);
final WebDevices deviceDiscoverer = WebDevices();
final List<Device> devices = await deviceDiscoverer.pollingGetDevices();
expect(devices, contains(isA<WebServerDevice>()));
}, overrides: <Type, Generator>{
ChromeLauncher: () => mockChromeLauncher,
testWithoutContext('Web Server device is listed by default', () async {
final WebDevices webDevices = WebDevices(
featureFlags: TestFeatureFlags(isWebEnabled: true),
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
platform: FakePlatform(
operatingSystem: 'linux',
environment: <String, String>{}
),
processManager: FakeProcessManager.any(),
);
expect(await webDevices.pollingGetDevices(),
contains(isA<WebServerDevice>()));
});
testUsingContext('Chrome invokes version command on non-Windows platforms', () async{
when(mockPlatform.isWindows).thenReturn(false);
when(mockProcessManager.canRun('chrome.foo')).thenReturn(true);
when(mockProcessManager.run(<String>['chrome.foo', '--version'])).thenAnswer((Invocation invocation) async {
return MockProcessResult(0, 'ABC');
});
final ChromeDevice chromeDevice = ChromeDevice();
testWithoutContext('Chrome invokes version command on non-Windows platforms', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>[
kLinuxExecutable,
'--version',
],
stdout: 'ABC'
)
]);
final WebDevices webDevices = WebDevices(
featureFlags: TestFeatureFlags(isWebEnabled: true),
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
platform: FakePlatform(
operatingSystem: 'linux',
environment: <String, String>{}
),
processManager: processManager,
);
final GoogleChromeDevice chromeDevice = (await webDevices.pollingGetDevices())
.whereType<GoogleChromeDevice>().first;
expect(chromeDevice.isSupported(), true);
expect(await chromeDevice.sdkNameAndVersion, 'ABC');
// Verify caching works correctly.
expect(await chromeDevice.sdkNameAndVersion, 'ABC');
verify(mockProcessManager.run(<String>['chrome.foo', '--version'])).called(1);
}, overrides: <Type, Generator>{
Platform: () => mockPlatform,
ProcessManager: () => mockProcessManager,
expect(processManager.hasRemainingExpectations, false);
});
testUsingContext('Chrome invokes different version command on windows.', () async {
when(mockPlatform.isWindows).thenReturn(true);
when(mockProcessManager.canRun('chrome.foo')).thenReturn(true);
when(mockProcessManager.run(<String>[
testWithoutContext('Chrome version check invokes registry query on windows.', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>[
'reg',
'query',
r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon',
'/v',
'version',
])).thenAnswer((Invocation invocation) async {
return MockProcessResult(0, r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon\ version REG_SZ 74.0.0 A');
});
final ChromeDevice chromeDevice = ChromeDevice();
],
stdout: r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon\ version REG_SZ 74.0.0 A',
)
]);
final WebDevices webDevices = WebDevices(
featureFlags: TestFeatureFlags(isWebEnabled: true),
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
platform: FakePlatform(
operatingSystem: 'windows',
environment: <String, String>{}
),
processManager: processManager,
);
final GoogleChromeDevice chromeDevice = (await webDevices.pollingGetDevices())
.whereType<GoogleChromeDevice>().first;
expect(chromeDevice.isSupported(), true);
expect(await chromeDevice.sdkNameAndVersion, 'Google Chrome 74.0.0');
}, overrides: <Type, Generator>{
Platform: () => mockPlatform,
ProcessManager: () => mockProcessManager,
// Verify caching works correctly.
expect(await chromeDevice.sdkNameAndVersion, 'Google Chrome 74.0.0');
expect(processManager.hasRemainingExpectations, false);
});
}
class MockChromeLauncher extends Mock implements ChromeLauncher {}
class MockPlatform extends Mock implements Platform {
@override
Map<String, String> environment = <String, String>{'FLUTTER_WEB': 'true', kChromeEnvironment: 'chrome.foo'};
}
// This is used to set `canRun` to false in a test.
class MockProcessManager extends Mock implements ProcessManager {}
class MockProcessResult extends Mock implements ProcessResult {
MockProcessResult(this.exitCode, this.stdout);
@override
final int exitCode;
@override
final String stdout;
}
class MockWebApplicationPackage extends Mock implements WebApplicationPackage {}
......@@ -4,6 +4,7 @@
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/web/chrome.dart';
import 'package:flutter_tools/src/web/web_validator.dart';
......@@ -17,9 +18,9 @@ import '../../src/fake_process_manager.dart';
void main() {
Platform platform;
ProcessManager processManager;
ChromeLauncher chromeLauncher;
ChromiumLauncher chromeLauncher;
FileSystem fileSystem;
WebValidator webValidator;
ChromiumValidator webValidator;
setUp(() {
fileSystem = MemoryFileSystem.test();
......@@ -28,17 +29,17 @@ void main() {
operatingSystem: 'macos',
environment: <String, String>{},
);
chromeLauncher = ChromeLauncher(
chromeLauncher = ChromiumLauncher(
fileSystem: fileSystem,
platform: platform,
processManager: processManager,
operatingSystemUtils: null,
logger: null,
logger: BufferLogger.test(),
browserFinder: findChromeExecutable,
);
webValidator = webValidator = WebValidator(
webValidator = webValidator = ChromeValidator(
platform: platform,
chromeLauncher: chromeLauncher,
fileSystem: fileSystem,
chromiumLauncher: chromeLauncher,
);
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment