Text Painting Fuzzer (#12813)

Various improvements (in particular a new painting fuzzer) to the text manual test.

Some additional documentation.

A fix to Stack to remove an LTR bias: make unpositioned children apply "alignment".

Some more debugging information on RichText and Text.

A fix to the flutter tool to not crash when an RPC call throws an exception.
diff --git a/packages/flutter/lib/src/painting/images.dart b/packages/flutter/lib/src/painting/images.dart
index b63c1e9..e02b5a3 100644
--- a/packages/flutter/lib/src/painting/images.dart
+++ b/packages/flutter/lib/src/painting/images.dart
@@ -187,6 +187,20 @@
   ImageStream _imageStream;
   ImageInfo _image;
 
+  /// Draw the image onto the given canvas.
+  ///
+  /// The image is drawn at the position and size given by the `rect` argument.
+  ///
+  /// The image is clipped to the given `clipPath`, if any.
+  ///
+  /// The `configuration` object is used to resolve the image (e.g. to pick
+  /// resolution-specific assets), and to implement the
+  /// [DecorationImage.matchTextDirection] feature.
+  ///
+  /// If the image needs to be painted again, e.g. because it is animated or
+  /// because it had not yet been loaded the first time this method was called,
+  /// then the `onChanged` callback passed to [DecorationImage.createPainter]
+  /// will be called.
   void paint(Canvas canvas, Rect rect, Path clipPath, ImageConfiguration configuration) {
     if (_details == null)
       return;
diff --git a/packages/flutter/lib/src/rendering/list_body.dart b/packages/flutter/lib/src/rendering/list_body.dart
index cfe3cb2..76fc6dc 100644
--- a/packages/flutter/lib/src/rendering/list_body.dart
+++ b/packages/flutter/lib/src/rendering/list_body.dart
@@ -43,6 +43,10 @@
       child.parentData = new ListBodyParentData();
   }
 
+  /// The direction in which the children are laid out.
+  ///
+  /// For example, if the [axisDirection] is [AxisDirection.down], each child
+  /// will be laid out below the next, vertically.
   AxisDirection get axisDirection => _axisDirection;
   AxisDirection _axisDirection;
   set axisDirection(AxisDirection value) {
@@ -53,6 +57,8 @@
     markNeedsLayout();
   }
 
+  /// The axis (horizontal or vertical) corresponding to the current
+  /// [axisDirection].
   Axis get mainAxis => axisDirectionToAxis(axisDirection);
 
   @override
diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart
index b0cb1a6..3bf20e5 100644
--- a/packages/flutter/lib/src/rendering/object.dart
+++ b/packages/flutter/lib/src/rendering/object.dart
@@ -2114,6 +2114,17 @@
     owner.requestVisualUpdate();
   }
 
+  /// Report the semantics of this node, for example for accessibility purposes.
+  ///
+  /// This method should be overridden by subclasses that have interesting
+  /// semantic information.
+  ///
+  /// The given [SemanticsConfiguration] object is mutable and should be
+  /// annotated in a manner that describes the current state. No reference
+  /// should be kept to that object; mutating it outside of the context of the
+  /// [describeSemanticsConfiguration] call (for example as a result of
+  /// asynchronous computation) will at best have no useful effect and at worse
+  /// will cause crashes as the data will be in an inconsistent state.
   @protected
   void describeSemanticsConfiguration(SemanticsConfiguration config) {
     // Nothing to do by default.
diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart
index a0d7097..0fd3675 100644
--- a/packages/flutter/lib/src/rendering/paragraph.dart
+++ b/packages/flutter/lib/src/rendering/paragraph.dart
@@ -403,6 +403,20 @@
     return _textPainter.getWordBoundary(position);
   }
 
