OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 // This is a test harness for running javascript tests in the browser. |
| 6 // The only identifier exposed by this harness is WebGLTestHarnessModule. |
| 7 // |
| 8 // To use it make an HTML page with an iframe. Then call the harness like this |
| 9 // |
| 10 // function reportResults(type, msg, success) { |
| 11 // ... |
| 12 // return true; |
| 13 // } |
| 14 // |
| 15 // var fileListURL = '00_test_list.txt'; |
| 16 // var testHarness = new WebGLTestHarnessModule.TestHarness( |
| 17 // iframe, |
| 18 // fileListURL, |
| 19 // reportResults); |
| 20 // |
| 21 // The harness will load the fileListURL and parse it for the URLs, one URL |
| 22 // per line. URLs should be on the same domain and at the same folder level |
| 23 // or below the main html file. If any URL ends in .txt it will be parsed |
| 24 // as well so you can nest .txt files. URLs inside a .txt file should be |
| 25 // relative to that text file. |
| 26 // |
| 27 // During startup, for each page found the reportFunction will be called with |
| 28 // WebGLTestHarnessModule.TestHarness.reportType.ADD_PAGE and msg will be |
| 29 // the URL of the test. |
| 30 // |
| 31 // Each test is required to call testHarness.reportResults. This is most easily |
| 32 // accomplished by storing that value on the main window with |
| 33 // |
| 34 // window.webglTestHarness = testHarness |
| 35 // |
| 36 // and then adding these to functions to your tests. |
| 37 // |
| 38 // function reportTestResultsToHarness(success, msg) { |
| 39 // if (window.parent.webglTestHarness) { |
| 40 // window.parent.webglTestHarness.reportResults(success, msg); |
| 41 // } |
| 42 // } |
| 43 // |
| 44 // function notifyFinishedToHarness() { |
| 45 // if (window.parent.webglTestHarness) { |
| 46 // window.parent.webglTestHarness.notifyFinished(); |
| 47 // } |
| 48 // } |
| 49 // |
| 50 // This way your tests will still run without the harness and you can use |
| 51 // any testing framework you want. |
| 52 // |
| 53 // Each test should call reportTestResultsToHarness with true for success if it |
| 54 // succeeded and false if it fail followed and any message it wants to |
| 55 // associate with the test. If your testing framework supports checking for |
| 56 // timeout you can call it with success equal to undefined in that case. |
| 57 // |
| 58 // To run the tests, call testHarness.runTests(); |
| 59 // |
| 60 // For each test run, before the page is loaded the reportFunction will be |
| 61 // called with WebGLTestHarnessModule.TestHarness.reportType.START_PAGE and msg |
| 62 // will be the URL of the test. You may return false if you want the test to be |
| 63 // skipped. |
| 64 // |
| 65 // For each test completed the reportFunction will be called with |
| 66 // with WebGLTestHarnessModule.TestHarness.reportType.TEST_RESULT, |
| 67 // success = true on success, false on failure, undefined on timeout |
| 68 // and msg is any message the test choose to pass on. |
| 69 // |
| 70 // When all the tests on the page have finished your page must call |
| 71 // notifyFinishedToHarness. If notifyFinishedToHarness is not called |
| 72 // the harness will assume the test timed out. |
| 73 // |
| 74 // When all the tests on a page have finished OR the page as timed out the |
| 75 // reportFunction will be called with |
| 76 // WebGLTestHarnessModule.TestHarness.reportType.FINISH_PAGE |
| 77 // where success = true if the page has completed or undefined if the page timed |
| 78 // out. |
| 79 // |
| 80 // Finally, when all the tests have completed the reportFunction will be called |
| 81 // with WebGLTestHarnessModule.TestHarness.reportType.FINISHED_ALL_TESTS. |
| 82 // |
| 83 |
| 84 WebGLTestHarnessModule = function() { |
| 85 |
| 86 /** |
| 87 * Wrapped logging function. |
| 88 */ |
| 89 var log = function(msg) { |
| 90 if (window.console && window.console.log) { |
| 91 window.console.log(msg); |
| 92 } |
| 93 }; |
| 94 |
| 95 /** |
| 96 * Loads text from an external file. This function is synchronous. |
| 97 * @param {string} url The url of the external file. |
| 98 * @param {!function(bool, string): void} callback that is sent a bool for |
| 99 * success and the string. |
| 100 */ |
| 101 var loadTextFileAsynchronous = function(url, callback) { |
| 102 log ("loading: " + url); |
| 103 var error = 'loadTextFileSynchronous failed to load url "' + url + '"'; |
| 104 var request; |
| 105 if (window.XMLHttpRequest) { |
| 106 request = new XMLHttpRequest(); |
| 107 if (request.overrideMimeType) { |
| 108 request.overrideMimeType('text/plain'); |
| 109 } |
| 110 } else { |
| 111 throw 'XMLHttpRequest is disabled'; |
| 112 } |
| 113 try { |
| 114 request.open('GET', url, true); |
| 115 request.onreadystatechange = function() { |
| 116 if (request.readyState == 4) { |
| 117 var text = ''; |
| 118 // HTTP reports success with a 200 status. The file protocol reports |
| 119 // success with zero. HTTP does not use zero as a status code (they |
| 120 // start at 100). |
| 121 // https://developer.mozilla.org/En/Using_XMLHttpRequest |
| 122 var success = request.status == 200 || request.status == 0; |
| 123 if (success) { |
| 124 text = request.responseText; |
| 125 } |
| 126 log("loaded: " + url); |
| 127 callback(success, text); |
| 128 } |
| 129 }; |
| 130 request.send(null); |
| 131 } catch (e) { |
| 132 log("failed to load: " + url); |
| 133 callback(false, ''); |
| 134 } |
| 135 }; |
| 136 |
| 137 var getFileList = function(url, callback) { |
| 138 var files = []; |
| 139 |
| 140 var getFileListImpl = function(url, callback) { |
| 141 var files = []; |
| 142 if (url.substr(url.length - 4) == '.txt') { |
| 143 loadTextFileAsynchronous(url, function() { |
| 144 return function(success, text) { |
| 145 if (!success) { |
| 146 callback(false, ''); |
| 147 return; |
| 148 } |
| 149 var lines = text.split('\n'); |
| 150 var prefix = ''; |
| 151 var lastSlash = url.lastIndexOf('/'); |
| 152 if (lastSlash >= 0) { |
| 153 prefix = url.substr(0, lastSlash + 1); |
| 154 } |
| 155 var fail = false; |
| 156 var count = 1; |
| 157 var index = 0; |
| 158 for (var ii = 0; ii < lines.length; ++ii) { |
| 159 var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, ''); |
| 160 if (str.length > 4 && |
| 161 str[0] != '#' && |
| 162 str[0] != ";" && |
| 163 str.substr(0, 2) != "//") { |
| 164 new_url = prefix + str; |
| 165 ++count; |
| 166 getFileListImpl(new_url, function(index) { |
| 167 return function(success, new_files) { |
| 168 log("got files: " + new_files.length); |
| 169 if (success) { |
| 170 files[index] = new_files; |
| 171 } |
| 172 finish(success); |
| 173 }; |
| 174 }(index++)); |
| 175 } |
| 176 } |
| 177 finish(true); |
| 178 |
| 179 function finish(success) { |
| 180 if (!success) { |
| 181 fail = true; |
| 182 } |
| 183 --count; |
| 184 log("count: " + count); |
| 185 if (!count) { |
| 186 callback(!fail, files); |
| 187 } |
| 188 } |
| 189 } |
| 190 }()); |
| 191 |
| 192 } else { |
| 193 files.push(url); |
| 194 callback(true, files); |
| 195 } |
| 196 }; |
| 197 |
| 198 getFileListImpl(url, function(success, files) { |
| 199 // flatten |
| 200 var flat = []; |
| 201 flatten(files); |
| 202 function flatten(files) { |
| 203 for (var ii = 0; ii < files.length; ++ii) { |
| 204 var value = files[ii]; |
| 205 if (typeof(value) == "string") { |
| 206 flat.push(value); |
| 207 } else { |
| 208 flatten(value); |
| 209 } |
| 210 } |
| 211 } |
| 212 callback(success, flat); |
| 213 }); |
| 214 }; |
| 215 |
| 216 var TestFile = function(url) { |
| 217 this.url = url; |
| 218 }; |
| 219 |
| 220 var TestHarness = function(iframe, filelistUrl, reportFunc) { |
| 221 this.window = window; |
| 222 this.iframe = iframe; |
| 223 this.reportFunc = reportFunc; |
| 224 this.timeoutDelay = 20000; |
| 225 this.files = []; |
| 226 |
| 227 var that = this; |
| 228 getFileList(filelistUrl, function() { |
| 229 return function(success, files) { |
| 230 that.addFiles_(success, files); |
| 231 }; |
| 232 }()); |
| 233 |
| 234 }; |
| 235 |
| 236 TestHarness.reportType = { |
| 237 ADD_PAGE: 1, |
| 238 READY: 2, |
| 239 START_PAGE: 3, |
| 240 TEST_RESULT: 4, |
| 241 FINISH_PAGE: 5, |
| 242 FINISHED_ALL_TESTS: 6 |
| 243 }; |
| 244 |
| 245 TestHarness.prototype.addFiles_ = function(success, files) { |
| 246 if (!success) { |
| 247 this.reportFunc( |
| 248 TestHarness.reportType.FINISHED_ALL_TESTS, |
| 249 'Unable to load tests. Are you running locally?\n' + |
| 250 'You need to run from a server or configure your\n' + |
| 251 'browser to allow access to local files (not recommended).\n\n' + |
| 252 'Note: An easy way to run from a server:\n\n' + |
| 253 '\tcd path_to_tests\n' + |
| 254 '\tpython -m SimpleHTTPServer\n\n' + |
| 255 'then point your browser to ' + |
| 256 '<a href="http://localhost:8000/webgl-conformance-tests.html">' + |
| 257 'http://localhost:8000/webgl-conformance-tests.html</a>', |
| 258 false) |
| 259 return; |
| 260 } |
| 261 log("total files: " + files.length); |
| 262 for (var ii = 0; ii < files.length; ++ii) { |
| 263 log("" + ii + ": " + files[ii]); |
| 264 this.files.push(new TestFile(files[ii])); |
| 265 this.reportFunc(TestHarness.reportType.ADD_PAGE, files[ii], undefined); |
| 266 } |
| 267 this.reportFunc(TestHarness.reportType.READY, undefined, undefined); |
| 268 this.nextFileIndex = files.length; |
| 269 this.lastFileIndex = files.length; |
| 270 } |
| 271 |
| 272 TestHarness.prototype.runTests = function(opt_start, opt_count) { |
| 273 var count = opt_count || this.files.length; |
| 274 this.nextFileIndex = opt_start || 0; |
| 275 this.lastFileIndex = this.nextFileIndex + count; |
| 276 this.startNextFile(); |
| 277 }; |
| 278 |
| 279 TestHarness.prototype.setTimeout = function() { |
| 280 var that = this; |
| 281 this.timeoutId = this.window.setTimeout(function() { |
| 282 that.timeout(); |
| 283 }, this.timeoutDelay); |
| 284 }; |
| 285 |
| 286 TestHarness.prototype.clearTimeout = function() { |
| 287 this.window.clearTimeout(this.timeoutId); |
| 288 }; |
| 289 |
| 290 TestHarness.prototype.startNextFile = function() { |
| 291 if (this.nextFileIndex >= this.lastFileIndex) { |
| 292 log("done"); |
| 293 this.reportFunc(TestHarness.reportType.FINISHED_ALL_TESTS, |
| 294 '', true); |
| 295 } else { |
| 296 this.currentFile = this.files[this.nextFileIndex++]; |
| 297 log("loading: " + this.currentFile.url); |
| 298 if (this.reportFunc(TestHarness.reportType.START_PAGE, |
| 299 this.currentFile.url, undefined)) { |
| 300 this.iframe.src = this.currentFile.url; |
| 301 this.setTimeout(); |
| 302 } else { |
| 303 this.reportResults(false, "skipped"); |
| 304 this.notifyFinished(); |
| 305 } |
| 306 } |
| 307 }; |
| 308 |
| 309 TestHarness.prototype.reportResults = function (success, msg) { |
| 310 this.clearTimeout(); |
| 311 log(success ? "PASS" : "FAIL", msg); |
| 312 this.reportFunc(TestHarness.reportType.TEST_RESULT, msg, success); |
| 313 // For each result we get, reset the timeout |
| 314 this.setTimeout(); |
| 315 }; |
| 316 |
| 317 TestHarness.prototype.notifyFinished = function () { |
| 318 this.clearTimeout(); |
| 319 var url = this.currentFile ? this.currentFile.url : 'unknown'; |
| 320 log(url + ": finished"); |
| 321 this.reportFunc(TestHarness.reportType.FINISH_PAGE, url, true); |
| 322 this.startNextFile(); |
| 323 }; |
| 324 |
| 325 TestHarness.prototype.timeout = function() { |
| 326 this.clearTimeout(); |
| 327 var url = this.currentFile ? this.currentFile.url : 'unknown'; |
| 328 log(url + ": timeout"); |
| 329 this.reportFunc(TestHarness.reportType.FINISH_PAGE, url, undefined); |
| 330 this.startNextFile(); |
| 331 }; |
| 332 |
| 333 TestHarness.prototype.setTimeoutDelay = function(x) { |
| 334 this.timeoutDelay = x; |
| 335 }; |
| 336 |
| 337 return { |
| 338 'TestHarness': TestHarness |
| 339 }; |
| 340 |
| 341 }(); |
| 342 |
| 343 |
| 344 |
OLD | NEW |