Merge pull request #101 from DrMarcII/master

Add awaitChecking mode to Lock class.
diff --git a/lib/support/async.dart b/lib/support/async.dart
index 137df94..d8f2314 100644
--- a/lib/support/async.dart
+++ b/lib/support/async.dart
@@ -17,6 +17,7 @@
 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);
@@ -103,12 +104,30 @@
 
 class Lock {
   Completer _lock;
+  Chain _stack;
 
-  Future acquire() async {
-    while (isHeld) {
-      await _lock.future;
+  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();
+      }();
     }
-    _lock = new Completer();
   }
 
   void release() {
@@ -117,6 +136,7 @@
     }
     _lock.complete();
     _lock = null;
+    _stack = null;
   }
 
   bool get isHeld => _lock != null;
diff --git a/test/src/command_event.dart b/test/src/command_event.dart
index 0baa524..aea573f 100644
--- a/test/src/command_event.dart
+++ b/test/src/command_event.dart
@@ -22,54 +22,49 @@
 import '../test_util.dart';
 
 void runTests() {
-  group(
-      'CommandEvent',
-      () {
-        WebDriver driver;
+  group('CommandEvent', () {
+    WebDriver driver;
 
-        var events = [];
-        var sub;
+    var events = [];
+    var sub;
 
-        setUp(() async {
-          driver = await createTestDriver();
-          sub = driver.onCommand.listen(events.add);
+    setUp(() async {
+      driver = await createTestDriver();
+      sub = driver.onCommand.listen(events.add);
 
-          await driver.get(testPagePath);
-        });
+      await driver.get(testPagePath);
+    });
 
-        tearDown(() async {
-          sub.cancel();
-          sub = null;
-          events.clear();
-          await driver.quit();
-          driver = null;
-        });
+    tearDown(() async {
+      sub.cancel();
+      sub = null;
+      events.clear();
+      await driver.quit();
+      driver = null;
+    });
 
-        test('handles exceptions', () async {
-          try {
-            await driver.switchTo.alert;
-          } catch (e) {}
-          await waitFor(() => events, matcher: hasLength(2));
-          expect(events[1].method, 'GET');
-          expect(events[1].endPoint, contains('alert'));
-          expect(events[1].exception, new isInstanceOf<WebDriverException>());
-          expect(events[1].result, isNull);
-          expect(events[1].startTime.isBefore(events[1].endTime), isTrue);
-          expect(events[1].stackTrace, new isInstanceOf<Chain>());
-        });
+    test('handles exceptions', () async {
+      try {
+        await driver.switchTo.alert;
+      } catch (e) {}
+      await waitFor(() => events, matcher: hasLength(2));
+      expect(events[1].method, 'GET');
+      expect(events[1].endPoint, contains('alert'));
+      expect(events[1].exception, new isInstanceOf<WebDriverException>());
+      expect(events[1].result, isNull);
+      expect(events[1].startTime.isBefore(events[1].endTime), isTrue);
+      expect(events[1].stackTrace, new isInstanceOf<Chain>());
+    });
 
-        test('handles normal operation', () async {
-          await driver
-              .findElements(const By.cssSelector('nosuchelement'))
-              .toList();
-          await waitFor(() => events, matcher: hasLength(2));
-          expect(events[1].method, 'POST');
-          expect(events[1].endPoint, contains('elements'));
-          expect(events[1].exception, isNull);
-          expect(events[1].result, hasLength(0));
-          expect(events[1].startTime.isBefore(events[1].endTime), isTrue);
-          expect(events[1].stackTrace, new isInstanceOf<Chain>());
-        });
-      },
-      testOn: '!js');
+    test('handles normal operation', () async {
+      await driver.findElements(const By.cssSelector('nosuchelement')).toList();
+      await waitFor(() => events, matcher: hasLength(2));
+      expect(events[1].method, 'POST');
+      expect(events[1].endPoint, contains('elements'));
+      expect(events[1].exception, isNull);
+      expect(events[1].result, hasLength(0));
+      expect(events[1].startTime.isBefore(events[1].endTime), isTrue);
+      expect(events[1].stackTrace, new isInstanceOf<Chain>());
+    });
+  }, testOn: '!js');
 }
diff --git a/test/src/navigation.dart b/test/src/navigation.dart
index c86bad6..5739c68 100644
--- a/test/src/navigation.dart
+++ b/test/src/navigation.dart
@@ -31,16 +31,13 @@
 
     tearDown(() => driver.quit());
 
