Updated testrunner:

- works with latest SDK
- understand and works with pub and pubspecs
- tested on Linux, Mac and Windows
- has its own tests

There is still as issue on Windows, where using Process.start to run a .bat file is not terminating until the timeout kills the cmd.exe process. My suspicion is that this is am issue with Process, not testrunner. I have contacted whesse et al about this.

Review URL: https://codereview.chromium.org//14247033

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@21969 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
gram@google.com 2013-04-24 18:39:50 +00:00
parent fb6ea9481a
commit eccaec8f3b
24 changed files with 1417 additions and 515 deletions

View file

@ -21,6 +21,8 @@ ArgParser getOptionParser() {
/** A simple HTTP server. Currently handles serving static files. */
class HttpTestServer {
HttpServer server;
List<Function> matchers = [];
List<Function> handlers = [];
/** If set, serve up static files from this directory. */
String staticFileDirectory;
@ -52,21 +54,20 @@ class HttpTestServer {
};
HttpTestServer(int port, this.staticFileDirectory) {
server = new HttpServer();
try {
server.listen("127.0.0.1", port);
HttpServer.bind("127.0.0.1", port).then((s) {
server = s;
print('Server listening on port $port');
} catch (e) {
print('Server listen on port $port failed');
throw e;
}
server.onError = (e) {
};
server.defaultRequestHandler =
(HttpRequest request, HttpResponse response) {
server.listen((HttpRequest request) {
for (var i = 0; i < matchers.length; i++) {
if (matchers[i](request)) {
handlers[i](request);
return;
}
}
HttpResponse response = request.response;
try {
if (staticFileDirectory != null) {
String fname = request.path;
String fname = request.uri.path;
String path = '$staticFileDirectory$fname';
File f = new File(path);
if (f.existsSync()) {
@ -78,31 +79,30 @@ class HttpTestServer {
new ContentType(ct.substring(0, idx),
ct.substring(idx + 1));
}
f.openInputStream().pipe(response.outputStream);
response.addStream(f.openRead()).then((_) => response.close());
} else {
response.statusCode = HttpStatus.NOT_FOUND;
response.reasonPhrase = '$path does not exist';
response.outputStream.close();
response.close();
}
}
} catch(e,s) {
response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
response.reasonPhrase = "$e";
response.outputStream.writeString(s.toString());
response.outputStream.close();
response.write(s);
response.close();
}
};
});
});
}
void addHandler(Function matcher, handler) {
if (handler is Function) {
server.addRequestHandler(matcher, handler);
} else {
server.addRequestHandler(matcher, handler.onRequest);
}
void addHandler(Function matcher, Function handler) {
matchers.add(matcher);
handlers.add(handler);
}
void close() {
server.close();
}
}

View file

@ -124,177 +124,223 @@ complete() {
notifyDone(failCount > 0 ? -1 : 0);
}
/*
* Run an external process [cmd] with command line arguments [args].
* [timeout] can be used to forcefully terminate the process after
* some number of seconds. This is used by runCommand and startProcess.
* If [procId] is non-zero (i.e. called from startProcess) then a reference
* to the [Process] will be put in a map with key [procId]; in this case
* the process can be terminated later by calling [stopProcess] and
* passing in the [procId].
* [outputMonitor] is an optional function that will be called back with each
* line of output from the process.
* Returns a [Future] for when the process terminates.
*/
Future _processHelper(String command, List<String> args,
List stdout, List stderr,
int timeout, int procId, Function outputMonitor, bool raw) {
var timer = null;
return Process.start(command, args).then((process) {
timer = new Timer(new Duration(seconds: timeout), () {
timer = null;
process.kill();
});
if (raw) {
process.stdout.listen((c) { stdout.addAll(c); });
} else {
_pipeStream(process.stdout, stdout, outputMonitor);
}
_pipeStream(process.stderr, stderr, outputMonitor);
return process.exitCode;
}).then((exitCode) {
if (timer != null) {
timer.cancel();
}
return exitCode;
})
.catchError((e) {
stderr.add("#Error starting process $command: ${e.error}");
});
}
void _pipeStream(Stream stream, List<String> destination,
Function outputMonitor) {
stream
.transform(new StringDecoder())
.transform(new LineTransformer())
.listen((String line) {
if (outputMonitor != null) {
outputMonitor(line);
}
destination.add(line);
});
}
/**
* Run an external process [cmd] with command line arguments [args].
* [timeout] can be used to forcefully terminate the process after
* some number of seconds.
* Returns a [Future] for when the process terminates.
*/
Future runCommand(String command, List<String> args,
List stdout, List stderr,
{int timeout: 300, Function outputMonitor,
bool raw: false}) {
return _processHelper(command, args, stdout, stderr,
timeout, 0, outputMonitor, raw);
}
String parseLabel(String line) {
if (line.startsWith('CONSOLE MESSAGE')) {
var idx = line.indexOf('#TEST ');
if (idx > 0) {
return line.substring(idx + 6);
}
}
return null;
}
runTextLayoutTest(testNum) {
var url = '$baseUrl?test=$testNum';
var stdout = new List();
var stderr = new List();
start = new DateTime.now();
Process.start(drt, [url]).then((process) {
// Drain stderr to not leak resources.
process.stderr.onData = process.stderr.read;
StringInputStream stdoutStringStream =
new StringInputStream(process.stdout);
stdoutStringStream.onLine = () {
if (stdoutStringStream.closed) return;
var line = stdoutStringStream.readLine();
while (null != line) {
stdout.add(line);
line = stdoutStringStream.readLine();
}
};
process.onExit = (exitCode) {
process.close();
if (stdout.length > 0 && stdout[stdout.length-1].startsWith('#EOF')) {
stdout.removeLast();
}
var done = false;
var i = 0;
var label = null;
var labelMarker = 'CONSOLE MESSAGE: #TEST ';
var contentMarker = 'layer at ';
while (i < stdout.length) {
if (label == null && stdout[i].startsWith(labelMarker)) {
label = stdout[i].substring(labelMarker.length);
if (label == 'NONEXISTENT') {
complete();
}
} else if (stdout[i].startsWith(contentMarker)) {
if (label == null) {
complete();
}
var expectedFileName =
'$sourceDir${Platform.pathSeparator}'
'${label.replaceAll("###", "_")
.replaceAll(new RegExp("[^A-Za-z0-9]"),"_")}.txt';
var expected = new File(expectedFileName);
if (regenerate) {
var ostream = expected.openOutputStream(FileMode.WRITE);
while (i < stdout.length) {
ostream.writeString(stdout[i]);
ostream.writeString('\n');
i++;
}
ostream.close();
pass(start, label);
} else if (!expected.existsSync()) {
fail(start, label, 'No expectation file');
} else {
var lines = expected.readAsLinesSync();
var actualLength = stdout.length - i;
var compareCount = min(lines.length, actualLength);
var match = true;
for (var j = 0; j < compareCount; j++) {
if (lines[j] != stdout[i + j]) {
fail(start, label, 'Expectation differs at line ${j + 1}');
match = false;
break;
}
}
if (match) {
if (lines.length != actualLength) {
fail(start, label, 'Expectation file has wrong length');
} else {
pass(start, label);
}
}
}
done = true;
break;
runCommand(drt, [url], stdout, stderr).then((e) {
if (stdout.length > 0 && stdout[stdout.length-1].startsWith('#EOF')) {
stdout.removeLast();
}
var done = false;
var i = 0;
var label = null;
var contentMarker = 'layer at ';
while (i < stdout.length) {
if (label == null && (label = parseLabel(stdout[i])) != null) {
if (label == 'NONEXISTENT') {
complete();
return;
}
i++;
} else if (stdout[i].startsWith(contentMarker)) {
if (label == null) {
complete();
return;
}
var expectedFileName =
'$sourceDir${Platform.pathSeparator}'
'${label.replaceAll("###", "_")
.replaceAll(new RegExp("[^A-Za-z0-9]"),"_")}.txt';
var expected = new File(expectedFileName);
if (regenerate) {
var osink = expected.openWrite();
while (i < stdout.length) {
osink.write(stdout[i]);
osink.write('\n');
i++;
}
osink.close();
pass(start, label);
} else if (!expected.existsSync()) {
fail(start, label, 'No expectation file');
} else {
var lines = expected.readAsLinesSync();
var actualLength = stdout.length - i;
var compareCount = min(lines.length, actualLength);
var match = true;
for (var j = 0; j < compareCount; j++) {
if (lines[j] != stdout[i + j]) {
fail(start, label, 'Expectation differs at line ${j + 1}');
match = false;
break;
}
}
if (match) {
if (lines.length != actualLength) {
fail(start, label, 'Expectation file has wrong length');
} else {
pass(start, label);
}
}
}
done = true;
break;
}
if (label != null) {
if (!done) error(start, label, 'Failed to parse output');
runTextLayoutTest(testNum + 1);
}
};
i++;
}
if (label != null) {
if (!done) error(start, label, 'Failed to parse output');
runTextLayoutTest(testNum + 1);
}
});
}
runPixelLayoutTest(int testNum) {
var url = '$baseUrl?test=$testNum';
var stdout = new List();
var stderr = new List();
start = new DateTime.now();
Process.start(drt, ["$url'-p"]).then((process) {
// Drain stderr to not leak resources.
process.stderr.onData = process.stderr.read;
ListInputStream stdoutStream = process.stdout;
stdoutStream.onData = () {
if (!stdoutStream.closed) {
var data = stdoutStream.read();
stdout.addAll(data);
runCommand(drt, ["$url'-p"], stdout, stderr, raw:true).then((exitCode) {
var contentMarker = 'Content-Length: ';
var eol = '\n'.codeUnitAt(0);
var pos = 0;
var label = null;
var done = false;
while(pos < stdout.length) {
StringBuffer sb = new StringBuffer();
while (pos < stdout.length && stdout[pos] != eol) {
sb.writeCharCode(stdout[pos++]);
}
};
stdoutStream.onError = (e) {
print(e);
};
process.onExit = (exitCode) {
stdout.addAll(process.stdout.read());
process.close();
var labelMarker = 'CONSOLE MESSAGE: #TEST ';
var contentMarker = 'Content-Length: ';
var eol = '\n'.codeUnitAt(0);
var pos = -1;
var label = null;
var done = false;
if (++pos >= stdout.length && line == '') break;
var line = sb.toString();
while(pos < stdout.length) {
var idx = stdout.indexOf(eol, ++pos);
if (idx < 0) break;
StringBuffer sb = new StringBuffer();
for (var i = pos; i < idx; i++) {
sb.writeCharCode(stdout[i]);
if (label == null && (label = parseLabel(line)) != null) {
if (label == 'NONEXISTENT') {
complete();
}
var line = sb.toString();
if (label == null && line.startsWith(labelMarker)) {
label = line.substring(labelMarker.length);
if (label == 'NONEXISTENT') {
complete();
}
} else if (line.startsWith(contentMarker)) {
if (label == null) {
complete();
}
var len = int.parse(line.substring(contentMarker.length));
pos = idx + 1;
var expectedFileName =
'$sourceDir${Platform.pathSeparator}'
'${label.replaceAll("###","_").
replaceAll(new RegExp("[^A-Za-z0-9]"),"_")}.png';
var expected = new File(expectedFileName);
if (regenerate) {
var ostream = expected.openOutputStream(FileMode.WRITE);
ostream.writeFrom(stdout, pos, len);
ostream.close();
pass(start, label);
} else if (!expected.existsSync()) {
fail(start, label, 'No expectation file');
} else if (line.startsWith(contentMarker)) {
if (label == null) {
complete();
}
var len = int.parse(line.substring(contentMarker.length));
var expectedFileName =
'$sourceDir${Platform.pathSeparator}'
'${label.replaceAll("###","_").
replaceAll(new RegExp("[^A-Za-z0-9]"),"_")}.png';
var expected = new File(expectedFileName);
if (regenerate) {
var osink = expected.openWrite();
stdout.removeRange(0, pos);
stdout.length = len;
osink.add(stdout);
osink.close();
pass(start, label);
} else if (!expected.existsSync()) {
fail(start, label, 'No expectation file');
} else {
var bytes = expected.readAsBytesSync();
if (bytes.length != len) {
fail(start, label, 'Expectation file has wrong length');
} else {
var bytes = expected.readAsBytesSync();
if (bytes.length != len) {
fail(start, label, 'Expectation file has wrong length');
} else {
var match = true;
for (var j = 0; j < len; j++) {
if (bytes[j] != stdout[pos + j]) {
fail(start, label, 'Expectation differs at byte ${j + 1}');
match = false;
break;
}
var match = true;
for (var j = 0; j < len; j++) {
if (bytes[j] != stdout[pos + j]) {
fail(start, label, 'Expectation differs at byte ${j + 1}');
match = false;
break;
}
if (match) pass(start, label);
}
if (match) pass(start, label);
}
done = true;
break;
}
pos = idx;
done = true;
break;
}
if (label != null) {
if (!done) error(start, label, 'Failed to parse output');
runPixelLayoutTest(testNum + 1);
}
};
}
if (label != null) {
if (!done) error(start, label, 'Failed to parse output');
runPixelLayoutTest(testNum + 1);
}
});
}

View file

@ -2,15 +2,18 @@
// 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.
part of layout_test;
// The filters must be set by the caller that #sources this file.
List includeFilters;
// TODO(gram): dart2js is not handling 'part of' properly yet; when it
// does uncomment this.
//part of layout_test;
List excludeFilters;
/**
* A special marker string used to separate group names and
* identify non-debug output.
*/
final marker = '###';
class LayoutTestConfiguration extends unittest.Configuration {
get autoStart => false;
void onTestResult(unittest.TestCase testCase) {
@ -19,7 +22,7 @@ class LayoutTestConfiguration extends unittest.Configuration {
}
filterTest(t) {
var name = t.description.replaceAll("###", " ");
var name = t.description.replaceAll(marker, " ");
if (includeFilters.length > 0) {
for (var f in includeFilters) {
if (name.indexOf(f) >= 0) return true;
@ -36,8 +39,8 @@ filterTest(t) {
}
runTests(testMain) {
unittest.groupSep = '###';
unittest.configure(new LayoutTestConfiguration());
unittest.groupSep = marker;
unittest.unittestConfiguration = new LayoutTestConfiguration();
// Create the set of test cases.
unittest.group('', testMain);

View file

@ -25,6 +25,9 @@ ArgParser getOptionParser() {
parser.addFlag('checked', defaultsTo: false,
help: 'Run tests in checked mode.');
parser.addFlag('sort', defaultsTo: false,
help: 'Sort test files before running.');
parser.addFlag('layout-text', defaultsTo: false,
help: 'Run text layout tests.');
@ -78,8 +81,8 @@ ArgParser getOptionParser() {
parser.addFlag('list-groups', defaultsTo: false,
help: 'List test groups only, do not run tests.');
parser.addFlag('keep-files', defaultsTo: false,
help: 'Keep the generated files in the temporary directory.');
parser.addFlag('clean-files', defaultsTo: false,
help: 'Remove the generated files from the temporary directory.');
parser.addFlag('list-options', defaultsTo: false,
help: 'Print non-default option settings, usable as a test.config.');
@ -92,7 +95,7 @@ ArgParser getOptionParser() {
defaultsTo: false);
parser.addFlag('stop-on-failure', defaultsTo: false,
help: 'Stop execution upon first failure.');
help: 'Stop execution after first file with failures.');
parser.addFlag('isolate', defaultsTo: false,
help: 'Runs each test in a separate isolate.');
@ -101,18 +104,14 @@ ArgParser getOptionParser() {
parser.addOption('dartsdk', help: 'Path to dart SDK.');
// The defaults here should be the name of the executable, with
// the assumption that it is available on the PATH.
parser.addOption('dart2js', help: 'Path to dart2js executable.',
defaultsTo: 'dart2js');
parser.addOption('dart', help: 'Path to dart executable.',
defaultsTo: 'dart');
parser.addOption('drt', help: 'Path to DumpRenderTree executable.',
defaultsTo: 'drt');
var tmp;
if (Platform.operatingSystem == 'windows') {
tmp = "c:\\tmp\\test";
} else {
tmp = "/tmp/test";
}
parser.addOption('tempdir', help: 'Directory to store temp files.',
defaultsTo: '${Platform.pathSeparator}tmp'
'${Platform.pathSeparator}testrunner');
defaultsTo: tmp);
parser.addOption('test-file-pattern',
help: 'A regular expression that test file names must match '
@ -147,8 +146,6 @@ ArgParser getOptionParser() {
parser.addOption('root',
help: 'Root directory for HTTP server for static files');
parser.addOption('unittest', help: '#import path for unit test library.');
parser.addOption('pipeline',
help: 'Pipeline script to use to run each test file.',
defaultsTo: 'run_pipeline.dart');
@ -157,34 +154,34 @@ ArgParser getOptionParser() {
}
/** Print a value option, quoting it if it has embedded spaces. */
_printValueOption(String name, value, OutputStream stream) {
_printValueOption(String name, value, IOSink dest) {
if (value.indexOf(' ') >= 0) {
stream.writeString("--$name='$value'\n");
dest.write("--$name='$value'\n");
} else {
stream.writeString("--$name=$value\n");
dest.write("--$name=$value\n");
}
}
/** Print the current option values. */
printOptions(ArgParser parser, ArgResults arguments,
bool includeDefaults, OutputStream stream) {
if (stream == null) return;
bool includeDefaults, IOSink dest) {
if (dest == null) return;
for (var name in arguments.options) {
if (!name.startsWith('list-')) {
var value = arguments[name];
var defaultValue = parser.getDefault(name);
if (value is bool) {
if (includeDefaults || (value != defaultValue)) {
stream.writeString('--${value ? "" : "no-"}$name\n');
dest.write('--${value ? "" : "no-"}$name\n');
}
} else if (value is List) {
if (value.length > 0) {
for (var v in value) {
_printValueOption(name, v, stream);
_printValueOption(name, v, dest);
}
}
} else if (value != null && (includeDefaults || value != defaultValue)) {
_printValueOption(name, value, stream);
_printValueOption(name, value, dest);
}
}
}
@ -206,6 +203,7 @@ ArgResults loadConfiguration(optionsParser) {
// multi-valued they will take precedence over the ones in test.config.
var commandLineArgs = new Options().arguments;
var cfgarg = '--configfile';
var cfgarge = '--configfile=';
for (var i = 0; i < commandLineArgs.length; i++) {
if (commandLineArgs[i].startsWith(cfgarg)) {
if (commandLineArgs[i] == cfgarg) {
@ -214,9 +212,9 @@ ArgResults loadConfiguration(optionsParser) {
}
options.addAll(getFileContents(commandLineArgs[++i], true).
where((e) => e.trim().length > 0 && e[0] != '#'));
} else if (commandLineArgs[i].startsWith('$cfgarg=')) {
} else if (commandLineArgs[i].startsWith(cfgarge)) {
options.addAll(
getFileContents(commandLineArgs[i].substring(cfgarg.length), true).
getFileContents(commandLineArgs[i].substring(cfgarge.length), true).
where((e) => e.trim().length > 0 && e[0] != '#'));
} else {
throw new Exception('Missing argument to $cfgarg');
@ -247,10 +245,6 @@ bool isSane(ArgResults config) {
print('Missing required option --runtime');
return false;
}
if (config['unittest'] == null) {
print('Missing required option --unittest');
return false;
}
if (config['include'].length > 0 &&
config['exclude'].length > 0) {
print('--include and --exclude are mutually exclusive.');

View file

@ -4,7 +4,7 @@
part of pipeline;
List stdout, stderr, log;
List log;
var replyPort;
int _procId = 1;
Map _procs = {};
@ -14,7 +14,7 @@ Map _procs = {};
* [tmpDir] directory, with name [basis], but with any extension
* stripped and replaced by [suffix].
*/
String createTempName(String tmpDir, String basis, String suffix) {
String createTempName(String tmpDir, String basis, [String suffix='']) {
var p = new Path(basis);
return '$tmpDir${Platform.pathSeparator}'
'${p.filenameWithoutExtension}${suffix}';
@ -41,9 +41,7 @@ String getDirectory(String file) =>
/** Create a file [fileName] and populate it with [contents]. */
void writeFile(String fileName, String contents) {
var file = new File(fileName);
var ostream = file.openOutputStream(FileMode.WRITE);
ostream.writeString(contents);
ostream.close();
file.writeAsStringSync(contents);
}
/*
@ -59,62 +57,62 @@ void writeFile(String fileName, String contents) {
* Returns a [Future] for when the process terminates.
*/
Future _processHelper(String command, List<String> args,
[int timeout = 300, int procId = 0, Function outputMonitor]) {
var completer = procId == 0 ? new Completer() : null;
log.add('Running $command ${args.join(" ")}');
List stdout, List stderr,
[int timeout = 30, int procId = 0, Function outputMonitor]) {
var timer = null;
var stdoutHandler, stderrHandler;
var processFuture = Process.start(command, args);
processFuture.then((process) {
_procs[procId] = process;
if (Platform.operatingSystem == 'windows' && command.endsWith('.bat')) {
var oldArgs = args;
args = new List();
args.add('/c');
// TODO(gram): We may need some escaping here if any of the
// components contain spaces.
args.add("$command ${oldArgs.join(' ')}");
command='cmd.exe';
}
log.add('Running $command ${args.join(" ")}');
return Process.start(command, args)
.then((process) {
_procs[procId.toString()] = process;
timer = new Timer(new Duration(seconds: timeout), () {
timer = null;
process.kill();
});
var stdoutFuture = _pipeStream(process.stdout, stdout, outputMonitor);
var stderrFuture = _pipeStream(process.stderr, stderr, outputMonitor);
process.onExit = (exitCode) {
if (timer != null) {
timer.cancel();
}
process.close();
if (completer != null) {
completer.complete(exitCode);
}
};
_pipeStream(process.stdout, stdout, outputMonitor);
_pipeStream(process.stderr, stderr, outputMonitor);
});
processFuture.handleException((e) {
stderr.add("Error starting process:");
stderr.add(" Command: $command");
stderr.add(" Error: $e");
completePipeline(-1);
return true;
});
return completer.future;
timer = new Timer(new Duration(seconds: timeout), () {
timer = null;
process.kill();
});
return Future.wait([process.exitCode, stdoutFuture, stderrFuture])
.then((values) {
if (timer != null) {
timer.cancel();
}
return values[0];
});
})
.catchError((e) {
stderr.add("Error starting process:");
stderr.add(" Command: $command");
stderr.add(" Error: ${e.toString()}");
return new Future.value(-1);
});
}
void _pipeStream(InputStream stream, List<String> destination,
Future _pipeStream(Stream stream, List<String> destination,
Function outputMonitor) {
var source = new StringInputStream(stream);
source.onLine = () {
if (source.available() == 0) return;
var line = source.readLine();
while (null != line) {
return stream
.transform(new StringDecoder())
.transform(new LineTransformer())
.listen((String line) {
if (config["immediate"] && line.startsWith('###')) {
// TODO - when we dump the list later skip '###' messages if immediate.
print(line.substring(3));
}
if (outputMonitor != null) {
outputMonitor(line);
}
destination.add(line);
line = source.readLine();
}
};
})
.asFuture();
}
/**
@ -124,26 +122,32 @@ void _pipeStream(InputStream stream, List<String> destination,
* Returns a [Future] for when the process terminates.
*/
Future runCommand(String command, List<String> args,
[int timeout = 300, Function outputMonitor]) {
return _processHelper(command, args, timeout, outputMonitor:outputMonitor);
List stdout, List stderr,
[int timeout = 30, Function outputMonitor]) {
return _processHelper(command, args, stdout, stderr,
timeout, 0, outputMonitor);
}
/**
* Start an external process [cmd] with command line arguments [args].
* Returns an ID by which it can later be stopped.
*/
int startProcess(String command, List<String> args, [Function outputMonitor]) {
int startProcess(String command, List<String> args, List stdout, List stderr,
[Function outputMonitor]) {
int id = _procId++;
_processHelper(command, args, 3000, id,
outputMonitor:outputMonitor).then((e) {
_procs.remove(id);
});
var f = _processHelper(command, args, stdout, stderr, 3000, id,
outputMonitor);
if (f != null) {
f.then((e) {
_procs.remove(id.toString());
});
}
return id;
}
/** Checks if a process is still running. */
bool isProcessRunning(int id) {
return _procs.containsKey(id);
return _procs.containsKey(id.toString());
}
/**
@ -151,15 +155,16 @@ bool isProcessRunning(int id) {
* given the id string.
*/
void stopProcess(int id) {
if (_procs.containsKey(id)) {
Process p = _procs.remove(id);
var sid = id.toString();
if (_procs.containsKey(sid)) {
Process p = _procs.remove(sid);
p.kill();
}
}
/** Delete a file named [fname] if it exists. */
bool cleanup(String fname) {
if (fname != null && !config['keep-files']) {
if (fname != null && config['clean-files']) {
var f = new File(fname);
try {
if (f.existsSync()) {
@ -173,6 +178,22 @@ bool cleanup(String fname) {
return true;
}
/** Delete a directory named [dname] if it exists. */
bool cleanupDir(String dname) {
if (dname != null && config['clean-files']) {
var d = new Directory(dname);
try {
if (d.existsSync()) {
logMessage('Removing $dname');
d.deleteSync(recursive: true);
}
} catch (e) {
return false;
}
}
return true;
}
initPipeline(port) {
replyPort = port;
stdout = new List();
@ -180,9 +201,12 @@ initPipeline(port) {
log = new List();
}
void completePipeline([exitCode = 0]) {
void completePipeline(List stdout, List stderr, [exitCode = 0]) {
replyPort.send([stdout, stderr, log, exitCode]);
}
/** Utility function to log diagnostic messages. */
void logMessage(msg) => log.add(msg);
/** Turn file paths into standard form with forward slashes. */
String normalizePath(String p) => (new Path(p)).toString();

View file

@ -1,4 +1,3 @@
name: testrunner
dependencies:
args: { sdk: args }
args: any

View file

@ -4,8 +4,10 @@
/** The default pipeline code for running a test file. */
library pipeline;
import 'dart:isolate';
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'dart:math';
part 'pipeline_utils.dart';
/**
@ -49,16 +51,26 @@ String serverPath;
/** Number of attempts we will make to start the HTTP server. */
const int MAX_SERVER_TRIES = 10;
/** Pipeline output. */
List stdout;
/** Pipeline errors. */
List stderr;
/** Directory where test wrappers are created. */
String tmpDir;
void main() {
port.receive((cfg, replyPort) {
config = cfg;
stdout = new List();
stderr = new List();
initPipeline(replyPort);
startHTTPServerStage();
});
}
/** Initial pipeline stage - starts the HTTP server, if appropriate. */
startHTTPServerStage() {
if (config["server"]) {
serverPath = config["testfile"];
@ -86,7 +98,8 @@ startHTTPServerStage() {
serverPort = int.parse(config["port"]);
// Start the HTTP server.
serverId = startProcess(config["dart"],
[ serverPath, '--port=$serverPort', '--root=$serverRoot']);
[ serverPath, '--port=$serverPort', '--root=$serverRoot'],
stdout, stderr);
}
}
}
@ -100,6 +113,7 @@ void tryStartHTTPServer(Random r, int remainingAttempts) {
'--root=$serverRoot');
serverId = startProcess(config["dart"],
[ serverPath, '--port=$serverPort', '--root=$serverRoot'],
stdout, stderr,
(line) {
if (line.startsWith('Server listening')) {
wrapStage();
@ -115,15 +129,9 @@ void tryStartHTTPServer(Random r, int remainingAttempts) {
/** Initial pipeline stage - generates Dart and HTML wrapper files. */
wrapStage() {
var tmpDir = config["tempdir"];
tmpDir = config["targetDir"];
var testFile = config["testfile"];
// Make sure the temp dir exists.
var d = new Directory(tmpDir);
if (!d.existsSync()) {
d.createSync();
}
// Generate names for the generated wrapper files.
tempDartFile = createTempName(tmpDir, testFile, '.dart');
if (config["runtime"] != 'vm') {
@ -146,50 +154,52 @@ wrapStage() {
if (config["layout"]) {
directives = '''
import 'dart:uri';
import 'dart:io';
import 'dart:math';
part '${config["runnerDir"]}/layout_test_controller.dart';
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:uri';
part '${normalizePath('${config["runnerDir"]}/layout_test_controller.dart')}';
''';
extras = '''
sourceDir = '${config["expectedDirectory"]}';
baseUrl = 'file://$tempHtmlFile';
tprint = (msg) => print('###\$msg');
notifyDone = (e) => exit(e);
baseUrl = 'file://${normalizePath('$tempHtmlFile')}';
tprint = (msg) => print('###\$msg');
notifyDone = (e) => exit(e);
''';
} else if (config["runtime"] == "vm") {
directives = '''
import 'dart:io';
import 'dart:isolate';
import '${config["unittest"]}' as unittest;
import '${config["testfile"]}' as test;
part '${config["runnerDir"]}/standard_test_runner.dart';
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'package:unittest/unittest.dart';
import '${normalizePath('${config["testfile"]}')}' as test;
part '${normalizePath('${config["runnerDir"]}/standard_test_runner.dart')}';
''';
extras = '''
includeFilters = ${config["include"]};
excludeFilters = ${config["exclude"]};
tprint = (msg) => print('###\$msg');
notifyDone = (e) {};
unittest.testState["port"] = $serverPort;
includeFilters = ${config["include"]};
excludeFilters = ${config["exclude"]};
tprint = (msg) => print('###\$msg');
notifyDone = (e) { exit(e); };
testState["port"] = $serverPort;
''';
} else {
directives = '''
import 'dart:html';
import 'dart:isolate';
import '${config["unittest"]}' as unittest;
import '${config["testfile"]}' as test;
part '${config["runnerDir"]}/standard_test_runner.dart';
import 'dart:async';
import 'dart:html';
import 'dart:isolate';
import 'package:unittest/unittest.dart';
import '${normalizePath('${config["testfile"]}')}' as test;
part '${normalizePath('${config["runnerDir"]}/standard_test_runner.dart')}';
''';
extras = '''
includeFilters = ${config["include"]};
excludeFilters = ${config["exclude"]};
tprint = (msg) => query('#console').addText('###\$msg\\n');
notifyDone = (e) => window.postMessage('done', '*');
unittest.testState["port"] = $serverPort;
includeFilters = ${config["include"]};
excludeFilters = ${config["exclude"]};
tprint = (msg) => query('#console').appendText('###\$msg\\n');
notifyDone = (e) => window.postMessage('done', '*');
testState["port"] = $serverPort;
''';
}
var action = 'process(test.main, unittest.runTests)';
var action = 'process(test.main, runTests)';
if (config["layout-text"]) {
action = 'runTextLayoutTests()';
} else if (config["layout-pixel"]) {
@ -204,44 +214,44 @@ wrapStage() {
logMessage('Creating $tempDartFile');
writeFile(tempDartFile, '''
library test_controller;
$directives
library test_controller;
$directives
main() {
immediate = ${config["immediate"]};
includeTime = ${config["time"]};
passFormat = '${config["pass-format"]}';
failFormat = '${config["fail-format"]}';
errorFormat = '${config["error-format"]}';
listFormat = '${config["list-format"]}';
regenerate = ${config["regenerate"]};
summarize = ${config["summary"]};
testfile = '$testFile';
drt = '${config["drt"]}';
$extras
$action;
}
main() {
immediate = ${config["immediate"]};
includeTime = ${config["time"]};
passFormat = '${config["pass-format"]}';
failFormat = '${config["fail-format"]}';
errorFormat = '${config["error-format"]}';
listFormat = '${config["list-format"]}';
regenerate = ${config["regenerate"]};
summarize = ${config["summary"]};
testfile = '${testFile.replaceAll("\\","\\\\")}';
drt = '${config["drt"].replaceAll("\\","\\\\")}';
$extras
$action;
}
''');
// Create the child wrapper for layout tests.
if (config["layout"]) {
logMessage('Creating $tempChildDartFile');
writeFile(tempChildDartFile, '''
library layout_test;
import 'dart:math';
import 'dart:isolate';
import 'dart:html';
import 'dart:uri';
import '${config["unittest"]}' as 'unittest' ;
import '$testFile', prefix: 'test' ;
part '${config["runnerDir"]}/layout_test_runner.dart';
library layout_test;
import 'dart:math';
import 'dart:isolate';
import 'dart:html';
import 'dart:uri';
import 'package:unittest/unittest.dart' as unittest;
import '${normalizePath('$testFile')}' as test;
part '${normalizePath('${config["runnerDir"]}/layout_test_runner.dart')}';
main() {
includeFilters = ${config["include"]};
excludeFilters = ${config["exclude"]};
unittest.testState["port"] = $serverPort;
runTests(test.main);
}
main() {
includeFilters = ${config["include"]};
excludeFilters = ${config["exclude"]};
unittest.testState["port"] = $serverPort;
runTests(test.main);
}
''');
}
@ -258,7 +268,7 @@ wrapStage() {
sourceFile = tempDartFile;
scriptFile = isJavascript ? tempJsFile : tempDartFile;
bodyElements = '<div id="container"></div><pre id="console"></pre>';
runAsText = "window.testRunner.dumpAsText();";
runAsText = "testRunner.dumpAsText();";
}
scriptType = isJavascript ? 'text/javascript' : 'application/dart';
@ -272,13 +282,14 @@ wrapStage() {
<title>$testFile</title>
<link rel="stylesheet" href="${config["runnerDir"]}/testrunner.css">
<script type='text/javascript'>
if (window.testRunner) {
var testRunner = window.testRunner || window.layoutTestController;
if (testRunner) {
function handleMessage(m) {
if (m.data == 'done') {
window.testRunner.notifyDone();
testRunner.notifyDone();
}
}
window.testRunner.waitUntilDone();
testRunner.waitUntilDone();
$runAsText
window.addEventListener("message", handleMessage, false);
}
@ -291,9 +302,6 @@ wrapStage() {
$bodyElements
<script type='$scriptType' src='$scriptFile'></script>
</script>
<script
src="http://dart.googlecode.com/svn/branches/bleeding_edge/dart/client/dart.js">
</script>
</body>
</html>
''');
@ -305,14 +313,15 @@ src="http://dart.googlecode.com/svn/branches/bleeding_edge/dart/client/dart.js">
/** Second stage of pipeline - compiles Dart to Javascript if needed. */
compileStage(isJavascript) {
if (isJavascript) { // Compile the Dart file.
var cmd = config["dart2js"];
var input = sourceFile.replaceAll('/', Platform.pathSeparator);
var output = scriptFile.replaceAll('/', Platform.pathSeparator);
if (config["checked"]) {
runCommand(config["dart2js"],
[ '--enable_checked_mode', '--out=$scriptFile', '$sourceFile' ]).
then(runTestStage);
runCommand(cmd, [ '-c', '-o$output', '$input' ], stdout, stderr)
.then(runTestStage);
} else {
runCommand(config["dart2js"],
[ '--out=$scriptFile', '$sourceFile' ]).
then(runTestStage);
runCommand(cmd, [ '-o$output', '$input' ], stdout, stderr)
.then(runTestStage);
}
} else {
runTestStage(0);
@ -334,7 +343,7 @@ runTestStage(_) {
cmd = config["drt"];
args = [ '--no-timeout', tempHtmlFile ];
}
runCommand(cmd, args, config["timeout"]).then(cleanupStage);
runCommand(cmd, args, stdout, stderr, config["timeout"]).then(cleanupStage);
}
/**
@ -346,12 +355,15 @@ cleanupStage(exitcode) {
stopProcess(serverId);
}
if (!config["keep-files"]) { // Remove the temporary files.
if (config["clean-files"]) { // Remove the temporary files.
cleanup(tempDartFile);
cleanup(tempHtmlFile);
cleanup(tempJsFile);
cleanup(tempChildDartFile);
cleanup(tempChildJsFile);
cleanup(createTempName(tmpDir, "pubspec", "yaml"));
cleanup(createTempName(tmpDir, "pubspec", "lock"));
cleanupDir(createTempName(tmpDir, "packages"));
}
completePipeline(exitcode);
completePipeline(stdout, stderr, exitcode);
}

View file

@ -2,9 +2,7 @@
// 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.
// TODO(gram): dart2js is not handling 'part of' properly yet; when it does
// uncomment this.
//part of test_controller;
part of test_controller;
/** Path to DRT executable. */
String drt;
@ -40,6 +38,12 @@ Function notifyDone;
/** The action function to use. */
Function action;
/**
* A special marker string used to separate group names and
* identify non-debug output.
*/
final marker = '###';
class Macros {
static const String testTime = '<TIME>';
static const String testfile = '<FILENAME>';
@ -49,10 +53,12 @@ class Macros {
static const String testStacktrace = '<STACK>';
}
class TestRunnerConfiguration extends unittest.Configuration {
class TestRunnerConfiguration extends Configuration {
get name => 'Minimal test runner configuration';
get autoStart => false;
void onInit() {}
String formatMessage(filename, groupname,
[ testname = '', testTime = '', result = '',
message = '', stack = '' ]) {
@ -68,7 +74,7 @@ class TestRunnerConfiguration extends unittest.Configuration {
replaceAll(Macros.testStacktrace, stack);
}
String elapsed(unittest.TestCase t) {
String elapsed(TestCase t) {
if (includeTime) {
double duration = t.runningTime.inMilliseconds.toDouble();
duration /= 1000;
@ -78,11 +84,11 @@ class TestRunnerConfiguration extends unittest.Configuration {
}
}
void dumpTestResult(source, unittest.TestCase t) {
void dumpTestResult(source, TestCase t) {
var groupName = '', testName = '';
var idx = t.description.lastIndexOf('###');
var idx = t.description.lastIndexOf(marker);
if (idx >= 0) {
groupName = t.description.substring(0, idx).replaceAll('###', ' ');
groupName = t.description.substring(0, idx).replaceAll(marker, ' ');
testName = t.description.substring(idx+3);
} else {
testName = t.description;
@ -94,7 +100,7 @@ class TestRunnerConfiguration extends unittest.Configuration {
duration, t.result, message, stack));
}
void onTestResult(unittest.TestCase testCase) {
void onTestResult(TestCase testCase) {
if (immediate) {
dumpTestResult('$testfile ', testCase);
}
@ -116,7 +122,7 @@ class TestRunnerConfiguration extends unittest.Configuration {
}
void onSummary(int passed, int failed, int errors,
List<unittest.TestCase> results, String uncaughtError) {
List<TestCase> results, String uncaughtError) {
if (!immediate) {
for (final testCase in results) {
dumpTestResult('$testfile ', testCase);
@ -134,12 +140,6 @@ class TestRunnerConfiguration extends unittest.Configuration {
}
}
// Support for listing tests and groups. We use a minimal config.
class MinimalTestRunnerConfiguration extends unittest.Configuration {
get name => 'Minimal test runner configuration';
get autoStart => false;
}
String formatListMessage(filename, groupname, [ testname = '']) {
return listFormat.
replaceAll(Macros.testfile, filename).
@ -148,13 +148,13 @@ String formatListMessage(filename, groupname, [ testname = '']) {
}
listGroups() {
List tests = unittest.testCases;
List tests = testCases;
Map groups = {};
for (var t in tests) {
var groupName, testName = '';
var idx = t.description.lastIndexOf('###');
var idx = t.description.lastIndexOf(marker);
if (idx >= 0) {
groupName = t.description.substring(0, idx).replaceAll('###', ' ');
groupName = t.description.substring(0, idx).replaceAll(marker, ' ');
if (!groups.containsKey(groupName)) {
groups[groupName] = '';
}
@ -162,7 +162,7 @@ listGroups() {
}
for (var g in groups.keys) {
var msg = formatListMessage('$testfile ', '$g ');
print('###$msg');
print('$marker$msg');
}
if (notifyDone != null) {
notifyDone(0);
@ -170,19 +170,19 @@ listGroups() {
}
listTests() {
List tests = unittest.testCases;
List tests = testCases;
for (var t in tests) {
var groupName, testName = '';
var idx = t.description.lastIndexOf('###');
var idx = t.description.lastIndexOf(marker);
if (idx >= 0) {
groupName = t.description.substring(0, idx).replaceAll('###', ' ');
groupName = t.description.substring(0, idx).replaceAll(marker, ' ');
testName = t.description.substring(idx+3);
} else {
groupName = '';
testName = t.description;
}
var msg = formatListMessage('$testfile ', '$groupName ', '$testName ');
print('###$msg');
print('$marker$msg');
}
if (notifyDone != null) {
notifyDone(0);
@ -191,13 +191,13 @@ listTests() {
// Support for running in isolates.
class TestRunnerChildConfiguration extends unittest.Configuration {
class TestRunnerChildConfiguration extends Configuration {
get name => 'Test runner child configuration';
get autoStart => false;
void onSummary(int passed, int failed, int errors,
List<unittest.TestCase> results, String uncaughtError) {
unittest.TestCase test = results[0];
List<TestCase> results, String uncaughtError) {
TestCase test = results[0];
parentPort.send([test.result, test.runningTime.inMilliseconds,
test.message, test.stackTrace]);
}
@ -207,59 +207,43 @@ var parentPort;
runChildTest() {
port.receive((testName, sendport) {
parentPort = sendport;
unittest.configure(new TestRunnerChildConfiguration());
unittest.groupSep = '###';
unittest.group('', test.main);
unittest.filterTests(testName);
unittest.runTests();
unittestConfiguration = new TestRunnerChildConfiguration();
groupSep = marker;
group('', test.main);
filterTests(testName);
runTests();
});
}
var testNum;
var failed;
var errors;
var passed;
runParentTest() {
var tests = unittest.testCases;
tests[testNum].startTime = new DateTime.now();
isolatedTestParentWrapper(testCase) => () {
SendPort childPort = spawnFunction(runChildTest);
childPort.call(tests[testNum].description).then((results) {
var f = childPort.call(testCase.description);
f.then((results) {
var result = results[0];
var duration = new Duration(milliseconds: results[1]);
var message = results[2];
var stack = results[3];
if (result == 'pass') {
tests[testNum].pass();
++passed;
} else if (result == 'fail') {
tests[testNum].fail(message, stack);
++failed;
} else {
tests[testNum].error(message, stack);
++errors;
}
tests[testNum].runningTime = duration;
++testNum;
if (testNum < tests.length) {
runParentTest();
} else {
unittest.config.onDone(passed, failed, errors,
unittest.testCases, null);
if (result == 'fail') {
testCase.fail(message, stack);
} else if (result == 'error') {
testCase.error(message, stack);
}
});
}
return f;
};
runIsolateTests() {
testNum = 0;
passed = failed = errors = 0;
runParentTest();
// Replace each test with a wrapped version first.
for (var i = 0; i < testCases.length; i++) {
testCases[i].testFunction = isolatedTestParentWrapper(testCases[i]);
}
runTests();
}
// Main
filterTest(t) {
var name = t.description.replaceAll("###", " ");
var name = t.description.replaceAll(marker, " ");
if (includeFilters.length > 0) {
for (var f in includeFilters) {
if (name.indexOf(f) >= 0) return true;
@ -276,10 +260,10 @@ filterTest(t) {
}
process(testMain, action) {
unittest.groupSep = '###';
unittest.configure(new TestRunnerConfiguration());
unittest.group('', testMain);
groupSep = marker;
unittestConfiguration = new TestRunnerConfiguration();
group('', testMain);
// Do any user-specified test filtering.
unittest.filterTests(filterTest);
filterTests(filterTest);
action();
}

View file

@ -10,7 +10,7 @@
* of the removed features are:
*
* - No support for test.status files. The assumption is that tests are
* expected to pass.
* expected to pass. Status file support will be added in the future.
* - A restricted set of runtimes. The assumption here is that the Dart
* libraries deal with platform dependencies, and so the primary
* SKUs that a user of this app would be concerned with would be
@ -43,7 +43,7 @@
* vm - run native Dart in the VM; i.e. using $DARTSDK/dart-sdk/bin/dart.
* drt-dart - run native Dart in DumpRenderTree, the headless version of
* Dartium, which is located in $DARTSDK/chromium/DumpRenderTree, if
* you intsalled the SDK that is bundled with the editor, or available
* you installed the SDK that is bundled with the editor, or available
* from http://gsdview.appspot.com/dartium-archive/continuous/
* otherwise.
*
@ -67,10 +67,27 @@
* which is run in an isolate. The `--pipeline` argument can be used to
* specify a different script for running a test file pipeline, allowing
* customization of the pipeline.
*
* Wrapper files are created for tests in the tmp directory, which can be
* overridden with --tempdir. These files are not removed after the tests
* are complete, primarily to reduce the amount of times pub must be
* executed. You can use --clean-files to force file cleanup. The temp
* directories will have pubspec.yaml files auto-generated unless the
* original test file directories have such files; in that case the existing
* files will be copied. Whenever a new pubspec file is copied or
* created pub will be run (but not otherwise - so if you want to do
* the equivelent of pub update you should use --clean-files and the rerun
* the tests).
*
* TODO(gram): if the user has a pubspec.yaml file, we should inspect the
* pubspec.lock file and give useful errors:
* - if the lock file doesn't exit, then run pub install
* - if it exists and it doesn't have the required packages (unittest or
* browser), ask the user to add them and run pub install again.
*/
// TODO - layout tests that use PNGs rather than DRT text render dumps.
library testrunner;
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'dart:math';
@ -91,18 +108,24 @@ int _numTasks;
/** The index of the next pipeline runner to execute. */
int _nextTask;
/** The stream to use for high-value messages, like test results. */
OutputStream _outStream;
/** The sink to use for high-value messages, like test results. */
IOSink _outSink;
/** The stream to use for low-value messages, like verbose output. */
OutputStream _logStream;
/** The sink to use for low-value messages, like verbose output. */
IOSink _logSink;
/**
* The user can specify output streams on the command line, using 'none',
* 'stdout', 'stderr', or a file path; [getStream] will take such a name
* and return an appropriate [OutputStream].
* The last temp test directory we accessed; we use this to know if we
* need to check the pub configuration.
*/
OutputStream getStream(String name) {
String _testDir;
/**
* The user can specify output streams on the command line, using 'none',
* 'stdout', 'stderr', or a file path; [getSink] will take such a name
* and return an appropriate [IOSink].
*/
IOSink getSink(String name) {
if (name == null || name == 'none') {
return null;
}
@ -112,7 +135,8 @@ OutputStream getStream(String name) {
if (name == 'stderr') {
return stderr;
}
return new File(name).openOutputStream(FileMode.WRITE);
var f = new File(name);
return f.openWrite();
}
/**
@ -120,13 +144,13 @@ OutputStream getStream(String name) {
* and execute pipelines for the files.
*/
void processTests(Map config, List testFiles) {
_outStream = getStream(config['out']);
_logStream = getStream(config['log']);
_outSink = getSink(config['out']);
_logSink = getSink(config['log']);
if (config['list-files']) {
if (_outStream != null) {
if (_outSink != null) {
for (var i = 0; i < testFiles.length; i++) {
_outStream.writeString(testFiles[i]);
_outStream.writeString('\n');
_outSink.write(testFiles[i]);
_outSink.write('\n');
}
}
} else {
@ -137,6 +161,69 @@ void processTests(Map config, List testFiles) {
}
}
/**
* Create or update a pubspec for the target test directory. We use the
* source directory pubspec if available; otherwise we create a minimal one.
* We return a Future if we are running pub install, or null otherwise.
*/
Future doPubConfig(Path sourcePath, String sourceDir,
Path targetPath, String targetDir,
String pub, String runtime) {
// Make sure the target directory exists.
var d = new Directory(targetDir);
if (!d.existsSync()) {
d.createSync(recursive: true);
}
// If the source has no pubspec, but the dest does, leave
// things as they are. If neither do, create one in dest.
var sourcePubSpecName = new Path(sourceDir).append("pubspec.yaml").
toNativePath();
var targetPubSpecName = new Path(targetDir).append("pubspec.yaml").
toNativePath();
var sourcePubSpec = new File(sourcePubSpecName);
var targetPubSpec = new File(targetPubSpecName);
if (!sourcePubSpec.existsSync()) {
if (targetPubSpec.existsSync()) {
return null;
} else {
// Create one.
if (runtime == 'vm') {
writeFile(targetPubSpecName,
"name: testrunner\ndependencies:\n unittest: any\n");
} else {
writeFile(targetPubSpecName,
"name: testrunner\ndependencies:\n unittest: any\n browser: any\n");
}
}
} else {
if (targetPubSpec.existsSync()) {
// If there is a source one, and it is older than the target,
// leave the target as is.
if (sourcePubSpec.lastModifiedSync().millisecondsSinceEpoch <
targetPubSpec.lastModifiedSync().millisecondsSinceEpoch) {
return null;
}
}
// Source exists and is newer than target or there is no target;
// copy the source to the target. If there is a pubspec.lock file,
// copy that too.
var s = sourcePubSpec.readAsStringSync();
targetPubSpec.writeAsStringSync(s);
var sourcePubLock = new File(sourcePubSpecName.replaceAll(".yaml", ".lock"));
if (sourcePubLock.existsSync()) {
var targetPubLock =
new File(targetPubSpecName.replaceAll(".yaml", ".lock"));
s = sourcePubLock.readAsStringSync();
targetPubLock.writeAsStringSync(s);
}
}
// A new target pubspec was created so run pub install.
return _processHelper(pub, [ 'install' ], workingDir: targetDir);
}
/** Execute as many tasks as possible up to the maxTasks limit. */
void spawnTasks(Map config, List testFiles) {
var verbose = config['verbose'];
@ -154,12 +241,12 @@ void spawnTasks(Map config, List testFiles) {
List stderr = msg[1];
List log = msg[2];
int exitCode = msg[3];
writelog(stdout, _outStream, _logStream, verbose, skipNonVerbose);
writelog(stderr, _outStream, _logStream, true, skipNonVerbose);
writelog(log, _outStream, _logStream, verbose, skipNonVerbose);
writelog(stdout, _outSink, _logSink, verbose, skipNonVerbose);
writelog(stderr, _outSink, _logSink, true, skipNonVerbose);
writelog(log, _outSink, _logSink, verbose, skipNonVerbose);
port.close();
--_numTasks;
if (exitCode == 0 || !config['stopOnFailure']) {
if (exitCode == 0 || !config['stop-on-failure']) {
spawnTasks(config, testFiles);
}
if (_numTasks == 0) {
@ -168,74 +255,129 @@ void spawnTasks(Map config, List testFiles) {
}
});
SendPort s = spawnUri(config['pipeline']);
s.send(config, port.toSendPort());
// Get the names of the source and target test files and containing
// directories.
var testPath = new Path(testfile);
var sourcePath = testPath.directoryPath;
var sourceDir = sourcePath.toNativePath();
var targetPath = new Path(config["tempdir"]);
var normalizedTarget = testPath.directoryPath.toNativePath()
.replaceAll(Platform.pathSeparator, '_')
.replaceAll(':', '_');
targetPath = targetPath.append("${normalizedTarget}_${config['runtime']}");
var targetDir = targetPath.toNativePath();
config['targetDir'] = targetDir;
// If this is a new target dir, we need to redo the pub check.
var f = null;
if (targetDir != _testDir) {
f = doPubConfig(sourcePath, sourceDir, targetPath, targetDir,
config['pub'], config['runtime']);
_testDir = targetDir;
}
if (f == null) {
s.send(config, port.toSendPort());
} else {
f.then((_) {
s.send(config, port.toSendPort());
});
break; // Don't do any more until pub is done.
}
}
}
/**
* Our tests are configured so that critical messages have a '###' prefix.
* [writeLog] takes the output from a pipeline execution and writes it to
* our output streams. It will strip the '###' if necessary on critical
* [writelog] takes the output from a pipeline execution and writes it to
* our output sinks. It will strip the '###' if necessary on critical
* messages; other messages will only be written if verbose output was
* specified.
*/
void writelog(List messages, OutputStream out, OutputStream log,
void writelog(List messages, IOSink out, IOSink log,
bool includeVerbose, bool skipNonVerbose) {
for (var i = 0; i < messages.length; i++) {
var msg = messages[i];
if (msg.startsWith('###')) {
if (!skipNonVerbose && out != null) {
out.writeString(msg.substring(3));
out.writeString('\n');
out.write(msg.substring(3));
out.write('\n');
}
} else if (msg.startsWith('CONSOLE MESSAGE:')) {
if (!skipNonVerbose && out != null) {
int idx = msg.indexOf('###');
if (idx > 0) {
out.writeString(msg.substring(idx + 3));
out.writeString('\n');
out.write(msg.substring(idx + 3));
out.write('\n');
}
}
} else if (includeVerbose && log != null) {
log.writeString(msg);
log.writeString('\n');
log.write(msg);
log.write('\n');
}
}
}
sanitizeConfig(Map config, ArgParser parser) {
normalizeFilter(List filter) {
// We want the filter to be a quoted string or list of quoted
// strings.
for (var i = 0; i < filter.length; i++) {
var f = filter[i];
if (f[0] != "'" && f[0] != '"') {
filter[i] = "'$f'"; // TODO(gram): Quote embedded quotes.
}
}
return filter;
}
void sanitizeConfig(Map config, ArgParser parser) {
config['layout'] = config['layout-text'] || config['layout-pixel'];
// TODO - check if next three are actually used.
config['runInBrowser'] = (config['runtime'] != 'vm');
config['verbose'] = (config['log'] != 'none' && !config['list-groups']);
config['filtering'] = (config['include'].length > 0 ||
config['exclude'].length > 0);
config['timeout'] = int.parse(config['timeout']);
config['tasks'] = int.parse(config['tasks']);
var dartsdk = config['dartsdk'];
var pathSep = Platform.pathSeparator;
if (dartsdk != null) {
if (parser.getDefault('dart2js') == config['dart2js']) {
config['dart2js'] =
'$dartsdk${pathSep}dart-sdk${pathSep}bin${pathSep}dart2js';
}
if (parser.getDefault('dart') == config['dart']) {
config['dart'] = '$dartsdk${pathSep}dart-sdk${pathSep}bin${pathSep}dart';
}
if (parser.getDefault('drt') == config['drt']) {
config['drt'] = '$dartsdk${pathSep}chromium${pathSep}DumpRenderTree';
if (dartsdk == null) {
var opt = new Options();
var runner = opt.executable;
var idx = runner.indexOf('dart-sdk');
if (idx < 0) {
print("Please use --dartsdk option or run using the dart executable "
"from the Dart SDK");
exit(0);
}
dartsdk = runner.substring(0, idx);
}
if (Platform.operatingSystem == 'macos') {
config['dart2js'] =
'$dartsdk${pathSep}dart-sdk${pathSep}bin${pathSep}dart2js';
config['dart'] = '$dartsdk${pathSep}dart-sdk${pathSep}bin${pathSep}dart';
config['pub'] = '$dartsdk${pathSep}dart-sdk${pathSep}bin${pathSep}pub';
config['drt'] =
'$dartsdk/chromium/DumpRenderTree.app/Contents/MacOS/DumpRenderTree';
} else if (Platform.operatingSystem == 'linux') {
config['dart2js'] =
'$dartsdk${pathSep}dart-sdk${pathSep}bin${pathSep}dart2js';
config['dart'] = '$dartsdk${pathSep}dart-sdk${pathSep}bin${pathSep}dart';
config['pub'] = '$dartsdk${pathSep}dart-sdk${pathSep}bin${pathSep}pub';
config['drt'] = '$dartsdk${pathSep}chromium${pathSep}DumpRenderTree';
} else {
config['dart2js'] =
'$dartsdk${pathSep}dart-sdk${pathSep}bin${pathSep}dart2js.bat';
config['dart'] = '$dartsdk${pathSep}dart-sdk${pathSep}bin${pathSep}dart.exe';
config['pub'] = '$dartsdk${pathSep}dart-sdk${pathSep}bin${pathSep}pub.bat';
config['drt'] = '$dartsdk${pathSep}chromium${pathSep}DumpRenderTree.exe';
}
config['unittest'] = makePathAbsolute(config['unittest']);
config['drt'] = makePathAbsolute(config['drt']);
config['dart'] = makePathAbsolute(config['dart']);
config['dart2js'] = makePathAbsolute(config['dart2js']);
for (var prog in [ 'drt', 'dart', 'pub', 'dart2js' ]) {
config[prog] = makePathAbsolute(config[prog]);
}
config['runnerDir'] = runnerDirectory;
config['include'] = normalizeFilter(config['include']);
config['exclude'] = normalizeFilter(config['exclude']);
}
main() {
@ -245,7 +387,7 @@ main() {
if (options['list-options']) {
printOptions(optionsParser, options, false, stdout);
} else if (options['list-all-options']) {
printOptions(optionsParser, options, true, stdout);
printOptions(optionsParser, options, true, stdout);
} else {
var config = new Map();
for (var option in options.options) {
@ -273,9 +415,10 @@ main() {
if (dirs.length == 0) {
dirs.add('.'); // Use current working directory as default.
}
buildFileList(dirs,
new RegExp(options['test-file-pattern']), options['recurse'],
(f) => processTests(config, f));
var f = buildFileList(dirs,
new RegExp(config['test-file-pattern']), config['recurse']);
if (config['sort']) f.sort();
processTests(config, f);
}
}
}

View file

@ -4,6 +4,12 @@
part of testrunner;
/** Create a file [fileName] and populate it with [contents]. */
void writeFile(String fileName, String contents) {
var file = new File(fileName);
file.writeAsStringSync(contents);
}
/**
* Read the contents of a file [fileName] into a [List] of [String]s.
* If the file does not exist and [errorIfNoFile] is true, throw an
@ -28,32 +34,24 @@ List<String> getFileContents(String filename, bool errorIfNoFile) {
String makePathAbsolute(String path) {
var p = new Path(path).canonicalize();
if (p.isAbsolute) {
return p.toString();
return p.toNativePath();
} else {
var cwd = new Path((new Directory.current()).path);
return cwd.join(p).toString();
return cwd.join(p).toNativePath();
}
}
/**
* Create the list of all the files in a set of directories
* ([dirs]) whose names match [filePat]. If [recurse] is true
* look at subdirectories too. Once they have all been enumerated,
* call [onComplete]. An optional [excludePat] can be supplied
* look at subdirectories too. An optional [excludePat] can be supplied
* and files or directories that match that will be excluded.
* [includeSymLinks] controls whether or not to include files that
* have symlinks in the traversed tree.
*/
// TODO(gram): The key thing here is we want to avoid package
// directories, which have symlinks. excludePat was added for
// that but can't currently be used because the symlinked files
// have canonicalized paths. So instead we exploit that fact and
// assert that every file must have a prefix that matches the
// directory. If this changes then we will need to switch to using
// the exclude pattern or some other mechanism.
void buildFileList(List dirs, RegExp filePat, bool recurse,
Function onComplete,
List buildFileList(List dirs, RegExp filePat, bool recurse,
[RegExp excludePat, bool includeSymLinks = false]) {
var files = new List();
var dirCount = 1;
for (var i = 0; i < dirs.length; i++) {
var path = dirs[i];
if (excludePat != null && excludePat.hasMatch(path)) {
@ -69,33 +67,24 @@ void buildFileList(List dirs, RegExp filePat, bool recurse,
path = makePathAbsolute(path);
Directory d = new Directory(path);
if (d.existsSync()) {
++dirCount;
d.list(recursive: recurse).listen(
(entity) {
if (entity is File) {
var file = entity.name;
if (filePat.hasMatch(file)) {
if (excludePat == null || !excludePat.hasMatch(file)) {
if (includeSymLinks || file.startsWith(path)) {
files.add(file);
}
}
}
var contents = d.listSync(recursive: recurse,
followLinks: includeSymLinks);
for (var entity in contents) {
if (entity is File) {
var file = entity.path;
if (filePat.hasMatch(file)) {
if (excludePat == null || !excludePat.hasMatch(file)) {
files.add(file);
}
},
onDone: () {
if (complete && --dirCount == 0) {
onComplete(files);
}
});
}
}
}
} else { // Does not exist.
print('$path does not exist.');
}
}
}
if (--dirCount == 0) {
onComplete(files);
}
return files;
}
/**
@ -108,3 +97,22 @@ String get runnerDirectory {
return libDirectory.substring(0,
libDirectory.lastIndexOf(Platform.pathSeparator));
}
/*
* Run an external process [cmd] with command line arguments [args].
* Returns a [Future] for when the process terminates.
*/
Future _processHelper(String command, List<String> args,
{String workingDir}) {
var options = null;
if (workingDir != null) {
options = new ProcessOptions();
options.workingDirectory = workingDir;
}
return Process.run(command, args, options)
.then((result) => result.exitCode)
.catchError((e) {
print("$command ${args.join(' ')}: ${e.toString()}");
});
}

View file

@ -0,0 +1,5 @@
name: browser_tests
dependencies:
html: any
unittest: any

View file

@ -0,0 +1,24 @@
// 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 testrunner_test;
import 'package:unittest/unittest.dart';
foo(bool x) => x;
main() {
group('group1', () {
test('test1', () {
expect(true, isFalse);
});
});
group('group2', () {
test('test2', () {
foo(3);
expect(true, isTrue);
});
});
}

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Testdriver tests</title>
</head>
<body>
<script type="application/dart" src="browser_test.dart"></script>
</body>
</html>

View file

@ -0,0 +1,42 @@
// 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 testrunner_test;
import 'dart:async';
import 'dart:io';
import 'dart:uri';
import 'package:unittest/unittest.dart';
main() {
var get = (String what, int code, String text) {
var c = new Completer();
HttpClient client = new HttpClient();
client.getUrl(new Uri.fromString("http://127.0.0.1:3456/$what"))
.then((HttpClientRequest request) {
// Prepare the request then call close on it to send it.
return request.close();
})
.then((HttpClientResponse response) {
// Process the response.
expect(response.statusCode, code);
var sb = new StringBuffer();
response.transform(new StringDecoder())
.listen((data) {
sb.write(data);
}, onDone: () {
expect(sb.toString(), text);
c.complete();
});
});
return c.future;
};
test('test1', () {
return get('test.txt', 200, "Hello world!\n");
});
test('test2', () {
return get('fail.txt', 404, "");
});
}

View file

@ -0,0 +1,5 @@
name: layout_tests
dependencies:
html: any
unittest: any

View file

@ -0,0 +1,17 @@
// 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 testrunner_test;
import 'dart:html';
import 'package:unittest/unittest.dart';
main() {
test("layout", () {
var lbl = new LabelElement();
lbl.text = 'Hello Dart!';
document.body.nodes.add(lbl);
});
}

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Testdriver tests</title>
</head>
<body>
<script type="application/dart" src="browser_test.dart"></script>
</body>
</html>

View file

@ -0,0 +1,24 @@
// 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 testrunner_test;
import 'package:unittest/unittest.dart';
foo(bool x) => x;
main() {
group('group1', () {
test('test1', () {
expect(true, isFalse);
});
});
group('group2', () {
test('test2', () {
foo(3);
expect(true, isTrue);
});
});
}

View file

@ -0,0 +1,16 @@
// 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 testrunner_test;
import 'package:unittest/unittest.dart';
main() {
group('foo', () {
test('bar', () {
expect(true, isTrue);
});
});
}

View file

@ -0,0 +1,4 @@
name: non_browser_tests
dependencies:
unittest: any

View file

@ -0,0 +1,4 @@
name: non_browser_tests
dependencies:
unittest: any

View file

@ -0,0 +1 @@
Hello world!

View file

@ -0,0 +1 @@
--checked

View file

@ -0,0 +1,522 @@
// 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 testrunner_test;
import 'dart:async';
import 'dart:io';
import 'package:unittest/unittest.dart';
var dart;
var debug = false;
Future runTestrunner(command, List<String> args,
List<String> stdout, List<String> stderr) {
if (debug) {
print("Running $command ${args.join(' ')}");
}
return Process.run(command, args).then((ProcessResult result) {
var lineEndings = new RegExp("\r\n|\n");
stdout.addAll(result.stdout.trim().split(lineEndings));
stderr.addAll(result.stderr.trim().split(lineEndings));
})
.catchError((e) {
stderr.add("Error starting process:");
stderr.add(" Command: $command");
stderr.add(" Error: ${e}");
completer.complete(-1);
});
}
// Useful utility for debugging test failures.
void dump(label, list) {
if (!debug) return;
print('\n@=[ $label ]=============================\n');
for (var i = 0; i < list.length; i++)
print('@ ${list[i]}\n');
print('------------------------------------------\n');
}
int stringCompare(String s1, String s2) => s1.compareTo(s2);
Future runTest(
List<String> args,
List<String> expected_stdout,
{List<String> expected_stderr, sort: false}) {
var stdout = new List<String>();
var stderr = new List<String>();
for (var i = 0; i < expected_stdout.length; i++) {
expected_stdout[i] = expected_stdout[i].
replaceAll('/', Platform.pathSeparator);
}
if (debug) {
args.insert(1, "--log=stderr");
}
var rtn = runTestrunner(dart, args, stdout, stderr);
rtn.then((_) {
dump('stderr', stderr);
dump('stdout', stdout);
if (expected_stderr != null) {
expect(stderr.length, orderedEquals(expected_stderr));
}
var i, l = 0, matched = 0;
if (sort) {
stdout.sort(stringCompare);
expected_stdout.sort(stringCompare);
}
for (i = 0; i < stdout.length; i++) {
if (!stdout[i].startsWith('@')) {
if (expected_stdout.length <= l) {
fail("Extra text in output: ${stdout[i]}");
return;
}
var actual = stdout[i].trim();
if (debug) {
print("Compare <$actual> and <${expected_stdout[l]}>");
}
if (expected_stdout[l].startsWith('*')) {
expect(actual, endsWith(expected_stdout[l].substring(1)));
} else if (expected_stdout[l].startsWith('?')) {
var pat = expected_stdout[l].substring(1);
if (Platform.operatingSystem == 'windows') {
// The joys of Windows...
pat = pat.replaceAll('\\','\\\\');
}
expect(actual, matches(pat));
} else {
expect(actual, expected_stdout[l]);
}
++l;
}
}
if (l < expected_stdout.length) {
fail("Only matched $l of ${expected_stdout.length} lines");
}
});
return rtn;
}
// A useful function to quickly disable a group of tests; just
// replace group() with skip_group().
skip_group(_1,_2) {}
main() {
var opt = new Options();
dart = opt.executable;
var idx = dart.indexOf('dart-sdk');
if (idx < 0) {
print("Please run using the dart executable from the Dart SDK");
exit(-1);
}
var _ = Platform.pathSeparator;
var testrunner = '../../testrunner/testrunner.dart'
.replaceAll('/', Platform.pathSeparator);
group("list tests", () {
test('list file', () {
return runTest(
[ testrunner,
'--list-files',
'non_browser_tests' ],
[ '?.*/non_browser_tests/non_browser_test.dart' ]);
});
test('list files', () {
return runTest(
[ testrunner,
'--recurse',
'--sort',
'--list-files',
'--test-file-pattern=.dart\$' ],
[ '*browser_tests/web/browser_test.dart',
'*http_client_tests/http_client_test.dart',
'*layout_tests/web/layout_test.dart',
'*non_browser_tests/non_browser_test.dart',
'*non_browser_tests/non_browser_toast.dart',
'*/testrunner_test.dart' ]
);
});
test('list files', () {
return runTest(
[ testrunner,
'--list-files',
'--test-file-pattern=.dart\$',
'non_browser_tests' ],
[ '*non_browser_tests/non_browser_test.dart',
'*non_browser_tests/non_browser_toast.dart' ],
sort:true
);
});
test('list groups', () {
return runTest(
[ testrunner,
'--list-groups',
'non_browser_tests' ],
[ '*non_browser_tests/non_browser_test.dart group1',
'*non_browser_tests/non_browser_test.dart group2']);
});
test('list tests', () {
return runTest(
[ testrunner,
'--list-tests',
'non_browser_tests' ],
[ '*non_browser_tests/non_browser_test.dart group1 test1',
'*non_browser_tests/non_browser_test.dart group2 test2' ]);
});
});
group("vm", () {
test("vm without timing info", () {
return runTest(
[ testrunner,
'--recurse',
'non_browser_tests' ],
[ '?FAIL .*/non_browser_tests/non_browser_test.dart group1 test1'
' Expected: false',
'?PASS .*/non_browser_tests/non_browser_test.dart group2 test2' ]);
});
test("vm with timing info", () {
return runTest(
[ testrunner,
'--recurse',
'--time',
'non_browser_tests' ],
[ '?FAIL [0-9.]+s .*/non_browser_tests/non_browser_test.dart group1'
' test1 Expected: false',
'?PASS [0-9.]+s .*/non_browser_tests/non_browser_test.dart group2'
' test2' ]);
});
});
group("selection", () {
test("--include", () {
return runTest(
[ testrunner,
'--recurse',
'--include=group1',
'non_browser_tests' ],
[ '?FAIL .*/non_browser_tests/non_browser_test.dart group1 test1 '
'Expected: false' ]);
});
test("--exclude", () {
return runTest(
[ testrunner,
'--recurse',
'--exclude=group1',
'non_browser_tests' ],
[ '?PASS .*/non_browser_tests/non_browser_test.dart group2 test2' ]);
});
test("test file pattern", () {
return runTest(
[ testrunner,
'--recurse',
'--test-file-pattern=toast',
'non_browser_tests' ],
[ '?PASS .*/non_browser_tests/non_browser_toast.dart foo bar' ]);
});
});
group("stop on failure tests", () {
test("without stop", () {
return runTest(
[ testrunner,
'--recurse',
'--sort',
'--tasks=1',
'--test-file-pattern=.dart\$',
'non_browser_tests' ],
[ '?FAIL .*/non_browser_tests/non_browser_test.dart group1 test1 '
'Expected: false',
'?PASS .*/non_browser_tests/non_browser_test.dart group2 test2',
'?PASS .*/non_browser_tests/non_browser_toast.dart foo bar' ]);
});
test("with stop", () {
return runTest(
[ testrunner,
'--recurse',
'--sort',
'--tasks=1',
'--test-file-pattern=.dart\$',
'--stop-on-failure',
'non_browser_tests' ],
[ '?FAIL .*/non_browser_tests/non_browser_test.dart group1 test1 '
'Expected: false',
'?PASS .*/non_browser_tests/non_browser_test.dart group2 test2' ]);
});
});
group("output control", () {
test("summary test", () {
return runTest(
[ testrunner,
'--recurse',
'--summary',
'non_browser_tests' ],
[ '?FAIL .*/non_browser_tests/non_browser_test.dart group1 test1 '
'Expected: false',
'?PASS .*/non_browser_tests/non_browser_test.dart group2 test2',
'',
'?.*/non_browser_tests/non_browser_test.dart: '
'1 PASSED, 1 FAILED, 0 ERRORS' ]);
});
test('list tests with custom format', () {
return runTest(
[ testrunner,
'--list-tests',
'--list-format="<FILENAME><TESTNAME>"',
'non_browser_tests' ],
[ '?.*/non_browser_tests/non_browser_test.dart test1',
'?.*/non_browser_tests/non_browser_test.dart test2' ]);
});
test("custom message formatting", () {
return runTest(
[ testrunner,
'--recurse',
'--pass-format=YIPPEE! <GROUPNAME><TESTNAME>',
'--fail-format=EPIC FAIL! <GROUPNAME><TESTNAME>',
'non_browser_tests' ],
[ 'EPIC FAIL! group1 test1', 'YIPPEE! group2 test2' ]);
});
});
test("checked mode test", () {
return runTest(
[ testrunner,
'--recurse',
'--checked',
'non_browser_tests' ],
[ '?FAIL .*/non_browser_tests/non_browser_test.dart group1 test1 '
'Expected: false',
"?FAIL .*/non_browser_tests/non_browser_test.dart group2 test2 "
"Caught type 'int' is not a subtype of type 'bool' of 'x'." ]);
});
group("browser", () {
test("native test", () {
return runTest(
[ testrunner,
'--recurse',
'--runtime=drt-dart',
'browser_tests' ],
[ '?FAIL .*/browser_tests/web/browser_test.dart group1 test1 '
'Expected: false',
'?PASS .*/browser_tests/web/browser_test.dart group2 test2' ]);
});
test("compiled test", () {
return runTest(
[ testrunner,
'--recurse',
'--runtime=drt-js',
'browser_tests' ],
[ '?FAIL .*/browser_tests/web/browser_test.dart group1 test1 '
'Expected: false',
'?PASS .*/browser_tests/web/browser_test.dart group2 test2' ]);
});
});
group("textual layout tests", () {
group("drt-dart", () {
test("no baseline", () {
var f = new File("layout_tests/web/layout_test/layout.txt");
if (f.existsSync()) {
f.deleteSync();
}
return runTest(
[ testrunner,
'--runtime=drt-dart',
'--recurse',
'--layout-text',
'layout_tests' ],
[ '?FAIL .*/layout_tests/web/layout_test.dart layout '
'No expectation file' ]);
});
test("create baseline", () {
return runTest(
[ testrunner,
'--runtime=drt-dart',
'--recurse',
'--layout-text',
'--regenerate',
'layout_tests' ],
[ '?PASS .*/layout_tests/web/layout_test.dart layout' ]);
});
test("test baseline", () {
return runTest(
[ testrunner,
'--runtime=drt-dart',
'--recurse',
'--layout-text',
'layout_tests' ],
[ '?PASS .*/layout_tests/web/layout_test.dart layout' ]);
});
});
group("drt-js", () {
test("no baseline", () {
var f = new File("layout_tests/web/layout_test/layout.txt");
if (f.existsSync()) {
f.deleteSync();
}
return runTest(
[ testrunner,
'--runtime=drt-js',
'--recurse',
'--layout-text',
'layout_tests' ],
[ '?FAIL .*/layout_tests/web/layout_test.dart layout '
'No expectation file' ]);
});
test("create baseline", () {
return runTest(
[ testrunner,
'--runtime=drt-js',
'--recurse',
'--layout-text',
'--regenerate',
'layout_tests' ],
[ '?PASS .*/layout_tests/web/layout_test.dart layout' ]);
});
test("test baseline", () {
return runTest(
[ testrunner,
'--runtime=drt-js',
'--recurse',
'--layout-text',
'layout_tests' ],
[ '?PASS .*/layout_tests/web/layout_test.dart layout' ]);
});
});
});
group("pixel layout tests", () {
group("drt-dart", () {
test("no baseline", () {
var f = new File("layout_tests/web/layout_test/layout.png");
if (f.existsSync()) {
f.deleteSync();
}
return runTest(
[ testrunner,
'--runtime=drt-dart',
'--recurse',
'--layout-pixel',
'layout_tests' ],
[ '?FAIL .*/layout_tests/web/layout_test.dart layout '
'No expectation file' ]);
});
test("create baseline", () {
return runTest(
[ testrunner,
'--runtime=drt-dart',
'--recurse',
'--layout-pixel',
'--regenerate',
'layout_tests' ],
[ '?PASS .*/layout_tests/web/layout_test.dart layout' ]);
});
test("test baseline", () {
return runTest(
[ testrunner,
'--runtime=drt-dart',
'--recurse',
'--layout-pixel',
'layout_tests' ],
[ '?PASS .*/layout_tests/web/layout_test.dart layout' ]);
});
// TODO(gram): Should add a test that changes a byte of the
// expectation .png.
});
group("drt-js", () {
test("no baseline", () {
var f = new File("layout_tests/web/layout_test/layout.png");
if (f.existsSync()) {
f.deleteSync();
}
return runTest(
[ testrunner,
'--runtime=drt-js',
'--recurse',
'--layout-pixel',
'layout_tests' ],
[ '?FAIL .*/layout_tests/web/layout_test.dart layout '
'No expectation file' ]);
});
test("create baseline", () {
return runTest(
[ testrunner,
'--runtime=drt-js',
'--recurse',
'--layout-pixel',
'--regenerate',
'layout_tests' ],
[ '?PASS .*/layout_tests/web/layout_test.dart layout' ]);
});
test("test baseline", () {
return runTest(
[ testrunner,
'--runtime=drt-js',
'--recurse',
'--layout-pixel',
'layout_tests' ],
[ '?PASS .*/layout_tests/web/layout_test.dart layout' ]);
});
});
});
group("run in isolate", () {
test("vm", () {
return runTest(
[ testrunner,
'--runtime=vm',
'--recurse',
'--isolate',
'non_browser_tests' ],
[ '?FAIL .*/non_browser_tests/non_browser_test.dart group1 test1'
' Expected: false',
'?PASS .*/non_browser_tests/non_browser_test.dart group2 test2' ]);
});
test("drt-dart", () {
return runTest(
[ testrunner,
'--runtime=drt-dart',
'--recurse',
'--isolate',
'non_browser_tests' ],
[ '?FAIL .*/non_browser_tests/non_browser_test.dart group1 test1'
' Expected: false',
'?PASS .*/non_browser_tests/non_browser_test.dart group2 test2' ]);
});
test("drt-js", () {
return runTest(
[ testrunner,
'--runtime=drt-js',
'--recurse',
'--isolate',
'non_browser_tests' ],
[ '?FAIL .*/non_browser_tests/non_browser_test.dart group1 test1 '
'Expected: false',
'?PASS .*/non_browser_tests/non_browser_test.dart group2 test2' ]);
});
});
group("embedded server", () {
test("get test", () {
return runTest(
[ testrunner,
'--recurse',
'--server',
'--port=3456',
'--root=${new Directory.current().path}',
'http_client_tests' ],
[ '?PASS .*/http_client_tests/http_client_test.dart test1',
'?PASS .*/http_client_tests/http_client_test.dart test2' ]);
});
});
}