blob: 875e750f3136047cd89ede7cbba2894609bc0b48 [file] [log] [blame]
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
library webdriver.support.async;
import 'dart:async' show Completer, Future;
import 'package:matcher/matcher.dart' as m;
import 'package:stack_trace/stack_trace.dart' show Chain;
import 'package:unittest/unittest.dart' as ut;
const defaultInterval = const Duration(milliseconds: 500);
const defaultTimeout = const Duration(seconds: 5);
const clock = const Clock();
Future<T> waitFor<T>(T condition(),
{matcher: null,
Duration timeout: defaultTimeout,
Duration interval: defaultInterval}) =>
clock.waitFor<T>(condition,
matcher: matcher, timeout: timeout, interval: interval);
class Clock {
const Clock();
/// Sleep for the specified time.
Future sleep([Duration interval = defaultInterval]) =>
new Future.delayed(interval);
/// The current time.
DateTime get now => new DateTime.now();
/// Waits until [condition] evaluates to a value that matches [matcher] or
/// until [timeout] time has passed. If [condition] returns a [Future], then
/// uses the value of that [Future] rather than the value of [condition].
///
/// If the wait is successful, then the matching return value of [condition]
/// is returned. Otherwise, if [condition] throws, then that exception is
/// rethrown. If [condition] doesn't throw then an [expect] exception is
/// thrown.
Future<T> waitFor<T>(T condition(),
{matcher: null,
Duration timeout: defaultTimeout,
Duration interval: defaultInterval}) async {
if (matcher != null && matcher is! ut.Matcher && matcher is! m.Matcher) {
matcher = m.equals(matcher);
}
var endTime = now.add(timeout);
while (true) {
try {
var value = await condition();
if (matcher is m.Matcher) {
_matcherExpect(value, matcher);
} else if (matcher is ut.Matcher) {
_unittestExpect(value, matcher);
}
return value;
} catch (e) {
if (now.isAfter(endTime)) {
rethrow;
} else {
await sleep(interval);
}
}
}
}
}
_unittestExpect(value, ut.Matcher matcher) => ut.expect(value, matcher);
_matcherExpect(value, m.Matcher matcher) {
var matchState = {};
if (matcher.matches(value, matchState)) {
return;
}
var desc = new m.StringDescription()
..add('Expected: ')
..addDescriptionOf(matcher)
..add('\n')
..add(' Actual: ')
..addDescriptionOf(value)
..add('\n');
var mismatchDescription = new m.StringDescription();
matcher.describeMismatch(value, mismatchDescription, matchState, true);
if (mismatchDescription.length > 0) {
desc.add(' Which: ${mismatchDescription}\n');
}
throw new Exception(desc.toString());
}
class Lock {
Completer _lock;
Chain _stack;
final bool awaitChecking;
Lock({this.awaitChecking: false});
Future acquire() {
if (awaitChecking) {
if (isHeld) {
return new Future.error(new StateError(
'Maybe you missed an await? Lock is already held by:\n$_stack'));
} else {
_stack = new Chain.current().terse;
_lock = new Completer();
return new Future.value();
}
} else {
return () async {
while (isHeld) {
await _lock.future;
}
_lock = new Completer();
}();
}
}
void release() {
if (!isHeld) {
throw new StateError('No lock to release');
}
_lock.complete();
_lock = null;
_stack = null;
}
bool get isHeld => _lock != null;
}