// Copyright (c) 2016, 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. /// A minimal, dependency-free emulation layer for a subset of the /// unittest/test API used by the language and core library (especially HTML) /// tests. /// /// A number of our language and core library tests were written against the /// unittest package, which is now deprecated in favor of the new test package. /// The latter is much better feature-wise, but is also quite complex and has /// many dependencies. For low-level language and core library tests, we don't /// want to have to pull in a large number of dependencies and be able to run /// them correctly in order to run a test, so we want to test them against /// something simpler. /// /// When possible, we just use the tiny expect library. But to avoid rewriting /// all of the existing tests that use unittest, they can instead use this, /// which shims the unittest API with as little code as possible and calls into /// expect. /// /// Eventually, it would be good to refactor those tests to use the expect /// package directly and remove this. import 'dart:async'; import 'package:expect/expect.dart'; typedef dynamic _Action(); typedef void _ExpectationFunction(dynamic actual); final List<_Group> _groups = [new _Group()]; final Object isFalse = new _Expectation(Expect.isFalse); final Object isNotNull = new _Expectation(Expect.isNotNull); final Object isNull = new _Expectation(Expect.isNull); final Object isTrue = new _Expectation(Expect.isTrue); final Object returnsNormally = new _Expectation((actual) { try { (actual as _Action)(); } catch (error) { Expect.fail("Expected function to return normally, but threw:\n$error"); } }); final Object throws = new _Expectation((actual) { Expect.throws(actual as _Action); }); final Object throwsArgumentError = new _Expectation((actual) { Expect.throws(actual as _Action, (error) => error is ArgumentError); }); final Object throwsNoSuchMethodError = new _Expectation((actual) { Expect.throws(actual as _Action, (error) => error is NoSuchMethodError); }); final Object throwsRangeError = new _Expectation((actual) { Expect.throws(actual as _Action, (error) => error is RangeError); }); final Object throwsStateError = new _Expectation((actual) { Expect.throws(actual as _Action, (error) => error is StateError); }); final Object throwsUnsupportedError = new _Expectation((actual) { Expect.throws(actual as _Action, (error) => error is UnsupportedError); }); /// The test runner should call this once after running a test file. void finishTests() { _groups.clear(); _groups.add(new _Group()); } void group(String description, body()) { // TODO(rnystrom): Do something useful with the description. _groups.add(new _Group()); try { var result = body(); if (result is Future) { Expect.testError("group() does not support asynchronous functions."); } } finally { _groups.removeLast(); } } void test(String description, body()) { // TODO(rnystrom): Do something useful with the description. for (var group in _groups) { var result = group.setUpFunction(); if (result is Future) { Expect.testError("setUp() does not support asynchronous functions."); } } try { var result = body(); if (result is Future) { Expect.testError("test() does not support asynchronous functions."); } } finally { for (var i = _groups.length - 1; i >= 0; i--) { var group = _groups[i]; var result = group.tearDownFunction(); if (result is Future) { Expect.testError("tearDown() does not support asynchronous functions."); } } } } void setUp(body()) { // Can't define multiple setUps at the same level. assert(_groups.last.setUpFunction == _defaultAction); _groups.last.setUpFunction = body; } void tearDown(body()) { // Can't define multiple tearDowns at the same level. assert(_groups.last.tearDownFunction == _defaultAction); _groups.last.tearDownFunction = body; } void expect(dynamic actual, dynamic expected, {String reason = ""}) { // TODO(rnystrom): Do something useful with reason. if (expected is! _Expectation) { expected = equals(expected); } var expectation = expected as _Expectation; expectation.function(actual); } void fail(String message) { Expect.fail(message); } Object equals(dynamic value) => new _Expectation((actual) { Expect.deepEquals(value, actual); }); Object notEquals(dynamic value) => new _Expectation((actual) { Expect.notEquals(value, actual); }); Object unorderedEquals(dynamic value) => new _Expectation((actual) { Expect.setEquals(value as Iterable, actual as Iterable); }); Object predicate(bool fn(dynamic value), [String description = ""]) => new _Expectation((actual) { Expect.isTrue(fn(actual), description); }); Object inInclusiveRange(num min, num max) => new _Expectation((actual) { var actualNum = actual as num; if (actualNum < min || actualNum > max) { fail("Expected $actualNum to be in the inclusive range [$min, $max]."); } }); Object greaterThan(num value) => new _Expectation((actual) { var actualNum = actual as num; if (actualNum <= value) { fail("Expected $actualNum to be greater than $value."); } }); Object same(dynamic value) => new _Expectation((actual) { Expect.identical(value, actual); }); Object closeTo(num value, num tolerance) => new _Expectation((actual) { Expect.approxEquals(value, actual as num, tolerance); }); /// Succeeds if the actual value is any of the given strings. Unlike matcher's /// [anyOf], this only works with strings and requires an explicit list. Object anyOf(List expected) => new _Expectation((actual) { for (var string in expected) { if (actual == string) return; } fail("Expected $actual to be one of $expected."); }); _defaultAction() {} /// One level of group() nesting to track an optional [setUp()] and [tearDown()] /// function for the group. /// /// There is also an implicit top level group. class _Group { _Action setUpFunction = _defaultAction; _Action tearDownFunction = _defaultAction; } /// A wrapper around an expectation function. /// /// This function is passed the actual value and should throw an /// [ExpectException] if the value doesn't match the expectation. class _Expectation { final _ExpectationFunction function; _Expectation(this.function); }