Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(990)

Unified Diff: mojo/public/dart/third_party/test/lib/src/runner/engine.dart

Issue 1346773002: Stop running pub get at gclient sync time and fix build bugs (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: mojo/public/dart/third_party/test/lib/src/runner/engine.dart
diff --git a/mojo/public/dart/third_party/test/lib/src/runner/engine.dart b/mojo/public/dart/third_party/test/lib/src/runner/engine.dart
new file mode 100644
index 0000000000000000000000000000000000000000..c93e793a2de1ce102f06573e1a309348e9caee91
--- /dev/null
+++ b/mojo/public/dart/third_party/test/lib/src/runner/engine.dart
@@ -0,0 +1,334 @@
+// Copyright (c) 2015, 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.
+
+library test.runner.engine;
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'package:async/async.dart' hide Result;
+import 'package:collection/collection.dart';
+import 'package:pool/pool.dart';
+
+import '../backend/live_test.dart';
+import '../backend/live_test_controller.dart';
+import '../backend/state.dart';
+import '../backend/test.dart';
+import 'load_suite.dart';
+import 'runner_suite.dart';
+
+/// An [Engine] manages a run that encompasses multiple test suites.
+///
+/// Test suites are provided by passing them into [suiteSink]. Once all suites
+/// have been provided, the user should close [suiteSink] to indicate this.
+/// [run] won't terminate until [suiteSink] is closed. Suites will be run in the
+/// order they're provided to [suiteSink]. Tests within those suites will
+/// likewise be run in the order of [Suite.tests].
+///
+/// The current status of every test is visible via [liveTests]. [onTestStarted]
+/// can also be used to be notified when a test is about to be run.
+///
+/// The engine has some special logic for [LoadSuite]s and the tests they
+/// contain, referred to as "load tests". Load tests exist to provide visibility
+/// into the process of loading test files, but as long as that process is
+/// proceeding normally users usually don't care about it, so the engine only
+/// surfaces running load tests (that is, includes them in [liveTests] and other
+/// collections) under specific circumstances.
+///
+/// If only load tests are running, exactly one load test will be in [active]
+/// and [liveTests]. If this test passes, it will be removed from both [active]
+/// and [liveTests] and *will not* be added to [passed]. If at any point a load
+/// test fails, it will be added to [failed] and [liveTests].
+///
+/// The test suite loaded by a load suite will be automatically be run by the
+/// engine; it doesn't need to be added to [suiteSink] manually.
+///
+/// Load tests will always be emitted through [onTestStarted] so users can watch
+/// their event streams once they start running.
+class Engine {
+ /// Whether [run] has been called yet.
+ var _runCalled = false;
+
+ /// Whether [close] has been called.
+ var _closed = false;
+
+ /// Whether [close] was called before all the tests finished running.
+ ///
+ /// This is `null` if close hasn't been called and the tests are still
+ /// running, `true` if close was called before the tests finished running, and
+ /// `false` if the tests finished running before close was called.
+ var _closedBeforeDone;
+
+ /// A pool that limits the number of test suites running concurrently.
+ final Pool _runPool;
+
+ /// A pool that limits the number of test suites loaded concurrently.
+ ///
+ /// Once this reaches its limit, loading any additional test suites will cause
+ /// previous suites to be unloaded in the order they completed.
+ final Pool _loadPool;
+
+ /// Whether all tests passed.
+ ///
+ /// This fires once all tests have completed and [suiteSink] has been closed.
+ /// This will be `null` if [close] was called before all the tests finished
+ /// running.
+ Future<bool> get success async {
+ await _group.future;
+ if (_closedBeforeDone) return null;
+ return liveTests.every((liveTest) =>
+ liveTest.state.result == Result.success);
+ }
+
+ /// A group of futures for each test suite.
+ final _group = new FutureGroup();
+
+ /// A sink used to pass [RunnerSuite]s in to the engine to run.
+ ///
+ /// Suites may be added as quickly as they're available; the Engine will only
+ /// run as many as necessary at a time based on its concurrency settings.
+ ///
+ /// Suites added to the sink will be closed by the engine based on its
+ /// internal logic.
+ Sink<RunnerSuite> get suiteSink => new DelegatingSink(_suiteController.sink);
+ final _suiteController = new StreamController<RunnerSuite>();
+
+ /// All the currently-known tests that have run, are running, or will run.
+ ///
+ /// These are [LiveTest]s, representing the in-progress state of each test.
+ /// Tests that have not yet begun running are marked [Status.pending]; tests
+ /// that have finished are marked [Status.complete].
+ ///
+ /// This is guaranteed to contain the same tests as the union of [passed],
+ /// [skipped], [failed], and [active].
+ ///
+ /// [LiveTest.run] must not be called on these tests.
+ List<LiveTest> get liveTests => new UnmodifiableListView(_liveTests);
+ final _liveTests = new List<LiveTest>();
+
+ /// A stream that emits each [LiveTest] as it's about to start running.
+ ///
+ /// This is guaranteed to fire before [LiveTest.onStateChange] first fires.
+ Stream<LiveTest> get onTestStarted => _onTestStartedController.stream;
+ final _onTestStartedController = new StreamController<LiveTest>.broadcast();
+
+ /// The set of tests that have completed and been marked as passing.
+ Set<LiveTest> get passed => new UnmodifiableSetView(_passed);
+ final _passed = new Set<LiveTest>();
+
+ /// The set of tests that have completed and been marked as skipped.
+ Set<LiveTest> get skipped => new UnmodifiableSetView(_skipped);
+ final _skipped = new Set<LiveTest>();
+
+ /// The set of tests that have completed and been marked as failing or error.
+ Set<LiveTest> get failed => new UnmodifiableSetView(_failed);
+ final _failed = new Set<LiveTest>();
+
+ /// The tests that are still running, in the order they begain running.
+ List<LiveTest> get active => new UnmodifiableListView(_active);
+ final _active = new QueueList<LiveTest>();
+
+ /// The tests from [LoadSuite]s that are still running, in the order they
+ /// began running.
+ ///
+ /// This is separate from [active] because load tests aren't always surfaced.
+ final _activeLoadTests = new List<LiveTest>();
+
+ /// Whether this engine is idle—that is, not currently executing a test.
+ bool get isIdle => _group.isIdle;
+
+ /// A broadcast stream that fires an event whenever [isIdle] switches from
+ /// `false` to `true`.
+ Stream get onIdle => _group.onIdle;
+
+ /// Creates an [Engine] that will run all tests provided via [suiteSink].
+ ///
+ /// [concurrency] controls how many suites are run at once, and defaults to 1.
+ /// [maxSuites] controls how many suites are *loaded* at once, and defaults to
+ /// four times [concurrency].
+ Engine({int concurrency, int maxSuites})
+ : _runPool = new Pool(concurrency == null ? 1 : concurrency),
+ _loadPool = new Pool(maxSuites == null
+ ? (concurrency == null ? 2 : concurrency * 2)
+ : maxSuites) {
+ _group.future.then((_) {
+ if (_closedBeforeDone == null) _closedBeforeDone = false;
+ }).catchError((_) {
+ // Don't top-level errors. They'll be thrown via [success] anyway.
+ });
+ }
+
+ /// Creates an [Engine] that will run all tests in [suites].
+ ///
+ /// [concurrency] controls how many suites are run at once. An engine
+ /// constructed this way will automatically close its [suiteSink], meaning
+ /// that no further suites may be provided.
+ factory Engine.withSuites(List<RunnerSuite> suites, {int concurrency}) {
+ var engine = new Engine(concurrency: concurrency);
+ for (var suite in suites) engine.suiteSink.add(suite);
+ engine.suiteSink.close();
+ return engine;
+ }
+
+ /// Runs all tests in all suites defined by this engine.
+ ///
+ /// This returns `true` if all tests succeed, and `false` otherwise. It will
+ /// only return once all tests have finished running and [suiteSink] has been
+ /// closed.
+ Future<bool> run() {
+ if (_runCalled) {
+ throw new StateError("Engine.run() may not be called more than once.");
+ }
+ _runCalled = true;
+
+ _suiteController.stream.listen((suite) {
+ _group.add(new Future.sync(() async {
+ var loadResource = await _loadPool.request();
+
+ if (suite is LoadSuite) {
+ suite = await _addLoadSuite(suite);
+ if (suite == null) {
+ loadResource.release();
+ return;
+ }
+ }
+
+ await _runPool.withResource(() async {
+ if (_closed) return null;
+
+ // TODO(nweiz): Use a real for loop when issue 23394 is fixed.
+ await Future.forEach(suite.tests, (test) async {
+ if (_closed) return;
+
+ var liveTest = test.metadata.skip
+ ? _skippedTest(suite, test)
+ : test.load(suite);
+ _liveTests.add(liveTest);
+ _active.add(liveTest);
+
+ // If there were no active non-load tests, the current active test
+ // would have been a load test. In that case, remove it, since now we
+ // have a non-load test to add.
+ if (_active.isNotEmpty && _active.first.suite is LoadSuite) {
+ _liveTests.remove(_active.removeFirst());
+ }
+
+ liveTest.onStateChange.listen((state) {
+ if (state.status != Status.complete) return;
+ _active.remove(liveTest);
+
+ // If we're out of non-load tests, surface a load test.
+ if (_active.isEmpty && _activeLoadTests.isNotEmpty) {
+ _active.add(_activeLoadTests.first);
+ _liveTests.add(_activeLoadTests.first);
+ }
+
+ if (state.result != Result.success) {
+ _passed.remove(liveTest);
+ _failed.add(liveTest);
+ } else if (liveTest.test.metadata.skip) {
+ _skipped.add(liveTest);
+ } else {
+ _passed.add(liveTest);
+ }
+ });
+
+ _onTestStartedController.add(liveTest);
+
+ // First, schedule a microtask to ensure that [onTestStarted] fires
+ // before the first [LiveTest.onStateChange] event. Once the test
+ // finishes, use [new Future] to do a coarse-grained event loop pump
+ // to avoid starving non-microtask events.
+ await new Future.microtask(liveTest.run);
+ await new Future(() {});
+ });
+
+ loadResource.allowRelease(() => suite.close());
+ });
+ }));
+ }, onDone: _group.close);
+
+ return success;
+ }
+
+ /// Returns a dummy [LiveTest] for a test marked as "skip".
+ LiveTest _skippedTest(RunnerSuite suite, Test test) {
+ var controller;
+ controller = new LiveTestController(suite, test, () {
+ controller.setState(const State(Status.running, Result.success));
+ controller.setState(const State(Status.complete, Result.success));
+ controller.completer.complete();
+ }, () {});
+ return controller.liveTest;
+ }
+
+ /// Adds listeners for [suite].
+ ///
+ /// Load suites have specific logic apart from normal test suites.
+ Future<RunnerSuite> _addLoadSuite(LoadSuite suite) async {
+ var liveTest = await suite.tests.single.load(suite);
+
+ _activeLoadTests.add(liveTest);
+
+ // Only surface the load test if there are no other tests currently running.
+ if (_active.isEmpty) {
+ _liveTests.add(liveTest);
+ _active.add(liveTest);
+ }
+
+ liveTest.onStateChange.listen((state) {
+ if (state.status != Status.complete) return;
+ _activeLoadTests.remove(liveTest);
+
+ // Only one load test will be active at any given time, and it will always
+ // be the only active test. Remove it and, if possible, surface another
+ // load test.
+ if (_active.isNotEmpty && _active.first.suite == suite) {
+ _active.remove(liveTest);
+ _liveTests.remove(liveTest);
+
+ if (_activeLoadTests.isNotEmpty) {
+ _active.add(_activeLoadTests.last);
+ _liveTests.add(_activeLoadTests.last);
+ }
+ }
+
+ // Surface the load test if it fails so that the user can see the failure.
+ if (state.result == Result.success) return;
+ _failed.add(liveTest);
+ _liveTests.add(liveTest);
+ });
+
+ // Run the test immediately. We don't want loading to be blocked on suites
+ // that are already running.
+ _onTestStartedController.add(liveTest);
+ await liveTest.run();
+
+ return suite.suite;
+ }
+
+ /// Signals that the caller is done paying attention to test results and the
+ /// engine should release any resources it has allocated.
+ ///
+ /// Any actively-running tests are also closed. VM tests are allowed to finish
+ /// running so that any modifications they've made to the filesystem can be
+ /// cleaned up.
+ ///
+ /// **Note that closing the engine is not the same as closing [suiteSink].**
+ /// Closing [suiteSink] indicates that no more input will be provided, closing
+ /// the engine indicates that no more output should be emitted.
+ Future close() async {
+ _closed = true;
+ if (_closedBeforeDone == null) _closedBeforeDone = true;
+ _suiteController.close();
+
+ // Close the running tests first so that we're sure to wait for them to
+ // finish before we close their suites and cause them to become unloaded.
+ var allLiveTests = liveTests.toSet()..addAll(_activeLoadTests);
+ await Future.wait(allLiveTests.map((liveTest) => liveTest.close()));
+
+ var allSuites = allLiveTests.map((liveTest) => liveTest.suite).toSet();
+ await Future.wait(allSuites.map((suite) => suite.close()));
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698