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

Side by Side Diff: utils/testrunner/testrunner.dart

Issue 10977069: New testrunner that runs the test pipleine in an isolate. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 2 months 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 //#!/usr/bin/env dart 1 //#!/usr/bin/env dart
2 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 2 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
3 // for details. All rights reserved. Use of this source code is governed by a 3 // for details. All rights reserved. Use of this source code is governed by a
4 // BSD-style license that can be found in the LICENSE file. 4 // BSD-style license that can be found in the LICENSE file.
5 5
6 /** 6 /**
7 * testrunner is a program to run Dart unit tests. Unlike $DART/tools/test.dart, 7 * testrunner is a program to run Dart unit tests. Unlike $DART/tools/test.dart,
8 * this program is intended for 3rd parties to be able to run unit tests in 8 * this program is intended for 3rd parties to be able to run unit tests in
9 * a batched fashion. As such, it adds some features and removes others. Some 9 * a batched fashion. As such, it adds some features and removes others. Some
10 * of the removed features are: 10 * of the removed features are:
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
55 * When running layout tests, testrunner will see if there is a file with 55 * When running layout tests, testrunner will see if there is a file with
56 * a .png or a .txt extension in a directory with the same name as the 56 * a .png or a .txt extension in a directory with the same name as the
57 * test file (without extension) and with the test name as the file name. 57 * test file (without extension) and with the test name as the file name.
58 * For example, if there is a test file foo_test.dart with tests 'test1' 58 * For example, if there is a test file foo_test.dart with tests 'test1'
59 * and 'test2', it will look for foo_test/test1.txt and foo_test/test2.txt 59 * and 'test2', it will look for foo_test/test1.txt and foo_test/test2.txt
60 * for text render layout files. If these exist it will do additional checks 60 * for text render layout files. If these exist it will do additional checks
61 * of the rendered layout; if not, the test will fail. 61 * of the rendered layout; if not, the test will fail.
62 * 62 *
63 * Layout file (re)generation can be done using `--regenerate`. This will 63 * Layout file (re)generation can be done using `--regenerate`. This will
64 * create or update the layout files (and implicitly pass the tests). 64 * create or update the layout files (and implicitly pass the tests).
65 *
66 * The wrapping and execution of test files is handled by test_pipeline.dart,
67 * which is run in an isolate. The `--pipeline` argument can be used to
68 * specify a different script for running a test file pipeline, allowing
69 * customization of the pipeline.
65 */ 70 */
66 71
67 // TODO - layout tests that use PNGs rather than DRT text render dumps. 72 // TODO - layout tests that use PNGs rather than DRT text render dumps.
68 #library('testrunner'); 73 #library('testrunner');
69 #import('dart:io'); 74 #import('dart:io');
70 #import('dart:isolate'); 75 #import('dart:isolate');
71 #import('dart:math'); 76 #import('dart:math');
72 #import('../../pkg/args/lib/args.dart'); 77 #import('../../pkg/args/lib/args.dart');
73 78
74 #source('client_server_task.dart');
75 #source('configuration.dart');
76 #source('dart_wrap_task.dart');
77 #source('dart2js_task.dart');
78 #source('delete_task.dart');
79 #source('html_wrap_task.dart');
80 #source('macros.dart');
81 #source('options.dart'); 79 #source('options.dart');
82 #source('pipeline_runner.dart');
83 #source('pipeline_task.dart');
84 #source('run_process_task.dart');
85 #source('utils.dart'); 80 #source('utils.dart');
86 81
87 /** The set of [PipelineRunner]s to execute. */ 82 /** The set of [PipelineRunner]s to execute. */
88 List _tasks; 83 List _tasks;
89 84
90 /** The maximum number of pipelines that can run concurrently. */ 85 /** The maximum number of pipelines that can run concurrently. */
91 int _maxTasks; 86 int _maxTasks;
92 87
93 /** The number of pipelines currently running. */ 88 /** The number of pipelines currently running. */
94 int _numTasks; 89 int _numTasks;
95 90
96 /** The index of the next pipeline runner to execute. */ 91 /** The index of the next pipeline runner to execute. */
97 int _nextTask; 92 int _nextTask;
98 93
99 /** The stream to use for high-value messages, like test results. */ 94 /** The stream to use for high-value messages, like test results. */
100 OutputStream _outStream; 95 OutputStream _outStream;
101 96
102 /** The stream to use for low-value messages, like verbose output. */ 97 /** The stream to use for low-value messages, like verbose output. */
103 OutputStream _logStream; 98 OutputStream _logStream;
104 99
105 /** The full set of options. */
106 Configuration config;
107
108 /** 100 /**
109 * The user can specify output streams on the command line, using 'none', 101 * The user can specify output streams on the command line, using 'none',
110 * 'stdout', 'stderr', or a file path; [getStream] will take such a name 102 * 'stdout', 'stderr', or a file path; [getStream] will take such a name
111 * and return an appropriate [OutputStream]. 103 * and return an appropriate [OutputStream].
112 */ 104 */
113 OutputStream getStream(String name) { 105 OutputStream getStream(String name) {
114 if (name == 'none') { 106 if (name == null || name == 'none') {
115 return null; 107 return null;
116 } 108 }
117 if (name == 'stdout') { 109 if (name == 'stdout') {
118 return stdout; 110 return stdout;
119 } 111 }
120 if (name == 'stderr') { 112 if (name == 'stderr') {
121 return stderr; 113 return stderr;
122 } 114 }
123 return new File(name).openOutputStream(FileMode.WRITE); 115 return new File(name).openOutputStream(FileMode.WRITE);
124 } 116 }
125 117
126 /** 118 /**
127 * Generate a templated list of commands that should be executed for each test
128 * file. Each command is an instance of a [PipelineTask].
129 * The commands can make use of a number of metatokens that will be
130 * expanded before execution (see the [Meta] class for details).
131 */
132 List getPipelineTemplate(String runtime, bool checkedMode, bool keepTests) {
133 var pipeline = new List();
134 var pathSep = Platform.pathSeparator;
135 Directory tempDir = new Directory(config.tempDir);
136
137 if (!tempDir.existsSync()) {
138 tempDir.createSync();
139 }
140
141 // Templates for the generated files that are used to run the wrapped test.
142 var basePath =
143 '${config.tempDir}$pathSep${Macros.flattenedDirectory}_'
144 '${Macros.filenameNoExtension}';
145 var tempDartFile = '${basePath}.dart';
146 var tempJsFile = '${basePath}.js';
147 var tempHTMLFile = '${basePath}.html';
148 var tempCSSFile = '${basePath}.css';
149
150 // Add step for wrapping in Dart scaffold.
151 pipeline.add(new DartWrapTask(Macros.fullFilePath, tempDartFile));
152
153 // Add the compiler step, unless we are running native Dart.
154 if (runtime == 'drt-js') {
155 if (checkedMode) {
156 pipeline.add(new Dart2jsTask.checked(tempDartFile, tempJsFile));
157 } else {
158 pipeline.add(new Dart2jsTask(tempDartFile, tempJsFile));
159 }
160 }
161
162 // Add step for wrapping in HTML, if we are running in DRT.
163 if (runtime != 'vm') {
164 // The user can have pre-existing HTML and CSS files for the test in the
165 // same directory and using the same name. The paths to these are matched
166 // by these two templates.
167 var HTMLFile =
168 '${Macros.directory}$pathSep${Macros.filenameNoExtension}.html';
169 var CSSFile =
170 '${Macros.directory}$pathSep${Macros.filenameNoExtension}.css';
171 pipeline.add(new HtmlWrapTask(Macros.fullFilePath,
172 HTMLFile, tempHTMLFile, CSSFile, tempCSSFile));
173 }
174
175 // Add the execution step.
176 var command;
177 var flags;
178 var task;
179 if (runtime == 'vm' || config.layoutPixel || config.layoutText) {
180 command = config.dartPath;
181 if (checkedMode) {
182 flags = ['--enable_asserts', '--enable_type_checks', tempDartFile];
183 } else {
184 flags = [tempDartFile];
185 }
186 } else {
187 command = config.drtPath;
188 flags = ['--no-timeout', tempHTMLFile];
189 }
190 if (config.runServer) {
191 task = new RunClientServerTask(command, flags, config.timeout);
192 } else {
193 task = new RunProcessTask(command, flags, config.timeout);
194 }
195 pipeline.add(task);
196 return pipeline;
197 }
198
199 /**
200 * Given a [List] of [testFiles], either print the list or create 119 * Given a [List] of [testFiles], either print the list or create
201 * and execute pipelines for the files. 120 * and execute pipelines for the files.
202 */ 121 */
203 void processTests(List pipelineTemplate, List testFiles) { 122 void processTests(Map config, List testFiles) {
204 _outStream = getStream(config.outputStream); 123 _outStream = getStream(config['out']);
205 _logStream = getStream(config.logStream); 124 _logStream = getStream(config['log']);
206 if (config.listFiles) { 125 if (config['list-files']) {
207 if (_outStream != null) { 126 if (_outStream != null) {
208 for (var i = 0; i < testFiles.length; i++) { 127 for (var i = 0; i < testFiles.length; i++) {
209 _outStream.writeString(testFiles[i]); 128 _outStream.writeString(testFiles[i]);
210 _outStream.writeString('\n'); 129 _outStream.writeString('\n');
211 } 130 }
212 } 131 }
213 } else { 132 } else {
214 // Create execution pipelines for each test file from the pipeline 133 _maxTasks = min(config['tasks'], testFiles.length);
215 // template and the concrete test file path, and then kick
216 // off execution of the first batch.
217 _tasks = new List();
218 for (var i = 0; i < testFiles.length; i++) {
219 _tasks.add(new PipelineRunner(pipelineTemplate, testFiles[i],
220 config.verbose, completeHandler));
221 }
222
223 _maxTasks = min(config.maxTasks, testFiles.length);
224 _numTasks = 0; 134 _numTasks = 0;
225 _nextTask = 0; 135 _nextTask = 0;
226 spawnTasks(); 136 spawnTasks(config, testFiles);
227 } 137 }
228 } 138 }
229 139
230 /** Execute as many tasks as possible up to the maxTasks limit. */ 140 /** Execute as many tasks as possible up to the maxTasks limit. */
231 void spawnTasks() { 141 void spawnTasks(Map config, List testFiles) {
232 while (_numTasks < _maxTasks && _nextTask < _tasks.length) { 142 var verbose = config['verbose'];
143 while (_numTasks < _maxTasks && _nextTask < testFiles.length) {
233 ++_numTasks; 144 ++_numTasks;
234 _tasks[_nextTask++].execute(); 145 var testfile = testFiles[_nextTask++];
235 } 146 config['testfile'] = testfile;
236 } 147 ReceivePort port = new ReceivePort();
237 148 port.receive((msg, _) {
238 /** 149 port.close();
239 * Handle the completion of a task. Kick off more tasks if we 150 List stdout = msg[0];
240 * have them. 151 List stderr = msg[1];
241 */ 152 List log = msg[2];
242 void completeHandler(String testFile, 153 int exitCode = msg[3];
243 int exitCode, 154 writelog(stdout, _outStream, _logStream, verbose);
244 List _stdout, 155 writelog(stderr, _outStream, _logStream, true);
245 List _stderr) { 156 writelog(log, _outStream, _logStream, verbose);
246 writelog(_stdout, _outStream, _logStream); 157 --_numTasks;
247 writelog(_stderr, _outStream, _logStream); 158 if (exitCode == 0 || !config['stopOnFailure']) {
248 --_numTasks; 159 spawnTasks(config, testFiles);
249 if (exitCode == 0 || !config.stopOnFailure) { 160 }
250 spawnTasks(); 161 if (_numTasks == 0) {
251 } 162 // No outstanding tasks; we're all done.
252 if (_numTasks == 0) { 163 // We could later print a summary report here.
253 // No outstanding tasks; we're all done. 164 }
254 // We could later print a summary report here. 165 });
166 SendPort s = spawnUri(config['pipeline']);
167 s.send(config, port.toSendPort());
255 } 168 }
256 } 169 }
257 170
258 /** 171 /**
259 * Our tests are configured so that critical messages have a '###' prefix. 172 * Our tests are configured so that critical messages have a '###' prefix.
260 * [writeLog] takes the output from a pipeline execution and writes it to 173 * [writeLog] takes the output from a pipeline execution and writes it to
261 * our output streams. It will strip the '###' if necessary on critical 174 * our output streams. It will strip the '###' if necessary on critical
262 * messages; other messages will only be written if verbose output was 175 * messages; other messages will only be written if verbose output was
263 * specified. 176 * specified.
264 */ 177 */
265 void writelog(List messages, OutputStream out, OutputStream log) { 178 void writelog(List messages, OutputStream out, OutputStream log, bool verbose) {
266 for (var i = 0; i < messages.length; i++) { 179 for (var i = 0; i < messages.length; i++) {
267 var msg = messages[i]; 180 var msg = messages[i];
268 if (msg.startsWith('###')) { 181 if (msg.startsWith('###')) {
269 if (out != null) { 182 if (out != null) {
270 out.writeString(msg.substring(3)); 183 out.writeString(msg.substring(3));
271 out.writeString('\n'); 184 out.writeString('\n');
272 } 185 }
273 } else if (config.verbose) { 186 } else if (verbose) {
274 if (log != null) { 187 if (log != null) {
275 log.writeString(msg); 188 log.writeString(msg);
276 log.writeString('\n'); 189 log.writeString('\n');
277 } 190 }
278 } 191 }
279 } 192 }
280 } 193 }
281 194
195 sanitizeConfig(Map config, ArgParser parser) {
196 config['layout'] = config['layout-text'] || config['layout-pixel'];
197
198 // TODO - check if next three are actually used.
199 config['runInBrowser'] = (config['runtime'] != 'vm');
200 config['verbose'] = (config['log'] != 'none' && !config['list-groups']);
201 config['filtering'] = (config['include'].length > 0 ||
202 config['exclude'].length > 0);
203
204 config['timeout'] = int.parse(config['timeout']);
205 config['tasks'] = int.parse(config['tasks']);
206
207 config['keep-files'] = (config['keep-files'] &&
208 !(config['list-groups'] || config['list-tests']));
209
210 var dartsdk = config['dartsdk'];
211 var pathSep = Platform.pathSeparator;
212
213 if (dartsdk != null) {
214 if (parser.getDefault('dart2js') == config['dart2js']) {
215 config['dart2js'] =
216 '$dartsdk${pathSep}dart-sdk${pathSep}bin${pathSep}dart2js';
217 }
218 if (parser.getDefault('dart') == config['dart']) {
219 config['dart'] = '$dartsdk${pathSep}dart-sdk${pathSep}bin${pathSep}dart';
220 }
221 if (parser.getDefault('drt') == config['drt']) {
222 config['drt'] = '$dartsdk${pathSep}chromium${pathSep}DumpRenderTree';
223 }
224 }
225
226 config['unittest'] = makePathAbsolute(config['unittest']);
227 config['drt'] = makePathAbsolute(config['drt']);
228 config['dart'] = makePathAbsolute(config['dart']);
229 config['dart2js'] = makePathAbsolute(config['dart2js']);
230 config['runnerDir'] = runnerDirectory;
231 }
232
282 main() { 233 main() {
283 var optionsParser = getOptionParser(); 234 var optionsParser = getOptionParser();
284 var options = loadConfiguration(optionsParser); 235 var options = loadConfiguration(optionsParser);
285 if (isSane(options)) { 236 if (isSane(options)) {
286 if (options['list-options']) { 237 if (options['list-options']) {
287 printOptions(optionsParser, options, false, stdout); 238 printOptions(optionsParser, options, false, stdout);
288 } else if (options['list-all-options']) { 239 } else if (options['list-all-options']) {
289 printOptions(optionsParser, options, true, stdout); 240 printOptions(optionsParser, options, true, stdout);
290 } else { 241 } else {
291 config = new Configuration(optionsParser, options); 242 var config = new Map();
292 // Build the command templates needed for test compile and execute. 243 for (var option in options.options) {
293 var pipelineTemplate = getPipelineTemplate(config.runtime, 244 config[option] = options[option];
294 config.checkedMode, 245 }
295 config.keepTests); 246 var rest = [];
296 if (pipelineTemplate != null) { 247 // Process the remmaining command line args. If they look like
297 // Build the list of tests and then execute them. 248 // options then split them up and add them to the map; they may be for
298 List dirs = options.rest; 249 // custom pipelines.
299 if (dirs.length == 0) { 250 for (var other in options.rest) {
300 dirs.add('.'); // Use current working directory as default. 251 var idx;
252 if (other.startsWith('--') && (idx = other.indexOf('=')) > 0) {
253 var optName = other.substring(2, idx);
254 var optValue = other.substring(idx+1);
255 config[optName] = optValue;
256 } else {
257 rest.add(other);
301 } 258 }
302 buildFileList(dirs,
303 new RegExp(options['test-file-pattern']), options['recurse'],
304 (f) => processTests(pipelineTemplate, f));
305 } 259 }
260
261 sanitizeConfig(config, optionsParser);
262
263 // Build the list of tests and then execute them.
264 List dirs = rest;
265 if (dirs.length == 0) {
266 dirs.add('.'); // Use current working directory as default.
267 }
268 buildFileList(dirs,
269 new RegExp(options['test-file-pattern']), options['recurse'],
270 (f) => processTests(config, f));
306 } 271 }
307 } 272 }
308 } 273 }
309 274
310 275
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698