Skip to content

Commit d5f2449

Browse files
committed
Add an Engine class for running groups of test suites.
[email protected] See #2 Review URL: https://codereview.chromium.org//891493004
1 parent 8ad00bd commit d5f2449

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-0
lines changed

lib/src/engine.dart

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
library unittest.engine;
6+
7+
import 'dart:async';
8+
import 'dart:collection';
9+
10+
import 'live_test.dart';
11+
import 'state.dart';
12+
import 'suite.dart';
13+
import 'utils.dart';
14+
15+
/// An [Engine] manages a run that encompasses multiple test suites.
16+
///
17+
/// The current status of every test is visible via [liveTests]. [onTestStarted]
18+
/// can also be used to be notified when a test is about to be run.
19+
///
20+
/// Suites will be run in the order they're provided to [new Engine]. Tests
21+
/// within those suites will likewise be run in the order of [Suite.tests].
22+
class Engine {
23+
/// Whether [run] has been called yet.
24+
var _runCalled = false;
25+
26+
/// An unmodifiable list of tests to run.
27+
///
28+
/// These are [LiveTest]s, representing the in-progress state of each test.
29+
/// Tests that have not yet begun running are marked [Status.pending]; tests
30+
/// that have finished are marked [Status.complete].
31+
///
32+
/// [LiveTest.run] must not be called on these tests.
33+
final List<LiveTest> liveTests;
34+
35+
/// A stream that emits each [LiveTest] as it's about to start running.
36+
///
37+
/// This is guaranteed to fire before [LiveTest.onStateChange] first fires.
38+
Stream<LiveTest> get onTestStarted => _onTestStartedController.stream;
39+
final _onTestStartedController = new StreamController<LiveTest>.broadcast();
40+
41+
/// Creates an [Engine] that will run all tests in [suites].
42+
Engine(Iterable<Suite> suites)
43+
: liveTests = new UnmodifiableListView(flatten(suites.map((suite) =>
44+
suite.tests.map((test) => test.load(suite)))));
45+
46+
/// Runs all tests in all suites defined by this engine.
47+
///
48+
/// This returns `true` if all tests succeed, and `false` otherwise. It will
49+
/// only return once all tests have finished running.
50+
Future<bool> run() {
51+
if (_runCalled) {
52+
throw new StateError("Engine.run() may not be called more than once.");
53+
}
54+
_runCalled = true;
55+
56+
return Future.forEach(liveTests, (liveTest) {
57+
_onTestStartedController.add(liveTest);
58+
59+
// First, schedule a microtask to ensure that [onTestStarted] fires before
60+
// the first [LiveTest.onStateChange] event. Once the test finishes, use
61+
// [new Future] to do a coarse-grained event loop pump to avoid starving
62+
// the DOM or other non-microtask events.
63+
return new Future.microtask(liveTest.run).then((_) => new Future(() {}));
64+
}).then((_) =>
65+
liveTests.every((liveTest) => liveTest.state.result == Result.success));
66+
}
67+
}

lib/src/utils.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,20 @@ Trace getTrace(stack, bool formatStacks, bool filterStacks) {
5656
return frame.package != 'unittest' || frame.member != 'TestCase._runTest';
5757
})).terse.foldFrames((frame) => frame.package == 'unittest' || frame.isCore);
5858
}
59+
60+
/// Flattens nested [Iterable]s inside an [Iterable] into a single [List]
61+
/// containing only non-[Iterable] elements.
62+
List flatten(Iterable nested) {
63+
var result = [];
64+
helper(iter) {
65+
for (var element in iter) {
66+
if (element is Iterable) {
67+
helper(element);
68+
} else {
69+
result.add(element);
70+
}
71+
}
72+
}
73+
helper(nested);
74+
return result;
75+
}

test/engine_test.dart

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:unittest/src/declarer.dart';
6+
import 'package:unittest/src/engine.dart';
7+
import 'package:unittest/src/state.dart';
8+
import 'package:unittest/src/suite.dart';
9+
import 'package:unittest/unittest.dart';
10+
11+
void main() {
12+
var declarer;
13+
setUp(() => declarer = new Declarer());
14+
15+
test("runs each test in each suite in order", () {
16+
var testsRun = 0;
17+
for (var i = 0; i < 4; i++) {
18+
declarer.test("test ${i + 1}", expectAsync(() {
19+
expect(testsRun, equals(i));
20+
testsRun++;
21+
}, max: 1));
22+
}
23+
24+
var engine = new Engine([
25+
new Suite("suite 1", declarer.tests.take(2)),
26+
new Suite("suite 2", declarer.tests.skip(2))
27+
]);
28+
29+
return engine.run().then((_) => expect(testsRun, equals(4)));
30+
});
31+
32+
test("emits each test before it starts running and after the previous test "
33+
"finished", () {
34+
var testsRun = 0;
35+
for (var i = 0; i < 3; i++) {
36+
declarer.test("test ${i + 1}", expectAsync(() => testsRun++, max: 1));
37+
}
38+
39+
var engine = new Engine([new Suite("suite", declarer.tests)]);
40+
engine.onTestStarted.listen(expectAsync((liveTest) {
41+
// [testsRun] should be one less than the test currently running.
42+
expect(liveTest.test.name, equals("test ${testsRun + 1}"));
43+
44+
// [Engine.onTestStarted] is guaranteed to fire before the first
45+
// [LiveTest.onStateChange].
46+
expect(liveTest.onStateChange.first,
47+
completion(equals(const State(Status.running, Result.success))));
48+
}, count: 3, max: 3));
49+
50+
return engine.run();
51+
});
52+
53+
test(".run() returns true if every test passes", () {
54+
for (var i = 0; i < 2; i++) {
55+
declarer.test("test ${i + 1}", () {});
56+
}
57+
58+
var engine = new Engine([new Suite("suite", declarer.tests)]);
59+
expect(engine.run(), completion(isTrue));
60+
});
61+
62+
test(".run() returns false if any test fails", () {
63+
for (var i = 0; i < 2; i++) {
64+
declarer.test("test ${i + 1}", () {});
65+
}
66+
declarer.test("failure", () => throw new TestFailure("oh no"));
67+
68+
var engine = new Engine([new Suite("suite", declarer.tests)]);
69+
expect(engine.run(), completion(isFalse));
70+
});
71+
72+
test(".run() returns false if any test errors", () {
73+
for (var i = 0; i < 2; i++) {
74+
declarer.test("test ${i + 1}", () {});
75+
}
76+
declarer.test("failure", () => throw "oh no");
77+
78+
var engine = new Engine([new Suite("suite", declarer.tests)]);
79+
expect(engine.run(), completion(isFalse));
80+
});
81+
82+
test(".run() may not be called more than once", () {
83+
var engine = new Engine([]);
84+
expect(engine.run(), completes);
85+
expect(() => engine.run(), throwsStateError);
86+
});
87+
}

0 commit comments

Comments
 (0)