Fix tests to use Ahem, and helpful changes around that (#9332)

* Fix tests to use Ahem, and helpful changes around that

- Fix fonts that had metric-specific behaviours.

- LiveTestWidgetsFlutterBinding.allowAllFrames has been renamed
  to LiveTestWidgetsFlutterBinding.framePolicy.

- LiveTestWidgetsFlutterBinding now defaults to using a frame policy
  that pumps slightly more frames, to animate the pointer crosshairs.

- Added "flutter run --use-test-fonts" to enable Ahem on devices.

- Changed how idle() works to be more effective in live mode.

- Display the test name in live mode (unless ahem fonts are enabled).

- Added a toString to TextSelectionPoint.

- Style nit fixes.

* Roll engine to get Ahem changes.

* Update tests for dartdoc changes.

* Fix flutter_tools tests
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index c84d3fc..a56ed9c 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-5d9a6422577d95c242f45f48c47b431f7cf3c548
+1fed16fb25f3f7afc8303116d6ef707c4043c127
diff --git a/dev/automated_tests/flutter_test/test_async_utils_guarded_expectation.txt b/dev/automated_tests/flutter_test/test_async_utils_guarded_expectation.txt
index 4601a54..e61ce99 100644
--- a/dev/automated_tests/flutter_test/test_async_utils_guarded_expectation.txt
+++ b/dev/automated_tests/flutter_test/test_async_utils_guarded_expectation.txt
@@ -15,7 +15,8 @@
 <<skip until matching line>>
 \(elided .+\)
 
-
+The test description was:
+TestAsyncUtils - custom guarded sections
 ════════════════════════════════════════════════════════════════════════════════════════════════════
 .*(this line has more of the test framework's output)?
   Test failed\. See exception logs above\.
diff --git a/dev/automated_tests/flutter_test/test_async_utils_unguarded_expectation.txt b/dev/automated_tests/flutter_test/test_async_utils_unguarded_expectation.txt
index de23568..1705102 100644
--- a/dev/automated_tests/flutter_test/test_async_utils_unguarded_expectation.txt
+++ b/dev/automated_tests/flutter_test/test_async_utils_unguarded_expectation.txt
@@ -14,7 +14,8 @@
 <<skip until matching line>>
 (elided [0-9]+ frames from .+)
 
-
+The test description was:
+TestAsyncUtils - handling unguarded async helper functions
 ════════════════════════════════════════════════════════════════════════════════════════════════════
 .*..:.. \+0 -1: - TestAsyncUtils - handling unguarded async helper functions *
   Test failed. See exception logs above.
diff --git a/dev/benchmarks/microbenchmarks/lib/stocks/build_bench.dart b/dev/benchmarks/microbenchmarks/lib/stocks/build_bench.dart
index f3ca937..ed81233 100644
--- a/dev/benchmarks/microbenchmarks/lib/stocks/build_bench.dart
+++ b/dev/benchmarks/microbenchmarks/lib/stocks/build_bench.dart
@@ -22,7 +22,7 @@
   // This allows us to call onBeginFrame even when the engine didn't request it,
   // and have it actually do something:
   final LiveTestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
-  binding.allowAllFrames = true;
+  binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
 
   final Stopwatch watch = new Stopwatch();
   int iterations = 0;
diff --git a/dev/benchmarks/microbenchmarks/lib/stocks/layout_bench.dart b/dev/benchmarks/microbenchmarks/lib/stocks/layout_bench.dart
index b317210..663adc8 100644
--- a/dev/benchmarks/microbenchmarks/lib/stocks/layout_bench.dart
+++ b/dev/benchmarks/microbenchmarks/lib/stocks/layout_bench.dart
@@ -21,7 +21,7 @@
   // This allows us to call onBeginFrame even when the engine didn't request it,
   // and have it actually do something:
   final LiveTestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
-  binding.allowAllFrames = true;
+  binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
 
   final Stopwatch watch = new Stopwatch();
   int iterations = 0;
diff --git a/dev/tools/dartdoc.dart b/dev/tools/dartdoc.dart
index 3ae633a..cc02754 100644
--- a/dev/tools/dartdoc.dart
+++ b/dev/tools/dartdoc.dart
@@ -108,9 +108,9 @@
 
 void sanityCheckDocs() {
   final List<String> canaries = <String>[
-    '$kDocRoot/api/dart-io/File-class.html',
-    '$kDocRoot/api/dart-ui/Canvas-class.html',
-    '$kDocRoot/api/dart-ui/Canvas/drawRect.html',
+    '$kDocRoot/api/dart.io/File-class.html',
+    '$kDocRoot/api/dart_ui/Canvas-class.html',
+    '$kDocRoot/api/dart_ui/Canvas/drawRect.html',
     '$kDocRoot/api/flutter_test/WidgetTester/pumpWidget.html',
     '$kDocRoot/api/material/Material-class.html',
     '$kDocRoot/api/material/Tooltip-class.html',
diff --git a/examples/flutter_gallery/test/calculator/smoke_test.dart b/examples/flutter_gallery/test/calculator/smoke_test.dart
index 93ed624..1292617 100644
--- a/examples/flutter_gallery/test/calculator/smoke_test.dart
+++ b/examples/flutter_gallery/test/calculator/smoke_test.dart
@@ -9,7 +9,7 @@
 void main() {
   final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
   if (binding is LiveTestWidgetsFlutterBinding)
-    binding.allowAllFrames = true;
+    binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
 
   // We press the "1" and the "2" buttons and check that the display
   // reads "12".
diff --git a/examples/flutter_gallery/test/example_code_display_test.dart b/examples/flutter_gallery/test/example_code_display_test.dart
index fcc9142..32e1922 100644
--- a/examples/flutter_gallery/test/example_code_display_test.dart
+++ b/examples/flutter_gallery/test/example_code_display_test.dart
@@ -9,7 +9,7 @@
 void main() {
   final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
   if (binding is LiveTestWidgetsFlutterBinding)
-    binding.allowAllFrames = true;
+    binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
 
   testWidgets('Flutter gallery button example code displays', (WidgetTester tester) async {
     // Regression test for https://github.com/flutter/flutter/issues/6147
diff --git a/examples/flutter_gallery/test/pesto_test.dart b/examples/flutter_gallery/test/pesto_test.dart
index a1c57a2..13257ec 100644
--- a/examples/flutter_gallery/test/pesto_test.dart
+++ b/examples/flutter_gallery/test/pesto_test.dart
@@ -9,7 +9,7 @@
 void main() {
   final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
   if (binding is LiveTestWidgetsFlutterBinding)
-    binding.allowAllFrames = true;
+    binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
 
   // Regression test for https://github.com/flutter/flutter/pull/5168
   testWidgets('Pesto appbar heroics', (WidgetTester tester) async {
diff --git a/examples/flutter_gallery/test/simple_smoke_test.dart b/examples/flutter_gallery/test/simple_smoke_test.dart
index 7fcbea3..776c5a7 100644
--- a/examples/flutter_gallery/test/simple_smoke_test.dart
+++ b/examples/flutter_gallery/test/simple_smoke_test.dart
@@ -9,7 +9,7 @@
 void main() {
   final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
   if (binding is LiveTestWidgetsFlutterBinding)
-    binding.allowAllFrames = true;
+    binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
 
   testWidgets('Flutter Gallery app simple smoke test', (WidgetTester tester) async {
     flutter_gallery_main.main(); // builds the app and schedules a frame but doesn't trigger one
diff --git a/examples/flutter_gallery/test/update_test.dart b/examples/flutter_gallery/test/update_test.dart
index 4cd742b..24cbf11 100644
--- a/examples/flutter_gallery/test/update_test.dart
+++ b/examples/flutter_gallery/test/update_test.dart
@@ -13,7 +13,7 @@
 void main() {
   final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
   if (binding is LiveTestWidgetsFlutterBinding)
-    binding.allowAllFrames = true;
+    binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
 
   // Regression test for https://github.com/flutter/flutter/pull/5168
   testWidgets('update dialog', (WidgetTester tester) async {
diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart
index 75e4b22..306dadb 100644
--- a/packages/flutter/lib/src/rendering/editable.dart
+++ b/packages/flutter/lib/src/rendering/editable.dart
@@ -40,6 +40,17 @@
 
   /// Direction of the text at this edge of the selection.
   final TextDirection direction;
+
+  @override
+  String toString() {
+    switch (direction) {
+      case TextDirection.ltr:
+        return '$point-ltr';
+      case TextDirection.rtl:
+        return '$point-rtl';
+    }
+    return '$point';
+  }
 }
 
 /// A single line of editable text.
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart
index 412aafc..7ae0e72 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:io';
 
 import 'package:flutter_test/flutter_test.dart';
 import 'package:flutter/material.dart';
@@ -41,12 +40,12 @@
   SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall);
 
   const String kThreeLines =
-    'First line of text is here abcdef ghijkl mnopqrst. ' +
-    'Second line of text goes until abcdef ghijkl mnopq. ' +
-    'Third line of stuff keeps going until abcdef ghijk. ';
+    'First line of text is ' +
+    'Second line goes until ' +
+    'Third line of stuff ';
   const String kFourLines =
     kThreeLines +
-    'Fourth line won\'t display and ends at abcdef ghi. ';
+    'Fourth line won\'t display and ends at';
 
   // Returns the first RenderEditable.
   RenderEditable findRenderEditable(WidgetTester tester) {
@@ -69,7 +68,8 @@
   Point textOffsetToPosition(WidgetTester tester, int offset) {
     final RenderEditable renderEditable = findRenderEditable(tester);
     final List<TextSelectionPoint> endpoints = renderEditable.getEndpointsForSelection(
-        new TextSelection.collapsed(offset: offset));
+      new TextSelection.collapsed(offset: offset),
+    );
     expect(endpoints.length, 1);
     return endpoints[0].point + const Offset(0.0, -2.0);
   }
@@ -102,15 +102,18 @@
     final Size emptyInputSize = inputBox.size;
 
     Future<Null> checkText(String testValue) async {
-      await tester.enterText(find.byType(EditableText), testValue);
+      return TestAsyncUtils.guard(() async {
+        await tester.enterText(find.byType(EditableText), testValue);
 
-      // Check that the onChanged event handler fired.
-      expect(textFieldValue, equals(testValue));
+        // Check that the onChanged event handler fired.
+        expect(textFieldValue, equals(testValue));
 
-      return await tester.pumpWidget(builder());
+        await tester.pumpWidget(builder());
+      });
     }
 
     await checkText(' ');
+
     expect(findTextFieldBox(), equals(inputBox));
     expect(inputBox.size, equals(emptyInputSize));
 
@@ -492,7 +495,7 @@
     await tester.pumpWidget(builder());
 
     final String testValue = kThreeLines;
-    final String cutValue = 'First line of stuff keeps going until abcdef ghijk. ';
+    final String cutValue = 'First line of stuff ';
     await tester.enterText(find.byType(EditableText), testValue);
 
     await tester.pumpWidget(builder());
@@ -513,8 +516,8 @@
     await gesture.up();
     await tester.pump();
 
-    expect(controller.selection.baseOffset, 76);
-    expect(controller.selection.extentOffset, 81);
+    expect(controller.selection.baseOffset, 39);
+    expect(controller.selection.extentOffset, 44);
 
     final RenderEditable renderEditable = findRenderEditable(tester);
     final List<TextSelectionPoint> endpoints = renderEditable.getEndpointsForSelection(
@@ -531,8 +534,8 @@
     await gesture.up();
     await tester.pumpWidget(builder());
 
-    expect(controller.selection.baseOffset, 76);
-    expect(controller.selection.extentOffset, 108);
+    expect(controller.selection.baseOffset, 39);
+    expect(controller.selection.extentOffset, 50);
 
     // Drag the left handle to the first line, just after 'First'.
     handlePos = endpoints[0].point + const Offset(-1.0, 1.0);
@@ -545,13 +548,13 @@
     await tester.pumpWidget(builder());
 
     expect(controller.selection.baseOffset, 5);
-    expect(controller.selection.extentOffset, 108);
+    expect(controller.selection.extentOffset, 50);
 
     await tester.tap(find.text('CUT'));
     await tester.pumpWidget(builder());
     expect(controller.selection.isCollapsed, true);
     expect(controller.text, cutValue);
-  }, skip: Platform.isMacOS); // Skip due to https://github.com/flutter/flutter/issues/6961
+  });
 
   testWidgets('Can scroll multiline input', (WidgetTester tester) async {
     final Key textFieldKey = new UniqueKey();
@@ -571,10 +574,12 @@
     }
 
     await tester.pumpWidget(builder());
+    await tester.pump(const Duration(seconds: 1));
 
     await tester.enterText(find.byType(EditableText), kFourLines);
 
     await tester.pumpWidget(builder());
+    await tester.pump(const Duration(seconds: 1));
 
     RenderBox findInputBox() => tester.renderObject(find.byKey(textFieldKey));
     final RenderBox inputBox = findInputBox();
@@ -590,11 +595,11 @@
     TestGesture gesture = await tester.startGesture(firstPos, pointer: 7);
     await tester.pump();
     await gesture.moveBy(const Offset(0.0, -1000.0));
-    await tester.pump(const Duration(seconds: 2));
+    await tester.pump(const Duration(seconds: 1));
     // Wait and drag again to trigger https://github.com/flutter/flutter/issues/6329
     // (No idea why this is necessary, but the bug wouldn't repro without it.)
     await gesture.moveBy(const Offset(0.0, -1000.0));
-    await tester.pump(const Duration(seconds: 2));
+    await tester.pump(const Duration(seconds: 1));
     await gesture.up();
     await tester.pump();
 
@@ -609,27 +614,26 @@
     // Now try scrolling by dragging the selection handle.
 
     // Long press the 'i' in 'Fourth line' to select the word.
-    await tester.pump(const Duration(seconds: 2));
+    await tester.pump(const Duration(seconds: 1));
     final Point untilPos = textOffsetToPosition(tester, kFourLines.indexOf('Fourth line')+8);
     gesture = await tester.startGesture(untilPos, pointer: 7);
-    await tester.pump(const Duration(seconds: 2));
+    await tester.pump(const Duration(seconds: 1));
     await gesture.up();
-    await tester.pump();
+    await tester.pump(const Duration(seconds: 1));
 
     final RenderEditable renderEditable = findRenderEditable(tester);
-    final List<TextSelectionPoint> endpoints = renderEditable.getEndpointsForSelection(
-        controller.selection);
+    final List<TextSelectionPoint> endpoints = renderEditable.getEndpointsForSelection(controller.selection);
     expect(endpoints.length, 2);
 
     // Drag the left handle to the first line, just after 'First'.
     final Point handlePos = endpoints[0].point + const Offset(-1.0, 1.0);
     final Point newHandlePos = textOffsetToPosition(tester, kFourLines.indexOf('First') + 5);
     gesture = await tester.startGesture(handlePos, pointer: 7);
-    await tester.pump();
+    await tester.pump(const Duration(seconds: 1));
     await gesture.moveTo(newHandlePos + const Offset(0.0, -10.0));
-    await tester.pump();
+    await tester.pump(const Duration(seconds: 1));
     await gesture.up();
-    await tester.pump();
+    await tester.pump(const Duration(seconds: 1));
 
     // The text should have scrolled up with the handle to keep the active
     // cursor visible, back to its original position.
@@ -638,7 +642,7 @@
     expect(newFirstPos.y, firstPos.y);
     expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isTrue);
     expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isFalse);
-  }, skip: Platform.isMacOS); // Skip due to https://github.com/flutter/flutter/issues/6961
+  });
 
   testWidgets('InputField smoke test', (WidgetTester tester) async {
     String textFieldValue;
@@ -658,16 +662,18 @@
 
     await tester.pumpWidget(builder());
 
-    Future<Null> checkText(String testValue) async {
-      await tester.enterText(find.byType(EditableText), testValue);
+    Future<Null> checkText(String testValue) {
+      return TestAsyncUtils.guard(() async {
+        await tester.enterText(find.byType(EditableText), testValue);
 
-      // Check that the onChanged event handler fired.
-      expect(textFieldValue, equals(testValue));
+        // Check that the onChanged event handler fired.
+        expect(textFieldValue, equals(testValue));
 
-      return await tester.pumpWidget(builder());
+        await tester.pumpWidget(builder());
+      });
     }
 
-    checkText('Hello World');
+    await checkText('Hello World');
   });
 
   testWidgets('InputField with global key', (WidgetTester tester) async {
@@ -691,15 +697,17 @@
     await tester.pumpWidget(builder());
 
     Future<Null> checkText(String testValue) async {
-      await tester.enterText(find.byType(EditableText), testValue);
+      return TestAsyncUtils.guard(() async {
+        await tester.enterText(find.byType(EditableText), testValue);
 
-      // Check that the onChanged event handler fired.
-      expect(textFieldValue, equals(testValue));
+        // Check that the onChanged event handler fired.
+        expect(textFieldValue, equals(testValue));
 
-      return await tester.pumpWidget(builder());
+        await tester.pumpWidget(builder());
+      });
     }
 
-    checkText('Hello World');
+    await checkText('Hello World');
   });
 
   testWidgets('TextField with default hintStyle', (WidgetTester tester) async {
@@ -929,35 +937,28 @@
         ),
       ),
     ));
-
     expect(tester.testTextInput.editingState['text'], isEmpty);
 
     await tester.tap(find.byType(TextField));
     await tester.pump();
-
     expect(tester.testTextInput.editingState['text'], equals('Initial Text'));
 
     controller.text = 'Updated Text';
     await tester.idle();
-
     expect(tester.testTextInput.editingState['text'], equals('Updated Text'));
 
     setState(() {
       currentController = controller2;
     });
-
     await tester.pump();
-
     expect(tester.testTextInput.editingState['text'], equals('More Text'));
 
     controller.text = 'Ignored Text';
     await tester.idle();
-
     expect(tester.testTextInput.editingState['text'], equals('More Text'));
 
     controller2.text = 'Final Text';
     await tester.idle();
-
     expect(tester.testTextInput.editingState['text'], equals('Final Text'));
   });
 }
diff --git a/packages/flutter/test/rendering/paragraph_test.dart b/packages/flutter/test/rendering/paragraph_test.dart
index 9c514c1..99f19dc 100644
--- a/packages/flutter/test/rendering/paragraph_test.dart
+++ b/packages/flutter/test/rendering/paragraph_test.dart
@@ -76,7 +76,10 @@
 
   test('overflow test', () {
     final RenderParagraph paragraph = new RenderParagraph(
-      const TextSpan(text: 'This is\na wrapping test. It should wrap at manual newlines, and if softWrap is true, also at spaces.'),
+      const TextSpan(
+        text: 'This\n' // 4 characters * 10px font size = 40px width on the first line
+              'is a wrapping test. It should wrap at manual newlines, and if softWrap is true, also at spaces.',
+        style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0)),
       maxLines: 1,
       softWrap: true,
     );
@@ -90,7 +93,7 @@
     }
 
     // Lay out in a narrow box to force wrapping.
-    layout(paragraph, constraints: const BoxConstraints(maxWidth: 50.0));
+    layout(paragraph, constraints: const BoxConstraints(maxWidth: 50.0)); // enough to fit "This" but not "This is"
     final double lineHeight = paragraph.size.height;
 
     relayoutWith(maxLines: 3, softWrap: true, overflow: TextOverflow.clip);
diff --git a/packages/flutter/test/widgets/scrollable_fling_test.dart b/packages/flutter/test/widgets/scrollable_fling_test.dart
index 656aee2..fb788cf 100644
--- a/packages/flutter/test/widgets/scrollable_fling_test.dart
+++ b/packages/flutter/test/widgets/scrollable_fling_test.dart
@@ -2,21 +2,27 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:io';
-
 import 'package:flutter_test/flutter_test.dart';
 import 'package:flutter/material.dart';
 
+const TextStyle testFont = const TextStyle(
+  color: const Color(0xFF00FF00),
+  fontFamily: 'Ahem',
+);
+
 Future<Null> pumpTest(WidgetTester tester, TargetPlatform platform) async {
   await tester.pumpWidget(new Container());
   await tester.pumpWidget(new MaterialApp(
     theme: new ThemeData(
-      platform: platform
+      platform: platform,
     ),
-    home: new ListView.builder(
-      itemBuilder: (BuildContext context, int index) {
-        return new Text('$index');
-      },
+    home: new Container(
+      color: const Color(0xFF111111),
+      child: new ListView.builder(
+        itemBuilder: (BuildContext context, int index) {
+          return new Text('$index', style: testFont);
+        },
+      ),
     ),
   ));
   return null;
@@ -53,44 +59,44 @@
     final List<String> log = <String>[];
 
     final List<Widget> textWidgets = <Widget>[];
-    for (int i = 0; i < 250; i++)
-      textWidgets.add(new GestureDetector(onTap: () { log.add('tap $i'); }, child: new Text('$i')));
+    for (int i = 0; i < 250; i += 1)
+      textWidgets.add(new GestureDetector(onTap: () { log.add('tap $i'); }, child: new Text('$i', style: testFont)));
     await tester.pumpWidget(new ListView(children: textWidgets));
 
     expect(log, equals(<String>[]));
     await tester.tap(find.byType(Scrollable));
     await tester.pump(const Duration(milliseconds: 50));
-    expect(log, equals(<String>['tap 18']));
+    expect(log, equals(<String>['tap 21']));
     await tester.fling(find.byType(Scrollable), const Offset(0.0, -200.0), 1000.0);
     await tester.pump(const Duration(milliseconds: 50));
-    expect(log, equals(<String>['tap 18']));
+    expect(log, equals(<String>['tap 21']));
+    await tester.tap(find.byType(Scrollable)); // should stop the fling but not tap anything
+    await tester.pump(const Duration(milliseconds: 50));
+    expect(log, equals(<String>['tap 21']));
     await tester.tap(find.byType(Scrollable));
     await tester.pump(const Duration(milliseconds: 50));
-    expect(log, equals(<String>['tap 18']));
-    await tester.tap(find.byType(Scrollable));
-    await tester.pump(const Duration(milliseconds: 50));
-    expect(log, equals(<String>['tap 18', 'tap 31']));
-  }, skip: Platform.isMacOS); // Skip due to https://github.com/flutter/flutter/issues/6961
+    expect(log, equals(<String>['tap 21', 'tap 35']));
+  });
 
   testWidgets('fling and wait and tap', (WidgetTester tester) async {
     final List<String> log = <String>[];
 
     final List<Widget> textWidgets = <Widget>[];
-    for (int i = 0; i < 250; i++)
-      textWidgets.add(new GestureDetector(onTap: () { log.add('tap $i'); }, child: new Text('$i')));
+    for (int i = 0; i < 250; i += 1)
+      textWidgets.add(new GestureDetector(onTap: () { log.add('tap $i'); }, child: new Text('$i', style: testFont)));
     await tester.pumpWidget(new ListView(children: textWidgets));
 
     expect(log, equals(<String>[]));
     await tester.tap(find.byType(Scrollable));
     await tester.pump(const Duration(milliseconds: 50));
-    expect(log, equals(<String>['tap 18']));
+    expect(log, equals(<String>['tap 21']));
     await tester.fling(find.byType(Scrollable), const Offset(0.0, -200.0), 1000.0);
     await tester.pump(const Duration(milliseconds: 50));
-    expect(log, equals(<String>['tap 18']));
-    await tester.pump(const Duration(seconds: 50));
-    expect(log, equals(<String>['tap 18']));
+    expect(log, equals(<String>['tap 21']));
+    await tester.pump(const Duration(seconds: 50)); // long wait, so the fling will have ended at the end of it
+    expect(log, equals(<String>['tap 21']));
     await tester.tap(find.byType(Scrollable));
     await tester.pump(const Duration(milliseconds: 50));
-    expect(log, equals(<String>['tap 18', 'tap 42']));
-  }, skip: Platform.isMacOS); // Skip due to https://github.com/flutter/flutter/issues/6961
+    expect(log, equals(<String>['tap 21', 'tap 48']));
+  });
 }
diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart
index 7bb8ae7..3223f94 100644
--- a/packages/flutter_test/lib/src/binding.dart
+++ b/packages/flutter_test/lib/src/binding.dart
@@ -159,6 +159,9 @@
   ///
   /// The supplied EnginePhase is the final phase reached during the pump pass;
   /// if not supplied, the whole pass is executed.
+  ///
+  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
+  /// this method works when the test is run with `flutter run`.
   Future<Null> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsTree ]);
 
   /// Artificially calls dispatchLocaleChanged on the Widget binding,
@@ -175,13 +178,20 @@
   /// Acts as if the application went idle.
   ///
   /// Runs all remaining microtasks, including those scheduled as a result of
-  /// running them, until there are no more microtasks scheduled.
+  /// running them, until there are no more microtasks scheduled. Then, runs any
+  /// previously scheduled timers with zero time, and completes the returned future.
   ///
-  /// Does not run timers. May result in an infinite loop or run out of memory
-  /// if microtasks continue to recursively schedule new microtasks.
+  /// May result in an infinite loop or run out of memory if microtasks continue
+  /// to recursively schedule new microtasks. Will not run any timers scheduled
+  /// after this method was invoked, even if they are zero-time timers.
   Future<Null> idle() {
-    TestAsyncUtils.guardSync();
-    return new Future<Null>.value();
+    return TestAsyncUtils.guard(() {
+      final Completer<Null> completer = new Completer<Null>();
+      Timer.run(() {
+        completer.complete(null);
+      });
+      return completer.future;
+    });
   }
 
   /// Convert the given point from the global coodinate system (as used by
