mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
[flutter_tools] ensure zoned errors are caught in new web runner (#50895)
This commit is contained in:
parent
dfcf9beb6b
commit
57acc68740
|
@ -4,14 +4,17 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dwds/dwds.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:vm_service/vm_service.dart' as vmservice;
|
||||
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
|
||||
hide StackTrace;
|
||||
|
||||
import '../application_package.dart';
|
||||
import '../base/async_guard.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/net.dart';
|
||||
import '../base/terminal.dart';
|
||||
|
@ -61,6 +64,10 @@ class DwdsWebRunnerFactory extends WebRunnerFactory {
|
|||
}
|
||||
}
|
||||
|
||||
const String kExitMessage = 'Failed to establish connection with the application '
|
||||
'instance in Chrome.\nThis can happen if the websocket connection used by the '
|
||||
'web tooling is unable to correctly establish a connection, for example due to a firewall.';
|
||||
|
||||
/// A hot-runner which handles browser specific delegation.
|
||||
abstract class ResidentWebRunner extends ResidentRunner {
|
||||
ResidentWebRunner(
|
||||
|
@ -393,45 +400,59 @@ class _ResidentWebRunner extends ResidentWebRunner {
|
|||
final int hostPort = debuggingOptions.port == null
|
||||
? await globals.os.findFreePort()
|
||||
: int.tryParse(debuggingOptions.port);
|
||||
device.devFS = WebDevFS(
|
||||
hostname: effectiveHostname,
|
||||
port: hostPort,
|
||||
packagesFilePath: packagesFilePath,
|
||||
urlTunneller: urlTunneller,
|
||||
buildMode: debuggingOptions.buildInfo.mode,
|
||||
enableDwds: _enableDwds,
|
||||
entrypoint: globals.fs.file(target).uri,
|
||||
);
|
||||
final Uri url = await device.devFS.create();
|
||||
if (debuggingOptions.buildInfo.isDebug) {
|
||||
final UpdateFSReport report = await _updateDevFS(fullRestart: true);
|
||||
if (!report.success) {
|
||||
globals.printError('Failed to compile application.');
|
||||
return 1;
|
||||
}
|
||||
device.generator.accept();
|
||||
} else {
|
||||
await buildWeb(
|
||||
flutterProject,
|
||||
target,
|
||||
debuggingOptions.buildInfo,
|
||||
debuggingOptions.initializePlatform,
|
||||
dartDefines,
|
||||
false,
|
||||
);
|
||||
|
||||
try {
|
||||
return await asyncGuard(() async {
|
||||
device.devFS = WebDevFS(
|
||||
hostname: effectiveHostname,
|
||||
port: hostPort,
|
||||
packagesFilePath: packagesFilePath,
|
||||
urlTunneller: urlTunneller,
|
||||
buildMode: debuggingOptions.buildInfo.mode,
|
||||
enableDwds: _enableDwds,
|
||||
entrypoint: globals.fs.file(target).uri,
|
||||
);
|
||||
final Uri url = await device.devFS.create();
|
||||
if (debuggingOptions.buildInfo.isDebug) {
|
||||
final UpdateFSReport report = await _updateDevFS(fullRestart: true);
|
||||
if (!report.success) {
|
||||
globals.printError('Failed to compile application.');
|
||||
return 1;
|
||||
}
|
||||
device.generator.accept();
|
||||
} else {
|
||||
await buildWeb(
|
||||
flutterProject,
|
||||
target,
|
||||
debuggingOptions.buildInfo,
|
||||
debuggingOptions.initializePlatform,
|
||||
dartDefines,
|
||||
false,
|
||||
);
|
||||
}
|
||||
await device.device.startApp(
|
||||
package,
|
||||
mainPath: target,
|
||||
debuggingOptions: debuggingOptions,
|
||||
platformArgs: <String, Object>{
|
||||
'uri': url.toString(),
|
||||
},
|
||||
);
|
||||
return attach(
|
||||
connectionInfoCompleter: connectionInfoCompleter,
|
||||
appStartedCompleter: appStartedCompleter,
|
||||
);
|
||||
});
|
||||
} on WebSocketException {
|
||||
throwToolExit(kExitMessage);
|
||||
} on ChromeDebugException {
|
||||
throwToolExit(kExitMessage);
|
||||
} on AppConnectionException {
|
||||
throwToolExit(kExitMessage);
|
||||
} on SocketException {
|
||||
throwToolExit(kExitMessage);
|
||||
}
|
||||
await device.device.startApp(
|
||||
package,
|
||||
mainPath: target,
|
||||
debuggingOptions: debuggingOptions,
|
||||
platformArgs: <String, Object>{
|
||||
'uri': url.toString(),
|
||||
},
|
||||
);
|
||||
return attach(
|
||||
connectionInfoCompleter: connectionInfoCompleter,
|
||||
appStartedCompleter: appStartedCompleter,
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -479,22 +500,25 @@ class _ResidentWebRunner extends ResidentWebRunner {
|
|||
}
|
||||
}
|
||||
|
||||
Duration transferMarker;
|
||||
try {
|
||||
if (!deviceIsDebuggable) {
|
||||
globals.printStatus('Recompile complete. Page requires refresh.');
|
||||
} else if (fullRestart || !debuggingOptions.buildInfo.isDebug) {
|
||||
} else if (!debuggingOptions.buildInfo.isDebug) {
|
||||
// On non-debug builds, a hard refresh is required to ensure the
|
||||
// up to date sources are loaded.
|
||||
await _wipConnection?.sendCommand('Page.reload', <String, Object>{
|
||||
'ignoreCache': !debuggingOptions.buildInfo.isDebug,
|
||||
});
|
||||
} else {
|
||||
await _wipConnection?.debugger
|
||||
?.sendCommand('Runtime.evaluate', params: <String, Object>{
|
||||
'expression': 'window.\$hotReloadHook([$reloadModules])',
|
||||
'awaitPromise': true,
|
||||
'returnByValue': true,
|
||||
});
|
||||
transferMarker = timer.elapsed;
|
||||
await _wipConnection?.debugger?.sendCommand(
|
||||
'Runtime.evaluate', params: <String, Object>{
|
||||
'expression': 'window.\$hotReloadHook([$reloadModules])',
|
||||
'awaitPromise': true,
|
||||
'returnByValue': true,
|
||||
},
|
||||
);
|
||||
}
|
||||
} on WipError catch (err) {
|
||||
globals.printError(err.toString());
|
||||
|
@ -503,8 +527,8 @@ class _ResidentWebRunner extends ResidentWebRunner {
|
|||
status.stop();
|
||||
}
|
||||
|
||||
final String verb = fullRestart ? 'Restarted' : 'Reloaded';
|
||||
globals.printStatus('$verb application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
|
||||
final String elapsed = getElapsedAsMilliseconds(timer.elapsed);
|
||||
globals.printStatus('Restarted application in $elapsed.');
|
||||
|
||||
// Don't track restart times for dart2js builds or web-server devices.
|
||||
if (debuggingOptions.buildInfo.isDebug && deviceIsDebuggable) {
|
||||
|
@ -517,6 +541,7 @@ class _ResidentWebRunner extends ResidentWebRunner {
|
|||
fullRestart: true,
|
||||
reason: reason,
|
||||
overallTimeInMs: timer.elapsed.inMilliseconds,
|
||||
transferTimeInMs: timer.elapsed.inMilliseconds - transferMarker.inMilliseconds
|
||||
).send();
|
||||
}
|
||||
return OperationResult.ok;
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'dart:convert';
|
|||
|
||||
import 'package:dwds/dwds.dart';
|
||||
import 'package:flutter_tools/src/base/common.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/terminal.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
|
@ -316,7 +317,7 @@ void main() {
|
|||
|
||||
final OperationResult result = await residentWebRunner.restart(fullRestart: false);
|
||||
|
||||
expect(testLogger.statusText, contains('Reloaded application in'));
|
||||
expect(testLogger.statusText, contains('Restarted application in'));
|
||||
expect(result.code, 0);
|
||||
verify(mockResidentCompiler.accept()).called(2);
|
||||
// ensure that analytics are sent.
|
||||
|
@ -836,6 +837,112 @@ void main() {
|
|||
}, 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(mockWebDevFS.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,
|
||||
), throwsToolExit());
|
||||
|
||||
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(mockWebDevFS.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,
|
||||
), throwsToolExit());
|
||||
|
||||
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(mockWebDevFS.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,
|
||||
), throwsToolExit());
|
||||
|
||||
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(mockWebDevFS.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,
|
||||
), throwsException);
|
||||
|
||||
unhandledErrorCompleter.complete();
|
||||
await expectation;
|
||||
}));
|
||||
test('Rethrows unknown exception type from web tooling', () => testbed.run(() async {
|
||||
_setupMocks();
|
||||
final DelegateLogger delegateLogger = globals.logger as DelegateLogger;
|
||||
final MockStatus mockStatus = MockStatus();
|
||||
delegateLogger.status = mockStatus;
|
||||
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
|
||||
final Completer<void> unhandledErrorCompleter = Completer<void>();
|
||||
when(mockWebDevFS.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,
|
||||
), throwsStateError);
|
||||
|
||||
unhandledErrorCompleter.complete();
|
||||
await expectation;
|
||||
verify(mockStatus.stop()).called(1);
|
||||
}, overrides: <Type, Generator>{
|
||||
Logger: () => DelegateLogger(BufferLogger(
|
||||
terminal: AnsiTerminal(
|
||||
stdio: null,
|
||||
platform: const LocalPlatform(),
|
||||
),
|
||||
outputPreferences: OutputPreferences.test(),
|
||||
))
|
||||
}));
|
||||
}
|
||||
|
||||
class MockChromeLauncher extends Mock implements ChromeLauncher {}
|
||||
|
|
Loading…
Reference in a new issue