+  /// Returns the size of the text as laid out.
+  ///
+  /// This can differ from [size] if the text overflowed or if the [constraints]
+  /// provided by the parent [RenderObject] forced the layout to be bigger than
+  /// necessary for the given [text].
+  ///
+  /// This returns the [TextPainter.size] of the underlying [TextPainter].
+  ///
+  /// Valid only after [layout].
+  Size get textSize {
+    assert(!debugNeedsLayout);
+    return _textPainter.size;
+  }
+
   @override
   void describeSemanticsConfiguration(SemanticsConfiguration config) {
     super.describeSemanticsConfiguration(config);
diff --git a/packages/flutter/lib/src/rendering/semantics.dart b/packages/flutter/lib/src/rendering/semantics.dart
index 2ede294..e8e2bc0 100644
--- a/packages/flutter/lib/src/rendering/semantics.dart
+++ b/packages/flutter/lib/src/rendering/semantics.dart
@@ -596,9 +596,18 @@
 
   static final SemanticsConfiguration _kEmptyConfig = new SemanticsConfiguration();
 
+  /// Reconfigures the properties of this object to describe the configuration
+  /// provided in the `config` argument and the children listen in the
+  /// `childrenInInversePaintOrder` argument.
+  ///
+  /// The arguments may be null; this represents an empty configuration (all
+  /// values at their defaults, no children).
+  ///
+  /// No reference is kept to the [SemanticsConfiguration] object, but the child
+  /// list is used as-is and should therefore not be changed after this call.
   void updateWith({
     @required SemanticsConfiguration config,
-    @required  List<SemanticsNode> childrenInInversePaintOrder,
+    @required List<SemanticsNode> childrenInInversePaintOrder,
   }) {
     config ??= _kEmptyConfig;
     if (_isDifferentFromCurrentSemanticAnnotation(config))
diff --git a/packages/flutter/lib/src/rendering/stack.dart b/packages/flutter/lib/src/rendering/stack.dart
index 36b7029..0d72ef0 100644
--- a/packages/flutter/lib/src/rendering/stack.dart
+++ b/packages/flutter/lib/src/rendering/stack.dart
@@ -332,14 +332,20 @@
     markNeedsLayout();
   }
 
-  /// How to align the non-positioned children in the stack.
+  /// How to align the non-positioned or partially-positioned children in the
+  /// stack.
   ///
   /// The non-positioned children are placed relative to each other such that
   /// the points determined by [alignment] are co-located. For example, if the
   /// [alignment] is [Alignment.topLeft], then the top left corner of
   /// each non-positioned child will be located at the same global coordinate.
   ///
-  /// If this is set to a [AlignmentDirectional] object, then [textDirection]
+  /// Partially-positioned children, those that do not specify an alignment in a
+  /// particular axis (e.g. that have neither `top` nor `bottom` set), use the
+  /// alignment to determine how they should be positioned in that
+  /// under-specified axis.
+  ///
+  /// If this is set to an [AlignmentDirectional] object, then [textDirection]
   /// must not be null.
   AlignmentGeometry get alignment => _alignment;
   AlignmentGeometry _alignment;
@@ -504,20 +510,26 @@
 
         child.layout(childConstraints, parentUsesSize: true);
 
-        double x = 0.0;
-        if (childParentData.left != null)
+        double x;
+        if (childParentData.left != null) {
           x = childParentData.left;
-        else if (childParentData.right != null)
+        } else if (childParentData.right != null) {
           x = size.width - childParentData.right - child.size.width;
+        } else {
+          x = _resolvedAlignment.alongOffset(size - child.size).dx;
+        }
 
         if (x < 0.0 || x + child.size.width > size.width)
           _hasVisualOverflow = true;
 
-        double y = 0.0;
-        if (childParentData.top != null)
+        double y;
+        if (childParentData.top != null) {
           y = childParentData.top;
-        else if (childParentData.bottom != null)
+        } else if (childParentData.bottom != null) {
           y = size.height - childParentData.bottom - child.size.height;
+        } else {
+          y = _resolvedAlignment.alongOffset(size - child.size).dy;
+        }
 
         if (y < 0.0 || y + child.size.height > size.height)
           _hasVisualOverflow = true;
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index a000c6a..90114c8 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -2239,9 +2239,10 @@
 /// Positioned children are those wrapped in a [Positioned] widget that has at
 /// least one non-null property. The stack sizes itself to contain all the
 /// non-positioned children, which are positioned according to [alignment]
-/// (which defaults to the top-left corner). The positioned children are then
-/// placed relative to the stack according to their top, right, bottom, and left
-/// properties.
+/// (which defaults to the top-left corner in left-to-right environments and the
+/// top-right corner in right-to-left environments). The positioned children are
+/// then placed relative to the stack according to their top, right, bottom, and
+/// left properties.
 ///
 /// The stack paints its children in order with the first child being at the
 /// bottom. If you want to change the order in which the children paint, you
@@ -2282,12 +2283,18 @@
     List<Widget> children: const <Widget>[],
   }) : super(key: key, children: children);
 
-  /// How to align the non-positioned children in the stack.
+  /// How to align the non-positioned and partially-positioned children in the
+  /// stack.
   ///
   /// The non-positioned children are placed relative to each other such that
   /// the points determined by [alignment] are co-located. For example, if the
   /// [alignment] is [Alignment.topLeft], then the top left corner of
   /// each non-positioned child will be located at the same global coordinate.
