Rendering errors with root causes in the widget layer should have a reference to the widget (#32511)
This commit is contained in:
parent
97b2c98642
commit
a76e39f984
@ -0,0 +1,26 @@
|
|||||||
|
<<skip until matching line>>
|
||||||
|
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
|
||||||
|
The following assertion was thrown building
|
||||||
|
RawGestureDetector-\[LabeledGlobalKey<RawGestureDetectorState>#.+\]\(state:
|
||||||
|
RawGestureDetectorState#.+\(gestures: <none>, behavior: opaque\)\):
|
||||||
|
'package:flutter\/src\/painting\/basic_types\.dart': Failed assertion: line 223 pos 10: 'textDirection
|
||||||
|
!= null': is not true\.
|
||||||
|
|
||||||
|
Either the assertion indicates an error in the framework itself, or we should provide substantially
|
||||||
|
more information in this error message to help you determine and fix the underlying cause\.
|
||||||
|
In either case, please report this assertion by filing a bug on GitHub:
|
||||||
|
https:\/\/github\.com\/flutter\/flutter\/issues\/new\?template=BUG\.md
|
||||||
|
|
||||||
|
User-created ancestor of the error-causing widget was:
|
||||||
|
CustomScrollView
|
||||||
|
file:\/\/\/.+print_user_created_ancestor_test\.dart:[0-9]+:7
|
||||||
|
|
||||||
|
When the exception was thrown, this was the stack:
|
||||||
|
<<skip until matching line>>
|
||||||
|
\(elided [0-9]+ frames from .+\)
|
||||||
|
════════════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
.*..:.. \+0 -1: Rendering Error *
|
||||||
|
Test failed\. See exception logs above\.
|
||||||
|
The test description was: Rendering Error
|
||||||
|
*
|
||||||
|
.*..:.. \+0 -1: Some tests failed\. *
|
@ -0,0 +1,25 @@
|
|||||||
|
<<skip until matching line>>
|
||||||
|
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
|
||||||
|
The following assertion was thrown building
|
||||||
|
RawGestureDetector-\[LabeledGlobalKey<RawGestureDetectorState>#.+\]\(state:
|
||||||
|
RawGestureDetectorState#.+\(gestures: <none>, behavior: opaque\)\):
|
||||||
|
'package:flutter\/src\/painting\/basic_types\.dart': Failed assertion: line 223 pos 10: 'textDirection
|
||||||
|
!= null': is not true\.
|
||||||
|
|
||||||
|
Either the assertion indicates an error in the framework itself, or we should provide substantially
|
||||||
|
more information in this error message to help you determine and fix the underlying cause\.
|
||||||
|
In either case, please report this assertion by filing a bug on GitHub:
|
||||||
|
https:\/\/github\.com\/flutter\/flutter\/issues\/new\?template=BUG\.md
|
||||||
|
|
||||||
|
Widget creation tracking is currently disabled. Enabling it enables improved error messages\. It can
|
||||||
|
be enabled by passing `--track-widget-creation` to `flutter run` or `flutter test`\.
|
||||||
|
|
||||||
|
When the exception was thrown, this was the stack:
|
||||||
|
<<skip until matching line>>
|
||||||
|
\(elided [0-9]+ frames from .+\)
|
||||||
|
════════════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
.*..:.. \+0 -1: Rendering Error *
|
||||||
|
Test failed\. See exception logs above\.
|
||||||
|
The test description was: Rendering Error
|
||||||
|
*
|
||||||
|
.*..:.. \+0 -1: Some tests failed\. *
|
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2019 The Chromium 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/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Rendering Error', (WidgetTester tester) async {
|
||||||
|
// this should fail
|
||||||
|
await tester.pumpWidget(
|
||||||
|
CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverToBoxAdapter(child: Container()),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2019 The Chromium 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/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Rendering Error', (WidgetTester tester) async {
|
||||||
|
// this should fail
|
||||||
|
await tester.pumpWidget(
|
||||||
|
CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverToBoxAdapter(child: Container()),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -12,6 +12,9 @@ import 'print.dart';
|
|||||||
/// Signature for [FlutterError.onError] handler.
|
/// Signature for [FlutterError.onError] handler.
|
||||||
typedef FlutterExceptionHandler = void Function(FlutterErrorDetails details);
|
typedef FlutterExceptionHandler = void Function(FlutterErrorDetails details);
|
||||||
|
|
||||||
|
/// Signature for [DiagnosticPropertiesBuilder] transformer.
|
||||||
|
typedef DiagnosticPropertiesTransformer = Iterable<DiagnosticsNode> Function(Iterable<DiagnosticsNode> properties);
|
||||||
|
|
||||||
/// Signature for [FlutterErrorDetails.informationCollector] callback
|
/// Signature for [FlutterErrorDetails.informationCollector] callback
|
||||||
/// and other callbacks that collect information describing an error.
|
/// and other callbacks that collect information describing an error.
|
||||||
typedef InformationCollector = Iterable<DiagnosticsNode> Function();
|
typedef InformationCollector = Iterable<DiagnosticsNode> Function();
|
||||||
@ -212,6 +215,20 @@ class FlutterErrorDetails extends Diagnosticable {
|
|||||||
this.silent = false,
|
this.silent = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Transformers to transform [DiagnosticsNode] in [DiagnosticPropertiesBuilder]
|
||||||
|
/// into a more descriptive form.
|
||||||
|
///
|
||||||
|
/// There are layers that attach certain [DiagnosticsNode] into
|
||||||
|
/// [FlutterErrorDetails] that require knowledge from other layers to parse.
|
||||||
|
/// To correctly interpret those [DiagnosticsNode], register transformers in
|
||||||
|
/// the layers that possess the knowledge.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [WidgetsBinding.initInstances], which registers its transformer.
|
||||||
|
static final List<DiagnosticPropertiesTransformer> propertiesTransformers =
|
||||||
|
<DiagnosticPropertiesTransformer>[];
|
||||||
|
|
||||||
/// The exception. Often this will be an [AssertionError], maybe specifically
|
/// The exception. Often this will be an [AssertionError], maybe specifically
|
||||||
/// a [FlutterError]. However, this could be any value at all.
|
/// a [FlutterError]. However, this could be any value at all.
|
||||||
final dynamic exception;
|
final dynamic exception;
|
||||||
@ -449,6 +466,15 @@ class FlutterErrorDetails extends Diagnosticable {
|
|||||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.debug}) {
|
String toString({DiagnosticLevel minLevel = DiagnosticLevel.debug}) {
|
||||||
return toDiagnosticsNode(style: DiagnosticsTreeStyle.error).toStringDeep(minLevel: minLevel);
|
return toDiagnosticsNode(style: DiagnosticsTreeStyle.error).toStringDeep(minLevel: minLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
DiagnosticsNode toDiagnosticsNode({ String name, DiagnosticsTreeStyle style }) {
|
||||||
|
return _FlutterErrorDetailsNode(
|
||||||
|
name: name,
|
||||||
|
value: this,
|
||||||
|
style: style,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error class used to report Flutter-specific assertion failures and
|
/// Error class used to report Flutter-specific assertion failures and
|
||||||
@ -777,3 +803,28 @@ class DiagnosticsStackTrace extends DiagnosticsBlock {
|
|||||||
return DiagnosticsNode.message(frame, allowWrap: false);
|
return DiagnosticsNode.message(frame, allowWrap: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _FlutterErrorDetailsNode extends DiagnosticableNode<FlutterErrorDetails> {
|
||||||
|
_FlutterErrorDetailsNode({
|
||||||
|
String name,
|
||||||
|
@required FlutterErrorDetails value,
|
||||||
|
@required DiagnosticsTreeStyle style,
|
||||||
|
}) : super(
|
||||||
|
name: name,
|
||||||
|
value: value,
|
||||||
|
style: style,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DiagnosticPropertiesBuilder get builder {
|
||||||
|
final DiagnosticPropertiesBuilder builder = super.builder;
|
||||||
|
if (builder == null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Iterable<DiagnosticsNode> properties = builder.properties;
|
||||||
|
for (DiagnosticPropertiesTransformer transformer in FlutterErrorDetails.propertiesTransformers) {
|
||||||
|
properties = transformer(properties);
|
||||||
|
}
|
||||||
|
return DiagnosticPropertiesBuilder.fromProperties(properties.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2774,7 +2774,10 @@ class DiagnosticableNode<T extends Diagnosticable> extends DiagnosticsNode {
|
|||||||
|
|
||||||
DiagnosticPropertiesBuilder _cachedBuilder;
|
DiagnosticPropertiesBuilder _cachedBuilder;
|
||||||
|
|
||||||
DiagnosticPropertiesBuilder get _builder {
|
/// Retrieve the [DiagnosticPropertiesBuilder] of current node.
|
||||||
|
///
|
||||||
|
/// It will cache the result to prevent duplicate operation.
|
||||||
|
DiagnosticPropertiesBuilder get builder {
|
||||||
if (kReleaseMode)
|
if (kReleaseMode)
|
||||||
return null;
|
return null;
|
||||||
if (_cachedBuilder == null) {
|
if (_cachedBuilder == null) {
|
||||||
@ -2786,14 +2789,14 @@ class DiagnosticableNode<T extends Diagnosticable> extends DiagnosticsNode {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
DiagnosticsTreeStyle get style {
|
DiagnosticsTreeStyle get style {
|
||||||
return kReleaseMode ? DiagnosticsTreeStyle.none : super.style ?? _builder.defaultDiagnosticsTreeStyle;
|
return kReleaseMode ? DiagnosticsTreeStyle.none : super.style ?? builder.defaultDiagnosticsTreeStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get emptyBodyDescription => kReleaseMode ? '' : _builder.emptyBodyDescription;
|
String get emptyBodyDescription => kReleaseMode ? '' : builder.emptyBodyDescription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<DiagnosticsNode> getProperties() => kReleaseMode ? const <DiagnosticsNode>[] : _builder.properties;
|
List<DiagnosticsNode> getProperties() => kReleaseMode ? const <DiagnosticsNode>[] : builder.properties;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<DiagnosticsNode> getChildren() {
|
List<DiagnosticsNode> getChildren() {
|
||||||
@ -2875,6 +2878,13 @@ String describeEnum(Object enumEntry) {
|
|||||||
/// Builder to accumulate properties and configuration used to assemble a
|
/// Builder to accumulate properties and configuration used to assemble a
|
||||||
/// [DiagnosticsNode] from a [Diagnosticable] object.
|
/// [DiagnosticsNode] from a [Diagnosticable] object.
|
||||||
class DiagnosticPropertiesBuilder {
|
class DiagnosticPropertiesBuilder {
|
||||||
|
/// Creates a [DiagnosticPropertiesBuilder] with [properties] initialize to
|
||||||
|
/// an empty array.
|
||||||
|
DiagnosticPropertiesBuilder() : properties = <DiagnosticsNode>[];
|
||||||
|
|
||||||
|
/// Creates a [DiagnosticPropertiesBuilder] with a given [properties].
|
||||||
|
DiagnosticPropertiesBuilder.fromProperties(this.properties);
|
||||||
|
|
||||||
/// Add a property to the list of properties.
|
/// Add a property to the list of properties.
|
||||||
void add(DiagnosticsNode property) {
|
void add(DiagnosticsNode property) {
|
||||||
if (!kReleaseMode) {
|
if (!kReleaseMode) {
|
||||||
@ -2883,7 +2893,7 @@ class DiagnosticPropertiesBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// List of properties accumulated so far.
|
/// List of properties accumulated so far.
|
||||||
final List<DiagnosticsNode> properties = <DiagnosticsNode>[];
|
final List<DiagnosticsNode> properties;
|
||||||
|
|
||||||
/// Default style to use for the [DiagnosticsNode] if no style is specified.
|
/// Default style to use for the [DiagnosticsNode] if no style is specified.
|
||||||
DiagnosticsTreeStyle defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.sparse;
|
DiagnosticsTreeStyle defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.sparse;
|
||||||
|
@ -6,6 +6,7 @@ import 'dart:math' as math;
|
|||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import 'object.dart';
|
import 'object.dart';
|
||||||
import 'stack.dart';
|
import 'stack.dart';
|
||||||
@ -248,6 +249,8 @@ mixin DebugOverflowIndicatorMixin on RenderObject {
|
|||||||
context: ErrorDescription('during layout'),
|
context: ErrorDescription('during layout'),
|
||||||
renderObject: this,
|
renderObject: this,
|
||||||
informationCollector: () sync* {
|
informationCollector: () sync* {
|
||||||
|
if (debugCreator != null)
|
||||||
|
yield DiagnosticsDebugCreator(debugCreator);
|
||||||
yield* overflowHints;
|
yield* overflowHints;
|
||||||
yield describeForError('The specific $runtimeType in question is');
|
yield describeForError('The specific $runtimeType in question is');
|
||||||
// TODO(jacobr): this line is ascii art that it would be nice to
|
// TODO(jacobr): this line is ascii art that it would be nice to
|
||||||
|
@ -1189,6 +1189,8 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
|
|||||||
context: ErrorDescription('during $method()'),
|
context: ErrorDescription('during $method()'),
|
||||||
renderObject: this,
|
renderObject: this,
|
||||||
informationCollector: () sync* {
|
informationCollector: () sync* {
|
||||||
|
if (debugCreator != null)
|
||||||
|
yield DiagnosticsDebugCreator(debugCreator);
|
||||||
yield describeForError('The following RenderObject was being processed when the exception was fired');
|
yield describeForError('The following RenderObject was being processed when the exception was fired');
|
||||||
// TODO(jacobr): this error message has a code smell. Consider whether
|
// TODO(jacobr): this error message has a code smell. Consider whether
|
||||||
// displaying the truncated children is really useful for command line
|
// displaying the truncated children is really useful for command line
|
||||||
@ -3675,3 +3677,20 @@ class _SemanticsGeometry {
|
|||||||
bool get markAsHidden => _markAsHidden;
|
bool get markAsHidden => _markAsHidden;
|
||||||
bool _markAsHidden = false;
|
bool _markAsHidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A class that creates [DiagnosticsNode] by wrapping [RenderObject.debugCreator].
|
||||||
|
///
|
||||||
|
/// Attach a [DiagnosticsDebugCreator] into [FlutterErrorDetails.informationCollector]
|
||||||
|
/// when a [RenderObject.debugCreator] is available. This will lead to improved
|
||||||
|
/// error message.
|
||||||
|
class DiagnosticsDebugCreator extends DiagnosticsProperty<Object> {
|
||||||
|
/// Create a [DiagnosticsProperty] with its [value] initialized to input
|
||||||
|
/// [RenderObject.debugCreator].
|
||||||
|
DiagnosticsDebugCreator(Object value):
|
||||||
|
assert(value != null),
|
||||||
|
super(
|
||||||
|
'debugCreator',
|
||||||
|
value,
|
||||||
|
level: DiagnosticLevel.hidden
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -256,6 +256,7 @@ mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererB
|
|||||||
window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
|
window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
|
||||||
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
|
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
|
||||||
SystemChannels.system.setMessageHandler(_handleSystemMessage);
|
SystemChannels.system.setMessageHandler(_handleSystemMessage);
|
||||||
|
FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The current [WidgetsBinding], if one has been created.
|
/// The current [WidgetsBinding], if one has been created.
|
||||||
|
@ -2344,6 +2344,7 @@ class BuildOwner {
|
|||||||
e,
|
e,
|
||||||
stack,
|
stack,
|
||||||
informationCollector: () sync* {
|
informationCollector: () sync* {
|
||||||
|
yield DiagnosticsDebugCreator(DebugCreator(_dirtyElements[index]));
|
||||||
yield _dirtyElements[index].describeElement('The element being rebuilt at the time was index $index of $dirtyCount');
|
yield _dirtyElements[index].describeElement('The element being rebuilt at the time was index $index of $dirtyCount');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -2771,7 +2772,6 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
|||||||
return StringProperty(name, debugGetCreatorChain(10));
|
return StringProperty(name, debugGetCreatorChain(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// This is used to verify that Element objects move through life in an
|
// This is used to verify that Element objects move through life in an
|
||||||
// orderly fashion.
|
// orderly fashion.
|
||||||
_ElementLifecycle _debugLifecycleState = _ElementLifecycle.initial;
|
_ElementLifecycle _debugLifecycleState = _ElementLifecycle.initial;
|
||||||
@ -3933,7 +3933,16 @@ abstract class ComponentElement extends Element {
|
|||||||
built = build();
|
built = build();
|
||||||
debugWidgetBuilderValue(widget, built);
|
debugWidgetBuilderValue(widget, built);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
built = ErrorWidget.builder(_debugReportException(ErrorDescription('building $this'), e, stack));
|
built = ErrorWidget.builder(
|
||||||
|
_debugReportException(
|
||||||
|
ErrorDescription('building $this'),
|
||||||
|
e,
|
||||||
|
stack,
|
||||||
|
informationCollector: () sync* {
|
||||||
|
yield DiagnosticsDebugCreator(DebugCreator(this));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
// We delay marking the element as clean until after calling build() so
|
// We delay marking the element as clean until after calling build() so
|
||||||
// that attempts to markNeedsBuild() during build() will be ignored.
|
// that attempts to markNeedsBuild() during build() will be ignored.
|
||||||
@ -3944,7 +3953,16 @@ abstract class ComponentElement extends Element {
|
|||||||
_child = updateChild(_child, built, slot);
|
_child = updateChild(_child, built, slot);
|
||||||
assert(_child != null);
|
assert(_child != null);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
built = ErrorWidget.builder(_debugReportException(ErrorDescription('building $this'), e, stack));
|
built = ErrorWidget.builder(
|
||||||
|
_debugReportException(
|
||||||
|
ErrorDescription('building $this'),
|
||||||
|
e,
|
||||||
|
stack,
|
||||||
|
informationCollector: () sync* {
|
||||||
|
yield DiagnosticsDebugCreator(DebugCreator(this));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
_child = updateChild(null, built, slot);
|
_child = updateChild(null, built, slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4738,7 +4756,7 @@ abstract class RenderObjectElement extends Element {
|
|||||||
|
|
||||||
void _debugUpdateRenderObjectOwner() {
|
void _debugUpdateRenderObjectOwner() {
|
||||||
assert(() {
|
assert(() {
|
||||||
_renderObject.debugCreator = _DebugCreator(this);
|
_renderObject.debugCreator = DebugCreator(this);
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
}
|
}
|
||||||
@ -5219,9 +5237,17 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DebugCreator {
|
/// A wrapper class for the [Element] that is the creator of a [RenderObject].
|
||||||
_DebugCreator(this.element);
|
///
|
||||||
final RenderObjectElement element;
|
/// Attaching a [DebugCreator] attach the [RenderObject] will lead to better error
|
||||||
|
/// message.
|
||||||
|
class DebugCreator {
|
||||||
|
/// Create a [DebugCreator] instance with input [Element].
|
||||||
|
DebugCreator(this.element);
|
||||||
|
|
||||||
|
/// The creator of the [RenderObject].
|
||||||
|
final Element element;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => element.debugGetCreatorChain(12);
|
String toString() => element.debugGetCreatorChain(12);
|
||||||
}
|
}
|
||||||
|
@ -113,14 +113,32 @@ class _LayoutBuilderElement extends RenderObjectElement {
|
|||||||
built = widget.builder(this, constraints);
|
built = widget.builder(this, constraints);
|
||||||
debugWidgetBuilderValue(widget, built);
|
debugWidgetBuilderValue(widget, built);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
built = ErrorWidget.builder(_debugReportException(ErrorDescription('building $widget'), e, stack));
|
built = ErrorWidget.builder(
|
||||||
|
_debugReportException(
|
||||||
|
ErrorDescription('building $widget'),
|
||||||
|
e,
|
||||||
|
stack,
|
||||||
|
informationCollector: () sync* {
|
||||||
|
yield DiagnosticsDebugCreator(DebugCreator(this));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
_child = updateChild(_child, built, null);
|
_child = updateChild(_child, built, null);
|
||||||
assert(_child != null);
|
assert(_child != null);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
built = ErrorWidget.builder(_debugReportException(ErrorDescription('building $widget'), e, stack));
|
built = ErrorWidget.builder(
|
||||||
|
_debugReportException(
|
||||||
|
ErrorDescription('building $widget'),
|
||||||
|
e,
|
||||||
|
stack,
|
||||||
|
informationCollector: () sync* {
|
||||||
|
yield DiagnosticsDebugCreator(DebugCreator(this));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
_child = updateChild(null, built, slot);
|
_child = updateChild(null, built, slot);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -228,13 +246,15 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren
|
|||||||
FlutterErrorDetails _debugReportException(
|
FlutterErrorDetails _debugReportException(
|
||||||
DiagnosticsNode context,
|
DiagnosticsNode context,
|
||||||
dynamic exception,
|
dynamic exception,
|
||||||
StackTrace stack,
|
StackTrace stack, {
|
||||||
) {
|
InformationCollector informationCollector,
|
||||||
|
}) {
|
||||||
final FlutterErrorDetails details = FlutterErrorDetails(
|
final FlutterErrorDetails details = FlutterErrorDetails(
|
||||||
exception: exception,
|
exception: exception,
|
||||||
stack: stack,
|
stack: stack,
|
||||||
library: 'widgets library',
|
library: 'widgets library',
|
||||||
context: context,
|
context: context,
|
||||||
|
informationCollector: informationCollector,
|
||||||
);
|
);
|
||||||
FlutterError.reportError(details);
|
FlutterError.reportError(details);
|
||||||
return details;
|
return details;
|
||||||
|
@ -1397,11 +1397,17 @@ mixin WidgetInspectorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _isLocalCreationLocation(_Location location) {
|
bool _isLocalCreationLocation(_Location location) {
|
||||||
if (_pubRootDirectories == null || location == null || location.file == null) {
|
if (location == null || location.file == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String file = Uri.parse(location.file).path;
|
final String file = Uri.parse(location.file).path;
|
||||||
|
|
||||||
|
// By default check whether the creation location was within package:flutter.
|
||||||
|
if (_pubRootDirectories == null) {
|
||||||
|
// TODO(chunhtai): Make it more robust once
|
||||||
|
// https://github.com/flutter/flutter/issues/32660 is fixed.
|
||||||
|
return !file.contains('packages/flutter/');
|
||||||
|
}
|
||||||
for (String directory in _pubRootDirectories) {
|
for (String directory in _pubRootDirectories) {
|
||||||
if (file.startsWith(directory)) {
|
if (file.startsWith(directory)) {
|
||||||
return true;
|
return true;
|
||||||
@ -2705,6 +2711,105 @@ class _Location {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isDebugCreator(DiagnosticsNode node) => node is DiagnosticsDebugCreator;
|
||||||
|
|
||||||
|
/// Transformer to parse and gather information about [DiagnosticsDebugCreator].
|
||||||
|
///
|
||||||
|
/// This function will be registered to [FlutterErrorDetails.propertiesTransformers]
|
||||||
|
/// in [WidgetsBinding.initInstances].
|
||||||
|
Iterable<DiagnosticsNode> transformDebugCreator(Iterable<DiagnosticsNode> properties) sync* {
|
||||||
|
final List<DiagnosticsNode> pending = <DiagnosticsNode>[];
|
||||||
|
bool foundStackTrace = false;
|
||||||
|
for (DiagnosticsNode node in properties) {
|
||||||
|
if (!foundStackTrace && node is DiagnosticsStackTrace)
|
||||||
|
foundStackTrace = true;
|
||||||
|
if (_isDebugCreator(node)) {
|
||||||
|
yield* _parseDiagnosticsNode(node);
|
||||||
|
} else {
|
||||||
|
if (foundStackTrace) {
|
||||||
|
pending.add(node);
|
||||||
|
} else {
|
||||||
|
yield node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield* pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transform the input [DiagnosticsNode].
|
||||||
|
///
|
||||||
|
/// Return null if input [DiagnosticsNode] is not applicable.
|
||||||
|
Iterable<DiagnosticsNode> _parseDiagnosticsNode(DiagnosticsNode node) {
|
||||||
|
if (!_isDebugCreator(node))
|
||||||
|
return null;
|
||||||
|
final DebugCreator debugCreator = node.value;
|
||||||
|
final Element element = debugCreator.element;
|
||||||
|
return _describeRelevantUserCode(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable<DiagnosticsNode> _describeRelevantUserCode(Element element) {
|
||||||
|
if (!WidgetInspectorService.instance.isWidgetCreationTracked()) {
|
||||||
|
return <DiagnosticsNode>[
|
||||||
|
ErrorDescription(
|
||||||
|
'Widget creation tracking is currently disabled. Enabling '
|
||||||
|
'it enables improved error messages. It can be enabled by passing '
|
||||||
|
'`--track-widget-creation` to `flutter run` or `flutter test`.',
|
||||||
|
),
|
||||||
|
ErrorSpacer(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
final List<DiagnosticsNode> nodes = <DiagnosticsNode>[];
|
||||||
|
element.visitAncestorElements((Element ancestor) {
|
||||||
|
// TODO(chunhtai): should print out all the widgets that are about to cross
|
||||||
|
// package boundaries.
|
||||||
|
if (_isLocalCreationLocation(ancestor)) {
|
||||||
|
nodes.add(
|
||||||
|
DiagnosticsBlock(
|
||||||
|
name: 'User-created ancestor of the error-causing widget was',
|
||||||
|
children: <DiagnosticsNode>[
|
||||||
|
ErrorDescription('${ancestor.widget.toStringShort()} ${_describeCreationLocation(ancestor)}'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
nodes.add(ErrorSpacer());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns if an object is user created.
|
||||||
|
///
|
||||||
|
/// This function will only work in debug mode builds when
|
||||||
|
/// the `--track-widget-creation` flag is passed to `flutter_tool`. Dart 2.0 is
|
||||||
|
/// required as injecting creation locations requires a
|
||||||
|
/// [Dart Kernel Transformer](https://github.com/dart-lang/sdk/wiki/Kernel-Documentation).
|
||||||
|
///
|
||||||
|
/// Currently is local creation locations are only available for
|
||||||
|
/// [Widget] and [Element].
|
||||||
|
bool _isLocalCreationLocation(Object object) {
|
||||||
|
final _Location location = _getCreationLocation(object);
|
||||||
|
if (location == null)
|
||||||
|
return false;
|
||||||
|
return WidgetInspectorService.instance._isLocalCreationLocation(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the creation location of an object in String format if one is available.
|
||||||
|
///
|
||||||
|
/// ex: "file:///path/to/main.dart:4:3"
|
||||||
|
///
|
||||||
|
/// Creation locations are only available for debug mode builds when
|
||||||
|
/// the `--track-widget-creation` flag is passed to `flutter_tool`. Dart 2.0 is
|
||||||
|
/// required as injecting creation locations requires a
|
||||||
|
/// [Dart Kernel Transformer](https://github.com/dart-lang/sdk/wiki/Kernel-Documentation).
|
||||||
|
///
|
||||||
|
/// Currently creation locations are only available for [Widget] and [Element].
|
||||||
|
String _describeCreationLocation(Object object) {
|
||||||
|
final _Location location = _getCreationLocation(object);
|
||||||
|
return location?.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the creation location of an object if one is available.
|
/// Returns the creation location of an object if one is available.
|
||||||
///
|
///
|
||||||
/// Creation locations are only available for debug mode builds when
|
/// Creation locations are only available for debug mode builds when
|
||||||
@ -2712,7 +2817,7 @@ class _Location {
|
|||||||
/// required as injecting creation locations requires a
|
/// required as injecting creation locations requires a
|
||||||
/// [Dart Kernel Transformer](https://github.com/dart-lang/sdk/wiki/Kernel-Documentation).
|
/// [Dart Kernel Transformer](https://github.com/dart-lang/sdk/wiki/Kernel-Documentation).
|
||||||
///
|
///
|
||||||
/// Currently creation locations are only available for [Widget] and [Element]
|
/// Currently creation locations are only available for [Widget] and [Element].
|
||||||
_Location _getCreationLocation(Object object) {
|
_Location _getCreationLocation(Object object) {
|
||||||
final Object candidate = object is Element ? object.widget : object;
|
final Object candidate = object is Element ? object.widget : object;
|
||||||
return candidate is _HasCreationLocation ? candidate._location : null;
|
return candidate is _HasCreationLocation ? candidate._location : null;
|
||||||
|
@ -527,9 +527,9 @@ void main() {
|
|||||||
final List<String> lines = errorDetails.toString().split('\n');
|
final List<String> lines = errorDetails.toString().split('\n');
|
||||||
// The lines in the middle of the error message contain the stack trace
|
// The lines in the middle of the error message contain the stack trace
|
||||||
// which will change depending on where the test is run.
|
// which will change depending on where the test is run.
|
||||||
expect(lines.length, greaterThan(9));
|
expect(lines.length, greaterThan(7));
|
||||||
expect(
|
expect(
|
||||||
lines.take(9).join('\n'),
|
lines.take(7).join('\n'),
|
||||||
equalsIgnoringHashCodes(
|
equalsIgnoringHashCodes(
|
||||||
'══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞════════════════════════\n'
|
'══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞════════════════════════\n'
|
||||||
'The following assertion was thrown building Stepper(dirty,\n'
|
'The following assertion was thrown building Stepper(dirty,\n'
|
||||||
@ -537,12 +537,9 @@ void main() {
|
|||||||
'_StepperState#00000):\n'
|
'_StepperState#00000):\n'
|
||||||
'Steppers must not be nested. The material specification advises\n'
|
'Steppers must not be nested. The material specification advises\n'
|
||||||
'that one should avoid embedding steppers within steppers.\n'
|
'that one should avoid embedding steppers within steppers.\n'
|
||||||
'https://material.io/archive/guidelines/components/steppers.html#steppers-usage\n'
|
'https://material.io/archive/guidelines/components/steppers.html#steppers-usage'
|
||||||
'\n'
|
|
||||||
'When the exception was thrown, this was the stack:'
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
///https://github.com/flutter/flutter/issues/16920
|
///https://github.com/flutter/flutter/issues/16920
|
||||||
|
@ -853,6 +853,132 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
|
|||||||
expect(paramB2['column'], equals(25));
|
expect(paramB2['column'], equals(25));
|
||||||
}, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // Test requires --track-widget-creation flag.
|
}, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // Test requires --track-widget-creation flag.
|
||||||
|
|
||||||
|
testWidgets('test transformDebugCreator will re-order if after stack trace', (WidgetTester tester) async {
|
||||||
|
final bool widgetTracked = WidgetInspectorService.instance.isWidgetCreationTracked();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: Stack(
|
||||||
|
children: const <Widget>[
|
||||||
|
Text('a'),
|
||||||
|
Text('b', textDirection: TextDirection.ltr),
|
||||||
|
Text('c', textDirection: TextDirection.ltr),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final Element elementA = find.text('a').evaluate().first;
|
||||||
|
String pubRootTest;
|
||||||
|
if (widgetTracked) {
|
||||||
|
final Map<String, Object> jsonObject = json.decode(
|
||||||
|
service.getSelectedWidget(null, 'my-group'));
|
||||||
|
final Map<String,
|
||||||
|
Object> creationLocation = jsonObject['creationLocation'];
|
||||||
|
expect(creationLocation, isNotNull);
|
||||||
|
final String fileA = creationLocation['file'];
|
||||||
|
expect(fileA, endsWith('widget_inspector_test.dart'));
|
||||||
|
expect(jsonObject, isNot(contains('createdByLocalProject')));
|
||||||
|
final List<String> segments = Uri
|
||||||
|
.parse(fileA)
|
||||||
|
.pathSegments;
|
||||||
|
// Strip a couple subdirectories away to generate a plausible pub root
|
||||||
|
// directory.
|
||||||
|
pubRootTest = '/' +
|
||||||
|
segments.take(segments.length - 2).join('/');
|
||||||
|
service.setPubRootDirectories(<Object>[pubRootTest]);
|
||||||
|
}
|
||||||
|
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||||
|
builder.add(StringProperty('dummy1', 'value'));
|
||||||
|
builder.add(StringProperty('dummy2', 'value'));
|
||||||
|
builder.add(DiagnosticsStackTrace('When the exception was thrown, this was the stack', null));
|
||||||
|
builder.add(DiagnosticsDebugCreator(DebugCreator(elementA)));
|
||||||
|
|
||||||
|
final List<DiagnosticsNode> nodes = List<DiagnosticsNode>.from(transformDebugCreator(builder.properties));
|
||||||
|
expect(nodes.length, 5);
|
||||||
|
expect(nodes[0].runtimeType, StringProperty);
|
||||||
|
expect(nodes[0].name, 'dummy1');
|
||||||
|
expect(nodes[1].runtimeType, StringProperty);
|
||||||
|
expect(nodes[1].name, 'dummy2');
|
||||||
|
// transformed node should come in front of stack trace.
|
||||||
|
if (widgetTracked) {
|
||||||
|
expect(nodes[2].runtimeType, DiagnosticsBlock);
|
||||||
|
final DiagnosticsBlock node = nodes[2];
|
||||||
|
final List<DiagnosticsNode> children = node.getChildren();
|
||||||
|
expect(children.length, 1);
|
||||||
|
final ErrorDescription child = children[0];
|
||||||
|
expect(child.valueToString().contains(Uri.parse(pubRootTest).path), true);
|
||||||
|
} else {
|
||||||
|
expect(nodes[2].runtimeType, ErrorDescription);
|
||||||
|
final ErrorDescription node = nodes[2];
|
||||||
|
expect(node.valueToString().startsWith('Widget creation tracking is currently disabled.'), true);
|
||||||
|
}
|
||||||
|
expect(nodes[3].runtimeType, ErrorSpacer);
|
||||||
|
expect(nodes[4].runtimeType, DiagnosticsStackTrace);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('test transformDebugCreator will not re-order if before stack trace', (WidgetTester tester) async {
|
||||||
|
final bool widgetTracked = WidgetInspectorService.instance.isWidgetCreationTracked();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: Stack(
|
||||||
|
children: const <Widget>[
|
||||||
|
Text('a'),
|
||||||
|
Text('b', textDirection: TextDirection.ltr),
|
||||||
|
Text('c', textDirection: TextDirection.ltr),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final Element elementA = find.text('a').evaluate().first;
|
||||||
|
String pubRootTest;
|
||||||
|
if (widgetTracked) {
|
||||||
|
final Map<String, Object> jsonObject = json.decode(
|
||||||
|
service.getSelectedWidget(null, 'my-group'));
|
||||||
|
final Map<String,
|
||||||
|
Object> creationLocation = jsonObject['creationLocation'];
|
||||||
|
expect(creationLocation, isNotNull);
|
||||||
|
final String fileA = creationLocation['file'];
|
||||||
|
expect(fileA, endsWith('widget_inspector_test.dart'));
|
||||||
|
expect(jsonObject, isNot(contains('createdByLocalProject')));
|
||||||
|
final List<String> segments = Uri
|
||||||
|
.parse(fileA)
|
||||||
|
.pathSegments;
|
||||||
|
// Strip a couple subdirectories away to generate a plausible pub root
|
||||||
|
// directory.
|
||||||
|
pubRootTest = '/' +
|
||||||
|
segments.take(segments.length - 2).join('/');
|
||||||
|
service.setPubRootDirectories(<Object>[pubRootTest]);
|
||||||
|
}
|
||||||
|
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||||
|
builder.add(StringProperty('dummy1', 'value'));
|
||||||
|
builder.add(DiagnosticsDebugCreator(DebugCreator(elementA)));
|
||||||
|
builder.add(StringProperty('dummy2', 'value'));
|
||||||
|
builder.add(DiagnosticsStackTrace('When the exception was thrown, this was the stack', null));
|
||||||
|
|
||||||
|
final List<DiagnosticsNode> nodes = List<DiagnosticsNode>.from(transformDebugCreator(builder.properties));
|
||||||
|
expect(nodes.length, 5);
|
||||||
|
expect(nodes[0].runtimeType, StringProperty);
|
||||||
|
expect(nodes[0].name, 'dummy1');
|
||||||
|
// transformed node stays at original place.
|
||||||
|
if (widgetTracked) {
|
||||||
|
expect(nodes[1].runtimeType, DiagnosticsBlock);
|
||||||
|
final DiagnosticsBlock node = nodes[1];
|
||||||
|
final List<DiagnosticsNode> children = node.getChildren();
|
||||||
|
expect(children.length, 1);
|
||||||
|
final ErrorDescription child = children[0];
|
||||||
|
expect(child.valueToString().contains(Uri.parse(pubRootTest).path), true);
|
||||||
|
} else {
|
||||||
|
expect(nodes[1].runtimeType, ErrorDescription);
|
||||||
|
final ErrorDescription node = nodes[1];
|
||||||
|
expect(node.valueToString().startsWith('Widget creation tracking is currently disabled.'), true);
|
||||||
|
}
|
||||||
|
expect(nodes[2].runtimeType, ErrorSpacer);
|
||||||
|
expect(nodes[3].runtimeType, StringProperty);
|
||||||
|
expect(nodes[3].name, 'dummy2');
|
||||||
|
expect(nodes[4].runtimeType, DiagnosticsStackTrace);
|
||||||
|
}, skip: WidgetInspectorService.instance.isWidgetCreationTracked());
|
||||||
|
|
||||||
testWidgets('WidgetInspectorService setPubRootDirectories', (WidgetTester tester) async {
|
testWidgets('WidgetInspectorService setPubRootDirectories', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Directionality(
|
Directionality(
|
||||||
|
@ -31,33 +31,44 @@ void main() {
|
|||||||
testUsingContext('report nice errors for exceptions thrown within testWidgets()', () async {
|
testUsingContext('report nice errors for exceptions thrown within testWidgets()', () async {
|
||||||
Cache.flutterRoot = '../..';
|
Cache.flutterRoot = '../..';
|
||||||
return _testFile('exception_handling', automatedTestsDirectory, flutterTestDirectory);
|
return _testFile('exception_handling', automatedTestsDirectory, flutterTestDirectory);
|
||||||
}, skip: io.Platform.isWindows); // Dart on Windows has trouble with unicode characters in output
|
}, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
|
||||||
|
|
||||||
testUsingContext('report a nice error when a guarded function was called without await', () async {
|
testUsingContext('report a nice error when a guarded function was called without await', () async {
|
||||||
Cache.flutterRoot = '../..';
|
Cache.flutterRoot = '../..';
|
||||||
return _testFile('test_async_utils_guarded', automatedTestsDirectory, flutterTestDirectory);
|
return _testFile('test_async_utils_guarded', automatedTestsDirectory, flutterTestDirectory);
|
||||||
}, skip: io.Platform.isWindows); // Dart on Windows has trouble with unicode characters in output
|
}, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
|
||||||
|
|
||||||
testUsingContext('report a nice error when an async function was called without await', () async {
|
testUsingContext('report a nice error when an async function was called without await', () async {
|
||||||
Cache.flutterRoot = '../..';
|
Cache.flutterRoot = '../..';
|
||||||
return _testFile('test_async_utils_unguarded', automatedTestsDirectory, flutterTestDirectory);
|
return _testFile('test_async_utils_unguarded', automatedTestsDirectory, flutterTestDirectory);
|
||||||
}, skip: io.Platform.isWindows); // Dart on Windows has trouble with unicode characters in output
|
}, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
|
||||||
|
|
||||||
testUsingContext('report a nice error when a Ticker is left running', () async {
|
testUsingContext('report a nice error when a Ticker is left running', () async {
|
||||||
Cache.flutterRoot = '../..';
|
Cache.flutterRoot = '../..';
|
||||||
return _testFile('ticker', automatedTestsDirectory, flutterTestDirectory);
|
return _testFile('ticker', automatedTestsDirectory, flutterTestDirectory);
|
||||||
}, skip: io.Platform.isWindows); // Dart on Windows has trouble with unicode characters in output
|
}, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
|
||||||
|
|
||||||
testUsingContext('report a nice error when a pubspec.yaml is missing a flutter_test dependency', () async {
|
testUsingContext('report a nice error when a pubspec.yaml is missing a flutter_test dependency', () async {
|
||||||
final String missingDependencyTests = fs.path.join('..', '..', 'dev', 'missing_dependency_tests');
|
final String missingDependencyTests = fs.path.join('..', '..', 'dev', 'missing_dependency_tests');
|
||||||
Cache.flutterRoot = '../..';
|
Cache.flutterRoot = '../..';
|
||||||
return _testFile('trivial', missingDependencyTests, missingDependencyTests);
|
return _testFile('trivial', missingDependencyTests, missingDependencyTests);
|
||||||
}, skip: io.Platform.isWindows); // Dart on Windows has trouble with unicode characters in output
|
}, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
|
||||||
|
|
||||||
|
testUsingContext('report which user created widget caused the error', () async {
|
||||||
|
Cache.flutterRoot = '../..';
|
||||||
|
return _testFile('print_user_created_ancestor', automatedTestsDirectory, flutterTestDirectory,
|
||||||
|
extraArguments: const <String>['--track-widget-creation']);
|
||||||
|
}, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
|
||||||
|
|
||||||
|
testUsingContext('report which user created widget caused the error - no flag', () async {
|
||||||
|
Cache.flutterRoot = '../..';
|
||||||
|
return _testFile('print_user_created_ancestor_no_flag', automatedTestsDirectory, flutterTestDirectory);
|
||||||
|
}, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
|
||||||
|
|
||||||
testUsingContext('run a test when its name matches a regexp', () async {
|
testUsingContext('run a test when its name matches a regexp', () async {
|
||||||
Cache.flutterRoot = '../..';
|
Cache.flutterRoot = '../..';
|
||||||
final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory,
|
final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory,
|
||||||
extraArgs: const <String>['--name', 'inc.*de']);
|
extraArguments: const <String>['--name', 'inc.*de']);
|
||||||
if (!result.stdout.contains('+1: All tests passed'))
|
if (!result.stdout.contains('+1: All tests passed'))
|
||||||
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
|
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
|
||||||
expect(result.exitCode, 0);
|
expect(result.exitCode, 0);
|
||||||
@ -66,7 +77,7 @@ void main() {
|
|||||||
testUsingContext('run a test when its name contains a string', () async {
|
testUsingContext('run a test when its name contains a string', () async {
|
||||||
Cache.flutterRoot = '../..';
|
Cache.flutterRoot = '../..';
|
||||||
final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory,
|
final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory,
|
||||||
extraArgs: const <String>['--plain-name', 'include']);
|
extraArguments: const <String>['--plain-name', 'include']);
|
||||||
if (!result.stdout.contains('+1: All tests passed'))
|
if (!result.stdout.contains('+1: All tests passed'))
|
||||||
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
|
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
|
||||||
expect(result.exitCode, 0);
|
expect(result.exitCode, 0);
|
||||||
@ -75,7 +86,7 @@ void main() {
|
|||||||
testUsingContext('test runs to completion', () async {
|
testUsingContext('test runs to completion', () async {
|
||||||
Cache.flutterRoot = '../..';
|
Cache.flutterRoot = '../..';
|
||||||
final ProcessResult result = await _runFlutterTest('trivial', automatedTestsDirectory, flutterTestDirectory,
|
final ProcessResult result = await _runFlutterTest('trivial', automatedTestsDirectory, flutterTestDirectory,
|
||||||
extraArgs: const <String>['--verbose']);
|
extraArguments: const <String>['--verbose']);
|
||||||
if ((!result.stdout.contains('+1: All tests passed')) ||
|
if ((!result.stdout.contains('+1: All tests passed')) ||
|
||||||
(!result.stdout.contains('test 0: starting shell process')) ||
|
(!result.stdout.contains('test 0: starting shell process')) ||
|
||||||
(!result.stdout.contains('test 0: deleting temporary directory')) ||
|
(!result.stdout.contains('test 0: deleting temporary directory')) ||
|
||||||
@ -90,7 +101,13 @@ void main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _testFile(String testName, String workingDirectory, String testDirectory, { Matcher exitCode }) async {
|
Future<void> _testFile(
|
||||||
|
String testName,
|
||||||
|
String workingDirectory,
|
||||||
|
String testDirectory, {
|
||||||
|
Matcher exitCode,
|
||||||
|
List<String> extraArguments = const <String>[],
|
||||||
|
}) async {
|
||||||
exitCode ??= isNonZero;
|
exitCode ??= isNonZero;
|
||||||
final String fullTestExpectation = fs.path.join(testDirectory, '${testName}_expectation.txt');
|
final String fullTestExpectation = fs.path.join(testDirectory, '${testName}_expectation.txt');
|
||||||
final File expectationFile = fs.file(fullTestExpectation);
|
final File expectationFile = fs.file(fullTestExpectation);
|
||||||
@ -100,7 +117,12 @@ Future<void> _testFile(String testName, String workingDirectory, String testDire
|
|||||||
while (_testExclusionLock != null)
|
while (_testExclusionLock != null)
|
||||||
await _testExclusionLock;
|
await _testExclusionLock;
|
||||||
|
|
||||||
final ProcessResult exec = await _runFlutterTest(testName, workingDirectory, testDirectory);
|
final ProcessResult exec = await _runFlutterTest(
|
||||||
|
testName,
|
||||||
|
workingDirectory,
|
||||||
|
testDirectory,
|
||||||
|
extraArguments: extraArguments,
|
||||||
|
);
|
||||||
|
|
||||||
expect(exec.exitCode, exitCode);
|
expect(exec.exitCode, exitCode);
|
||||||
final List<String> output = exec.stdout.split('\n');
|
final List<String> output = exec.stdout.split('\n');
|
||||||
@ -164,7 +186,7 @@ Future<ProcessResult> _runFlutterTest(
|
|||||||
String testName,
|
String testName,
|
||||||
String workingDirectory,
|
String workingDirectory,
|
||||||
String testDirectory, {
|
String testDirectory, {
|
||||||
List<String> extraArgs = const <String>[],
|
List<String> extraArguments = const <String>[],
|
||||||
}) async {
|
}) async {
|
||||||
|
|
||||||
final String testFilePath = fs.path.join(testDirectory, '${testName}_test.dart');
|
final String testFilePath = fs.path.join(testDirectory, '${testName}_test.dart');
|
||||||
@ -177,7 +199,7 @@ Future<ProcessResult> _runFlutterTest(
|
|||||||
fs.path.absolute(fs.path.join('bin', 'flutter_tools.dart')),
|
fs.path.absolute(fs.path.join('bin', 'flutter_tools.dart')),
|
||||||
'test',
|
'test',
|
||||||
'--no-color',
|
'--no-color',
|
||||||
...extraArgs,
|
...extraArguments,
|
||||||
testFilePath
|
testFilePath
|
||||||
];
|
];
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user