@@ -272,7 +282,11 @@
   ///
   /// The `invariantTester` argument is called after the `testBody`'s [Future]
   /// completes. If it throws, then the test is marked as failed.
-  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester);
+  ///
+  /// The `description` is used by the [LiveTestWidgetsFlutterBinding] to
+  /// show a label on the screen during the test. The description comes from
+  /// the value passed to [testWidgets]. It must not be null.
+  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester, { String description: '' });
 
   /// This is called during test execution before and after the body has been
   /// executed.
@@ -305,7 +319,8 @@
       _currentTestCompleter.complete(null);
   }
 
-  Future<Null> _runTest(Future<Null> testBody(), VoidCallback invariantTester) {
+  Future<Null> _runTest(Future<Null> testBody(), VoidCallback invariantTester, String description) {
+    assert(description != null);
     assert(inTest);
     _oldExceptionHandler = FlutterError.onError;
     int _exceptionCount = 0; // number of un-taken exceptions
@@ -392,6 +407,8 @@
               information.writeln('At the time of the failure, the widget tree looked as follows:');
               information.writeln('# ${treeDump.split("\n").takeWhile((String s) => s != "").join("\n# ")}');
             }
+            if (description.isNotEmpty)
+              information.writeln('The test description was:\n$description');
           }
         ));
         assert(_parentZone != null);
