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
diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart
index 361ff1e..34469fb 100644
--- a/packages/flutter/lib/src/widgets/widget_inspector.dart
+++ b/packages/flutter/lib/src/widgets/widget_inspector.dart
@@ -744,6 +744,8 @@
bool _trackRebuildDirtyWidgets = false;
bool _trackRepaintWidgets = false;
+ FlutterExceptionHandler _structuredExceptionHandler;
+
_RegisterServiceExtensionCallback _registerServiceExtensionCallback;
/// Registers a service extension method with the given name (full
/// name "ext.flutter.inspector.name").
@@ -941,6 +943,10 @@
_errorsSinceReload = 0;
}
+ bool isStructuredErrorsEnabled() {
+ return const bool.fromEnvironment('flutter.inspector.structuredErrors');
+ }
+
/// Called to register service extensions.
///
/// See also:
@@ -949,6 +955,10 @@
/// * [BindingBase.initServiceExtensions], which explains when service
/// extensions can be used.
void initServiceExtensions(_RegisterServiceExtensionCallback registerServiceExtensionCallback) {
+ _structuredExceptionHandler = _reportError;
+ if (isStructuredErrorsEnabled()) {
+ FlutterError.onError = _structuredExceptionHandler;
+ }
_registerServiceExtensionCallback = registerServiceExtensionCallback;
assert(!_debugServiceExtensionsRegistered);
assert(() {
@@ -958,14 +968,13 @@
SchedulerBinding.instance.addPersistentFrameCallback(_onFrameStart);
- final FlutterExceptionHandler structuredExceptionHandler = _reportError;
final FlutterExceptionHandler defaultExceptionHandler = FlutterError.presentError;
_registerBoolServiceExtension(
name: 'structuredErrors',
- getter: () async => FlutterError.presentError == structuredExceptionHandler,
+ getter: () async => FlutterError.presentError == _structuredExceptionHandler,
setter: (bool value) {
- FlutterError.presentError = value ? structuredExceptionHandler : defaultExceptionHandler;
+ FlutterError.presentError = value ? _structuredExceptionHandler : defaultExceptionHandler;
return Future<void>.value();
},
);
diff --git a/packages/flutter/test/widgets/widget_inspector_init_with_structured_error_test.dart b/packages/flutter/test/widgets/widget_inspector_init_with_structured_error_test.dart
new file mode 100644
index 0000000..50fe302
--- /dev/null
+++ b/packages/flutter/test/widgets/widget_inspector_init_with_structured_error_test.dart
@@ -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;
+ });
+ }
+}
diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart
index e57ddcc..8cdd02a 100644
--- a/packages/flutter/test/widgets/widget_inspector_test.dart
+++ b/packages/flutter/test/widgets/widget_inspector_test.dart
@@ -14,6 +14,8 @@
import 'package:flutter/widgets.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
// columns will impact whether tests pass.
@@ -222,64 +224,10 @@
}
void main() {
- TestWidgetInspectorService.runTests();
+ _TestWidgetInspectorService.runTests();
}
-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);
- }
- }
-
-
+class _TestWidgetInspectorService extends TestWidgetInspectorService {
// These tests need access to protected members of WidgetInspectorService.
static void runTests() {
final TestWidgetInspectorService service = TestWidgetInspectorService();
@@ -1725,7 +1673,7 @@
_CreationLocation location = knownLocations[id];
expect(location.file, equals(file));
// ClockText widget.
- expect(location.line, equals(51));
+ expect(location.line, equals(53));
expect(location.column, equals(9));
expect(count, equals(1));
@@ -1734,7 +1682,7 @@
location = knownLocations[id];
expect(location.file, equals(file));
// Text widget in _ClockTextState build method.
- expect(location.line, equals(89));
+ expect(location.line, equals(91));
expect(location.column, equals(12));
expect(count, equals(1));
@@ -1759,7 +1707,7 @@
location = knownLocations[id];
expect(location.file, equals(file));
// ClockText widget.
- expect(location.line, equals(51));
+ expect(location.line, equals(53));
expect(location.column, equals(9));
expect(count, equals(3)); // 3 clock widget instances rebuilt.
@@ -1768,7 +1716,7 @@
location = knownLocations[id];
expect(location.file, equals(file));
// Text widget in _ClockTextState build method.
- expect(location.line, equals(89));
+ expect(location.line, equals(91));
expect(location.column, equals(12));
expect(count, equals(3)); // 3 clock widget instances rebuilt.
diff --git a/packages/flutter/test/widgets/widget_inspector_test_utils.dart b/packages/flutter/test/widgets/widget_inspector_test_utils.dart
new file mode 100644
index 0000000..f5d3184
--- /dev/null
+++ b/packages/flutter/test/widgets/widget_inspector_test_utils.dart
@@ -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);
+ }
+ }
+}