-    test(
-        'forward/back',
-        () async {
-          await driver.get('http://www.yahoo.com');
-          await driver.navigate.back();
-          await waitFor(() => driver.title, matcher: contains('Google'));
-          await driver.navigate.forward();
-          await waitFor(() => driver.title, matcher: contains('Yahoo'));
-        },
-        skip: 'TODO(DrMarcII): fix test');
+    test('forward/back', () async {
+      await driver.get('http://www.yahoo.com');
+      await driver.navigate.back();
+      await waitFor(() => driver.title, matcher: contains('Google'));
+      await driver.navigate.forward();
+      await waitFor(() => driver.title, matcher: contains('Yahoo'));
+    }, skip: 'TODO(DrMarcII): fix test');
 
     test('refresh', () async {
       var element = await driver.findElement(const By.name('q'));
diff --git a/test/src/web_driver.dart b/test/src/web_driver.dart
index 57870f8..1c2f0d5 100644
--- a/test/src/web_driver.dart
+++ b/test/src/web_driver.dart
@@ -38,17 +38,14 @@
         await driver.quit();
       });
 
-      test(
-          'firefox',
-          () async {
-            WebDriver driver = await createTestDriver(
-                additionalCapabilities: Capabilities.firefox);
-            await driver.get('http://www.google.com');
-            var element = await driver.findElement(const By.name('q'));
-            expect(await element.name, 'input');
-            await driver.quit();
-          },
-          skip: runningOnTravis);
+      test('firefox', () async {
+        WebDriver driver = await createTestDriver(
+            additionalCapabilities: Capabilities.firefox);
+        await driver.get('http://www.google.com');
+        var element = await driver.findElement(const By.name('q'));
+        expect(await element.name, 'input');
+        await driver.quit();
+      }, skip: runningOnTravis);
     });
 
     group('methods', () {
diff --git a/test/src/window.dart b/test/src/window.dart
index 2e547bc..0b60fe0 100644
--- a/test/src/window.dart
+++ b/test/src/window.dart
@@ -32,47 +32,38 @@
 
     tearDown(() => driver.quit());
 
-    test(
-        'size',
-        () async {
-          var window = await driver.window;
-          var size = const Rectangle<int>(0, 0, 600, 400);
-          await window.setSize(size);
-          expect(await window.size, size);
-        },
-        skip: true);
+    test('size', () async {
+      var window = await driver.window;
+      var size = const Rectangle<int>(0, 0, 600, 400);
+      await window.setSize(size);
+      expect(await window.size, size);
+    }, skip: true);
 
-    test(
-        'location',
-        () async {
-          var window = await driver.window;
-          var position = const Point<int>(100, 200);
-          await window.setLocation(position);
-          expect(await window.location, position);
-        },
-        skip: true);
+    test('location', () async {
+      var window = await driver.window;
+      var position = const Point<int>(100, 200);
+      await window.setLocation(position);
+      expect(await window.location, position);
+    }, skip: true);
 
     // May not work on some OS/browser combinations (notably Mac OS X).
-    test(
-        'maximize',
-        () async {
-          var window = await driver.window;
-          await window.setSize(const Rectangle<int>(0, 0, 300, 200));
-          await window.setLocation(const Point<int>(100, 200));
-          await window.maximize();
+    test('maximize', () async {
+      var window = await driver.window;
+      await window.setSize(const Rectangle<int>(0, 0, 300, 200));
+      await window.setLocation(const Point<int>(100, 200));
+      await window.maximize();
 
-          // maximizing can take some time
-          await waitFor(() async => (await window.size).height,
-              matcher: greaterThan(200));
+      // maximizing can take some time
+      await waitFor(() async => (await window.size).height,
+          matcher: greaterThan(200));
 
-          var location = await window.location;
-          var size = await window.size;
-          // Changed from `lessThan(100)` to pass the test on Mac.
-          expect(location.x, lessThanOrEqualTo(100));
-          expect(location.y, lessThan(200));
-          expect(size.height, greaterThan(200));
-          expect(size.width, greaterThan(300));
-        },
-        skip: true);
+      var location = await window.location;
+      var size = await window.size;
+      // Changed from `lessThan(100)` to pass the test on Mac.
+      expect(location.x, lessThanOrEqualTo(100));
+      expect(location.y, lessThan(200));
+      expect(size.height, greaterThan(200));
+      expect(size.width, greaterThan(300));
+    }, skip: true);
   });
 }
diff --git a/test/support/async_test.dart b/test/support/async_test.dart
index b38e990..d185fd6 100644
--- a/test/support/async_test.dart
+++ b/test/support/async_test.dart
@@ -52,6 +52,15 @@
       await new Future.delayed(const Duration(seconds: 1));
       expect(secondLockAcquired, isTrue);
     });
+
+    test('awaitChecking throws exception on acquire of held lock', () async {
+      var lock = new Lock(awaitChecking: true);
+      await lock.acquire();
+      expect(lock.acquire(), throws);
+      lock.release();
+      await lock.acquire();
+      lock.release();
+    });
   });
 
   group('Clock.waitFor', () {