blob: ed55aff16ff4eefe459e1dd10240f9703a9432d1 [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.
library command_line_config;
import 'dart:io';
import 'dart:math' as math;
import 'package:pathos/path.dart' as path;
import 'package:unittest/unittest.dart';
import '../lib/src/utils.dart';
/// Pretty Unicode characters!
final _checkbox = getSpecial('\u2713', 'PASS');
final _ballotX = getSpecial('\u2717', 'FAIL');
final _lambda = getSpecial('\u03bb', '<fn>');
final _green = getSpecial('\u001b[32m');
final _red = getSpecial('\u001b[31m');
final _magenta = getSpecial('\u001b[35m');
final _none = getSpecial('\u001b[0m');
/// A custom unittest configuration for running the pub tests from the
/// command-line and generating human-friendly output.
class CommandLineConfiguration extends Configuration {
void onInit() {
// Do nothing. Overridden to prevent the base class from printing.
}
void onTestResult(TestCase testCase) {
var result;
switch (testCase.result) {
case PASS: result = '$_green$_checkbox$_none'; break;
case FAIL: result = '$_red$_ballotX$_none'; break;
case ERROR: result = '$_magenta?$_none'; break;
}
print('$result ${testCase.description}');
if (testCase.message != '') {
print(_indent(testCase.message));
}
_printStackTrace(testCase.stackTrace);
super.onTestResult(testCase);
}
void onSummary(int passed, int failed, int errors, List<TestCase> results,
String uncaughtError) {
var success = false;
if (uncaughtError != null) {
print('Top-level uncaught error: $uncaughtError');
} else if (errors != 0) {
print('${_green}$passed${_none} passed, ${_red}$failed${_none} failed, '
'${_magenta}$errors${_none} errors.');
} else if (failed != 0) {
print('${_green}$passed${_none} passed, ${_red}$failed${_none} '
'failed.');
} else if (passed == 0) {
print('No tests found.');
} else {
print('All ${_green}$passed${_none} tests passed!');
success = true;
}
}
void onDone(bool success) {
if (!success) exit(1);
}
void _printStackTrace(String stackTrace) {
if (stackTrace == null || stackTrace == '') return;
print('');
// Parse out each stack entry.
var stack = [];
for (var line in stackTrace.split('\n')) {
if (line.trim() == '') continue;
stack.add(new _StackFrame(line));
}
if (stack.length == 0) return;
// Figure out the longest path so we know how much to pad.
int longest = stack.map((frame) => frame.location.length).reduce(math.max);
// Print out the stack trace nicely formatted.
for (var frame in stack) {
print(' ${_padLeft(frame.location, longest)} ${frame.member}');
}
print('');
}
String _padLeft(String string, int length) {
if (string.length >= length) return string;
var result = new StringBuffer();
result.write(string);
for (var i = 0; i < length - string.length; i++) {
result.write(' ');
}
return result.toString();
}
String _indent(String str) {
// TODO(nweiz): Use this simpler code once issue 2980 is fixed.
// return str.replaceAll(new RegExp("^", multiLine: true), " ");
return str.split("\n").map((line) => " $line").join("\n");
}
}
class _StackFrame {
static final fileRegExp = new RegExp(
r'#\d+\s+(.*) \(file://(/.+):(\d+):(\d+)\)');
static final coreRegExp = new RegExp(r'#\d+\s+(.*) \((.+):(\d+):(\d+)\)');
/// If `true`, then this stack frame is for a library built into Dart and
/// not a regular file path.
final bool isCore;
/// The path to the library or the library name if a core library.
String library;
/// The line number.
final String line;
/// The column number.
final String column;
/// The member where the error occurred.
final String member;
/// A formatted description of the code location.
String get location => '$library $line:$column';
_StackFrame._(this.isCore, this.library, this.line, this.column, this.member);
factory _StackFrame(String text) {
var match = fileRegExp.firstMatch(text);
var isCore = false;
if (match == null) {
match = coreRegExp.firstMatch(text);
if (match == null) {
throw new FormatException("Couldn't parse stack trace line '$text'.");
}
isCore = true;
}
var library = match[2];
if (!isCore) {
// Make the library path relative to the entrypoint.
library = path.relative(library);
}
var member = match[1].replaceAll("<anonymous closure>", _lambda);
return new _StackFrame._(isCore, library, match[3], match[4], member);
}
}