mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Treat some exceptions as unhandled when a debugger is attached (#78649)
This commit is contained in:
parent
eb73516739
commit
2a8dba4af6
@ -135,6 +135,7 @@ mixin AnimationLocalListenersMixin {
|
|||||||
/// If listeners are added or removed during this function, the modifications
|
/// If listeners are added or removed during this function, the modifications
|
||||||
/// will not change which listeners are called during this iteration.
|
/// will not change which listeners are called during this iteration.
|
||||||
@protected
|
@protected
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void notifyListeners() {
|
void notifyListeners() {
|
||||||
final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
|
final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
|
||||||
for (final VoidCallback listener in localListeners) {
|
for (final VoidCallback listener in localListeners) {
|
||||||
@ -223,6 +224,7 @@ mixin AnimationLocalStatusListenersMixin {
|
|||||||
/// If listeners are added or removed during this function, the modifications
|
/// If listeners are added or removed during this function, the modifications
|
||||||
/// will not change which listeners are called during this iteration.
|
/// will not change which listeners are called during this iteration.
|
||||||
@protected
|
@protected
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void notifyStatusListeners(AnimationStatus status) {
|
void notifyStatusListeners(AnimationStatus status) {
|
||||||
final List<AnimationStatusListener> localListeners = List<AnimationStatusListener>.from(_statusListeners);
|
final List<AnimationStatusListener> localListeners = List<AnimationStatusListener>.from(_statusListeners);
|
||||||
for (final AnimationStatusListener listener in localListeners) {
|
for (final AnimationStatusListener listener in localListeners) {
|
||||||
|
@ -15,6 +15,7 @@ import 'stack_frame.dart';
|
|||||||
// late bool draconisAlive;
|
// late bool draconisAlive;
|
||||||
// late bool draconisAmulet;
|
// late bool draconisAmulet;
|
||||||
// late Diagnosticable draconis;
|
// late Diagnosticable draconis;
|
||||||
|
// void methodThatMayThrow() { }
|
||||||
|
|
||||||
/// Signature for [FlutterError.onError] handler.
|
/// Signature for [FlutterError.onError] handler.
|
||||||
typedef FlutterExceptionHandler = void Function(FlutterErrorDetails details);
|
typedef FlutterExceptionHandler = void Function(FlutterErrorDetails details);
|
||||||
@ -876,9 +877,7 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti
|
|||||||
///
|
///
|
||||||
/// Do not call [onError] directly, instead, call [reportError], which
|
/// Do not call [onError] directly, instead, call [reportError], which
|
||||||
/// forwards to [onError] if it is not null.
|
/// forwards to [onError] if it is not null.
|
||||||
static FlutterExceptionHandler? onError = _defaultErrorHandler;
|
static FlutterExceptionHandler? onError = presentError;
|
||||||
|
|
||||||
static void _defaultErrorHandler(FlutterErrorDetails details) => presentError(details);
|
|
||||||
|
|
||||||
/// Called by the Flutter framework before attempting to parse a [StackTrace].
|
/// Called by the Flutter framework before attempting to parse a [StackTrace].
|
||||||
///
|
///
|
||||||
@ -1101,6 +1100,31 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Calls [onError] with the given details, unless it is null.
|
/// Calls [onError] with the given details, unless it is null.
|
||||||
|
///
|
||||||
|
/// {@tool snippet}
|
||||||
|
/// When calling this from a `catch` block consider annotating the method
|
||||||
|
/// containing the `catch` block with
|
||||||
|
/// `@pragma('vm:notify-debugger-on-exception')` to allow an attached debugger
|
||||||
|
/// to treat the exception as unhandled. This means instead of executing the
|
||||||
|
/// `catch` block, the debugger can break at the original source location from
|
||||||
|
/// which the exception was thrown.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// @pragma('vm:notify-debugger-on-exception')
|
||||||
|
/// void doSomething() {
|
||||||
|
/// try {
|
||||||
|
/// methodThatMayThrow();
|
||||||
|
/// } catch (exception, stack) {
|
||||||
|
/// FlutterError.reportError(FlutterErrorDetails(
|
||||||
|
/// exception: exception,
|
||||||
|
/// stack: stack,
|
||||||
|
/// library: 'example library',
|
||||||
|
/// context: ErrorDescription('while doing something'),
|
||||||
|
/// ));
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// {@end-tool}
|
||||||
static void reportError(FlutterErrorDetails details) {
|
static void reportError(FlutterErrorDetails details) {
|
||||||
assert(details != null);
|
assert(details != null);
|
||||||
assert(details.exception != null);
|
assert(details.exception != null);
|
||||||
|
@ -283,6 +283,7 @@ class ChangeNotifier implements Listenable {
|
|||||||
/// See the discussion at [removeListener].
|
/// See the discussion at [removeListener].
|
||||||
@protected
|
@protected
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void notifyListeners() {
|
void notifyListeners() {
|
||||||
assert(_debugAssertNotDisposed());
|
assert(_debugAssertNotDisposed());
|
||||||
if (_count == 0)
|
if (_count == 0)
|
||||||
|
@ -390,6 +390,7 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
|
|||||||
/// The `hitTestResult` argument may only be null for [PointerAddedEvent]s or
|
/// The `hitTestResult` argument may only be null for [PointerAddedEvent]s or
|
||||||
/// [PointerRemovedEvent]s.
|
/// [PointerRemovedEvent]s.
|
||||||
@override // from HitTestDispatcher
|
@override // from HitTestDispatcher
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
|
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
|
||||||
assert(!locked);
|
assert(!locked);
|
||||||
// No hit test information implies that this is a [PointerHoverEvent],
|
// No hit test information implies that this is a [PointerHoverEvent],
|
||||||
|
@ -87,6 +87,7 @@ class PointerRouter {
|
|||||||
throw UnsupportedError('debugGlobalRouteCount is not supported in release builds');
|
throw UnsupportedError('debugGlobalRouteCount is not supported in release builds');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) {
|
void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) {
|
||||||
try {
|
try {
|
||||||
event = event.transformed(transform);
|
event = event.transformed(transform);
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import 'events.dart';
|
import 'events.dart';
|
||||||
@ -189,6 +188,7 @@ class PointerSignalResolver {
|
|||||||
///
|
///
|
||||||
/// This is called by the [GestureBinding] after the framework has finished
|
/// This is called by the [GestureBinding] after the framework has finished
|
||||||
/// dispatching the pointer signal event.
|
/// dispatching the pointer signal event.
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void resolve(PointerSignalEvent event) {
|
void resolve(PointerSignalEvent event) {
|
||||||
if (_firstRegisteredCallback == null) {
|
if (_firstRegisteredCallback == null) {
|
||||||
assert(_currentEvent == null);
|
assert(_currentEvent == null);
|
||||||
|
@ -165,6 +165,7 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
|
|||||||
/// callback that returns a string describing useful debugging information,
|
/// callback that returns a string describing useful debugging information,
|
||||||
/// e.g. the arguments passed to the callback.
|
/// e.g. the arguments passed to the callback.
|
||||||
@protected
|
@protected
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
T? invokeCallback<T>(String name, RecognizerCallback<T> callback, { String Function()? debugReport }) {
|
T? invokeCallback<T>(String name, RecognizerCallback<T> callback, { String Function()? debugReport }) {
|
||||||
assert(callback != null);
|
assert(callback != null);
|
||||||
T? result;
|
T? result;
|
||||||
|
@ -614,6 +614,7 @@ abstract class ImageStreamCompleter with Diagnosticable {
|
|||||||
|
|
||||||
/// Calls all the registered listeners to notify them of a new image.
|
/// Calls all the registered listeners to notify them of a new image.
|
||||||
@protected
|
@protected
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void setImage(ImageInfo image) {
|
void setImage(ImageInfo image) {
|
||||||
_checkDisposed();
|
_checkDisposed();
|
||||||
_currentImage?.dispose();
|
_currentImage?.dispose();
|
||||||
@ -668,6 +669,7 @@ abstract class ImageStreamCompleter with Diagnosticable {
|
|||||||
///
|
///
|
||||||
/// See [FlutterErrorDetails] for further details on these values.
|
/// See [FlutterErrorDetails] for further details on these values.
|
||||||
@protected
|
@protected
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void reportError({
|
void reportError({
|
||||||
DiagnosticsNode? context,
|
DiagnosticsNode? context,
|
||||||
required Object exception,
|
required Object exception,
|
||||||
|
@ -1617,6 +1617,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
|
|||||||
owner!._nodesNeedingLayout.add(this);
|
owner!._nodesNeedingLayout.add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void _layoutWithoutResize() {
|
void _layoutWithoutResize() {
|
||||||
assert(_relayoutBoundary == this);
|
assert(_relayoutBoundary == this);
|
||||||
RenderObject? debugPreviousActiveLayout;
|
RenderObject? debugPreviousActiveLayout;
|
||||||
@ -1671,6 +1672,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
|
|||||||
/// children unconditionally. It is the [layout] method's responsibility (as
|
/// children unconditionally. It is the [layout] method's responsibility (as
|
||||||
/// implemented here) to return early if the child does not need to do any
|
/// implemented here) to return early if the child does not need to do any
|
||||||
/// work to update its layout information.
|
/// work to update its layout information.
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void layout(Constraints constraints, { bool parentUsesSize = false }) {
|
void layout(Constraints constraints, { bool parentUsesSize = false }) {
|
||||||
if (!kReleaseMode && debugProfileLayoutsEnabled)
|
if (!kReleaseMode && debugProfileLayoutsEnabled)
|
||||||
Timeline.startSync('$runtimeType', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
Timeline.startSync('$runtimeType', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||||
|
@ -280,6 +280,7 @@ mixin SchedulerBinding on BindingBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void _executeTimingsCallbacks(List<FrameTiming> timings) {
|
void _executeTimingsCallbacks(List<FrameTiming> timings) {
|
||||||
final List<TimingsCallback> clonedCallbacks =
|
final List<TimingsCallback> clonedCallbacks =
|
||||||
List<TimingsCallback>.from(_timingsCallbacks);
|
List<TimingsCallback>.from(_timingsCallbacks);
|
||||||
@ -450,6 +451,10 @@ mixin SchedulerBinding on BindingBase {
|
|||||||
///
|
///
|
||||||
/// Also returns false if there are no tasks remaining.
|
/// Also returns false if there are no tasks remaining.
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
|
// TODO(goderbauer): Add pragma (and enable test in
|
||||||
|
// break_on_framework_exceptions_test.dart) once debugger breaks on correct
|
||||||
|
// line, https://github.com/dart-lang/sdk/issues/45684
|
||||||
|
// @pragma('vm:notify-debugger-on-exception')
|
||||||
bool handleEventLoopCallback() {
|
bool handleEventLoopCallback() {
|
||||||
if (_taskQueue.isEmpty || locked)
|
if (_taskQueue.isEmpty || locked)
|
||||||
return false;
|
return false;
|
||||||
@ -1133,6 +1138,7 @@ mixin SchedulerBinding on BindingBase {
|
|||||||
// Wraps the callback in a try/catch and forwards any error to
|
// Wraps the callback in a try/catch and forwards any error to
|
||||||
// [debugSchedulerExceptionHandler], if set. If not set, then simply prints
|
// [debugSchedulerExceptionHandler], if set. If not set, then simply prints
|
||||||
// the error.
|
// the error.
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace? callbackStack ]) {
|
void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace? callbackStack ]) {
|
||||||
assert(callback != null);
|
assert(callback != null);
|
||||||
assert(_FrameCallbackEntry.debugCurrentCallbackStack == null);
|
assert(_FrameCallbackEntry.debugCurrentCallbackStack == null);
|
||||||
|
@ -272,6 +272,10 @@ class _DefaultBinaryMessenger extends BinaryMessenger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
// TODO(goderbauer): Add pragma (and enable test in
|
||||||
|
// break_on_framework_exceptions_test.dart) when it works on async methods,
|
||||||
|
// https://github.com/dart-lang/sdk/issues/45673
|
||||||
|
// @pragma('vm:notify-debugger-on-exception')
|
||||||
Future<void> handlePlatformMessage(
|
Future<void> handlePlatformMessage(
|
||||||
String channel,
|
String channel,
|
||||||
ByteData? data,
|
ByteData? data,
|
||||||
|
@ -198,6 +198,7 @@ abstract class Action<T extends Intent> with Diagnosticable {
|
|||||||
/// See the discussion at [removeActionListener].
|
/// See the discussion at [removeActionListener].
|
||||||
@protected
|
@protected
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void notifyActionListeners() {
|
void notifyActionListeners() {
|
||||||
if (_listeners.isEmpty) {
|
if (_listeners.isEmpty) {
|
||||||
return;
|
return;
|
||||||
|
@ -1187,6 +1187,7 @@ class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObje
|
|||||||
assert(_newWidget == null);
|
assert(_newWidget == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void _rebuild() {
|
void _rebuild() {
|
||||||
try {
|
try {
|
||||||
_child = updateChild(_child, widget.child, _rootChildSlot);
|
_child = updateChild(_child, widget.child, _rootChildSlot);
|
||||||
|
@ -1873,6 +1873,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void _finalizeEditing(TextInputAction action, {required bool shouldUnfocus}) {
|
void _finalizeEditing(TextInputAction action, {required bool shouldUnfocus}) {
|
||||||
// Take any actions necessary now that the user has completed editing.
|
// Take any actions necessary now that the user has completed editing.
|
||||||
if (widget.onEditingComplete != null) {
|
if (widget.onEditingComplete != null) {
|
||||||
@ -2126,6 +2127,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) {
|
void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) {
|
||||||
// We return early if the selection is not valid. This can happen when the
|
// We return early if the selection is not valid. This can happen when the
|
||||||
// text of [EditableText] is updated at the same time as the selection is
|
// text of [EditableText] is updated at the same time as the selection is
|
||||||
@ -2259,6 +2261,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
_lastBottomViewInset = WidgetsBinding.instance!.window.viewInsets.bottom;
|
_lastBottomViewInset = WidgetsBinding.instance!.window.viewInsets.bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void _formatAndSetValue(TextEditingValue value, SelectionChangedCause? cause, {bool userInteraction = false}) {
|
void _formatAndSetValue(TextEditingValue value, SelectionChangedCause? cause, {bool userInteraction = false}) {
|
||||||
// Only apply input formatters if the text has changed (including uncommited
|
// Only apply input formatters if the text has changed (including uncommited
|
||||||
// text in the composing region), or when the user committed the composing
|
// text in the composing region), or when the user committed the composing
|
||||||
|
@ -1596,6 +1596,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
|
|||||||
/// [FocusManager] notifies.
|
/// [FocusManager] notifies.
|
||||||
void removeHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _listeners.remove(listener);
|
void removeHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _listeners.remove(listener);
|
||||||
|
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void _notifyHighlightModeListeners() {
|
void _notifyHighlightModeListeners() {
|
||||||
if (_listeners.isEmpty) {
|
if (_listeners.isEmpty) {
|
||||||
return;
|
return;
|
||||||
|
@ -2505,6 +2505,7 @@ class BuildOwner {
|
|||||||
/// [debugPrintBuildScope] to true. This is useful when debugging problems
|
/// [debugPrintBuildScope] to true. This is useful when debugging problems
|
||||||
/// involving widgets not getting marked dirty, or getting marked dirty too
|
/// involving widgets not getting marked dirty, or getting marked dirty too
|
||||||
/// often.
|
/// often.
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void buildScope(Element context, [ VoidCallback? callback ]) {
|
void buildScope(Element context, [ VoidCallback? callback ]) {
|
||||||
if (callback == null && _dirtyElements.isEmpty)
|
if (callback == null && _dirtyElements.isEmpty)
|
||||||
return;
|
return;
|
||||||
@ -2833,6 +2834,10 @@ class BuildOwner {
|
|||||||
///
|
///
|
||||||
/// After the current call stack unwinds, a microtask that notifies listeners
|
/// After the current call stack unwinds, a microtask that notifies listeners
|
||||||
/// about changes to global keys will run.
|
/// about changes to global keys will run.
|
||||||
|
// TODO(goderbauer): Add pragma (and enable test in
|
||||||
|
// break_on_framework_exceptions_test.dart) once debugger breaks on correct
|
||||||
|
// line, https://github.com/dart-lang/sdk/issues/45684
|
||||||
|
// @pragma('vm:notify-debugger-on-exception')
|
||||||
void finalizeTree() {
|
void finalizeTree() {
|
||||||
Timeline.startSync('Finalize tree', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
Timeline.startSync('Finalize tree', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||||
try {
|
try {
|
||||||
@ -4579,6 +4584,7 @@ abstract class ComponentElement extends Element {
|
|||||||
/// Called automatically during [mount] to generate the first build, and by
|
/// Called automatically during [mount] to generate the first build, and by
|
||||||
/// [rebuild] when the element needs updating.
|
/// [rebuild] when the element needs updating.
|
||||||
@override
|
@override
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void performRebuild() {
|
void performRebuild() {
|
||||||
if (!kReleaseMode && debugProfileBuildsEnabled)
|
if (!kReleaseMode && debugProfileBuildsEnabled)
|
||||||
Timeline.startSync('${widget.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
Timeline.startSync('${widget.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||||
|
@ -115,6 +115,10 @@ class _LayoutBuilderElement<ConstraintType extends Constraints> extends RenderOb
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _layout(ConstraintType constraints) {
|
void _layout(ConstraintType constraints) {
|
||||||
|
// TODO(goderbauer): When https://github.com/dart-lang/sdk/issues/45710 is
|
||||||
|
// fixed: refactor the anonymous closure below into a named one, apply the
|
||||||
|
// @pragma('vm:notify-debugger-on-exception') to it and enable the
|
||||||
|
// corresponding test in break_on_framework_exceptions_test.dart.
|
||||||
owner!.buildScope(this, () {
|
owner!.buildScope(this, () {
|
||||||
Widget built;
|
Widget built;
|
||||||
try {
|
try {
|
||||||
|
@ -760,6 +760,7 @@ class _CallbackHookProvider<T> {
|
|||||||
/// Exceptions thrown by callbacks will be caught and reported using
|
/// Exceptions thrown by callbacks will be caught and reported using
|
||||||
/// [FlutterError.reportError].
|
/// [FlutterError.reportError].
|
||||||
@protected
|
@protected
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
T invokeCallback(T defaultValue) {
|
T invokeCallback(T defaultValue) {
|
||||||
if (_callbacks.isEmpty)
|
if (_callbacks.isEmpty)
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
@ -773,7 +774,7 @@ class _CallbackHookProvider<T> {
|
|||||||
context: ErrorDescription('while invoking the callback for $runtimeType'),
|
context: ErrorDescription('while invoking the callback for $runtimeType'),
|
||||||
informationCollector: () sync* {
|
informationCollector: () sync* {
|
||||||
yield DiagnosticsProperty<_CallbackHookProvider<T>>(
|
yield DiagnosticsProperty<_CallbackHookProvider<T>>(
|
||||||
'The $runtimeType that invoked the callback was:',
|
'The $runtimeType that invoked the callback was',
|
||||||
this,
|
this,
|
||||||
style: DiagnosticsTreeStyle.errorProperty,
|
style: DiagnosticsTreeStyle.errorProperty,
|
||||||
);
|
);
|
||||||
|
@ -446,6 +446,7 @@ class SliverChildBuilderDelegate extends SliverChildDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
Widget? build(BuildContext context, int index) {
|
Widget? build(BuildContext context, int index) {
|
||||||
assert(builder != null);
|
assert(builder != null);
|
||||||
if (index < 0 || (childCount != null && index >= childCount!))
|
if (index < 0 || (childCount != null && index >= childCount!))
|
||||||
|
@ -0,0 +1,766 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// @dart = 2.8
|
||||||
|
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
|
||||||
|
import '../src/common.dart';
|
||||||
|
import 'test_data/project.dart';
|
||||||
|
import 'test_driver.dart';
|
||||||
|
import 'test_utils.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
Directory tempDir;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
tempDir = createResolvedTempDirectorySync('break_on_framework_exceptions.');
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
tryToDelete(tempDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when AnimationController listener throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
AnimationController(vsync: TestVSync(), duration: Duration.zero)
|
||||||
|
..addListener(() {
|
||||||
|
throw 'AnimationController listener';
|
||||||
|
})
|
||||||
|
..forward();
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'AnimationController listener';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when AnimationController status listener throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
AnimationController(vsync: TestVSync(), duration: Duration.zero)
|
||||||
|
..addStatusListener((AnimationStatus _) {
|
||||||
|
throw 'AnimationController status listener';
|
||||||
|
})
|
||||||
|
..forward();
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'AnimationController status listener';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when ChangeNotifier listener throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
ValueNotifier<int>(0)
|
||||||
|
..addListener(() {
|
||||||
|
throw 'ValueNotifier listener';
|
||||||
|
})
|
||||||
|
..value = 1;
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'ValueNotifier listener';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when handling a gesture throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Center(
|
||||||
|
child: ElevatedButton(
|
||||||
|
child: const Text('foo'),
|
||||||
|
onPressed: () {
|
||||||
|
throw 'while handling a gesture';
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await tester.tap(find.byType(ElevatedButton));
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'while handling a gesture';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when platform message callback throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
BasicMessageChannel<String>('foo', const StringCodec()).setMessageHandler((_) {
|
||||||
|
throw 'platform message callback';
|
||||||
|
});
|
||||||
|
tester.binding.defaultBinaryMessenger.handlePlatformMessage('foo', const StringCodec().encodeMessage('Hello'), (_) {});
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'platform message callback';"));
|
||||||
|
}, skip: 'TODO(goderbauer): add pragma to _DefaultBinaryMessenger.handlePlatformMessage when async methods are supported (https://github.com/dart-lang/sdk/issues/45673) and enable this test');
|
||||||
|
|
||||||
|
testWithoutContext('breaks when SliverChildBuilderDelegate.builder throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
home: ListView.builder(
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
throw 'cannot build child';
|
||||||
|
},
|
||||||
|
),
|
||||||
|
));
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'cannot build child';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when EditableText.onChanged throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: TextField(
|
||||||
|
onChanged: (String t) {
|
||||||
|
throw 'onChanged';
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
await tester.enterText(find.byType(TextField), 'foo');
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'onChanged';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when EditableText.onEditingComplete throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: TextField(
|
||||||
|
onEditingComplete: () {
|
||||||
|
throw 'onEditingComplete';
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
await tester.tap(find.byType(EditableText));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'onEditingComplete';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when EditableText.onSelectionChanged throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
home: SelectableText('hello',
|
||||||
|
onSelectionChanged: (TextSelection selection, SelectionChangedCause? cause) {
|
||||||
|
throw 'onSelectionChanged';
|
||||||
|
},
|
||||||
|
),
|
||||||
|
));
|
||||||
|
await tester.tap(find.byType(SelectableText));
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'onSelectionChanged';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when Action listener throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
CallbackAction<Intent>(onInvoke: (Intent _) { })
|
||||||
|
..addActionListener((_) {
|
||||||
|
throw 'action listener';
|
||||||
|
})
|
||||||
|
..notifyActionListeners();
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'action listener';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when pointer route throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
PointerRouter()
|
||||||
|
..addRoute(2, (PointerEvent event) {
|
||||||
|
throw 'pointer route';
|
||||||
|
})
|
||||||
|
..route(TestPointer(2).down(Offset.zero));
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'pointer route';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when PointerSignalResolver callback throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
const PointerScrollEvent originalEvent = PointerScrollEvent();
|
||||||
|
PointerSignalResolver()
|
||||||
|
..register(originalEvent, (PointerSignalEvent event) {
|
||||||
|
throw 'PointerSignalResolver callback';
|
||||||
|
})
|
||||||
|
..resolve(originalEvent);
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'PointerSignalResolver callback';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when PointerSignalResolver callback throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
FocusManager.instance
|
||||||
|
..addHighlightModeListener((_) {
|
||||||
|
throw 'highlight mode listener';
|
||||||
|
})
|
||||||
|
..highlightStrategy = FocusHighlightStrategy.alwaysTouch
|
||||||
|
..highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'highlight mode listener';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when GestureBinding.dispatchEvent throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MouseRegion(
|
||||||
|
onHover: (_) {
|
||||||
|
throw 'onHover';
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
|
await gesture.addPointer(location: Offset.zero);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byType(MouseRegion)));
|
||||||
|
await tester.pump();
|
||||||
|
gesture.removePointer();
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'onHover';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when ImageStreamListener.onImage throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
final Completer<ImageInfo> completer = Completer<ImageInfo>();
|
||||||
|
OneFrameImageStreamCompleter(completer.future)
|
||||||
|
..addListener(ImageStreamListener((ImageInfo _, bool __) {
|
||||||
|
throw 'setImage';
|
||||||
|
}));
|
||||||
|
completer.complete(ImageInfo(image: image));
|
||||||
|
''',
|
||||||
|
setup: r'''
|
||||||
|
late ui.Image image;
|
||||||
|
setUp(() async {
|
||||||
|
image = await createTestImage();
|
||||||
|
});
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'setImage';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when ImageStreamListener.onError throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
final Completer<ImageInfo> completer = Completer<ImageInfo>();
|
||||||
|
OneFrameImageStreamCompleter(completer.future)
|
||||||
|
..addListener(ImageStreamListener(
|
||||||
|
(ImageInfo _, bool __) { },
|
||||||
|
onError: (Object _, StackTrace? __) {
|
||||||
|
throw 'onError';
|
||||||
|
},
|
||||||
|
));
|
||||||
|
completer.completeError('ERROR');
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'onError';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when LayoutBuilder.builder throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
await tester.pumpWidget(LayoutBuilder(
|
||||||
|
builder: (_, __) {
|
||||||
|
throw 'LayoutBuilder.builder';
|
||||||
|
},
|
||||||
|
));
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'LayoutBuilder.builder';"));
|
||||||
|
}, skip: 'TODO(goderbauer): Once https://github.com/dart-lang/sdk/issues/45710 is fixed, fix TODO in _LayoutBuilderElement._layout and enable this test');
|
||||||
|
|
||||||
|
testWithoutContext('breaks when _CallbackHookProvider callback throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
RootBackButtonDispatcher()
|
||||||
|
..addCallback(() {
|
||||||
|
throw '_CallbackHookProvider.callback';
|
||||||
|
})
|
||||||
|
..invokeCallback(Future.value(false));
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw '_CallbackHookProvider.callback';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when TimingsCallback throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
SchedulerBinding.instance!.addTimingsCallback((List<FrameTiming> timings) {
|
||||||
|
throw 'TimingsCallback';
|
||||||
|
});
|
||||||
|
ui.window.onReportTimings!(<FrameTiming>[]);
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'TimingsCallback';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when TimingsCallback throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
SchedulerBinding.instance!.scheduleTask(
|
||||||
|
() {
|
||||||
|
throw 'scheduled task';
|
||||||
|
},
|
||||||
|
Priority.touch,
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'scheduled task';"));
|
||||||
|
}, skip: 'TODO(goderbauer): add pragma to SchedulerBinding.handleEventLoopCallback when https://github.com/dart-lang/sdk/issues/45684 is fixed and enable this test');
|
||||||
|
|
||||||
|
testWithoutContext('breaks when FrameCallback throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
SchedulerBinding.instance!.addPostFrameCallback((_) {
|
||||||
|
throw 'FrameCallback';
|
||||||
|
});
|
||||||
|
await tester.pump();
|
||||||
|
'''
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'FrameCallback';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when attaching to render tree throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
await tester.pumpWidget(TestWidget());
|
||||||
|
''',
|
||||||
|
classes: r'''
|
||||||
|
class TestWidget extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
StatelessElement createElement() {
|
||||||
|
throw 'create element';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Container();
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'create element';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when RenderObject.performLayout throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
TestRender().layout(BoxConstraints());
|
||||||
|
''',
|
||||||
|
classes: r'''
|
||||||
|
class TestRender extends RenderBox {
|
||||||
|
@override
|
||||||
|
void performLayout() {
|
||||||
|
throw 'performLayout';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'performLayout';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when RenderObject.performResize throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
TestRender().layout(BoxConstraints());
|
||||||
|
''',
|
||||||
|
classes: r'''
|
||||||
|
class TestRender extends RenderBox {
|
||||||
|
@override
|
||||||
|
bool get sizedByParent => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void performResize() {
|
||||||
|
throw 'performResize';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'performResize';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when RenderObject.performLayout (without resize) throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
await tester.pumpWidget(TestWidget());
|
||||||
|
tester.renderObject<TestRender>(find.byType(TestWidget)).layoutThrows = true;
|
||||||
|
await tester.pump();
|
||||||
|
''',
|
||||||
|
classes: r'''
|
||||||
|
class TestWidget extends LeafRenderObjectWidget {
|
||||||
|
@override
|
||||||
|
RenderObject createRenderObject(BuildContext context) => TestRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestRender extends RenderBox {
|
||||||
|
bool get layoutThrows => _layoutThrows;
|
||||||
|
bool _layoutThrows = false;
|
||||||
|
set layoutThrows(bool value) {
|
||||||
|
if (value == _layoutThrows) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_layoutThrows = value;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void performLayout() {
|
||||||
|
if (layoutThrows) {
|
||||||
|
throw 'performLayout without resize';
|
||||||
|
}
|
||||||
|
size = constraints.biggest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'performLayout without resize';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when StatelessWidget.build throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
await tester.pumpWidget(TestWidget());
|
||||||
|
''',
|
||||||
|
classes: r'''
|
||||||
|
class TestWidget extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
throw 'StatelessWidget.build';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'StatelessWidget.build';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when StatefulWidget.build throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
await tester.pumpWidget(TestWidget());
|
||||||
|
''',
|
||||||
|
classes: r'''
|
||||||
|
class TestWidget extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_TestWidgetState createState() => _TestWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TestWidgetState extends State<TestWidget> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
throw 'StatefulWidget.build';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'StatefulWidget.build';"));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('breaks when finalizing the tree throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
await tester.pumpWidget(TestWidget());
|
||||||
|
''',
|
||||||
|
classes: r'''
|
||||||
|
class TestWidget extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_TestWidgetState createState() => _TestWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TestWidgetState extends State<TestWidget> {
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
throw 'dispose';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Container();
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'dispose';"));
|
||||||
|
}, skip: 'TODO(goderbauer): add pragma to BuildOwner.finalizeTree when https://github.com/dart-lang/sdk/issues/45684 is fixed and enable this test');
|
||||||
|
|
||||||
|
testWithoutContext('breaks when rebuilding dirty elements throws', () async {
|
||||||
|
final TestProject project = TestProject(
|
||||||
|
r'''
|
||||||
|
await tester.pumpWidget(TestWidget());
|
||||||
|
tester.element<TestElement>(find.byType(TestWidget)).throwOnRebuild = true;
|
||||||
|
await tester.pump();
|
||||||
|
''',
|
||||||
|
classes: r'''
|
||||||
|
class TestWidget extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
StatelessElement createElement() => TestElement(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestElement extends StatelessElement {
|
||||||
|
TestElement(StatelessWidget widget) : super(widget);
|
||||||
|
|
||||||
|
bool get throwOnRebuild => _throwOnRebuild;
|
||||||
|
bool _throwOnRebuild = false;
|
||||||
|
set throwOnRebuild(bool value) {
|
||||||
|
if (value == _throwOnRebuild) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_throwOnRebuild = value;
|
||||||
|
markNeedsBuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void rebuild() {
|
||||||
|
if (_throwOnRebuild) {
|
||||||
|
throw 'rebuild';
|
||||||
|
}
|
||||||
|
super.rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final FlutterTestTestDriver flutter = FlutterTestTestDriver(tempDir);
|
||||||
|
await flutter.test(withDebugger: true, pauseOnExceptions: true);
|
||||||
|
await flutter.waitForPause();
|
||||||
|
|
||||||
|
final int breakLine = (await flutter.getSourceLocation()).line;
|
||||||
|
expect(breakLine, project.lineContaining(project.test, "throw 'rebuild';"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestProject extends Project {
|
||||||
|
TestProject(this.testBody, { this.setup, this.classes });
|
||||||
|
|
||||||
|
final String testBody;
|
||||||
|
final String setup;
|
||||||
|
final String classes;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String pubspec = '''
|
||||||
|
name: test
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.12.0-0 <3.0.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
''';
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String main = '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get test => _test.replaceFirst('// SETUP', setup ?? '').replaceFirst('// TEST_BODY', testBody).replaceFirst('// CLASSES', classes ?? '');
|
||||||
|
|
||||||
|
final String _test = r'''
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter/animation.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// SETUP
|
||||||
|
testWidgets('test', (WidgetTester tester) async {
|
||||||
|
// TEST_BODY
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// CLASSES
|
||||||
|
''';
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user