blob: 7ea0f4505e8e2b220fa767a08c4c85051a594471 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. 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' show stdout;
import 'dart:isolate';
import 'test.dart';
/// A suite of tests, added with the [test] method, which will be run in a
/// following event.
class TestSuite {
/// Creates a new [TestSuite] with logs written to [logger] and callbacks
/// given by [lifecycle].
TestSuite({
StringSink? logger,
Lifecycle? lifecycle,
}) :
_logger = logger ?? stdout,
_lifecycle = lifecycle ?? _DefaultLifecycle();
final Lifecycle _lifecycle;
final StringSink _logger;
bool _testQueuePrimed = false;
final Queue<Test> _testQueue = Queue<Test>();
final Map<String, Test> _runningTests = <String, Test>{};
/// Adds a test to the test suite.
void test(
String name,
dynamic Function() body, {
bool skip = false,
}) {
if (_runningTests.isNotEmpty) {
throw StateError(
'Test "$name" added after tests have started to run. '
'Calls to test() must be synchronous with main().',
);
}
if (skip) {
_logger.writeln('Test "$name": Skipped');
_primeQueue();
return;
}
_pushTest(name, body);
}
void _primeQueue() {
if (!_testQueuePrimed) {
// All tests() must be added synchronously with main, so we can enqueue an
// event to start all tests to run after main() is done.
Timer.run(_startAllTests);
_testQueuePrimed = true;
}
}
void _pushTest(
String name,
dynamic Function() body,
) {
final Test newTest = Test(name, body, logger: _logger);
_testQueue.add(newTest);
newTest.state = TestState.queued;
_primeQueue();
}
void _startAllTests() {
for (final Test t in _testQueue) {
_runningTests[t.name] = t;
t.run(onDone: () {
_runningTests.remove(t.name);
if (_runningTests.isEmpty) {
_lifecycle.onDone(_testQueue);
}
});
}
_lifecycle.onStart();
if (_testQueue.isEmpty) {
_logger.writeln('All tests skipped.');
_lifecycle.onDone(_testQueue);
}
}
}
/// Callbacks for the lifecycle of a [TestSuite].
abstract class Lifecycle {
/// Called after a test suite has started.
void onStart();
/// Called after the last test in a test suite has completed.
void onDone(Queue<Test> tests);
}
class _DefaultLifecycle implements Lifecycle {
final ReceivePort _suitePort = ReceivePort('Suite port');
late Queue<Test> _tests;
@override
void onStart() {
_suitePort.listen((dynamic msg) {
_suitePort.close();
_processResults();
});
}
@override
void onDone(Queue<Test> tests) {
_tests = tests;
_suitePort.sendPort.send(null);
}
void _processResults() {
bool testsSucceeded = true;
for (final Test t in _tests) {
testsSucceeded = testsSucceeded && (t.state == TestState.succeeded);
}
if (!testsSucceeded) {
throw 'A test failed';
}
}
}