mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Send text error in JSON and print in tools (#58994)
* Revert "Revert "Send text error in JSON and print in tools (#58284)" (#58872)" This reverts commit c2d5e18cb2df9400ab3b34f12fb1eba957084e2f. * Put streamListen in try/catch if extension events already listened for
This commit is contained in:
parent
a802faa9a9
commit
f0174b176a
@ -929,8 +929,17 @@ mixin WidgetInspectorService {
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -2254,6 +2254,10 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
||||
|
||||
// Validate that we received an error count.
|
||||
expect(error['errorsSinceReload'], 0);
|
||||
expect(
|
||||
error['renderedErrorText'],
|
||||
startsWith(
|
||||
'══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞════════════'));
|
||||
|
||||
// Send a second error.
|
||||
FlutterError.reportError(FlutterErrorDetailsForRendering(
|
||||
@ -2267,6 +2271,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
||||
expect(flutterErrorEvents, hasLength(2));
|
||||
error = flutterErrorEvents.last;
|
||||
expect(error['errorsSinceReload'], 1);
|
||||
expect(error['renderedErrorText'], startsWith('Another exception was thrown:'));
|
||||
|
||||
// Reloads the app.
|
||||
final FlutterExceptionHandler oldHandler = FlutterError.onError;
|
||||
|
||||
@ -112,6 +112,7 @@ abstract class ResidentWebRunner extends ResidentRunner {
|
||||
ConnectionResult _connectionResult;
|
||||
StreamSubscription<vmservice.Event> _stdOutSub;
|
||||
StreamSubscription<vmservice.Event> _stdErrSub;
|
||||
StreamSubscription<vmservice.Event> _extensionEventSub;
|
||||
bool _exited = false;
|
||||
WipConnection _wipConnection;
|
||||
ChromiumLauncher _chromiumLauncher;
|
||||
@ -150,6 +151,7 @@ abstract class ResidentWebRunner extends ResidentRunner {
|
||||
}
|
||||
await _stdOutSub?.cancel();
|
||||
await _stdErrSub?.cancel();
|
||||
await _extensionEventSub?.cancel();
|
||||
await device.device.stopApp(null);
|
||||
try {
|
||||
_generatedEntrypointDirectory?.deleteSync(recursive: true);
|
||||
@ -675,6 +677,8 @@ class _ResidentWebRunner extends ResidentWebRunner {
|
||||
final String message = utf8.decode(base64.decode(log.bytes));
|
||||
globals.printStatus(message, newline: false);
|
||||
});
|
||||
_extensionEventSub =
|
||||
_vmService.onExtensionEvent.listen(printStructuredErrorLog);
|
||||
try {
|
||||
await _vmService.streamListen(vmservice.EventStreams.kStdout);
|
||||
} on vmservice.RPCError {
|
||||
@ -693,6 +697,12 @@ class _ResidentWebRunner extends ResidentWebRunner {
|
||||
// It is safe to ignore this error because we expect an error to be
|
||||
// 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'));
|
||||
_vmService.registerServiceCallback('reloadSources', (Map<String, Object> params) async {
|
||||
final bool pause = params['pause'] as bool ?? false;
|
||||
|
||||
@ -184,6 +184,7 @@ class FlutterDevice {
|
||||
CompileExpression compileExpression,
|
||||
ReloadMethod reloadMethod,
|
||||
GetSkSLMethod getSkSLMethod,
|
||||
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
|
||||
}) {
|
||||
final Completer<void> completer = Completer<void>();
|
||||
StreamSubscription<void> subscription;
|
||||
@ -203,6 +204,7 @@ class FlutterDevice {
|
||||
compileExpression: compileExpression,
|
||||
reloadMethod: reloadMethod,
|
||||
getSkSLMethod: getSkSLMethod,
|
||||
printStructuredErrorLogMethod: printStructuredErrorLogMethod,
|
||||
device: device,
|
||||
);
|
||||
} on Exception catch (exception) {
|
||||
@ -1055,11 +1057,23 @@ abstract class ResidentRunner {
|
||||
final String copyPath = getDefaultCachedKernelPath(
|
||||
trackWidgetCreation: trackWidgetCreation,
|
||||
);
|
||||
globals.fs.file(copyPath).parent.createSync(recursive: true);
|
||||
globals.fs
|
||||
.file(copyPath)
|
||||
.parent
|
||||
.createSync(recursive: true);
|
||||
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
|
||||
/// will be registered.
|
||||
//
|
||||
@ -1084,7 +1098,8 @@ abstract class ResidentRunner {
|
||||
restart: restart,
|
||||
compileExpression: compileExpression,
|
||||
reloadMethod: reloadMethod,
|
||||
getSkSLMethod: getSkSLMethod
|
||||
getSkSLMethod: getSkSLMethod,
|
||||
printStructuredErrorLogMethod: printStructuredErrorLog,
|
||||
);
|
||||
// This will wait for at least one flutter view before returning.
|
||||
final Status status = globals.logger.startProgress(
|
||||
|
||||
@ -31,6 +31,8 @@ const int kIsolateReloadBarred = 1005;
|
||||
/// for [WebSocket]s (used by tests).
|
||||
typedef WebSocketConnector = Future<io.WebSocket> Function(String url, {io.CompressionOptions compression});
|
||||
|
||||
typedef PrintStructuredErrorLogMethod = void Function(vm_service.Event);
|
||||
|
||||
WebSocketConnector _openChannel = _defaultOpenChannel;
|
||||
|
||||
/// The error codes for the JSON-RPC standard.
|
||||
@ -149,6 +151,7 @@ typedef VMServiceConnector = Future<vm_service.VmService> Function(Uri httpUri,
|
||||
CompileExpression compileExpression,
|
||||
ReloadMethod reloadMethod,
|
||||
GetSkSLMethod getSkSLMethod,
|
||||
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
|
||||
io.CompressionOptions compression,
|
||||
Device device,
|
||||
});
|
||||
@ -175,6 +178,7 @@ vm_service.VmService setUpVmService(
|
||||
Device device,
|
||||
ReloadMethod reloadMethod,
|
||||
GetSkSLMethod skSLMethod,
|
||||
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
|
||||
vm_service.VmService vmService
|
||||
) {
|
||||
if (reloadSources != null) {
|
||||
@ -293,6 +297,15 @@ vm_service.VmService setUpVmService(
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
@ -311,6 +324,7 @@ Future<vm_service.VmService> connectToVmService(
|
||||
CompileExpression compileExpression,
|
||||
ReloadMethod reloadMethod,
|
||||
GetSkSLMethod getSkSLMethod,
|
||||
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
|
||||
io.CompressionOptions compression = io.CompressionOptions.compressionDefault,
|
||||
Device device,
|
||||
}) async {
|
||||
@ -323,6 +337,7 @@ Future<vm_service.VmService> connectToVmService(
|
||||
device: device,
|
||||
reloadMethod: reloadMethod,
|
||||
getSkSLMethod: getSkSLMethod,
|
||||
printStructuredErrorLogMethod: printStructuredErrorLogMethod,
|
||||
);
|
||||
}
|
||||
|
||||
@ -333,6 +348,7 @@ Future<vm_service.VmService> _connect(
|
||||
CompileExpression compileExpression,
|
||||
ReloadMethod reloadMethod,
|
||||
GetSkSLMethod getSkSLMethod,
|
||||
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
|
||||
io.CompressionOptions compression = io.CompressionOptions.compressionDefault,
|
||||
Device device,
|
||||
}) async {
|
||||
@ -354,6 +370,7 @@ Future<vm_service.VmService> _connect(
|
||||
device,
|
||||
reloadMethod,
|
||||
getSkSLMethod,
|
||||
printStructuredErrorLogMethod,
|
||||
delegateService,
|
||||
);
|
||||
_httpAddressExpando[service] = httpUri;
|
||||
|
||||
@ -679,6 +679,7 @@ VMServiceConnector getFakeVmServiceFactory({
|
||||
CompileExpression compileExpression,
|
||||
ReloadMethod reloadMethod,
|
||||
GetSkSLMethod getSkSLMethod,
|
||||
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
|
||||
CompressionOptions compression,
|
||||
Device device,
|
||||
}) async {
|
||||
|
||||
@ -135,6 +135,7 @@ class TestFlutterDevice extends FlutterDevice {
|
||||
CompileExpression compileExpression,
|
||||
ReloadMethod reloadMethod,
|
||||
GetSkSLMethod getSkSLMethod,
|
||||
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
|
||||
}) async {
|
||||
throw exception;
|
||||
}
|
||||
|
||||
@ -510,6 +510,7 @@ class TestFlutterDevice extends FlutterDevice {
|
||||
CompileExpression compileExpression,
|
||||
ReloadMethod reloadMethod,
|
||||
GetSkSLMethod getSkSLMethod,
|
||||
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
|
||||
}) async {
|
||||
throw exception;
|
||||
}
|
||||
|
||||
@ -1357,6 +1357,7 @@ void main() {
|
||||
CompileExpression compileExpression,
|
||||
ReloadMethod reloadMethod,
|
||||
GetSkSLMethod getSkSLMethod,
|
||||
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
|
||||
io.CompressionOptions compression,
|
||||
Device device,
|
||||
}) async => mockVMService,
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dwds/dwds.dart';
|
||||
import 'package:flutter_tools/src/base/common.dart';
|
||||
@ -56,6 +57,12 @@ const List<VmServiceExpectation> kAttachIsolateExpectations = <VmServiceExpectat
|
||||
'streamId': 'Isolate'
|
||||
}
|
||||
),
|
||||
FakeVmServiceRequest(
|
||||
method: 'streamListen',
|
||||
args: <String, Object>{
|
||||
'streamId': 'Extension',
|
||||
},
|
||||
),
|
||||
FakeVmServiceRequest(
|
||||
method: 'registerService',
|
||||
args: <String, Object>{
|
||||
@ -340,6 +347,68 @@ void main() {
|
||||
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 {
|
||||
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
|
||||
residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
|
||||
@ -1073,19 +1142,7 @@ void main() {
|
||||
test('Sends launched app.webLaunchUrl event for Chrome device', () => testbed.run(() async {
|
||||
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
|
||||
...kAttachLogExpectations,
|
||||
const FakeVmServiceRequest(
|
||||
method: 'streamListen',
|
||||
args: <String, Object>{
|
||||
'streamId': 'Isolate'
|
||||
}
|
||||
),
|
||||
const FakeVmServiceRequest(
|
||||
method: 'registerService',
|
||||
args: <String, Object>{
|
||||
'service': 'reloadSources',
|
||||
'alias': 'FlutterTools',
|
||||
}
|
||||
)
|
||||
...kAttachIsolateExpectations,
|
||||
]);
|
||||
_setupMocks();
|
||||
final ChromiumLauncher chromiumLauncher = MockChromeLauncher();
|
||||
|
||||
@ -104,6 +104,7 @@ void main() {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
mockVMService,
|
||||
);
|
||||
|
||||
@ -123,6 +124,7 @@ void main() {
|
||||
null,
|
||||
reloadMethod,
|
||||
null,
|
||||
null,
|
||||
mockVMService,
|
||||
);
|
||||
|
||||
@ -142,6 +144,7 @@ void main() {
|
||||
mockDevice,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
mockVMService,
|
||||
);
|
||||
|
||||
@ -159,6 +162,7 @@ void main() {
|
||||
null,
|
||||
null,
|
||||
() async => 'hello',
|
||||
null,
|
||||
mockVMService,
|
||||
);
|
||||
|
||||
@ -167,6 +171,26 @@ void main() {
|
||||
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 {
|
||||
final MockVMService mockVMService = MockVMService();
|
||||
setUpVmService(
|
||||
@ -176,6 +200,7 @@ void main() {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
mockVMService,
|
||||
);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user