OLD | NEW |
(Empty) | |
| 1 /* |
| 2 ** Copyright (c) 2012 The Khronos Group Inc. |
| 3 ** |
| 4 ** Permission is hereby granted, free of charge, to any person obtaining a |
| 5 ** copy of this software and/or associated documentation files (the |
| 6 ** "Materials"), to deal in the Materials without restriction, including |
| 7 ** without limitation the rights to use, copy, modify, merge, publish, |
| 8 ** distribute, sublicense, and/or sell copies of the Materials, and to |
| 9 ** permit persons to whom the Materials are furnished to do so, subject to |
| 10 ** the following conditions: |
| 11 ** |
| 12 ** The above copyright notice and this permission notice shall be included |
| 13 ** in all copies or substantial portions of the Materials. |
| 14 ** |
| 15 ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| 16 ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| 17 ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| 18 ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| 19 ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| 20 ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| 21 ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. |
| 22 */ |
| 23 |
| 24 // This is a test harness for running javascript tests in the browser. |
| 25 // The only identifier exposed by this harness is WebGLTestHarnessModule. |
| 26 // |
| 27 // To use it make an HTML page with an iframe. Then call the harness like this |
| 28 // |
| 29 // function reportResults(type, msg, success) { |
| 30 // ... |
| 31 // return true; |
| 32 // } |
| 33 // |
| 34 // var fileListURL = '00_test_list.txt'; |
| 35 // var testHarness = new WebGLTestHarnessModule.TestHarness( |
| 36 // iframe, |
| 37 // fileListURL, |
| 38 // reportResults, |
| 39 // options); |
| 40 // |
| 41 // The harness will load the fileListURL and parse it for the URLs, one URL |
| 42 // per line preceded by options, see below. URLs should be on the same domain |
| 43 // and at the same folder level or below the main html file. If any URL ends |
| 44 // in .txt it will be parsed as well so you can nest .txt files. URLs inside a |
| 45 // .txt file should be relative to that text file. |
| 46 // |
| 47 // During startup, for each page found the reportFunction will be called with |
| 48 // WebGLTestHarnessModule.TestHarness.reportType.ADD_PAGE and msg will be |
| 49 // the URL of the test. |
| 50 // |
| 51 // Each test is required to call testHarness.reportResults. This is most easily |
| 52 // accomplished by storing that value on the main window with |
| 53 // |
| 54 // window.webglTestHarness = testHarness |
| 55 // |
| 56 // and then adding these to functions to your tests. |
| 57 // |
| 58 // function reportTestResultsToHarness(success, msg) { |
| 59 // if (window.parent.webglTestHarness) { |
| 60 // window.parent.webglTestHarness.reportResults(success, msg); |
| 61 // } |
| 62 // } |
| 63 // |
| 64 // function notifyFinishedToHarness() { |
| 65 // if (window.parent.webglTestHarness) { |
| 66 // window.parent.webglTestHarness.notifyFinished(); |
| 67 // } |
| 68 // } |
| 69 // |
| 70 // This way your tests will still run without the harness and you can use |
| 71 // any testing framework you want. |
| 72 // |
| 73 // Each test should call reportTestResultsToHarness with true for success if it |
| 74 // succeeded and false if it fail followed and any message it wants to |
| 75 // associate with the test. If your testing framework supports checking for |
| 76 // timeout you can call it with success equal to undefined in that case. |
| 77 // |
| 78 // To run the tests, call testHarness.runTests(); |
| 79 // |
| 80 // For each test run, before the page is loaded the reportFunction will be |
| 81 // called with WebGLTestHarnessModule.TestHarness.reportType.START_PAGE and msg |
| 82 // will be the URL of the test. You may return false if you want the test to be |
| 83 // skipped. |
| 84 // |
| 85 // For each test completed the reportFunction will be called with |
| 86 // with WebGLTestHarnessModule.TestHarness.reportType.TEST_RESULT, |
| 87 // success = true on success, false on failure, undefined on timeout |
| 88 // and msg is any message the test choose to pass on. |
| 89 // |
| 90 // When all the tests on the page have finished your page must call |
| 91 // notifyFinishedToHarness. If notifyFinishedToHarness is not called |
| 92 // the harness will assume the test timed out. |
| 93 // |
| 94 // When all the tests on a page have finished OR the page as timed out the |
| 95 // reportFunction will be called with |
| 96 // WebGLTestHarnessModule.TestHarness.reportType.FINISH_PAGE |
| 97 // where success = true if the page has completed or undefined if the page timed |
| 98 // out. |
| 99 // |
| 100 // Finally, when all the tests have completed the reportFunction will be called |
| 101 // with WebGLTestHarnessModule.TestHarness.reportType.FINISHED_ALL_TESTS. |
| 102 // |
| 103 // Harness Options |
| 104 // |
| 105 // These are passed in to the TestHarness as a JavaScript object |
| 106 // |
| 107 // version: (required!) |
| 108 // |
| 109 // Specifies a version used to filter tests. Tests marked as requiring |
| 110 // a version greater than this version will not be included. |
| 111 // |
| 112 // example: new TestHarness(...., {version: "3.1.2"}); |
| 113 // |
| 114 // minVersion: |
| 115 // |
| 116 // Specifies the minimum version a test must require to be included. |
| 117 // This basically flips the filter so that only tests marked with |
| 118 // --min-version will be included if they are at this minVersion or |
| 119 // greater. |
| 120 // |
| 121 // example: new TestHarness(...., {minVersion: "2.3.1"}); |
| 122 // |
| 123 // fast: |
| 124 // |
| 125 // Specifies to skip any tests marked as slow. |
| 126 // |
| 127 // example: new TestHarness(..., {fast: true}); |
| 128 // |
| 129 // Test Options: |
| 130 // |
| 131 // Any test URL or .txt file can be prefixed by the following options |
| 132 // |
| 133 // min-version: |
| 134 // |
| 135 // Sets the minimum version requires to include this test. A version is |
| 136 // passed into the harness options. Any test marked as requiring a |
| 137 // min-version greater than the version passed to the harness is skipped. |
| 138 // This allows you to add new tests to a suite of tests for a future |
| 139 // version of the suite without including the test in the current version. |
| 140 // If no -min-version is specified it is inheriited from the .txt file |
| 141 // including it. The default is 1.0.0 |
| 142 // |
| 143 // example: --min-version 2.1.3 sometest.html |
| 144 // |
| 145 // slow: |
| 146 // |
| 147 // Marks a test as slow. Slow tests can be skipped by passing fastOnly: true |
| 148 // to the TestHarness. Of course you need to pass all tests but sometimes |
| 149 // you'd like to test quickly and run only the fast subset of tests. |
| 150 // |
| 151 // example: --slow some-test-that-takes-2-mins.html |
| 152 // |
| 153 |
| 154 WebGLTestHarnessModule = function() { |
| 155 |
| 156 /** |
| 157 * Wrapped logging function. |
| 158 */ |
| 159 var log = function(msg) { |
| 160 if (window.console && window.console.log) { |
| 161 window.console.log(msg); |
| 162 } |
| 163 }; |
| 164 |
| 165 /** |
| 166 * Loads text from an external file. This function is synchronous. |
| 167 * @param {string} url The url of the external file. |
| 168 * @param {!function(bool, string): void} callback that is sent a bool for |
| 169 * success and the string. |
| 170 */ |
| 171 var loadTextFileAsynchronous = function(url, callback) { |
| 172 log ("loading: " + url); |
| 173 var error = 'loadTextFileSynchronous failed to load url "' + url + '"'; |
| 174 var request; |
| 175 if (window.XMLHttpRequest) { |
| 176 request = new XMLHttpRequest(); |
| 177 if (request.overrideMimeType) { |
| 178 request.overrideMimeType('text/plain'); |
| 179 } |
| 180 } else { |
| 181 throw 'XMLHttpRequest is disabled'; |
| 182 } |
| 183 try { |
| 184 request.open('GET', url, true); |
| 185 request.onreadystatechange = function() { |
| 186 if (request.readyState == 4) { |
| 187 var text = ''; |
| 188 // HTTP reports success with a 200 status. The file protocol reports |
| 189 // success with zero. HTTP does not use zero as a status code (they |
| 190 // start at 100). |
| 191 // https://developer.mozilla.org/En/Using_XMLHttpRequest |
| 192 var success = request.status == 200 || request.status == 0; |
| 193 if (success) { |
| 194 text = request.responseText; |
| 195 } |
| 196 log("loaded: " + url); |
| 197 callback(success, text); |
| 198 } |
| 199 }; |
| 200 request.send(null); |
| 201 } catch (e) { |
| 202 log("failed to load: " + url); |
| 203 callback(false, ''); |
| 204 } |
| 205 }; |
| 206 |
| 207 /** |
| 208 * Compare version strings. |
| 209 */ |
| 210 var greaterThanOrEqualToVersion = function(have, want) { |
| 211 have = have.split(" ")[0].split("."); |
| 212 want = want.split(" ")[0].split("."); |
| 213 |
| 214 //have 1.2.3 want 1.1 |
| 215 //have 1.1.1 want 1.1 |
| 216 //have 1.0.9 want 1.1 |
| 217 //have 1.1 want 1.1.1 |
| 218 |
| 219 for (var ii = 0; ii < want.length; ++ii) { |
| 220 var wantNum = parseInt(want[ii]); |
| 221 var haveNum = have[ii] ? parseInt(have[ii]) : 0 |
| 222 if (haveNum < wantNum) { |
| 223 return false; |
| 224 } |
| 225 } |
| 226 return true; |
| 227 }; |
| 228 |
| 229 /** |
| 230 * Reads a file, recursively adding files referenced inside. |
| 231 * |
| 232 * Each line of URL is parsed, comments starting with '#' or ';' |
| 233 * or '//' are stripped. |
| 234 * |
| 235 * arguments beginning with -- are extracted |
| 236 * |
| 237 * lines that end in .txt are recursively scanned for more files |
| 238 * other lines are added to the list of files. |
| 239 * |
| 240 * @param {string} url The url of the file to read. |
| 241 * @param {void function(boolean, !Array.<string>)} callback. |
| 242 * Callback that is called with true for success and an |
| 243 * array of filenames. |
| 244 * @param {Object} options. Optional options |
| 245 * |
| 246 * Options: |
| 247 * version: {string} The version of the conformance test. |
| 248 * Tests with the argument --min-version <version> will |
| 249 * be ignored version is less then <version> |
| 250 * |
| 251 */ |
| 252 var getFileList = function(url, callback, options) { |
| 253 var files = []; |
| 254 |
| 255 var copyObject = function(obj) { |
| 256 return JSON.parse(JSON.stringify(obj)); |
| 257 }; |
| 258 |
| 259 var toCamelCase = function(str) { |
| 260 return str.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase() }); |
| 261 }; |
| 262 |
| 263 var globalOptions = copyObject(options); |
| 264 globalOptions.defaultVersion = "1.0"; |
| 265 |
| 266 var getFileListImpl = function(prefix, line, lineNum, hierarchicalOptions, cal
lback) { |
| 267 var files = []; |
| 268 |
| 269 var args = line.split(/\s+/); |
| 270 var nonOptions = []; |
| 271 var useTest = true; |
| 272 var testOptions = {}; |
| 273 for (var jj = 0; jj < args.length; ++jj) { |
| 274 var arg = args[jj]; |
| 275 if (arg[0] == '-') { |
| 276 if (arg[1] != '-') { |
| 277 throw ("bad option at in " + url + ":" + lineNum + ": " + arg); |
| 278 } |
| 279 var option = arg.substring(2); |
| 280 switch (option) { |
| 281 // no argument options. |
| 282 case 'slow': |
| 283 testOptions[toCamelCase(option)] = true; |
| 284 break; |
| 285 // one argument options. |
| 286 case 'min-version': |
| 287 ++jj; |
| 288 testOptions[toCamelCase(option)] = args[jj]; |
| 289 break; |
| 290 default: |
| 291 throw ("bad unknown option '" + option + "' at in " + url + ":" + li
neNum + ": " + arg); |
| 292 } |
| 293 } else { |
| 294 nonOptions.push(arg); |
| 295 } |
| 296 } |
| 297 var url = prefix + nonOptions.join(" "); |
| 298 |
| 299 if (url.substr(url.length - 4) != '.txt') { |
| 300 var minVersion = testOptions.minVersion; |
| 301 if (!minVersion) { |
| 302 minVersion = hierarchicalOptions.defaultVersion; |
| 303 } |
| 304 var slow = testOptions.slow; |
| 305 if (!slow) { |
| 306 slow = hierarchicalOptions.defaultSlow; |
| 307 } |
| 308 |
| 309 if (globalOptions.fast && slow) { |
| 310 useTest = false; |
| 311 } else if (globalOptions.minVersion) { |
| 312 useTest = greaterThanOrEqualToVersion(minVersion, globalOptions.minVersi
on); |
| 313 } else { |
| 314 useTest = greaterThanOrEqualToVersion(globalOptions.version, minVersion)
; |
| 315 } |
| 316 } |
| 317 |
| 318 if (!useTest) { |
| 319 callback(true, []); |
| 320 return; |
| 321 } |
| 322 |
| 323 if (url.substr(url.length - 4) == '.txt') { |
| 324 // If a version was explicity specified pass it down. |
| 325 if (testOptions.minVersion) { |
| 326 hierarchicalOptions.defaultVersion = testOptions.minVersion; |
| 327 } |
| 328 if (testOptions.slow) { |
| 329 hierarchicalOptions.defaultSlow = testOptions.slow; |
| 330 } |
| 331 loadTextFileAsynchronous(url, function() { |
| 332 return function(success, text) { |
| 333 if (!success) { |
| 334 callback(false, ''); |
| 335 return; |
| 336 } |
| 337 var lines = text.split('\n'); |
| 338 var prefix = ''; |
| 339 var lastSlash = url.lastIndexOf('/'); |
| 340 if (lastSlash >= 0) { |
| 341 prefix = url.substr(0, lastSlash + 1); |
| 342 } |
| 343 var fail = false; |
| 344 var count = 1; |
| 345 var index = 0; |
| 346 for (var ii = 0; ii < lines.length; ++ii) { |
| 347 var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, ''); |
| 348 if (str.length > 4 && |
| 349 str[0] != '#' && |
| 350 str[0] != ";" && |
| 351 str.substr(0, 2) != "//") { |
| 352 ++count; |
| 353 getFileListImpl(prefix, str, ii + 1, copyObject(hierarchicalOption
s), function(index) { |
| 354 return function(success, new_files) { |
| 355 //log("got files: " + new_files.length); |
| 356 if (success) { |
| 357 files[index] = new_files; |
| 358 } |
| 359 finish(success); |
| 360 }; |
| 361 }(index++)); |
| 362 } |
| 363 } |
| 364 finish(true); |
| 365 |
| 366 function finish(success) { |
| 367 if (!success) { |
| 368 fail = true; |
| 369 } |
| 370 --count; |
| 371 //log("count: " + count); |
| 372 if (!count) { |
| 373 callback(!fail, files); |
| 374 } |
| 375 } |
| 376 } |
| 377 }()); |
| 378 } else { |
| 379 files.push(url); |
| 380 callback(true, files); |
| 381 } |
| 382 }; |
| 383 |
| 384 getFileListImpl('', url, 1, globalOptions, function(success, files) { |
| 385 // flatten |
| 386 var flat = []; |
| 387 flatten(files); |
| 388 function flatten(files) { |
| 389 for (var ii = 0; ii < files.length; ++ii) { |
| 390 var value = files[ii]; |
| 391 if (typeof(value) == "string") { |
| 392 flat.push(value); |
| 393 } else { |
| 394 flatten(value); |
| 395 } |
| 396 } |
| 397 } |
| 398 callback(success, flat); |
| 399 }); |
| 400 }; |
| 401 |
| 402 var TestFile = function(url) { |
| 403 this.url = url; |
| 404 }; |
| 405 |
| 406 var TestHarness = function(iframe, filelistUrl, reportFunc, options) { |
| 407 this.window = window; |
| 408 this.iframe = iframe; |
| 409 this.reportFunc = reportFunc; |
| 410 this.timeoutDelay = 20000; |
| 411 this.files = []; |
| 412 |
| 413 var that = this; |
| 414 getFileList(filelistUrl, function() { |
| 415 return function(success, files) { |
| 416 that.addFiles_(success, files); |
| 417 }; |
| 418 }(), options); |
| 419 |
| 420 }; |
| 421 |
| 422 TestHarness.reportType = { |
| 423 ADD_PAGE: 1, |
| 424 READY: 2, |
| 425 START_PAGE: 3, |
| 426 TEST_RESULT: 4, |
| 427 FINISH_PAGE: 5, |
| 428 FINISHED_ALL_TESTS: 6 |
| 429 }; |
| 430 |
| 431 TestHarness.prototype.addFiles_ = function(success, files) { |
| 432 if (!success) { |
| 433 this.reportFunc( |
| 434 TestHarness.reportType.FINISHED_ALL_TESTS, |
| 435 'Unable to load tests. Are you running locally?\n' + |
| 436 'You need to run from a server or configure your\n' + |
| 437 'browser to allow access to local files (not recommended).\n\n' + |
| 438 'Note: An easy way to run from a server:\n\n' + |
| 439 '\tcd path_to_tests\n' + |
| 440 '\tpython -m SimpleHTTPServer\n\n' + |
| 441 'then point your browser to ' + |
| 442 '<a href="http://localhost:8000/webgl-conformance-tests.html">' + |
| 443 'http://localhost:8000/webgl-conformance-tests.html</a>', |
| 444 false) |
| 445 return; |
| 446 } |
| 447 log("total files: " + files.length); |
| 448 for (var ii = 0; ii < files.length; ++ii) { |
| 449 log("" + ii + ": " + files[ii]); |
| 450 this.files.push(new TestFile(files[ii])); |
| 451 this.reportFunc(TestHarness.reportType.ADD_PAGE, files[ii], undefined); |
| 452 } |
| 453 this.reportFunc(TestHarness.reportType.READY, undefined, undefined); |
| 454 } |
| 455 |
| 456 TestHarness.prototype.runTests = function(opt_start, opt_count) { |
| 457 var count = opt_count || this.files.length; |
| 458 this.nextFileIndex = opt_start || 0; |
| 459 this.lastFileIndex = this.nextFileIndex + count; |
| 460 this.startNextFile(); |
| 461 }; |
| 462 |
| 463 TestHarness.prototype.setTimeout = function() { |
| 464 var that = this; |
| 465 this.timeoutId = this.window.setTimeout(function() { |
| 466 that.timeout(); |
| 467 }, this.timeoutDelay); |
| 468 }; |
| 469 |
| 470 TestHarness.prototype.clearTimeout = function() { |
| 471 this.window.clearTimeout(this.timeoutId); |
| 472 }; |
| 473 |
| 474 TestHarness.prototype.startNextFile = function() { |
| 475 if (this.nextFileIndex >= this.lastFileIndex) { |
| 476 log("done"); |
| 477 this.reportFunc(TestHarness.reportType.FINISHED_ALL_TESTS, |
| 478 '', true); |
| 479 } else { |
| 480 this.currentFile = this.files[this.nextFileIndex++]; |
| 481 log("loading: " + this.currentFile.url); |
| 482 if (this.reportFunc(TestHarness.reportType.START_PAGE, |
| 483 this.currentFile.url, undefined)) { |
| 484 this.iframe.src = this.currentFile.url; |
| 485 this.setTimeout(); |
| 486 } else { |
| 487 this.reportResults(false, "skipped"); |
| 488 this.notifyFinished(); |
| 489 } |
| 490 } |
| 491 }; |
| 492 |
| 493 TestHarness.prototype.reportResults = function (success, msg) { |
| 494 this.clearTimeout(); |
| 495 log(success ? "PASS" : "FAIL", msg); |
| 496 this.reportFunc(TestHarness.reportType.TEST_RESULT, msg, success); |
| 497 // For each result we get, reset the timeout |
| 498 this.setTimeout(); |
| 499 }; |
| 500 |
| 501 TestHarness.prototype.notifyFinished = function () { |
| 502 this.clearTimeout(); |
| 503 var url = this.currentFile ? this.currentFile.url : 'unknown'; |
| 504 log(url + ": finished"); |
| 505 this.reportFunc(TestHarness.reportType.FINISH_PAGE, url, true); |
| 506 this.startNextFile(); |
| 507 }; |
| 508 |
| 509 TestHarness.prototype.timeout = function() { |
| 510 this.clearTimeout(); |
| 511 var url = this.currentFile ? this.currentFile.url : 'unknown'; |
| 512 log(url + ": timeout"); |
| 513 this.reportFunc(TestHarness.reportType.FINISH_PAGE, url, undefined); |
| 514 this.startNextFile(); |
| 515 }; |
| 516 |
| 517 TestHarness.prototype.setTimeoutDelay = function(x) { |
| 518 this.timeoutDelay = x; |
| 519 }; |
| 520 |
| 521 return { |
| 522 'TestHarness': TestHarness |
| 523 }; |
| 524 |
| 525 }(); |
| 526 |
| 527 |
| 528 |
OLD | NEW |