| // 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 'runner/engine.dart'; | |
| import 'runner/plugin/environment.dart'; | |
| import 'runner/reporter.dart'; | |
| import 'runner/reporter/direct.dart'; | |
| import 'runner/runner_suite.dart'; | |
| import 'runner/suite.dart'; | |
| import 'util/os.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 https://github.com/dart-lang/test/issues/1571 | |
| bool allowDuplicateTestNames = true, | |
| }) => _directRunTests( | |
| testMain, | |
| 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, | |
| }) => _directRunTests( | |
| testMain, | |
| reporterFactory: reporterFactory, | |
| fullTestName: fullTestName, | |
| allowDuplicateTestNames: false, | |
| ); | |
| Future<bool> _directRunTests( | |
| FutureOr<void> Function() testMain, { | |
| Reporter Function(Engine)? reporterFactory, | |
| String? fullTestName, | |
| required bool allowDuplicateTestNames, | |
| }) async { | |
| reporterFactory ??= createDirectReporter; | |
| final declarer = Declarer( | |
| fullTestName: fullTestName, | |
| allowDuplicateTestNames: allowDuplicateTestNames, | |
| ); | |
| await declarer.declare(testMain); | |
| final suite = RunnerSuite( | |
| const PluginEnvironment(), | |
| SuiteConfiguration.empty, | |
| declarer.build(), | |
| SuitePlatform(Runtime.vm, compiler: null, os: currentOSGuess), | |
| path: p.prettyUri(Uri.base), | |
| ); | |
| final engine = Engine() | |
| ..suiteSink.add(suite) | |
| ..suiteSink.close(); | |
| reporterFactory(engine); | |
| final success = | |
| await runZoned( | |
| () => Invoker.guard(engine.run), | |
| zoneValues: {#test.declarer: declarer}, | |
| ) ?? | |
| false; | |
| 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([declarer.build()]); | |
| final unskippedTestNames = <String>{}; | |
| while (toVisit.isNotEmpty) { | |
| final current = toVisit.removeLast(); | |
| if (current is Group) { | |
| toVisit.addAll(current.entries.reversed); | |
| } else if (current is Test) { | |
| if (current.metadata.skip) continue; | |
| unskippedTestNames.add(current.name); | |
| } 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; | |
| MissingTestException(this.name); | |
| @override | |
| String toString() => | |
| 'A test with the name "$name" was not declared in the test suite.'; | |
| } |