Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(663)

Unified Diff: tools/testing/dart/test_runner.dart

Issue 11586012: Call DumpRenderTree directly from test.dart instead of using the drt-trampoline.py indirection (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/testing/dart/test_progress.dart ('k') | tools/testing/dart/test_suite.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/testing/dart/test_runner.dart
diff --git a/tools/testing/dart/test_runner.dart b/tools/testing/dart/test_runner.dart
index e160e5ef753db6f2bdd84dd7041fd22a367464d5..5117ac17c7f3299603485c47fd47276a113120d1 100644
--- a/tools/testing/dart/test_runner.dart
+++ b/tools/testing/dart/test_runner.dart
@@ -25,6 +25,55 @@ typedef void TestCaseEvent(TestCase testCase);
typedef void ExitCodeEvent(int exitCode);
typedef void EnqueueMoreWork(ProcessQueue queue);
+
+/**
+ * [areByteArraysEqual] compares a range of bytes from [buffer1] with a
+ * range of bytes from [buffer2].
+ *
+ * Returns [true] if the [count] bytes in [buffer1] (starting at
+ * [offset1]) match the [count] bytes in [buffer2] (starting at
+ * [offset2]).
+ * Otherwise [false] is returned.
+ */
+bool areByteArraysEqual(List<int> buffer1, int offset1,
+ List<int> buffer2, int offset2,
+ int count) {
+ if ((offset1 + count) > buffer1.length ||
+ (offset2 + count) > buffer2.length) {
+ return false;
+ }
+
+ for (var i = 0; i < count; i++) {
+ if (buffer1[offset1 + i] != buffer2[offset2 + i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * [findBytes] searches for [pattern] in [data] beginning at [startPos].
+ *
+ * Returns [true] if [pattern] was found in [data].
+ * Otherwise [false] is returned.
+ */
+int findBytes(List<int> data, List<int> pattern, [int startPos=0]) {
+ // TODO(kustermann): Use one of the fast string-matching algorithms!
+ for (int i=startPos; i < (data.length-pattern.length); i++) {
+ bool found = true;
+ for (int j=0; j<pattern.length; j++) {
+ if (data[i+j] != pattern[j]) {
+ found = false;
+ }
+ }
+ if (found) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+
/** A command executed as a step in a test case. */
class Command {
/** Path to the executable of this command. */
@@ -32,11 +81,14 @@ class Command {
/** Command line arguments to the executable. */
List<String> arguments;
+
+ /** Environment for the command */
+ Map<String,String> environment;
/** The actual command line that will be executed. */
String commandLine;
- Command(this.executable, this.arguments) {
+ Command(this.executable, this.arguments, [this.environment = null]) {
if (Platform.operatingSystem == 'windows') {
// Windows can't handle the first command if it is a .bat file or the like
// with the slashes going the other direction.
@@ -49,6 +101,8 @@ class Command {
String toString() => commandLine;
Future<bool> get outputIsUpToDate => new Future.immediate(false);
+ Path get expectedOutputFile => null;
+ bool get isPixelTest => false;
}
class CompilationCommand extends Command {
@@ -105,6 +159,55 @@ class CompilationCommand extends Command {
}
}
+class DumpRenderTreeCommand extends Command {
+ /**
+ * If [expectedOutputPath] is set, the output of DumpRenderTree is compared
+ * with the content of [expectedOutputPath].
+ * This is used for example for pixel tests, where [expectedOutputPath] points
+ * to a *png file.
+ */
+ Path expectedOutputPath;
+
+ DumpRenderTreeCommand(String executable,
+ String htmlFile,
+ List<String> options,
+ List<String> dartFlags,
+ Uri packageRootUri,
+ Path this.expectedOutputPath)
+ : super(executable,
+ _getArguments(options, htmlFile),
+ _getEnvironment(dartFlags, packageRootUri));
+
+ static Map _getEnvironment(List<String> dartFlags, Uri packageRootUri) {
+ var needDartFlags = dartFlags != null && dartFlags.length > 0;
+ var needDartPackageRoot = packageRootUri != null;
+
+ var env = null;
+ if (needDartFlags || needDartPackageRoot) {
+ env = new Map.from(Platform.environment);
+ if (needDartFlags) {
+ env['DART_FLAGS'] = Strings.join(dartFlags, " ");
+ }
+ if (needDartPackageRoot) {
+ env['DART_PACKAGE_ROOT'] = packageRootUri.toString();
+ }
+ }
+
+ return env;
+ }
+
+ static List<String> _getArguments(List<String> options, String htmlFile) {
+ var arguments = new List.from(options);
+ arguments.add(htmlFile);
+ return arguments;
+ }
+
+ Path get expectedOutputFile => expectedOutputPath;
+ bool get isPixelTest => (expectedOutputFile != null &&
+ expectedOutputFile.filename.endsWith(".png"));
+}
+
+
/**
* TestCase contains all the information needed to run a test and evaluate
* its output. Running a test involves starting a separate process, with
@@ -317,8 +420,8 @@ abstract class CommandOutput {
int exitCode,
bool incomplete,
bool timedOut,
- List<String> stdout,
- List<String> stderr,
+ List<int> stdout,
+ List<int> stderr,
Duration time,
bool compilationSkipped) {
return new CommandOutputImpl.fromCase(testCase,
@@ -332,6 +435,8 @@ abstract class CommandOutput {
compilationSkipped);
}
+ Command get command;
+
bool get incomplete;
String get result;
@@ -350,9 +455,9 @@ abstract class CommandOutput {
int get exitCode;
- List<String> get stdout;
+ List<int> get stdout;
- List<String> get stderr;
+ List<int> get stderr;
List<String> get diagnostics;
@@ -360,6 +465,7 @@ abstract class CommandOutput {
}
class CommandOutputImpl implements CommandOutput {
+ Command command;
TestCase testCase;
int exitCode;
@@ -368,8 +474,8 @@ class CommandOutputImpl implements CommandOutput {
bool timedOut;
bool failed = false;
- List<String> stdout;
- List<String> stderr;
+ List<int> stdout;
+ List<int> stderr;
Duration time;
List<String> diagnostics;
bool compilationSkipped;
@@ -389,12 +495,12 @@ class CommandOutputImpl implements CommandOutput {
// Don't call this constructor, call CommandOutput.fromCase() to
// get a new TestOutput instance.
CommandOutputImpl(TestCase this.testCase,
- Command command,
+ Command this.command,
int this.exitCode,
bool this.incomplete,
bool this.timedOut,
- List<String> this.stdout,
- List<String> this.stderr,
+ List<int> this.stdout,
+ List<int> this.stderr,
Duration this.time,
bool this.compilationSkipped) {
testCase.commandOutputs[command] = this;
@@ -405,8 +511,8 @@ class CommandOutputImpl implements CommandOutput {
int exitCode,
bool incomplete,
bool timedOut,
- List<String> stdout,
- List<String> stderr,
+ List<int> stdout,
+ List<int> stderr,
Duration time,
bool compilationSkipped) {
if (testCase is BrowserTestCase) {
@@ -499,14 +605,27 @@ class BrowserCommandOutputImpl extends CommandOutputImpl {
compilationSkipped);
bool get didFail {
+ if (_failedBecauseOfMissingXDisplay) {
+ return true;
+ }
+
+ if (command.expectedOutputFile != null) {
+ // We are either doing a pixel test or a layout test with DumpRenderTree
+ return _failedBecauseOfUnexpectedDRTOutput;
+ }
+ return _browserTestFailure;
+ }
+
+ bool get _failedBecauseOfMissingXDisplay {
// Browser case:
// If the browser test failed, it may have been because DumpRenderTree
// and the virtual framebuffer X server didn't hook up, or DRT crashed with
// a core dump. Sometimes DRT crashes after it has set the stdout to PASS,
// so we have to do this check first.
- for (String line in super.stderr) {
+ var stderrLines = new String.fromCharCodes(super.stderr).split("\n");
+ for (String line in stderrLines) {
if (line.contains('Gtk-WARNING **: cannot open display: :99') ||
- line.contains('Failed to run command. return code=1')) {
+ line.contains('Failed to run command. return code=1')) {
// If we get the X server error, or DRT crashes with a core dump, retry
// the test.
if ((testCase as BrowserTestCase).numRetries > 0) {
@@ -515,16 +634,72 @@ class BrowserCommandOutputImpl extends CommandOutputImpl {
return true;
}
}
+ return false;
+ }
+ bool get _failedBecauseOfUnexpectedDRTOutput {
+ /*
+ * The output of DumpRenderTree is different for pixel tests than for
+ * layout tests.
+ *
+ * On a pixel test, the DRT output has the following format
+ * ......
+ * ......
+ * Content-Length: ...\n
+ * <*png data>
+ * #EOF\n
+ * So we need to get the byte-range of the png data first, before
+ * comparing it with the content of the expected output file.
+ *
+ * On a layout tests, the DRT output is directly compared with the
+ * content of the expected output.
+ */
+ var stdout = testCase.commandOutputs[command].stdout;
+ var file = new File.fromPath(command.expectedOutputFile);
+ if (file.existsSync()) {
+ var bytesContentLength = "Content-Length:".charCodes;
+ var bytesNewLine = "\n".charCodes;
+ var bytesEOF = "#EOF\n".charCodes;
+
+ var expectedContent = file.readAsBytesSync();
+ if (command.isPixelTest) {
+ var startOfContentLength = findBytes(stdout, bytesContentLength);
+ if (startOfContentLength >= 0) {
+ var newLineAfterContentLength = findBytes(stdout,
+ bytesNewLine,
+ startOfContentLength);
+ if (newLineAfterContentLength > 0) {
+ var startPosition = newLineAfterContentLength +
+ bytesNewLine.length;
+ var endPosition = stdout.length - bytesEOF.length;
+
+ return !areByteArraysEqual(expectedContent,
+ 0,
+ stdout,
+ startPosition,
+ endPosition - startPosition);
+ }
+ }
+ return true;
+ } else {
+ return !areByteArraysEqual(expectedContent, 0,
+ stdout, 0,
+ stdout.length);
+ }
+ }
+ return true;
+ }
+
+ bool get _browserTestFailure {
// Browser tests fail unless stdout contains
// 'Content-Type: text/plain' followed by 'PASS'.
bool has_content_type = false;
- for (String line in super.stdout) {
+ var stdoutLines = new String.fromCharCodes(super.stdout).split("\n");
+ for (String line in stdoutLines) {
switch (line) {
case 'Content-Type: text/plain':
has_content_type = true;
break;
-
case 'PASS':
if (has_content_type) {
return (exitCode != 0 && !hasCrashed);
@@ -582,7 +757,8 @@ class AnalysisCommandOutputImpl extends CommandOutputImpl {
List<String> staticWarnings = [];
// Read the returned list of errors and stuff them away.
- for (String line in super.stderr) {
+ var stderrLines = new String.fromCharCodes(super.stderr).split("\n");
+ for (String line in stderrLines) {
if (line.length == 0) continue;
List<String> fields = splitMachineError(line);
if (fields[ERROR_LEVEL] == 'ERROR') {
@@ -731,8 +907,9 @@ class RunningProcess {
bool timedOut = false;
Date startTime;
Timer timeoutTimer;
- List<String> stdout;
- List<String> stderr;
+ List<int> stdout;
+ List<int> stderr;
+ List<String> notifications;
bool compilationSkipped;
bool allowRetries;
@@ -746,6 +923,8 @@ class RunningProcess {
* Called when all commands are executed.
*/
void testComplete(CommandOutput lastCommandOutput) {
+ var command = lastCommandOutput.command;
+
if (timeoutTimer != null) {
timeoutTimer.cancel();
}
@@ -753,8 +932,22 @@ class RunningProcess {
&& testCase.configuration['verbose'] != null
&& testCase.configuration['verbose']) {
print(testCase.displayName);
- for (var line in lastCommandOutput.stderr) print(line);
- for (var line in lastCommandOutput.stdout) print(line);
+
+ print(new String.fromCharCodes(lastCommandOutput.stderr));
+ if (!lastCommandOutput.command.isPixelTest) {
+ print(new String.fromCharCodes(lastCommandOutput.stdout));
+ } else {
+ print("DRT pixel test failed! stdout is not printed because it "
+ "contains binary data!");
+ }
+ print('');
+ if (notifications.length > 0) {
+ print("Notifications:");
+ for (var line in notifications) {
+ print(notifications);
+ }
+ print('');
+ }
}
if (allowRetries && testCase.usesWebDriver
&& lastCommandOutput.unexpectedOutput
@@ -796,14 +989,14 @@ class RunningProcess {
testComplete(createCommandOutput(command, exitCode, false));
} else if (exitCode != 0) {
// One of the steps failed.
- stderr.add('test.dart: Compilation failed$suffix, exit code $exitCode\n');
+ notifications.add('test.dart: Compilation failed$suffix, '
+ 'exit code $exitCode\n');
testComplete(createCommandOutput(command, exitCode, true));
} else {
createCommandOutput(command, exitCode, true);
// One compilation step successfully completed, move on to the
// next step.
- stderr.add('test.dart: Compilation finished $suffix\n');
- stdout.add('test.dart: Compilation finished $suffix\n');
+ notifications.add('test.dart: Compilation finished $suffix\n\n');
if (currentStep == totalSteps - 1 && testCase.usesWebDriver &&
!testCase.configuration['noBatch']) {
// Note: processQueue will always be non-null for runtime == ie9, ie10,
@@ -841,22 +1034,25 @@ class RunningProcess {
}
void resetLocalOutputInformation() {
- stdout = new List<String>();
- stderr = new List<String>();
+ stdout = new List<int>();
+ stderr = new List<int>();
+ notifications = new List<String>();
compilationSkipped = false;
}
- VoidFunction makeReadHandler(StringInputStream source,
- List<String> destination) {
- void handler () {
- if (source.closed) return; // TODO(whesse): Remove when bug is fixed.
- var line = source.readLine();
- while (null != line) {
- destination.add(line);
- line = source.readLine();
+ void drainStream(InputStream source, List<int> destination) {
+ void onDataHandler () {
+ if (source.closed) {
+ return; // TODO(whesse): Remove when bug is fixed.
+ }
+ var data = source.read();
+ while (data != null) {
+ destination.addAll(data);
+ data = source.read();
}
}
- return handler;
+ source.onData = onDataHandler;
+ source.onClosed = onDataHandler;
}
void start() {
@@ -874,14 +1070,20 @@ class RunningProcess {
command.outputIsUpToDate.then((bool isUpToDate) {
if (isUpToDate) {
- stdout.add("Skipped compilation because the old output is "
- "still up to date!");
+ notifications.add("Skipped compilation because the old output is "
+ "still up to date!");
compilationSkipped = true;
commandComplete(command, 0);
} else {
ProcessOptions options = new ProcessOptions();
- options.environment =
- new Map<String, String>.from(Platform.environment);
+ if (command.environment != null) {
+ options.environment =
+ new Map<String, String>.from(command.environment);
+ } else {
+ options.environment =
+ new Map<String, String>.from(Platform.environment);
+ }
+
options.environment['DART_CONFIGURATION'] =
TestUtils.configurationDir(testCase.configuration);
Future processFuture = Process.start(command.executable,
@@ -890,12 +1092,8 @@ class RunningProcess {
processFuture.then((Process p) {
process = p;
process.onExit = processExitHandler;
- var stdoutStringStream = new StringInputStream(process.stdout);
- var stderrStringStream = new StringInputStream(process.stderr);
- stdoutStringStream.onLine =
- makeReadHandler(stdoutStringStream, stdout);
- stderrStringStream.onLine =
- makeReadHandler(stderrStringStream, stderr);
+ drainStream(process.stdout, stdout);
+ drainStream(process.stderr, stderr);
if (timeoutTimer == null) {
// Create one timeout timer when starting test case, remove it at
// the end.
@@ -951,8 +1149,8 @@ class BatchRunnerProcess {
StringInputStream _stderrStream;
TestCase _currentTest;
- List<String> _testStdout;
- List<String> _testStderr;
+ List<int> _testStdout;
+ List<int> _testStderr;
String _status;
bool _stdoutDrained = false;
bool _stderrDrained = false;
@@ -1029,9 +1227,15 @@ class BatchRunnerProcess {
_stdoutDrained = false;
_stderrDrained = false;
_ignoreStreams = new MutableValue<bool>(false); // Captured by closures.
- _stdoutStream.onLine = _readStdout(_stdoutStream, _testStdout);
- _stderrStream.onLine = _readStderr(_stderrStream, _testStderr);
+ _readStdout(_stdoutStream, _testStdout);
+ _readStderr(_stderrStream, _testStderr);
_timer = new Timer(testCase.timeout * 1000, _timeoutHandler);
+
+ if (testCase.commands.last.environment != null) {
+ print("Warning: command.environment != null, but we don't support custom "
+ "environments for batch runner tests!");
+ }
+
var line = _createArgumentsLine(testCase.batchTestArguments);
_process.stdin.onError = (err) {
print('Error on batch runner input stream stdin');
@@ -1081,9 +1285,9 @@ class BatchRunnerProcess {
if (_stderrDrained) _reportResult();
}
- VoidFunction _readStdout(StringInputStream stream, List<String> buffer) {
+ void _readStdout(StringInputStream stream, List<int> buffer) {
var ignoreStreams = _ignoreStreams; // Capture this mutable object.
- void reader() {
+ void onLineHandler() {
if (ignoreStreams.value) {
while (stream.readLine() != null) {
// Do nothing.
@@ -1100,7 +1304,7 @@ class BatchRunnerProcess {
} else if (line.startsWith('>>> ')) {
throw new Exception('Unexpected command from dartc batch runner.');
} else {
- buffer.add(line);
+ buffer.addAll("$line\n".charCodes);
}
line = stream.readLine();
}
@@ -1109,12 +1313,12 @@ class BatchRunnerProcess {
_stdoutDone();
}
}
- return reader;
+ stream.onLine = onLineHandler;
}
- VoidFunction _readStderr(StringInputStream stream, List<String> buffer) {
+ void _readStderr(StringInputStream stream, List<int> buffer) {
var ignoreStreams = _ignoreStreams; // Capture this mutable object.
- void reader() {
+ void onLineHandler() {
if (ignoreStreams.value) {
while (stream.readLine() != null) {
// Do nothing.
@@ -1127,12 +1331,12 @@ class BatchRunnerProcess {
if (line.startsWith('>>> EOF STDERR')) {
_stderrDone();
} else {
- buffer.add(line);
+ buffer.addAll("$line\n".charCodes);
}
line = stream.readLine();
}
}
- return reader;
+ stream.onLine = onLineHandler;
}
ExitCodeEvent makeExitHandler(String status) {
« no previous file with comments | « tools/testing/dart/test_progress.dart ('k') | tools/testing/dart/test_suite.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698