// 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 '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/invoker.dart'; import 'package:test_api/src/backend/suite.dart'; import 'package:test_api/src/backend/test.dart'; import '../utils.dart'; late Suite _suite; void main() { setUp(() { _suite = Suite(Group.root([]), suitePlatform, ignoreTimeouts: false); }); group('.test()', () { test('declares a test with a description and body', () async { var bodyRun = false; var tests = declare(() { test('description', () { bodyRun = true; }); }); expect(tests, hasLength(1)); expect(tests.single.name, equals('description')); await _runTest(tests[0] as Test); expect(bodyRun, isTrue); }); test('declares a test with an object as the description', () async { var tests = declare(() { test(Object, () {}); }); expect(tests.single.name, equals('Object')); }); test('declares multiple tests', () { var tests = declare(() { test('description 1', () {}); test('description 2', () {}); test('description 3', () {}); }); expect(tests, hasLength(3)); expect(tests[0].name, equals('description 1')); expect(tests[1].name, equals('description 2')); expect(tests[2].name, equals('description 3')); }); }); group('.setUp()', () { test('is run before all tests', () async { var setUpRun = false; var tests = declare(() { setUp(() => setUpRun = true); test( 'description 1', expectAsync0(() { expect(setUpRun, isTrue); setUpRun = false; }, max: 1), ); test( 'description 2', expectAsync0(() { expect(setUpRun, isTrue); setUpRun = false; }, max: 1), ); }); await _runTest(tests[0] as Test); await _runTest(tests[1] as Test); }); test('can return a Future', () { var setUpRun = false; var tests = declare(() { setUp(() { return Future(() => setUpRun = true); }); test( 'description', expectAsync0(() { expect(setUpRun, isTrue); }, max: 1), ); }); return _runTest(tests.single as Test); }); test('runs in call order within a group', () async { var firstSetUpRun = false; var secondSetUpRun = false; var thirdSetUpRun = false; var tests = declare(() { setUp( expectAsync0(() async { expect(secondSetUpRun, isFalse); expect(thirdSetUpRun, isFalse); firstSetUpRun = true; }), ); setUp( expectAsync0(() async { expect(firstSetUpRun, isTrue); expect(thirdSetUpRun, isFalse); secondSetUpRun = true; }), ); setUp( expectAsync0(() async { expect(firstSetUpRun, isTrue); expect(secondSetUpRun, isTrue); thirdSetUpRun = true; }), ); test( 'description', expectAsync0(() { expect(firstSetUpRun, isTrue); expect(secondSetUpRun, isTrue); expect(thirdSetUpRun, isTrue); }), ); }); await _runTest(tests.single as Test); }); }); group('.tearDown()', () { test('is run after all tests', () async { late bool tearDownRun; var tests = declare(() { setUp(() => tearDownRun = false); tearDown(() => tearDownRun = true); test( 'description 1', expectAsync0(() { expect(tearDownRun, isFalse); }, max: 1), ); test( 'description 2', expectAsync0(() { expect(tearDownRun, isFalse); }, max: 1), ); }); await _runTest(tests[0] as Test); expect(tearDownRun, isTrue); await _runTest(tests[1] as Test); expect(tearDownRun, isTrue); }); test('is run after an out-of-band failure', () async { late bool tearDownRun; var tests = declare(() { setUp(() => tearDownRun = false); tearDown(() => tearDownRun = true); test( 'description 1', expectAsync0(() { Invoker.current!.addOutstandingCallback(); Future( () => throw TestFailure('oh no'), ).whenComplete(Invoker.current!.removeOutstandingCallback); }, max: 1), ); }); await _runTest(tests.single as Test, shouldFail: true); expect(tearDownRun, isTrue); }); test('can return a Future', () async { var tearDownRun = false; var tests = declare(() { tearDown(() { return Future(() => tearDownRun = true); }); test( 'description', expectAsync0(() { expect(tearDownRun, isFalse); }, max: 1), ); }); await _runTest(tests.single as Test); expect(tearDownRun, isTrue); }); test("isn't run until there are no outstanding callbacks", () async { var outstandingCallbackRemoved = false; var outstandingCallbackRemovedBeforeTeardown = false; var tests = declare(() { tearDown(() { outstandingCallbackRemovedBeforeTeardown = outstandingCallbackRemoved; }); test('description', () { Invoker.current!.addOutstandingCallback(); pumpEventQueue().then((_) { outstandingCallbackRemoved = true; Invoker.current!.removeOutstandingCallback(); }); }); }); await _runTest(tests.single as Test); expect(outstandingCallbackRemovedBeforeTeardown, isTrue); }); test( "isn't run until test body completes after out-of-band error", () async { var hasTestFinished = false; var hasTestFinishedBeforeTeardown = false; var tests = declare(() { tearDown(() { hasTestFinishedBeforeTeardown = hasTestFinished; }); test('description', () { Future.error('oh no'); return pumpEventQueue().then((_) { hasTestFinished = true; }); }); }); await _runTest(tests.single as Test, shouldFail: true); expect(hasTestFinishedBeforeTeardown, isTrue); }, ); test("doesn't complete until there are no outstanding callbacks", () async { var outstandingCallbackRemoved = false; var tests = declare(() { tearDown(() { Invoker.current!.addOutstandingCallback(); pumpEventQueue().then((_) { outstandingCallbackRemoved = true; Invoker.current!.removeOutstandingCallback(); }); }); test('description', () {}); }); await _runTest(tests.single as Test); expect(outstandingCallbackRemoved, isTrue); }); test('runs in reverse call order within a group', () async { var firstTearDownRun = false; var secondTearDownRun = false; var thirdTearDownRun = false; var tests = declare(() { tearDown( expectAsync0(() async { expect(secondTearDownRun, isTrue); expect(thirdTearDownRun, isTrue); firstTearDownRun = true; }), ); tearDown( expectAsync0(() async { expect(firstTearDownRun, isFalse); expect(thirdTearDownRun, isTrue); secondTearDownRun = true; }), ); tearDown( expectAsync0(() async { expect(firstTearDownRun, isFalse); expect(secondTearDownRun, isFalse); thirdTearDownRun = true; }), ); test( 'description', expectAsync0(() { expect(firstTearDownRun, isFalse); expect(secondTearDownRun, isFalse); expect(thirdTearDownRun, isFalse); }, max: 1), ); }); await _runTest(tests.single as Test); }); test('runs further tearDowns in a group even if one fails', () async { var tests = declare(() { tearDown(expectAsync0(() {})); tearDown(() async { throw 'error'; }); test('description', expectAsync0(() {})); }); await _runTest(tests.single as Test, shouldFail: true); }); test('runs in the same error zone as the test', () { return expectTestsPass(() { late Zone testBodyZone; tearDown(() { final tearDownZone = Zone.current; expect( tearDownZone.inSameErrorZone(testBodyZone), isTrue, reason: 'The tear down callback is in a different error zone ' 'than the test body.', ); }); test('test', () { testBodyZone = Zone.current; }); }); }); }); group('in a group,', () { test("tests inherit the group's description", () { var entries = declare(() { group('group', () { test('description', () {}); }); }); expect(entries, hasLength(1)); var testGroup = entries.single as Group; expect(testGroup.name, equals('group')); expect(testGroup.entries, hasLength(1)); expect(testGroup.entries.single, const TypeMatcher()); expect(testGroup.entries.single.name, 'group description'); }); test("tests inherit the group's description when it's not a string", () { var entries = declare(() { group(Object, () { test('description', () {}); }); }); expect(entries, hasLength(1)); var testGroup = entries.single as Group; expect(testGroup.name, equals('Object')); expect(testGroup.entries, hasLength(1)); expect(testGroup.entries.single, const TypeMatcher()); expect(testGroup.entries.single.name, 'Object description'); }); test("a test's timeout factor is applied to the group's", () { var entries = declare(() { group('group', () { test('test', () {}, timeout: const Timeout.factor(3)); }, timeout: const Timeout.factor(2)); }); expect(entries, hasLength(1)); var testGroup = entries.single as Group; expect(testGroup.metadata.timeout.scaleFactor, equals(2)); expect(testGroup.entries, hasLength(1)); expect(testGroup.entries.single, const TypeMatcher()); expect(testGroup.entries.single.metadata.timeout.scaleFactor, equals(6)); }); test("a test's timeout factor is applied to the group's duration", () { var entries = declare(() { group('group', () { test('test', () {}, timeout: const Timeout.factor(2)); }, timeout: const Timeout(Duration(seconds: 10))); }); expect(entries, hasLength(1)); var testGroup = entries.single as Group; expect( testGroup.metadata.timeout.duration, equals(const Duration(seconds: 10)), ); expect(testGroup.entries, hasLength(1)); expect(testGroup.entries.single, const TypeMatcher()); expect( testGroup.entries.single.metadata.timeout.duration, equals(const Duration(seconds: 20)), ); }); test("a test's timeout duration is applied over the group's", () { var entries = declare(() { group('group', () { test('test', () {}, timeout: const Timeout(Duration(seconds: 15))); }, timeout: const Timeout(Duration(seconds: 10))); }); expect(entries, hasLength(1)); var testGroup = entries.single as Group; expect( testGroup.metadata.timeout.duration, equals(const Duration(seconds: 10)), ); expect(testGroup.entries, hasLength(1)); expect(testGroup.entries.single, const TypeMatcher()); expect( testGroup.entries.single.metadata.timeout.duration, equals(const Duration(seconds: 15)), ); }); test('disallows asynchronous groups', () async { declare(() { expect(() => group('group', () async {}), throwsArgumentError); }); }); group('.setUp()', () { test('is scoped to the group', () async { var setUpRun = false; var entries = declare(() { group('group', () { setUp(() => setUpRun = true); test( 'description 1', expectAsync0(() { expect(setUpRun, isTrue); setUpRun = false; }, max: 1), ); }); test( 'description 2', expectAsync0(() { expect(setUpRun, isFalse); setUpRun = false; }, max: 1), ); }); await _runTest((entries[0] as Group).entries.single as Test); await _runTest(entries[1] as Test); }); test('runs from the outside in', () { var outerSetUpRun = false; var middleSetUpRun = false; var innerSetUpRun = false; var entries = declare(() { setUp( expectAsync0(() { expect(middleSetUpRun, isFalse); expect(innerSetUpRun, isFalse); outerSetUpRun = true; }, max: 1), ); group('middle', () { setUp( expectAsync0(() { expect(outerSetUpRun, isTrue); expect(innerSetUpRun, isFalse); middleSetUpRun = true; }, max: 1), ); group('inner', () { setUp( expectAsync0(() { expect(outerSetUpRun, isTrue); expect(middleSetUpRun, isTrue); innerSetUpRun = true; }, max: 1), ); test( 'description', expectAsync0(() { expect(outerSetUpRun, isTrue); expect(middleSetUpRun, isTrue); expect(innerSetUpRun, isTrue); }, max: 1), ); }); }); }); var middleGroup = entries.single as Group; var innerGroup = middleGroup.entries.single as Group; return _runTest(innerGroup.entries.single as Test); }); test('handles Futures when chained', () { var outerSetUpRun = false; var innerSetUpRun = false; var entries = declare(() { setUp( expectAsync0(() { expect(innerSetUpRun, isFalse); return Future(() => outerSetUpRun = true); }, max: 1), ); group('inner', () { setUp( expectAsync0(() { expect(outerSetUpRun, isTrue); return Future(() => innerSetUpRun = true); }, max: 1), ); test( 'description', expectAsync0(() { expect(outerSetUpRun, isTrue); expect(innerSetUpRun, isTrue); }, max: 1), ); }); }); var innerGroup = entries.single as Group; return _runTest(innerGroup.entries.single as Test); }); test("inherits group's tags", () { var tests = declare(() { group('outer', () { group('inner', () { test('with tags', () {}, tags: 'd'); }, tags: ['b', 'c']); }, tags: 'a'); }); var outerGroup = tests.single as Group; var innerGroup = outerGroup.entries.single as Group; var testWithTags = innerGroup.entries.single; expect(outerGroup.metadata.tags, unorderedEquals(['a'])); expect(innerGroup.metadata.tags, unorderedEquals(['a', 'b', 'c'])); expect( testWithTags.metadata.tags, unorderedEquals(['a', 'b', 'c', 'd']), ); }); test('throws on invalid tags', () { expect(() { declare(() { group('a', () {}, tags: 1); }); }, throwsArgumentError); }); }); group('.tearDown()', () { test('is scoped to the group', () async { late bool tearDownRun; var entries = declare(() { setUp(() => tearDownRun = false); group('group', () { tearDown(() => tearDownRun = true); test( 'description 1', expectAsync0(() { expect(tearDownRun, isFalse); }, max: 1), ); }); test( 'description 2', expectAsync0(() { expect(tearDownRun, isFalse); }, max: 1), ); }); var testGroup = entries[0] as Group; await _runTest(testGroup.entries.single as Test); expect(tearDownRun, isTrue); await _runTest(entries[1] as Test); expect(tearDownRun, isFalse); }); test('runs from the inside out', () async { var innerTearDownRun = false; var middleTearDownRun = false; var outerTearDownRun = false; var entries = declare(() { tearDown( expectAsync0(() { expect(innerTearDownRun, isTrue); expect(middleTearDownRun, isTrue); outerTearDownRun = true; }, max: 1), ); group('middle', () { tearDown( expectAsync0(() { expect(innerTearDownRun, isTrue); expect(outerTearDownRun, isFalse); middleTearDownRun = true; }, max: 1), ); group('inner', () { tearDown( expectAsync0(() { expect(outerTearDownRun, isFalse); expect(middleTearDownRun, isFalse); innerTearDownRun = true; }, max: 1), ); test( 'description', expectAsync0(() { expect(outerTearDownRun, isFalse); expect(middleTearDownRun, isFalse); expect(innerTearDownRun, isFalse); }, max: 1), ); }); }); }); var middleGroup = entries.single as Group; var innerGroup = middleGroup.entries.single as Group; await _runTest(innerGroup.entries.single as Test); expect(innerTearDownRun, isTrue); expect(middleTearDownRun, isTrue); expect(outerTearDownRun, isTrue); }); test('handles Futures when chained', () async { var outerTearDownRun = false; var innerTearDownRun = false; var entries = declare(() { tearDown( expectAsync0(() { expect(innerTearDownRun, isTrue); return Future(() => outerTearDownRun = true); }, max: 1), ); group('inner', () { tearDown( expectAsync0(() { expect(outerTearDownRun, isFalse); return Future(() => innerTearDownRun = true); }, max: 1), ); test( 'description', expectAsync0(() { expect(outerTearDownRun, isFalse); expect(innerTearDownRun, isFalse); }, max: 1), ); }); }); var innerGroup = entries.single as Group; await _runTest(innerGroup.entries.single as Test); expect(innerTearDownRun, isTrue); expect(outerTearDownRun, isTrue); }); test('runs outer callbacks even when inner ones fail', () async { var outerTearDownRun = false; var entries = declare(() { tearDown(() { return Future(() => outerTearDownRun = true); }); group('inner', () { tearDown(() { throw 'inner error'; }); test( 'description', expectAsync0(() { expect(outerTearDownRun, isFalse); }, max: 1), ); }); }); var innerGroup = entries.single as Group; await _runTest(innerGroup.entries.single as Test, shouldFail: true); expect(outerTearDownRun, isTrue); }); }); }); group('duplicate names', () { test('can be enabled', () { expect( () => declare(() { test('a', expectAsync0(() {}, count: 0)); test('a', expectAsync0(() {}, count: 0)); }, allowDuplicateTestNames: false), throwsA( isA().having((e) => e.name, 'name', 'a'), ), ); }); test('are allowed by default', () { expect( declare(() { test('a', expectAsync0(() {}, count: 0)); test('a', expectAsync0(() {}, count: 0)); }).map((e) => e.name), equals(['a', 'a']), ); }); }); } /// Runs [test]. /// /// This automatically sets up an `onError` listener to ensure that the test /// doesn't throw any invisible exceptions. Future _runTest(Test test, {bool shouldFail = false}) { var liveTest = test.load(_suite); if (shouldFail) { liveTest.onError.listen(expectAsync1((_) {})); } else { liveTest.onError.listen((e) => registerException(e.error, e.stackTrace)); } return liveTest.run(); }