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,
ChromeValidator(
chromiumLauncher: ChromiumLauncher(
browserFinder: findChromeExecutable,
fileSystem: globals.fs,
logger: globals.logger,
operatingSystemUtils: globals.os,
platform: globals.platform,
processManager: globals.processManager,
),
platform: globals.platform,
fileSystem: globals.fs,
),
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();
......
......@@ -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,92 +4,80 @@
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;
setUpAll(() {
Cache.disableLocking();
});
tearDown(() {
if (configDir != null) {
tryToDelete(configDir);
configDir = null;
}
});
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']);
setUpAll(() {
Cache.disableLocking();
configDir ??= globals.fs.systemTempDirectory.createTempSync(
'flutter_config_dir_test.',
);
config = Config.test(
Config.kFlutterSettings,
directory: configDir,
logger: globals.logger,
)..setValue('enable-web', true);
expect(
json.decode(logger.statusText),
isEmpty,
);
}, 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>>[
<String, Object>{
'name': 'Web Server',
'id': 'web-server',
'isSupported': true,
'targetPlatform': 'web-javascript',
'emulator': false,
'sdk': 'Flutter Tools',
'capabilities': <String, Object>{
'hotReload': true,
'hotRestart': true,
'screenshot': false,
'fastStart': false,
'flutterExit': true,
'hardwareRendering': false,
'startPaused': true
}
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),
contains(equals(
<String, Object>{
'name': 'Web Server',
'id': 'web-server',
'isSupported': true,
'targetPlatform': 'web-javascript',
'emulator': false,
'sdk': 'Flutter Tools',
'capabilities': <String, Object>{
'hotReload': true,
'hotRestart': true,
'screenshot': false,
'fastStart': false,
'flutterExit': true,
'hardwareRendering': false,
'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(
'example_url',
skipCheck: true,
debugPort: 10000,
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(
'example_url',
skipCheck: true,
headless: true,
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,
......
......@@ -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