@@ -514,7 +531,7 @@
   @override
   Future<Null> idle() {
     final Future<Null> result = super.idle();
-    _fakeAsync.flushMicrotasks();
+    _fakeAsync.elapse(const Duration());
     return result;
   }
 
@@ -551,7 +568,8 @@
   }
 
   @override
-  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester) {
+  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester, { String description: '' }) {
+    assert(description != null);
     assert(!inTest);
     assert(_fakeAsync == null);
     assert(_clock == null);
@@ -560,7 +578,7 @@
     Future<Null> testBodyResult;
     _fakeAsync.run((FakeAsync fakeAsync) {
       assert(fakeAsync == _fakeAsync);
-      testBodyResult = _runTest(testBody, invariantTester);
+      testBodyResult = _runTest(testBody, invariantTester, description);
       assert(inTest);
     });
     // testBodyResult is a Future that was created in the Zone of the fakeAsync.
@@ -603,6 +621,35 @@
 
 }
 
+/// Available policies for how a [LiveTestWidgetsFlutterBinding] should paint
+/// frames.
+///
+/// These values are set on the binding's
+/// [LiveTestWidgetsFlutterBinding.framePolicy] property. The default is
+/// [fadePointers].
+enum LiveTestWidgetsFlutterBindingFramePolicy {
+  /// Strictly show only frames that are explicitly pumped. This most closely
+  /// matches the behavior of tests when run under `flutter test`.
+  onlyPumps,
+
+  /// Show pumped frames, and additionally schedule and run frames to fade
+  /// out the pointer crosshairs and other debugging information shown by
+  /// the binding.
+  ///
+  /// This can result in additional frames being pumped beyond those that
+  /// the test itself requests, which can cause differences in behavior.
+  fadePointers,
+
+  /// Show every frame that the framework requests, even if the frames are not
+  /// explicitly pumped.
+  ///
+  /// This can help with orienting the developer when looking at
+  /// heavily-animated situations, and will almost certainly result in
+  /// additional frames being pumped beyond those that the test itself requests,
+  /// which can cause differences in behavior.
+  fullyLive,
+}
+
 /// A variant of [TestWidgetsFlutterBinding] for executing tests in
 /// the `flutter run` environment, on a device. This is intended to
 /// allow interactive test development.
@@ -611,13 +658,21 @@
 /// a device from a development computer, see the [flutter_driver]
 /// package and the `flutter drive` command.
 ///
-/// This binding overrides the default [SchedulerBinding] behavior to
-/// ensure that tests work in the same way in this environment as they
-/// would under the [AutomatedTestWidgetsFlutterBinding]. To override
-/// this (and see intermediate frames that the test does not
-/// explicitly trigger), set [allowAllFrames] to true. (This is likely
-/// to make tests fail, though, especially if e.g. they test how many
-/// times a particular widget was built.)
+/// When running tests using `flutter run`, consider adding the
+/// `--use-test-fonts` argument so that the fonts used match those used under
+/// `flutter test`. (This forces all text to use the "Ahem" font, which is a
+/// font that covers ASCII characters and gives them all the appearance of a
+/// square whose size equals the font size.)
+///
+/// This binding overrides the default [SchedulerBinding] behavior to ensure
+/// that tests work in the same way in this environment as they would under the
+/// [AutomatedTestWidgetsFlutterBinding]. To override this (and see intermediate
+/// frames that the test does not explicitly trigger), set [framePolicy] to
+/// [LiveTestWidgetsFlutterBindingFramePolicy.fullyLive]. (This is likely to
+/// make tests fail, though, especially if e.g. they test how many times a
+/// particular widget was built.) The default behavior is to show pumped frames
+/// and a few additional frames when pointers are triggered (to animate the
+/// pointer crosshairs).
 ///
 /// This binding does not support the [EnginePhase] argument to
 /// [pump]. (There would be no point setting it to a value that
@@ -644,6 +699,7 @@
 
   Completer<Null> _pendingFrame;
   bool _expectingFrame = false;
+  bool _viewNeedsPaint = false;
 
   /// Whether to have [pump] with a duration only pump a single frame
   /// (as would happen in a normal test environment using
@@ -652,31 +708,46 @@
   /// asynchronous pause in the test (as would normally happen when
   /// running an application with [WidgetsFlutterBinding]).
   ///
-  /// `false` is the default behavior, which is to only pump once.
+  /// * [LiveTestWidgetsFlutterBindingFramePolicy.fadePointers] is the default
+  ///   behavior, which is to only pump once, except when there has been some
+  ///   activity with [TestPointer]s, in which case those are shown and may pump
+  ///   additional frames.
   ///
-  /// `true` allows all frame requests from the engine to be serviced.
+  /// * [LiveTestWidgetsFlutterBindingFramePolicy.onlyPumps] is the strictest
+  ///   behavior, which is to only pump once. This most closely matches the
+  ///   [AutomatedTestWidgetsFlutterBinding] (`flutter test`) behavior.
   ///