+  ///
+  /// Partially-positioned children, those that do not specify an alignment in a
+  /// particular axis (e.g. that have neither `top` nor `bottom` set), use the
+  /// alignment to determine how they should be positioned in that
+  /// under-specified axis.
   final AlignmentGeometry alignment;
 
   /// The text direction with which to resolve [alignment].
@@ -2398,6 +2405,12 @@
 /// [height] properties can be used to give the dimensions, with one
 /// corresponding position property (e.g. [top] and [height]).
 ///
+/// If all three values on a particular axis are null, then the
+/// [Stack.alignment] property is used to position the child.
+///
+/// If all six values are null, the child is a non-positioned child. The [Stack]
+/// uses only the non-positioned children to size itself.
+///
 /// See also:
 ///
 ///  * [PositionedDirectional], which adapts to the ambient [Directionality].
@@ -2413,6 +2426,8 @@
   ///
   ///  * [Positioned.directional], which specifies the widget's horizontal
   ///    position using `start` and `end` rather than `left` and `right`.
+  ///  * [PositionedDirectional], which is similar to [Positioned.directional]
+  ///    but adapts to the ambient [Directionality].
   const Positioned({
     Key key,
     this.left,
@@ -2530,36 +2545,54 @@
   ///
   /// Only two out of the three horizontal values ([left], [right], [width]) can be
   /// set. The third must be null.
+  ///
+  /// If all three are null, the [Stack.alignment] is used to position the child
+  /// horizontally.
   final double left;
 
   /// The distance that the child's top edge is inset from the top of the stack.
   ///
   /// Only two out of the three vertical values ([top], [bottom], [height]) can be
   /// set. The third must be null.
+  ///
+  /// If all three are null, the [Stack.alignment] is used to position the child
+  /// vertically.
   final double top;
 
   /// The distance that the child's right edge is inset from the right of the stack.
   ///
   /// Only two out of the three horizontal values ([left], [right], [width]) can be
   /// set. The third must be null.
+  ///
+  /// If all three are null, the [Stack.alignment] is used to position the child
+  /// horizontally.
   final double right;
 
   /// The distance that the child's bottom edge is inset from the bottom of the stack.
   ///
   /// Only two out of the three vertical values ([top], [bottom], [height]) can be
   /// set. The third must be null.
+  ///
+  /// If all three are null, the [Stack.alignment] is used to position the child
+  /// vertically.
   final double bottom;
 
   /// The child's width.
   ///
   /// Only two out of the three horizontal values ([left], [right], [width]) can be
   /// set. The third must be null.
+  ///
+  /// If all three are null, the [Stack.alignment] is used to position the child
+  /// horizontally.
   final double width;
 
   /// The child's height.
   ///
   /// Only two out of the three vertical values ([top], [bottom], [height]) can be
   /// set. The third must be null.
+  ///
+  /// If all three are null, the [Stack.alignment] is used to position the child
+  /// vertically.
   final double height;
 
   @override
@@ -3897,6 +3930,18 @@
       ..textScaleFactor = textScaleFactor
       ..maxLines = maxLines;
   }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(new EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: TextAlign.start));
+    description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    description.add(new FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true));
+    description.add(new EnumProperty<TextOverflow>('overflow', overflow, defaultValue: TextOverflow.clip));
+    description.add(new DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: 1.0));
+    description.add(new IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
+    description.add(new StringProperty('text', text.toPlainText()));
+  }
 }
 
 /// A widget that displays a [dart:ui.Image] directly.
diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart
index c047394..04be6d5 100644
--- a/packages/flutter/lib/src/widgets/framework.dart
+++ b/packages/flutter/lib/src/widgets/framework.dart
@@ -4519,6 +4519,11 @@
   void removeChildRenderObject(RenderObject child) {
     assert(false);
   }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    return widget.debugDescribeChildren();
+  }
 }
 
 /// An [Element] that uses a [SingleChildRenderObjectWidget] as its configuration.
diff --git a/packages/flutter/lib/src/widgets/icon_data.dart b/packages/flutter/lib/src/widgets/icon_data.dart
index 98c4a4d..c34a754 100644
--- a/packages/flutter/lib/src/widgets/icon_data.dart
+++ b/packages/flutter/lib/src/widgets/icon_data.dart
@@ -29,7 +29,14 @@
   /// The font family from which the glyph for the [codePoint] will be selected.
   final String fontFamily;
 
-  // The name of the package from which the font family is included.
+  /// The name of the package from which the font family is included.
+  ///
+  /// The name is used by the [Icon] widget when configuring the [TextStyle] so
+  /// that the given [fontFamily] is obtained from the appropriate asset.
+  ///
+  /// See also:
+  ///
+  ///  * [TextStyle], which describes how to use fonts from other packages.
   final String fontPackage;
 
   @override
