Skip to content

Commit 479f370

Browse files
authored
Add function to set structured error early (flutter#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
1 parent a1636b6 commit 479f370

File tree

4 files changed

+151
-63
lines changed

4 files changed

+151
-63
lines changed

packages/flutter/lib/src/widgets/widget_inspector.dart

+12-3
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,8 @@ mixin WidgetInspectorService {
744744
bool _trackRebuildDirtyWidgets = false;
745745
bool _trackRepaintWidgets = false;
746746

747+
FlutterExceptionHandler _structuredExceptionHandler;
748+
747749
_RegisterServiceExtensionCallback _registerServiceExtensionCallback;
748750
/// Registers a service extension method with the given name (full
749751
/// name "ext.flutter.inspector.name").
@@ -941,6 +943,10 @@ mixin WidgetInspectorService {
941943
_errorsSinceReload = 0;
942944
}
943945

946+
bool isStructuredErrorsEnabled() {
947+
return const bool.fromEnvironment('flutter.inspector.structuredErrors');
948+
}
949+
944950
/// Called to register service extensions.
945951
///
946952
/// See also:
@@ -949,6 +955,10 @@ mixin WidgetInspectorService {
949955
/// * [BindingBase.initServiceExtensions], which explains when service
950956
/// extensions can be used.
951957
void initServiceExtensions(_RegisterServiceExtensionCallback registerServiceExtensionCallback) {
958+
_structuredExceptionHandler = _reportError;
959+
if (isStructuredErrorsEnabled()) {
960+
FlutterError.onError = _structuredExceptionHandler;
961+
}
952962
_registerServiceExtensionCallback = registerServiceExtensionCallback;
953963
assert(!_debugServiceExtensionsRegistered);
954964
assert(() {
@@ -958,14 +968,13 @@ mixin WidgetInspectorService {
958968

959969
SchedulerBinding.instance.addPersistentFrameCallback(_onFrameStart);
960970

961-
final FlutterExceptionHandler structuredExceptionHandler = _reportError;
962971
final FlutterExceptionHandler defaultExceptionHandler = FlutterError.presentError;
963972

964973
_registerBoolServiceExtension(
965974
name: 'structuredErrors',
966-
getter: () async => FlutterError.presentError == structuredExceptionHandler,
975+
getter: () async => FlutterError.presentError == _structuredExceptionHandler,
967976
setter: (bool value) {
968-
FlutterError.presentError = value ? structuredExceptionHandler : defaultExceptionHandler;
977+
FlutterError.presentError = value ? _structuredExceptionHandler : defaultExceptionHandler;
969978
return Future<void>.value();
970979
},
971980
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/foundation.dart';
6+
import 'package:flutter/material.dart';
7+
import 'package:flutter/rendering.dart';
8+
import 'package:flutter/widgets.dart';
9+
import 'package:flutter_test/flutter_test.dart';
10+
11+
import 'widget_inspector_test_utils.dart';
12+
13+
void main() {
14+
StructuredErrorTestService.runTests();
15+
}
16+
17+
class StructuredErrorTestService extends TestWidgetInspectorService {
18+
@override
19+
bool isStructuredErrorsEnabled() {
20+
return true;
21+
}
22+
23+
static void runTests() {
24+
final StructuredErrorTestService service = StructuredErrorTestService();
25+
WidgetInspectorService.instance = service;
26+
FlutterExceptionHandler testHandler;
27+
FlutterExceptionHandler inspectorServiceErrorHandler;
28+
29+
setUpAll(() {
30+
inspectorServiceErrorHandler = FlutterError.onError;
31+
});
32+
33+
setUp(() {
34+
testHandler = FlutterError.onError;
35+
});
36+
37+
testWidgets('ext.flutter.inspector.setStructuredErrors',
38+
(WidgetTester tester) async {
39+
// The test framework resets FlutterError.onError, so we set it back to
40+
// what it was after WidgetInspectorService::initServiceExtensions ran.
41+
FlutterError.onError = inspectorServiceErrorHandler;
42+
43+
List<Map<Object, Object>> flutterErrorEvents =
44+
service.getEventsDispatched('Flutter.Error');
45+
expect(flutterErrorEvents, hasLength(0));
46+
47+
// Create an error.
48+
FlutterError.reportError(FlutterErrorDetailsForRendering(
49+
library: 'rendering library',
50+
context: ErrorDescription('during layout'),
51+
exception: StackTrace.current,
52+
));
53+
54+
// Validate that we received an error.
55+
flutterErrorEvents = service.getEventsDispatched('Flutter.Error');
56+
expect(flutterErrorEvents, hasLength(1));
57+
});
58+
59+
tearDown(() {
60+
FlutterError.onError = testHandler;
61+
});
62+
}
63+
}

packages/flutter/test/widgets/widget_inspector_test.dart

+8-60
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import 'package:flutter/rendering.dart';
1414
import 'package:flutter/widgets.dart';
1515
import 'package:flutter_test/flutter_test.dart';
1616

17+
import 'widget_inspector_test_utils.dart';
18+
1719
// Start of block of code where widget creation location line numbers and
1820
// columns will impact whether tests pass.
1921

@@ -222,64 +224,10 @@ int getChildLayerCount(OffsetLayer layer) {
222224
}
223225

224226
void main() {
225-
TestWidgetInspectorService.runTests();
227+
_TestWidgetInspectorService.runTests();
226228
}
227229

228-
class TestWidgetInspectorService extends Object with WidgetInspectorService {
229-
final Map<String, InspectorServiceExtensionCallback> extensions = <String, InspectorServiceExtensionCallback>{};
230-
231-
final Map<String, List<Map<Object, Object>>> eventsDispatched = <String, List<Map<Object, Object>>>{};
232-
233-
@override
234-
void registerServiceExtension({
235-
@required String name,
236-
@required FutureOr<Map<String, Object>> callback(Map<String, String> parameters),
237-
}) {
238-
assert(!extensions.containsKey(name));
239-
extensions[name] = callback;
240-
}
241-
242-
@override
243-
void postEvent(String eventKind, Map<Object, Object> eventData) {
244-
getEventsDispatched(eventKind).add(eventData);
245-
}
246-
247-
List<Map<Object, Object>> getEventsDispatched(String eventKind) {
248-
return eventsDispatched.putIfAbsent(eventKind, () => <Map<Object, Object>>[]);
249-
}
250-
251-
Iterable<Map<Object, Object>> getServiceExtensionStateChangedEvents(String extensionName) {
252-
return getEventsDispatched('Flutter.ServiceExtensionStateChanged')
253-
.where((Map<Object, Object> event) => event['extension'] == extensionName);
254-
}
255-
256-
Future<Object> testExtension(String name, Map<String, String> arguments) async {
257-
expect(extensions, contains(name));
258-
// Encode and decode to JSON to match behavior using a real service
259-
// extension where only JSON is allowed.
260-
return json.decode(json.encode(await extensions[name](arguments)))['result'];
261-
}
262-
263-
Future<String> testBoolExtension(String name, Map<String, String> arguments) async {
264-
expect(extensions, contains(name));
265-
// Encode and decode to JSON to match behavior using a real service
266-
// extension where only JSON is allowed.
267-
return json.decode(json.encode(await extensions[name](arguments)))['enabled'] as String;
268-
}
269-
270-
int rebuildCount = 0;
271-
272-
@override
273-
Future<void> forceRebuild() async {
274-
rebuildCount++;
275-
final WidgetsBinding binding = WidgetsBinding.instance;
276-
277-
if (binding.renderViewElement != null) {
278-
binding.buildOwner.reassemble(binding.renderViewElement);
279-
}
280-
}
281-
282-
230+
class _TestWidgetInspectorService extends TestWidgetInspectorService {
283231
// These tests need access to protected members of WidgetInspectorService.
284232
static void runTests() {
285233
final TestWidgetInspectorService service = TestWidgetInspectorService();
@@ -1725,7 +1673,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
17251673
_CreationLocation location = knownLocations[id];
17261674
expect(location.file, equals(file));
17271675
// ClockText widget.
1728-
expect(location.line, equals(51));
1676+
expect(location.line, equals(53));
17291677
expect(location.column, equals(9));
17301678
expect(count, equals(1));
17311679

@@ -1734,7 +1682,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
17341682
location = knownLocations[id];
17351683
expect(location.file, equals(file));
17361684
// Text widget in _ClockTextState build method.
1737-
expect(location.line, equals(89));
1685+
expect(location.line, equals(91));
17381686
expect(location.column, equals(12));
17391687
expect(count, equals(1));
17401688

@@ -1759,7 +1707,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
17591707
location = knownLocations[id];
17601708
expect(location.file, equals(file));
17611709
// ClockText widget.
1762-
expect(location.line, equals(51));
1710+
expect(location.line, equals(53));
17631711
expect(location.column, equals(9));
17641712
expect(count, equals(3)); // 3 clock widget instances rebuilt.
17651713

@@ -1768,7 +1716,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
17681716
location = knownLocations[id];
17691717
expect(location.file, equals(file));
17701718
// Text widget in _ClockTextState build method.
1771-
expect(location.line, equals(89));
1719+
expect(location.line, equals(91));
17721720
expect(location.column, equals(12));
17731721
expect(count, equals(3)); // 3 clock widget instances rebuilt.
17741722

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:convert';
7+
8+
import 'package:flutter/foundation.dart';
9+
import 'package:flutter/material.dart';
10+
import 'package:flutter/widgets.dart';
11+
import 'package:flutter_test/flutter_test.dart';
12+
13+
typedef InspectorServiceExtensionCallback = FutureOr<Map<String, Object>> Function(Map<String, String> parameters);
14+
15+
class TestWidgetInspectorService extends Object with WidgetInspectorService {
16+
final Map<String, InspectorServiceExtensionCallback> extensions = <String, InspectorServiceExtensionCallback>{};
17+
18+
final Map<String, List<Map<Object, Object>>> eventsDispatched = <String, List<Map<Object, Object>>>{};
19+
20+
@override
21+
void registerServiceExtension({
22+
@required String name,
23+
@required FutureOr<Map<String, Object>> callback(Map<String, String> parameters),
24+
}) {
25+
assert(!extensions.containsKey(name));
26+
extensions[name] = callback;
27+
}
28+
29+
@override
30+
void postEvent(String eventKind, Map<Object, Object> eventData) {
31+
getEventsDispatched(eventKind).add(eventData);
32+
}
33+
34+
List<Map<Object, Object>> getEventsDispatched(String eventKind) {
35+
return eventsDispatched.putIfAbsent(eventKind, () => <Map<Object, Object>>[]);
36+
}
37+
38+
Iterable<Map<Object, Object>> getServiceExtensionStateChangedEvents(String extensionName) {
39+
return getEventsDispatched('Flutter.ServiceExtensionStateChanged')
40+
.where((Map<Object, Object> event) => event['extension'] == extensionName);
41+
}
42+
43+
Future<Object> testExtension(String name, Map<String, String> arguments) async {
44+
expect(extensions, contains(name));
45+
// Encode and decode to JSON to match behavior using a real service
46+
// extension where only JSON is allowed.
47+
return json.decode(json.encode(await extensions[name](arguments)))['result'];
48+
}
49+
50+
Future<String> testBoolExtension(String name, Map<String, String> arguments) async {
51+
expect(extensions, contains(name));
52+
// Encode and decode to JSON to match behavior using a real service
53+
// extension where only JSON is allowed.
54+
return json.decode(json.encode(await extensions[name](arguments)))['enabled'] as String;
55+
}
56+
57+
int rebuildCount = 0;
58+
59+
@override
60+
Future<void> forceRebuild() async {
61+
rebuildCount++;
62+
final WidgetsBinding binding = WidgetsBinding.instance;
63+
64+
if (binding.renderViewElement != null) {
65+
binding.buildOwner.reassemble(binding.renderViewElement);
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)