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

Add "web" server device to allow running flutter for web on arbitrary browsers (#39951)

* add web server device

* remove extra async

* fixes to server device

* testing updates

* fix test cases

* address comments
parent a7aff567
...@@ -43,6 +43,7 @@ dart_library("flutter_tools") { ...@@ -43,6 +43,7 @@ dart_library("flutter_tools") {
"//third_party/dart-pkg/pub/quiver", "//third_party/dart-pkg/pub/quiver",
"//third_party/dart-pkg/pub/shelf_packages_handler", "//third_party/dart-pkg/pub/shelf_packages_handler",
"//third_party/dart-pkg/pub/shelf_static", "//third_party/dart-pkg/pub/shelf_static",
"//third_party/dart-pkg/pub/shelf_proxy",
"//third_party/dart-pkg/pub/stack_trace", "//third_party/dart-pkg/pub/stack_trace",
"//third_party/dart-pkg/pub/test", "//third_party/dart-pkg/pub/test",
"//third_party/dart-pkg/pub/usage", "//third_party/dart-pkg/pub/usage",
......
...@@ -20,6 +20,7 @@ import '../device.dart'; ...@@ -20,6 +20,7 @@ import '../device.dart';
import '../globals.dart'; import '../globals.dart';
import '../project.dart'; import '../project.dart';
import '../resident_runner.dart'; import '../resident_runner.dart';
import '../web/web_device.dart';
import '../web/web_runner.dart'; import '../web/web_runner.dart';
import 'web_fs.dart'; import 'web_fs.dart';
...@@ -63,7 +64,10 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -63,7 +64,10 @@ class ResidentWebRunner extends ResidentRunner {
// Only the debug builds of the web support the service protocol. // Only the debug builds of the web support the service protocol.
@override @override
bool get supportsServiceProtocol => isRunningDebug; bool get supportsServiceProtocol => isRunningDebug && device is! WebServerDevice;
@override
bool get debuggingEnabled => isRunningDebug && device is! WebServerDevice;
WebFs _webFs; WebFs _webFs;
DebugConnection _debugConnection; DebugConnection _debugConnection;
...@@ -103,6 +107,7 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -103,6 +107,7 @@ class ResidentWebRunner extends ResidentRunner {
await _debugConnection?.close(); await _debugConnection?.close();
await _stdOutSub?.cancel(); await _stdOutSub?.cancel();
await _webFs?.stop(); await _webFs?.stop();
await device.stopApp(null);
_exited = true; _exited = true;
} }
...@@ -162,7 +167,11 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -162,7 +167,11 @@ class ResidentWebRunner extends ResidentRunner {
target: target, target: target,
flutterProject: flutterProject, flutterProject: flutterProject,
buildInfo: debuggingOptions.buildInfo, buildInfo: debuggingOptions.buildInfo,
skipDwds: true,
); );
await device.startApp(package, mainPath: target, platformArgs: <String, Object>{
'uri': _webFs.uri
});
if (supportsServiceProtocol) { if (supportsServiceProtocol) {
_debugConnection = await _webFs.runAndDebug(); _debugConnection = await _webFs.runAndDebug();
unawaited(_debugConnection.onDone.whenComplete(exit)); unawaited(_debugConnection.onDone.whenComplete(exit));
...@@ -246,15 +255,13 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -246,15 +255,13 @@ class ResidentWebRunner extends ResidentRunner {
? OperationResult.ok ? OperationResult.ok
: OperationResult(1, reloadResponse.toString()); : OperationResult(1, reloadResponse.toString());
} on vmservice.RPCError { } on vmservice.RPCError {
await _webFs.hardRefresh(); return OperationResult(1, 'Page requires refresh.');
return OperationResult(1, 'Page requires full reload');
} finally { } finally {
status.stop(); status.stop();
} }
} }
// If we're not in hot mode, the only way to restart is to reload the tab.
await _webFs.hardRefresh();
status.stop(); status.stop();
printStatus('Recompile complete. Page requires refresh.');
return OperationResult.ok; return OperationResult.ok;
} }
......
...@@ -16,7 +16,7 @@ import 'package:http_multi_server/http_multi_server.dart'; ...@@ -16,7 +16,7 @@ import 'package:http_multi_server/http_multi_server.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io; import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart' hide StackTrace; import 'package:shelf_proxy/shelf_proxy.dart';
import '../artifacts.dart'; import '../artifacts.dart';
import '../asset.dart'; import '../asset.dart';
...@@ -74,6 +74,7 @@ typedef WebFsFactory = Future<WebFs> Function({ ...@@ -74,6 +74,7 @@ typedef WebFsFactory = Future<WebFs> Function({
@required String target, @required String target,
@required FlutterProject flutterProject, @required FlutterProject flutterProject,
@required BuildInfo buildInfo, @required BuildInfo buildInfo,
@required bool skipDwds,
}); });
/// The dev filesystem responsible for building and serving web applications. /// The dev filesystem responsible for building and serving web applications.
...@@ -83,12 +84,14 @@ class WebFs { ...@@ -83,12 +84,14 @@ class WebFs {
this._client, this._client,
this._server, this._server,
this._dwds, this._dwds,
this._chrome, this.uri,
); );
/// The server uri.
final String uri;
final HttpServer _server; final HttpServer _server;
final Dwds _dwds; final Dwds _dwds;
final Chrome _chrome;
final BuildDaemonClient _client; final BuildDaemonClient _client;
StreamSubscription<void> _connectedApps; StreamSubscription<void> _connectedApps;
...@@ -96,14 +99,13 @@ class WebFs { ...@@ -96,14 +99,13 @@ class WebFs {
Future<void> stop() async { Future<void> stop() async {
await _client.close(); await _client.close();
await _dwds.stop(); await _dwds?.stop();
await _server.close(force: true); await _server.close(force: true);
await _chrome.close();
await _connectedApps?.cancel(); await _connectedApps?.cancel();
} }
/// Retrieve the [DebugConnection] for the current application. /// Retrieve the [DebugConnection] for the current application.
Future<DebugConnection> runAndDebug() async { Future<DebugConnection> runAndDebug() {
final Completer<DebugConnection> firstConnection = Completer<DebugConnection>(); final Completer<DebugConnection> firstConnection = Completer<DebugConnection>();
_connectedApps = _dwds.connectedApps.listen((AppConnection appConnection) async { _connectedApps = _dwds.connectedApps.listen((AppConnection appConnection) async {
appConnection.runMain(); appConnection.runMain();
...@@ -115,18 +117,6 @@ class WebFs { ...@@ -115,18 +117,6 @@ class WebFs {
return firstConnection.future; return firstConnection.future;
} }
/// Perform a hard refresh of all connected browser tabs.
Future<void> hardRefresh() async {
final List<ChromeTab> tabs = await _chrome.chromeConnection.getTabs();
for (ChromeTab tab in tabs) {
if (!tab.url.contains('localhost')) {
continue;
}
final WipConnection connection = await tab.connect();
await connection.sendCommand('Page.reload');
}
}
/// Recompile the web application and return whether this was successful. /// Recompile the web application and return whether this was successful.
Future<bool> recompile() async { Future<bool> recompile() async {
_client.startBuild(); _client.startBuild();
...@@ -148,7 +138,8 @@ class WebFs { ...@@ -148,7 +138,8 @@ class WebFs {
static Future<WebFs> start({ static Future<WebFs> start({
@required String target, @required String target,
@required FlutterProject flutterProject, @required FlutterProject flutterProject,
@required BuildInfo buildInfo @required BuildInfo buildInfo,
@required bool skipDwds,
}) async { }) async {
// workaround for https://github.com/flutter/flutter/issues/38290 // workaround for https://github.com/flutter/flutter/issues/38290
if (!flutterProject.dartTool.existsSync()) { if (!flutterProject.dartTool.existsSync()) {
...@@ -175,21 +166,6 @@ class WebFs { ...@@ -175,21 +166,6 @@ class WebFs {
// Initialize the dwds server. // Initialize the dwds server.
final int port = await os.findFreePort(); final int port = await os.findFreePort();
final Dwds dwds = await dwdsFactory(
hostname: _kHostName,
applicationPort: port,
applicationTarget: kBuildTargetName,
assetServerPort: daemonAssetPort,
buildResults: filteredBuildResults,
chromeConnection: () async {
return (await ChromeLauncher.connectedInstance).chromeConnection;
},
reloadConfiguration: ReloadConfiguration.none,
serveDevTools: true,
verbose: false,
enableDebugExtension: true,
logWriter: (dynamic level, String message) => printTrace(message),
);
// Map the bootstrap files to the correct package directory. // Map the bootstrap files to the correct package directory.
final String targetBaseName = fs.path final String targetBaseName = fs.path
.withoutExtension(target).replaceFirst('lib${fs.path.separator}', ''); .withoutExtension(target).replaceFirst('lib${fs.path.separator}', '');
...@@ -203,7 +179,7 @@ class WebFs { ...@@ -203,7 +179,7 @@ class WebFs {
'${targetBaseName}_web_entrypoint.digests': 'packages/${flutterProject.manifest.appName}/' '${targetBaseName}_web_entrypoint.digests': 'packages/${flutterProject.manifest.appName}/'
'${targetBaseName}_web_entrypoint.digests', '${targetBaseName}_web_entrypoint.digests',
}; };
final Handler handler = const Pipeline().addMiddleware((Handler innerHandler) { final Pipeline pipeline = const Pipeline().addMiddleware((Handler innerHandler) {
return (Request request) async { return (Request request) async {
// Redirect the main.dart.js to the target file we decided to serve. // Redirect the main.dart.js to the target file we decided to serve.
if (mappedUrls.containsKey(request.url.path)) { if (mappedUrls.containsKey(request.url.path)) {
...@@ -222,19 +198,39 @@ class WebFs { ...@@ -222,19 +198,39 @@ class WebFs {
return innerHandler(request); return innerHandler(request);
} }
}; };
}) });
.addHandler(dwds.handler); Handler handler;
Dwds dwds;
if (!skipDwds) {
dwds = await dwdsFactory(
hostname: _kHostName,
applicationPort: port,
applicationTarget: kBuildTargetName,
assetServerPort: daemonAssetPort,
buildResults: filteredBuildResults,
chromeConnection: () async {
return (await ChromeLauncher.connectedInstance).chromeConnection;
},
reloadConfiguration: ReloadConfiguration.none,
serveDevTools: true,
verbose: false,
enableDebugExtension: true,
logWriter: (dynamic level, String message) => printTrace(message),
);
handler = pipeline.addHandler(dwds.handler);
} else {
handler = pipeline.addHandler(proxyHandler('http://localhost:$daemonAssetPort/web/'));
}
Cascade cascade = Cascade(); Cascade cascade = Cascade();
cascade = cascade.add(handler); cascade = cascade.add(handler);
cascade = cascade.add(_assetHandler(flutterProject)); cascade = cascade.add(_assetHandler(flutterProject));
final HttpServer server = await httpMultiServerFactory(_kHostName, port); final HttpServer server = await httpMultiServerFactory(_kHostName, port);
shelf_io.serveRequests(server, cascade.handler); shelf_io.serveRequests(server, cascade.handler);
final Chrome chrome = await chromeLauncher.launch('http://$_kHostName:$port/');
return WebFs( return WebFs(
client, client,
server, server,
dwds, dwds,
chrome, 'http://$_kHostName:$port/',
); );
} }
...@@ -319,7 +315,11 @@ class WebFs { ...@@ -319,7 +315,11 @@ class WebFs {
} else if (request.url.path.contains('assets')) { } else if (request.url.path.contains('assets')) {
final String assetPath = request.url.path.replaceFirst('assets/', ''); final String assetPath = request.url.path.replaceFirst('assets/', '');
final File file = fs.file(fs.path.join(getAssetBuildDirectory(), assetPath)); final File file = fs.file(fs.path.join(getAssetBuildDirectory(), assetPath));
return Response.ok(file.readAsBytesSync()); if (file.existsSync()) {
return Response.ok(file.readAsBytesSync());
} else {
return Response.notFound('');
}
} }
return Response.notFound(''); return Response.notFound('');
}; };
......
...@@ -484,7 +484,7 @@ class AppDomain extends Domain { ...@@ -484,7 +484,7 @@ class AppDomain extends Domain {
Completer<DebugConnectionInfo> connectionInfoCompleter; Completer<DebugConnectionInfo> connectionInfoCompleter;
if (runner.debuggingOptions.debuggingEnabled) { if (runner.debuggingEnabled) {
connectionInfoCompleter = Completer<DebugConnectionInfo>(); connectionInfoCompleter = Completer<DebugConnectionInfo>();
// We don't want to wait for this future to complete and callbacks won't fail. // We don't want to wait for this future to complete and callbacks won't fail.
// As it just writes to stdout. // As it just writes to stdout.
......
...@@ -573,6 +573,7 @@ abstract class ResidentRunner { ...@@ -573,6 +573,7 @@ abstract class ResidentRunner {
bool hotMode ; bool hotMode ;
String getReloadPath({ bool fullRestart }) => mainPath + (fullRestart ? '' : '.incremental') + '.dill'; String getReloadPath({ bool fullRestart }) => mainPath + (fullRestart ? '' : '.incremental') + '.dill';
bool get debuggingEnabled => debuggingOptions.debuggingEnabled;
bool get isRunningDebug => debuggingOptions.buildInfo.isDebug; bool get isRunningDebug => debuggingOptions.buildInfo.isDebug;
bool get isRunningProfile => debuggingOptions.buildInfo.isProfile; bool get isRunningProfile => debuggingOptions.buildInfo.isProfile;
bool get isRunningRelease => debuggingOptions.buildInfo.isRelease; bool get isRunningRelease => debuggingOptions.buildInfo.isRelease;
......
...@@ -12,6 +12,7 @@ import '../base/process_manager.dart'; ...@@ -12,6 +12,7 @@ import '../base/process_manager.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../device.dart'; import '../device.dart';
import '../features.dart'; import '../features.dart';
import '../globals.dart';
import '../project.dart'; import '../project.dart';
import 'chrome.dart'; import 'chrome.dart';
import 'workflow.dart'; import 'workflow.dart';
...@@ -36,6 +37,9 @@ class ChromeDevice extends Device { ...@@ -36,6 +37,9 @@ class ChromeDevice extends Device {
ephemeral: false, ephemeral: false,
); );
/// The active chrome instance.
Chrome _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.
@override @override
...@@ -112,7 +116,7 @@ class ChromeDevice extends Device { ...@@ -112,7 +116,7 @@ class ChromeDevice extends Device {
version = result.stdout; version = result.stdout;
} }
} }
return version; return version.trim();
} }
@override @override
...@@ -127,11 +131,13 @@ class ChromeDevice extends Device { ...@@ -127,11 +131,13 @@ class ChromeDevice extends Device {
}) async { }) async {
// See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart // See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart
// for the web initialization and server logic. // for the web initialization and server logic.
_chrome = await chromeLauncher.launch(platformArgs['uri']);
return LaunchResult.succeeded(observatoryUri: null); return LaunchResult.succeeded(observatoryUri: null);
} }
@override @override
Future<bool> stopApp(ApplicationPackage app) async { Future<bool> stopApp(ApplicationPackage app) async {
await _chrome.close();
return true; return true;
} }
...@@ -151,6 +157,7 @@ class WebDevices extends PollingDeviceDiscovery { ...@@ -151,6 +157,7 @@ class WebDevices extends PollingDeviceDiscovery {
WebDevices() : super('chrome'); WebDevices() : super('chrome');
final ChromeDevice _webDevice = ChromeDevice(); final ChromeDevice _webDevice = ChromeDevice();
final WebServerDevice _webServerDevice = WebServerDevice();
@override @override
bool get canListAnything => featureFlags.isWebEnabled; bool get canListAnything => featureFlags.isWebEnabled;
...@@ -159,6 +166,7 @@ class WebDevices extends PollingDeviceDiscovery { ...@@ -159,6 +166,7 @@ class WebDevices extends PollingDeviceDiscovery {
Future<List<Device>> pollingGetDevices() async { Future<List<Device>> pollingGetDevices() async {
return <Device>[ return <Device>[
_webDevice, _webDevice,
_webServerDevice,
]; ];
} }
...@@ -170,3 +178,81 @@ class WebDevices extends PollingDeviceDiscovery { ...@@ -170,3 +178,81 @@ class WebDevices extends PollingDeviceDiscovery {
String parseVersionForWindows(String input) { String parseVersionForWindows(String input) {
return input.split(RegExp('\w')).last; return input.split(RegExp('\w')).last;
} }
/// A special device type to allow serving for arbitrary browsers.
class WebServerDevice extends Device {
WebServerDevice() : super(
'web',
platformType: PlatformType.web,
category: Category.web,
ephemeral: false,
);
@override
void clearLogs() { }
@override
Future<String> get emulatorId => null;
@override
DeviceLogReader getLogReader({ApplicationPackage app}) {
return NoOpDeviceLogReader(app.name);
}
@override
Future<bool> installApp(ApplicationPackage app) async => true;
@override
Future<bool> isAppInstalled(ApplicationPackage app) async => true;
@override
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true;
@override
Future<bool> get isLocalEmulator async => false;
@override
bool isSupported() => featureFlags.isWebEnabled;
@override
bool isSupportedForProject(FlutterProject flutterProject) {
return flutterProject.web.existsSync();
}
@override
String get name => 'Server';
@override
DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder();
@override
Future<String> get sdkNameAndVersion async => 'Flutter Tools';
@override
Future<LaunchResult> startApp(ApplicationPackage package, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
Map<String, Object> platformArgs,
bool prebuiltApplication = false,
bool ipv6 = false,
}) async {
final String url = platformArgs['uri'];
printStatus('$mainPath is being served at $url', emphasis: true);
return LaunchResult.succeeded(observatoryUri: null);
}
@override
Future<bool> stopApp(ApplicationPackage app) async {
return true;
}
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript;
@override
Future<bool> uninstallApp(ApplicationPackage app) async {
return true;
}
}
...@@ -42,6 +42,7 @@ void main() { ...@@ -42,6 +42,7 @@ void main() {
@required String target, @required String target,
@required FlutterProject flutterProject, @required FlutterProject flutterProject,
@required BuildInfo buildInfo, @required BuildInfo buildInfo,
@required bool skipDwds,
}) async { }) async {
return mockWebFs; return mockWebFs;
}, },
...@@ -76,7 +77,6 @@ void main() { ...@@ -76,7 +77,6 @@ void main() {
when(mockWebFs.recompile()).thenAnswer((Invocation _) async { when(mockWebFs.recompile()).thenAnswer((Invocation _) async {
return true; return true;
}); });
when(mockWebFs.hardRefresh()).thenAnswer((Invocation _) async { });
final OperationResult result = await residentWebRunner.restart(fullRestart: true); final OperationResult result = await residentWebRunner.restart(fullRestart: true);
expect(result.code, 0); expect(result.code, 0);
......
...@@ -15,6 +15,7 @@ import 'package:flutter_tools/src/project.dart'; ...@@ -15,6 +15,7 @@ import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/build_runner/resident_web_runner.dart'; import 'package:flutter_tools/src/build_runner/resident_web_runner.dart';
import 'package:flutter_tools/src/build_runner/web_fs.dart'; import 'package:flutter_tools/src/build_runner/web_fs.dart';
import 'package:flutter_tools/src/web/web_device.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:vm_service/vm_service.dart'; import 'package:vm_service/vm_service.dart';
...@@ -49,6 +50,7 @@ void main() { ...@@ -49,6 +50,7 @@ void main() {
@required String target, @required String target,
@required FlutterProject flutterProject, @required FlutterProject flutterProject,
@required BuildInfo buildInfo, @required BuildInfo buildInfo,
@required bool skipDwds,
}) async { }) async {
return mockWebFs; return mockWebFs;
}, },
...@@ -75,6 +77,18 @@ void main() { ...@@ -75,6 +77,18 @@ void main() {
when(mockDebugConnection.uri).thenReturn('ws://127.0.0.1/abcd/'); when(mockDebugConnection.uri).thenReturn('ws://127.0.0.1/abcd/');
} }
test('runner with web server device does not support debugging', () => testbed.run(() {
final ResidentRunner profileResidentWebRunner = ResidentWebRunner(
WebServerDevice(),
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
);
expect(profileResidentWebRunner.debuggingEnabled, false);
expect(residentWebRunner.debuggingEnabled, true);
}));
test('profile does not supportsServiceProtocol', () => testbed.run(() { test('profile does not supportsServiceProtocol', () => testbed.run(() {
final ResidentRunner profileResidentWebRunner = ResidentWebRunner( final ResidentRunner profileResidentWebRunner = ResidentWebRunner(
MockWebDevice(), MockWebDevice(),
...@@ -204,7 +218,7 @@ void main() { ...@@ -204,7 +218,7 @@ void main() {
final OperationResult result = await residentWebRunner.restart(fullRestart: true); final OperationResult result = await residentWebRunner.restart(fullRestart: true);
expect(result.code, 1); expect(result.code, 1);
expect(result.message, contains('Page requires full reload')); expect(result.message, contains('Page requires refresh'));
})); }));
test('printHelp without details is spoopy', () => testbed.run(() async { test('printHelp without details is spoopy', () => testbed.run(() async {
...@@ -380,7 +394,7 @@ void main() { ...@@ -380,7 +394,7 @@ void main() {
})); }));
} }
class MockWebDevice extends Mock implements Device {} class MockWebDevice extends Mock implements ChromeDevice {}
class MockBuildDaemonCreator extends Mock implements BuildDaemonCreator {} class MockBuildDaemonCreator extends Mock implements BuildDaemonCreator {}
class MockFlutterWebFs extends Mock implements WebFs {} class MockFlutterWebFs extends Mock implements WebFs {}
class MockDebugConnection extends Mock implements DebugConnection {} class MockDebugConnection extends Mock implements DebugConnection {}
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.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';
...@@ -13,68 +14,86 @@ import '../../src/common.dart'; ...@@ -13,68 +14,86 @@ import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
void main() { void main() {
group(ChromeDevice, () { MockChromeLauncher mockChromeLauncher;
MockChromeLauncher mockChromeLauncher; MockPlatform mockPlatform;
MockPlatform mockPlatform; MockProcessManager mockProcessManager;
MockProcessManager mockProcessManager; MockWebApplicationPackage mockWebApplicationPackage;
setUp(() async { setUp(() async {
mockProcessManager = MockProcessManager(); mockWebApplicationPackage = MockWebApplicationPackage();
mockChromeLauncher = MockChromeLauncher(); mockProcessManager = MockProcessManager();
mockPlatform = MockPlatform(); mockChromeLauncher = MockChromeLauncher();
when(mockChromeLauncher.launch(any)).thenAnswer((Invocation invocation) async { mockPlatform = MockPlatform();
return null; when(mockChromeLauncher.launch(any)).thenAnswer((Invocation invocation) async {
}); return null;
}); });
when(mockWebApplicationPackage.name).thenReturn('test');
});
test('Defaults', () async { test('Chrome defaults', () async {
final ChromeDevice chromeDevice = ChromeDevice(); final ChromeDevice chromeDevice = ChromeDevice();
expect(chromeDevice.name, 'Chrome'); expect(chromeDevice.name, 'Chrome');
expect(chromeDevice.id, 'chrome'); expect(chromeDevice.id, 'chrome');
expect(chromeDevice.supportsHotReload, true); expect(chromeDevice.supportsHotReload, true);
expect(chromeDevice.supportsHotRestart, true); expect(chromeDevice.supportsHotRestart, true);
expect(chromeDevice.supportsStartPaused, true); expect(chromeDevice.supportsStartPaused, true);
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), isInstanceOf<NoOpDeviceLogReader>());
expect(await chromeDevice.portForwarder.forward(1), 1);
});
testUsingContext('Invokes version command on non-Windows platforms', () async{ test('Server defaults', () async {
when(mockPlatform.isWindows).thenReturn(false); final WebServerDevice device = WebServerDevice();
when(mockProcessManager.canRun('chrome.foo')).thenReturn(true);
when(mockProcessManager.run(<String>['chrome.foo', '--version'])).thenAnswer((Invocation invocation) async {
return MockProcessResult(0, 'ABC');
});
final ChromeDevice chromeDevice = ChromeDevice();
expect(chromeDevice.isSupported(), true); expect(device.name, 'Server');
expect(await chromeDevice.sdkNameAndVersion, 'ABC'); expect(device.id, 'web');
}, overrides: <Type, Generator>{ expect(device.supportsHotReload, true);
Platform: () => mockPlatform, expect(device.supportsHotRestart, true);
ProcessManager: () => mockProcessManager, expect(device.supportsStartPaused, true);
expect(device.supportsFlutterExit, true);
expect(device.supportsScreenshot, false);
expect(await device.isLocalEmulator, false);
expect(device.getLogReader(app: mockWebApplicationPackage), isInstanceOf<NoOpDeviceLogReader>());
expect(await device.portForwarder.forward(1), 1);
});
testUsingContext('Chrome invokes version command on non-Windows platforms', () async{
when(mockPlatform.isWindows).thenReturn(false);
when(mockProcessManager.canRun('chrome.foo')).thenReturn(true);
when(mockProcessManager.run(<String>['chrome.foo', '--version'])).thenAnswer((Invocation invocation) async {
return MockProcessResult(0, 'ABC');
}); });
final ChromeDevice chromeDevice = ChromeDevice();
testUsingContext('Invokes different version command on windows.', () async { expect(chromeDevice.isSupported(), true);
when(mockPlatform.isWindows).thenReturn(true); expect(await chromeDevice.sdkNameAndVersion, 'ABC');
when(mockProcessManager.canRun('chrome.foo')).thenReturn(true); }, overrides: <Type, Generator>{
when(mockProcessManager.run(<String>[ Platform: () => mockPlatform,
'reg', ProcessManager: () => mockProcessManager,
'query', });
'HKEY_CURRENT_USER\\Software\\Google\\Chrome\\BLBeacon',
'/v',
'version',
])).thenAnswer((Invocation invocation) async {
return MockProcessResult(0, r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon\ version REG_SZ 74.0.0 A');
});
final ChromeDevice chromeDevice = ChromeDevice();
expect(chromeDevice.isSupported(), true); testUsingContext('Chrome invokes different version command on windows.', () async {
expect(await chromeDevice.sdkNameAndVersion, 'Google Chrome 74.0.0'); when(mockPlatform.isWindows).thenReturn(true);
}, overrides: <Type, Generator>{ when(mockProcessManager.canRun('chrome.foo')).thenReturn(true);
Platform: () => mockPlatform, when(mockProcessManager.run(<String>[
ProcessManager: () => mockProcessManager, 'reg',
'query',
'HKEY_CURRENT_USER\\Software\\Google\\Chrome\\BLBeacon',
'/v',
'version',
])).thenAnswer((Invocation invocation) async {
return MockProcessResult(0, r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon\ version REG_SZ 74.0.0 A');
}); });
final ChromeDevice chromeDevice = ChromeDevice();
expect(chromeDevice.isSupported(), true);
expect(await chromeDevice.sdkNameAndVersion, 'Google Chrome 74.0.0');
}, overrides: <Type, Generator>{
Platform: () => mockPlatform,
ProcessManager: () => mockProcessManager,
}); });
} }
...@@ -93,3 +112,4 @@ class MockProcessResult extends Mock implements ProcessResult { ...@@ -93,3 +112,4 @@ class MockProcessResult extends Mock implements ProcessResult {
@override @override
final String stdout; final String stdout;
} }
class MockWebApplicationPackage extends Mock implements WebApplicationPackage {}
...@@ -79,6 +79,7 @@ void main() { ...@@ -79,6 +79,7 @@ void main() {
test('Can create webFs from mocked interfaces', () => testbed.run(() async { test('Can create webFs from mocked interfaces', () => testbed.run(() async {
final FlutterProject flutterProject = FlutterProject.current(); final FlutterProject flutterProject = FlutterProject.current();
await WebFs.start( await WebFs.start(
skipDwds: false,
target: fs.path.join('lib', 'main.dart'), target: fs.path.join('lib', 'main.dart'),
buildInfo: BuildInfo.debug, buildInfo: BuildInfo.debug,
flutterProject: flutterProject, flutterProject: flutterProject,
...@@ -87,9 +88,6 @@ void main() { ...@@ -87,9 +88,6 @@ void main() {
// The build daemon is told to build once. // The build daemon is told to build once.
verify(mockBuildDaemonClient.startBuild()).called(1); verify(mockBuildDaemonClient.startBuild()).called(1);
// Chrome is launched based on port from above.
verify(mockChromeLauncher.launch('http://localhost:1234/')).called(1);
// .dart_tool directory is created. // .dart_tool directory is created.
expect(flutterProject.dartTool.existsSync(), true); expect(flutterProject.dartTool.existsSync(), true);
})); }));
......
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