diff --git a/packages/flutter/lib/src/widgets/text.dart b/packages/flutter/lib/src/widgets/text.dart
index b8165f7..a2ff2df 100644
--- a/packages/flutter/lib/src/widgets/text.dart
+++ b/packages/flutter/lib/src/widgets/text.dart
@@ -292,5 +292,11 @@
     super.debugFillProperties(description);
     description.add(new StringProperty('data', data, showName: false));
     style?.debugFillProperties(description);
+    description.add(new EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
+    description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    description.add(new FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true));
+    description.add(new EnumProperty<TextOverflow>('overflow', overflow, defaultValue: null));
+    description.add(new DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null));
+    description.add(new IntProperty('maxLines', maxLines, defaultValue: null));
   }
 }
diff --git a/packages/flutter/test/painting/text_painter_rtl_test.dart b/packages/flutter/test/painting/text_painter_rtl_test.dart
index e1edb61..065d7ac 100644
--- a/packages/flutter/test/painting/text_painter_rtl_test.dart
+++ b/packages/flutter/test/painting/text_painter_rtl_test.dart
@@ -46,6 +46,9 @@
     expect(painter.text.text.length, 28);
     painter.layout();
 
+    // The skips here are because the old rendering code considers the bidi formatting characters
+    // to be part of the word sometimes and not others, which is fine, but we'd mildly prefer if
+    // we were consistently considering them part of words always.
     final TextRange hebrew1 = painter.getWordBoundary(const TextPosition(offset: 4, affinity: TextAffinity.downstream));
     expect(hebrew1, const TextRange(start: 0, end: 8), skip: skipExpectsWithKnownBugs);
     final TextRange english2 = painter.getWordBoundary(const TextPosition(offset: 14, affinity: TextAffinity.downstream));
diff --git a/packages/flutter/test/widgets/stack_test.dart b/packages/flutter/test/widgets/stack_test.dart
index 62652e4..a3e4cdf 100644
--- a/packages/flutter/test/widgets/stack_test.dart
+++ b/packages/flutter/test/widgets/stack_test.dart
@@ -629,4 +629,122 @@
       ),
     );
   });
