1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/canvas/test/webgl-conformance/resources/webgl-test-harness.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,444 @@ 1.4 +// Copyright (c) 2009 The Chromium Authors. All rights reserved. 1.5 +// Use of this source code is governed by a BSD-style license that can be 1.6 +// found in the LICENSE file. 1.7 + 1.8 +// This is a test harness for running javascript tests in the browser. 1.9 +// The only identifier exposed by this harness is WebGLTestHarnessModule. 1.10 +// 1.11 +// To use it make an HTML page with an iframe. Then call the harness like this 1.12 +// 1.13 +// function reportResults(type, msg, success) { 1.14 +// ... 1.15 +// return true; 1.16 +// } 1.17 +// 1.18 +// var fileListURL = '00_test_list.txt'; 1.19 +// var testHarness = new WebGLTestHarnessModule.TestHarness( 1.20 +// iframe, 1.21 +// fileListURL, 1.22 +// reportResults); 1.23 +// 1.24 +// The harness will load the fileListURL and parse it for the URLs, one URL 1.25 +// per line. URLs should be on the same domain and at the same folder level 1.26 +// or below the main html file. If any URL ends in .txt it will be parsed 1.27 +// as well so you can nest .txt files. URLs inside a .txt file should be 1.28 +// relative to that text file. 1.29 +// 1.30 +// During startup, for each page found the reportFunction will be called with 1.31 +// WebGLTestHarnessModule.TestHarness.reportType.ADD_PAGE and msg will be 1.32 +// the URL of the test. 1.33 +// 1.34 +// Each test is required to call testHarness.reportResults. This is most easily 1.35 +// accomplished by storing that value on the main window with 1.36 +// 1.37 +// window.webglTestHarness = testHarness 1.38 +// 1.39 +// and then adding these to functions to your tests. 1.40 +// 1.41 +// function reportTestResultsToHarness(success, msg) { 1.42 +// if (window.parent.webglTestHarness) { 1.43 +// window.parent.webglTestHarness.reportResults(success, msg); 1.44 +// } 1.45 +// } 1.46 +// 1.47 +// function notifyFinishedToHarness() { 1.48 +// if (window.parent.webglTestHarness) { 1.49 +// window.parent.webglTestHarness.notifyFinished(); 1.50 +// } 1.51 +// } 1.52 +// 1.53 +// This way your tests will still run without the harness and you can use 1.54 +// any testing framework you want. 1.55 +// 1.56 +// Each test should call reportTestResultsToHarness with true for success if it 1.57 +// succeeded and false if it fail followed and any message it wants to 1.58 +// associate with the test. If your testing framework supports checking for 1.59 +// timeout you can call it with success equal to undefined in that case. 1.60 +// 1.61 +// To run the tests, call testHarness.runTests(); 1.62 +// 1.63 +// For each test run, before the page is loaded the reportFunction will be 1.64 +// called with WebGLTestHarnessModule.TestHarness.reportType.START_PAGE and msg 1.65 +// will be the URL of the test. You may return false if you want the test to be 1.66 +// skipped. 1.67 +// 1.68 +// For each test completed the reportFunction will be called with 1.69 +// with WebGLTestHarnessModule.TestHarness.reportType.TEST_RESULT, 1.70 +// success = true on success, false on failure, undefined on timeout 1.71 +// and msg is any message the test choose to pass on. 1.72 +// 1.73 +// When all the tests on the page have finished your page must call 1.74 +// notifyFinishedToHarness. If notifyFinishedToHarness is not called 1.75 +// the harness will assume the test timed out. 1.76 +// 1.77 +// When all the tests on a page have finished OR the page as timed out the 1.78 +// reportFunction will be called with 1.79 +// WebGLTestHarnessModule.TestHarness.reportType.FINISH_PAGE 1.80 +// where success = true if the page has completed or undefined if the page timed 1.81 +// out. 1.82 +// 1.83 +// Finally, when all the tests have completed the reportFunction will be called 1.84 +// with WebGLTestHarnessModule.TestHarness.reportType.FINISHED_ALL_TESTS. 1.85 +// 1.86 + 1.87 +WebGLTestHarnessModule = function() { 1.88 + 1.89 +/** 1.90 + * Wrapped logging function. 1.91 + */ 1.92 +var log = function(msg) { 1.93 + if (window.console && window.console.log) { 1.94 + window.console.log(msg); 1.95 + } 1.96 +}; 1.97 + 1.98 +/** 1.99 + * Loads text from an external file. This function is synchronous. 1.100 + * @param {string} url The url of the external file. 1.101 + * @param {!function(bool, string): void} callback that is sent a bool for 1.102 + * success and the string. 1.103 + */ 1.104 +var loadTextFileAsynchronous = function(url, callback) { 1.105 + log ("loading: " + url); 1.106 + var error = 'loadTextFileSynchronous failed to load url "' + url + '"'; 1.107 + var request; 1.108 + if (window.XMLHttpRequest) { 1.109 + request = new XMLHttpRequest(); 1.110 + if (request.overrideMimeType) { 1.111 + request.overrideMimeType('text/plain'); 1.112 + } 1.113 + } else { 1.114 + throw 'XMLHttpRequest is disabled'; 1.115 + } 1.116 + try { 1.117 + request.open('GET', url, true); 1.118 + request.onreadystatechange = function() { 1.119 + if (request.readyState == 4) { 1.120 + var text = ''; 1.121 + // HTTP reports success with a 200 status. The file protocol reports 1.122 + // success with zero. HTTP does not use zero as a status code (they 1.123 + // start at 100). 1.124 + // https://developer.mozilla.org/En/Using_XMLHttpRequest 1.125 + var success = request.status == 200 || request.status == 0; 1.126 + if (success) { 1.127 + text = request.responseText; 1.128 + } 1.129 + log("loaded: " + url); 1.130 + callback(success, text); 1.131 + } 1.132 + }; 1.133 + request.send(null); 1.134 + } catch (e) { 1.135 + log("failed to load: " + url); 1.136 + callback(false, ''); 1.137 + } 1.138 +}; 1.139 + 1.140 +/** 1.141 + * Compare version strings. 1.142 + */ 1.143 +var greaterThanOrEqualToVersion = function(have, want) { 1.144 + have = have.split(" ")[0].split("."); 1.145 + want = want.split(" ")[0].split("."); 1.146 + 1.147 + //have 1.2.3 want 1.1 1.148 + //have 1.1.1 want 1.1 1.149 + //have 1.0.9 want 1.1 1.150 + //have 1.1 want 1.1.1 1.151 + 1.152 + for (var ii = 0; ii < want.length; ++ii) { 1.153 + var wantNum = parseInt(want[ii]); 1.154 + var haveNum = have[ii] ? parseInt(have[ii]) : 0 1.155 + if (haveNum < wantNum) { 1.156 + return false; 1.157 + } 1.158 + } 1.159 + return true; 1.160 +}; 1.161 + 1.162 +/** 1.163 + * Reads a file, recursively adding files referenced inside. 1.164 + * 1.165 + * Each line of URL is parsed, comments starting with '#' or ';' 1.166 + * or '//' are stripped. 1.167 + * 1.168 + * arguments beginning with -- are extracted 1.169 + * 1.170 + * lines that end in .txt are recursively scanned for more files 1.171 + * other lines are added to the list of files. 1.172 + * 1.173 + * @param {string} url The url of the file to read. 1.174 + * @param {void function(boolean, !Array.<string>)} callback. 1.175 + * Callback that is called with true for success and an 1.176 + * array of filenames. 1.177 + * @param {Object} options. Optional options 1.178 + * 1.179 + * Options: 1.180 + * version: {string} The version of the conformance test. 1.181 + * Tests with the argument --min-version <version> will 1.182 + * be ignored version is less then <version> 1.183 + * 1.184 + */ 1.185 +var getFileList = function(url, callback, options) { 1.186 + var files = []; 1.187 + 1.188 + var copyObject = function(obj) { 1.189 + return JSON.parse(JSON.stringify(obj)); 1.190 + }; 1.191 + 1.192 + var toCamelCase = function(str) { 1.193 + return str.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase() }); 1.194 + }; 1.195 + 1.196 + var globalOptions = copyObject(options); 1.197 + globalOptions.defaultVersion = "1.0"; 1.198 + 1.199 + var getFileListImpl = function(prefix, line, hierarchicalOptions, callback) { 1.200 + var files = []; 1.201 + 1.202 + var args = line.split(/\s+/); 1.203 + var nonOptions = []; 1.204 + var useTest = true; 1.205 + var testOptions = {}; 1.206 + for (var jj = 0; jj < args.length; ++jj) { 1.207 + var arg = args[jj]; 1.208 + if (arg[0] == '-') { 1.209 + if (arg[1] != '-') { 1.210 + throw ("bad option at in " + url + ":" + (ii + 1) + ": " + str); 1.211 + } 1.212 + var option = arg.substring(2); 1.213 + switch (option) { 1.214 + case 'min-version': 1.215 + ++jj; 1.216 + testOptions[toCamelCase(option)] = args[jj]; 1.217 + break; 1.218 + default: 1.219 + throw ("bad unknown option '" + option + "' at in " + url + ":" + (ii + 1) + ": " + str); 1.220 + } 1.221 + } else { 1.222 + nonOptions.push(arg); 1.223 + } 1.224 + } 1.225 + var url = prefix + nonOptions.join(" "); 1.226 + 1.227 + if (url.substr(url.length - 4) != '.txt') { 1.228 + var minVersion = testOptions.minVersion; 1.229 + if (!minVersion) { 1.230 + minVersion = hierarchicalOptions.defaultVersion; 1.231 + } 1.232 + 1.233 + if (globalOptions.minVersion) { 1.234 + useTest = greaterThanOrEqualToVersion(minVersion, globalOptions.minVersion); 1.235 + } else { 1.236 + useTest = greaterThanOrEqualToVersion(globalOptions.version, minVersion); 1.237 + } 1.238 + } 1.239 + 1.240 + if (!useTest) { 1.241 + callback(true, []); 1.242 + return; 1.243 + } 1.244 + 1.245 + if (url.substr(url.length - 4) == '.txt') { 1.246 + // If a version was explicity specified pass it down. 1.247 + if (testOptions.minVersion) { 1.248 + hierarchicalOptions.defaultVersion = testOptions.minVersion; 1.249 + } 1.250 + loadTextFileAsynchronous(url, function() { 1.251 + return function(success, text) { 1.252 + if (!success) { 1.253 + callback(false, ''); 1.254 + return; 1.255 + } 1.256 + var lines = text.split('\n'); 1.257 + var prefix = ''; 1.258 + var lastSlash = url.lastIndexOf('/'); 1.259 + if (lastSlash >= 0) { 1.260 + prefix = url.substr(0, lastSlash + 1); 1.261 + } 1.262 + var fail = false; 1.263 + var count = 1; 1.264 + var index = 0; 1.265 + for (var ii = 0; ii < lines.length; ++ii) { 1.266 + var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 1.267 + if (str.length > 4 && 1.268 + str[0] != '#' && 1.269 + str[0] != ";" && 1.270 + str.substr(0, 2) != "//") { 1.271 + ++count; 1.272 + getFileListImpl(prefix, str, copyObject(hierarchicalOptions), function(index) { 1.273 + return function(success, new_files) { 1.274 + log("got files: " + new_files.length); 1.275 + if (success) { 1.276 + files[index] = new_files; 1.277 + } 1.278 + finish(success); 1.279 + }; 1.280 + }(index++)); 1.281 + } 1.282 + } 1.283 + finish(true); 1.284 + 1.285 + function finish(success) { 1.286 + if (!success) { 1.287 + fail = true; 1.288 + } 1.289 + --count; 1.290 + log("count: " + count); 1.291 + if (!count) { 1.292 + callback(!fail, files); 1.293 + } 1.294 + } 1.295 + } 1.296 + }()); 1.297 + } else { 1.298 + files.push(url); 1.299 + callback(true, files); 1.300 + } 1.301 + }; 1.302 + 1.303 + getFileListImpl('', url, globalOptions, function(success, files) { 1.304 + // flatten 1.305 + var flat = []; 1.306 + flatten(files); 1.307 + function flatten(files) { 1.308 + for (var ii = 0; ii < files.length; ++ii) { 1.309 + var value = files[ii]; 1.310 + if (typeof(value) == "string") { 1.311 + flat.push(value); 1.312 + } else { 1.313 + flatten(value); 1.314 + } 1.315 + } 1.316 + } 1.317 + callback(success, flat); 1.318 + }); 1.319 +}; 1.320 + 1.321 +var TestFile = function(url) { 1.322 + this.url = url; 1.323 +}; 1.324 + 1.325 +var TestHarness = function(iframe, filelistUrl, reportFunc, options) { 1.326 + this.window = window; 1.327 + this.iframe = iframe; 1.328 + this.reportFunc = reportFunc; 1.329 + this.timeoutDelay = 20000; 1.330 + this.files = []; 1.331 + 1.332 + var that = this; 1.333 + getFileList(filelistUrl, function() { 1.334 + return function(success, files) { 1.335 + that.addFiles_(success, files); 1.336 + }; 1.337 + }(), options); 1.338 + 1.339 +}; 1.340 + 1.341 +TestHarness.reportType = { 1.342 + ADD_PAGE: 1, 1.343 + READY: 2, 1.344 + START_PAGE: 3, 1.345 + TEST_RESULT: 4, 1.346 + FINISH_PAGE: 5, 1.347 + FINISHED_ALL_TESTS: 6 1.348 +}; 1.349 + 1.350 +TestHarness.prototype.addFiles_ = function(success, files) { 1.351 + if (!success) { 1.352 + this.reportFunc( 1.353 + TestHarness.reportType.FINISHED_ALL_TESTS, 1.354 + 'Unable to load tests. Are you running locally?\n' + 1.355 + 'You need to run from a server or configure your\n' + 1.356 + 'browser to allow access to local files (not recommended).\n\n' + 1.357 + 'Note: An easy way to run from a server:\n\n' + 1.358 + '\tcd path_to_tests\n' + 1.359 + '\tpython -m SimpleHTTPServer\n\n' + 1.360 + 'then point your browser to ' + 1.361 + '<a href="http://localhost:8000/webgl-conformance-tests.html">' + 1.362 + 'http://localhost:8000/webgl-conformance-tests.html</a>', 1.363 + false) 1.364 + return; 1.365 + } 1.366 + log("total files: " + files.length); 1.367 + for (var ii = 0; ii < files.length; ++ii) { 1.368 + log("" + ii + ": " + files[ii]); 1.369 + this.files.push(new TestFile(files[ii])); 1.370 + this.reportFunc(TestHarness.reportType.ADD_PAGE, files[ii], undefined); 1.371 + } 1.372 + this.reportFunc(TestHarness.reportType.READY, undefined, undefined); 1.373 +} 1.374 + 1.375 +TestHarness.prototype.runTests = function(opt_start, opt_count) { 1.376 + var count = opt_count || this.files.length; 1.377 + this.nextFileIndex = opt_start || 0; 1.378 + this.lastFileIndex = this.nextFileIndex + count; 1.379 + this.startNextFile(); 1.380 +}; 1.381 + 1.382 +TestHarness.prototype.setTimeout = function() { 1.383 + var that = this; 1.384 + this.timeoutId = this.window.setTimeout(function() { 1.385 + that.timeout(); 1.386 + }, this.timeoutDelay); 1.387 +}; 1.388 + 1.389 +TestHarness.prototype.clearTimeout = function() { 1.390 + this.window.clearTimeout(this.timeoutId); 1.391 +}; 1.392 + 1.393 +TestHarness.prototype.startNextFile = function() { 1.394 + if (this.nextFileIndex >= this.lastFileIndex) { 1.395 + log("done"); 1.396 + this.reportFunc(TestHarness.reportType.FINISHED_ALL_TESTS, 1.397 + '', true); 1.398 + } else { 1.399 + this.currentFile = this.files[this.nextFileIndex++]; 1.400 + log("loading: " + this.currentFile.url); 1.401 + if (this.reportFunc(TestHarness.reportType.START_PAGE, 1.402 + this.currentFile.url, undefined)) { 1.403 + this.iframe.src = this.currentFile.url; 1.404 + this.setTimeout(); 1.405 + } else { 1.406 + this.reportResults(false, "skipped"); 1.407 + this.notifyFinished(); 1.408 + } 1.409 + } 1.410 +}; 1.411 + 1.412 +TestHarness.prototype.reportResults = function (success, msg) { 1.413 + this.clearTimeout(); 1.414 + log(success ? "PASS" : "FAIL", msg); 1.415 + this.reportFunc(TestHarness.reportType.TEST_RESULT, msg, success); 1.416 + // For each result we get, reset the timeout 1.417 + this.setTimeout(); 1.418 +}; 1.419 + 1.420 +TestHarness.prototype.notifyFinished = function () { 1.421 + this.clearTimeout(); 1.422 + var url = this.currentFile ? this.currentFile.url : 'unknown'; 1.423 + log(url + ": finished"); 1.424 + this.reportFunc(TestHarness.reportType.FINISH_PAGE, url, true); 1.425 + this.startNextFile(); 1.426 +}; 1.427 + 1.428 +TestHarness.prototype.timeout = function() { 1.429 + this.clearTimeout(); 1.430 + var url = this.currentFile ? this.currentFile.url : 'unknown'; 1.431 + log(url + ": timeout"); 1.432 + this.reportFunc(TestHarness.reportType.FINISH_PAGE, url, undefined); 1.433 + this.startNextFile(); 1.434 +}; 1.435 + 1.436 +TestHarness.prototype.setTimeoutDelay = function(x) { 1.437 + this.timeoutDelay = x; 1.438 +}; 1.439 + 1.440 +return { 1.441 + 'TestHarness': TestHarness 1.442 + }; 1.443 + 1.444 +}(); 1.445 + 1.446 + 1.447 +