mirror of
https://github.com/flutter/flutter
synced 2024-10-14 04:02:56 +00:00
Add function to set structured error early (#58118)
* Add function to set structured error early * Remove unused imports * Save test handler in setUp * Add utils file for shared test service * Rename structured error functions * Use setUpAll to save error handler and call initStructuredError from other init * Rename widget inspector test service * Remove debugging print statement * Move error handling setting back to initServiceExtensions * Rename structured error handler in test * Namespace environment variable * Rename test
This commit is contained in:
parent
a1636b6fef
commit
479f370379
|
@ -744,6 +744,8 @@ mixin WidgetInspectorService {
|
||||||
bool _trackRebuildDirtyWidgets = false;
|
bool _trackRebuildDirtyWidgets = false;
|
||||||
bool _trackRepaintWidgets = false;
|
bool _trackRepaintWidgets = false;
|
||||||
|
|
||||||
|
FlutterExceptionHandler _structuredExceptionHandler;
|
||||||
|
|
||||||
_RegisterServiceExtensionCallback _registerServiceExtensionCallback;
|
_RegisterServiceExtensionCallback _registerServiceExtensionCallback;
|
||||||
/// Registers a service extension method with the given name (full
|
/// Registers a service extension method with the given name (full
|
||||||
/// name "ext.flutter.inspector.name").
|
/// name "ext.flutter.inspector.name").
|
||||||
|
@ -941,6 +943,10 @@ mixin WidgetInspectorService {
|
||||||
_errorsSinceReload = 0;
|
_errorsSinceReload = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isStructuredErrorsEnabled() {
|
||||||
|
return const bool.fromEnvironment('flutter.inspector.structuredErrors');
|
||||||
|
}
|
||||||
|
|
||||||
/// Called to register service extensions.
|
/// Called to register service extensions.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
|
@ -949,6 +955,10 @@ mixin WidgetInspectorService {
|
||||||
/// * [BindingBase.initServiceExtensions], which explains when service
|
/// * [BindingBase.initServiceExtensions], which explains when service
|
||||||
/// extensions can be used.
|
/// extensions can be used.
|
||||||
void initServiceExtensions(_RegisterServiceExtensionCallback registerServiceExtensionCallback) {
|
void initServiceExtensions(_RegisterServiceExtensionCallback registerServiceExtensionCallback) {
|
||||||
|
_structuredExceptionHandler = _reportError;
|
||||||
|
if (isStructuredErrorsEnabled()) {
|
||||||
|
FlutterError.onError = _structuredExceptionHandler;
|
||||||
|
}
|
||||||
_registerServiceExtensionCallback = registerServiceExtensionCallback;
|
_registerServiceExtensionCallback = registerServiceExtensionCallback;
|
||||||
assert(!_debugServiceExtensionsRegistered);
|
assert(!_debugServiceExtensionsRegistered);
|
||||||
assert(() {
|
assert(() {
|
||||||
|
@ -958,14 +968,13 @@ mixin WidgetInspectorService {
|
||||||
|
|
||||||
SchedulerBinding.instance.addPersistentFrameCallback(_onFrameStart);
|
SchedulerBinding.instance.addPersistentFrameCallback(_onFrameStart);
|
||||||
|
|
||||||
final FlutterExceptionHandler structuredExceptionHandler = _reportError;
|
|
||||||
final FlutterExceptionHandler defaultExceptionHandler = FlutterError.presentError;
|
final FlutterExceptionHandler defaultExceptionHandler = FlutterError.presentError;
|
||||||
|
|
||||||
_registerBoolServiceExtension(
|
_registerBoolServiceExtension(
|
||||||
name: 'structuredErrors',
|
name: 'structuredErrors',
|
||||||
getter: () async => FlutterError.presentError == structuredExceptionHandler,
|
getter: () async => FlutterError.presentError == _structuredExceptionHandler,
|
||||||
setter: (bool value) {
|
setter: (bool value) {
|
||||||
FlutterError.presentError = value ? structuredExceptionHandler : defaultExceptionHandler;
|
FlutterError.presentError = value ? _structuredExceptionHandler : defaultExceptionHandler;
|
||||||
return Future<void>.value();
|
return Future<void>.value();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
// 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 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'widget_inspector_test_utils.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
StructuredErrorTestService.runTests();
|
||||||
|
}
|
||||||
|
|
||||||
|
class StructuredErrorTestService extends TestWidgetInspectorService {
|
||||||
|
@override
|
||||||
|
bool isStructuredErrorsEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void runTests() {
|
||||||
|
final StructuredErrorTestService service = StructuredErrorTestService();
|
||||||
|
WidgetInspectorService.instance = service;
|
||||||
|
FlutterExceptionHandler testHandler;
|
||||||
|
FlutterExceptionHandler inspectorServiceErrorHandler;
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
inspectorServiceErrorHandler = FlutterError.onError;
|
||||||
|
});
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
testHandler = FlutterError.onError;
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ext.flutter.inspector.setStructuredErrors',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
// The test framework resets FlutterError.onError, so we set it back to
|
||||||
|
// what it was after WidgetInspectorService::initServiceExtensions ran.
|
||||||
|
FlutterError.onError = inspectorServiceErrorHandler;
|
||||||
|
|
||||||
|
List<Map<Object, Object>> flutterErrorEvents =
|
||||||
|
service.getEventsDispatched('Flutter.Error');
|
||||||
|
expect(flutterErrorEvents, hasLength(0));
|
||||||
|
|
||||||
|
// Create an error.
|
||||||
|
FlutterError.reportError(FlutterErrorDetailsForRendering(
|
||||||
|
library: 'rendering library',
|
||||||
|
context: ErrorDescription('during layout'),
|
||||||
|
exception: StackTrace.current,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Validate that we received an error.
|
||||||
|
flutterErrorEvents = service.getEventsDispatched('Flutter.Error');
|
||||||
|
expect(flutterErrorEvents, hasLength(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
FlutterError.onError = testHandler;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,8 @@ import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'widget_inspector_test_utils.dart';
|
||||||
|
|
||||||
// Start of block of code where widget creation location line numbers and
|
// Start of block of code where widget creation location line numbers and
|
||||||
// columns will impact whether tests pass.
|
// columns will impact whether tests pass.
|
||||||
|
|
||||||
|
@ -222,64 +224,10 @@ int getChildLayerCount(OffsetLayer layer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
TestWidgetInspectorService.runTests();
|
_TestWidgetInspectorService.runTests();
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestWidgetInspectorService extends Object with WidgetInspectorService {
|
class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
||||||
final Map<String, InspectorServiceExtensionCallback> extensions = <String, InspectorServiceExtensionCallback>{};
|
|
||||||
|
|
||||||
final Map<String, List<Map<Object, Object>>> eventsDispatched = <String, List<Map<Object, Object>>>{};
|
|
||||||
|
|
||||||
@override
|
|
||||||
void registerServiceExtension({
|
|
||||||
@required String name,
|
|
||||||
@required FutureOr<Map<String, Object>> callback(Map<String, String> parameters),
|
|
||||||
}) {
|
|
||||||
assert(!extensions.containsKey(name));
|
|
||||||
extensions[name] = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void postEvent(String eventKind, Map<Object, Object> eventData) {
|
|
||||||
getEventsDispatched(eventKind).add(eventData);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Map<Object, Object>> getEventsDispatched(String eventKind) {
|
|
||||||
return eventsDispatched.putIfAbsent(eventKind, () => <Map<Object, Object>>[]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterable<Map<Object, Object>> getServiceExtensionStateChangedEvents(String extensionName) {
|
|
||||||
return getEventsDispatched('Flutter.ServiceExtensionStateChanged')
|
|
||||||
.where((Map<Object, Object> event) => event['extension'] == extensionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Object> testExtension(String name, Map<String, String> arguments) async {
|
|
||||||
expect(extensions, contains(name));
|
|
||||||
// Encode and decode to JSON to match behavior using a real service
|
|
||||||
// extension where only JSON is allowed.
|
|
||||||
return json.decode(json.encode(await extensions[name](arguments)))['result'];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> testBoolExtension(String name, Map<String, String> arguments) async {
|
|
||||||
expect(extensions, contains(name));
|
|
||||||
// Encode and decode to JSON to match behavior using a real service
|
|
||||||
// extension where only JSON is allowed.
|
|
||||||
return json.decode(json.encode(await extensions[name](arguments)))['enabled'] as String;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rebuildCount = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> forceRebuild() async {
|
|
||||||
rebuildCount++;
|
|
||||||
final WidgetsBinding binding = WidgetsBinding.instance;
|
|
||||||
|
|
||||||
if (binding.renderViewElement != null) {
|
|
||||||
binding.buildOwner.reassemble(binding.renderViewElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// These tests need access to protected members of WidgetInspectorService.
|
// These tests need access to protected members of WidgetInspectorService.
|
||||||
static void runTests() {
|
static void runTests() {
|
||||||
final TestWidgetInspectorService service = TestWidgetInspectorService();
|
final TestWidgetInspectorService service = TestWidgetInspectorService();
|
||||||
|
@ -1725,7 +1673,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
|
||||||
_CreationLocation location = knownLocations[id];
|
_CreationLocation location = knownLocations[id];
|
||||||
expect(location.file, equals(file));
|
expect(location.file, equals(file));
|
||||||
// ClockText widget.
|
// ClockText widget.
|
||||||
expect(location.line, equals(51));
|
expect(location.line, equals(53));
|
||||||
expect(location.column, equals(9));
|
expect(location.column, equals(9));
|
||||||
expect(count, equals(1));
|
expect(count, equals(1));
|
||||||
|
|
||||||
|
@ -1734,7 +1682,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
|
||||||
location = knownLocations[id];
|
location = knownLocations[id];
|
||||||
expect(location.file, equals(file));
|
expect(location.file, equals(file));
|
||||||
// Text widget in _ClockTextState build method.
|
// Text widget in _ClockTextState build method.
|
||||||
expect(location.line, equals(89));
|
expect(location.line, equals(91));
|
||||||
expect(location.column, equals(12));
|
expect(location.column, equals(12));
|
||||||
expect(count, equals(1));
|
expect(count, equals(1));
|
||||||
|
|
||||||
|
@ -1759,7 +1707,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
|
||||||
location = knownLocations[id];
|
location = knownLocations[id];
|
||||||
expect(location.file, equals(file));
|
expect(location.file, equals(file));
|
||||||
// ClockText widget.
|
// ClockText widget.
|
||||||
expect(location.line, equals(51));
|
expect(location.line, equals(53));
|
||||||
expect(location.column, equals(9));
|
expect(location.column, equals(9));
|
||||||
expect(count, equals(3)); // 3 clock widget instances rebuilt.
|
expect(count, equals(3)); // 3 clock widget instances rebuilt.
|
||||||
|
|
||||||
|
@ -1768,7 +1716,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
|
||||||
location = knownLocations[id];
|
location = knownLocations[id];
|
||||||
expect(location.file, equals(file));
|
expect(location.file, equals(file));
|
||||||
// Text widget in _ClockTextState build method.
|
// Text widget in _ClockTextState build method.
|
||||||
expect(location.line, equals(89));
|
expect(location.line, equals(91));
|
||||||
expect(location.column, equals(12));
|
expect(location.column, equals(12));
|
||||||
expect(count, equals(3)); // 3 clock widget instances rebuilt.
|
expect(count, equals(3)); // 3 clock widget instances rebuilt.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
// 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:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
typedef InspectorServiceExtensionCallback = FutureOr<Map<String, Object>> Function(Map<String, String> parameters);
|
||||||
|
|
||||||
|
class TestWidgetInspectorService extends Object with WidgetInspectorService {
|
||||||
|
final Map<String, InspectorServiceExtensionCallback> extensions = <String, InspectorServiceExtensionCallback>{};
|
||||||
|
|
||||||
|
final Map<String, List<Map<Object, Object>>> eventsDispatched = <String, List<Map<Object, Object>>>{};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void registerServiceExtension({
|
||||||
|
@required String name,
|
||||||
|
@required FutureOr<Map<String, Object>> callback(Map<String, String> parameters),
|
||||||
|
}) {
|
||||||
|
assert(!extensions.containsKey(name));
|
||||||
|
extensions[name] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void postEvent(String eventKind, Map<Object, Object> eventData) {
|
||||||
|
getEventsDispatched(eventKind).add(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<Object, Object>> getEventsDispatched(String eventKind) {
|
||||||
|
return eventsDispatched.putIfAbsent(eventKind, () => <Map<Object, Object>>[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable<Map<Object, Object>> getServiceExtensionStateChangedEvents(String extensionName) {
|
||||||
|
return getEventsDispatched('Flutter.ServiceExtensionStateChanged')
|
||||||
|
.where((Map<Object, Object> event) => event['extension'] == extensionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Object> testExtension(String name, Map<String, String> arguments) async {
|
||||||
|
expect(extensions, contains(name));
|
||||||
|
// Encode and decode to JSON to match behavior using a real service
|
||||||
|
// extension where only JSON is allowed.
|
||||||
|
return json.decode(json.encode(await extensions[name](arguments)))['result'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> testBoolExtension(String name, Map<String, String> arguments) async {
|
||||||
|
expect(extensions, contains(name));
|
||||||
|
// Encode and decode to JSON to match behavior using a real service
|
||||||
|
// extension where only JSON is allowed.
|
||||||
|
return json.decode(json.encode(await extensions[name](arguments)))['enabled'] as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rebuildCount = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> forceRebuild() async {
|
||||||
|
rebuildCount++;
|
||||||
|
final WidgetsBinding binding = WidgetsBinding.instance;
|
||||||
|
|
||||||
|
if (binding.renderViewElement != null) {
|
||||||
|
binding.buildOwner.reassemble(binding.renderViewElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue