Bug 1932347 - Adjust positioning of tab preview for vertical tabs r=tabbrowser-review...
[gecko.git] / dom / canvas / test / webgl-conf / checkout / js / webgl-test-harness.js
blobca6cbfcd364a226247ef16fab3d3383ef185756a
1 /*
2 Copyright (c) 2019 The Khronos Group Inc.
3 Use of this source code is governed by an MIT-style license that can be
4 found in the LICENSE.txt file.
5 */
7 // This is a test harness for running javascript tests in the browser.
8 // The only identifier exposed by this harness is WebGLTestHarnessModule.
9 //
10 // To use it make an HTML page with an iframe. Then call the harness like this
12 //    function reportResults(type, msg, success) {
13 //      ...
14 //      return true;
15 //    }
17 //    var fileListURL = '00_test_list.txt';
18 //    var testHarness = new WebGLTestHarnessModule.TestHarness(
19 //        iframe,
20 //        fileListURL,
21 //        reportResults,
22 //        options);
24 // The harness will load the fileListURL and parse it for the URLs, one URL
25 // per line preceded by options, see below. URLs should be on the same domain
26 // and at the  same folder level or below the main html file.  If any URL ends
27 // in .txt it will be parsed as well so you can nest .txt files. URLs inside a
28 // .txt file should be relative to that text file.
30 // During startup, for each page found the reportFunction will be called with
31 // WebGLTestHarnessModule.TestHarness.reportType.ADD_PAGE and msg will be
32 // the URL of the test.
34 // Each test is required to call testHarness.reportResults. This is most easily
35 // accomplished by storing that value on the main window with
37 //     window.webglTestHarness = testHarness
39 // and then adding these to functions to your tests.
41 //     function reportTestResultsToHarness(success, msg) {
42 //       if (window.parent.webglTestHarness) {
43 //         window.parent.webglTestHarness.reportResults(success, msg);
44 //       }
45 //     }
47 //     function notifyFinishedToHarness() {
48 //       if (window.parent.webglTestHarness) {
49 //         window.parent.webglTestHarness.notifyFinished();
50 //       }
51 //     }
53 // This way your tests will still run without the harness and you can use
54 // any testing framework you want.
56 // Each test should call reportTestResultsToHarness with true for success if it
57 // succeeded and false if it fail followed and any message it wants to
58 // associate with the test. If your testing framework supports checking for
59 // timeout you can call it with success equal to undefined in that case.
61 // To run the tests, call testHarness.runTests(options);
63 // For each test run, before the page is loaded the reportFunction will be
64 // called with WebGLTestHarnessModule.TestHarness.reportType.START_PAGE and msg
65 // will be the URL of the test. You may return false if you want the test to be
66 // skipped.
68 // For each test completed the reportFunction will be called with
69 // with WebGLTestHarnessModule.TestHarness.reportType.TEST_RESULT,
70 // success = true on success, false on failure, undefined on timeout
71 // and msg is any message the test choose to pass on.
73 // When all the tests on the page have finished your page must call
74 // notifyFinishedToHarness.  If notifyFinishedToHarness is not called
75 // the harness will assume the test timed out.
77 // When all the tests on a page have finished OR the page as timed out the
78 // reportFunction will be called with
79 // WebGLTestHarnessModule.TestHarness.reportType.FINISH_PAGE
80 // where success = true if the page has completed or undefined if the page timed
81 // out.
83 // Finally, when all the tests have completed the reportFunction will be called
84 // with WebGLTestHarnessModule.TestHarness.reportType.FINISHED_ALL_TESTS.
86 // Harness Options
88 // These are passed in to the TestHarness as a JavaScript object
90 // version: (required!)
92 //     Specifies a version used to filter tests. Tests marked as requiring
93 //     a version greater than this version will not be included.
95 //     example: new TestHarness(...., {version: "3.1.2"});
97 // minVersion:
99 //     Specifies the minimum version a test must require to be included.
100 //     This basically flips the filter so that only tests marked with
101 //     --min-version will be included if they are at this minVersion or
102 //     greater.
104 //     example: new TestHarness(...., {minVersion: "2.3.1"});
106 // maxVersion:
108 //     Specifies the maximum version a test must require to be included.
109 //     This basically flips the filter so that only tests marked with
110 //     --max-version will be included if they are at this maxVersion or
111 //     less.
113 //     example: new TestHarness(...., {maxVersion: "2.3.1"});
115 // fast:
117 //     Specifies to skip any tests marked as slow.
119 //     example: new TestHarness(..., {fast: true});
121 // Test Options:
123 // Any test URL or .txt file can be prefixed by the following options
125 // min-version:
127 //     Sets the minimum version required to include this test. A version is
128 //     passed into the harness options. Any test marked as requiring a
129 //     min-version greater than the version passed to the harness is skipped.
130 //     This allows you to add new tests to a suite of tests for a future
131 //     version of the suite without including the test in the current version.
132 //     If no -min-version is specified it is inheriited from the .txt file
133 //     including it. The default is 1.0.0
135 //     example:  --min-version 2.1.3 sometest.html
137 // max-version:
139 //     Sets the maximum version required to include this test. A version is
140 //     passed into the harness options. Any test marked as requiring a
141 //     max-version less than the version passed to the harness is skipped.
142 //     This allows you to test functionality that has been removed from later
143 //     versions of the suite.
144 //     If no -max-version is specified it is inherited from the .txt file
145 //     including it.
147 //     example:  --max-version 1.9.9 sometest.html
149 // slow:
151 //     Marks a test as slow. Slow tests can be skipped by passing fastOnly: true
152 //     to the TestHarness. Of course you need to pass all tests but sometimes
153 //     you'd like to test quickly and run only the fast subset of tests.
155 //     example:  --slow some-test-that-takes-2-mins.html
158 WebGLTestHarnessModule = function() {
161  * Wrapped logging function.
162  */
163 var log = function(msg) {
164   if (window.console && window.console.log) {
165     window.console.log(msg);
166   }
170  * Loads text from an external file. This function is synchronous.
171  * @param {string} url The url of the external file.
172  * @param {!function(bool, string): void} callback that is sent a bool for
173  *     success and the string.
174  */
175 var loadTextFileAsynchronous = function(url, callback) {
176   log ("loading: " + url);
177   var error = 'loadTextFileSynchronous failed to load url "' + url + '"';
178   var request;
179   if (window.XMLHttpRequest) {
180     request = new XMLHttpRequest();
181     if (request.overrideMimeType) {
182       request.overrideMimeType('text/plain');
183     }
184   } else {
185     throw 'XMLHttpRequest is disabled';
186   }
187   try {
188     request.open('GET', url, true);
189     request.onreadystatechange = function() {
190       if (request.readyState == 4) {
191         var text = '';
192         // HTTP reports success with a 200 status. The file protocol reports
193         // success with zero. HTTP does not use zero as a status code (they
194         // start at 100).
195         // https://developer.mozilla.org/En/Using_XMLHttpRequest
196         var success = request.status == 200 || request.status == 0;
197         if (success) {
198           text = request.responseText;
199         }
200         log("loaded: " + url);
201         callback(success, text);
202       }
203     };
204     request.send(null);
205   } catch (e) {
206     log("failed to load: " + url);
207     callback(false, '');
208   }
212  * @param {string} versionString WebGL version string.
213  * @return {number} Integer containing the WebGL major version.
214  */
215 var getMajorVersion = function(versionString) {
216   if (!versionString) {
217     return 1;
218   }
219   return parseInt(versionString.split(" ")[0].split(".")[0], 10);
223  * @param {string} url Base URL of the test.
224  * @param {map} options Map of options to append to the URL's query string.
225  * @return {string} URL that will run the test with the given WebGL version.
226  */
227 var getURLWithOptions = function(url, options) {
228   var queryArgs = 0;
230   for (i in options) {
231     url += queryArgs ? "&" : "?";
232     url += i + "=" + options[i];
233     queryArgs++;
234   }
236   return url;
240  * Compare version strings.
241  */
242 var greaterThanOrEqualToVersion = function(have, want) {
243   have = have.split(" ")[0].split(".");
244   want = want.split(" ")[0].split(".");
246   //have 1.2.3   want  1.1
247   //have 1.1.1   want  1.1
248   //have 1.0.9   want  1.1
249   //have 1.1     want  1.1.1
251   for (var ii = 0; ii < want.length; ++ii) {
252     var wantNum = parseInt(want[ii]);
253     var haveNum = have[ii] ? parseInt(have[ii]) : 0
254     if (haveNum > wantNum) {
255       return true; // 2.0.0 is greater than 1.2.3
256     }
257     if (haveNum < wantNum) {
258       return false;
259     }
260   }
261   return true;
265  * Reads a file, recursively adding files referenced inside.
267  * Each line of URL is parsed, comments starting with '#' or ';'
268  * or '//' are stripped.
270  * arguments beginning with -- are extracted
272  * lines that end in .txt are recursively scanned for more files
273  * other lines are added to the list of files.
275  * @param {string} url The url of the file to read.
276  * @param {function(boolean, !Array.<string>):void} callback
277  *      Callback that is called with true for success and an
278  *      array of filenames.
279  * @param {Object} options Optional options
281  * Options:
282  *    version: {string} The version of the conformance test.
283  *    Tests with the argument --min-version <version> will
284  *    be ignored version is less then <version>
286  */
287 var getFileList = function(url, callback, options) {
288   var files = [];
290   var copyObject = function(obj) {
291     return JSON.parse(JSON.stringify(obj));
292   };
294   var toCamelCase = function(str) {
295     return str.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase() });
296   };
298   var globalOptions = copyObject(options);
299   globalOptions.defaultVersion = "1.0";
300   globalOptions.defaultMaxVersion = null;
302   var getFileListImpl = function(prefix, line, lineNum, hierarchicalOptions, callback) {
303     var files = [];
305     var args = line.split(/\s+/);
306     var nonOptions = [];
307     var useTest = true;
308     var testOptions = {};
309     for (var jj = 0; jj < args.length; ++jj) {
310       var arg = args[jj];
311       if (arg[0] == '-') {
312         if (arg[1] != '-') {
313           throw ("bad option at in " + url + ":" + lineNum + ": " + arg);
314         }
315         var option = arg.substring(2);
316         switch (option) {
317           // no argument options.
318           case 'slow':
319             testOptions[toCamelCase(option)] = true;
320             break;
321           // one argument options.
322           case 'min-version':
323           case 'max-version':
324             ++jj;
325             testOptions[toCamelCase(option)] = args[jj];
326             break;
327           default:
328             throw ("bad unknown option '" + option + "' at in " + url + ":" + lineNum + ": " + arg);
329         }
330       } else {
331         nonOptions.push(arg);
332       }
333     }
334     var url = prefix + nonOptions.join(" ");
336     if (url.substr(url.length - 4) != '.txt') {
337       var minVersion = testOptions.minVersion;
338       if (!minVersion) {
339         minVersion = hierarchicalOptions.defaultVersion;
340       }
341       var maxVersion = testOptions.maxVersion;
342       if (!maxVersion) {
343         maxVersion = hierarchicalOptions.defaultMaxVersion;
344       }
345       var slow = testOptions.slow;
346       if (!slow) {
347         slow = hierarchicalOptions.defaultSlow;
348       }
350       if (globalOptions.fast && slow) {
351         useTest = false;
352       } else if (globalOptions.minVersion) {
353         useTest = greaterThanOrEqualToVersion(minVersion, globalOptions.minVersion);
354       } else if (globalOptions.maxVersion && maxVersion) {
355         useTest = greaterThanOrEqualToVersion(globalOptions.maxVersion, maxVersion);
356       } else {
357         useTest = greaterThanOrEqualToVersion(globalOptions.version, minVersion);
358         if (maxVersion) {
359           useTest = useTest && greaterThanOrEqualToVersion(maxVersion, globalOptions.version);
360         }
361       }
362     }
364     if (!useTest) {
365       callback(true, []);
366       return;
367     }
369     if (url.substr(url.length - 4) == '.txt') {
370       // If a version was explicity specified pass it down.
371       if (testOptions.minVersion) {
372         hierarchicalOptions.defaultVersion = testOptions.minVersion;
373       }
374       if (testOptions.maxVersion) {
375         hierarchicalOptions.defaultMaxVersion = testOptions.maxVersion;
376       }
377       if (testOptions.slow) {
378         hierarchicalOptions.defaultSlow = testOptions.slow;
379       }
380       loadTextFileAsynchronous(url, function() {
381         return function(success, text) {
382           if (!success) {
383             callback(false, '');
384             return;
385           }
386           var lines = text.split('\n');
387           var prefix = '';
388           var lastSlash = url.lastIndexOf('/');
389           if (lastSlash >= 0) {
390             prefix = url.substr(0, lastSlash + 1);
391           }
392           var fail = false;
393           var count = 1;
394           var index = 0;
395           for (var ii = 0; ii < lines.length; ++ii) {
396             var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, '');
397             if (str.length > 4 &&
398                 str[0] != '#' &&
399                 str[0] != ";" &&
400                 str.substr(0, 2) != "//") {
401               ++count;
402               getFileListImpl(prefix, str, ii + 1, copyObject(hierarchicalOptions), function(index) {
403                 return function(success, new_files) {
404                   //log("got files: " + new_files.length);
405                   if (success) {
406                     files[index] = new_files;
407                   }
408                   finish(success);
409                 };
410               }(index++));
411             }
412           }
413           finish(true);
415           function finish(success) {
416             if (!success) {
417               fail = true;
418             }
419             --count;
420             //log("count: " + count);
421             if (!count) {
422               callback(!fail, files);
423             }
424           }
425         }
426       }());
427     } else {
428       files.push(url);
429       callback(true, files);
430     }
431   };
433   getFileListImpl('', url, 1, globalOptions, function(success, files) {
434     // flatten
435     var flat = [];
436     flatten(files);
437     function flatten(files) {
438       for (var ii = 0; ii < files.length; ++ii) {
439         var value = files[ii];
440         if (typeof(value) == "string") {
441           flat.push(value);
442         } else {
443           flatten(value);
444         }
445       }
446     }
447     callback(success, flat);
448   });
451 var FilterURL = (function() {
452   var prefix = window.location.pathname;
453   prefix = prefix.substring(0, prefix.lastIndexOf("/") + 1);
454   return function(url) {
455     if (url.substring(0, prefix.length) == prefix) {
456       url = url.substring(prefix.length);
457     }
458     return url;
459   };
460 }());
462 var TestFile = function(url) {
463   this.url = url;
466 var Test = function(file) {
467   this.file = file;
470 var TestHarness = function(iframe, filelistUrl, reportFunc, options) {
471   this.window = window;
472   this.iframes = iframe.length ? iframe : [iframe];
473   this.reportFunc = reportFunc;
474   this.timeoutDelay = 20000;
475   this.files = [];
476   this.allowSkip = options.allowSkip;
477   this.webglVersion = getMajorVersion(options.version);
478   this.dumpShaders = options.dumpShaders;
479   this.quiet = options.quiet;
481   var that = this;
482   getFileList(filelistUrl, function() {
483     return function(success, files) {
484       that.addFiles_(success, files);
485     };
486   }(), options);
490 TestHarness.reportType = {
491   ADD_PAGE: 1,
492   READY: 2,
493   START_PAGE: 3,
494   TEST_RESULT: 4,
495   FINISH_PAGE: 5,
496   FINISHED_ALL_TESTS: 6
499 TestHarness.prototype.addFiles_ = function(success, files) {
500   if (!success) {
501     this.reportFunc(
502         TestHarness.reportType.FINISHED_ALL_TESTS,
503         '',
504         'Unable to load tests. Are you running locally?\n' +
505         'You need to run from a server or configure your\n' +
506         'browser to allow access to local files (not recommended).\n\n' +
507         'Note: An easy way to run from a server:\n\n' +
508         '\tcd path_to_tests\n' +
509         '\tpython -m SimpleHTTPServer\n\n' +
510         'then point your browser to ' +
511           '<a href="http://localhost:8000/webgl-conformance-tests.html">' +
512           'http://localhost:8000/webgl-conformance-tests.html</a>',
513         false)
514     return;
515   }
516   log("total files: " + files.length);
517   for (var ii = 0; ii < files.length; ++ii) {
518     log("" + ii + ": " + files[ii]);
519     this.files.push(new TestFile(files[ii]));
520     this.reportFunc(TestHarness.reportType.ADD_PAGE, '', files[ii], undefined);
521   }
522   this.reportFunc(TestHarness.reportType.READY, '', undefined, undefined);
525 TestHarness.prototype.runTests = function(opt_options) {
526   var options = opt_options || { };
527   options.start = options.start || 0;
528   options.count = options.count || this.files.length;
530   this.idleIFrames = this.iframes.slice(0);
531   this.runningTests = {};
532   var testsToRun = [];
533   for (var ii = 0; ii < options.count; ++ii) {
534     testsToRun.push(ii + options.start);
535   }
536   this.numTestsRemaining = options.count;
537   this.testsToRun = testsToRun;
538   this.startNextTest();
541 TestHarness.prototype._bumpTimeout = function(test) {
542   const newTimeoutAt = performance.now() + this.timeoutDelay;
543   if (test.timeoutAt) {
544     test.timeoutAt = newTimeoutAt;
545     return;
546   }
547   test.timeoutAt = newTimeoutAt;
549   const harness = this;
551   function enqueueWatchdog() {
552     const remaining = test.timeoutAt - performance.now();
553     //console.log(`watchdog started at ${performance.now()}, ${test.timeoutAt} requested`);
554     this.window.setTimeout(() => {
555       if (!test.timeoutAt) return; // Timeout was cleared.
556       const remainingAtCheckTime = test.timeoutAt - performance.now();
557       if (performance.now() >= test.timeoutAt) {
558         //console.log(`watchdog won at ${performance.now()}, ${test.timeoutAt} requested`);
559         harness.timeout(test);
560         return;
561       }
562       //console.log(`watchdog lost at ${performance.now()}, as ${test.timeoutAt} is now requested`);
563       enqueueWatchdog();
564     }, remaining);
565   }
566   enqueueWatchdog();
569 TestHarness.prototype.clearTimeout = function(test) {
570   test.timeoutAt = null;
573 TestHarness.prototype.startNextTest = function() {
574   if (this.numTestsRemaining == 0) {
575     log("done");
576     this.reportFunc(TestHarness.reportType.FINISHED_ALL_TESTS,
577                     '', '', true);
578   } else {
579     while (this.testsToRun.length > 0 && this.idleIFrames.length > 0) {
580       var testId = this.testsToRun.shift();
581       var iframe = this.idleIFrames.shift();
582       this.startTest(iframe, this.files[testId], this.webglVersion);
583     }
584   }
587 TestHarness.prototype.startTest = function(iframe, testFile, webglVersion) {
588   var test = {
589     iframe: iframe,
590     testFile: testFile
591   };
592   var url = testFile.url;
593   this.runningTests[url] = test;
594   log("loading: " + url);
595   if (this.reportFunc(TestHarness.reportType.START_PAGE, url, url, undefined)) {
596     iframe.src = getURLWithOptions(url, {
597       "webglVersion": webglVersion,
598       "dumpShaders": this.dumpShaders,
599       "quiet": this.quiet
600     });
601     this._bumpTimeout(test);
602   } else {
603     this.reportResults(url, !!this.allowSkip, "skipped", true);
604     this.notifyFinished(url);
605   }
608 TestHarness.prototype.getTest = function(url) {
609   var test = this.runningTests[FilterURL(url)];
610   if (!test) {
611     throw("unknown test:" + url);
612   }
613   return test;
616 TestHarness.prototype.reportResults = function(url, success, msg, skipped) {
617   url = FilterURL(url);
618   var test = this.getTest(url);
619   if (0) {
620     // This is too slow to leave on for tests like
621     // deqp/functional/gles3/vertexarrays/multiple_attributes.output.html
622     // which has 33013505 calls to reportResults.
623     log((success ? "PASS" : "FAIL") + ": " + msg);
624   }
625   this.reportFunc(TestHarness.reportType.TEST_RESULT, url, msg, success, skipped);
626   // For each result we get, reset the timeout
627   this._bumpTimeout(test);
630 TestHarness.prototype.dequeTest = function(test) {
631   this.clearTimeout(test);
632   this.idleIFrames.push(test.iframe);
633   delete this.runningTests[test.testFile.url];
634   --this.numTestsRemaining;
637 TestHarness.prototype.notifyFinished = function(url) {
638   url = FilterURL(url);
639   var test = this.getTest(url);
640   log(url + ": finished");
641   this.dequeTest(test);
642   this.reportFunc(TestHarness.reportType.FINISH_PAGE, url, url, true);
643   this.startNextTest();
646 TestHarness.prototype.timeout = function(test) {
647   this.dequeTest(test);
648   var url = test.testFile.url;
649   log(url + ": timeout");
650   this.reportFunc(TestHarness.reportType.FINISH_PAGE, url, url, undefined);
651   this.startNextTest();
654 TestHarness.prototype.setTimeoutDelay = function(x) {
655   this.timeoutDelay = x;
658 return {
659     'TestHarness': TestHarness,
660     'getMajorVersion': getMajorVersion,
661     'getURLWithOptions': getURLWithOptions
662   };
664 }();