| // Copyright (c) 2016, 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 'dart:collection'; |
| import 'dart:io'; |
| |
| import 'package:test/test.dart'; |
| |
| import 'package:bazel_worker/bazel_worker.dart'; |
| import 'package:bazel_worker/driver.dart'; |
| |
| void main() { |
| BazelWorkerDriver driver; |
| |
| group('basic driver', () { |
| test('can run a single request', () async { |
| await _doRequests(count: 1); |
| await _doRequests(count: 1); |
| }); |
| |
| test('can run multiple batches of requests through multiple workers', |
| () async { |
| int maxWorkers = 4; |
| int maxIdleWorkers = 2; |
| driver = new BazelWorkerDriver(MockWorker.spawn, |
| maxWorkers: maxWorkers, maxIdleWorkers: maxIdleWorkers); |
| for (int i = 0; i < 10; i++) { |
| await _doRequests(driver: driver); |
| expect(MockWorker.liveWorkers.length, maxIdleWorkers); |
| // No workers should be killed while there is ongoing work, but they |
| // should be cleaned up once there isn't any more work to do. |
| expect(MockWorker.deadWorkers.length, |
| (maxWorkers - maxIdleWorkers) * (i + 1)); |
| } |
| }); |
| |
| test('can run multiple requests through one worker', () async { |
| int maxWorkers = 1; |
| int maxIdleWorkers = 1; |
| driver = new BazelWorkerDriver(MockWorker.spawn, |
| maxWorkers: maxWorkers, maxIdleWorkers: maxIdleWorkers); |
| for (int i = 0; i < 10; i++) { |
| await _doRequests(driver: driver); |
| expect(MockWorker.liveWorkers.length, 1); |
| expect(MockWorker.deadWorkers.length, 0); |
| } |
| }); |
| |
| test('can run one request through multiple workers', () async { |
| driver = new BazelWorkerDriver(MockWorker.spawn, |
| maxWorkers: 4, maxIdleWorkers: 4); |
| for (int i = 0; i < 10; i++) { |
| await _doRequests(driver: driver, count: 1); |
| expect(MockWorker.liveWorkers.length, 1); |
| expect(MockWorker.deadWorkers.length, 0); |
| } |
| }); |
| |
| test('can run with maxIdleWorkers == 0', () async { |
| int maxWorkers = 4; |
| driver = new BazelWorkerDriver(MockWorker.spawn, |
| maxWorkers: maxWorkers, maxIdleWorkers: 0); |
| for (int i = 0; i < 10; i++) { |
| await _doRequests(driver: driver); |
| expect(MockWorker.liveWorkers.length, 0); |
| expect(MockWorker.deadWorkers.length, maxWorkers * (i + 1)); |
| } |
| }); |
| |
| tearDown(() async { |
| await driver?.terminateWorkers(); |
| expect(MockWorker.liveWorkers, isEmpty); |
| MockWorker.deadWorkers.clear(); |
| }); |
| }); |
| } |
| |
| /// Runs [count] of fake work requests through [driver], and asserts that they |
| /// all completed. |
| Future _doRequests({BazelWorkerDriver driver, int count}) async { |
| // If we create a driver, we need to make sure and terminate it. |
| var terminateDriver = driver == null; |
| driver ??= new BazelWorkerDriver(MockWorker.spawn); |
| count ??= 100; |
| terminateDriver ??= true; |
| var requests = new List.generate(count, (_) => new WorkRequest()); |
| var responses = new List.generate(count, (_) => new WorkResponse()); |
| MockWorker.responseQueue.addAll(responses); |
| var actualResponses = await Future.wait(requests.map(driver.doWork)); |
| expect(actualResponses, unorderedEquals(responses)); |
| if (terminateDriver) await driver.terminateWorkers(); |
| } |
| |
| /// A mock worker loop that returns work responses from the provided list. |
| /// |
| /// Throws if it runs out of responses. |
| class MockWorkerLoop extends AsyncWorkerLoop { |
| final Queue<WorkResponse> _responseQueue; |
| |
| MockWorkerLoop(this._responseQueue, {AsyncWorkerConnection connection}) |
| : super(connection: connection); |
| |
| Future<WorkResponse> performRequest(WorkRequest request) async { |
| print('Performing request $request'); |
| return _responseQueue.removeFirst(); |
| } |
| } |
| |
| /// A mock worker process. |
| /// |
| /// Items in [responseQueue] will be returned in order based on requests. |
| /// |
| /// If there are no items left in [responseQueue] then it will throw. |
| class MockWorker implements Process { |
| /// Spawns a new [MockWorker]. |
| static Future<MockWorker> spawn() async => new MockWorker(); |
| |
| /// Worker loop that handles reading requests and responding. |
| AsyncWorkerLoop _workerLoop; |
| |
| /// Static queue of pending responses, these are shared by all workers. |
| /// |
| /// If this is empty and a request is received then it will throw. |
| static final responseQueue = new Queue<WorkResponse>(); |
| |
| /// Static list of all live workers. |
| static final liveWorkers = <MockWorker>[]; |
| |
| /// Static list of all the dead workers. |
| static final deadWorkers = <MockWorker>[]; |
| |
| /// Standard constructor, creates the [_workerLoop]. |
| MockWorker() { |
| liveWorkers.add(this); |
| _workerLoop = new MockWorkerLoop(responseQueue, |
| connection: new StdAsyncWorkerConnection( |
| inputStream: this._stdinController.stream, |
| outputStream: this._stdoutController.sink)) |
| ..run(); |
| } |
| |
| Future<int> get exitCode => _exitCodeCompleter.future; |
| final _exitCodeCompleter = new Completer<int>(); |
| |
| @override |
| Stream<List<int>> get stdout => _stdoutController.stream; |
| final _stdoutController = new StreamController<List<int>>(); |
| |
| @override |
| Stream<List<int>> get stderr => _stderrController.stream; |
| final _stderrController = new StreamController<List<int>>(); |
| |
| @override |
| IOSink get stdin { |
| _stdin ??= new IOSink(_stdinController.sink); |
| return _stdin; |
| } |
| |
| IOSink _stdin; |
| final _stdinController = new StreamController<List<int>>(); |
| |
| int get pid => throw new UnsupportedError('Not needed.'); |
| |
| @override |
| bool kill([ProcessSignal = ProcessSignal.SIGTERM, int exitCode = 0]) { |
| if (_killed) return false; |
| () async { |
| await _stdoutController.close(); |
| await _stderrController.close(); |
| await _stdinController.close(); |
| _exitCodeCompleter.complete(exitCode); |
| }(); |
| deadWorkers.add(this); |
| liveWorkers.remove(this); |
| return true; |
| } |
| |
| bool _killed = false; |
| } |