// 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. import 'dart:async'; import 'dart:collection'; import 'package:boolean_selector/boolean_selector.dart'; import 'package:glob/glob.dart'; import 'package:test/test.dart'; import 'package:test_api/src/backend/declarer.dart'; import 'package:test_api/src/backend/group.dart'; import 'package:test_api/src/backend/group_entry.dart'; import 'package:test_api/src/backend/live_test.dart'; import 'package:test_api/src/backend/platform_selector.dart'; import 'package:test_api/src/backend/runtime.dart'; import 'package:test_api/src/backend/state.dart'; import 'package:test_api/src/backend/suite_platform.dart'; import 'package:test_core/src/runner/application_exception.dart'; import 'package:test_core/src/runner/compiler_selection.dart'; import 'package:test_core/src/runner/configuration.dart'; import 'package:test_core/src/runner/configuration/custom_runtime.dart'; import 'package:test_core/src/runner/configuration/runtime_settings.dart'; import 'package:test_core/src/runner/engine.dart'; import 'package:test_core/src/runner/load_suite.dart'; import 'package:test_core/src/runner/plugin/environment.dart'; import 'package:test_core/src/runner/runner_suite.dart'; import 'package:test_core/src/runner/runtime_selection.dart'; import 'package:test_core/src/runner/suite.dart'; /// A dummy suite platform to use for testing suites. final suitePlatform = SuitePlatform(Runtime.vm, compiler: null); // The last state change detected via [expectStates]. State? _lastState; /// Asserts that exactly [states] will be emitted via [liveTest.onStateChange]. /// /// The most recent emitted state is stored in [_lastState]. void expectStates(LiveTest liveTest, Iterable statesIter) { var states = Queue.of(statesIter); liveTest.onStateChange.listen( expectAsync1( (state) { _lastState = state; expect(state, equals(states.removeFirst())); }, count: states.length, max: states.length, ), ); } /// Asserts that errors will be emitted via [liveTest.onError] that match /// [validators], in order. void expectErrors( LiveTest liveTest, Iterable validatorsIter, ) { var validators = Queue.of(validatorsIter); liveTest.onError.listen( expectAsync1( (error) { validators.removeFirst()(error.error); }, count: validators.length, max: validators.length, ), ); } /// Asserts that [liveTest] will have a single failure with message `"oh no"`. void expectSingleFailure(LiveTest liveTest) { expectStates(liveTest, [ const State(Status.running, Result.success), const State(Status.complete, Result.failure), ]); expectErrors(liveTest, [ (error) { expect(_lastState!.status, equals(Status.complete)); expect(error, _isTestFailure('oh no')); }, ]); } /// Returns a matcher that matches a [TestFailure] with the given [message]. /// /// [message] can be a string or a [Matcher]. Matcher _isTestFailure(Object message) => const TypeMatcher() .having((e) => e.message, 'message', message); /// Returns a matcher that matches a [ApplicationException] with the given /// [message]. /// /// [message] can be a string or a [Matcher]. Matcher isApplicationException(Object message) => const TypeMatcher().having( (e) => e.message, 'message', message, ); /// Asserts that [liveTest] has completed and passed. /// /// If the test had any errors, they're surfaced nicely into the outer test. void expectTestPassed(LiveTest liveTest) { // Since the test is expected to pass, we forward any current or future errors // to the outer test, because they're definitely unexpected. for (var error in liveTest.errors) { registerException(error.error, error.stackTrace); } liveTest.onError.listen((error) { registerException(error.error, error.stackTrace); }); expect(liveTest.state.status, equals(Status.complete)); expect(liveTest.state.result, equals(Result.success)); } /// Asserts that [liveTest] failed with a single [TestFailure] whose message /// matches [message]. void expectTestFailed(LiveTest liveTest, Object message) { expect(liveTest.state.status, equals(Status.complete)); expect(liveTest.state.result, equals(Result.failure)); expect(liveTest.errors, hasLength(1)); expect(liveTest.errors.first.error, _isTestFailure(message)); } /// Runs [body] with a declarer and returns the declared entries. List declare(void Function() body) { var declarer = Declarer()..declare(body); return declarer.build().entries; } /// Runs [body] with a declarer and returns an engine that runs those tests. Engine declareEngine( void Function() body, { bool runSkipped = false, String? coverage, String? coverageLcov, bool stopOnFirstFailure = false, }) { var declarer = Declarer()..declare(body); return Engine.withSuites( [ RunnerSuite( const PluginEnvironment(), SuiteConfiguration.runSkipped(runSkipped), declarer.build(), suitePlatform, ), ], coverage: coverage, coverageLcov: coverageLcov, stopOnFirstFailure: stopOnFirstFailure, ); } /// Returns a [RunnerSuite] with a default environment and configuration. RunnerSuite runnerSuite(Group root) => RunnerSuite( const PluginEnvironment(), SuiteConfiguration.empty, root, suitePlatform, ); /// Returns a [LoadSuite] with a default configuration. LoadSuite loadSuite(String name, FutureOr Function() body) => LoadSuite(name, SuiteConfiguration.empty, suitePlatform, body); SuiteConfiguration suiteConfiguration({ bool? allowDuplicateTestNames, bool? allowTestRandomization, bool? jsTrace, bool? runSkipped, Iterable? dart2jsArgs, String? precompiledPath, Iterable? compilerSelections, Iterable? runtimes, Map? tags, Map? onPlatform, bool? ignoreTimeouts, Timeout? suiteLoadTimeout, // Test-level configuration Timeout? timeout, bool? verboseTrace, bool? chainStackTraces, bool? skip, int? retry, String? skipReason, PlatformSelector? testOn, Iterable? addTags, }) => SuiteConfiguration( allowDuplicateTestNames: allowDuplicateTestNames, allowTestRandomization: allowTestRandomization, jsTrace: jsTrace, runSkipped: runSkipped, dart2jsArgs: dart2jsArgs, precompiledPath: precompiledPath, compilerSelections: compilerSelections, runtimes: runtimes, tags: tags, onPlatform: onPlatform, ignoreTimeouts: ignoreTimeouts, suiteLoadTimeout: suiteLoadTimeout, timeout: timeout, verboseTrace: verboseTrace, chainStackTraces: chainStackTraces, skip: skip, retry: retry, skipReason: skipReason, testOn: testOn, addTags: addTags, ); Configuration configuration({ bool? help, String? customHtmlTemplatePath, bool? version, bool? pauseAfterLoad, bool? debug, bool? color, String? configurationPath, String? reporter, Map? fileReporters, String? coverage, String? coverageLcov, bool? branchCoverage, List? coveragePackages, int? concurrency, int? shardIndex, int? totalShards, Map>? testSelections, Iterable? foldTraceExcept, Iterable? foldTraceOnly, Glob? filename, Iterable? chosenPresets, Map? presets, Map? overrideRuntimes, Map? defineRuntimes, bool? noRetry, bool? ignoreTimeouts, // Suite-level configuration bool? allowDuplicateTestNames, bool? allowTestRandomization, bool? jsTrace, bool? runSkipped, Iterable? dart2jsArgs, String? precompiledPath, Iterable? globalPatterns, Iterable? compilerSelections, Iterable? runtimes, BooleanSelector? includeTags, BooleanSelector? excludeTags, Map? tags, Map? onPlatform, int? testRandomizeOrderingSeed, Timeout? suiteLoadTimeout, // Test-level configuration Timeout? timeout, bool? verboseTrace, bool? chainStackTraces, bool? skip, int? retry, String? skipReason, PlatformSelector? testOn, Iterable? addTags, }) => Configuration( help: help, customHtmlTemplatePath: customHtmlTemplatePath, version: version, pauseAfterLoad: pauseAfterLoad, debug: debug, color: color, configurationPath: configurationPath, reporter: reporter, fileReporters: fileReporters, coverage: coverage, coverageLcov: coverageLcov, branchCoverage: branchCoverage, coveragePackages: coveragePackages, concurrency: concurrency, shardIndex: shardIndex, totalShards: totalShards, testSelections: testSelections, foldTraceExcept: foldTraceExcept, foldTraceOnly: foldTraceOnly, filename: filename, chosenPresets: chosenPresets, presets: presets, overrideRuntimes: overrideRuntimes, defineRuntimes: defineRuntimes, noRetry: noRetry, ignoreTimeouts: ignoreTimeouts, allowDuplicateTestNames: allowDuplicateTestNames, allowTestRandomization: allowTestRandomization, jsTrace: jsTrace, runSkipped: runSkipped, dart2jsArgs: dart2jsArgs, precompiledPath: precompiledPath, globalPatterns: globalPatterns, compilerSelections: compilerSelections, runtimes: runtimes, includeTags: includeTags, excludeTags: excludeTags, tags: tags, onPlatform: onPlatform, testRandomizeOrderingSeed: testRandomizeOrderingSeed, suiteLoadTimeout: suiteLoadTimeout, stopOnFirstFailure: false, timeout: timeout, verboseTrace: verboseTrace, chainStackTraces: chainStackTraces, skip: skip, retry: retry, skipReason: skipReason, testOn: testOn, addTags: addTags, );