anchor.diff:  typedef NewCache<T, S> = Cache<T, S> Function(Loader<T, S> loader);
   
   // Various common loaders used with the tests.
  -String failingLoader(int key) => fail('Loader should never be called');
  -
   String immediateLoader(int key) => '$key';
   
  +String immediateFailingLoader(int key) =>
  +    throw StateError('Loader for $key is failing.');
  +
   Future<String> futureLoader(int key) => Future.value(immediateLoader(key));
   
  -Future<String> delayedLoader(int key) =>
  +Future<String> futureFailingLoader(int key) =>
  +    Future.error(StateError('Loader for $key is failing.'));
  +
  +Future<String> futureDelayedLoader(int key) =>
       Future.delayed(delay, () => immediateLoader(key));
  -
  -String throwingLoader(int key) => throw UnsupportedError('$key');
   
   // Basic tests that should pass on any (stateless) cache.
   void statelessCacheTests(NewCache<int, String> newCache) {
  -  test('empty', () {
  -    final cache = newCache(failingLoader);
  -    return cache
  -        .size()
  -        .then((value) => expect(value, 0, reason: 'cache should be empty'));
  -  });
  -  test('no present value', () {
  -    final cache = newCache(failingLoader);
  -    return cache.getIfPresent(1).then((value) =>
  -        expect(value, isNull, reason: 'there should be no present value'));
  -  });
  -  test('load immediate value', () {
  +  test('empty', () async {
  +    final cache = newCache(immediateFailingLoader);
  +    await expectLater(cache.size(), completion(0));
  +  });
  +  test('no present value', () async {
  +    final cache = newCache(immediateFailingLoader);
  +    await expectLater(cache.getIfPresent(1), completion(isNull));
  +  });
  +  test('load immediate value', () async {
       final cache = newCache(immediateLoader);
  -    return cache.get(1).then(
  -        (value) => expect(value, '1', reason: 'get value should be loaded'));
  -  });
  -  test('load future value', () {
  +    await expectLater(cache.get(1), completion('1'));
  +  });
  +  test('load immediate failing value', () async {
  +    final cache = newCache(immediateFailingLoader);
  +    await expectLater(cache.get(1), throwsA(isStateError));
  +  });
  +  test('load future value', () async {
       final cache = newCache(futureLoader);
  -    return cache.get(1).then(
  -        (value) => expect(value, '1', reason: 'get value should be loaded'));
  -  });
  -  test('load throwing value', () {
  -    final cache = newCache(throwingLoader);
  -    return cache.get(1).then((value) => fail('expected error'),
  -        onError: (exception) => expect(exception, isUnsupportedError));
  -  });
  -  test('load delayed value', () {
  -    final cache = newCache(delayedLoader);
  -    return cache.get(1).then(
  -        (value) => expect(value, '1', reason: 'get value should be loaded'));
  -  });
  -  test('set returns value', () {
  -    final cache = newCache(failingLoader);
  -    return cache.set(1, 'foo').then((value) =>
  -        expect(value, 'foo', reason: 'set value should be returned'));
  -  });
  -  test('invalidate empty', () {
  -    final cache = newCache(failingLoader);
  -    return cache
  -        .invalidate(1)
  -        .then((_) => cache.size())
  -        .then((value) => expect(value, 0, reason: 'cache should be empty'));
  -  });
  -  test('invalidate all empty', () {
  -    final cache = newCache(failingLoader);
  -    return cache
  -        .invalidateAll()
  -        .then((_) => cache.size())
  -        .then((value) => expect(value, 0, reason: 'cache should be empty'));
  -  });
  -  test('reap is a no-op', () {
  -    final cache = newCache(failingLoader);
  -    return cache
  -        .reap()
  -        .then((size) => expect(size, 0, reason: 'nothing should be gone'));
  +    await expectLater(cache.get(1), completion('1'));
  +  });
  +  test('load future failing value', () async {
  +    final cache = newCache(futureFailingLoader);
  +    await expectLater(cache.get(1), throwsA(isStateError));
  +  });
  +  test('load future delayed value', () async {
  +    final cache = newCache(futureDelayedLoader);
  +    await expectLater(cache.get(1), completion('1'));
  +  });
  +  test('invalidate empty', () async {
  +    final cache = newCache(immediateFailingLoader);
  +    await cache.invalidate(1);
  +    await expectLater(cache.size(), completion(0));
  +  });
  +  test('invalidate all empty', () async {
  +    final cache = newCache(immediateFailingLoader);
  +    await cache.invalidateAll();
  +    await expectLater(cache.size(), completion(0));
  +  });
  +  test('reap is a no-op', () async {
  +    final cache = newCache(immediateFailingLoader);
  +    await expectLater(cache.reap(), completion(0));
     });
   }
   
  
positive.code:     });
  
      group('update expire cache', () {
        test('expire a set value after it has been updated', () {
          final cache = newUpdateExpireCache(immediateLoader);
          return cache
              .set(1, '2')
              .then((_) => offset = const Duration(seconds: 20))
              .then((_) => cache.getIfPresent(1))
              .then((value) => expect(value, '2', reason: 'value is set'))
              .then((_) => cache.set(1, '3'))
              .then((_) => offset = const Duration(seconds: 40))
              .then((_) => cache.getIfPresent(1))
              .then((value) => expect(value, '3', reason: 'value is updated'))
              .then((_) => offset = const Duration(seconds: 41))
              .then((_) => cache.getIfPresent(1))
              .then(
                  (value) => expect(value, isNull, reason: 'value has expired'));
        });
        test('loaded value expires', () {
          final cache = newUpdateExpireCache(immediateLoader);
          return cache
              .get(1)
              .then((value) => expect(value, '1', reason: 'value is loaded'))
              .then((_) => offset = const Duration(seconds: 20))
              .then((_) => cache.getIfPresent(1))
              .then(
                  (value) => expect(value, '1', reason: 'value is still present'))
              .then((_) => offset = const Duration(seconds: 21))
              .then((_) => cache.getIfPresent(1))
              .then((value) => expect(value, isNull, reason: 'value has expired'))
              .then((_) => cache.size())
              .then((size) => expect(size, 0, reason: 'cache should be empty'));
        });
        test('re-loaded value expires', () {
          var counter = 0;
          final cache = newUpdateExpireCache((key) {
            counter++;
            return immediateLoader(key);
          });
          return cache
              .get(0)
              .then((value) => expect(value, '0', reason: 'value is loaded'))
              .then((value) => expect(counter, 1, reason: 'loaded once'))
              .then((_) => offset = const Duration(seconds: 20))
              .then((_) => cache.get(0))
              .then(
                  (value) => expect(value, '0', reason: 'value is still present'))
              .then((value) => expect(counter, 1, reason: 'still loaded once'))
              .then((_) => offset = const Duration(seconds: 21))
              .then((_) => cache.get(0))
              .then((value) => expect(value, '0', reason: 'value is reloaded'))
              .then((value) => expect(counter, 2, reason: 'value loaded again'));
        });
        test('reap expired items', () {
          final cache = newUpdateExpireCache(immediateLoader);
          return cache
              .set(1, '1')
              .then((_) => offset = const Duration(seconds: 21))
              .then((_) => cache.size())
              .then(
                  (size) => expect(size, 1, reason: 'cache has not been reaped'))
              .then((_) => cache.reap())
              .then((size) => expect(size, 1, reason: 'one item should be gone'))
              .then((_) => cache.size())
              .then((size) => expect(size, 0, reason: 'cache should be empty'));
        });
      });
      group('access expire cache', () {
        test('expire a set value after it has been updated', () {
          final cache = newAccessExpireCache(immediateLoader);
          return cache
              .set(1, '2')
              .then((_) => offset = const Duration(seconds: 20))
              .then((_) => cache.getIfPresent(1))
              .then((value) => expect(value, '2', reason: 'value is set'))
              .then((_) => cache.set(1, '3'))
              .then((_) => offset = const Duration(seconds: 40))
              .then((_) => cache.getIfPresent(1))
              .then((value) => expect(value, '3', reason: 'value is updated'))
              .then((_) => offset = const Duration(seconds: 61))
              .then((_) => cache.getIfPresent(1))
              .then(
                  (value) => expect(value, isNull, reason: 'value has expired'));
        });
        test('loaded value expires', () {
          final cache = newAccessExpireCache(immediateLoader);
          return cache
              .get(1)
              .then((value) => expect(value, '1', reason: 'value is loaded'))
              .then((_) => offset = const Duration(seconds: 20))
              .then((_) => cache.getIfPresent(1))
              .then(
                  (value) => expect(value, '1', reason: 'value is still present'))
              .then((_) => offset = const Duration(seconds: 41))
              .then((_) => cache.getIfPresent(1))
              .then((value) => expect(value, isNull, reason: 'value has expired'))
              .then((_) => cache.size())
              .then((size) => expect(size, 0, reason: 'cache should be empty'));
        });
        test('re-loaded value expires', () {
          var counter = 0;
          final cache = newAccessExpireCache((key) {
            counter++;
            return immediateLoader(key);
          });
          return cache
              .get(0)
              .then((value) => expect(value, '0', reason: 'value is loaded'))
              .then((value) => expect(counter, 1, reason: 'loaded once'))
              .then((_) => offset = const Duration(seconds: 20))
              .then((_) => cache.get(0))
              .then(
                  (value) => expect(value, '0', reason: 'value is still present'))
              .then((value) => expect(counter, 1, reason: 'still loaded once'))
              .then((_) => offset = const Duration(seconds: 41))
              .then((_) => cache.get(0))
              .then((value) => expect(value, '0', reason: 'value is reloaded'))
              .then((value) => expect(counter, 2, reason: 'value loaded again'));
        });
        test('reap expired items', () {
          final cache = newAccessExpireCache(immediateLoader);
          return cache
              .set(1, '1')
              .then((_) => offset = const Duration(seconds: 120))
              .then((_) => cache.size())
              .then(
                  (size) => expect(size, 1, reason: 'cache has not been reaped'))
              .then((_) => cache.reap())
              .then((size) => expect(size, 1, reason: 'one item should be gone'))
              .then((_) => cache.size())
              .then((size) => expect(size, 0, reason: 'cache should be empty'));
        });
      });
    });
  
negative.code:         Cache.fifo(loader: loader, maximumSize: 5);
  
      group('constructors', () {
        test('non positive max size', () {
          expect(() => Cache.fifo(loader: immediateLoader, maximumSize: 0),
              throwsArgumentError);
  