Added a Driver wait condition for no pending platform messages (#39196)
This commit is contained in:
parent
6f71ce26d6
commit
5667b78291
@ -201,6 +201,26 @@ class FirstFrameRasterized extends SerializableWaitCondition {
|
||||
String get conditionName => 'FirstFrameRasterizedCondition';
|
||||
}
|
||||
|
||||
/// A condition that waits until there are no pending platform messages.
|
||||
class NoPendingPlatformMessages extends SerializableWaitCondition {
|
||||
/// Creates a [NoPendingPlatformMessages] condition.
|
||||
const NoPendingPlatformMessages();
|
||||
|
||||
/// Factory constructor to parse a [NoPendingPlatformMessages] instance from the
|
||||
/// given JSON map.
|
||||
///
|
||||
/// The [json] argument must not be null.
|
||||
factory NoPendingPlatformMessages.deserialize(Map<String, dynamic> json) {
|
||||
assert(json != null);
|
||||
if (json['conditionName'] != 'NoPendingPlatformMessagesCondition')
|
||||
throw SerializationException('Error occurred during deserializing the NoPendingPlatformMessagesCondition JSON string: $json');
|
||||
return const NoPendingPlatformMessages();
|
||||
}
|
||||
|
||||
@override
|
||||
String get conditionName => 'NoPendingPlatformMessagesCondition';
|
||||
}
|
||||
|
||||
/// A combined condition that waits until all the given [conditions] are met.
|
||||
class CombinedCondition extends SerializableWaitCondition {
|
||||
/// Creates a [CombinedCondition] condition.
|
||||
@ -260,6 +280,8 @@ SerializableWaitCondition _deserialize(Map<String, dynamic> json) {
|
||||
return NoPendingFrame.deserialize(json);
|
||||
case 'FirstFrameRasterizedCondition':
|
||||
return FirstFrameRasterized.deserialize(json);
|
||||
case 'NoPendingPlatformMessagesCondition':
|
||||
return NoPendingPlatformMessages.deserialize(json);
|
||||
case 'CombinedCondition':
|
||||
return CombinedCondition.deserialize(json);
|
||||
}
|
||||
|
@ -56,6 +56,11 @@ class _DriverBinding extends BindingBase with ServicesBinding, SchedulerBinding,
|
||||
callback: extension.call,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
BinaryMessenger createBinaryMessenger() {
|
||||
return TestDefaultBinaryMessenger(super.createBinaryMessenger());
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables Flutter Driver VM service extension.
|
||||
|
@ -3,7 +3,9 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../common/wait.dart';
|
||||
|
||||
@ -32,7 +34,7 @@ abstract class WaitCondition {
|
||||
|
||||
/// A condition that waits until no transient callbacks are scheduled.
|
||||
class _InternalNoTransientCallbacksCondition implements WaitCondition {
|
||||
/// Creates an [InternalNoTransientCallbacksCondition] instance.
|
||||
/// Creates an [_InternalNoTransientCallbacksCondition] instance.
|
||||
const _InternalNoTransientCallbacksCondition();
|
||||
|
||||
/// Factory constructor to parse an [InternalNoTransientCallbacksCondition]
|
||||
@ -60,7 +62,7 @@ class _InternalNoTransientCallbacksCondition implements WaitCondition {
|
||||
|
||||
/// A condition that waits until no pending frame is scheduled.
|
||||
class _InternalNoPendingFrameCondition implements WaitCondition {
|
||||
/// Creates an [InternalNoPendingFrameCondition] instance.
|
||||
/// Creates an [_InternalNoPendingFrameCondition] instance.
|
||||
const _InternalNoPendingFrameCondition();
|
||||
|
||||
/// Factory constructor to parse an [InternalNoPendingFrameCondition] instance
|
||||
@ -88,7 +90,7 @@ class _InternalNoPendingFrameCondition implements WaitCondition {
|
||||
|
||||
/// A condition that waits until the Flutter engine has rasterized the first frame.
|
||||
class _InternalFirstFrameRasterizedCondition implements WaitCondition {
|
||||
/// Creates an [InternalFirstFrameRasterizedCondition] instance.
|
||||
/// Creates an [_InternalFirstFrameRasterizedCondition] instance.
|
||||
const _InternalFirstFrameRasterizedCondition();
|
||||
|
||||
/// Factory constructor to parse an [InternalNoPendingFrameCondition] instance
|
||||
@ -112,16 +114,48 @@ class _InternalFirstFrameRasterizedCondition implements WaitCondition {
|
||||
}
|
||||
}
|
||||
|
||||
/// A condition that waits until no pending platform messages.
|
||||
class _InternalNoPendingPlatformMessagesCondition implements WaitCondition {
|
||||
/// Creates an [_InternalNoPendingPlatformMessagesCondition] instance.
|
||||
const _InternalNoPendingPlatformMessagesCondition();
|
||||
|
||||
/// Factory constructor to parse an [_InternalNoPendingPlatformMessagesCondition] instance
|
||||
/// from the given [SerializableWaitCondition] instance.
|
||||
///
|
||||
/// The [condition] argument must not be null.
|
||||
factory _InternalNoPendingPlatformMessagesCondition.deserialize(SerializableWaitCondition condition) {
|
||||
assert(condition != null);
|
||||
if (condition.conditionName != 'NoPendingPlatformMessagesCondition')
|
||||
throw SerializationException('Error occurred during deserializing from the given condition: ${condition.serialize()}');
|
||||
return const _InternalNoPendingPlatformMessagesCondition();
|
||||
}
|
||||
|
||||
@override
|
||||
bool get condition {
|
||||
final TestDefaultBinaryMessenger binaryMessenger = ServicesBinding.instance.defaultBinaryMessenger;
|
||||
return binaryMessenger.pendingMessageCount == 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> wait() async {
|
||||
final TestDefaultBinaryMessenger binaryMessenger = ServicesBinding.instance.defaultBinaryMessenger;
|
||||
while (!condition) {
|
||||
await binaryMessenger.platformMessagesFinished;
|
||||
}
|
||||
assert(condition);
|
||||
}
|
||||
}
|
||||
|
||||
/// A combined condition that waits until all the given [conditions] are met.
|
||||
class _InternalCombinedCondition implements WaitCondition {
|
||||
/// Creates an [InternalCombinedCondition] instance with the given list of
|
||||
/// Creates an [_InternalCombinedCondition] instance with the given list of
|
||||
/// [conditions].
|
||||
///
|
||||
/// The [conditions] argument must not be null.
|
||||
const _InternalCombinedCondition(this.conditions)
|
||||
: assert(conditions != null);
|
||||
|
||||
/// Factory constructor to parse an [InternalCombinedCondition] instance from
|
||||
/// Factory constructor to parse an [_InternalCombinedCondition] instance from
|
||||
/// the given [SerializableWaitCondition] instance.
|
||||
///
|
||||
/// The [condition] argument must not be null.
|
||||
@ -173,6 +207,8 @@ WaitCondition deserializeCondition(SerializableWaitCondition waitCondition) {
|
||||
return _InternalNoPendingFrameCondition.deserialize(waitCondition);
|
||||
case 'FirstFrameRasterizedCondition':
|
||||
return _InternalFirstFrameRasterizedCondition.deserialize(waitCondition);
|
||||
case 'NoPendingPlatformMessagesCondition':
|
||||
return _InternalNoPendingPlatformMessagesCondition.deserialize(waitCondition);
|
||||
case 'CombinedCondition':
|
||||
return _InternalCombinedCondition.deserialize(waitCondition);
|
||||
}
|
||||
|
@ -262,6 +262,18 @@ void main() {
|
||||
await driver.waitForCondition(const NoPendingFrame(), timeout: _kTestTimeout);
|
||||
});
|
||||
|
||||
test('sends the wait for NoPendingPlatformMessages command', () async {
|
||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||
expect(i.positionalArguments[1], <String, dynamic>{
|
||||
'command': 'waitForCondition',
|
||||
'timeout': _kSerializedTestTimeout,
|
||||
'conditionName': 'NoPendingPlatformMessagesCondition',
|
||||
});
|
||||
return makeMockResponse(<String, dynamic>{});
|
||||
});
|
||||
await driver.waitForCondition(const NoPendingPlatformMessages(), timeout: _kTestTimeout);
|
||||
});
|
||||
|
||||
test('sends the waitForCondition of combined conditions command', () async {
|
||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||
expect(i.positionalArguments[1], <String, dynamic>{
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:flutter_driver/src/common/diagnostics_tree.dart';
|
||||
@ -245,6 +246,207 @@ void main() {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'waiting for NoPendingPlatformMessages returns immediately when there\'re no platform messages', (WidgetTester tester) async {
|
||||
extension
|
||||
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
|
||||
.then<void>(expectAsync1((Map<String, dynamic> r) {
|
||||
result = r;
|
||||
}));
|
||||
|
||||
await tester.idle();
|
||||
expect(
|
||||
result,
|
||||
<String, dynamic>{
|
||||
'isError': false,
|
||||
'response': null,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'waiting for NoPendingPlatformMessages returns until a single method channel call returns', (WidgetTester tester) async {
|
||||
const MethodChannel channel = MethodChannel('helloChannel', JSONMethodCodec());
|
||||
const MessageCodec<dynamic> jsonMessage = JSONMessageCodec();
|
||||
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
|
||||
'helloChannel', (ByteData message) {
|
||||
return Future<ByteData>.delayed(
|
||||
const Duration(milliseconds: 10),
|
||||
() => jsonMessage.encodeMessage(<dynamic>['hello world']));
|
||||
});
|
||||
channel.invokeMethod<String>('sayHello', 'hello');
|
||||
|
||||
extension
|
||||
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
|
||||
.then<void>(expectAsync1((Map<String, dynamic> r) {
|
||||
result = r;
|
||||
}));
|
||||
|
||||
// The channel message are delayed for 10 milliseconds, so nothing happens yet.
|
||||
await tester.pump(const Duration(milliseconds: 5));
|
||||
expect(result, isNull);
|
||||
|
||||
// Now we receive the result.
|
||||
await tester.pump(const Duration(milliseconds: 5));
|
||||
expect(
|
||||
result,
|
||||
<String, dynamic>{
|
||||
'isError': false,
|
||||
'response': null,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'waiting for NoPendingPlatformMessages returns until both method channel calls return', (WidgetTester tester) async {
|
||||
const MessageCodec<dynamic> jsonMessage = JSONMessageCodec();
|
||||
// Configures channel 1
|
||||
const MethodChannel channel1 = MethodChannel('helloChannel1', JSONMethodCodec());
|
||||
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
|
||||
'helloChannel1', (ByteData message) {
|
||||
return Future<ByteData>.delayed(
|
||||
const Duration(milliseconds: 10),
|
||||
() => jsonMessage.encodeMessage(<dynamic>['hello world']));
|
||||
});
|
||||
|
||||
// Configures channel 2
|
||||
const MethodChannel channel2 = MethodChannel('helloChannel2', JSONMethodCodec());
|
||||
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
|
||||
'helloChannel2', (ByteData message) {
|
||||
return Future<ByteData>.delayed(
|
||||
const Duration(milliseconds: 20),
|
||||
() => jsonMessage.encodeMessage(<dynamic>['hello world']));
|
||||
});
|
||||
|
||||
channel1.invokeMethod<String>('sayHello', 'hello');
|
||||
channel2.invokeMethod<String>('sayHello', 'hello');
|
||||
|
||||
extension
|
||||
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
|
||||
.then<void>(expectAsync1((Map<String, dynamic> r) {
|
||||
result = r;
|
||||
}));
|
||||
|
||||
// Neither of the channel responses is received, so nothing happens yet.
|
||||
await tester.pump(const Duration(milliseconds: 5));
|
||||
expect(result, isNull);
|
||||
|
||||
// Result of channel 1 is received, but channel 2 is still pending, so still waiting.
|
||||
await tester.pump(const Duration(milliseconds: 10));
|
||||
expect(result, isNull);
|
||||
|
||||
// Both of the results are received. Now we receive the result.
|
||||
await tester.pump(const Duration(milliseconds: 30));
|
||||
expect(
|
||||
result,
|
||||
<String, dynamic>{
|
||||
'isError': false,
|
||||
'response': null,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'waiting for NoPendingPlatformMessages returns until new method channel call returns', (WidgetTester tester) async {
|
||||
const MessageCodec<dynamic> jsonMessage = JSONMessageCodec();
|
||||
// Configures channel 1
|
||||
const MethodChannel channel1 = MethodChannel('helloChannel1', JSONMethodCodec());
|
||||
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
|
||||
'helloChannel1', (ByteData message) {
|
||||
return Future<ByteData>.delayed(
|
||||
const Duration(milliseconds: 10),
|
||||
() => jsonMessage.encodeMessage(<dynamic>['hello world']));
|
||||
});
|
||||
|
||||
// Configures channel 2
|
||||
const MethodChannel channel2 = MethodChannel('helloChannel2', JSONMethodCodec());
|
||||
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
|
||||
'helloChannel2', (ByteData message) {
|
||||
return Future<ByteData>.delayed(
|
||||
const Duration(milliseconds: 20),
|
||||
() => jsonMessage.encodeMessage(<dynamic>['hello world']));
|
||||
});
|
||||
|
||||
channel1.invokeMethod<String>('sayHello', 'hello');
|
||||
|
||||
// Calls the waiting API before the second channel message is sent.
|
||||
extension
|
||||
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
|
||||
.then<void>(expectAsync1((Map<String, dynamic> r) {
|
||||
result = r;
|
||||
}));
|
||||
|
||||
// The first channel message is not received, so nothing happens yet.
|
||||
await tester.pump(const Duration(milliseconds: 5));
|
||||
expect(result, isNull);
|
||||
|
||||
channel2.invokeMethod<String>('sayHello', 'hello');
|
||||
|
||||
// Result of channel 1 is received, but channel 2 is still pending, so still waiting.
|
||||
await tester.pump(const Duration(milliseconds: 15));
|
||||
expect(result, isNull);
|
||||
|
||||
// Both of the results are received. Now we receive the result.
|
||||
await tester.pump(const Duration(milliseconds: 10));
|
||||
expect(
|
||||
result,
|
||||
<String, dynamic>{
|
||||
'isError': false,
|
||||
'response': null,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'waiting for NoPendingPlatformMessages returns until both old and new method channel calls return', (WidgetTester tester) async {
|
||||
const MessageCodec<dynamic> jsonMessage = JSONMessageCodec();
|
||||
// Configures channel 1
|
||||
const MethodChannel channel1 = MethodChannel('helloChannel1', JSONMethodCodec());
|
||||
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
|
||||
'helloChannel1', (ByteData message) {
|
||||
return Future<ByteData>.delayed(
|
||||
const Duration(milliseconds: 20),
|
||||
() => jsonMessage.encodeMessage(<dynamic>['hello world']));
|
||||
});
|
||||
|
||||
// Configures channel 2
|
||||
const MethodChannel channel2 = MethodChannel('helloChannel2', JSONMethodCodec());
|
||||
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
|
||||
'helloChannel2', (ByteData message) {
|
||||
return Future<ByteData>.delayed(
|
||||
const Duration(milliseconds: 10),
|
||||
() => jsonMessage.encodeMessage(<dynamic>['hello world']));
|
||||
});
|
||||
|
||||
channel1.invokeMethod<String>('sayHello', 'hello');
|
||||
|
||||
extension
|
||||
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
|
||||
.then<void>(expectAsync1((Map<String, dynamic> r) {
|
||||
result = r;
|
||||
}));
|
||||
|
||||
// The first channel message is not received, so nothing happens yet.
|
||||
await tester.pump(const Duration(milliseconds: 5));
|
||||
expect(result, isNull);
|
||||
|
||||
channel2.invokeMethod<String>('sayHello', 'hello');
|
||||
|
||||
// Result of channel 2 is received, but channel 1 is still pending, so still waiting.
|
||||
await tester.pump(const Duration(milliseconds: 10));
|
||||
expect(result, isNull);
|
||||
|
||||
// Now we receive the result.
|
||||
await tester.pump(const Duration(milliseconds: 5));
|
||||
expect(
|
||||
result,
|
||||
<String, dynamic>{
|
||||
'isError': false,
|
||||
'response': null,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('getSemanticsId', () {
|
||||
|
@ -80,6 +80,67 @@ enum TestBindingEventSource {
|
||||
|
||||
const Size _kDefaultTestViewportSize = Size(800.0, 600.0);
|
||||
|
||||
/// A [BinaryMessenger] subclass that is used as the default binary messenger
|
||||
/// under testing environment.
|
||||
///
|
||||
/// It tracks status of data sent across the Flutter platform barrier, which is
|
||||
/// useful for testing frameworks to monitor and synchronize against the
|
||||
/// platform messages.
|
||||
class TestDefaultBinaryMessenger extends BinaryMessenger {
|
||||
/// Creates a [TestDefaultBinaryMessenger] instance.
|
||||
///
|
||||
/// The [delegate] instance must not be null.
|
||||
TestDefaultBinaryMessenger(this.delegate): assert(delegate != null);
|
||||
|
||||
/// The delegate [BinaryMessenger].
|
||||
final BinaryMessenger delegate;
|
||||
|
||||
final List<Future<ByteData>> _pendingMessages = <Future<ByteData>>[];
|
||||
|
||||
/// The number of incomplete/pending calls sent to the platform channels.
|
||||
int get pendingMessageCount => _pendingMessages.length;
|
||||
|
||||
@override
|
||||
Future<ByteData> send(String channel, ByteData message) {
|
||||
final Future<ByteData> resultFuture = delegate.send(channel, message);
|
||||
// Removes the future itself from the [_pendingMessages] list when it
|
||||
// completes.
|
||||
if (resultFuture != null) {
|
||||
_pendingMessages.add(resultFuture);
|
||||
resultFuture.whenComplete(() => _pendingMessages.remove(resultFuture));
|
||||
}
|
||||
return resultFuture;
|
||||
}
|
||||
|
||||
/// Returns a Future that completes after all the platform calls are finished.
|
||||
///
|
||||
/// If a new platform message is sent after this method is called, this new
|
||||
/// message is not tracked. Use with [pendingMessageCount] to guarantee no
|
||||
/// pending message calls.
|
||||
Future<void> get platformMessagesFinished {
|
||||
return Future.wait<void>(_pendingMessages);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handlePlatformMessage(
|
||||
String channel,
|
||||
ByteData data,
|
||||
ui.PlatformMessageResponseCallback callback,
|
||||
) {
|
||||
return delegate.handlePlatformMessage(channel, data, callback);
|
||||
}
|
||||
|
||||
@override
|
||||
void setMessageHandler(String channel, MessageHandler handler) {
|
||||
delegate.setMessageHandler(channel, handler);
|
||||
}
|
||||
|
||||
@override
|
||||
void setMockMessageHandler(String channel, MessageHandler handler) {
|
||||
delegate.setMockMessageHandler(channel, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// Base class for bindings used by widgets library tests.
|
||||
///
|
||||
/// The [ensureInitialized] method creates (if necessary) and returns
|
||||
@ -220,6 +281,11 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
|
||||
// doesn't get generated for tests.
|
||||
}
|
||||
|
||||
@override
|
||||
BinaryMessenger createBinaryMessenger() {
|
||||
return TestDefaultBinaryMessenger(super.createBinaryMessenger());
|
||||
}
|
||||
|
||||
/// Whether there is currently a test executing.
|
||||
bool get inTest;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user