diff options
Diffstat (limited to 'dom/canvas/test/webgl-conf/checkout/js/webgl-test-harness.js')
-rw-r--r-- | dom/canvas/test/webgl-conf/checkout/js/webgl-test-harness.js | 659 |
1 files changed, 659 insertions, 0 deletions
diff --git a/dom/canvas/test/webgl-conf/checkout/js/webgl-test-harness.js b/dom/canvas/test/webgl-conf/checkout/js/webgl-test-harness.js new file mode 100644 index 000000000..0287bf6ec --- /dev/null +++ b/dom/canvas/test/webgl-conf/checkout/js/webgl-test-harness.js @@ -0,0 +1,659 @@ +/* +** Copyright (c) 2012 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +// This is a test harness for running javascript tests in the browser. +// The only identifier exposed by this harness is WebGLTestHarnessModule. +// +// To use it make an HTML page with an iframe. Then call the harness like this +// +// function reportResults(type, msg, success) { +// ... +// return true; +// } +// +// var fileListURL = '00_test_list.txt'; +// var testHarness = new WebGLTestHarnessModule.TestHarness( +// iframe, +// fileListURL, +// reportResults, +// options); +// +// The harness will load the fileListURL and parse it for the URLs, one URL +// per line preceded by options, see below. URLs should be on the same domain +// and at the same folder level or below the main html file. If any URL ends +// in .txt it will be parsed as well so you can nest .txt files. URLs inside a +// .txt file should be relative to that text file. +// +// During startup, for each page found the reportFunction will be called with +// WebGLTestHarnessModule.TestHarness.reportType.ADD_PAGE and msg will be +// the URL of the test. +// +// Each test is required to call testHarness.reportResults. This is most easily +// accomplished by storing that value on the main window with +// +// window.webglTestHarness = testHarness +// +// and then adding these to functions to your tests. +// +// function reportTestResultsToHarness(success, msg) { +// if (window.parent.webglTestHarness) { +// window.parent.webglTestHarness.reportResults(success, msg); +// } +// } +// +// function notifyFinishedToHarness() { +// if (window.parent.webglTestHarness) { +// window.parent.webglTestHarness.notifyFinished(); +// } +// } +// +// This way your tests will still run without the harness and you can use +// any testing framework you want. +// +// Each test should call reportTestResultsToHarness with true for success if it +// succeeded and false if it fail followed and any message it wants to +// associate with the test. If your testing framework supports checking for +// timeout you can call it with success equal to undefined in that case. +// +// To run the tests, call testHarness.runTests(options); +// +// For each test run, before the page is loaded the reportFunction will be +// called with WebGLTestHarnessModule.TestHarness.reportType.START_PAGE and msg +// will be the URL of the test. You may return false if you want the test to be +// skipped. +// +// For each test completed the reportFunction will be called with +// with WebGLTestHarnessModule.TestHarness.reportType.TEST_RESULT, +// success = true on success, false on failure, undefined on timeout +// and msg is any message the test choose to pass on. +// +// When all the tests on the page have finished your page must call +// notifyFinishedToHarness. If notifyFinishedToHarness is not called +// the harness will assume the test timed out. +// +// When all the tests on a page have finished OR the page as timed out the +// reportFunction will be called with +// WebGLTestHarnessModule.TestHarness.reportType.FINISH_PAGE +// where success = true if the page has completed or undefined if the page timed +// out. +// +// Finally, when all the tests have completed the reportFunction will be called +// with WebGLTestHarnessModule.TestHarness.reportType.FINISHED_ALL_TESTS. +// +// Harness Options +// +// These are passed in to the TestHarness as a JavaScript object +// +// version: (required!) +// +// Specifies a version used to filter tests. Tests marked as requiring +// a version greater than this version will not be included. +// +// example: new TestHarness(...., {version: "3.1.2"}); +// +// minVersion: +// +// Specifies the minimum version a test must require to be included. +// This basically flips the filter so that only tests marked with +// --min-version will be included if they are at this minVersion or +// greater. +// +// example: new TestHarness(...., {minVersion: "2.3.1"}); +// +// maxVersion: +// +// Specifies the maximum version a test must require to be included. +// This basically flips the filter so that only tests marked with +// --max-version will be included if they are at this maxVersion or +// less. +// +// example: new TestHarness(...., {maxVersion: "2.3.1"}); +// +// fast: +// +// Specifies to skip any tests marked as slow. +// +// example: new TestHarness(..., {fast: true}); +// +// Test Options: +// +// Any test URL or .txt file can be prefixed by the following options +// +// min-version: +// +// Sets the minimum version required to include this test. A version is +// passed into the harness options. Any test marked as requiring a +// min-version greater than the version passed to the harness is skipped. +// This allows you to add new tests to a suite of tests for a future +// version of the suite without including the test in the current version. +// If no -min-version is specified it is inheriited from the .txt file +// including it. The default is 1.0.0 +// +// example: --min-version 2.1.3 sometest.html +// +// max-version: +// +// Sets the maximum version required to include this test. A version is +// passed into the harness options. Any test marked as requiring a +// max-version less than the version passed to the harness is skipped. +// This allows you to test functionality that has been removed from later +// versions of the suite. +// If no -max-version is specified it is inherited from the .txt file +// including it. +// +// example: --max-version 1.9.9 sometest.html +// +// slow: +// +// Marks a test as slow. Slow tests can be skipped by passing fastOnly: true +// to the TestHarness. Of course you need to pass all tests but sometimes +// you'd like to test quickly and run only the fast subset of tests. +// +// example: --slow some-test-that-takes-2-mins.html +// + +WebGLTestHarnessModule = function() { + +/** + * Wrapped logging function. + */ +var log = function(msg) { + if (window.console && window.console.log) { + window.console.log(msg); + } +}; + +/** + * Loads text from an external file. This function is synchronous. + * @param {string} url The url of the external file. + * @param {!function(bool, string): void} callback that is sent a bool for + * success and the string. + */ +var loadTextFileAsynchronous = function(url, callback) { + log ("loading: " + url); + var error = 'loadTextFileSynchronous failed to load url "' + url + '"'; + var request; + if (window.XMLHttpRequest) { + request = new XMLHttpRequest(); + if (request.overrideMimeType) { + request.overrideMimeType('text/plain'); + } + } else { + throw 'XMLHttpRequest is disabled'; + } + try { + request.open('GET', url, true); + request.onreadystatechange = function() { + if (request.readyState == 4) { + var text = ''; + // HTTP reports success with a 200 status. The file protocol reports + // success with zero. HTTP does not use zero as a status code (they + // start at 100). + // https://developer.mozilla.org/En/Using_XMLHttpRequest + var success = request.status == 200 || request.status == 0; + if (success) { + text = request.responseText; + } + log("loaded: " + url); + callback(success, text); + } + }; + request.send(null); + } catch (e) { + log("failed to load: " + url); + callback(false, ''); + } +}; + +/** + * @param {string} versionString WebGL version string. + * @return {number} Integer containing the WebGL major version. + */ +var getMajorVersion = function(versionString) { + if (!versionString) { + return 1; + } + return parseInt(versionString.split(" ")[0].split(".")[0], 10); +}; + +/** + * @param {string} url Base URL of the test. + * @param {map} options Map of options to append to the URL's query string. + * @return {string} URL that will run the test with the given WebGL version. + */ +var getURLWithOptions = function(url, options) { + var queryArgs = 0; + + for (i in options) { + url += queryArgs ? "&" : "?"; + url += i + "=" + options[i]; + queryArgs++; + } + + return url; +}; + +/** + * Compare version strings. + */ +var greaterThanOrEqualToVersion = function(have, want) { + have = have.split(" ")[0].split("."); + want = want.split(" ")[0].split("."); + + //have 1.2.3 want 1.1 + //have 1.1.1 want 1.1 + //have 1.0.9 want 1.1 + //have 1.1 want 1.1.1 + + for (var ii = 0; ii < want.length; ++ii) { + var wantNum = parseInt(want[ii]); + var haveNum = have[ii] ? parseInt(have[ii]) : 0 + if (haveNum > wantNum) { + return true; // 2.0.0 is greater than 1.2.3 + } + if (haveNum < wantNum) { + return false; + } + } + return true; +}; + +/** + * Reads a file, recursively adding files referenced inside. + * + * Each line of URL is parsed, comments starting with '#' or ';' + * or '//' are stripped. + * + * arguments beginning with -- are extracted + * + * lines that end in .txt are recursively scanned for more files + * other lines are added to the list of files. + * + * @param {string} url The url of the file to read. + * @param {function(boolean, !Array.<string>):void} callback + * Callback that is called with true for success and an + * array of filenames. + * @param {Object} options Optional options + * + * Options: + * version: {string} The version of the conformance test. + * Tests with the argument --min-version <version> will + * be ignored version is less then <version> + * + */ +var getFileList = function(url, callback, options) { + var files = []; + + var copyObject = function(obj) { + return JSON.parse(JSON.stringify(obj)); + }; + + var toCamelCase = function(str) { + return str.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase() }); + }; + + var globalOptions = copyObject(options); + globalOptions.defaultVersion = "1.0"; + globalOptions.defaultMaxVersion = null; + + var getFileListImpl = function(prefix, line, lineNum, hierarchicalOptions, callback) { + var files = []; + + var args = line.split(/\s+/); + var nonOptions = []; + var useTest = true; + var testOptions = {}; + for (var jj = 0; jj < args.length; ++jj) { + var arg = args[jj]; + if (arg[0] == '-') { + if (arg[1] != '-') { + throw ("bad option at in " + url + ":" + lineNum + ": " + arg); + } + var option = arg.substring(2); + switch (option) { + // no argument options. + case 'slow': + testOptions[toCamelCase(option)] = true; + break; + // one argument options. + case 'min-version': + case 'max-version': + ++jj; + testOptions[toCamelCase(option)] = args[jj]; + break; + default: + throw ("bad unknown option '" + option + "' at in " + url + ":" + lineNum + ": " + arg); + } + } else { + nonOptions.push(arg); + } + } + var url = prefix + nonOptions.join(" "); + + if (url.substr(url.length - 4) != '.txt') { + var minVersion = testOptions.minVersion; + if (!minVersion) { + minVersion = hierarchicalOptions.defaultVersion; + } + var maxVersion = testOptions.maxVersion; + if (!maxVersion) { + maxVersion = hierarchicalOptions.defaultMaxVersion; + } + var slow = testOptions.slow; + if (!slow) { + slow = hierarchicalOptions.defaultSlow; + } + + if (globalOptions.fast && slow) { + useTest = false; + } else if (globalOptions.minVersion) { + useTest = greaterThanOrEqualToVersion(minVersion, globalOptions.minVersion); + } else if (globalOptions.maxVersion && maxVersion) { + useTest = greaterThanOrEqualToVersion(globalOptions.maxVersion, maxVersion); + } else { + useTest = greaterThanOrEqualToVersion(globalOptions.version, minVersion); + if (maxVersion) { + useTest = useTest && greaterThanOrEqualToVersion(maxVersion, globalOptions.version); + } + } + } + + if (!useTest) { + callback(true, []); + return; + } + + if (url.substr(url.length - 4) == '.txt') { + // If a version was explicity specified pass it down. + if (testOptions.minVersion) { + hierarchicalOptions.defaultVersion = testOptions.minVersion; + } + if (testOptions.maxVersion) { + hierarchicalOptions.defaultMaxVersion = testOptions.maxVersion; + } + if (testOptions.slow) { + hierarchicalOptions.defaultSlow = testOptions.slow; + } + loadTextFileAsynchronous(url, function() { + return function(success, text) { + if (!success) { + callback(false, ''); + return; + } + var lines = text.split('\n'); + var prefix = ''; + var lastSlash = url.lastIndexOf('/'); + if (lastSlash >= 0) { + prefix = url.substr(0, lastSlash + 1); + } + var fail = false; + var count = 1; + var index = 0; + for (var ii = 0; ii < lines.length; ++ii) { + var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + if (str.length > 4 && + str[0] != '#' && + str[0] != ";" && + str.substr(0, 2) != "//") { + ++count; + getFileListImpl(prefix, str, ii + 1, copyObject(hierarchicalOptions), function(index) { + return function(success, new_files) { + //log("got files: " + new_files.length); + if (success) { + files[index] = new_files; + } + finish(success); + }; + }(index++)); + } + } + finish(true); + + function finish(success) { + if (!success) { + fail = true; + } + --count; + //log("count: " + count); + if (!count) { + callback(!fail, files); + } + } + } + }()); + } else { + files.push(url); + callback(true, files); + } + }; + + getFileListImpl('', url, 1, globalOptions, function(success, files) { + // flatten + var flat = []; + flatten(files); + function flatten(files) { + for (var ii = 0; ii < files.length; ++ii) { + var value = files[ii]; + if (typeof(value) == "string") { + flat.push(value); + } else { + flatten(value); + } + } + } + callback(success, flat); + }); +}; + +var FilterURL = (function() { + var prefix = window.location.pathname; + prefix = prefix.substring(0, prefix.lastIndexOf("/") + 1); + return function(url) { + if (url.substring(0, prefix.length) == prefix) { + url = url.substring(prefix.length); + } + return url; + }; +}()); + +var TestFile = function(url) { + this.url = url; +}; + +var Test = function(file) { + this.file = file; +}; + +var TestHarness = function(iframe, filelistUrl, reportFunc, options) { + this.window = window; + this.iframes = iframe.length ? iframe : [iframe]; + this.reportFunc = reportFunc; + this.timeoutDelay = 20000; + this.files = []; + this.allowSkip = options.allowSkip; + this.webglVersion = getMajorVersion(options.version); + this.dumpShaders = options.dumpShaders; + this.quiet = options.quiet; + + var that = this; + getFileList(filelistUrl, function() { + return function(success, files) { + that.addFiles_(success, files); + }; + }(), options); + +}; + +TestHarness.reportType = { + ADD_PAGE: 1, + READY: 2, + START_PAGE: 3, + TEST_RESULT: 4, + FINISH_PAGE: 5, + FINISHED_ALL_TESTS: 6 +}; + +TestHarness.prototype.addFiles_ = function(success, files) { + if (!success) { + this.reportFunc( + TestHarness.reportType.FINISHED_ALL_TESTS, + '', + 'Unable to load tests. Are you running locally?\n' + + 'You need to run from a server or configure your\n' + + 'browser to allow access to local files (not recommended).\n\n' + + 'Note: An easy way to run from a server:\n\n' + + '\tcd path_to_tests\n' + + '\tpython -m SimpleHTTPServer\n\n' + + 'then point your browser to ' + + '<a href="http://localhost:8000/webgl-conformance-tests.html">' + + 'http://localhost:8000/webgl-conformance-tests.html</a>', + false) + return; + } + log("total files: " + files.length); + for (var ii = 0; ii < files.length; ++ii) { + log("" + ii + ": " + files[ii]); + this.files.push(new TestFile(files[ii])); + this.reportFunc(TestHarness.reportType.ADD_PAGE, '', files[ii], undefined); + } + this.reportFunc(TestHarness.reportType.READY, '', undefined, undefined); +} + +TestHarness.prototype.runTests = function(opt_options) { + var options = opt_options || { }; + options.start = options.start || 0; + options.count = options.count || this.files.length; + + this.idleIFrames = this.iframes.slice(0); + this.runningTests = {}; + var testsToRun = []; + for (var ii = 0; ii < options.count; ++ii) { + testsToRun.push(ii + options.start); + } + this.numTestsRemaining = options.count; + this.testsToRun = testsToRun; + this.startNextTest(); +}; + +TestHarness.prototype.setTimeout = function(test) { + var that = this; + test.timeoutId = this.window.setTimeout(function() { + that.timeout(test); + }, this.timeoutDelay); +}; + +TestHarness.prototype.clearTimeout = function(test) { + this.window.clearTimeout(test.timeoutId); +}; + +TestHarness.prototype.startNextTest = function() { + if (this.numTestsRemaining == 0) { + log("done"); + this.reportFunc(TestHarness.reportType.FINISHED_ALL_TESTS, + '', '', true); + } else { + while (this.testsToRun.length > 0 && this.idleIFrames.length > 0) { + var testId = this.testsToRun.shift(); + var iframe = this.idleIFrames.shift(); + this.startTest(iframe, this.files[testId], this.webglVersion); + } + } +}; + +TestHarness.prototype.startTest = function(iframe, testFile, webglVersion) { + var test = { + iframe: iframe, + testFile: testFile + }; + var url = testFile.url; + this.runningTests[url] = test; + log("loading: " + url); + if (this.reportFunc(TestHarness.reportType.START_PAGE, url, url, undefined)) { + iframe.src = getURLWithOptions(url, { + "webglVersion": webglVersion, + "dumpShaders": this.dumpShaders, + "quiet": this.quiet + }); + this.setTimeout(test); + } else { + this.reportResults(url, !!this.allowSkip, "skipped", true); + this.notifyFinished(url); + } +}; + +TestHarness.prototype.getTest = function(url) { + var test = this.runningTests[FilterURL(url)]; + if (!test) { + throw("unknown test:" + url); + } + return test; +}; + +TestHarness.prototype.reportResults = function(url, success, msg, skipped) { + url = FilterURL(url); + var test = this.getTest(url); + this.clearTimeout(test); + log(success ? "PASS" : "FAIL", msg); + this.reportFunc(TestHarness.reportType.TEST_RESULT, url, msg, success, skipped); + // For each result we get, reset the timeout + this.setTimeout(test); +}; + +TestHarness.prototype.dequeTest = function(test) { + this.clearTimeout(test); + this.idleIFrames.push(test.iframe); + delete this.runningTests[test.testFile.url]; + --this.numTestsRemaining; +} + +TestHarness.prototype.notifyFinished = function(url) { + url = FilterURL(url); + var test = this.getTest(url); + log(url + ": finished"); + this.dequeTest(test); + this.reportFunc(TestHarness.reportType.FINISH_PAGE, url, url, true); + this.startNextTest(); +}; + +TestHarness.prototype.timeout = function(test) { + this.dequeTest(test); + var url = test.testFile.url; + log(url + ": timeout"); + this.reportFunc(TestHarness.reportType.FINISH_PAGE, url, url, undefined); + this.startNextTest(); +}; + +TestHarness.prototype.setTimeoutDelay = function(x) { + this.timeoutDelay = x; +}; + +return { + 'TestHarness': TestHarness, + 'getMajorVersion': getMajorVersion, + 'getURLWithOptions': getURLWithOptions + }; + +}(); + + + |