Unverified Commit e62c1f69 authored by Helin Shiah's avatar Helin Shiah Committed by GitHub

Send text error in JSON and print in tools (#59018)

parent ec4049e7
...@@ -929,8 +929,17 @@ mixin WidgetInspectorService { ...@@ -929,8 +929,17 @@ mixin WidgetInspectorService {
); );
errorJson['errorsSinceReload'] = _errorsSinceReload; errorJson['errorsSinceReload'] = _errorsSinceReload;
_errorsSinceReload += 1; if (_errorsSinceReload == 0) {
errorJson['renderedErrorText'] = TextTreeRenderer(
wrapWidth: FlutterError.wrapWidth,
wrapWidthProperties: FlutterError.wrapWidth,
maxDescendentsTruncatableNode: 5,
).render(details.toDiagnosticsNode(style: DiagnosticsTreeStyle.error)).trimRight();
} else {
errorJson['renderedErrorText'] = 'Another exception was thrown: ${details.summary}';
}
_errorsSinceReload += 1;
postEvent('Flutter.Error', errorJson); postEvent('Flutter.Error', errorJson);
} }
......
...@@ -2254,6 +2254,10 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { ...@@ -2254,6 +2254,10 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
// Validate that we received an error count. // Validate that we received an error count.
expect(error['errorsSinceReload'], 0); expect(error['errorsSinceReload'], 0);
expect(
error['renderedErrorText'],
startsWith(
'══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞════════════'));
// Send a second error. // Send a second error.
FlutterError.reportError(FlutterErrorDetailsForRendering( FlutterError.reportError(FlutterErrorDetailsForRendering(
...@@ -2267,6 +2271,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { ...@@ -2267,6 +2271,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
expect(flutterErrorEvents, hasLength(2)); expect(flutterErrorEvents, hasLength(2));
error = flutterErrorEvents.last; error = flutterErrorEvents.last;
expect(error['errorsSinceReload'], 1); expect(error['errorsSinceReload'], 1);
expect(error['renderedErrorText'], startsWith('Another exception was thrown:'));
// Reloads the app. // Reloads the app.
final FlutterExceptionHandler oldHandler = FlutterError.onError; final FlutterExceptionHandler oldHandler = FlutterError.onError;
......
...@@ -112,6 +112,7 @@ abstract class ResidentWebRunner extends ResidentRunner { ...@@ -112,6 +112,7 @@ abstract class ResidentWebRunner extends ResidentRunner {
ConnectionResult _connectionResult; ConnectionResult _connectionResult;
StreamSubscription<vmservice.Event> _stdOutSub; StreamSubscription<vmservice.Event> _stdOutSub;
StreamSubscription<vmservice.Event> _stdErrSub; StreamSubscription<vmservice.Event> _stdErrSub;
StreamSubscription<vmservice.Event> _extensionEventSub;
bool _exited = false; bool _exited = false;
WipConnection _wipConnection; WipConnection _wipConnection;
ChromiumLauncher _chromiumLauncher; ChromiumLauncher _chromiumLauncher;
...@@ -150,6 +151,7 @@ abstract class ResidentWebRunner extends ResidentRunner { ...@@ -150,6 +151,7 @@ abstract class ResidentWebRunner extends ResidentRunner {
} }
await _stdOutSub?.cancel(); await _stdOutSub?.cancel();
await _stdErrSub?.cancel(); await _stdErrSub?.cancel();
await _extensionEventSub?.cancel();
await device.device.stopApp(null); await device.device.stopApp(null);
try { try {
_generatedEntrypointDirectory?.deleteSync(recursive: true); _generatedEntrypointDirectory?.deleteSync(recursive: true);
...@@ -675,6 +677,8 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -675,6 +677,8 @@ class _ResidentWebRunner extends ResidentWebRunner {
final String message = utf8.decode(base64.decode(log.bytes)); final String message = utf8.decode(base64.decode(log.bytes));
globals.printStatus(message, newline: false); globals.printStatus(message, newline: false);
}); });
_extensionEventSub =
_vmService.onExtensionEvent.listen(printStructuredErrorLog);
try { try {
await _vmService.streamListen(vmservice.EventStreams.kStdout); await _vmService.streamListen(vmservice.EventStreams.kStdout);
} on vmservice.RPCError { } on vmservice.RPCError {
...@@ -693,6 +697,12 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -693,6 +697,12 @@ class _ResidentWebRunner extends ResidentWebRunner {
// It is safe to ignore this error because we expect an error to be // It is safe to ignore this error because we expect an error to be
// thrown if we're not already subscribed. // thrown if we're not already subscribed.
} }
try {
await _vmService.streamListen(vmservice.EventStreams.kExtension);
} on vmservice.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're not already subscribed.
}
unawaited(_vmService.registerService('reloadSources', 'FlutterTools')); unawaited(_vmService.registerService('reloadSources', 'FlutterTools'));
_vmService.registerServiceCallback('reloadSources', (Map<String, Object> params) async { _vmService.registerServiceCallback('reloadSources', (Map<String, Object> params) async {
final bool pause = params['pause'] as bool ?? false; final bool pause = params['pause'] as bool ?? false;
......
...@@ -188,6 +188,7 @@ class FlutterDevice { ...@@ -188,6 +188,7 @@ class FlutterDevice {
CompileExpression compileExpression, CompileExpression compileExpression,
ReloadMethod reloadMethod, ReloadMethod reloadMethod,
GetSkSLMethod getSkSLMethod, GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
}) { }) {
final Completer<void> completer = Completer<void>(); final Completer<void> completer = Completer<void>();
StreamSubscription<void> subscription; StreamSubscription<void> subscription;
...@@ -207,6 +208,7 @@ class FlutterDevice { ...@@ -207,6 +208,7 @@ class FlutterDevice {
compileExpression: compileExpression, compileExpression: compileExpression,
reloadMethod: reloadMethod, reloadMethod: reloadMethod,
getSkSLMethod: getSkSLMethod, getSkSLMethod: getSkSLMethod,
printStructuredErrorLogMethod: printStructuredErrorLogMethod,
device: device, device: device,
); );
} on Exception catch (exception) { } on Exception catch (exception) {
...@@ -1061,11 +1063,23 @@ abstract class ResidentRunner { ...@@ -1061,11 +1063,23 @@ abstract class ResidentRunner {
final String copyPath = getDefaultCachedKernelPath( final String copyPath = getDefaultCachedKernelPath(
trackWidgetCreation: trackWidgetCreation, trackWidgetCreation: trackWidgetCreation,
); );
globals.fs.file(copyPath).parent.createSync(recursive: true); globals.fs
.file(copyPath)
.parent
.createSync(recursive: true);
outputDill.copySync(copyPath); outputDill.copySync(copyPath);
} }
} }
void printStructuredErrorLog(vm_service.Event event) {
if (event.extensionKind == 'Flutter.Error') {
final Map<dynamic, dynamic> json = event.extensionData?.data;
if (json != null && json.containsKey('renderedErrorText')) {
globals.printStatus('\n${json['renderedErrorText']}');
}
}
}
/// If the [reloadSources] parameter is not null the 'reloadSources' service /// If the [reloadSources] parameter is not null the 'reloadSources' service
/// will be registered. /// will be registered.
// //
...@@ -1090,7 +1104,8 @@ abstract class ResidentRunner { ...@@ -1090,7 +1104,8 @@ abstract class ResidentRunner {
restart: restart, restart: restart,
compileExpression: compileExpression, compileExpression: compileExpression,
reloadMethod: reloadMethod, reloadMethod: reloadMethod,
getSkSLMethod: getSkSLMethod getSkSLMethod: getSkSLMethod,
printStructuredErrorLogMethod: printStructuredErrorLog,
); );
// This will wait for at least one flutter view before returning. // This will wait for at least one flutter view before returning.
final Status status = globals.logger.startProgress( final Status status = globals.logger.startProgress(
......
...@@ -46,7 +46,12 @@ class Tracing { ...@@ -46,7 +46,12 @@ class Tracing {
); );
try { try {
final Completer<void> whenFirstFrameRendered = Completer<void>(); final Completer<void> whenFirstFrameRendered = Completer<void>();
await vmService.streamListen('Extension'); try {
await vmService.streamListen(vm_service.EventStreams.kExtension);
} on vm_service.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're already subscribed.
}
vmService.onExtensionEvent.listen((vm_service.Event event) { vmService.onExtensionEvent.listen((vm_service.Event event) {
if (event.extensionKind == 'Flutter.FirstFrame') { if (event.extensionKind == 'Flutter.FirstFrame') {
whenFirstFrameRendered.complete(); whenFirstFrameRendered.complete();
......
...@@ -31,6 +31,8 @@ const int kIsolateReloadBarred = 1005; ...@@ -31,6 +31,8 @@ const int kIsolateReloadBarred = 1005;
/// for [WebSocket]s (used by tests). /// for [WebSocket]s (used by tests).
typedef WebSocketConnector = Future<io.WebSocket> Function(String url, {io.CompressionOptions compression}); typedef WebSocketConnector = Future<io.WebSocket> Function(String url, {io.CompressionOptions compression});
typedef PrintStructuredErrorLogMethod = void Function(vm_service.Event);
WebSocketConnector _openChannel = _defaultOpenChannel; WebSocketConnector _openChannel = _defaultOpenChannel;
/// The error codes for the JSON-RPC standard. /// The error codes for the JSON-RPC standard.
...@@ -149,6 +151,7 @@ typedef VMServiceConnector = Future<vm_service.VmService> Function(Uri httpUri, ...@@ -149,6 +151,7 @@ typedef VMServiceConnector = Future<vm_service.VmService> Function(Uri httpUri,
CompileExpression compileExpression, CompileExpression compileExpression,
ReloadMethod reloadMethod, ReloadMethod reloadMethod,
GetSkSLMethod getSkSLMethod, GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
io.CompressionOptions compression, io.CompressionOptions compression,
Device device, Device device,
}); });
...@@ -175,6 +178,7 @@ vm_service.VmService setUpVmService( ...@@ -175,6 +178,7 @@ vm_service.VmService setUpVmService(
Device device, Device device,
ReloadMethod reloadMethod, ReloadMethod reloadMethod,
GetSkSLMethod skSLMethod, GetSkSLMethod skSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
vm_service.VmService vmService vm_service.VmService vmService
) { ) {
if (reloadSources != null) { if (reloadSources != null) {
...@@ -293,6 +297,15 @@ vm_service.VmService setUpVmService( ...@@ -293,6 +297,15 @@ vm_service.VmService setUpVmService(
}); });
vmService.registerService('flutterGetSkSL', 'Flutter Tools'); vmService.registerService('flutterGetSkSL', 'Flutter Tools');
} }
if (printStructuredErrorLogMethod != null) {
try {
vmService.streamListen(vm_service.EventStreams.kExtension);
} on vm_service.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're already subscribed.
}
vmService.onExtensionEvent.listen(printStructuredErrorLogMethod);
}
return vmService; return vmService;
} }
...@@ -311,6 +324,7 @@ Future<vm_service.VmService> connectToVmService( ...@@ -311,6 +324,7 @@ Future<vm_service.VmService> connectToVmService(
CompileExpression compileExpression, CompileExpression compileExpression,
ReloadMethod reloadMethod, ReloadMethod reloadMethod,
GetSkSLMethod getSkSLMethod, GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
io.CompressionOptions compression = io.CompressionOptions.compressionDefault, io.CompressionOptions compression = io.CompressionOptions.compressionDefault,
Device device, Device device,
}) async { }) async {
...@@ -323,6 +337,7 @@ Future<vm_service.VmService> connectToVmService( ...@@ -323,6 +337,7 @@ Future<vm_service.VmService> connectToVmService(
device: device, device: device,
reloadMethod: reloadMethod, reloadMethod: reloadMethod,
getSkSLMethod: getSkSLMethod, getSkSLMethod: getSkSLMethod,
printStructuredErrorLogMethod: printStructuredErrorLogMethod,
); );
} }
...@@ -333,6 +348,7 @@ Future<vm_service.VmService> _connect( ...@@ -333,6 +348,7 @@ Future<vm_service.VmService> _connect(
CompileExpression compileExpression, CompileExpression compileExpression,
ReloadMethod reloadMethod, ReloadMethod reloadMethod,
GetSkSLMethod getSkSLMethod, GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
io.CompressionOptions compression = io.CompressionOptions.compressionDefault, io.CompressionOptions compression = io.CompressionOptions.compressionDefault,
Device device, Device device,
}) async { }) async {
...@@ -354,6 +370,7 @@ Future<vm_service.VmService> _connect( ...@@ -354,6 +370,7 @@ Future<vm_service.VmService> _connect(
device, device,
reloadMethod, reloadMethod,
getSkSLMethod, getSkSLMethod,
printStructuredErrorLogMethod,
delegateService, delegateService,
); );
_httpAddressExpando[service] = httpUri; _httpAddressExpando[service] = httpUri;
......
...@@ -705,6 +705,7 @@ VMServiceConnector getFakeVmServiceFactory({ ...@@ -705,6 +705,7 @@ VMServiceConnector getFakeVmServiceFactory({
CompileExpression compileExpression, CompileExpression compileExpression,
ReloadMethod reloadMethod, ReloadMethod reloadMethod,
GetSkSLMethod getSkSLMethod, GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
CompressionOptions compression, CompressionOptions compression,
Device device, Device device,
}) async { }) async {
......
...@@ -135,6 +135,7 @@ class TestFlutterDevice extends FlutterDevice { ...@@ -135,6 +135,7 @@ class TestFlutterDevice extends FlutterDevice {
CompileExpression compileExpression, CompileExpression compileExpression,
ReloadMethod reloadMethod, ReloadMethod reloadMethod,
GetSkSLMethod getSkSLMethod, GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
}) async { }) async {
throw exception; throw exception;
} }
......
...@@ -510,6 +510,7 @@ class TestFlutterDevice extends FlutterDevice { ...@@ -510,6 +510,7 @@ class TestFlutterDevice extends FlutterDevice {
CompileExpression compileExpression, CompileExpression compileExpression,
ReloadMethod reloadMethod, ReloadMethod reloadMethod,
GetSkSLMethod getSkSLMethod, GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
}) async { }) async {
throw exception; throw exception;
} }
......
...@@ -1357,6 +1357,7 @@ void main() { ...@@ -1357,6 +1357,7 @@ void main() {
CompileExpression compileExpression, CompileExpression compileExpression,
ReloadMethod reloadMethod, ReloadMethod reloadMethod,
GetSkSLMethod getSkSLMethod, GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
io.CompressionOptions compression, io.CompressionOptions compression,
Device device, Device device,
}) async => mockVMService, }) async => mockVMService,
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:dwds/dwds.dart'; import 'package:dwds/dwds.dart';
import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/common.dart';
...@@ -56,6 +57,12 @@ const List<VmServiceExpectation> kAttachIsolateExpectations = <VmServiceExpectat ...@@ -56,6 +57,12 @@ const List<VmServiceExpectation> kAttachIsolateExpectations = <VmServiceExpectat
'streamId': 'Isolate' 'streamId': 'Isolate'
} }
), ),
FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Extension',
},
),
FakeVmServiceRequest( FakeVmServiceRequest(
method: 'registerService', method: 'registerService',
args: <String, Object>{ args: <String, Object>{
...@@ -340,6 +347,68 @@ void main() { ...@@ -340,6 +347,68 @@ void main() {
expect(testLogger.statusText, contains('SO IS THIS')); expect(testLogger.statusText, contains('SO IS THIS'));
})); }));
test('Listens to extension events with structured errors', () => testbed.run(() async {
final Map<String, String> extensionData = <String, String>{
'test': 'data',
'renderedErrorText': 'error text',
};
final Map<String, String> emptyExtensionData = <String, String>{
'test': 'data',
'renderedErrorText': '',
};
final Map<String, String> nonStructuredErrorData = <String, String>{
'other': 'other stuff',
};
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
FakeVmServiceStreamResponse(
streamId: 'Extension',
event: vm_service.Event(
timestamp: 0,
extensionKind: 'Flutter.Error',
extensionData: vm_service.ExtensionData.parse(extensionData),
kind: vm_service.EventStreams.kExtension,
),
),
// Empty error text should not break anything.
FakeVmServiceStreamResponse(
streamId: 'Extension',
event: vm_service.Event(
timestamp: 0,
extensionKind: 'Flutter.Error',
extensionData: vm_service.ExtensionData.parse(emptyExtensionData),
kind: vm_service.EventStreams.kExtension,
),
),
// This is not Flutter.Error kind data, so it should not be logged.
FakeVmServiceStreamResponse(
streamId: 'Extension',
event: vm_service.Event(
timestamp: 0,
extensionKind: 'Other',
extensionData: vm_service.ExtensionData.parse(nonStructuredErrorData),
kind: vm_service.EventStreams.kExtension,
),
),
]);
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
// Need these to run events, otherwise expect statements below run before
// structured errors are processed.
await null;
await null;
await null;
expect(testLogger.statusText, contains('\nerror text'));
expect(testLogger.statusText, isNot(contains('other stuff')));
}));
test('Does not run main with --start-paused', () => testbed.run(() async { test('Does not run main with --start-paused', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
residentWebRunner = DwdsWebRunnerFactory().createWebRunner( residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
...@@ -1073,19 +1142,7 @@ void main() { ...@@ -1073,19 +1142,7 @@ void main() {
test('Sends launched app.webLaunchUrl event for Chrome device', () => testbed.run(() async { test('Sends launched app.webLaunchUrl event for Chrome device', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachLogExpectations, ...kAttachLogExpectations,
const FakeVmServiceRequest( ...kAttachIsolateExpectations,
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate'
}
),
const FakeVmServiceRequest(
method: 'registerService',
args: <String, Object>{
'service': 'reloadSources',
'alias': 'FlutterTools',
}
)
]); ]);
_setupMocks(); _setupMocks();
final ChromiumLauncher chromiumLauncher = MockChromeLauncher(); final ChromiumLauncher chromiumLauncher = MockChromeLauncher();
......
...@@ -104,6 +104,7 @@ void main() { ...@@ -104,6 +104,7 @@ void main() {
null, null,
null, null,
null, null,
null,
mockVMService, mockVMService,
); );
...@@ -123,6 +124,7 @@ void main() { ...@@ -123,6 +124,7 @@ void main() {
null, null,
reloadMethod, reloadMethod,
null, null,
null,
mockVMService, mockVMService,
); );
...@@ -142,6 +144,7 @@ void main() { ...@@ -142,6 +144,7 @@ void main() {
mockDevice, mockDevice,
null, null,
null, null,
null,
mockVMService, mockVMService,
); );
...@@ -159,6 +162,7 @@ void main() { ...@@ -159,6 +162,7 @@ void main() {
null, null,
null, null,
() async => 'hello', () async => 'hello',
null,
mockVMService, mockVMService,
); );
...@@ -167,6 +171,26 @@ void main() { ...@@ -167,6 +171,26 @@ void main() {
Logger: () => BufferLogger.test() Logger: () => BufferLogger.test()
}); });
testUsingContext('VmService registers flutterPrintStructuredErrorLogMethod', () async {
final MockVMService mockVMService = MockVMService();
when(mockVMService.onExtensionEvent).thenAnswer((Invocation invocation) {
return const Stream<vm_service.Event>.empty();
});
setUpVmService(
null,
null,
null,
null,
null,
null,
(vm_service.Event event) async => 'hello',
mockVMService,
);
verify(mockVMService.streamListen(vm_service.EventStreams.kExtension)).called(1);
}, overrides: <Type, Generator>{
Logger: () => BufferLogger.test()
});
testUsingContext('VMService returns correct FlutterVersion', () async { testUsingContext('VMService returns correct FlutterVersion', () async {
final MockVMService mockVMService = MockVMService(); final MockVMService mockVMService = MockVMService();
setUpVmService( setUpVmService(
...@@ -176,6 +200,7 @@ void main() { ...@@ -176,6 +200,7 @@ void main() {
null, null,
null, null,
null, null,
null,
mockVMService, mockVMService,
); );
......
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