-  /// Setting this to `true` means pumping extra frames, which might
-  /// involve calling builders more, or calling paint callbacks more,
-  /// etc, which might interfere with the test. If you know your test
-  /// file wouldn't be affected by this, you can set it to true
-  /// persistently in that particular test file. To set this to `true`
-  /// while still allowing the test file to work as a normal test, add
-  /// the following code to your test file at the top of your `void
-  /// main() { }` function, before calls to `testWidgets`:
+  /// * [LiveTestWidgetsFlutterBindingFramePolicy.fullyLive] allows all frame
+  ///   requests from the engine to be serviced, even those the test did not
+  ///   explicitly pump.
+  ///
+  /// Setting this to anything other than
+  /// [LiveTestWidgetsFlutterBindingFramePolicy.onlyPumps] means pumping extra
+  /// frames, which might involve calling builders more, or calling paint
+  /// callbacks more, etc, which might interfere with the test. If you know your
+  /// test file wouldn't be affected by this, you can set it to
+  /// [LiveTestWidgetsFlutterBindingFramePolicy.fullyLive] persistently in that
+  /// particular test file. To set this to
+  /// [LiveTestWidgetsFlutterBindingFramePolicy.fullyLive] while still allowing
+  /// the test file to work as a normal test, add the following code to your
+  /// test file at the top of your `void main() { }` function, before calls to
+  /// [testWidgets]:
   ///
   /// ```dart
   /// TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
   /// if (binding is LiveTestWidgetsFlutterBinding)
-  ///   binding.allowAllFrames = true;
+  ///   binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
   /// ```
-  bool allowAllFrames = false;
+  LiveTestWidgetsFlutterBindingFramePolicy framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fadePointers;
 
   @override
   void handleBeginFrame(Duration rawTimeStamp) {
-    if (_expectingFrame || allowAllFrames)
+    if (_expectingFrame ||
+        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fullyLive) ||
+        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fadePointers && _viewNeedsPaint))
       super.handleBeginFrame(rawTimeStamp);
-    if (_expectingFrame) {
+    _viewNeedsPaint = false;
+    if (_expectingFrame) { // set during pump
       assert(_pendingFrame != null);
       _pendingFrame.complete(); // unlocks the test API
       _pendingFrame = null;
@@ -689,13 +760,21 @@
   @override
   void initRenderView() {
     assert(renderView == null);
-    renderView = new _LiveTestRenderView(configuration: createViewConfiguration());
+    renderView = new _LiveTestRenderView(
+      configuration: createViewConfiguration(),
+      onNeedPaint: _handleViewNeedsPaint,
+    );
     renderView.scheduleInitialFrame();
   }
 
   @override
   _LiveTestRenderView get renderView => super.renderView;
 
+  void _handleViewNeedsPaint() {
+    _viewNeedsPaint = true;
+    renderView.markNeedsPaint();
+  }
+
   /// An object to which real device events should be routed.
   ///
   /// Normally, device events are silently dropped. However, if this property is
@@ -719,7 +798,7 @@
           if (!event.down)
             renderView._pointers[event.pointer].decay = _kPointerDecay;
         }
-        renderView.markNeedsPaint();
+        _handleViewNeedsPaint();
         super.dispatchEvent(event, result, source: source);
         break;
       case TestBindingEventSource.device:
@@ -751,10 +830,12 @@
   }
 
   @override
-  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester) async {
+  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester, { String description: '' }) async {
+    assert(description != null);
     assert(!inTest);
     _inTest = true;
-    return _runTest(testBody, invariantTester);
+    renderView._setDescription(description);
+    return _runTest(testBody, invariantTester, description);
   }
 
   @override
@@ -857,7 +938,8 @@
 
 class _LiveTestRenderView extends RenderView {
   _LiveTestRenderView({
-    ViewConfiguration configuration
+    ViewConfiguration configuration,
+    this.onNeedPaint,
   }) : super(configuration: configuration);
 
   @override
@@ -865,8 +947,28 @@
   @override
   set configuration(covariant TestViewConfiguration value) { super.configuration = value; }
 
+  final VoidCallback onNeedPaint;
+
   final Map<int, _LiveTestPointerRecord> _pointers = <int, _LiveTestPointerRecord>{};
 
+  TextPainter _label;
+  static const TextStyle _labelStyle = const TextStyle(
+    fontFamily: 'sans-serif',
+    fontSize: 10.0,
+  );
+  void _setDescription(String value) {
+    assert(value != null);
+    if (value.isEmpty) {
+      _label = null;
+      return;
+    }
+    _label ??= new TextPainter(textAlign: TextAlign.left);
+    _label.text = new TextSpan(text: value, style: _labelStyle);
+    _label.layout();
+    if (onNeedPaint != null)
+      onNeedPaint();
+  }
+
   @override
   bool hitTest(HitTestResult result, { Point position }) {
     final Matrix4 transform = configuration.toHitTestMatrix();
@@ -906,9 +1008,10 @@
         .where((int pointer) => _pointers[pointer].decay == 0)
         .toList()
         .forEach(_pointers.remove);
-      if (dirty)
-        scheduleMicrotask(markNeedsPaint);
+      if (dirty && onNeedPaint != null)
+        scheduleMicrotask(onNeedPaint);
     }
+    _label?.paint(context.canvas, offset - const Offset(0.0, 10.0));
   }
 }
 
diff --git a/packages/flutter_test/lib/src/test_async_utils.dart b/packages/flutter_test/lib/src/test_async_utils.dart
index 100089c..bba63a4 100644
--- a/packages/flutter_test/lib/src/test_async_utils.dart
+++ b/packages/flutter_test/lib/src/test_async_utils.dart
@@ -282,9 +282,13 @@
     }
   }
 
+  static bool _stripAsynchronousSuspensions(String line) {
+    return line != '<asynchronous suspension>';
+  }
+
   static _StackEntry _findResponsibleMethod(StackTrace rawStack, String method, StringBuffer errors) {
     assert(method == 'guard' || method == 'guardSync');
-    final List<String> stack = rawStack.toString().split('\n');
+    final List<String> stack = rawStack.toString().split('\n').where(_stripAsynchronousSuspensions).toList();
     assert(stack.last == '');
     stack.removeLast();
     final RegExp getClassPattern = new RegExp(r'^#[0-9]+ +([^. ]+)');
diff --git a/packages/flutter_test/lib/src/test_text_input.dart b/packages/flutter_test/lib/src/test_text_input.dart
index e4c3629..f7631f8 100644
--- a/packages/flutter_test/lib/src/test_text_input.dart
+++ b/packages/flutter_test/lib/src/test_text_input.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:typed_data';
 
 import 'package:flutter/services.dart';
 
@@ -48,7 +49,7 @@
           <dynamic>[_client, value.toJSON()],
         ),
       ),
-      (_) {},
+      (ByteData data) { /* response from framework is discarded */ },
     );
   }
 
diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart
index 8fa900c..88e0afe 100644
--- a/packages/flutter_test/lib/src/widget_tester.dart
+++ b/packages/flutter_test/lib/src/widget_tester.dart
@@ -51,7 +51,17 @@
   final WidgetTester tester = new WidgetTester._(binding);
   timeout ??= binding.defaultTestTimeout;
   test_package.group('-', () {
-    test_package.test(description, () => binding.runTest(() => callback(tester), tester._endOfTestVerifications), skip: skip);
+    test_package.test(
+      description,
+      () {
+        return binding.runTest(
+          () => callback(tester),
+          tester._endOfTestVerifications,
+          description: description ?? '',
+        );
+      },
+      skip: skip,
+    );
     test_package.tearDown(binding.postTest);
   }, timeout: timeout);
 }
@@ -109,7 +119,10 @@
   final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
   assert(binding is! AutomatedTestWidgetsFlutterBinding);
   final WidgetTester tester = new WidgetTester._(binding);
-  return binding.runTest(() => callback(tester), tester._endOfTestVerifications) ?? new Future<Null>.value();
+  return binding.runTest(
+    () => callback(tester),
+    tester._endOfTestVerifications,
+  ) ?? new Future<Null>.value();
 }
 
 /// Assert that `actual` matches `matcher`.
@@ -163,6 +176,9 @@
   /// Subsequent calls to this is different from [pump] in that it forces a full
   /// rebuild of the tree, even if [widget] is the same as the previous call.
   /// [pump] will only rebuild the widgets that have changed.
