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