Commit be7be2b8 authored by Ian Hickson's avatar Ian Hickson Committed by Adam Barth

Test service extensions (#7849)

...and fix bugs that the tests uncovered.

WRITE TEST FIND BUG
parent cce70d70
...@@ -2,9 +2,16 @@ ...@@ -2,9 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
class TestTestBinding extends AutomatedTestWidgetsFlutterBinding {
@override
DebugPrintCallback get debugPrintOverride => testPrint;
static void testPrint(String message, { int wrapWidth }) { print(message); }
}
Future<Null> guardedHelper(WidgetTester tester) { Future<Null> guardedHelper(WidgetTester tester) {
return TestAsyncUtils.guard(() async { return TestAsyncUtils.guard(() async {
await tester.pumpWidget(new Text('Hello')); await tester.pumpWidget(new Text('Hello'));
...@@ -12,8 +19,8 @@ Future<Null> guardedHelper(WidgetTester tester) { ...@@ -12,8 +19,8 @@ Future<Null> guardedHelper(WidgetTester tester) {
} }
void main() { void main() {
new TestTestBinding();
testWidgets('TestAsyncUtils - custom guarded sections', (WidgetTester tester) async { testWidgets('TestAsyncUtils - custom guarded sections', (WidgetTester tester) async {
debugPrint = (String message, { int wrapWidth }) { print(message); };
await tester.pumpWidget(new Container()); await tester.pumpWidget(new Container());
expect(find.byElementType(Container), isNotNull); expect(find.byElementType(Container), isNotNull);
guardedHelper(tester); guardedHelper(tester);
......
...@@ -2,16 +2,22 @@ ...@@ -2,16 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/widgets.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
class TestTestBinding extends AutomatedTestWidgetsFlutterBinding {
@override
DebugPrintCallback get debugPrintOverride => testPrint;
static void testPrint(String message, { int wrapWidth }) { print(message); }
}
Future<Null> helperFunction(WidgetTester tester) async { Future<Null> helperFunction(WidgetTester tester) async {
await tester.pump(); await tester.pump();
} }
void main() { void main() {
new TestTestBinding();
testWidgets('TestAsyncUtils - handling unguarded async helper functions', (WidgetTester tester) async { testWidgets('TestAsyncUtils - handling unguarded async helper functions', (WidgetTester tester) async {
debugPrint = (String message, { int wrapWidth }) { print(message); };
helperFunction(tester); helperFunction(tester);
helperFunction(tester); helperFunction(tester);
// this should fail // this should fail
......
...@@ -46,7 +46,7 @@ class TestAssetBundle extends AssetBundle { ...@@ -46,7 +46,7 @@ class TestAssetBundle extends AssetBundle {
} }
@override @override
Future<dynamic> loadStructuredData<T>(String key, Future<T> parser(String value)) async { Future<T> loadStructuredData<T>(String key, Future<T> parser(String value)) async {
return parser(await loadString(key)); return parser(await loadString(key));
} }
......
...@@ -13,6 +13,7 @@ export 'src/foundation/assertions.dart'; ...@@ -13,6 +13,7 @@ export 'src/foundation/assertions.dart';
export 'src/foundation/basic_types.dart'; export 'src/foundation/basic_types.dart';
export 'src/foundation/binding.dart'; export 'src/foundation/binding.dart';
export 'src/foundation/change_notifier.dart'; export 'src/foundation/change_notifier.dart';
export 'src/foundation/debug.dart';
export 'src/foundation/licenses.dart'; export 'src/foundation/licenses.dart';
export 'src/foundation/observer_list.dart'; export 'src/foundation/observer_list.dart';
export 'src/foundation/platform.dart'; export 'src/foundation/platform.dart';
......
...@@ -19,7 +19,7 @@ import 'basic_types.dart'; ...@@ -19,7 +19,7 @@ import 'basic_types.dart';
/// "type" key will be set to the string `_extensionType` to indicate /// "type" key will be set to the string `_extensionType` to indicate
/// that this is a return value from a service extension, and the /// that this is a return value from a service extension, and the
/// "method" key will be set to the full name of the method. /// "method" key will be set to the full name of the method.
typedef Future<Map<String, dynamic>> ServiceExtensionCallback(Map<String, String> parameters); typedef Future<Map<String, String>> ServiceExtensionCallback(Map<String, String> parameters);
/// Base class for mixins that provide singleton services (also known as /// Base class for mixins that provide singleton services (also known as
/// "bindings"). /// "bindings").
...@@ -56,7 +56,7 @@ abstract class BindingBase { ...@@ -56,7 +56,7 @@ abstract class BindingBase {
initServiceExtensions(); initServiceExtensions();
assert(_debugServiceExtensionsRegistered); assert(_debugServiceExtensionsRegistered);
developer.postEvent('Flutter.FrameworkInitialization', <String, dynamic>{}); developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
developer.Timeline.finishSync(); developer.Timeline.finishSync();
} }
...@@ -150,7 +150,7 @@ abstract class BindingBase { ...@@ -150,7 +150,7 @@ abstract class BindingBase {
name: name, name: name,
callback: (Map<String, String> parameters) async { callback: (Map<String, String> parameters) async {
await callback(); await callback();
return <String, dynamic>{}; return <String, String>{};
} }
); );
} }
...@@ -181,7 +181,7 @@ abstract class BindingBase { ...@@ -181,7 +181,7 @@ abstract class BindingBase {
callback: (Map<String, String> parameters) async { callback: (Map<String, String> parameters) async {
if (parameters.containsKey('enabled')) if (parameters.containsKey('enabled'))
await setter(parameters['enabled'] == 'true'); await setter(parameters['enabled'] == 'true');
return <String, dynamic>{ 'enabled': await getter() }; return <String, String>{ 'enabled': await getter() ? 'true' : 'false' };
} }
); );
} }
...@@ -211,7 +211,7 @@ abstract class BindingBase { ...@@ -211,7 +211,7 @@ abstract class BindingBase {
callback: (Map<String, String> parameters) async { callback: (Map<String, String> parameters) async {
if (parameters.containsKey(name)) if (parameters.containsKey(name))
await setter(double.parse(parameters[name])); await setter(double.parse(parameters[name]));
return <String, dynamic>{ name: await getter() }; return <String, String>{ name: (await getter()).toString() };
} }
); );
} }
...@@ -240,7 +240,7 @@ abstract class BindingBase { ...@@ -240,7 +240,7 @@ abstract class BindingBase {
callback: (Map<String, String> parameters) async { callback: (Map<String, String> parameters) async {
if (parameters.containsKey('value')) if (parameters.containsKey('value'))
await setter(parameters['value']); await setter(parameters['value']);
return <String, dynamic>{ 'value': await getter() }; return <String, String>{ 'value': await getter() };
} }
); );
} }
...@@ -267,7 +267,7 @@ abstract class BindingBase { ...@@ -267,7 +267,7 @@ abstract class BindingBase {
assert(method == methodName); assert(method == methodName);
dynamic caughtException; dynamic caughtException;
StackTrace caughtStack; StackTrace caughtStack;
Map<String, dynamic> result; Map<String, String> result;
try { try {
result = await callback(parameters); result = await callback(parameters);
} catch (exception, stack) { } catch (exception, stack) {
...@@ -286,10 +286,10 @@ abstract class BindingBase { ...@@ -286,10 +286,10 @@ abstract class BindingBase {
)); ));
return new developer.ServiceExtensionResponse.error( return new developer.ServiceExtensionResponse.error(
developer.ServiceExtensionResponse.extensionError, developer.ServiceExtensionResponse.extensionError,
JSON.encode(<String, dynamic>{ JSON.encode(<String, String>{
'exception': caughtException.toString(), 'exception': caughtException.toString(),
'stack': caughtStack.toString(), 'stack': caughtStack.toString(),
'method': method 'method': method,
}) })
); );
} }
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'assertions.dart';
import 'print.dart';
/// Returns true if none of the foundation library debug variables have been
/// changed.
///
/// This function is used by the test framework to ensure that debug variables
/// haven't been inadvertently changed.
///
/// The `debugPrintOverride` argument can be specified to indicate the expected
/// value of the [debugPrint] variable. This is useful for test frameworks that
/// override [debugPrint] themselves and want to check that their own custom
/// value wasn't overridden by a test.
///
/// See [https://docs.flutter.io/flutter/foundation/foundation-library.html] for
/// a complete list.
bool debugAssertAllFoundationVarsUnset(String reason, { DebugPrintCallback debugPrintOverride: debugPrintThrottled }) {
assert(() {
if (debugPrint != debugPrintOverride)
throw new FlutterError(reason);
return true;
});
return true;
}
...@@ -57,8 +57,7 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding, ...@@ -57,8 +57,7 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
if (debugPaintSizeEnabled == value) if (debugPaintSizeEnabled == value)
return new Future<Null>.value(); return new Future<Null>.value();
debugPaintSizeEnabled = value; debugPaintSizeEnabled = value;
_forceRepaint(); return _forceRepaint();
return endOfFrame;
} }
); );
return true; return true;
...@@ -78,8 +77,8 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding, ...@@ -78,8 +77,8 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
bool repaint = debugRepaintRainbowEnabled && !value; bool repaint = debugRepaintRainbowEnabled && !value;
debugRepaintRainbowEnabled = value; debugRepaintRainbowEnabled = value;
if (repaint) if (repaint)
_forceRepaint(); return _forceRepaint();
return endOfFrame; return new Future<Null>.value();
} }
); );
return true; return true;
...@@ -249,13 +248,14 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding, ...@@ -249,13 +248,14 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
super.hitTest(result, position); // ignore: abstract_super_member_reference super.hitTest(result, position); // ignore: abstract_super_member_reference
} }
void _forceRepaint() { Future<Null> _forceRepaint() {
RenderObjectVisitor visitor; RenderObjectVisitor visitor;
visitor = (RenderObject child) { visitor = (RenderObject child) {
child.markNeedsPaint(); child.markNeedsPaint();
child.visitChildren(visitor); child.visitChildren(visitor);
}; };
instance?.renderView?.visitChildren(visitor); instance?.renderView?.visitChildren(visitor);
return endOfFrame;
} }
} }
......
...@@ -66,7 +66,7 @@ abstract class AssetBundle { ...@@ -66,7 +66,7 @@ abstract class AssetBundle {
/// ///
/// Implementations may cache the result, so a particular key should only be /// Implementations may cache the result, so a particular key should only be
/// used with one parser for the lifetime of the asset bundle. /// used with one parser for the lifetime of the asset bundle.
Future<dynamic> loadStructuredData<T>(String key, Future<T> parser(String value)); Future<T> loadStructuredData<T>(String key, Future<T> parser(String value));
/// If this is a caching asset bundle, and the given key describes a cached /// If this is a caching asset bundle, and the given key describes a cached
/// asset, then evict the asset from the cache so that the next time it is /// asset, then evict the asset from the cache so that the next time it is
...@@ -110,7 +110,7 @@ class NetworkAssetBundle extends AssetBundle { ...@@ -110,7 +110,7 @@ class NetworkAssetBundle extends AssetBundle {
/// The result is not cached. The parser is run each time the resource is /// The result is not cached. The parser is run each time the resource is
/// fetched. /// fetched.
@override @override
Future<dynamic> loadStructuredData<T>(String key, Future<T> parser(String value)) async { Future<T> loadStructuredData<T>(String key, Future<T> parser(String value)) async {
assert(key != null); assert(key != null);
assert(parser != null); assert(parser != null);
return parser(await loadString(key)); return parser(await loadString(key));
...@@ -159,7 +159,7 @@ abstract class CachingAssetBundle extends AssetBundle { ...@@ -159,7 +159,7 @@ abstract class CachingAssetBundle extends AssetBundle {
/// subsequent calls will be a [SynchronousFuture], which resolves its /// subsequent calls will be a [SynchronousFuture], which resolves its
/// callback synchronously. /// callback synchronously.
@override @override
Future<dynamic> loadStructuredData<T>(String key, Future<T> parser(String value)) { Future<T> loadStructuredData<T>(String key, Future<T> parser(String value)) {
assert(key != null); assert(key != null);
assert(parser != null); assert(parser != null);
if (_structuredDataCache.containsKey(key)) if (_structuredDataCache.containsKey(key))
......
...@@ -86,8 +86,7 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren ...@@ -86,8 +86,7 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren
if (WidgetsApp.showPerformanceOverlayOverride == value) if (WidgetsApp.showPerformanceOverlayOverride == value)
return new Future<Null>.value(); return new Future<Null>.value();
WidgetsApp.showPerformanceOverlayOverride = value; WidgetsApp.showPerformanceOverlayOverride = value;
buildOwner.reassemble(renderViewElement); return _forceRebuild();
return endOfFrame;
} }
); );
...@@ -98,12 +97,19 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren ...@@ -98,12 +97,19 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren
if (WidgetsApp.debugAllowBannerOverride == value) if (WidgetsApp.debugAllowBannerOverride == value)
return new Future<Null>.value(); return new Future<Null>.value();
WidgetsApp.debugAllowBannerOverride = value; WidgetsApp.debugAllowBannerOverride = value;
buildOwner.reassemble(renderViewElement); return _forceRebuild();
return endOfFrame;
} }
); );
} }
Future<Null> _forceRebuild() {
if (renderViewElement != null) {
buildOwner.reassemble(renderViewElement);
return endOfFrame;
}
return new Future<Null>.value();
}
/// The [BuildOwner] in charge of executing the build pipeline for the /// The [BuildOwner] in charge of executing the build pipeline for the
/// widget tree rooted at this binding. /// widget tree rooted at this binding.
BuildOwner get buildOwner => _buildOwner; BuildOwner get buildOwner => _buildOwner;
......
This diff is collapsed.
...@@ -86,6 +86,14 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -86,6 +86,14 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
RendererBinding, RendererBinding,
// Services binding omitted to avoid dragging in the licenses code. // Services binding omitted to avoid dragging in the licenses code.
WidgetsBinding { WidgetsBinding {
TestWidgetsFlutterBinding() {
debugPrint = debugPrintOverride;
}
@protected
DebugPrintCallback get debugPrintOverride => debugPrint;
/// Creates and initializes the binding. This function is /// Creates and initializes the binding. This function is
/// idempotent; calling it a second time will just return the /// idempotent; calling it a second time will just return the
/// previously-created instance. /// previously-created instance.
...@@ -399,6 +407,10 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -399,6 +407,10 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
assert(debugAssertNoTransientCallbacks( assert(debugAssertNoTransientCallbacks(
'An animation is still running even after the widget tree was disposed.' 'An animation is still running even after the widget tree was disposed.'
)); ));
assert(debugAssertAllFoundationVarsUnset(
'The value of a foundation debug variable was changed by the test.',
debugPrintOverride: debugPrintOverride,
));
assert(debugAssertAllRenderVarsUnset( assert(debugAssertAllRenderVarsUnset(
'The value of a rendering debug variable was changed by the test.' 'The value of a rendering debug variable was changed by the test.'
)); ));
...@@ -431,7 +443,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -431,7 +443,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
@override @override
void initInstances() { void initInstances() {
debugPrint = debugPrintSynchronously;
super.initInstances(); super.initInstances();
ui.window.onBeginFrame = null; ui.window.onBeginFrame = null;
} }
...@@ -439,6 +450,9 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { ...@@ -439,6 +450,9 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
FakeAsync _fakeAsync; FakeAsync _fakeAsync;
Clock _clock; Clock _clock;
@override
DebugPrintCallback get debugPrintOverride => debugPrintSynchronously;
@override @override
test_package.Timeout get defaultTestTimeout => const test_package.Timeout(const Duration(seconds: 5)); test_package.Timeout get defaultTestTimeout => const test_package.Timeout(const Duration(seconds: 5));
......
...@@ -82,7 +82,7 @@ Future<Null> _testFile(String testName, int wantedExitCode, String workingDirect ...@@ -82,7 +82,7 @@ Future<Null> _testFile(String testName, int wantedExitCode, String workingDirect
expect(haveSeenStdErrMarker, isFalse); expect(haveSeenStdErrMarker, isFalse);
haveSeenStdErrMarker = true; haveSeenStdErrMarker = true;
} }
expect(outputLine, matches(expectationLine)); expect(outputLine, matches(expectationLine), verbose: true, reason: 'Full output:\n- - - -----8<----- - - -\n${output.join("\n")}\n- - - -----8<----- - - -');
expectationLineNumber += 1; expectationLineNumber += 1;
outputLineNumber += 1; outputLineNumber += 1;
} }
......
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