+
+  testWidgets('Alignment with partially-positioned children', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      new Directionality(
+        textDirection: TextDirection.rtl,
+        child: new Stack(
+          alignment: Alignment.center,
+          children: <Widget>[
+            const SizedBox(width: 100.0, height: 100.0),
+            const Positioned(left: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const Positioned(right: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const Positioned(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const Positioned(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(start: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(end: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+          ],
+        ),
+      ),
+    );
+    expect(tester.getRect(find.byType(SizedBox).at(0)), new Rect.fromLTWH(350.0, 250.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(1)), new Rect.fromLTWH(0.0,   250.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(2)), new Rect.fromLTWH(700.0, 250.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(3)), new Rect.fromLTWH(350.0, 0.0,   100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(4)), new Rect.fromLTWH(350.0, 500.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(5)), new Rect.fromLTWH(700.0, 250.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(6)), new Rect.fromLTWH(0.0,   250.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(7)), new Rect.fromLTWH(350.0, 0.0,   100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(8)), new Rect.fromLTWH(350.0, 500.0, 100.0, 100.0));
+
+    await tester.pumpWidget(
+      new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new Stack(
+          alignment: Alignment.center,
+          children: <Widget>[
+            const SizedBox(width: 100.0, height: 100.0),
+            const Positioned(left: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const Positioned(right: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const Positioned(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const Positioned(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(start: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(end: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+          ],
+        ),
+      ),
+    );
+    expect(tester.getRect(find.byType(SizedBox).at(0)), new Rect.fromLTWH(350.0, 250.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(1)), new Rect.fromLTWH(0.0,   250.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(2)), new Rect.fromLTWH(700.0, 250.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(3)), new Rect.fromLTWH(350.0, 0.0,   100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(4)), new Rect.fromLTWH(350.0, 500.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(5)), new Rect.fromLTWH(0.0,   250.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(6)), new Rect.fromLTWH(700.0, 250.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(7)), new Rect.fromLTWH(350.0, 0.0,   100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(8)), new Rect.fromLTWH(350.0, 500.0, 100.0, 100.0));
+
+    await tester.pumpWidget(
+      new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new Stack(
+          alignment: Alignment.bottomRight,
+          children: <Widget>[
+            const SizedBox(width: 100.0, height: 100.0),
+            const Positioned(left: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const Positioned(right: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const Positioned(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const Positioned(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(start: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(end: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+          ],
+        ),
+      ),
+    );
+    expect(tester.getRect(find.byType(SizedBox).at(0)), new Rect.fromLTWH(700.0, 500.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(1)), new Rect.fromLTWH(0.0,   500.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(2)), new Rect.fromLTWH(700.0, 500.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(3)), new Rect.fromLTWH(700.0, 0.0,   100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(4)), new Rect.fromLTWH(700.0, 500.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(5)), new Rect.fromLTWH(0.0,   500.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(6)), new Rect.fromLTWH(700.0, 500.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(7)), new Rect.fromLTWH(700.0, 0.0,   100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(8)), new Rect.fromLTWH(700.0, 500.0, 100.0, 100.0));
+
+    await tester.pumpWidget(
+      new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new Stack(
+          alignment: Alignment.topLeft,
+          children: <Widget>[
+            const SizedBox(width: 100.0, height: 100.0),
+            const Positioned(left: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const Positioned(right: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const Positioned(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const Positioned(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(start: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(end: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+            const PositionedDirectional(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
+          ],
+        ),
+      ),
+    );
+    expect(tester.getRect(find.byType(SizedBox).at(0)), new Rect.fromLTWH(0.0,   0.0,   100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(1)), new Rect.fromLTWH(0.0,   0.0,   100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(2)), new Rect.fromLTWH(700.0, 0.0,   100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(3)), new Rect.fromLTWH(0.0,   0.0,   100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(4)), new Rect.fromLTWH(0.0,   500.0, 100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(5)), new Rect.fromLTWH(0.0,   0.0,   100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(6)), new Rect.fromLTWH(700.0, 0.0,   100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(7)), new Rect.fromLTWH(0.0,   0.0,   100.0, 100.0));
+    expect(tester.getRect(find.byType(SizedBox).at(8)), new Rect.fromLTWH(0.0,   500.0, 100.0, 100.0));
+  });
 }
diff --git a/packages/flutter/test/widgets/table_test.dart b/packages/flutter/test/widgets/table_test.dart
index 4d8c8a9..5ce9a7f 100644
--- a/packages/flutter/test/widgets/table_test.dart
+++ b/packages/flutter/test/widgets/table_test.dart
@@ -765,23 +765,23 @@
       equalsIgnoringHashCodes(
         'Table-[GlobalKey#00000](renderObject: RenderTable#00000)\n'
         '├Text("A")\n'
-        '│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
+        '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "A", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
         '├Text("B")\n'
-        '│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
+        '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "B", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
         '├Text("C")\n'
-        '│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
+        '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "C", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
         '├Text("D")\n'
-        '│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
+        '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "D", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
         '├Text("EEE")\n'
-        '│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
+        '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "EEE", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
         '├Text("F")\n'
-        '│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
+        '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "F", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
         '├Text("G")\n'
-        '│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
+        '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "G", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
         '├Text("H")\n'
-        '│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
+        '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "H", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
         '└Text("III")\n'
-        ' └RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n',
+        ' └RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "III", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
       ),
     );
   });
diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart
index c9970d3..af4903b 100644
--- a/packages/flutter_test/test/widget_tester_test.dart
+++ b/packages/flutter_test/test/widget_tester_test.dart
@@ -52,7 +52,7 @@
       final String message = failure.message;
 
       expect(message, contains('Expected: no matching nodes in the widget tree\n'));
-      expect(message, contains('Actual: ?:<exactly one widget with text "foo": Text("foo")>\n'));
+      expect(message, contains('Actual: ?:<exactly one widget with text "foo": Text("foo", textDirection: ltr)>\n'));
       expect(message, contains('Which: means one was found but none were expected\n'));
     });
 
@@ -70,7 +70,7 @@
       final String message = failure.message;
 
       expect(message, contains('Expected: no matching nodes in the widget tree\n'));
-      expect(message, contains('Actual: ?:<exactly one widget with text "foo" (ignoring offstage widgets): Text("foo")>\n'));
+      expect(message, contains('Actual: ?:<exactly one widget with text "foo" (ignoring offstage widgets): Text("foo", textDirection: ltr)>\n'));
       expect(message, contains('Which: means one was found but none were expected\n'));
     });
 
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
index 651c65a..7cfab01 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -738,6 +738,10 @@
     } on WebSocketChannelException catch (error) {
       throwToolExit('Error connecting to observatory: $error');
       return null;
+    } on rpc.RpcException catch (error) {
+      printError('Error ${error.code} received from application: ${error.message}');
+      printTrace('${error.data}');
+      return null;
     }
   }