Commit 2a545243 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

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