| // Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file | 
 | // for details. All rights reserved. Use of this source code is governed by a | 
 | // BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | import 'dart:async'; | 
 |  | 
 | import 'package:pub/src/rate_limited_scheduler.dart'; | 
 | import 'package:test/test.dart'; | 
 |  | 
 | void main() { | 
 |   Map<String, Completer> threeCompleters() => | 
 |       {'a': Completer(), 'b': Completer(), 'c': Completer()}; | 
 |  | 
 |   test('scheduler is rate limited', () async { | 
 |     final completers = threeCompleters(); | 
 |     final isBeingProcessed = threeCompleters(); | 
 |  | 
 |     Future<String> f(String i) async { | 
 |       isBeingProcessed[i]!.complete(); | 
 |       await completers[i]!.future; | 
 |       return i.toUpperCase(); | 
 |     } | 
 |  | 
 |     final scheduler = RateLimitedScheduler(f, maxConcurrentOperations: 2); | 
 |     await scheduler.withPrescheduling((preschedule) async { | 
 |       preschedule('a'); | 
 |       preschedule('b'); | 
 |       preschedule('c'); | 
 |       await Future.wait( | 
 |           [isBeingProcessed['a']!.future, isBeingProcessed['b']!.future]); | 
 |       expect(isBeingProcessed['c']!.isCompleted, isFalse); | 
 |       completers['a']!.complete(); | 
 |       await isBeingProcessed['c']!.future; | 
 |       completers['c']!.complete(); | 
 |       expect(await scheduler.schedule('c'), 'C'); | 
 |     }); | 
 |   }); | 
 |  | 
 |   test('scheduler.preschedule cancels unrun prescheduled task after callback', | 
 |       () async { | 
 |     final completers = threeCompleters(); | 
 |     final isBeingProcessed = threeCompleters(); | 
 |  | 
 |     Future<String> f(String i) async { | 
 |       isBeingProcessed[i]!.complete(); | 
 |       await completers[i]!.future; | 
 |       return i.toUpperCase(); | 
 |     } | 
 |  | 
 |     final scheduler = RateLimitedScheduler(f, maxConcurrentOperations: 1); | 
 |  | 
 |     await scheduler.withPrescheduling((preschedule1) async { | 
 |       await scheduler.withPrescheduling((preschedule2) async { | 
 |         preschedule1('a'); | 
 |         preschedule2('b'); | 
 |         preschedule1('c'); | 
 |         await isBeingProcessed['a']!.future; | 
 |         // b, c should not start processing due to rate-limiting. | 
 |         expect(isBeingProcessed['b']!.isCompleted, isFalse); | 
 |         expect(isBeingProcessed['c']!.isCompleted, isFalse); | 
 |       }); | 
 |       completers['a']!.complete(); | 
 |       // b is removed from the queue, now c should start processing. | 
 |       await isBeingProcessed['c']!.future; | 
 |       completers['c']!.complete(); | 
 |       expect(await scheduler.schedule('c'), 'C'); | 
 |       // b is not on the queue anymore. | 
 |       expect(isBeingProcessed['b']!.isCompleted, isFalse); | 
 |     }); | 
 |   }); | 
 |  | 
 |   test('scheduler.preschedule does not cancel tasks that are scheduled', | 
 |       () async { | 
 |     final completers = threeCompleters(); | 
 |     final isBeingProcessed = threeCompleters(); | 
 |  | 
 |     Future<String> f(String i) async { | 
 |       isBeingProcessed[i]!.complete(); | 
 |       await completers[i]!.future; | 
 |       return i.toUpperCase(); | 
 |     } | 
 |  | 
 |     final scheduler = RateLimitedScheduler(f, maxConcurrentOperations: 1); | 
 |  | 
 |     Future? b; | 
 |     await scheduler.withPrescheduling((preschedule) async { | 
 |       preschedule('a'); | 
 |       preschedule('b'); | 
 |       await isBeingProcessed['a']!.future; | 
 |       // b should not start processing due to rate-limiting. | 
 |       expect(isBeingProcessed['b']!.isCompleted, isFalse); | 
 |       b = scheduler.schedule('b'); | 
 |     }); | 
 |     completers['a']!.complete(); | 
 |     expect(await scheduler.schedule('a'), 'A'); | 
 |     // b was scheduled, so it should get processed now | 
 |     await isBeingProcessed['b']!.future; | 
 |     completers['b']!.complete(); | 
 |     expect(await b, 'B'); | 
 |   }); | 
 |  | 
 |   test('scheduler caches results', () async { | 
 |     final completers = threeCompleters(); | 
 |     final isBeingProcessed = threeCompleters(); | 
 |  | 
 |     Future<String> f(String i) async { | 
 |       isBeingProcessed[i]!.complete(); | 
 |       await completers[i]!.future; | 
 |       return i.toUpperCase(); | 
 |     } | 
 |  | 
 |     final scheduler = RateLimitedScheduler(f, maxConcurrentOperations: 2); | 
 |  | 
 |     completers['a']!.complete(); | 
 |     expect(await scheduler.schedule('a'), 'A'); | 
 |     // Would fail if isBeingProcessed['a'] was completed twice | 
 |     expect(await scheduler.schedule('a'), 'A'); | 
 |   }); | 
 |  | 
 |   test('scheduler prioritizes fetched tasks before prefetched', () async { | 
 |     final completers = threeCompleters(); | 
 |     final isBeingProcessed = threeCompleters(); | 
 |  | 
 |     Future<String> f(String i) async { | 
 |       isBeingProcessed[i]!.complete(); | 
 |       await completers[i]!.future; | 
 |       return i.toUpperCase(); | 
 |     } | 
 |  | 
 |     final scheduler = RateLimitedScheduler(f, maxConcurrentOperations: 1); | 
 |     await scheduler.withPrescheduling((preschedule) async { | 
 |       preschedule('a'); | 
 |       preschedule('b'); | 
 |       await isBeingProcessed['a']!.future; | 
 |       final cResult = scheduler.schedule('c'); | 
 |       expect(isBeingProcessed['b']!.isCompleted, isFalse); | 
 |       completers['a']!.complete(); | 
 |       completers['c']!.complete(); | 
 |       await isBeingProcessed['c']!.future; | 
 |       // 'c' is done before we allow 'b' to finish processing | 
 |       expect(await cResult, 'C'); | 
 |     }); | 
 |   }); | 
 |  | 
 |   test('Errors trigger when the scheduled future is listened to', () async { | 
 |     final completers = threeCompleters(); | 
 |     final isBeingProcessed = threeCompleters(); | 
 |  | 
 |     Future<String> f(String i) async { | 
 |       isBeingProcessed[i]!.complete(); | 
 |       await completers[i]!.future; | 
 |       return i.toUpperCase(); | 
 |     } | 
 |  | 
 |     final scheduler = RateLimitedScheduler(f, maxConcurrentOperations: 2); | 
 |  | 
 |     await scheduler.withPrescheduling((preschedule) async { | 
 |       preschedule('a'); | 
 |       preschedule('b'); | 
 |       preschedule('c'); | 
 |  | 
 |       await isBeingProcessed['a']!.future; | 
 |       await isBeingProcessed['b']!.future; | 
 |       expect(isBeingProcessed['c']!.isCompleted, isFalse); | 
 |       unawaited(completers['c']!.future.catchError((_) {})); | 
 |       completers['c']!.completeError('errorC'); | 
 |       completers['a']!.completeError('errorA'); | 
 |       await isBeingProcessed['c']!.future; | 
 |       completers['b']!.completeError('errorB'); | 
 |       expect(() async => await scheduler.schedule('a'), throwsA('errorA')); | 
 |       expect(() async => await scheduler.schedule('b'), throwsA('errorB')); | 
 |       expect(() async => await scheduler.schedule('c'), throwsA('errorC')); | 
 |     }); | 
 |   }); | 
 |  | 
 |   test('tasks run in the zone they where enqueued in', () async { | 
 |     final completers = threeCompleters(); | 
 |     final isBeingProcessed = threeCompleters(); | 
 |  | 
 |     Future<String?> f(String i) async { | 
 |       isBeingProcessed[i]!.complete(); | 
 |       await completers[i]!.future; | 
 |       return Zone.current['zoneValue']; | 
 |     } | 
 |  | 
 |     final scheduler = RateLimitedScheduler(f, maxConcurrentOperations: 2); | 
 |     await scheduler.withPrescheduling((preschedule) async { | 
 |       runZoned(() { | 
 |         preschedule('a'); | 
 |       }, zoneValues: {'zoneValue': 'A'}); | 
 |       runZoned(() { | 
 |         preschedule('b'); | 
 |       }, zoneValues: {'zoneValue': 'B'}); | 
 |       runZoned(() { | 
 |         preschedule('c'); | 
 |       }, zoneValues: {'zoneValue': 'C'}); | 
 |  | 
 |       await runZoned(() async { | 
 |         await isBeingProcessed['a']!.future; | 
 |         await isBeingProcessed['b']!.future; | 
 |         // This will put 'c' in front of the queue, but in a zone with zoneValue | 
 |         // bound to S. | 
 |         final f = expectLater(scheduler.schedule('c'), completion('S')); | 
 |         completers['a']!.complete(); | 
 |         completers['b']!.complete(); | 
 |         expect(await scheduler.schedule('a'), 'A'); | 
 |         expect(await scheduler.schedule('b'), 'B'); | 
 |         completers['c']!.complete(); | 
 |         await f; | 
 |       }, zoneValues: {'zoneValue': 'S'}); | 
 |     }); | 
 |   }); | 
 | } |