blob: 6562509a18067611e0ba33498d856145effa445a [file] [log] [blame]
// Copyright (c) 2013, 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 library for writing dart unit tests.
*
* ## Installing ##
*
* Use [pub][] to install this package. Add the following to your `pubspec.yaml`
* file.
*
* dependencies:
* unittest: any
*
* Then run `pub install`.
*
* For more information, see the
* [unittest package on pub.dartlang.org][pkg].
*
* See the [Getting Started](http://pub.dartlang.org/doc)
* guide for more details.
*
* ##Concepts##
*
* * __Tests__: Tests are specified via the top-level function [test], they can be
* organized together using [group].
* * __Checks__: Test expectations can be specified via [expect]
* * __Matchers__: [expect] assertions are written declaratively using the
* [Matcher] class.
* * __Configuration__: The framework can be adapted by calling [configure] with a
* [Configuration]. See the other libraries in the `unittest` package for
* alternative implementations of [Configuration] including
* `compact_vm_config.dart`, `html_config.dart` and
* `html_enhanced_config.dart`.
*
* ##Examples##
*
* A trivial test:
*
* import 'package:unittest/unittest.dart';
* main() {
* test('this is a test', () {
* int x = 2 + 3;
* expect(x, equals(5));
* });
* }
*
* Multiple tests:
*
* import 'package:unittest/unittest.dart';
* main() {
* test('this is a test', () {
* int x = 2 + 3;
* expect(x, equals(5));
* });
* test('this is another test', () {
* int x = 2 + 3;
* expect(x, equals(5));
* });
* }
*
* Multiple tests, grouped by category:
*
* import 'package:unittest/unittest.dart';
* main() {
* group('group A', () {
* test('test A.1', () {
* int x = 2 + 3;
* expect(x, equals(5));
* });
* test('test A.2', () {
* int x = 2 + 3;
* expect(x, equals(5));
* });
* });
* group('group B', () {
* test('this B.1', () {
* int x = 2 + 3;
* expect(x, equals(5));
* });
* });
* }
*
* Asynchronous tests: if callbacks expect between 0 and 2 positional arguments,
* depending on the suffix of expectAsyncX(). expectAsyncX() will wrap a
* function into a new callback and will not consider the test complete until
* that callback is run. A count argument can be provided to specify the number
* of times the callback should be called (the default is 1).
*
* import 'package:unittest/unittest.dart';
* import 'dart:isolate';
* main() {
* test('callback is executed once', () {
* // wrap the callback of an asynchronous call with [expectAsync0] if
* // the callback takes 0 arguments...
* var timer = Timer.run(expectAsync0(() {
* int x = 2 + 3;
* expect(x, equals(5));
* }));
* });
*
* test('callback is executed twice', () {
* var callback = expectAsync0(() {
* int x = 2 + 3;
* expect(x, equals(5));
* }, count: 2); // <-- we can indicate multiplicity to [expectAsync0]
* Timer.run(callback);
* Timer.run(callback);
* });
* }
*
* expectAsyncX() will wrap the callback code in a try/catch handler to handle
* exceptions (treated as test failures). There may be times when the number of
* times a callback should be called is non-deterministic. In this case a dummy
* callback can be created with expectAsync0((){}) and this can be called from
* the real callback when it is finally complete. In this case the body of the
* callback should be protected within a call to guardAsync(); this will ensure
* that exceptions are properly handled.
*
* A variation on this is expectAsyncUntilX(), which takes a callback as the
* first parameter and a predicate function as the second parameter; after each
* time * the callback is called, the predicate function will be called; if it
* returns false the test will still be considered incomplete.
*
* Test functions can return [Future]s, which provide another way of doing
* asynchronous tests. The test framework will handle exceptions thrown by
* the Future, and will advance to the next test when the Future is complete.
* It is still important to use expectAsync/guardAsync with any parts of the
* test that may be invoked from a top level context (for example, with
* Timer.run()], as the Future exception handler may not capture exceptions
* in such code.
*
* Note: due to some language limitations we have to use different functions
* depending on the number of positional arguments of the callback. In the
* future, we plan to expose a single `expectAsync` function that can be used
* regardless of the number of positional arguments. This requires new langauge
* features or fixes to the current spec (e.g. see
* [Issue 2706](http://dartbug.com/2706)).
*
* Meanwhile, we plan to add this alternative API for callbacks of more than 2
* arguments or that take named parameters. (this is not implemented yet,
* but will be coming here soon).
*
* import 'package:unittest/unittest.dart';
* import 'dart:isolate';
* main() {
* test('callback is executed', () {
* // indicate ahead of time that an async callback is expected.
* var async = startAsync();
* Timer.run(() {
* // Guard the body of the callback, so errors are propagated
* // correctly.
* guardAsync(() {
* int x = 2 + 3;
* expect(x, equals(5));
* });
* // indicate that the asynchronous callback was invoked.
* async.complete();
* });
* });
* }
*
* [pub]: http://pub.dartlang.org
* [pkg]: http://pub.dartlang.org/packages/unittest
*/
library unittest;
import 'dart:async';
import 'dart:collection';
import 'dart:isolate';
import 'dart:math' show max;
import 'matcher.dart';
export 'matcher.dart';
// TODO(amouravski): We should not need to import mock here, but it's necessary
// to enable dartdoc on the mock library, as it's not picked up normally.
import 'mock.dart';
part 'src/config.dart';
part 'src/test_case.dart';
Configuration _config;
/**
* [Configuration] used by the unittest library. Note that if a
* configuration has not been set, calling this getter will create
* a default configuration.
*/
Configuration get unittestConfiguration {
if (_config == null) {
_config = new Configuration();
}
return _config;
}
/**
* Sets the [Configuration] used by the unittest library.
*
* Throws a [StateError] if there is an existing, incompatible value.
*/
void set unittestConfiguration(Configuration value) {
if(!identical(_config, value)) {
if(_config != null) {
throw new StateError('unittestConfiguration has already been set');
}
_config = value;
}
}
/**
* Can be called by tests to log status. Tests should use this
* instead of [print].
*/
void logMessage(String message) =>
_config.onLogMessage(currentTestCase, message);
/** Separator used between group names and test names. */
String groupSep = ' ';
final List<TestCase> _testCases = new List<TestCase>();
/** Tests executed in this suite. */
final List<TestCase> testCases = new UnmodifiableListView<TestCase>(_testCases);
/**
* The set of tests to run can be restricted by using [solo_test] and
* [solo_group].
* As groups can be nested we use a counter to keep track of the nest level
* of soloing, and a flag to tell if we have seen any solo tests.
*/
int _soloNestingLevel = 0;
bool _soloTestSeen = false;
/**
* Setup and teardown functions for a group and its parents, the latter
* for chaining.
*/
class _GroupContext {
final _GroupContext parent;
/** Description text of the current test group. */
final String _name;
/** Setup function called before each test in a group. */
Function _testSetup;
get testSetup => _testSetup;
get parentSetup => (parent == null) ? null : parent.testSetup;
set testSetup(Function setup) {
var preSetup = parentSetup;
if (preSetup == null) {
_testSetup = setup;
} else {
_testSetup = () {
var f = preSetup();
if (f is Future) {
return f.then((_) => setup());
} else {
return setup();
}
};
}
}
/** Teardown function called after each test in a group. */
Function _testTeardown;
get testTeardown => _testTeardown;
get parentTeardown => (parent == null) ? null : parent.testTeardown;
set testTeardown(Function teardown) {
var postTeardown = parentTeardown;
if (postTeardown == null) {
_testTeardown = teardown;
} else {
_testTeardown = () {
var f = teardown();
if (f is Future) {
return f.then((_) => postTeardown());
} else {
return postTeardown();
}
};
}
}
String get fullName => (parent == null || parent == _rootContext)
? _name
: "${parent.fullName}$groupSep$_name";
_GroupContext([this.parent, this._name = '']) {
_testSetup = parentSetup;
_testTeardown = parentTeardown;
}
}
// We use a 'dummy' context for the top level to eliminate null
// checks when querying the context. This allows us to easily
// support top-level setUp/tearDown functions as well.
final _rootContext = new _GroupContext();
_GroupContext _currentContext = _rootContext;
int _currentTestCaseIndex = 0;
/** [TestCase] currently being executed. */
TestCase get currentTestCase =>
(_currentTestCaseIndex >= 0 && _currentTestCaseIndex < testCases.length)
? testCases[_currentTestCaseIndex]
: null;
/** Whether the framework is in an initialized state. */
bool _initialized = false;
String _uncaughtErrorMessage = null;
/** Test case result strings. */
// TODO(gram) we should change these constants to use a different string
// (so that writing 'FAIL' in the middle of a test doesn't
// imply that the test fails). We can't do it without also changing
// the testrunner and test.dart though.
const PASS = 'pass';
const FAIL = 'fail';
const ERROR = 'error';
/**
* A map that can be used to communicate state between a test driver
* or main() function and the tests, particularly when these two
* are otherwise independent. For example, a test driver that starts
* an HTTP server and then runs tests that access that server could use
* this as a way of communicating the server port to the tests.
*/
Map testState = {};
/**
* Creates a new test case with the given description and body. The
* description will include the descriptions of any surrounding group()
* calls.
*/
void test(String spec, TestFunction body) {
ensureInitialized();
if (!_soloTestSeen || _soloNestingLevel > 0) {
var testcase = new TestCase._internal(testCases.length + 1, _fullSpec(spec),
body);
_testCases.add(testcase);
}
}
/** Convenience function for skipping a test. */
void skip_test(String spec, TestFunction body){}
/**
* Creates a new test case with the given description and body. The
* description will include the descriptions of any surrounding group()
* calls.
*
* If we use [solo_test] (or [solo_group]) instead of test, then all non-solo
* tests will be disabled. Note that if we use [solo_group], all tests in
* the group will be enabled, regardless of whether they use [test] or
* [solo_test], or whether they are in a nested [group] vs [solo_group]. Put
* another way, if there are any calls to [solo_test] or [solo_group] in a test
* file, all tests that are not inside a [solo_group] will be disabled unless
* they are [solo_test]s.
*
* [skip_test] and [skip_group] take precedence over soloing, by virtue of the
* fact that they are effectively no-ops.
*/
void solo_test(String spec, TestFunction body) {
ensureInitialized();
if (!_soloTestSeen) {
_soloTestSeen = true;
// This is the first solo-ed test. Discard all tests up to now.
_testCases.clear();
}
++_soloNestingLevel;
try {
test(spec, body);
} finally {
--_soloNestingLevel;
}
}
/** Sentinel value for [_SpreadArgsHelper]. */
class _Sentinel {
const _Sentinel();
}
/** Simulates spread arguments using named arguments. */
// TODO(sigmund): remove this class and simply use a closure with named
// arguments (if still applicable).
class _SpreadArgsHelper {
final Function callback;
final int minExpectedCalls;
final int maxExpectedCalls;
final Function isDone;
final String id;
int actualCalls = 0;
final TestCase testCase;
bool complete;
static const sentinel = const _Sentinel();
_SpreadArgsHelper(Function callback, int minExpected, int maxExpected,
Function isDone, String id)
: this.callback = callback,
minExpectedCalls = minExpected,
maxExpectedCalls = (maxExpected == 0 && minExpected > 0)
? minExpected
: maxExpected,
this.isDone = isDone,
this.testCase = currentTestCase,
this.id = _makeCallbackId(id, callback) {
ensureInitialized();
if (testCase == null) {
throw new StateError("No valid test. Did you forget to run your test "
"inside a call to test()?");
}
if (isDone != null || minExpected > 0) {
testCase._callbackFunctionsOutstanding++;
complete = false;
} else {
complete = true;
}
}
static String _makeCallbackId(String id, Function callback) {
// Try to create a reasonable id.
if (id != null) {
return "$id ";
} else {
// If the callback is not an anonymous closure, try to get the
// name.
var fname = callback.toString();
var prefix = "Function '";
var pos = fname.indexOf(prefix);
if (pos > 0) {
pos += prefix.length;
var epos = fname.indexOf("'", pos);
if (epos > 0) {
return "${fname.substring(pos, epos)} ";
}
}
}
return '';
}
bool shouldCallBack() {
++actualCalls;
if (testCase.isComplete) {
// Don't run if the test is done. We don't throw here as this is not
// the current test, but we do mark the old test as having an error
// if it previously passed.
if (testCase.result == PASS) {
testCase.error(
'Callback ${id}called ($actualCalls) after test case '
'${testCase.description} has already been marked as '
'${testCase.result}.', '');
}
return false;
} else if (maxExpectedCalls >= 0 && actualCalls > maxExpectedCalls) {
throw new TestFailure('Callback ${id}called more times than expected '
'($maxExpectedCalls).');
}
return true;
}
void after() {
if (!complete) {
if (minExpectedCalls > 0 && actualCalls < minExpectedCalls) return;
if (isDone != null && !isDone()) return;
// Mark this callback as complete and remove it from the testcase
// oustanding callback count; if that hits zero the testcase is done.
complete = true;
testCase._markCallbackComplete();
}
}
invoke0() {
return _guardAsync(
() {
if (shouldCallBack()) {
return callback();
}
},
after, testCase);
}
invoke1(arg1) {
return _guardAsync(
() {
if (shouldCallBack()) {
return callback(arg1);
}
},
after, testCase);
}
invoke2(arg1, arg2) {
return _guardAsync(
() {
if (shouldCallBack()) {
return callback(arg1, arg2);
}
},
after, testCase);
}
}
/**
* Indicate that [callback] is expected to be called a [count] number of times
* (by default 1). The unittest framework will wait for the callback to run the
* specified [count] times before it continues with the following test. Using
* [expectAsync0] will also ensure that errors that occur within [callback] are
* tracked and reported. [callback] should take 0 positional arguments (named
* arguments are not supported). [id] can be used to provide more
* descriptive error messages if the callback is called more often than
* expected. [max] can be used to specify an upper bound on the number of
* calls; if this is exceeded the test will fail (or be marked as in error if
* it was already complete). A value of 0 for [max] (the default) will set
* the upper bound to the same value as [count]; i.e. the callback should be
* called exactly [count] times. A value of -1 for [max] will mean no upper
* bound.
*/
// TODO(sigmund): deprecate this API when issue 2706 is fixed.
Function expectAsync0(Function callback,
{int count: 1, int max: 0, String id}) {
return new _SpreadArgsHelper(callback, count, max, null, id).invoke0;
}
/** Like [expectAsync0] but [callback] should take 1 positional argument. */
// TODO(sigmund): deprecate this API when issue 2706 is fixed.
Function expectAsync1(Function callback,
{int count: 1, int max: 0, String id}) {
return new _SpreadArgsHelper(callback, count, max, null, id).invoke1;
}
/** Like [expectAsync0] but [callback] should take 2 positional arguments. */
// TODO(sigmund): deprecate this API when issue 2706 is fixed.
Function expectAsync2(Function callback,
{int count: 1, int max: 0, String id}) {
return new _SpreadArgsHelper(callback, count, max, null, id).invoke2;
}
/**
* Indicate that [callback] is expected to be called until [isDone] returns
* true. The unittest framework check [isDone] after each callback and only
* when it returns true will it continue with the following test. Using
* [expectAsyncUntil0] will also ensure that errors that occur within
* [callback] are tracked and reported. [callback] should take 0 positional
* arguments (named arguments are not supported). [id] can be used to
* identify the callback in error messages (for example if it is called
* after the test case is complete).
*/
// TODO(sigmund): deprecate this API when issue 2706 is fixed.
Function expectAsyncUntil0(Function callback, Function isDone, {String id}) {
return new _SpreadArgsHelper(callback, 0, -1, isDone, id).invoke0;
}
/**
* Like [expectAsyncUntil0] but [callback] should take 1 positional argument.
*/
// TODO(sigmund): deprecate this API when issue 2706 is fixed.
Function expectAsyncUntil1(Function callback, Function isDone, {String id}) {
return new _SpreadArgsHelper(callback, 0, -1, isDone, id).invoke1;
}
/**
* Like [expectAsyncUntil0] but [callback] should take 2 positional arguments.
*/
// TODO(sigmund): deprecate this API when issue 2706 is fixed.
Function expectAsyncUntil2(Function callback, Function isDone, {String id}) {
return new _SpreadArgsHelper(callback, 0, -1, isDone, id).invoke2;
}
/**
* Wraps the [callback] in a new function and returns that function. The new
* function will be able to handle exceptions by directing them to the correct
* test. This is thus similar to expectAsync0. Use it to wrap any callbacks that
* might optionally be called but may never be called during the test.
* [callback] should take 0 positional arguments (named arguments are not
* supported). [id] can be used to identify the callback in error
* messages (for example if it is called after the test case is complete).
*/
// TODO(sigmund): deprecate this API when issue 2706 is fixed.
Function protectAsync0(Function callback, {String id}) {
return new _SpreadArgsHelper(callback, 0, -1, null, id).invoke0;
}
/**
* Like [protectAsync0] but [callback] should take 1 positional argument.
*/
// TODO(sigmund): deprecate this API when issue 2706 is fixed.
Function protectAsync1(Function callback, {String id}) {
return new _SpreadArgsHelper(callback, 0, -1, null, id).invoke1;
}
/**
* Like [protectAsync0] but [callback] should take 2 positional arguments.
*/
// TODO(sigmund): deprecate this API when issue 2706 is fixed.
Function protectAsync2(Function callback, {String id}) {
return new _SpreadArgsHelper(callback, 0, -1, null, id).invoke2;
}
/**
* Creates a new named group of tests. Calls to group() or test() within the
* body of the function passed to this will inherit this group's description.
*/
void group(String description, void body()) {
ensureInitialized();
_currentContext = new _GroupContext(_currentContext, description);
try {
body();
} catch (e, trace) {
var stack = (trace == null) ? '' : ': ${trace.toString()}';
_uncaughtErrorMessage = "${e.toString()}$stack";
} finally {
// Now that the group is over, restore the previous one.
_currentContext = _currentContext.parent;
}
}
/** Like [skip_test], but for groups. */
void skip_group(String description, void body()) {}
/** Like [solo_test], but for groups. */
void solo_group(String description, void body()) {
ensureInitialized();
if (!_soloTestSeen) {
_soloTestSeen = true;
// This is the first solo-ed group. Discard all tests up to now.
_testCases.clear();
}
++_soloNestingLevel;
try {
group(description, body);
} finally {
--_soloNestingLevel;
}
}
/**
* Register a [setUp] function for a test [group]. This function will
* be called before each test in the group is run.
* [setUp] and [tearDown] should be called within the [group] before any
* calls to [test]. The [setupTest] function can be asynchronous; in this
* case it must return a [Future].
*/
void setUp(Function setupTest) {
_currentContext.testSetup = setupTest;
}
/**
* Register a [tearDown] function for a test [group]. This function will
* be called after each test in the group is run. Note that if groups
* are nested only the most locally scoped [teardownTest] function will be run.
* [setUp] and [tearDown] should be called within the [group] before any
* calls to [test]. The [teardownTest] function can be asynchronous; in this
* case it must return a [Future].
*/
void tearDown(Function teardownTest) {
_currentContext.testTeardown = teardownTest;
}
/** Advance to the next test case. */
void _nextTestCase() {
runAsync(() {
_currentTestCaseIndex++;
_nextBatch();
});
}
/**
* Utility function that can be used to notify the test framework that an
* error was caught outside of this library.
*/
void _reportTestError(String msg, String trace) {
if (_currentTestCaseIndex < testCases.length) {
final testCase = testCases[_currentTestCaseIndex];
testCase.error(msg, trace);
} else {
_uncaughtErrorMessage = "$msg: $trace";
}
}
void rerunTests() {
_uncaughtErrorMessage = null;
_initialized = true; // We don't want to reset the test array.
runTests();
}
/**
* Filter the tests. [testFilter] can be a [RegExp], a [String] or a
* predicate function. This is different to enabling/disabling tests
* in that it removes the tests completely.
*/
void filterTests(testFilter) {
var filterFunction;
if (testFilter is String) {
RegExp re = new RegExp(testFilter);
filterFunction = (t) => re.hasMatch(t.description);
} else if (testFilter is RegExp) {
filterFunction = (t) => testFilter.hasMatch(t.description);
} else if (testFilter is Function) {
filterFunction = testFilter;
}
_testCases.retainWhere(filterFunction);
}
/** Runs all queued tests, one at a time. */
void runTests() {
_ensureInitialized(false);
_currentTestCaseIndex = 0;
_config.onStart();
runAsync(() {
_nextBatch();
});
}
/**
* Run [tryBody] guarded in a try-catch block. If an exception is thrown, it is
* passed to the corresponding test.
*
* The value returned by [tryBody] (if any) is returned by [guardAsync].
*/
guardAsync(Function tryBody) {
return _guardAsync(tryBody, null, currentTestCase);
}
_guardAsync(Function tryBody, Function finallyBody, TestCase testCase) {
assert(testCase != null);
try {
return tryBody();
} catch (e, trace) {
_registerException(testCase, e, trace);
} finally {
if (finallyBody != null) finallyBody();
}
}
/**
* Registers that an exception was caught for the current test.
*/
void registerException(e, [trace]) {
_registerException(currentTestCase, e, trace);
}
/**
* Registers that an exception was caught for the current test.
*/
void _registerException(TestCase testCase, e, [trace]) {
trace = trace == null ? '' : trace.toString();
String message = (e is TestFailure) ? e.message : 'Caught $e';
if (testCase.result == null) {
testCase.fail(message, trace);
} else {
testCase.error(message, trace);
}
}
/**
* Runs a batch of tests, yielding whenever an asynchronous test starts
* running. Tests will resume executing when such asynchronous test calls
* [done] or if it fails with an exception.
*/
void _nextBatch() {
while (true) {
if (_currentTestCaseIndex >= testCases.length) {
_completeTests();
break;
}
final testCase = testCases[_currentTestCaseIndex];
var f = _guardAsync(testCase._run, null, testCase);
if (f != null) {
f.whenComplete(() {
_nextTestCase(); // Schedule the next test.
});
break;
}
_currentTestCaseIndex++;
}
}
/** Publish results on the page and notify controller. */
void _completeTests() {
if (!_initialized) return;
int passed = 0;
int failed = 0;
int errors = 0;
for (TestCase t in testCases) {
switch (t.result) {
case PASS: passed++; break;
case FAIL: failed++; break;
case ERROR: errors++; break;
}
}
_config.onSummary(passed, failed, errors, testCases, _uncaughtErrorMessage);
_config.onDone(passed > 0 && failed == 0 && errors == 0 &&
_uncaughtErrorMessage == null);
_initialized = false;
}
String _fullSpec(String spec) {
var group = '${_currentContext.fullName}';
if (spec == null) return group;
return group != '' ? '$group$groupSep$spec' : spec;
}
/**
* Lazily initializes the test library if not already initialized.
*/
void ensureInitialized() {
_ensureInitialized(true);
}
void _ensureInitialized(bool configAutoStart) {
if (_initialized) {
return;
}
_initialized = true;
// Hook our async guard into the matcher library.
wrapAsync = (f, [id]) => expectAsync1(f, id: id);
_uncaughtErrorMessage = null;
unittestConfiguration.onInit();
if (configAutoStart && _config.autoStart) {
// Immediately queue the suite up. It will run after a timeout (i.e. after
// main() has returned).
runAsync(runTests);
}
}
/** Select a solo test by ID. */
void setSoloTest(int id) =>
_testCases.retainWhere((t) => t.id == id);
/** Enable/disable a test by ID. */
void _setTestEnabledState(int testId, bool state) {
// Try fast path first.
if (testCases.length > testId && testCases[testId].id == testId) {
testCases[testId].enabled = state;
} else {
for (var i = 0; i < testCases.length; i++) {
if (testCases[i].id == testId) {
testCases[i].enabled = state;
break;
}
}
}
}
/** Enable a test by ID. */
void enableTest(int testId) => _setTestEnabledState(testId, true);
/** Disable a test by ID. */
void disableTest(int testId) => _setTestEnabledState(testId, false);
/** Signature for a test function. */
typedef dynamic TestFunction();
// Stack formatting utility. Strips extraneous content from a stack trace.
// Stack frame lines are parsed with a regexp, which has been tested
// in Chrome, Firefox and the VM. If a line fails to be parsed it is
// included in the output to be conservative.
//
// The output stack consists of everything after the call to TestCase._run.
// If we see an 'expect' in the frame we will prune everything above that
// as well.
final _frameRegExp = new RegExp(
r'^\s*' // Skip leading spaces.
r'(?:' // Group of choices for the prefix.
r'(?:#\d+\s*)|' // Skip VM's #<frameNumber>.
r'(?:at )|' // Skip Firefox's 'at '.
r'(?:))' // Other environments have nothing here.
r'(.+)' // Extract the function/method.
r'\s*[@\(]' // Skip space and @ or (.
r'(' // This group of choices is for the source file.
r'(?:.+:\/\/.+\/[^:]*)|' // Handle file:// or http:// URLs.
r'(?:dart:[^:]*)|' // Handle dart:<lib>.
r'(?:package:[^:]*)' // Handle package:<path>
r'):([:\d]+)[\)]?$'); // Get the line number and optional column number.
String _formatStack(stack) {
var lines;
if (stack is StackTrace) {
lines = stack.toString().split('\n');
} else if (stack is String) {
lines = stack.split('\n');
} else {
return stack.toString();
}
// Calculate the max width of first column so we can
// pad to align the second columns.
int padding = lines.fold(0, (n, line) {
var match = _frameRegExp.firstMatch(line);
if (match == null) return n;
return max(n, match[1].length + 1);
});
// We remove all entries that have a location in unittest.
// We strip out anything before _nextBatch too.
var sb = new StringBuffer();
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line == '') continue;
var match = _frameRegExp.firstMatch(line);
if (match == null) {
sb.write(line);
sb.write('\n');
} else {
var member = match[1];
var location = match[2];
var position = match[3];
if (member.indexOf('TestCase._runTest') >= 0) {
// Don't include anything after this.
break;
} else if (member.indexOf('expect') >= 0) {
// It looks like this was an expect() failure;
// drop all the frames up to here.
sb.clear();
} else {
sb.write(member);
// Pad second column to a fixed position.
for (var j = 0; j <= padding - member.length; j++) {
sb.write(' ');
}
sb.write(location);
sb.write(' ');
sb.write(position);
sb.write('\n');
}
}
}
return sb.toString();
}