blob: 3977fdf634295852e05e7fe3de2863791c14a6f9 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'package:build_daemon/client.dart';
import 'package:dwds/dwds.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/net.dart';
import 'package:flutter_tools/src/build_info.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/compile.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/web/chrome.dart';
import 'package:flutter_tools/src/web/web_device.dart';
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
import 'package:vm_service/vm_service.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/testbed.dart';
void main() {
Testbed testbed;
MockFlutterWebFs mockWebFs;
ResidentWebRunner residentWebRunner;
MockDebugConnection mockDebugConnection;
MockVmService mockVmService;
MockChromeDevice mockChromeDevice;
MockAppConnection mockAppConnection;
MockFlutterDevice mockFlutterDevice;
MockWebDevFS mockWebDevFS;
MockResidentCompiler mockResidentCompiler;
MockChrome mockChrome;
MockChromeConnection mockChromeConnection;
MockChromeTab mockChromeTab;
MockWipConnection mockWipConnection;
MockWipDebugger mockWipDebugger;
MockWebServerDevice mockWebServerDevice;
bool didSkipDwds;
setUp(() {
resetChromeForTesting();
mockWebFs = MockFlutterWebFs();
mockDebugConnection = MockDebugConnection();
mockVmService = MockVmService();
mockChromeDevice = MockChromeDevice();
mockAppConnection = MockAppConnection();
mockFlutterDevice = MockFlutterDevice();
mockWebDevFS = MockWebDevFS();
mockResidentCompiler = MockResidentCompiler();
mockChrome = MockChrome();
mockChromeConnection = MockChromeConnection();
mockChromeTab = MockChromeTab();
mockWipConnection = MockWipConnection();
mockWipDebugger = MockWipDebugger();
mockWebServerDevice = MockWebServerDevice();
when(mockFlutterDevice.device).thenReturn(mockChromeDevice);
testbed = Testbed(
setup: () {
residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
stayResident: true,
dartDefines: const <String>[],
urlTunneller: null,
) as ResidentWebRunner;
},
overrides: <Type, Generator>{
WebFsFactory: () => ({
@required String target,
@required FlutterProject flutterProject,
@required BuildInfo buildInfo,
@required bool skipDwds,
@required bool initializePlatform,
@required String hostname,
@required String port,
@required List<String> dartDefines,
@required UrlTunneller urlTunneller,
}) async {
didSkipDwds = skipDwds;
return mockWebFs;
},
},
);
});
void _setupMocks() {
fs.file('pubspec.yaml').createSync();
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
fs.file(fs.path.join('web', 'index.html')).createSync(recursive: true);
when(mockWebFs.connect(any)).thenAnswer((Invocation _) async {
return ConnectionResult(mockAppConnection, mockDebugConnection);
});
when(mockWebFs.recompile()).thenAnswer((Invocation _) {
return Future<bool>.value(false);
});
when(mockWebFs.uri).thenReturn('http://localhost:8765/app/');
when(mockDebugConnection.vmService).thenReturn(mockVmService);
when(mockDebugConnection.onDone).thenAnswer((Invocation invocation) {
return Completer<void>().future;
});
when(mockVmService.onStdoutEvent).thenAnswer((Invocation _) {
return const Stream<Event>.empty();
});
when(mockVmService.onDebugEvent).thenAnswer((Invocation _) {
return const Stream<Event>.empty();
});
when(mockDebugConnection.uri).thenReturn('ws://127.0.0.1/abcd/');
when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS);
when(mockWebDevFS.sources).thenReturn(<Uri>[]);
when(mockFlutterDevice.generator).thenReturn(mockResidentCompiler);
when(mockChrome.chromeConnection).thenReturn(mockChromeConnection);
when(mockChromeConnection.getTab(any)).thenAnswer((Invocation invocation) async {
return mockChromeTab;
});
when(mockChromeTab.connect()).thenAnswer((Invocation invocation) async {
return mockWipConnection;
});
when(mockWipConnection.debugger).thenReturn(mockWipDebugger);
}
test('runner with web server device does not support debugging without --start-paused', () => testbed.run(() {
when(mockFlutterDevice.device).thenReturn(WebServerDevice());
final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
stayResident: true,
dartDefines: const <String>[],
urlTunneller: null,
) as ResidentWebRunner;
expect(profileResidentWebRunner.debuggingEnabled, false);
when(mockFlutterDevice.device).thenReturn(MockChromeDevice());
expect(residentWebRunner.debuggingEnabled, true);
}));
test('runner with web server device does not initialize dwds', () => testbed.run(() async {
_setupMocks();
when(mockFlutterDevice.device).thenReturn(WebServerDevice());
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
expect(didSkipDwds, true);
}));
test('runner with web server device supports debugging with --start-paused', () => testbed.run(() {
when(mockFlutterDevice.device).thenReturn(WebServerDevice());
final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true),
ipv6: true,
stayResident: true,
dartDefines: <String>[],
urlTunneller: null,
);
expect(profileResidentWebRunner.debuggingEnabled, true);
}));
test('runner with web server device uses debug extension with --start-paused', () => testbed.run(() async {
_setupMocks();
when(mockFlutterDevice.device).thenReturn(WebServerDevice());
final ResidentWebRunner runner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true),
ipv6: true,
stayResident: true,
dartDefines: <String>[],
urlTunneller: null,
) as ResidentWebRunner;
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(runner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
// Check connect() was told to use the debug extension.
verify(mockWebFs.connect(true)).called(1);
// And ensure the debug services was started.
expect(testLogger.statusText, contains('Debug service listening on'));
}));
test('profile does not supportsServiceProtocol', () => testbed.run(() {
when(mockFlutterDevice.device).thenReturn(mockChromeDevice);
final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile),
ipv6: true,
stayResident: true,
dartDefines: const <String>[],
urlTunneller: null,
);
expect(profileResidentWebRunner.supportsServiceProtocol, false);
expect(residentWebRunner.supportsServiceProtocol, true);
}));
test('Exits on run if application does not support the web', () => testbed.run(() async {
fs.file('pubspec.yaml').createSync();
expect(await residentWebRunner.run(), 1);
expect(testLogger.errorText, contains('This application is not configured to build on the web'));
}));
test('Exits on run if target file does not exist', () => testbed.run(() async {
fs.file('pubspec.yaml').createSync();
fs.file(fs.path.join('web', 'index.html')).createSync(recursive: true);
expect(await residentWebRunner.run(), 1);
final String absoluteMain = fs.path.absolute(fs.path.join('lib', 'main.dart'));
expect(testLogger.errorText, contains('Tried to run $absoluteMain, but that file does not exist.'));
}));
test('Can successfully run and connect to vmservice', () => testbed.run(() async {
_setupMocks();
final DelegateLogger delegateLogger = logger as DelegateLogger;
final BufferLogger bufferLogger = delegateLogger.delegate as BufferLogger;
final MockStatus status = MockStatus();
delegateLogger.status = status;
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future;
verify(mockAppConnection.runMain()).called(1);
verify(mockVmService.registerService('reloadSources', 'FlutterTools')).called(1);
verify(status.stop()).called(2);
expect(bufferLogger.statusText, contains('Debug service listening on ws://127.0.0.1/abcd/'));
expect(debugConnectionInfo.wsUri.toString(), 'ws://127.0.0.1/abcd/');
}, overrides: <Type, Generator>{
Logger: () => DelegateLogger(BufferLogger()),
}));
test('Can successfully run and disconnect with --no-resident', () => testbed.run(() async {
_setupMocks();
residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
stayResident: false,
dartDefines: const <String>[],
urlTunneller: null,
) as ResidentWebRunner;
expect(await residentWebRunner.run(), 0);
}));
test('Listens to stdout streams before running main', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
final StreamController<Event> controller = StreamController<Event>.broadcast();
when(mockVmService.onStdoutEvent).thenAnswer((Invocation _) {
return controller.stream;
});
when(mockAppConnection.runMain()).thenAnswer((Invocation invocation) {
controller.add(Event.parse(<String, Object>{
'type': 'Event',
'kind': 'WriteEvent',
'timestamp': 1569473488296,
'bytes': base64.encode('THIS MESSAGE IS IMPORTANT'.codeUnits),
}));
});
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
expect(testLogger.statusText, contains('THIS MESSAGE IS IMPORTANT'));
}));
test('Does not run main with --start-paused', () => testbed.run(() async {
residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true),
ipv6: true,
stayResident: true,
dartDefines: const <String>[],
urlTunneller: null,
) as ResidentWebRunner;
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
final StreamController<Event> controller = StreamController<Event>.broadcast();
when(mockVmService.onStdoutEvent).thenAnswer((Invocation _) {
return controller.stream;
});
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
verifyNever(mockAppConnection.runMain());
}));
test('Can hot reload after attaching', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
when(mockWebFs.recompile()).thenAnswer((Invocation invocation) async {
return true;
});
when(mockVmService.callServiceExtension('hotRestart')).thenAnswer((Invocation _) async {
return Response.parse(<String, Object>{'type': 'Success'});
});
final OperationResult result = await residentWebRunner.restart(fullRestart: false);
expect(testLogger.statusText, contains('Reloaded application in'));
expect(result.code, 0);
// ensure that analytics are sent.
verify(Usage.instance.sendEvent('hot', 'restart', parameters: <String, String>{
'cd27': 'web-javascript',
'cd28': null,
'cd29': 'false',
'cd30': 'true',
})).called(1);
verify(Usage.instance.sendTiming('hot', 'web-restart', any)).called(1);
verify(Usage.instance.sendTiming('hot', 'web-refresh', any)).called(1);
verify(Usage.instance.sendTiming('hot', 'web-recompile', any)).called(1);
}, overrides: <Type, Generator>{
Usage: () => MockFlutterUsage(),
}));
test('Can hot reload after attaching - experimental', () => testbed.run(() async {
_setupMocks();
launchChromeInstance(mockChrome);
when(mockWebDevFS.update(
mainPath: anyNamed('mainPath'),
target: anyNamed('target'),
bundle: anyNamed('bundle'),
firstBuildTime: anyNamed('firstBuildTime'),
bundleFirstUpload: anyNamed('bundleFirstUpload'),
generator: anyNamed('generator'),
fullRestart: anyNamed('fullRestart'),
dillOutputPath: anyNamed('dillOutputPath'),
trackWidgetCreation: anyNamed('trackWidgetCreation'),
projectRootPath: anyNamed('projectRootPath'),
pathToReload: anyNamed('pathToReload'),
invalidatedFiles: anyNamed('invalidatedFiles'),
)).thenAnswer((Invocation invocation) async {
return UpdateFSReport(success: true)
..invalidatedModules = <String>['example'];
});
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
final OperationResult result = await residentWebRunner.restart(fullRestart: false);
expect(testLogger.statusText, contains('Reloaded application in'));
expect(result.code, 0);
verify(mockResidentCompiler.accept()).called(2);
// ensure that analytics are sent.
verify(Usage.instance.sendEvent('hot', 'restart', parameters: <String, String>{
'cd27': 'web-javascript',
'cd28': null,
'cd29': 'false',
'cd30': 'true',
})).called(1);
verify(Usage.instance.sendTiming('hot', 'web-incremental-restart', any)).called(1);
}, overrides: <Type, Generator>{
Usage: () => MockFlutterUsage(),
FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true),
}));
test('Can hot restart after attaching - experimental', () => testbed.run(() async {
_setupMocks();
launchChromeInstance(mockChrome);
when(mockWebDevFS.update(
mainPath: anyNamed('mainPath'),
target: anyNamed('target'),
bundle: anyNamed('bundle'),
firstBuildTime: anyNamed('firstBuildTime'),
bundleFirstUpload: anyNamed('bundleFirstUpload'),
generator: anyNamed('generator'),
fullRestart: anyNamed('fullRestart'),
dillOutputPath: anyNamed('dillOutputPath'),
trackWidgetCreation: anyNamed('trackWidgetCreation'),
projectRootPath: anyNamed('projectRootPath'),
pathToReload: anyNamed('pathToReload'),
invalidatedFiles: anyNamed('invalidatedFiles'),
)).thenAnswer((Invocation invocation) async {
return UpdateFSReport(success: true)
..invalidatedModules = <String>['example'];
});
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
expect(testLogger.statusText, contains('Restarted application in'));
expect(result.code, 0);
verify(mockResidentCompiler.accept()).called(2);
// ensure that analytics are sent.
verify(Usage.instance.sendEvent('hot', 'restart', parameters: <String, String>{
'cd27': 'web-javascript',
'cd28': null,
'cd29': 'false',
'cd30': 'true',
})).called(1);
verifyNever(Usage.instance.sendTiming('hot', 'web-incremental-restart', any));
}, overrides: <Type, Generator>{
Usage: () => MockFlutterUsage(),
FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true),
}));
test('Can hot restart after attaching - experimental with web-server device', () => testbed.run(() async {
_setupMocks();
when(mockFlutterDevice.device).thenReturn(mockWebServerDevice);
when(mockWebDevFS.update(
mainPath: anyNamed('mainPath'),
target: anyNamed('target'),
bundle: anyNamed('bundle'),
firstBuildTime: anyNamed('firstBuildTime'),
bundleFirstUpload: anyNamed('bundleFirstUpload'),
generator: anyNamed('generator'),
fullRestart: anyNamed('fullRestart'),
dillOutputPath: anyNamed('dillOutputPath'),
trackWidgetCreation: anyNamed('trackWidgetCreation'),
projectRootPath: anyNamed('projectRootPath'),
pathToReload: anyNamed('pathToReload'),
invalidatedFiles: anyNamed('invalidatedFiles'),
)).thenAnswer((Invocation invocation) async {
return UpdateFSReport(success: true)
..invalidatedModules = <String>['example'];
});
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
expect(testLogger.statusText, contains('Restarted application in'));
expect(result.code, 0);
verify(mockResidentCompiler.accept()).called(2);
// ensure that analytics are sent.
verify(Usage.instance.sendEvent('hot', 'restart', parameters: <String, String>{
'cd27': 'web-javascript',
'cd28': null,
'cd29': 'false',
'cd30': 'true',
})).called(1);
verifyNever(Usage.instance.sendTiming('hot', 'web-incremental-restart', any));
}, overrides: <Type, Generator>{
Usage: () => MockFlutterUsage(),
FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true),
}));
test('Can hot restart after attaching', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
when(mockWebFs.recompile()).thenAnswer((Invocation invocation) async {
return true;
});
when(mockVmService.callServiceExtension('fullReload')).thenAnswer((Invocation _) async {
return Response.parse(<String, Object>{'type': 'Success'});
});
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
expect(testLogger.statusText, contains('Restarted application in'));
expect(result.code, 0);
// ensure that analytics are sent.
verify(Usage.instance.sendEvent('hot', 'restart', parameters: <String, String>{
'cd27': 'web-javascript',
'cd28': null,
'cd29': 'false',
'cd30': 'true',
})).called(1);
verifyNever(Usage.instance.sendTiming('hot', 'web-restart', any));
verifyNever(Usage.instance.sendTiming('hot', 'web-refresh', any));
verify(Usage.instance.sendTiming('hot', 'web-recompile', any)).called(1);
}, overrides: <Type, Generator>{
Usage: () => MockFlutterUsage(),
}));
test('Selects Dwds runner in profile mode with incremental compiler enabled', () => testbed.run(() async {
final ResidentWebRunner residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile),
ipv6: true,
stayResident: true,
dartDefines: const <String>[],
urlTunneller: null,
) as ResidentWebRunner;
expect(residentWebRunner.runtimeType.toString(), '_DwdsResidentWebRunner');
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true),
}));
test('Fails on compilation errors in hot restart', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
when(mockWebFs.recompile()).thenAnswer((Invocation _) async {
return false;
});
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
expect(result.code, 1);
expect(result.message, contains('Failed to recompile application.'));
verifyNever(Usage.instance.sendTiming('hot', 'web-restart', any));
verifyNever(Usage.instance.sendTiming('hot', 'web-refresh', any));
verifyNever(Usage.instance.sendTiming('hot', 'web-recompile', any));
}, overrides: <Type, Generator>{
Usage: () => MockFlutterUsage(),
}));
test('Fails on vmservice response error for hot restart', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
when(mockWebFs.recompile()).thenAnswer((Invocation _) async {
return true;
});
when(mockVmService.callServiceExtension('fullReload')).thenAnswer((Invocation _) async {
return Response.parse(<String, Object>{'type': 'Failed'});
});
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
expect(result.code, 1);
expect(result.message, contains('Failed'));
verifyNever(Usage.instance.sendTiming('hot', 'web-restart', any));
verifyNever(Usage.instance.sendTiming('hot', 'web-refresh', any));
verify(Usage.instance.sendTiming('hot', 'web-recompile', any)).called(1);
}, overrides: <Type, Generator>{
Usage: () => MockFlutterUsage(),
}));
test('Fails on vmservice response error for hot reload', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
when(mockWebFs.recompile()).thenAnswer((Invocation _) async {
return true;
});
when(mockVmService.callServiceExtension('hotRestart')).thenAnswer((Invocation _) async {
return Response.parse(<String, Object>{'type': 'Failed'});
});
final OperationResult result = await residentWebRunner.restart(fullRestart: false);
expect(result.code, 1);
expect(result.message, contains('Failed'));
verifyNever(Usage.instance.sendTiming('hot', 'web-restart', any));
verifyNever(Usage.instance.sendTiming('hot', 'web-refresh', any));
verify(Usage.instance.sendTiming('hot', 'web-recompile', any)).called(1);
}, overrides: <Type, Generator>{
Usage: () => MockFlutterUsage(),
}));
test('Fails on vmservice RpcError', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
when(mockWebFs.recompile()).thenAnswer((Invocation _) async {
return true;
});
when(mockVmService.callServiceExtension('hotRestart')).thenThrow(RPCError('', 2, '123'));
final OperationResult result = await residentWebRunner.restart(fullRestart: false);
expect(result.code, 1);
expect(result.message, contains('Page requires refresh'));
}));
test('printHelp without details has web warning', () => testbed.run(() async {
residentWebRunner.printHelp(details: false);
expect(testLogger.statusText, contains('Warning'));
expect(testLogger.statusText, contains('https://flutter.dev/web'));
expect(testLogger.statusText, isNot(contains('https://flutter.dev/web.')));
}));
test('debugDumpApp', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
await residentWebRunner.debugDumpApp();
verify(mockVmService.callServiceExtension('ext.flutter.debugDumpApp')).called(1);
}));
test('debugDumpLayerTree', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
await residentWebRunner.debugDumpLayerTree();
verify(mockVmService.callServiceExtension('ext.flutter.debugDumpLayerTree')).called(1);
}));
test('debugDumpRenderTree', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
await residentWebRunner.debugDumpRenderTree();
verify(mockVmService.callServiceExtension('ext.flutter.debugDumpRenderTree')).called(1);
}));
test('debugDumpSemanticsTreeInTraversalOrder', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
await residentWebRunner.debugDumpSemanticsTreeInTraversalOrder();
verify(mockVmService.callServiceExtension('ext.flutter.debugDumpSemanticsTreeInTraversalOrder')).called(1);
}));
test('debugDumpSemanticsTreeInInverseHitTestOrder', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
await residentWebRunner.debugDumpSemanticsTreeInInverseHitTestOrder();
verify(mockVmService.callServiceExtension('ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder')).called(1);
}));
test('debugToggleDebugPaintSizeEnabled', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
when(mockVmService.callServiceExtension('ext.flutter.debugPaint'))
.thenAnswer((Invocation _) async {
return Response.parse(<String, Object>{'enabled': false});
});
await residentWebRunner.debugToggleDebugPaintSizeEnabled();
verify(mockVmService.callServiceExtension('ext.flutter.debugPaint',
args: <String, Object>{'enabled': true})).called(1);
}));
test('debugTogglePerformanceOverlayOverride', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
when(mockVmService.callServiceExtension('ext.flutter.showPerformanceOverlay'))
.thenAnswer((Invocation _) async {
return Response.parse(<String, Object>{'enabled': false});
});
await residentWebRunner.debugTogglePerformanceOverlayOverride();
verify(mockVmService.callServiceExtension('ext.flutter.showPerformanceOverlay',
args: <String, Object>{'enabled': true})).called(1);
}));
test('debugToggleWidgetInspector', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
when(mockVmService.callServiceExtension('ext.flutter.debugToggleWidgetInspector'))
.thenAnswer((Invocation _) async {
return Response.parse(<String, Object>{'enabled': false});
});
await residentWebRunner.debugToggleWidgetInspector();
verify(mockVmService.callServiceExtension('ext.flutter.debugToggleWidgetInspector',
args: <String, Object>{'enabled': true})).called(1);
}));
test('debugToggleProfileWidgetBuilds', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
when(mockVmService.callServiceExtension('ext.flutter.profileWidgetBuilds'))
.thenAnswer((Invocation _) async {
return Response.parse(<String, Object>{'enabled': false});
});
await residentWebRunner.debugToggleProfileWidgetBuilds();
verify(mockVmService.callServiceExtension('ext.flutter.profileWidgetBuilds',
args: <String, Object>{'enabled': true})).called(1);
}));
test('debugTogglePlatform', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
when(mockVmService.callServiceExtension('ext.flutter.platformOverride'))
.thenAnswer((Invocation _) async {
return Response.parse(<String, Object>{'value': 'iOS'});
});
await residentWebRunner.debugTogglePlatform();
expect(testLogger.statusText, contains('Switched operating system to android'));
verify(mockVmService.callServiceExtension('ext.flutter.platformOverride',
args: <String, Object>{'value': 'android'})).called(1);
}));
test('cleanup of resources is safe to call multiple times', () => testbed.run(() async {
_setupMocks();
bool debugClosed = false;
when(mockChromeDevice.stopApp(any)).thenAnswer((Invocation invocation) async {
if (debugClosed) {
throw StateError('debug connection closed twice');
}
debugClosed = true;
return true;
});
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
await residentWebRunner.exit();
await residentWebRunner.exit();
verifyNever(mockDebugConnection.close());
}));
test('cleans up Chrome if tab is closed', () => testbed.run(() async {
_setupMocks();
final Completer<void> onDone = Completer<void>();
when(mockDebugConnection.onDone).thenAnswer((Invocation invocation) {
return onDone.future;
});
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
final Future<int> result = residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
);
await connectionInfoCompleter.future;
onDone.complete();
await result;
verify(mockWebFs.stop()).called(1);
}));
test('Prints target and device name on run', () => testbed.run(() async {
_setupMocks();
when(mockChromeDevice.name).thenReturn('Chromez');
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
expect(testLogger.statusText, contains('Launching ${fs.path.join('lib', 'main.dart')} on Chromez in debug mode'));
}));
test('Sends launched app.webLaunchUrl event for Chrome device', () => testbed.run(() async {
_setupMocks();
when(mockFlutterDevice.device).thenReturn(ChromeDevice());
final DelegateLogger delegateLogger = logger as DelegateLogger;
final MockStatus mockStatus = MockStatus();
delegateLogger.status = mockStatus;
final ResidentWebRunner runner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
stayResident: true,
dartDefines: const <String>[],
urlTunneller: null,
) as ResidentWebRunner;
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(runner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
// Ensure we got the URL and that it was already launched.
verify(logger.sendEvent(
'app.webLaunchUrl',
argThat(allOf(
containsPair('url', 'http://localhost:8765/app/'),
containsPair('launched', true),
))
));
}, overrides: <Type, Generator>{
Logger: () => DelegateLogger(MockLogger()),
ChromeLauncher: () => MockChromeLauncher(),
}));
test('Sends unlaunched app.webLaunchUrl event for Web Server device', () => testbed.run(() async {
_setupMocks();
when(mockFlutterDevice.device).thenReturn(WebServerDevice());
final DelegateLogger delegateLogger = logger as DelegateLogger;
final MockStatus mockStatus = MockStatus();
delegateLogger.status = mockStatus;
final ResidentWebRunner runner = DwdsWebRunnerFactory().createWebRunner(
mockFlutterDevice,
flutterProject: FlutterProject.current(),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
stayResident: true,
dartDefines: const <String>[],
urlTunneller: null,
) as ResidentWebRunner;
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(runner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
// Ensure we got the URL and that it was not already launched.
verify(logger.sendEvent(
'app.webLaunchUrl',
argThat(allOf(
containsPair('url', 'http://localhost:8765/app/'),
containsPair('launched', false),
))
));
}, overrides: <Type, Generator>{
Logger: () => DelegateLogger(MockLogger())
}));
test('Successfully turns WebSocketException into ToolExit', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
final Completer<void> unhandledErrorCompleter = Completer<void>();
when(mockWebFs.connect(any)).thenAnswer((Invocation _) async {
unawaited(unhandledErrorCompleter.future.then((void value) {
throw const WebSocketException();
}));
return ConnectionResult(mockAppConnection, mockDebugConnection);
});
final Future<void> expectation = expectLater(() => residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
), throwsA(isInstanceOf<ToolExit>()));
unhandledErrorCompleter.complete();
await expectation;
}));
test('Successfully turns AppConnectionException into ToolExit', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
final Completer<void> unhandledErrorCompleter = Completer<void>();
when(mockWebFs.connect(any)).thenAnswer((Invocation _) async {
unawaited(unhandledErrorCompleter.future.then((void value) {
throw AppConnectionException('Could not connect to application with appInstanceId: c0ae0750-ee91-11e9-cea6-35d95a968356');
}));
return ConnectionResult(mockAppConnection, mockDebugConnection);
});
final Future<void> expectation = expectLater(() => residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
), throwsA(isInstanceOf<ToolExit>()));
unhandledErrorCompleter.complete();
await expectation;
}));
test('Successfully turns ChromeDebugError into ToolExit', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
final Completer<void> unhandledErrorCompleter = Completer<void>();
when(mockWebFs.connect(any)).thenAnswer((Invocation _) async {
unawaited(unhandledErrorCompleter.future.then((void value) {
throw ChromeDebugException(<String, dynamic>{});
}));
return ConnectionResult(mockAppConnection, mockDebugConnection);
});
final Future<void> expectation = expectLater(() => residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
), throwsA(isInstanceOf<ToolExit>()));
unhandledErrorCompleter.complete();
await expectation;
}));
test('Successfully turns OptionsSkew error into ToolExit', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
final Completer<void> unhandledErrorCompleter = Completer<void>();
when(mockWebFs.connect(any)).thenAnswer((Invocation _) async {
unawaited(unhandledErrorCompleter.future.then((void value) {
throw OptionsSkew();
}));
return ConnectionResult(mockAppConnection, mockDebugConnection);
});
final Future<void> expectation = expectLater(() => residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
), throwsA(isInstanceOf<ToolExit>()));
unhandledErrorCompleter.complete();
await expectation;
}));
test('Successfully turns VersionSkew error into ToolExit', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
final Completer<void> unhandledErrorCompleter = Completer<void>();
when(mockWebFs.connect(any)).thenAnswer((Invocation _) async {
unawaited(unhandledErrorCompleter.future.then((void value) {
throw VersionSkew();
}));
return ConnectionResult(mockAppConnection, mockDebugConnection);
});
final Future<void> expectation = expectLater(() => residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
), throwsA(isInstanceOf<ToolExit>()));
unhandledErrorCompleter.complete();
await expectation;
}));
test('Successfully turns failed startup StateError error into ToolExit', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
final Completer<void> unhandledErrorCompleter = Completer<void>();
when(mockWebFs.connect(any)).thenAnswer((Invocation _) async {
unawaited(unhandledErrorCompleter.future.then((void value) {
throw StateError('Unable to start build daemon');
}));
return ConnectionResult(mockAppConnection, mockDebugConnection);
});
final Future<void> expectation = expectLater(() => residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
), throwsA(isInstanceOf<ToolExit>()));
unhandledErrorCompleter.complete();
await expectation;
}));
test('Rethrows Exception type', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
final Completer<void> unhandledErrorCompleter = Completer<void>();
when(mockWebFs.connect(any)).thenAnswer((Invocation _) async {
unawaited(unhandledErrorCompleter.future.then((void value) {
throw Exception('Something went wrong');
}));
return ConnectionResult(mockAppConnection, mockDebugConnection);
});
final Future<void> expectation = expectLater(() => residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
), throwsA(isInstanceOf<Exception>()));
unhandledErrorCompleter.complete();
await expectation;
}));
test('Successfully turns MissingPortFile error into ToolExit', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
final Completer<void> unhandledErrorCompleter = Completer<void>();
when(mockWebFs.connect(any)).thenAnswer((Invocation _) async {
unawaited(unhandledErrorCompleter.future.then((void value) {
throw MissingPortFile();
}));
return ConnectionResult(mockAppConnection, mockDebugConnection);
});
final Future<void> expectation = expectLater(() => residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
), throwsA(isInstanceOf<ToolExit>()));
unhandledErrorCompleter.complete();
await expectation;
}));
test('Rethrows unknown exception type from web tooling', () => testbed.run(() async {
_setupMocks();
final DelegateLogger delegateLogger = logger as DelegateLogger;
final MockStatus mockStatus = MockStatus();
delegateLogger.status = mockStatus;
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
final Completer<void> unhandledErrorCompleter = Completer<void>();
when(mockWebFs.connect(any)).thenAnswer((Invocation _) async {
unawaited(unhandledErrorCompleter.future.then((void value) {
throw StateError('Something went wrong');
}));
return ConnectionResult(mockAppConnection, mockDebugConnection);
});
final Future<void> expectation = expectLater(() => residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
), throwsA(isInstanceOf<StateError>()));
unhandledErrorCompleter.complete();
await expectation;
verify(mockStatus.stop()).called(2);
}, overrides: <Type, Generator>{
Logger: () => DelegateLogger(BufferLogger())
}));
}
class MockChromeLauncher extends Mock implements ChromeLauncher {}
class MockFlutterUsage extends Mock implements Usage {}
class MockChromeDevice extends Mock implements ChromeDevice {}
class MockBuildDaemonCreator extends Mock implements BuildDaemonCreator {}
class MockFlutterWebFs extends Mock implements WebFs {}
class MockDebugConnection extends Mock implements DebugConnection {}
class MockAppConnection extends Mock implements AppConnection {}
class MockVmService extends Mock implements VmService {}
class MockStatus extends Mock implements Status {}
class MockFlutterDevice extends Mock implements FlutterDevice {}
class MockWebDevFS extends Mock implements DevFS {}
class MockResidentCompiler extends Mock implements ResidentCompiler {}
class MockChrome extends Mock implements Chrome {}
class MockChromeConnection extends Mock implements ChromeConnection {}
class MockChromeTab extends Mock implements ChromeTab {}
class MockWipConnection extends Mock implements WipConnection {}
class MockWipDebugger extends Mock implements WipDebugger {}
class MockLogger extends Mock implements Logger {}
class MockWebServerDevice extends Mock implements WebServerDevice {}