blob: c5742579c78237ba336e52907ce700b5da5908ae [file] [log] [blame]
// Copyright (c) 2020, 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 'package:path/path.dart' as p;
import 'package:test_api/backend.dart';
import 'package:test_api/src/backend/declarer.dart'; //ignore: implementation_imports
import 'package:test_api/src/backend/group.dart'; //ignore: implementation_imports
import 'package:test_api/src/backend/group_entry.dart'; //ignore: implementation_imports
import 'package:test_api/src/backend/invoker.dart'; // ignore: implementation_imports
import 'package:test_api/src/backend/test.dart'; //ignore: implementation_imports
import 'runner/configuration.dart';
import 'runner/engine.dart';
import 'runner/plugin/environment.dart';
import 'runner/reporter.dart';
import 'runner/reporter/expanded.dart';
import 'runner/runner_suite.dart';
import 'runner/suite.dart';
import 'util/os.dart';
import 'util/print_sink.dart';
/// Runs all unskipped test cases declared in [testMain].
/// Test suite level metadata defined in annotations is not read. No filtering
/// is applied except for the filtering defined by `solo` or `skip` arguments to
/// `group` and `test`. Returns [true] if all tests passed.
Future<bool> directRunTests(FutureOr<void> Function() testMain,
{Reporter Function(Engine)? reporterFactory,
// TODO: Change the default
bool allowDuplicateTestNames = true}) =>
reporterFactory: reporterFactory,
allowDuplicateTestNames: allowDuplicateTestNames);
/// Runs a single test declared in [testMain] matched by it's full test name.
/// There must be exactly one test defined with the name [fullTestName]. Note
/// that not all tests and groups are checked, so a test case that is not be
/// intended to be run (due to a `solo` on a different test) may still be run
/// with this API. Only the test names returned by [enumerateTestCases] should
/// be used to prevent running skipped tests.
/// Return [true] if the test passes.
/// If there are no tests matching [fullTestName] a [MissingTestException] is
/// thrown. If there is more than one test with the name [fullTestName] they
/// will both be run, then a [DuplicateTestnameException] will be thrown.
Future<bool> directRunSingleTest(
FutureOr<void> Function() testMain, String fullTestName,
{Reporter Function(Engine)? reporterFactory}) =>
reporterFactory: reporterFactory,
fullTestName: fullTestName,
allowDuplicateTestNames: false);
Future<bool> _directRunTests(FutureOr<void> Function() testMain,
{Reporter Function(Engine)? reporterFactory,
String? fullTestName,
required bool allowDuplicateTestNames}) async {
reporterFactory ??= (engine) =>, PrintSink(),
color: Configuration.empty.color, printPath: false, printPlatform: false);
final declarer = Declarer(
fullTestName: fullTestName,
allowDuplicateTestNames: allowDuplicateTestNames);
await declarer.declare(testMain);
final suite = RunnerSuite(
const PluginEnvironment(),
SuitePlatform(Runtime.vm, compiler: null, os: currentOSGuess),
path: p.prettyUri(Uri.base));
final engine = Engine()
final success = await runZoned(() => Invoker.guard(,
zoneValues: {#test.declarer: declarer}) ??
if (fullTestName != null) {
final testCount = engine.liveTests.length;
if (testCount == 0) {
throw MissingTestException(fullTestName);
return success;
/// Runs [testMain] and returns the names of all declared tests.
/// Test names declared must be unique. If any test repeats the full name,
/// including group prefixes, of a prior test a [DuplicateTestNameException]
/// will be thrown.
/// Skipped tests are ignored.
Future<Set<String>> enumerateTestCases(
FutureOr<void> Function() testMain) async {
final declarer = Declarer();
await declarer.declare(testMain);
final toVisit = Queue<GroupEntry>.of([]);
final unskippedTestNames = <String>{};
while (toVisit.isNotEmpty) {
final current = toVisit.removeLast();
if (current is Group) {
} else if (current is Test) {
if (current.metadata.skip) continue;
} else {
throw StateError('Unhandled Group Entry: ${current.runtimeType}');
return unskippedTestNames;
/// An exception thrown when a specific test was requested by name that does not
/// exist.
class MissingTestException implements Exception {
final String name;
String toString() =>
'A test with the name "$name" was not declared in the test suite.';