+  ///
+  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
+  /// this method works when the test is run with `flutter run`.
   Future<Null> pumpWidget(Widget widget, [
     Duration duration,
     EnginePhase phase = EnginePhase.sendSemanticsTree
@@ -182,6 +198,9 @@
   ///
   /// This is a convenience function that just calls
   /// [TestWidgetsFlutterBinding.pump].
+  ///
+  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
+  /// this method works when the test is run with `flutter run`.
   @override
   Future<Null> pump([
     Duration duration,
@@ -426,23 +445,25 @@
   /// Tests that just need to add text to widgets like [Input] or [TextField]
   /// only need to call [enterText].
   Future<Null> showKeyboard(Finder finder) async {
-    // TODO(hansmuller): Once find.descendant (#7789) lands replace the following
-    // RHS with state(find.descendant(finder), find.byType(EditableText)).
-    final EditableTextState editable = state(finder);
-    if (editable != binding.focusedEditable) {
-      binding.focusedEditable = editable;
-      await pump();
-    }
-    return null;
+    return TestAsyncUtils.guard(() async {
+      // TODO(hansmuller): Once find.descendant (#7789) lands replace the following
+      // RHS with state(find.descendant(finder), find.byType(EditableText)).
+      final EditableTextState editable = state(finder);
+      if (editable != binding.focusedEditable) {
+        binding.focusedEditable = editable;
+        await pump();
+      }
+    });
   }
 
   /// Give the EditableText widget specified by [finder] the focus and
   /// enter [text] as if it been provided by the onscreen keyboard.
   Future<Null> enterText(Finder finder, String text) async {
-    await showKeyboard(finder);
-    testTextInput.enterText(text);
-    await idle();
-    return null;
+    return TestAsyncUtils.guard(() async {
+      await showKeyboard(finder);
+      testTextInput.enterText(text);
+      await idle();
+    });
   }
 }
 
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 2e1bf5e..9bc7f22 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -337,7 +337,7 @@
 
     if (debuggingOptions.debuggingEnabled) {
       // TODO(devoncarew): Remember the forwarding information (so we can later remove the
-      // port forwarding).
+      // port forwarding or set it up again when adb fails on us).
       observatoryDiscovery = new ProtocolDiscovery.observatory(
         getLogReader(), portForwarder: portForwarder, hostPort: debuggingOptions.observatoryPort);
       diagnosticDiscovery = new ProtocolDiscovery.diagnosticService(
@@ -363,6 +363,8 @@
         cmd.addAll(<String>['--ez', 'enable-checked-mode', 'true']);
       if (debuggingOptions.startPaused)
         cmd.addAll(<String>['--ez', 'start-paused', 'true']);
+      if (debuggingOptions.useTestFonts)
+        cmd.addAll(<String>['--ez', 'use-test-fonts', 'true']);
     }
     cmd.add(apk.launchActivity);
     final String result = runCheckedSync(cmd);
@@ -372,9 +374,8 @@
       return new LaunchResult.failed();
     }
 
-    if (!debuggingOptions.debuggingEnabled) {
+    if (!debuggingOptions.debuggingEnabled)
       return new LaunchResult.succeeded();
-    }
 
     // Wait for the service protocol port here. This will complete once the
     // device has printed "Observatory is listening on...".
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index 16da917..50483fe 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -296,6 +296,7 @@
     final String deviceId = _getStringArg(args, 'deviceId', required: true);
     final String projectDirectory = _getStringArg(args, 'projectDirectory', required: true);
     final bool startPaused = _getBoolArg(args, 'startPaused') ?? false;
+    final bool useTestFonts = _getBoolArg(args, 'useTestFonts') ?? false;
     final String route = _getStringArg(args, 'route');
     final String mode = _getStringArg(args, 'mode');
     final String target = _getStringArg(args, 'target');
@@ -309,10 +310,25 @@
       throw "'$projectDirectory' does not exist";
 
     final BuildMode buildMode = getBuildModeForName(mode) ?? BuildMode.debug;
+    DebuggingOptions options;
+    if (buildMode == BuildMode.release) {
+      options = new DebuggingOptions.disabled(buildMode);
+    } else {
+      options = new DebuggingOptions.enabled(
+        buildMode,
+        startPaused: startPaused,
+        useTestFonts: useTestFonts,
+      );
+    }
 
     final AppInstance app = await startApp(
-        device, projectDirectory, target, route,
-        buildMode, startPaused, enableHotReload);
+      device,
+      projectDirectory,
+      target,
+      route,
+      options,
+      enableHotReload,
+    );
 
     return <String, dynamic>{
       'appId': app.id,
@@ -324,28 +340,14 @@
 
   Future<AppInstance> startApp(
     Device device, String projectDirectory, String target, String route,
-    BuildMode buildMode, bool startPaused, bool enableHotReload, {
+    DebuggingOptions options, bool enableHotReload, {
     String applicationBinary,
     String projectRootPath,
     String packagesFilePath,
     String projectAssets,
   }) async {
-    DebuggingOptions options;
-
-    switch (buildMode) {
-      case BuildMode.debug:
-      case BuildMode.profile:
-        options = new DebuggingOptions.enabled(buildMode, startPaused: startPaused);
-        break;
-      case BuildMode.release:
-        options = new DebuggingOptions.disabled(buildMode);
-        break;
-      default:
-        throw 'unhandle build mode: $buildMode';
-    }
-
-    if (device.isLocalEmulator && !isEmulatorBuildMode(buildMode))
-      throw '${toTitleCase(getModeName(buildMode))} mode is not supported for emulators.';
+    if (device.isLocalEmulator && !isEmulatorBuildMode(options.buildMode))
+      throw '${toTitleCase(getModeName(options.buildMode))} mode is not supported for emulators.';
 
     // We change the current working directory for the duration of the `start` command.
     final Directory cwd = fs.currentDirectory;
diff --git a/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart b/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart
index dcce29e..e399fc6 100644
--- a/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart
+++ b/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart
@@ -25,21 +25,6 @@
 //       -g //lib/flutter/examples/flutter_gallery:flutter_gallery
 
 class FuchsiaReloadCommand extends FlutterCommand {
-  String _fuchsiaRoot;
-  String _projectRoot;
-  String _projectName;
-  String _binaryName;
-  String _fuchsiaProjectPath;
-  String _target;
-  String _address;
-  String _dotPackagesPath;
-
-  @override
-  final String name = 'fuchsia_reload';
-
-  @override
-  final String description = 'Hot reload on Fuchsia.';
-
   FuchsiaReloadCommand() {
     addBuildModeFlags(defaultToRelease: false);
     argParser.addOption('address',
@@ -58,7 +43,7 @@
       help: 'GN target of the application, e.g //path/to/app:app');
     argParser.addOption('name-override',
       abbr: 'n',
-      help: 'On-device name of the application binary');
+      help: 'On-device name of the application binary.');
     argParser.addOption('target',
       abbr: 't',
       defaultsTo: flx.defaultMainPath,
@@ -67,6 +52,21 @@
   }
 
   @override
+  final String name = 'fuchsia_reload';
+
+  @override
+  final String description = 'Hot reload on Fuchsia.';
+
+  String _fuchsiaRoot;
+  String _projectRoot;
+  String _projectName;
+  String _binaryName;
+  String _fuchsiaProjectPath;
+  String _target;
+  String _address;
+  String _dotPackagesPath;
+
+  @override
   Future<Null> runCommand() async {
     Cache.releaseLockEarly();
 
@@ -74,37 +74,34 @@
 
     // Find the network ports used on the device by VM service instances.
     final List<int> servicePorts = await _getServicePorts();
-    if (servicePorts.isEmpty) {
-      throwToolExit("Couldn't find any running Observatory instances.");
-    }
-    for (int port in servicePorts) {
-      printTrace("Fuchsia service port: $port");
-    }
+    if (servicePorts.isEmpty)
+      throwToolExit('Couldn\'t find any running Observatory instances.');
+    for (int port in servicePorts)
+      printTrace('Fuchsia service port: $port');
 
     // Check that there are running VM services on the returned
     // ports, and find the Isolates that are running the target app.
-    final String isolateName = "$_binaryName\$main";
+    final String isolateName = '$_binaryName\$main';
     final List<int> targetPorts = await _filterPorts(servicePorts, isolateName);
-    if (targetPorts.isEmpty) {
-      throwToolExit("No VMs found running $_binaryName");
-    }
-    for (int port in targetPorts) {
-      printTrace("Found $_binaryName at $port");
-    }
+    if (targetPorts.isEmpty)
+      throwToolExit('No VMs found running $_binaryName.');
+    for (int port in targetPorts)
+      printTrace('Found $_binaryName at $port');
 
     // Set up a device and hot runner and attach the hot runner to the first
     // vm service we found.
     final int firstPort = targetPorts[0];
-    final String fullAddress = "$_address:$firstPort";
+    final String fullAddress = '$_address:$firstPort';
     final FuchsiaDevice device = new FuchsiaDevice(fullAddress);
     final HotRunner hotRunner = new HotRunner(
-        device,
-        debuggingOptions: new DebuggingOptions.enabled(getBuildMode()),
-        target: _target,
-        projectRootPath: _fuchsiaProjectPath,
-        packagesFilePath: _dotPackagesPath);
-    final Uri observatoryUri = Uri.parse("http://$fullAddress");
-    printStatus("Connecting to $_binaryName at $observatoryUri");
+      device,
+      debuggingOptions: new DebuggingOptions.enabled(getBuildMode()),
+      target: _target,
+      projectRootPath: _fuchsiaProjectPath,
+      packagesFilePath: _dotPackagesPath,
+    );
+    final Uri observatoryUri = Uri.parse('http://$fullAddress');
+    printStatus('Connecting to $_binaryName at $observatoryUri');
     await hotRunner.attach(observatoryUri, isolateFilter: isolateName);
   }
 
@@ -112,20 +109,19 @@
   Future<List<int>> _filterPorts(List<int> ports, String isolateFilter) async {
     final List<int> result = <int>[];
     for (int port in ports) {
-      final String addr = "http://$_address:$port";
+      final String addr = 'http://$_address:$port';
       final Uri uri = Uri.parse(addr);
       final VMService vmService = VMService.connect(uri);
       await vmService.getVM();
       await vmService.waitForViews();
       if (vmService.vm.firstView == null) {
-        printTrace("Found no views at $addr");
+        printTrace('Found no views at $addr');
         continue;
       }
       for (FlutterView v in vmService.vm.views) {
-        printTrace("At $addr, found view: ${v.uiIsolate.name}");
-        if (v.uiIsolate.name.indexOf(isolateFilter) == 0) {
+        printTrace('At $addr, found view: ${v.uiIsolate.name}');
+        if (v.uiIsolate.name.indexOf(isolateFilter) == 0)
           result.add(port);
-        }
       }
     }
     return result;
@@ -133,48 +129,36 @@
 
   void _validateArguments() {
     _fuchsiaRoot = argResults['fuchsia-root'];
-    if (_fuchsiaRoot == null) {
-      throwToolExit(
-          "Please give the location of the Fuchsia tree with --fuchsia-root");
-    }
-    if (!_directoryExists(_fuchsiaRoot)) {
-      throwToolExit("Specified --fuchsia-root '$_fuchsiaRoot' does not exist");
-    }
+    if (_fuchsiaRoot == null)
+      throwToolExit('Please give the location of the Fuchsia tree with --fuchsia-root.');
+    if (!_directoryExists(_fuchsiaRoot))
+      throwToolExit('Specified --fuchsia-root "$_fuchsiaRoot" does not exist.');
 
     _address = argResults['address'];
-    if (_address == null) {
-      throwToolExit(
-          "Give the address of the device running Fuchsia with --address");
-    }
+    if (_address == null)
+      throwToolExit('Give the address of the device running Fuchsia with --address.');
 
     final List<String> gnTarget = _extractPathAndName(argResults['gn-target']);
     _projectRoot = gnTarget[0];
     _projectName = gnTarget[1];
-    _fuchsiaProjectPath = "$_fuchsiaRoot/$_projectRoot";
-    if (!_directoryExists(_fuchsiaProjectPath)) {
-      throwToolExit(
-          "Target does not exist in the Fuchsia tree: $_fuchsiaProjectPath");
-    }
+    _fuchsiaProjectPath = '$_fuchsiaRoot/$_projectRoot';
+    if (!_directoryExists(_fuchsiaProjectPath))
+      throwToolExit('Target does not exist in the Fuchsia tree: $_fuchsiaProjectPath.');
 
     final String relativeTarget = argResults['target'];
-    if (relativeTarget == null) {
-      throwToolExit('Give the application entry point with --target');
-    }
-    _target = "$_fuchsiaProjectPath/$relativeTarget";
-    if (!_fileExists(_target)) {
-      throwToolExit("Couldn't find application entry point at $_target");
-    }
+    if (relativeTarget == null)
+      throwToolExit('Give the application entry point with --target.');
+    _target = '$_fuchsiaProjectPath/$relativeTarget';
+    if (!_fileExists(_target))
+      throwToolExit('Couldn\'t find application entry point at $_target.');
 
     final String buildType = argResults['build-type'];
-    if (buildType == null) {
-      throwToolExit("Give the build type with --build-type");
-    }
-    final String packagesFileName = "${_projectName}_dart_package.packages";
-    _dotPackagesPath =
-        "$_fuchsiaRoot/out/$buildType/gen/$_projectRoot/$packagesFileName";
-    if (!_fileExists(_dotPackagesPath)) {
-      throwToolExit("Couldn't find .packages file at $_dotPackagesPath");
-    }
+    if (buildType == null)
+      throwToolExit('Give the build type with --build-type.');
+    final String packagesFileName = '${_projectName}_dart_package.packages';
+    _dotPackagesPath = '$_fuchsiaRoot/out/$buildType/gen/$_projectRoot/$packagesFileName';
+    if (!_fileExists(_dotPackagesPath))
+      throwToolExit('Couldn\'t find .packages file at $_dotPackagesPath.');
 
     final String nameOverride = argResults['name-override'];
     if (nameOverride == null) {
@@ -186,26 +170,23 @@
 
   List<String> _extractPathAndName(String gnTarget) {
     final String errorMessage =
-        "fuchsia_reload --target '$gnTarget' should have the form: "
-        "'//path/to/app:name'";
+      'fuchsia_reload --target "$gnTarget" should have the form: '
+      '"//path/to/app:name"';
     // Separate strings like //path/to/target:app into [path/to/target, app]
     final int lastColon = gnTarget.lastIndexOf(':');
-    if (lastColon < 0) {
+    if (lastColon < 0)
       throwToolExit(errorMessage);
-    }
     final String name = gnTarget.substring(lastColon + 1);
     // Skip '//' and chop off after :
-    if ((gnTarget.length < 3) || (gnTarget[0] != '/') || (gnTarget[1] != '/')) {
+    if ((gnTarget.length < 3) || (gnTarget[0] != '/') || (gnTarget[1] != '/'))
       throwToolExit(errorMessage);
-    }
     final String path = gnTarget.substring(2, lastColon);
     return <String>[path, name];
   }
 
   Future<List<int>> _getServicePorts() async {
-    final FuchsiaDeviceCommandRunner runner =
-        new FuchsiaDeviceCommandRunner(_fuchsiaRoot);
-    final List<String> lsOutput = await runner.run("ls /tmp/dart.services");
+    final FuchsiaDeviceCommandRunner runner = new FuchsiaDeviceCommandRunner(_fuchsiaRoot);
+    final List<String> lsOutput = await runner.run('ls /tmp/dart.services');
     final List<int> ports = <int>[];
     for (String s in lsOutput) {
       final String trimmed = s.trim();
@@ -213,9 +194,8 @@
       final String lastWord = trimmed.substring(lastSpace + 1);
       if ((lastWord != '.') && (lastWord != '..')) {
         final int value = int.parse(lastWord, onError: (_) => null);
-        if (value != null) {
+        if (value != null)
           ports.add(value);
-        }
       }
     }
     return ports;
@@ -242,26 +222,24 @@
 
   Future<List<String>> run(String command) async {
     final int tag = _rng.nextInt(999999);
-    const String kNetRunCommand = "out/build-magenta/tools/netruncmd";
+    const String kNetRunCommand = 'out/build-magenta/tools/netruncmd';
     final String netruncmd = fs.path.join(_fuchsiaRoot, kNetRunCommand);
-    const String kNetCP = "out/build-magenta/tools/netcp";
+    const String kNetCP = 'out/build-magenta/tools/netcp';
     final String netcp = fs.path.join(_fuchsiaRoot, kNetCP);
-    final String remoteStdout = "/tmp/netruncmd.$tag";
-    final String localStdout = "${fs.systemTempDirectory.path}/netruncmd.$tag";
-    final String redirectedCommand = "$command > $remoteStdout";
+    final String remoteStdout = '/tmp/netruncmd.$tag';
+    final String localStdout = '${fs.systemTempDirectory.path}/netruncmd.$tag';
+    final String redirectedCommand = '$command > $remoteStdout';
     // Run the command with output directed to a tmp file.
     ProcessResult result =
-        await Process.run(netruncmd, <String>[":", redirectedCommand]);
-    if (result.exitCode != 0) {
+        await Process.run(netruncmd, <String>[':', redirectedCommand]);
+    if (result.exitCode != 0)
       return null;
-    }
     // Copy that file to the local filesystem.
-    result = await Process.run(netcp, <String>[":$remoteStdout", localStdout]);
+    result = await Process.run(netcp, <String>[':$remoteStdout', localStdout]);
     // Try to delete the remote file. Don't care about the result;
-    Process.run(netruncmd, <String>[":", "rm $remoteStdout"]);
-    if (result.exitCode != 0) {
+    Process.run(netruncmd, <String>[':', 'rm $remoteStdout']);
+    if (result.exitCode != 0)
       return null;
-    }
     // Read the local file.
     final File f = fs.file(localStdout);
     List<String> lines;
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index e159780..50fe3ed 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -88,6 +88,14 @@
         defaultsTo: false,
         negatable: false,
         help: 'Start in a paused mode and wait for a debugger to connect.');
+    argParser.addFlag('use-test-fonts',
+        negatable: true,
+        defaultsTo: false,
+        help: 'Enable (and default to) the "Ahem" font. This is a special font\n'
+              'used in tests to remove any dependencies on the font metrics. It\n'
+              'is enabled when you use "flutter test". Set this flag when running\n'
+              'a test using "flutter run" for debugging purposes. This flag is\n'
+              'only available when running in debug mode.');
     argParser.addFlag('build',
         defaultsTo: true,
         help: 'If necessary, build the app before running.');
@@ -126,18 +134,19 @@
         hide: !verboseHelp,
         help: 'Stay resident after launching the application.');
 
-    // Hidden option to enable a benchmarking mode. This will run the given
-    // application, measure the startup time and the app restart time, write the
-    // results out to 'refresh_benchmark.json', and exit. This flag is intended
-    // for use in generating automated flutter benchmarks.
-    argParser.addFlag('benchmark', negatable: false, hide: !verboseHelp);
+    argParser.addFlag('benchmark',
+      negatable: false,
+      hide: !verboseHelp,
+      help: 'Enable a benchmarking mode. This will run the given application,\n'
+            'measure the startup time and the app restart time, write the\n'
+            'results out to "refresh_benchmark.json", and exit. This flag is\n'
+            'intended for use in generating automated flutter benchmarks.');
 
     commandValidator = () {
-      if (!runningWithPrebuiltApplication)
-        commonCommandValidator();
-
       // When running with a prebuilt application, no command validation is
       // necessary.
+      if (!runningWithPrebuiltApplication)
+        commonCommandValidator();
     };
   }
 
@@ -196,6 +205,20 @@
     return super.verifyThenRunCommand();
   }
 
+  DebuggingOptions _createDebuggingOptions() {
+    if (getBuildMode() == BuildMode.release) {
+      return new DebuggingOptions.disabled(getBuildMode());
+    } else {
+      return new DebuggingOptions.enabled(
+        getBuildMode(),
+        startPaused: argResults['start-paused'],
+        useTestFonts: argResults['use-test-fonts'],
+        observatoryPort: observatoryPort,
+        diagnosticPort: diagnosticPort,
+      );
+    }
+  }
+
   @override
   Future<Null> runCommand() async {
 
@@ -212,7 +235,7 @@
       try {
         app = await daemon.appDomain.startApp(
           device, fs.currentDirectory.path, targetFile, route,
-          getBuildMode(), argResults['start-paused'], hotMode,
+          _createDebuggingOptions(), hotMode,
           applicationBinary: argResults['use-application-binary'],
           projectRootPath: argResults['project-root'],
           packagesFilePath: argResults['packages'],
@@ -229,19 +252,6 @@
     if (device.isLocalEmulator && !isEmulatorBuildMode(getBuildMode()))
       throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.');
 
-    DebuggingOptions options;
-
-    if (getBuildMode() == BuildMode.release) {
-      options = new DebuggingOptions.disabled(getBuildMode());
-    } else {
-      options = new DebuggingOptions.enabled(
-        getBuildMode(),
-        startPaused: argResults['start-paused'],
-        observatoryPort: observatoryPort,
-        diagnosticPort: diagnosticPort,
-      );
-    }
-
     if (hotMode) {
       if (!device.supportsHotMode)
         throwToolExit('Hot mode is not supported by this device. Run with --no-hot.');
@@ -258,7 +268,7 @@
       runner = new HotRunner(
         device,
         target: targetFile,
-        debuggingOptions: options,
+        debuggingOptions: _createDebuggingOptions(),
         benchmarkMode: argResults['benchmark'],
         applicationBinary: argResults['use-application-binary'],
         kernelFilePath: argResults['kernel'],
@@ -271,7 +281,7 @@
       runner = new ColdRunner(
         device,
         target: targetFile,
-        debuggingOptions: options,
+        debuggingOptions: _createDebuggingOptions(),
         traceStartup: traceStartup,
         applicationBinary: argResults['use-application-binary'],
         stayResident: stayResident,
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index a69951e..7489153 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -279,12 +279,14 @@
 class DebuggingOptions {
   DebuggingOptions.enabled(this.buildMode, {
     this.startPaused: false,
+    this.useTestFonts: false,
     this.observatoryPort,
     this.diagnosticPort
    }) : debuggingEnabled = true;
 
   DebuggingOptions.disabled(this.buildMode) :
     debuggingEnabled = false,
+    useTestFonts = false,
     startPaused = false,
     observatoryPort = null,
     diagnosticPort = null;
@@ -293,6 +295,7 @@
 
   final BuildMode buildMode;
   final bool startPaused;
+  final bool useTestFonts;
   final int observatoryPort;
   final int diagnosticPort;
 
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 8f642db..f9cd43b 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -228,6 +228,9 @@
     if (debuggingOptions.startPaused)
       launchArguments.add("--start-paused");
 
+    if (debuggingOptions.useTestFonts)
+      launchArguments.add("--use-test-fonts");
+
     if (debuggingOptions.debuggingEnabled) {
       launchArguments.add("--enable-checked-mode");
 
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 3ecd761..fbd99dd 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -455,6 +455,8 @@
         args.add('--enable-checked-mode');
       if (debuggingOptions.startPaused)
         args.add('--start-paused');
+      if (debuggingOptions.useTestFonts)
+        args.add('--use-test-fonts');
 
       final int observatoryPort = await debuggingOptions.findBestObservatoryPort();
       args.add('--observatory-port=$observatoryPort');
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 94ab4bc3..d843fee 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -210,9 +210,8 @@
   }
 
   Future<Null> connectToServiceProtocol(Uri uri, {String isolateFilter}) async {
-    if (!debuggingOptions.debuggingEnabled) {
+    if (!debuggingOptions.debuggingEnabled)
       return new Future<Null>.error('Error the service protocol is not enabled.');
-    }
     vmService = VMService.connect(uri);
     printTrace('Connected to service protocol: $uri');
     await vmService.getVM();
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart
index 7bd6ced..a31a326 100644
--- a/packages/flutter_tools/lib/src/run_cold.dart
+++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -100,9 +100,8 @@
     startTime.stop();
 
     // Connect to observatory.
-    if (debuggingOptions.debuggingEnabled) {
+    if (debuggingOptions.debuggingEnabled)
       await connectToServiceProtocol(_result.observatoryUri);
-    }
 
     if (_result.hasObservatory) {
       connectionInfoCompleter?.complete(new DebugConnectionInfo(