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:
Helin Shiah 2020-05-28 23:38:10 +00:00 committed by GitHub
parent a1636b6fef
commit 479f370379
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 151 additions and 63 deletions

View file

@ -744,6 +744,8 @@ mixin WidgetInspectorService {
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 @@ mixin WidgetInspectorService {
_errorsSinceReload = 0;
}
bool isStructuredErrorsEnabled() {
return const bool.fromEnvironment('flutter.inspector.structuredErrors');
}
/// Called to register service extensions.
///
/// See also:
@ -949,6 +955,10 @@ mixin WidgetInspectorService {
/// * [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 @@ mixin WidgetInspectorService {
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();
},
);

View file

@ -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;
});
}
}

View file

@ -14,6 +14,8 @@ import 'package:flutter/rendering.dart';
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 @@ int getChildLayerCount(OffsetLayer layer) {
}
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 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
_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 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
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 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
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 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
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.

View file

@ -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);
}
}
}