From ef87233a17dee7fa8052df12e382799c7cb67c8b Mon Sep 17 00:00:00 2001 From: Fritz Heiden Date: Thu, 16 May 2024 16:46:41 +0200 Subject: [PATCH] feat: update wave test runner to current version --- tools/wave/configuration_loader.py | 7 +- tools/wave/data/client.py | 4 +- tools/wave/data/device.py | 4 +- tools/wave/data/event_listener.py | 6 +- tools/wave/data/http_polling_client.py | 4 +- .../wave/data/http_polling_event_listener.py | 4 +- tools/wave/data/session.py | 11 +- tools/wave/ecmascript/README.MD | 51 + tools/wave/ecmascript/generate-tests.js | 309 ++++++ tools/wave/ecmascript/test-template.html | 13 + .../wave/ecmascript/test-template.iframe.html | 16 + tools/wave/ecmascript/webplatform-adapter.js | 109 ++ tools/wave/network/api/api_handler.py | 23 +- tools/wave/network/api/devices_api_handler.py | 26 +- tools/wave/network/api/general_api_handler.py | 5 +- tools/wave/network/api/results_api_handler.py | 8 +- .../wave/network/api/sessions_api_handler.py | 63 +- tools/wave/network/api/tests_api_handler.py | 16 +- tools/wave/network/http_handler.py | 40 +- tools/wave/network/static_handler.py | 8 +- tools/wave/requirements.txt | 4 +- tools/wave/testing/devices_manager.py | 6 +- tools/wave/testing/event_dispatcher.py | 5 +- tools/wave/testing/results_manager.py | 64 +- tools/wave/testing/sessions_manager.py | 93 +- tools/wave/testing/test_loader.py | 43 +- tools/wave/testing/tests_manager.py | 133 ++- tools/wave/testing/wpt_report.py | 26 +- tools/wave/tests/test_api.py | 164 +++ tools/wave/tests/test_wave.py | 12 +- tools/wave/tox.ini | 5 +- tools/wave/utils/deserializer.py | 8 +- tools/wave/utils/serializer.py | 4 +- tools/wave/utils/user_agent_parser.py | 4 +- tools/wave/wave-cli.py | 113 +++ tools/wave/wave_server.py | 5 +- tools/wave/webgl/prepare-tests.js | 82 ++ tools/wave/webgl/resources/js-test-pre.js | 531 ++++++++++ tools/wave/webgl/resources/list_all_tests | 677 ++++++++++++ tools/wave/webgl/resources/unit.js | 960 ++++++++++++++++++ tools/wave/www/comparison.html | 1 + tools/wave/www/configuration.html | 193 ++-- tools/wave/www/finish.html | 1 + tools/wave/www/lib/wave-service.js | 25 +- tools/wave/www/newsession.html | 1 + tools/wave/www/next.html | 8 +- tools/wave/www/overview.html | 1 + tools/wave/www/pause.html | 1 + tools/wave/www/results.html | 1 + tools/wave/www/test.html | 1 + 50 files changed, 3478 insertions(+), 421 deletions(-) create mode 100644 tools/wave/ecmascript/README.MD create mode 100644 tools/wave/ecmascript/generate-tests.js create mode 100644 tools/wave/ecmascript/test-template.html create mode 100644 tools/wave/ecmascript/test-template.iframe.html create mode 100644 tools/wave/ecmascript/webplatform-adapter.js create mode 100644 tools/wave/tests/test_api.py create mode 100644 tools/wave/wave-cli.py create mode 100644 tools/wave/webgl/prepare-tests.js create mode 100644 tools/wave/webgl/resources/js-test-pre.js create mode 100644 tools/wave/webgl/resources/list_all_tests create mode 100644 tools/wave/webgl/resources/unit.js diff --git a/tools/wave/configuration_loader.py b/tools/wave/configuration_loader.py index 2e2aa331511741a..5741ba3cf65780e 100644 --- a/tools/wave/configuration_loader.py +++ b/tools/wave/configuration_loader.py @@ -1,7 +1,8 @@ -# mypy: allow-untyped-defs - +from __future__ import absolute_import +from __future__ import unicode_literals import json import os +from io import open from tools.wpt import wpt @@ -91,7 +92,7 @@ def load_configuration_file(path): return {} configuration = None - with open(path) as configuration_file: + with open(path, "r") as configuration_file: configuration_file_content = configuration_file.read() configuration = json.loads(configuration_file_content) return configuration diff --git a/tools/wave/data/client.py b/tools/wave/data/client.py index d5643a5660173e6..ab6851ab34b91a3 100644 --- a/tools/wave/data/client.py +++ b/tools/wave/data/client.py @@ -1,6 +1,4 @@ -# mypy: allow-untyped-defs - -class Client: +class Client(object): def __init__(self, session_token): self.session_token = session_token diff --git a/tools/wave/data/device.py b/tools/wave/data/device.py index b1d06cbf30d62a8..3b2ccdf4075b9ca 100644 --- a/tools/wave/data/device.py +++ b/tools/wave/data/device.py @@ -1,6 +1,4 @@ -# mypy: allow-untyped-defs - -class Device: +class Device(object): def __init__(self, token, user_agent, name, last_active): self.token = token self.user_agent = user_agent diff --git a/tools/wave/data/event_listener.py b/tools/wave/data/event_listener.py index c4b98653e1883de..2695df83cbab59a 100644 --- a/tools/wave/data/event_listener.py +++ b/tools/wave/data/event_listener.py @@ -1,8 +1,6 @@ -# mypy: allow-untyped-defs - -class EventListener: +class EventListener(object): def __init__(self, dispatcher_token): - super().__init__() + super(EventListener, self).__init__() self.dispatcher_token = dispatcher_token self.token = None diff --git a/tools/wave/data/http_polling_client.py b/tools/wave/data/http_polling_client.py index 3235569a9891a73..740f547c9584931 100644 --- a/tools/wave/data/http_polling_client.py +++ b/tools/wave/data/http_polling_client.py @@ -1,11 +1,9 @@ -# mypy: allow-untyped-defs - from .client import Client class HttpPollingClient(Client): def __init__(self, session_token, event): - super().__init__(session_token) + super(HttpPollingClient, self).__init__(session_token) self.event = event def send_message(self, message): diff --git a/tools/wave/data/http_polling_event_listener.py b/tools/wave/data/http_polling_event_listener.py index b1e46edd36d853d..df731d4856c4f9b 100644 --- a/tools/wave/data/http_polling_event_listener.py +++ b/tools/wave/data/http_polling_event_listener.py @@ -1,10 +1,8 @@ -# mypy: allow-untyped-defs - from .event_listener import EventListener class HttpPollingEventListener(EventListener): def __init__(self, dispatcher_token, event): - super().__init__(dispatcher_token) + super(HttpPollingEventListener, self).__init__(dispatcher_token) self.event = event self.message = None diff --git a/tools/wave/data/session.py b/tools/wave/data/session.py index bb1b932dae60b15..f80b4719d0d3f09 100644 --- a/tools/wave/data/session.py +++ b/tools/wave/data/session.py @@ -1,5 +1,5 @@ -# mypy: allow-untyped-defs - +from __future__ import absolute_import +from __future__ import unicode_literals from ..testing.test_loader import MANUAL, AUTOMATIC PAUSED = "paused" @@ -9,8 +9,11 @@ PENDING = "pending" UNKNOWN = "unknown" +WMAS = "wmas" +DPCTF = "dpctf" + -class Session: +class Session(object): def __init__( self, token=None, @@ -32,7 +35,6 @@ def __init__( reference_tokens=None, browser=None, expiration_date=None, - type=None, malfunctioning_tests=None ): if token is None: @@ -72,7 +74,6 @@ def __init__( self.reference_tokens = reference_tokens self.browser = browser self.expiration_date = expiration_date - self.type = type if malfunctioning_tests is None: malfunctioning_tests = [] self.malfunctioning_tests = malfunctioning_tests diff --git a/tools/wave/ecmascript/README.MD b/tools/wave/ecmascript/README.MD new file mode 100644 index 000000000000000..96d51477fb7952d --- /dev/null +++ b/tools/wave/ecmascript/README.MD @@ -0,0 +1,51 @@ +# WMAS2017 ECMA Integration + +## Generating Tests + +Clone the ECMAScript 5 tests into the working directory + +``` +$ git clone git@github.com:tc39/test262.git -b es5-tests +``` + +Working directory should look like this + +``` +generate-tests.js +test262 +test-template.html +webplatform-adapter.js +``` + +Generate the tests by running + +``` +$ node generate-tests.js +``` + +Generated tests are placed in `ecmascript` directory. Copy this +directory into the top level directory of the Web Platform Tests +hierarchy in order for the Web Platform Test Runner to run them. + +## Test generation parameters + +``` +$ node generate-tests.js < test262-repo-dir > < output-dir > +``` + +You can specify where the test262 repository is located and where the +generated tests should be put in by passing the paths to the +generator script as shown above. + +## Excluded tests + +The following tests are automatically excluded, because they are +causing the browser to freeze. + +``` +ch15/15.4/15.4.4/15.4.4.15/15.4.4.15-3-14.js +ch15/15.4/15.4.4/15.4.4.18/15.4.4.18-3-14.js +ch15/15.4/15.4.4/15.4.4.20/15.4.4.20-3-14.js +ch15/15.4/15.4.4/15.4.4.21/15.4.4.21-3-14.js +ch15/15.4/15.4.4/15.4.4.22/15.4.4.22-3-14.js +``` diff --git a/tools/wave/ecmascript/generate-tests.js b/tools/wave/ecmascript/generate-tests.js new file mode 100644 index 000000000000000..3a6d54929a1bfd1 --- /dev/null +++ b/tools/wave/ecmascript/generate-tests.js @@ -0,0 +1,309 @@ +const fs = require("fs-extra"); +const path = require("path"); + +const readDirectory = async directoryPath => { + return new Promise((resolve, reject) => { + fs.readdir(directoryPath, (error, files) => { + if (error) { + reject(error); + } + resolve(files); + }); + }); +}; + +const makeDirectory = async directoryPath => { + return new Promise((resolve, reject) => { + fs.mkdir(directoryPath, error => { + if (error) { + reject(error); + } + resolve(); + }); + }); +}; + +const readStats = async path => { + return new Promise((resolve, reject) => { + fs.stat(path, (error, stats) => { + if (error) { + resolve(null); + } + resolve(stats); + }); + }); +}; + +const readFile = async path => { + return new Promise((resolve, reject) => { + fs.readFile( + path, + { + encoding: "UTF-8" + }, + (error, data) => { + if (error) { + reject(error); + } + resolve(data); + } + ); + }); +}; + +const writeFile = async (path, data) => { + return new Promise((resolve, reject) => { + fs.writeFile(path, data, error => { + if (error) { + reject(error); + } + resolve(); + }); + }); +}; + +const parseFrontmatter = src => { + var start = src.indexOf("/*---"); + var end = src.indexOf("---*/"); + if (start === -1 || end === -1) return null; + + var match, + includes = [], + flags = {}, + negative = null; + var frontmatter = src.substring(start + 5, end); + + match = frontmatter.match(/(?:^|\n)\s*includes:\s*\[([^\]]*)\]/); + if (match) { + includes = match[1].split(",").map(function f(s) { + return s.replace(/^\s+|\s+$/g, ""); + }); + } else { + match = frontmatter.match(/(?:^|\n)\s*includes:\s*\n(\s+-.*\n)/); + if (match) { + includes = match[1].split(",").map(function f(s) { + return s.replace(/^[\s\-]+|\s+$/g, ""); + }); + } + } + + match = frontmatter.match(/(?:^|\n)\s*flags:\s*\[([^\]]*)\]/); + if (match) { + match[1] + .split(",") + .map(function f(s) { + return s.replace(/^\s+|\s+$/g, ""); + }) + .forEach(function(flag) { + switch (flag) { + case "onlyStrict": + if (flags.strict) { + console.error("flag conflict", src); + } + flags.strict = "always"; + break; + case "noStrict": + if (flags.strict) { + console.error("flag conflict"); + } + flags.strict = "never"; + break; + case "module": + flags.module = true; + break; + case "raw": + flags.raw = true; + break; + case "async": + flags.async = true; + break; + case "generated": + case "non-deterministic": + case "CanBlockIsTrue": + case "CanBlockIsFalse": + break; + default: + console.error("unrecocognized flag: " + flag, frontmatter); + break; + } + }); + } + + match = frontmatter.match(/(?:^|\n)\s*negative:/); + if (match) { + var phase, type; + frontmatter + .substr(match.index + 9) + .split("\n") + .forEach(function(line) { + var match = line.match(/\s+phase:\s*(\S+)/); + if (match) { + phase = match[1]; + } + match = line.match(/\s+type:\s*(\S+)/); + if (match) { + type = match[1]; + } + }); + if (!phase || !type) return null; + negative = { + phase: phase, + type: type + }; + } + + return { + includes: includes, + flags: flags, + negative: negative, + isDynamic: /dynamic-import/.test(frontmatter) + }; // lol, do better +}; + +const getOutputPath = ({ testsPath, currentPath, outputPath }) => { + return path.join(outputPath, path.relative(testsPath, currentPath)); +}; + +// Tests that will freeze the runner +// ch15/15.4/15.4.4/15.4.4.15/15.4.4.15-3-14.js +// ch15/15.4/15.4.4/15.4.4.18/15.4.4.18-3-14.js +// ch15/15.4/15.4.4/15.4.4.20/15.4.4.20-3-14.js +// ch15/15.4/15.4.4/15.4.4.21/15.4.4.21-3-14.js +// ch15/15.4/15.4.4/15.4.4.22/15.4.4.22-3-14.js +const excludedTests = [ + /15\.4\.4\.15-3-14\.js/, + /15\.4\.4\.18-3-14\.js/, + /15\.4\.4\.20-3-14\.js/, + /15\.4\.4\.21-3-14\.js/, + /15\.4\.4\.22-3-14\.js/ +]; + +let testCount = 0; + +const generateTest = async ({ + testsPath, + outputPath, + currentPath, + templateContent, + iframeTemplateContent +}) => { + if (!currentPath) currentPath = testsPath; + let stats = await readStats(currentPath); + if (stats.isDirectory()) { + const outputDir = getOutputPath({ + testsPath, + outputPath, + currentPath + }); + if (!(await readStats(outputDir))) await makeDirectory(outputDir); + let files = await readDirectory(currentPath); + for (let file of files) { + await generateTest({ + currentPath: path.join(currentPath, file), + outputPath, + testsPath, + templateContent, + iframeTemplateContent + }); + } + } else { + if ( + currentPath.indexOf(".js") === -1 || + excludedTests.some(regex => regex.test(currentPath)) + ) { + return; + } + + const jsRelativePath = path.relative(testsPath, currentPath); + // console.log(jsRelativePath.replace('.js', '')) + const jsOutputPath = path.join(outputPath, jsRelativePath); + const htmlOutputPath = jsOutputPath.replace(".js", ".html"); + const iframeHtmlOutputPath = jsOutputPath.replace(".js", ".iframe.html"); + const jsSrc = await readFile(currentPath); + const meta = parseFrontmatter(jsSrc); + const includes = (meta && meta.includes) || []; + //console.log(includes); + const testContent = replacePlaceholders(templateContent, { + jsRelativePath, + includes, + iframeTestPath: `/${iframeHtmlOutputPath}` + }); + + const iframeTestContent = replacePlaceholders(iframeTemplateContent, { + jsRelativePath, + includes, + iframeTestPath: `/${iframeHtmlOutputPath}` + }); + + await writeFile(htmlOutputPath, testContent); + await writeFile(iframeHtmlOutputPath, iframeTestContent); + await fs.copy(currentPath, jsOutputPath); + testCount++; + } +}; + +function replacePlaceholders( + content, + { jsRelativePath, includes, iframeTestPath } +) { + content = content.replace( + "{{ TEST_URL }}", + "/ecmascript/tests/" + jsRelativePath + ); + content = content.replace("{{ IFRAME_TEST_URL }}", iframeTestPath); + content = content.replace( + "{{ TEST_TITLE }}", + jsRelativePath.split("/").pop() + ); + content = content.replace( + "{{ INCLUDES }}", + includes + .map(function(src) { + return ""; + }) + .join("\n") + ); + return content; +} + +(async () => { + const ADAPTER_SCRIPT_NAME = "webplatform-adapter.js"; + const HTML_TEMPLATE_NAME = path.join(__dirname, "test-template.html"); + const IFRAME_HTML_TEMPLATE_NAME = path.join( + __dirname, + "test-template.iframe.html" + ); + const DEFAULT_TEST_DIR = "./test262"; + const DEFAULT_OUTPUT_DIR = "."; + const SUB_DIR_NAME = "ecmascript"; + + const testDir = process.argv[2] || DEFAULT_TEST_DIR; + const testsPath = path.join(testDir, "test"); + const harnessDir = path.join(testDir, "harness"); + let outputPath = process.argv[3] || DEFAULT_OUTPUT_DIR; + outputPath = path.join(outputPath, SUB_DIR_NAME); + const testsOutputPath = path.join(outputPath, "tests"); + const harnessOutputDir = path.join(outputPath, "harness"); + const adapterSourcePath = path.join(__dirname, ADAPTER_SCRIPT_NAME); + const adapterDestinationPath = path.join(outputPath, ADAPTER_SCRIPT_NAME); + + if (!(await readStats(outputPath))) await makeDirectory(outputPath); + + console.log("Reading test templates ..."); + const templateContent = await readFile(HTML_TEMPLATE_NAME); + const iframeTemplateContent = await readFile(IFRAME_HTML_TEMPLATE_NAME); + console.log("Generating tests ..."); + await generateTest({ + testsPath, + outputPath: testsOutputPath, + templateContent, + iframeTemplateContent + }); + await fs.copy(adapterSourcePath, adapterDestinationPath); + await fs.copy(harnessDir, harnessOutputDir); + console.log( + `Generated ${testCount} tests in directory ${outputPath} (${path.resolve( + outputPath + )})` + ); +})(); diff --git a/tools/wave/ecmascript/test-template.html b/tools/wave/ecmascript/test-template.html new file mode 100644 index 000000000000000..24a7a09e7d84588 --- /dev/null +++ b/tools/wave/ecmascript/test-template.html @@ -0,0 +1,13 @@ + +{{ TEST_TITLE }} + + + +{{ INCLUDES }} + + + + + diff --git a/tools/wave/ecmascript/test-template.iframe.html b/tools/wave/ecmascript/test-template.iframe.html new file mode 100644 index 000000000000000..9a22eabc7243136 --- /dev/null +++ b/tools/wave/ecmascript/test-template.iframe.html @@ -0,0 +1,16 @@ + +{{ TEST_TITLE }} + + + + + + + +{{ INCLUDES }} + + + + diff --git a/tools/wave/ecmascript/webplatform-adapter.js b/tools/wave/ecmascript/webplatform-adapter.js new file mode 100644 index 000000000000000..21668861eac797b --- /dev/null +++ b/tools/wave/ecmascript/webplatform-adapter.js @@ -0,0 +1,109 @@ +setup(function() { }, { + allow_uncaught_exception: true +}); + +var evaluated = false; + +function $TEST_COMPLETED() { + evaluate(); +} + +function $ERROR(error) { + evaluate(error); +} + +function evaluate(error) { + if (evaluated) { + return; + } + evaluated = true; + getSource(function(source) { + var meta = parseMetadata(source); + console.log(meta); + test(function() { + var negative = null; + if (meta.hasOwnProperty("negative")) { + negative = {}; + if (meta["negative"] !== "") { + negative.regex = new RegExp(meta["negative"]); + } + } + + if (negative) { + if (negative.regex) { + assert_regexp_match(error, negative.regex, meta.description); + } else { + if (error) { + assert_true(true, meta.description); + } else { + throw new Error("Expected an error to be thrown."); + } + } + } else { + if (error) { + throw error; + } else { + assert_true(true, meta.description); + } + } + done(); + }, meta.description); + }); +} + +function getSource(loadedCallback) { + var path = testUrl; + var xhr = new XMLHttpRequest(); + xhr.addEventListener("load", function(content) { + loadedCallback(content.srcElement.response); + }); + xhr.open("GET", path); + xhr.send(); +} + +function parseMetadata(src) { + var meta = {}; + var inMeta = false; + var lines = src.split("\n"); + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (inMeta) { + if (/\*\//.test(line)) { + break; + } + if (/@.+/.test(line)) { + var key = ""; + var value = ""; + var parts = line.split(" "); + for (var j = 0; j < parts.length; j++) { + var part = parts[j]; + if (key === "") { + if (/^@/.test(part)) key = part.replace("@", ""); + } else { + value += part + " "; + } + } + value = value.trim(); + meta[key] = value; + } + } else { + inMeta = /\/\*\*/.test(line); + } + } + return meta; +} + +var errorEventListener = function(error) { + evaluate(error.message); + window.removeEventListener("error", errorEventListener); + document.getElementById("iframe").contentWindow.removeEventListener("error", errorEventListener); +}; + +window.addEventListener("error", errorEventListener); +document.getElementById("iframe").contentWindow.addEventListener("error", errorEventListener); +document.getElementById("iframe").contentWindow.$ERROR = $ERROR; + +// /ecmascript/tests/built-ins/RegExp/prototype/Symbol.match/builtin-coerce-global.html "Aw Snap" +// /ecmascript/tests/built-ins/RegExp/prototype/Symbol.match/coerce-global.html "Aw Snap" +// /ecmascript/tests/built-ins/RegExp/prototype/Symbol.replace/coerce-global.html "Aw Snap" +// /ecmascript/tests/language/statements/for-of/iterator-next-reference.html "Website unresponsive" diff --git a/tools/wave/network/api/api_handler.py b/tools/wave/network/api/api_handler.py index 9c67e6c0cda1e0c..5f1fedd07bb86ae 100644 --- a/tools/wave/network/api/api_handler.py +++ b/tools/wave/network/api/api_handler.py @@ -1,17 +1,20 @@ -# mypy: allow-untyped-defs - +from __future__ import absolute_import +from __future__ import unicode_literals import json import sys import traceback import logging -from urllib.parse import parse_qsl +try: + from urllib.parse import parse_qsl +except ImportError: + from urlparse import parse_qsl global logger logger = logging.getLogger("wave-api-handler") -class ApiHandler: +class ApiHandler(object): def __init__(self, web_root): self._web_root = web_root @@ -53,7 +56,7 @@ def parse_query_parameters(self, request): def handle_exception(self, message): info = sys.exc_info() traceback.print_tb(info[2]) - logger.error(f"{message}: {info[0].__name__}: {info[1].args[0]}") + logger.error("{}: {}: {}".format(message, info[0].__name__, info[1].args[0])) def create_hal_list(self, items, uris, index, count, total): hal_list = {} @@ -68,21 +71,21 @@ def create_hal_list(self, items, uris, index, count, total): if "self" in uris: self_uri = uris["self"] - self_uri += f"?index={index}&count={count}" + self_uri += "?index={}&count={}".format(index, count) links["self"] = {"href": self_uri} first_uri = uris["self"] - first_uri += f"?index={0}&count={count}" + first_uri += "?index={}&count={}".format(0, count) links["first"] = {"href": first_uri} last_uri = uris["self"] - last_uri += f"?index={total - (total % count)}&count={count}" + last_uri += "?index={}&count={}".format(total - (total % count), count) links["last"] = {"href": last_uri} if index + count <= total: next_index = index + count next_uri = uris["self"] - next_uri += f"?index={next_index}&count={count}" + next_uri += "?index={}&count={}".format(next_index, count) links["next"] = {"href": next_uri} if index != 0: @@ -90,7 +93,7 @@ def create_hal_list(self, items, uris, index, count, total): if previous_index < 0: previous_index = 0 previous_uri = uris["self"] - previous_uri += f"?index={previous_index}&count={count}" + previous_uri += "?index={}&count={}".format(previous_index, count) links["previous"] = {"href": previous_uri} hal_list["_links"] = links diff --git a/tools/wave/network/api/devices_api_handler.py b/tools/wave/network/api/devices_api_handler.py index ecd9a9677088e7d..5e8b02bca1f37a7 100644 --- a/tools/wave/network/api/devices_api_handler.py +++ b/tools/wave/network/api/devices_api_handler.py @@ -1,5 +1,3 @@ -# mypy: allow-untyped-defs - import json import threading @@ -13,7 +11,7 @@ class DevicesApiHandler(ApiHandler): def __init__(self, devices_manager, event_dispatcher, web_root): - super().__init__(web_root) + super(DevicesApiHandler, self).__init__(web_root) self._devices_manager = devices_manager self._event_dispatcher = event_dispatcher @@ -65,7 +63,7 @@ def register_event_listener(self, request, response): token = uri_parts[2] query = self.parse_query_parameters(request) - if "device_token" in query: + if u"device_token" in query: self._devices_manager.refresh_device(query["device_token"]) event = threading.Event() @@ -91,7 +89,7 @@ def register_global_event_listener(self, request, response): try: query = self.parse_query_parameters(request) - if "device_token" in query: + if u"device_token" in query: self._devices_manager.refresh_device(query["device_token"]) event = threading.Event() @@ -110,7 +108,7 @@ def register_global_event_listener(self, request, response): self.send_json(data=message, response=response) self._event_dispatcher.remove_event_listener(event_listener_token) except Exception: - self.handle_exception("Failed to register global event listener") + self.handle_exception(u"Failed to register global event listener") response.status = 500 def post_global_event(self, request, response): @@ -121,7 +119,7 @@ def post_global_event(self, request, response): event = json.loads(body) query = self.parse_query_parameters(request) - if "device_token" in query: + if u"device_token" in query: self._devices_manager.refresh_device(query["device_token"]) event_type = None @@ -147,7 +145,7 @@ def post_event(self, request, response): event = json.loads(body) query = self.parse_query_parameters(request) - if "device_token" in query: + if u"device_token" in query: self._devices_manager.refresh_device(query["device_token"]) event_type = None @@ -168,18 +166,18 @@ def handle_request(self, request, response): # /api/devices if len(uri_parts) == 2: - if method == "POST": + if method == u"POST": self.create_device(request, response) return - if method == "GET": + if method == u"GET": self.read_devices(request, response) return # /api/devices/ if len(uri_parts) == 3: function = uri_parts[2] - if method == "GET": - if function == "events": + if method == u"GET": + if function == u"events": self.register_global_event_listener(request, response) return self.read_device(request, response) @@ -192,8 +190,8 @@ def handle_request(self, request, response): # /api/devices// if len(uri_parts) == 4: function = uri_parts[3] - if method == "GET": - if function == "events": + if method == u"GET": + if function == u"events": self.register_event_listener(request, response) return if method == "POST": diff --git a/tools/wave/network/api/general_api_handler.py b/tools/wave/network/api/general_api_handler.py index 65883a9b75abfd7..bd588d13e85e5a7 100644 --- a/tools/wave/network/api/general_api_handler.py +++ b/tools/wave/network/api/general_api_handler.py @@ -1,4 +1,5 @@ -# mypy: allow-untyped-defs +from __future__ import absolute_import +from __future__ import unicode_literals from .api_handler import ApiHandler @@ -16,7 +17,7 @@ def __init__( test_type_selection_enabled, test_file_selection_enabled ): - super().__init__(web_root) + super(GeneralApiHandler, self).__init__(web_root) self.read_sessions_enabled = read_sessions_enabled self.import_results_enabled = import_results_enabled self.reports_enabled = reports_enabled diff --git a/tools/wave/network/api/results_api_handler.py b/tools/wave/network/api/results_api_handler.py index a9da0df10f3f7a5..276ee023892a08d 100644 --- a/tools/wave/network/api/results_api_handler.py +++ b/tools/wave/network/api/results_api_handler.py @@ -1,5 +1,5 @@ -# mypy: allow-untyped-defs - +from __future__ import absolute_import +from __future__ import unicode_literals import json from .api_handler import ApiHandler @@ -9,7 +9,7 @@ class ResultsApiHandler(ApiHandler): def __init__(self, results_manager, session_manager, web_root): - super().__init__(web_root) + super(ResultsApiHandler, self).__init__(web_root) self._results_manager = results_manager self._sessions_manager = session_manager @@ -75,7 +75,7 @@ def read_results_api_wpt_report_url(self, request, response): def read_results_api_wpt_multi_report_uri(self, request, response): try: uri_parts = self.parse_uri(request) - api = uri_parts[2] + api = uri_parts[3] query = self.parse_query_parameters(request) tokens = query["tokens"].split(",") uri = self._results_manager.read_results_wpt_multi_report_uri( diff --git a/tools/wave/network/api/sessions_api_handler.py b/tools/wave/network/api/sessions_api_handler.py index 9eb896b80702e99..61b291998ce3ff7 100644 --- a/tools/wave/network/api/sessions_api_handler.py +++ b/tools/wave/network/api/sessions_api_handler.py @@ -1,5 +1,5 @@ -# mypy: allow-untyped-defs - +from __future__ import absolute_import +from __future__ import unicode_literals import json import threading @@ -20,9 +20,9 @@ def __init__( results_manager, event_dispatcher, web_root, - read_sessions_enabled + read_sessions_enabled, ): - super().__init__(web_root) + super(SessionsApiHandler, self).__init__(web_root) self._sessions_manager = sessions_manager self._results_manager = results_manager self._event_dispatcher = event_dispatcher @@ -53,9 +53,6 @@ def create_session(self, body, headers): expiration_date = None if "expiration_date" in config: expiration_date = config["expiration_date"] - type = None - if "type" in config: - type = config["type"] session = self._sessions_manager.create_session( tests, @@ -65,20 +62,16 @@ def create_session(self, body, headers): user_agent, labels, expiration_date, - type ) - return { - "format": "application/json", - "data": {"token": session.token} - } + return {"format": "application/json", "data": {"token": session.token}} except InvalidDataException: self.handle_exception("Failed to create session") return { "format": "application/json", "data": {"error": "Invalid input data!"}, - "status": 400 + "status": 400, } except Exception: @@ -106,8 +99,8 @@ def read_session(self, token): "browser": data["browser"], "is_public": data["is_public"], "date_created": data["date_created"], - "labels": data["labels"] - } + "labels": data["labels"], + }, } except Exception: self.handle_exception("Failed to read session") @@ -125,7 +118,9 @@ def read_sessions(self, query_parameters, uri_path): if "expand" in query_parameters: expand = query_parameters["expand"].split(",") - session_tokens = self._sessions_manager.read_sessions(index=index, count=count) + session_tokens = self._sessions_manager.read_sessions( + index=index, count=count + ) total_sessions = self._sessions_manager.get_total_sessions() embedded = {} @@ -152,18 +147,17 @@ def read_sessions(self, query_parameters, uri_path): uris = { "self": uri_path, "configuration": self._web_root + "api/sessions/{token}", - "status": self._web_root + "api/sessions/{token}/status" + "status": self._web_root + "api/sessions/{token}/status", } - data = self.create_hal_list(session_tokens, uris, index, count, total=total_sessions) + data = self.create_hal_list( + session_tokens, uris, index, count, total=total_sessions + ) if len(embedded) > 0: data["_embedded"] = embedded - return { - "format": "application/json", - "data": data - } + return {"format": "application/json", "data": data} except Exception: self.handle_exception("Failed to read session") return {"status": 500} @@ -183,8 +177,8 @@ def read_session_status(self, token): "status": data["status"], "date_started": data["date_started"], "date_finished": data["date_finished"], - "expiration_date": data["expiration_date"] - } + "expiration_date": data["expiration_date"], + }, } except Exception: self.handle_exception("Failed to read session status") @@ -221,17 +215,9 @@ def update_session_configuration(self, request, response): reference_tokens = [] if "reference_tokens" in config: reference_tokens = config["reference_tokens"] - type = None - if "type" in config: - type = config["type"] self._sessions_manager.update_session_configuration( - token, - tests, - test_types, - timeouts, - reference_tokens, - type + token, tests, test_types, timeouts, reference_tokens ) except NotFoundException: self.handle_exception("Failed to update session configuration") @@ -338,12 +324,14 @@ def register_event_listener(self, request, response): query_parameters = self.parse_query_parameters(request) last_event_number = None - if ("last_event" in query_parameters): + if "last_event" in query_parameters: last_event_number = int(query_parameters["last_event"]) event = threading.Event() http_polling_event_listener = HttpPollingEventListener(token, event) - event_listener_token = self._event_dispatcher.add_event_listener(http_polling_event_listener, last_event_number) + event_listener_token = self._event_dispatcher.add_event_listener( + http_polling_event_listener, last_event_number + ) event.wait() @@ -364,9 +352,8 @@ def push_event(self, request, response): message = json.loads(body) self._event_dispatcher.dispatch_event( - token, - message["type"], - message["data"]) + token, message["type"], message["data"] + ) except Exception: self.handle_exception("Failed to push session event") diff --git a/tools/wave/network/api/tests_api_handler.py b/tools/wave/network/api/tests_api_handler.py index 38035837711ab65..c6126d92a79d51e 100644 --- a/tools/wave/network/api/tests_api_handler.py +++ b/tools/wave/network/api/tests_api_handler.py @@ -1,8 +1,11 @@ -# mypy: allow-untyped-defs - +from __future__ import absolute_import +from __future__ import unicode_literals import json -from urllib.parse import urlunsplit +try: + from urllib.parse import urlunsplit +except ImportError: + from urlparse import urlunsplit from .api_handler import ApiHandler from ...utils.serializer import serialize_session @@ -27,7 +30,7 @@ def __init__( web_root, test_loader ): - super().__init__(web_root) + super(TestsApiHandler, self).__init__(web_root) self._tests_manager = tests_manager self._sessions_manager = sessions_manager self._wpt_port = wpt_port @@ -111,7 +114,8 @@ def read_next_test(self, request, response): test_timeout = self._tests_manager.get_test_timeout( test=test, session=session) - test = self._sessions_manager.get_test_path_with_query(test, session) + test = self._sessions_manager.get_test_path_with_query( + test, session) url = self._generate_test_url( test=test, token=token, @@ -295,4 +299,4 @@ def _generate_url(self, query = "" if protocol is None: protocol = "http" - return urlunsplit([protocol, f"{hostname}:{port}", uri, query, '']) + return urlunsplit([protocol, "{}:{}".format(hostname, port), uri, query, '']) diff --git a/tools/wave/network/http_handler.py b/tools/wave/network/http_handler.py index b76f711cf1d7c65..5951e1e409dbd6e 100644 --- a/tools/wave/network/http_handler.py +++ b/tools/wave/network/http_handler.py @@ -1,6 +1,8 @@ -# mypy: allow-untyped-defs - -import http.client as httplib +from __future__ import unicode_literals +try: + import http.client as httplib +except ImportError: + import httplib import sys import logging import traceback @@ -9,17 +11,17 @@ global logger logger = logging.getLogger("wave-api-handler") -class HttpHandler: +class HttpHandler(object): def __init__( self, - static_handler, - sessions_api_handler, - tests_api_handler, - results_api_handler, - devices_api_handler, - general_api_handler, - http_port, - web_root + static_handler=None, + sessions_api_handler=None, + tests_api_handler=None, + results_api_handler=None, + devices_api_handler=None, + general_api_handler=None, + http_port=None, + web_root=None ): self.static_handler = static_handler self.sessions_api_handler = sessions_api_handler @@ -89,10 +91,9 @@ def _remove_web_root(self, path): path = path[len(self._web_root):] return path - def _proxy(self, request, response): host = 'localhost' - port = int(self._http_port) + port = str(self._http_port) uri = request.url_parts.path uri = uri + "?" + request.url_parts.query content_length = request.headers.get('Content-Length') @@ -100,11 +101,10 @@ def _proxy(self, request, response): if content_length is not None: data = request.raw_input.read(int(content_length)) method = request.method - headers = {} - for key in request.headers: - value = request.headers[key] - headers[key.decode("utf-8")] = value.decode("utf-8") + + for header in request.headers: + headers[header] = request.headers[header] try: proxy_connection = httplib.HTTPConnection(host, port) @@ -114,9 +114,9 @@ def _proxy(self, request, response): response.headers = proxy_response.getheaders() response.status = proxy_response.status - except OSError: + except IOError: message = "Failed to perform proxy request" info = sys.exc_info() traceback.print_tb(info[2]) - logger.error(f"{message}: {info[0].__name__}: {info[1].args[0]}") + logger.error("{}: {}: {}".format(message, info[0].__name__, info[1].args[0])) response.status = 500 diff --git a/tools/wave/network/static_handler.py b/tools/wave/network/static_handler.py index 230af8da1a41cb3..f5b7d7a5f6fa652 100644 --- a/tools/wave/network/static_handler.py +++ b/tools/wave/network/static_handler.py @@ -1,9 +1,11 @@ -# mypy: allow-untyped-defs - +from __future__ import with_statement +from __future__ import absolute_import +from __future__ import unicode_literals import os +from io import open -class StaticHandler: +class StaticHandler(object): def __init__(self, web_root, http_port, https_port): self.static_dir = os.path.join( os.getcwd(), "tools/wave/www") diff --git a/tools/wave/requirements.txt b/tools/wave/requirements.txt index 3bb476fd968b2d7..ea9f2810db419e5 100644 --- a/tools/wave/requirements.txt +++ b/tools/wave/requirements.txt @@ -1,2 +1,2 @@ -ua-parser==0.18.0 -python-dateutil==2.9.0.post0 +ua-parser==0.8.0 +python-dateutil==2.8.1 diff --git a/tools/wave/testing/devices_manager.py b/tools/wave/testing/devices_manager.py index 935782d1372b678..c03572e25903977 100644 --- a/tools/wave/testing/devices_manager.py +++ b/tools/wave/testing/devices_manager.py @@ -1,5 +1,3 @@ -# mypy: allow-untyped-defs - import time import uuid @@ -15,7 +13,7 @@ DEVICE_TIMEOUT = 60000 # 60sec RECONNECT_TIME = 5000 # 5sec -class DevicesManager: +class DevicesManager(object): def initialize(self, event_dispatcher): self.devices = {} self._event_dispatcher = event_dispatcher @@ -41,7 +39,7 @@ def create_device(self, user_agent): def read_device(self, token): if token not in self.devices: - raise NotFoundException(f"Could not find device '{token}'") + raise NotFoundException("Could not find device '{}'".format(token)) return self.devices[token] def read_devices(self): diff --git a/tools/wave/testing/event_dispatcher.py b/tools/wave/testing/event_dispatcher.py index 9bfb6ed712a034f..0ded9c6e553ffc3 100644 --- a/tools/wave/testing/event_dispatcher.py +++ b/tools/wave/testing/event_dispatcher.py @@ -1,5 +1,4 @@ -# mypy: allow-untyped-defs - +from __future__ import unicode_literals import uuid import time from threading import Timer @@ -13,7 +12,7 @@ DEVICE_ADDED_EVENT = "device_added" DEVICE_REMOVED_EVENT = "device_removed" -class EventDispatcher: +class EventDispatcher(object): def __init__(self, event_cache_duration): self._listeners = {} self._events = {} diff --git a/tools/wave/testing/results_manager.py b/tools/wave/testing/results_manager.py index 13dfcbad7d8515a..fa1c0e92faa81bf 100644 --- a/tools/wave/testing/results_manager.py +++ b/tools/wave/testing/results_manager.py @@ -1,5 +1,5 @@ -# mypy: allow-untyped-defs - +from __future__ import absolute_import +from __future__ import unicode_literals import os import shutil import re @@ -7,7 +7,6 @@ import hashlib import zipfile import time -from threading import Timer from ..utils.user_agent_parser import parse_user_agent, abbreviate_browser_name from ..utils.serializer import serialize_session @@ -20,12 +19,11 @@ from ..data.session import COMPLETED WAVE_SRC_DIR = "./tools/wave" -RESULTS_FILE_REGEX = r"^\w\w\d\d\d?\.json$" +RESULTS_FILE_REGEX = "^\w\w\d\d\d?\.json$" RESULTS_FILE_PATTERN = re.compile(RESULTS_FILE_REGEX) -SESSION_RESULTS_TIMEOUT = 60*30 # 30min -class ResultsManager: +class ResultsManager(object): def initialize( self, results_directory_path, @@ -42,7 +40,6 @@ def initialize( self._reports_enabled = reports_enabled self._results = {} self._persisting_interval = persisting_interval - self._timeouts = {} def create_result(self, token, data): result = self.prepare_result(data) @@ -89,10 +86,10 @@ def read_results(self, token, filter_path=None): if filter_path is not None: filter_api = next((p for p in filter_path.split("/") if p is not None), None) - results = self._read_from_cache(token) - if results == []: - results = self.load_results(token) - self._set_session_cache(token, results) + cached_results = self._read_from_cache(token) + persisted_results = self.load_results(token) + results = self._combine_results_by_api(cached_results, + persisted_results) filtered_results = {} @@ -216,13 +213,12 @@ def read_common_passed_tests(self, tokens=None): if test in failed_tests[api]: continue failed_tests[api].append(test) - return passed_tests def read_results_wpt_report_uri(self, token, api): api_directory = os.path.join(self._results_directory_path, token, api) if not os.path.isdir(api_directory): return None - return f"/results/{token}/{api}/all.html" + return "/results/{}/{}/all.html".format(token, api) def read_results_wpt_multi_report_uri(self, tokens, api): comparison_directory_name = self.get_comparison_identifier(tokens) @@ -238,7 +234,7 @@ def read_results_wpt_multi_report_uri(self, tokens, api): if not os.path.isdir(api_directory_path): self.generate_multi_report(tokens, api) - return f"/results/{relative_api_directory_path}/all.html" + return "/results/{}/all.html".format(relative_api_directory_path) def delete_results(self, token): results_directory = os.path.join(self._results_directory_path, token) @@ -252,7 +248,8 @@ def persist_session(self, session): return for api in list(self._results[token].keys())[:]: self.save_api_results(token, api) - self.create_info_file(session) + self.create_info_file(session) + self._clear_cache_api(token, api) session.recent_completed_count = 0 self._sessions_manager.update_session(session) @@ -272,7 +269,7 @@ def load_results(self, token): continue file_path = os.path.join(api_directory, file_name) data = None - with open(file_path) as file: + with open(file_path, "r") as file: data = file.read() result = json.loads(data) results[api] = result["results"] @@ -289,28 +286,22 @@ def _push_to_cache(self, token, result): if api not in self._results[token]: self._results[token][api] = [] self._results[token][api].append(result) - self._set_timeout(token) - - def _set_session_cache(self, token, results): - if token is None: - return - self._results[token] = results - self._set_timeout(token) def _read_from_cache(self, token): if token is None: return [] if token not in self._results: return [] - self._set_timeout(token) return self._results[token] - def _clear_session_cache(self, token): + def _clear_cache_api(self, token, api): if token is None: return if token not in self._results: return - del self._results[token] + if api not in self._results[token]: + return + del self._results[token][api] def _combine_results_by_api(self, result_a, result_b): combined_result = {} @@ -488,7 +479,7 @@ def export_results_api_json(self, token, api): if not os.path.isfile(file_path): return None - with open(file_path) as file: + with open(file_path, "r") as file: blob = file.read() return blob @@ -547,7 +538,7 @@ def export_results(self, token): zip.write(file_path, file_name, zipfile.ZIP_DEFLATED) zip.close() - with open(zip_file_name) as file: + with open(zip_file_name, "rb") as file: blob = file.read() os.remove(zip_file_name) @@ -556,7 +547,7 @@ def export_results(self, token): def export_results_overview(self, token): session = self._sessions_manager.read_session(token) if session is None: - raise NotFoundException(f"Could not find session {token}") + raise NotFoundException("Could not find session {}".format(token)) tmp_file_name = str(time.time()) + ".zip" zip = zipfile.ZipFile(tmp_file_name, "w") @@ -598,7 +589,7 @@ def load_session_from_info_file(self, info_file_path): if not os.path.isfile(info_file_path): return None - with open(info_file_path) as info_file: + with open(info_file_path, "r") as info_file: data = info_file.read() info_file.close() info = json.loads(str(data)) @@ -607,7 +598,7 @@ def load_session_from_info_file(self, info_file_path): def import_results(self, blob): if not self.is_import_results_enabled: raise PermissionDeniedException() - tmp_file_name = f"{str(time.time())}.zip" + tmp_file_name = "{}.zip".format(str(time.time())) with open(tmp_file_name, "w") as file: file.write(blob) @@ -627,7 +618,7 @@ def import_results(self, blob): os.makedirs(destination_path) zip.extractall(destination_path) self.remove_tmp_files() - self.load_results(token) + self.load_results() return token def import_results_api_json(self, token, api, blob): @@ -663,12 +654,3 @@ def remove_tmp_files(self): if re.match(r"\d{10}\.\d{2}\.zip", file) is None: continue os.remove(file) - - def _set_timeout(self, token): - if token in self._timeouts: - self._timeouts[token].cancel() - - def handler(self, token): - self._clear_session_cache(token) - - self._timeouts[token] = Timer(SESSION_RESULTS_TIMEOUT, handler, [self, token]) diff --git a/tools/wave/testing/sessions_manager.py b/tools/wave/testing/sessions_manager.py index 093c3cffe8507a8..5e70848823bd66a 100644 --- a/tools/wave/testing/sessions_manager.py +++ b/tools/wave/testing/sessions_manager.py @@ -1,5 +1,6 @@ -# mypy: allow-untyped-defs - +from __future__ import division +from __future__ import absolute_import +from __future__ import unicode_literals import uuid import time import os @@ -22,14 +23,16 @@ DEFAULT_TEST_MANUAL_TIMEOUT = 300000 -class SessionsManager: - def initialize(self, - test_loader, - event_dispatcher, - tests_manager, - results_directory, - results_manager, - configuration): +class SessionsManager(object): + def initialize( + self, + test_loader, + event_dispatcher, + tests_manager, + results_directory, + results_manager, + configuration, + ): self._test_loader = test_loader self._sessions = {} self._expiration_timeout = None @@ -48,7 +51,6 @@ def create_session( user_agent=None, labels=None, expiration_date=None, - type=None ): if tests is None: tests = {} @@ -74,20 +76,26 @@ def create_session( for test_type in test_types: if test_type != "automatic" and test_type != "manual": - raise InvalidDataException(f"Unknown type '{test_type}'") + raise InvalidDataException("Unknown type '{}'".format(test_type)) + + if expiration_date is not None and type(expiration_date) != int: + expiration_date = iso_to_millis(expiration_date) + if type(expiration_date) != int: + raise InvalidDataException( + "Expected ISO string for expiration date: {}", expiration_date + ) token = str(uuid.uuid1()) pending_tests = self._test_loader.get_tests( test_types, include_list=tests["include"], exclude_list=tests["exclude"], - reference_tokens=reference_tokens) + reference_tokens=reference_tokens, + ) browser = parse_user_agent(user_agent) - test_files_count = self._tests_manager.calculate_test_files_count( - pending_tests - ) + test_files_count = self._tests_manager.calculate_test_files_count(pending_tests) test_state = {} for api in test_files_count: @@ -97,7 +105,8 @@ def create_session( "timeout": 0, "not_run": 0, "total": test_files_count[api], - "complete": 0} + "complete": 0, + } date_created = int(time.time() * 1000) @@ -114,9 +123,8 @@ def create_session( status=PENDING, reference_tokens=reference_tokens, labels=labels, - type=type, expiration_date=expiration_date, - date_created=date_created + date_created=date_created, ) self._push_to_cache(session) @@ -130,7 +138,6 @@ def read_session(self, token): return None session = self._read_from_cache(token) if session is None or session.test_state is None: - print("loading session from file system") session = self.load_session(token) if session is not None: self._push_to_cache(session) @@ -180,7 +187,7 @@ def update_session(self, session): self._push_to_cache(session) def update_session_configuration( - self, token, tests, test_types, timeouts, reference_tokens, type + self, token, tests, test_types, timeouts, reference_tokens ): session = self.read_session(token) if session is None: @@ -202,12 +209,13 @@ def update_session_configuration( include_list=tests["include"], exclude_list=tests["exclude"], reference_tokens=reference_tokens, - test_types=test_types + test_types=test_types, ) session.pending_tests = pending_tests session.tests = tests test_files_count = self._tests_manager.calculate_test_files_count( - pending_tests) + pending_tests + ) test_state = {} for api in test_files_count: test_state[api] = { @@ -230,8 +238,6 @@ def update_session_configuration( session.timeouts = timeouts if reference_tokens is not None: session.reference_tokens = reference_tokens - if type is not None: - session.type = type self._push_to_cache(session) return session @@ -299,7 +305,7 @@ def load_session_info(self, token): return None info_data = None - with open(info_file) as file: + with open(info_file, "r") as file: info_data = file.read() parsed_info_data = json.loads(info_data) @@ -373,9 +379,7 @@ def start_session(self, token): self.update_session(session) self._event_dispatcher.dispatch_event( - token, - event_type=STATUS_EVENT, - data=session.status + token, event_type=STATUS_EVENT, data=session.status ) def pause_session(self, token): @@ -385,9 +389,7 @@ def pause_session(self, token): session.status = PAUSED self.update_session(session) self._event_dispatcher.dispatch_event( - token, - event_type=STATUS_EVENT, - data=session.status + token, event_type=STATUS_EVENT, data=session.status ) self._results_manager.persist_session(session) @@ -399,9 +401,7 @@ def stop_session(self, token): session.date_finished = int(time.time() * 1000) self.update_session(session) self._event_dispatcher.dispatch_event( - token, - event_type=STATUS_EVENT, - data=session.status + token, event_type=STATUS_EVENT, data=session.status ) def resume_session(self, token, resume_token): @@ -409,9 +409,7 @@ def resume_session(self, token, resume_token): if session.status != PENDING: return self._event_dispatcher.dispatch_event( - token, - event_type=RESUME_EVENT, - data=resume_token + token, event_type=RESUME_EVENT, data=resume_token ) self.delete_session(token) @@ -423,18 +421,18 @@ def complete_session(self, token): session.date_finished = int(time.time() * 1000) self.update_session(session) self._event_dispatcher.dispatch_event( - token, - event_type=STATUS_EVENT, - data=session.status + token, event_type=STATUS_EVENT, data=session.status ) def test_in_session(self, test, session): - return self._test_list_contains_test(test, session.pending_tests) \ - or self._test_list_contains_test(test, session.running_tests) + return self._test_list_contains_test( + test, session.pending_tests + ) or self._test_list_contains_test(test, session.running_tests) def is_test_complete(self, test, session): - return not self._test_list_contains_test(test, session.pending_tests) \ - and not self._test_list_contains_test(test, session.running_tests) + return not self._test_list_contains_test( + test, session.pending_tests + ) and not self._test_list_contains_test(test, session.running_tests) def is_test_running(self, test, session): return self._test_list_contains_test(test, session.running_tests) @@ -446,8 +444,7 @@ def _test_list_contains_test(self, test, test_list): return False def is_api_complete(self, api, session): - return api not in session.pending_tests \ - and api not in session.running_tests + return api not in session.pending_tests and api not in session.running_tests def get_test_path_with_query(self, test, session): query_string = "" @@ -461,7 +458,7 @@ def get_test_path_with_query(self, test, session): pattern = re.compile("^" + include_test) if pattern.match(test) is not None: query_string += query + "&" - return f"{test}?{query_string}" + return "{}?{}".format(test, query_string) def find_token(self, fragment): if len(fragment) < 8: diff --git a/tools/wave/testing/test_loader.py b/tools/wave/testing/test_loader.py index 8d751260d976567..d2664bb5dc587b7 100644 --- a/tools/wave/testing/test_loader.py +++ b/tools/wave/testing/test_loader.py @@ -1,5 +1,5 @@ -# mypy: allow-untyped-defs - +from __future__ import absolute_import +from __future__ import unicode_literals import os import re @@ -9,7 +9,7 @@ TEST_TYPES = [AUTOMATIC, MANUAL] -class TestLoader: +class TestLoader(object): def initialize( self, exclude_list_file_path, @@ -48,7 +48,7 @@ def load_tests(self, tests): self._tests[AUTOMATIC][api].remove(test_path) if not self._is_valid_test(test_path, - include_list=include_list): + include_regex_list=include_list): continue if api not in self._tests[MANUAL]: @@ -94,14 +94,23 @@ def _parse_api_name(self, test_path): continue return part - def _is_valid_test(self, test_path, exclude_list=None, include_list=None): + def _convert_list_to_regex(self, test_list): + regex_patterns = [] + + if test_list is not None and len(test_list) > 0: + is_valid = False + for test in test_list: + test = test.split("?")[0] + pattern = re.compile("^" + test) + regex_patterns.append(pattern) + return regex_patterns + + def _is_valid_test(self, test_path, exclude_regex_list=None, include_regex_list=None): is_valid = True - if include_list is not None and len(include_list) > 0: + if include_regex_list is not None and len(include_regex_list) > 0: is_valid = False - for include_test in include_list: - include_test = include_test.split("?")[0] - pattern = re.compile("^" + include_test) + for pattern in include_regex_list: if pattern.match(test_path) is not None: is_valid = True break @@ -109,11 +118,9 @@ def _is_valid_test(self, test_path, exclude_list=None, include_list=None): if not is_valid: return is_valid - if exclude_list is not None and len(exclude_list) > 0: + if exclude_regex_list is not None and len(exclude_regex_list) > 0: is_valid = True - for exclude_test in exclude_list: - exclude_test = exclude_test.split("?")[0] - pattern = re.compile("^" + exclude_test) + for pattern in exclude_regex_list: if pattern.match(test_path) is not None: is_valid = False break @@ -154,6 +161,9 @@ def get_tests( if reference_tokens is None: reference_tokens = [] + exclude_regex_list = self._convert_list_to_regex(exclude_list) + include_regex_list = self._convert_list_to_regex(include_list) + loaded_tests = {} reference_results = self._results_manager.read_common_passed_tests( @@ -164,16 +174,17 @@ def get_tests( continue for api in self._tests[test_type]: for test_path in self._tests[test_type][api]: - if not self._is_valid_test(test_path, exclude_list, - include_list): + if not self._is_valid_test(test_path, exclude_regex_list, + include_regex_list): continue if reference_results is not None and \ (api not in reference_results or - (api in reference_results and test_path not in reference_results[api])): + (api in reference_results and test_path not in reference_results[api])): continue if api not in loaded_tests: loaded_tests[api] = [] loaded_tests[api].append(test_path) + return loaded_tests def get_apis(self): diff --git a/tools/wave/testing/tests_manager.py b/tools/wave/testing/tests_manager.py index 8187eb4a7db4f1b..c4dd695c6f3912f 100644 --- a/tools/wave/testing/tests_manager.py +++ b/tools/wave/testing/tests_manager.py @@ -1,7 +1,9 @@ -# mypy: allow-untyped-defs - +from __future__ import division +from __future__ import absolute_import +from __future__ import unicode_literals import re from threading import Timer +import functools from .event_dispatcher import TEST_COMPLETED_EVENT @@ -9,13 +11,9 @@ from ..data.session import COMPLETED, ABORTED -class TestsManager: +class TestsManager(object): def initialize( - self, - test_loader, - sessions_manager, - results_manager, - event_dispatcher + self, test_loader, sessions_manager, results_manager, event_dispatcher ): self._test_loader = test_loader self._sessions_manager = sessions_manager @@ -23,6 +21,7 @@ def initialize( self._event_dispatcher = event_dispatcher self._timeouts = [] + self._logs = {} def next_test(self, session): if session.status == COMPLETED or session.status == ABORTED: @@ -53,10 +52,7 @@ def handler(self, token, test): self._on_test_timeout(token, test) timer = Timer(test_timeout, handler, [self, token, test]) - self._timeouts.append({ - "test": test, - "timeout": timer - }) + self._timeouts.append({"test": test, "timeout": timer}) session.pending_tests = pending_tests session.running_tests = running_tests @@ -110,8 +106,11 @@ def read_last_completed_tests(self, token, count): tests["pass"].append(result["test"]) if not passes and len(tests["fail"]) < count: tests["fail"].append(result["test"]) - if len(tests["pass"]) == count and len(tests["fail"]) == count \ - and len(tests["timeout"]) == count: + if ( + len(tests["pass"]) == count + and len(tests["fail"]) == count + and len(tests["timeout"]) == count + ): return tests return tests @@ -122,34 +121,33 @@ def _sort_tests_by_execution(self, tests): for test in tests[api]: sorted_tests.append(test) - class compare: - def __init__(self, tests_manager, test): - self.test = test - self.tests_manager = tests_manager - - def __lt__(self, test_b): - test_a = self.test - test_b = test_b.test - micro_test_list = {} - api_a = "" - for part in test_a.split("/"): - if part != "": - api_a = part - break - api_b = "" - for part in test_b.split("/"): - if part != "": - api_b = part - break - if api_a == api_b: - micro_test_list[api_a] = [test_a, test_b] - else: - micro_test_list[api_a] = [test_a] - micro_test_list[api_b] = [test_b] - next_test = self.tests_manager._get_next_test_from_list(micro_test_list) - return next_test == test_b - - sorted_tests.sort(key=lambda test: compare(self, test)) + def compare(tests_manager, test_a, test_b): + micro_test_list = {} + api_a = "" + for part in test_a.split("/"): + if part != "": + api_a = part + break + api_b = "" + for part in test_b.split("/"): + if part != "": + api_b = part + break + if api_a == api_b: + micro_test_list[api_a] = [test_a, test_b] + else: + micro_test_list[api_a] = [test_a] + micro_test_list[api_b] = [test_b] + next_test = tests_manager._get_next_test_from_list(micro_test_list) + if next_test == test_a: + return -1 + return 1 + + sorted_tests.sort( + key=functools.cmp_to_key( + lambda test_a, test_b: compare(self, test_a, test_b) + ) + ) return sorted_tests def _get_next_test_from_list(self, tests): @@ -164,7 +162,7 @@ def _get_next_test_from_list(self, tests): apis.sort(key=lambda api: api.lower()) for api in apis: - tests[api].sort(key=lambda test: test.replace("/", "").lower()) + tests[api].sort(key=lambda api: api.replace("/", "").lower()) while test is None: if len(apis) <= current_api: @@ -220,14 +218,14 @@ def skip_to(self, test_list, test): if test not in sorted_tests: return test_list index = sorted_tests.index(test) - remaining_tests = sorted_tests[index + 1:] + remaining_tests = sorted_tests[index + 1 :] remaining_tests_by_api = {} current_api = "___" for test in remaining_tests: - if not test.startswith("/" + current_api) and \ - not test.startswith(current_api): - current_api = next((p for p in test.split("/") if p != ""), - None) + if not test.startswith("/" + current_api) and not test.startswith( + current_api + ): + current_api = next((p for p in test.split("/") if p != ""), None) if current_api not in remaining_tests_by_api: remaining_tests_by_api[current_api] = [] remaining_tests_by_api[current_api].append(test) @@ -283,16 +281,15 @@ def get_test_timeout(self, test, session): return test_timeout def _on_test_timeout(self, token, test): + logs = [] + if token in self._logs: + logs = self._logs[token] data = { "test": test, "status": "TIMEOUT", "message": None, - "subtests": [ - { - "status": "TIMEOUT", - "xstatus": "SERVERTIMEOUT" - } - ] + "subtests": [{"status": "TIMEOUT", "xstatus": "SERVERTIMEOUT"}], + "logs": logs, } self._results_manager.create_result(token, data) @@ -310,23 +307,13 @@ def complete_test(self, test, session): timeout["timeout"].cancel() self._timeouts.remove(timeout) - self.update_tests( - running_tests=running_tests, - session=session - ) + self.update_tests(running_tests=running_tests, session=session) self._event_dispatcher.dispatch_event( - dispatcher_token=session.token, - event_type=TEST_COMPLETED_EVENT, - data=test + dispatcher_token=session.token, event_type=TEST_COMPLETED_EVENT, data=test ) - def update_tests( - self, - pending_tests=None, - running_tests=None, - session=None - ): + def update_tests(self, pending_tests=None, running_tests=None, session=None): if pending_tests is not None: session.pending_tests = pending_tests @@ -364,7 +351,7 @@ def load_tests(self, session): session.test_types, include_list=session.tests["include"], exclude_list=session.tests["exclude"], - reference_tokens=session.reference_tokens + reference_tokens=session.reference_tokens, ) last_completed_test = session.last_completed_test @@ -372,3 +359,13 @@ def load_tests(self, session): pending_tests = self.skip_to(pending_tests, last_completed_test) return pending_tests + + def add_logs(self, token, logs): + if token not in self._logs: + self._logs[token] = [] + self._logs[token] = self._logs[token] + logs + + def get_logs(self, token): + if token not in self._logs: + return [] + return self._logs[token] diff --git a/tools/wave/testing/wpt_report.py b/tools/wave/testing/wpt_report.py index b84119ed8526665..a7b8351cc1e7e85 100644 --- a/tools/wave/testing/wpt_report.py +++ b/tools/wave/testing/wpt_report.py @@ -1,5 +1,4 @@ -# mypy: allow-untyped-defs - +from __future__ import unicode_literals import subprocess import os import ntpath @@ -12,7 +11,10 @@ def generate_report( output_html_directory_path=None, spec_name=None, is_multi=None, - reference_dir=None): + reference_dir=None, + tests_base_url=None): + if not is_wptreport_installed(): + return if is_multi is None: is_multi = False try: @@ -25,14 +27,15 @@ def generate_report( "--failures", "true", "--tokenFileName", "true" if is_multi else "false", "--pass", "100", - "--ref", reference_dir if reference_dir is not None else ""] - whole_command = "" - for command_part in command: - whole_command += command_part + " " + "--ref", reference_dir if reference_dir is not None else "", + "--testsBaseUrl", tests_base_url + ] subprocess.call(command, shell=False) except subprocess.CalledProcessError as e: info = sys.exc_info() raise Exception("Failed to execute wptreport: " + str(info[0].__name__) + ": " + e.output) + except FileNotFoundError as e: + raise Exception("Failed to execute wptreport: " + " ".join(command)) def generate_multi_report( @@ -40,6 +43,8 @@ def generate_multi_report( spec_name=None, result_json_files=None, reference_dir=None): + if not is_wptreport_installed(): + return for file in result_json_files: if not os.path.isfile(file["path"]): continue @@ -55,3 +60,10 @@ def generate_multi_report( spec_name=spec_name, is_multi=True, reference_dir=reference_dir) + +def is_wptreport_installed(): + try: + subprocess.check_output(["wptreport", "--help"]) + return True + except Exception: + return False \ No newline at end of file diff --git a/tools/wave/tests/test_api.py b/tools/wave/tests/test_api.py new file mode 100644 index 000000000000000..1391febd3687258 --- /dev/null +++ b/tools/wave/tests/test_api.py @@ -0,0 +1,164 @@ +import pytest +import requests +import json +import copy + +def test_import_results(): + # Arrange + token = create_session() + print(token) + tests = read_available_tests() + apis = list(tests.keys()) + api = apis[0] + test = tests[api][0] + set_session_tests(token, [test]) + print(api) + print(test) + start_session(token) + next_test = read_next_test(token) + create_positive_result(token, test) + next_test = read_next_test(token) + + # Act + url = "/".join([get_url(), "api/results", token, api, "json"]) + r = requests.get(url) + old_results = copy.deepcopy(r.json()) + + new_results = copy.deepcopy(old_results) + new_results["results"][0]["subtests"].append({"name": "Subtest ab", "status": "FAIL", "message": None}) + url = "/".join([get_url(), "api/results", token, api, "json"]) + r = requests.post(url, data=json.dumps(new_results)) + status_code = r.status_code + + url = "/".join([get_url(), "api/results", token, api, "json"]) + r = requests.get(url) + updated_results = r.json() + + # Assert + assert status_code == 200 + assert updated_results == new_results + assert updated_results != old_results + + # CleanUp + delete_session(token) + +def test_last_completed_tests(): + # Arrange + token = create_session() + print(token) + tests = read_available_tests() + apis = list(tests.keys()) + api = apis[0] + test = tests[api][0] + set_session_tests(token, [test]) + print(api) + print(test) + start_session(token) + next_test = read_next_test(token) + create_timed_out_result(token, test) + next_test = read_next_test(token) + + # Act + url = "/".join([get_url(), "api/tests/", token, "/last_completed"]) + r = requests.get(url) + status_code = r.status_code + + url = "/".join([get_url(), "api/results", token, api, "json"]) + r = requests.get(url) + updated_results = r.json() + + # Assert + assert status_code == 200 + + # CleanUp + delete_session(token) + +def create_session(): + url = "/".join([get_url(), "api/sessions"]) + r = requests.post(url) + response_payload = r.json() + token = response_payload["token"] + return token + +def read_session(token): + url = "/".join([get_url(), "api/sessions", token]) + r = requests.get(url) + session = r.json() + return session + +def read_session_status(token): + url = "/".join([get_url(), "api/sessions", token, "status"]) + r = requests.get(url) + status = r.json() + return status + +def read_available_tests(): + url = "/".join([get_url(), "api/tests"]) + r = requests.get(url) + tests = r.json() + return tests + +def set_session_tests(token, tests): + url = "/".join([get_url(), "api/sessions", token]) + config = { + "tests": { + "include": tests + } + } + + r = requests.put(url, data=json.dumps(config)) + +def read_next_test(token): + url = "/".join([get_url(), "api/tests", token, "next"]) + r = requests.get(url) + next_test = r.json()["next_test"] + return next_test + + +def start_session(token): + url = "/".join([get_url(), "api/sessions", token, "start"]) + r = requests.post(url) + +def create_result(token, test, result): + url = "/".join([get_url(), "api/results", token]) + r = requests.post(url, data=json.dumps(result)) + print(r.status_code) + print(result) + +def create_positive_result(token, test): + result = { + "test": test, + "status": "OK", + "message": None, + "subtests": [ + {"name": "Subtest xy", "status": "PASS", "message": None} + ] + } + + create_result(token, test, result) + +def create_timed_out_result(token, test): + result = { + "test": test, + "status": "TIMEOUT", + "message": None, + "subtests": [] + } + + create_result(token, test, result) + +def read_session_tests(token): + url = "/".join([get_url(), "api/tests", token]) + r = requests.get(url) + tests = r.json() + return tests + +def delete_session(token): + url = "/".join([get_url(), "api/sessions", token]) + r = requests.delete(url) + + +def get_url(): + host = "127.0.0.1:8000" + base_url = "_wave" + return "/".join(["http:/", host, base_url]) \ No newline at end of file diff --git a/tools/wave/tests/test_wave.py b/tools/wave/tests/test_wave.py index a7d87a38e1b01f9..cfb84df6564c41a 100644 --- a/tools/wave/tests/test_wave.py +++ b/tools/wave/tests/test_wave.py @@ -1,13 +1,14 @@ -# mypy: allow-untyped-defs - import errno import os import socket import subprocess import time -from urllib.request import urlopen -from urllib.error import URLError +try: + from urllib.request import urlopen + from urllib.error import URLError +except ImportError: + from urllib2 import urlopen, URLError from tools.wpt import wpt @@ -15,7 +16,7 @@ def is_port_8080_in_use(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.bind(("127.0.0.1", 8080)) - except OSError as e: + except socket.error as e: if e.errno == errno.EADDRINUSE: return True else: @@ -52,4 +53,3 @@ def test_serve(): break finally: os.killpg(p.pid, 15) - p.wait(10) diff --git a/tools/wave/tox.ini b/tools/wave/tox.ini index 88c76096f459839..b868686452f88a1 100644 --- a/tools/wave/tox.ini +++ b/tools/wave/tox.ini @@ -1,14 +1,13 @@ [tox] -envlist = py38,py39,py310,py311,py312 +envlist = py36,py37,py38,py39 skipsdist=True skip_missing_interpreters = False [testenv] deps = -r{toxinidir}/../requirements_pytest.txt - -r{toxinidir}/requirements.txt -r{toxinidir}/../wptrunner/requirements.txt - -r{toxinidir}/../wptrunner/requirements_chromium.txt + -r{toxinidir}/../wptrunner/requirements_chrome.txt -r{toxinidir}/../wptrunner/requirements_firefox.txt commands = diff --git a/tools/wave/utils/deserializer.py b/tools/wave/utils/deserializer.py index 28d1054f6ba77aa..ddf77a99d58da3d 100644 --- a/tools/wave/utils/deserializer.py +++ b/tools/wave/utils/deserializer.py @@ -1,5 +1,5 @@ -# mypy: allow-untyped-defs - +from __future__ import absolute_import +from __future__ import unicode_literals from ..data.session import Session, UNKNOWN from datetime import datetime import dateutil.parser @@ -76,9 +76,6 @@ def deserialize_session(session_dict): if "expiration_date" in session_dict: expiration_date = session_dict["expiration_date"] expiration_date = iso_to_millis(expiration_date) - type = None - if "type" in session_dict: - type = session_dict["type"] malfunctioning_tests = [] if "malfunctioning_tests" in session_dict: malfunctioning_tests = session_dict["malfunctioning_tests"] @@ -102,7 +99,6 @@ def deserialize_session(session_dict): reference_tokens=reference_tokens, browser=browser, expiration_date=expiration_date, - type=type, malfunctioning_tests=malfunctioning_tests ) diff --git a/tools/wave/utils/serializer.py b/tools/wave/utils/serializer.py index 995365081db0ce2..72fb55d3293ddb0 100644 --- a/tools/wave/utils/serializer.py +++ b/tools/wave/utils/serializer.py @@ -1,5 +1,4 @@ -# mypy: allow-untyped-defs - +from __future__ import unicode_literals from datetime import datetime @@ -23,7 +22,6 @@ def serialize_session(session): "is_public": session.is_public, "reference_tokens": session.reference_tokens, "expiration_date": millis_to_iso(session.expiration_date), - "type": session.type, "malfunctioning_tests": session.malfunctioning_tests } diff --git a/tools/wave/utils/user_agent_parser.py b/tools/wave/utils/user_agent_parser.py index d1da820b530ed5e..7c0727e1f3cd844 100644 --- a/tools/wave/utils/user_agent_parser.py +++ b/tools/wave/utils/user_agent_parser.py @@ -1,5 +1,5 @@ -# mypy: allow-untyped-defs - +from __future__ import absolute_import +from __future__ import unicode_literals from ua_parser import user_agent_parser diff --git a/tools/wave/wave-cli.py b/tools/wave/wave-cli.py new file mode 100644 index 000000000000000..f920515c66153df --- /dev/null +++ b/tools/wave/wave-cli.py @@ -0,0 +1,113 @@ +import sys +import os +import urllib2 +import zipfile + +START = "start" +DOWNLOAD_REFERENCE_BROWSERS = "download-reference-results" + +REFERENCE_BROWSERS = { + "chrome": { + "name": "Chromium 73.0.3640.0", + "url": "https://s3.us-east-2.amazonaws.com/wave-browser-snapshots/wave-reference-browser-results/WMAS+2018/Chromium73-a50c6db0-6a94-11e9-8d1b-e23fc4555885.zip" + }, + "edge": { + "name": "Edge 44.17763", + "url": "https://s3.us-east-2.amazonaws.com/wave-browser-snapshots/wave-reference-browser-results/WMAS+2018/Edge44-b2924d20-6a93-11e9-98b4-a11fb92a6d1c.zip" + }, + "firefox": { + "name": "Firefox 64.0", + "url": + "https://s3.us-east-2.amazonaws.com/wave-browser-snapshots/wave-reference-browser-results/WMAS+2018/Firefox64-bb7aafa0-6a92-11e9-8ec2-04f58dad2e4f.zip" + }, + "webkit": { + "name": "WebKit r239158", + "url": + "https://s3.us-east-2.amazonaws.com/wave-browser-snapshots/wave-reference-browser-results/WMAS+2018/WebKitr239158-caf823e0-6a92-11e9-b732-3188d0065ebc.zip" + } +} + + +def main(): + parameters = get_run_parameters() + configuration_file_path = None + if ("configuration_file_path" in parameters): + configuration_file_path = parameters["configuration_file_path"] + + if (parameters["operation"] == DOWNLOAD_REFERENCE_BROWSERS): + download_reference_browsers() + + +def get_run_parameters(): + arguments = sys.argv + parameters = {} + + operation = arguments[1].lower() + + if operation != START and operation != DOWNLOAD_REFERENCE_BROWSERS: + raise Exception("Unknown operation {}".format(operation)) + + parameters["operation"] = operation + + iterator = iter(arguments) + next(iterator) + next(iterator) + for argument in iterator: + if (argument.lower() == "--config"): + path = next(iterator) + if not path.startswith("/"): + path = os.path.join(os.getcwd(), path) + parameters["configuration_file_path"] = path + continue + + raise Exception("Unknown option {}".format(argument)) + + if "configuration_file_path" not in parameters: + parameters["configuration_file_path"] = os.path.join( + os.getcwd(), "config.json") + + return parameters + + +def download_file(url, file_path): + response = urllib2.urlopen(url) + data = response.read() + file = open(file_path, "wb") + file.write(data) + file.close() + + +def printt(text): + sys.stdout.write(text) + sys.stdout.flush() + + +def download_reference_browsers(): + result_directory = os.path.abspath("./results") + + if not os.path.isdir(result_directory): + os.mkdir(result_directory) + + for id in REFERENCE_BROWSERS: + browser = REFERENCE_BROWSERS[id] + browser["zip"] = browser["url"].split("/")[-1] + printt("Downloading {} results ...".format(browser["name"])) + download_file(browser["url"], os.path.join( + result_directory, browser["zip"])) + print(" done.") + + for id in REFERENCE_BROWSERS: + browser = REFERENCE_BROWSERS[id] + printt("Extracting {} results ...".format(browser["name"])) + zip_file = zipfile.ZipFile(os.path.join( + result_directory, browser["zip"])) + zip_file.extractall(result_directory) + print(" done.") + + print("Cleaning ...") + for id in REFERENCE_BROWSERS: + browser = REFERENCE_BROWSERS[id] + os.remove(os.path.join( + result_directory, browser["zip"])) + +main() diff --git a/tools/wave/wave_server.py b/tools/wave/wave_server.py index 1439c0b7cae39c1..8f1998b7af7d676 100644 --- a/tools/wave/wave_server.py +++ b/tools/wave/wave_server.py @@ -1,5 +1,4 @@ -# mypy: allow-untyped-defs - +from __future__ import unicode_literals import os import logging @@ -23,7 +22,7 @@ VERSION_STRING = "v3.3.0" -class WaveServer: +class WaveServer(object): def initialize(self, tests, configuration_file_path=None, diff --git a/tools/wave/webgl/prepare-tests.js b/tools/wave/webgl/prepare-tests.js new file mode 100644 index 000000000000000..9ac8012ad70d674 --- /dev/null +++ b/tools/wave/webgl/prepare-tests.js @@ -0,0 +1,82 @@ +const fs = require("fs"); +const fse = require('fs-extra'); +const path = require("path"); + + +const makeDirectory = async directoryPath => { + return new Promise((resolve, reject) => { + fs.mkdir(directoryPath, error => { + if (error) { + reject(error); + } + resolve(); + }); + }); +}; + +const readStats = async path => { + return new Promise((resolve, reject) => { + fs.stat(path, (error, stats) => { + if (error) { + resolve(null); + } + resolve(stats); + }); + }); +}; + + +const addHarnessToTestsHeader = async(testsPath,testsListPath) =>{ + var files = fs.readFileSync(testsListPath).toString().split("\n"); + var numberOfTestFiles = 0; + for(var i=0; i", ' \n \n \n'); + var file = fs.openSync(filename,'r+'); + fs.writeSync(file, content); + numberOfTestFiles += 1; + } + } + } + return numberOfTestFiles; +} + + +(async () => { + + const testDir = process.argv[2] || DEFAULT_TEST_DIR; + + // Files that will be overwritten in the original webgl test suite + const PRE_TEST_NAME = "js-test-pre.js"; + const UNIT_TEST_NAME = "unit.js"; + + const RESOURCES = path.join( __dirname ,"resources"); + const DEFAULT_TEST_DIR = "/webgl/"; + const DEFAULT_OUTPUT_DIR = "."; + const SUB_DIR_NAME = "webgl"; + + const testsPath = path.join(testDir, "conformance-suites"); + const v1_0_3_harnessDir = path.join(testsPath, "1.0.3"); + const preTestsPath = path.join(RESOURCES, PRE_TEST_NAME); + const unitTestPath = path.join(RESOURCES, UNIT_TEST_NAME); + let outputPath = process.argv[3] || DEFAULT_OUTPUT_DIR; + outputPath = path.join(outputPath, SUB_DIR_NAME); + const testsOutputPath = path.join(outputPath, "conformance-suite"); + const resourcesPath = path.join(testsOutputPath, "resources"); + const presTestDestinationPath = path.join(resourcesPath, "js-test-pre.js"); + const unitTestDestinationputPath = path.join(testsOutputPath, "conformance", "more", "unit.js"); + + const testsListPath = path.join(RESOURCES, "list_all_tests") + + if (!(await readStats(SUB_DIR_NAME))) await makeDirectory(SUB_DIR_NAME); + + await fse.copy(v1_0_3_harnessDir, testsOutputPath); + await fse.copy(preTestsPath, presTestDestinationPath); + await fse.copy(unitTestPath, unitTestDestinationputPath); + const numberOfTestFiles = await addHarnessToTestsHeader(testsOutputPath,testsListPath); + console.log(`Total of ${numberOfTestFiles} webGl tests integrated`, testsListPath); +})(); diff --git a/tools/wave/webgl/resources/js-test-pre.js b/tools/wave/webgl/resources/js-test-pre.js new file mode 100644 index 000000000000000..47ef89f737aa8c4 --- /dev/null +++ b/tools/wave/webgl/resources/js-test-pre.js @@ -0,0 +1,531 @@ +/* +** 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. +*/ + +// Array containing the runned subtests. +// All the runned tests will be set to done once +// notifyFinishedToHarness is called. +var subTests = []; + +(function() { + var testHarnessInitialized = false; + + setup({explicit_timeout: true}); + + var initNonKhronosFramework = function() { + if (testHarnessInitialized) { + return; + } + testHarnessInitialized = true; + + /* -- plaform specific code -- */ + + // WebKit Specific code. Add your code here. + if (window.testRunner && !window.layoutTestController) { + window.layoutTestController = window.testRunner; + } + + if (window.layoutTestController) { + layoutTestController.overridePreference("WebKitWebGLEnabled", "1"); + layoutTestController.dumpAsText(); + layoutTestController.waitUntilDone(); + } + if (window.internals) { + // The WebKit testing system compares console output. + // Because the output of the WebGL Tests is GPU dependent + // we turn off console messages. + window.console.log = function() { }; + window.console.error = function() { }; + window.internals.settings.setWebGLErrorsToConsoleEnabled(false); + } + + /* -- end platform specific code --*/ + } + + this.initTestingHarness = function() { + initNonKhronosFramework(); + } +}()); + +function nonKhronosFrameworkNotifyDone() { + // WebKit Specific code. Add your code here. + if (window.layoutTestController) { + layoutTestController.notifyDone(); + } +} + +function reportTestResultsToHarness(success, msg) { + var testTitle = "["+ subTests.length + "] " + msg; + var test = async_test(testTitle); + test.step(function() { + assert_true(success, testTitle + " should be true"); + }); + subTests.push(test); + if (window.parent.webglTestHarness) { + window.parent.webglTestHarness.reportResults(window.location.pathname, success, msg); + } +} + +function notifyFinishedToHarness() { + for(var i=0; i < subTests.length ; i++){ + subTests[i].done(); + } + if (window.parent.webglTestHarness) { + window.parent.webglTestHarness.notifyFinished(window.location.pathname); + } + if (window.nonKhronosFrameworkNotifyDone) { + window.nonKhronosFrameworkNotifyDone(); + } +} + +function _logToConsole(msg) +{ + if (window.console) + window.console.log(msg); +} + +var _jsTestPreVerboseLogging = false; + +function enableJSTestPreVerboseLogging() +{ + _jsTestPreVerboseLogging = true; +} + +function description(msg) +{ + initTestingHarness(); + if (msg === undefined) { + msg = document.title; + } + // For MSIE 6 compatibility + var span = document.createElement("span"); + span.innerHTML = '

' + msg + '

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".

'; + var description = document.getElementById("description"); + if (description.firstChild) + description.replaceChild(span, description.firstChild); + else + description.appendChild(span); + if (_jsTestPreVerboseLogging) { + _logToConsole(msg); + } +} + +function _addSpan(contents) +{ + var span = document.createElement("span"); + document.getElementById("console").appendChild(span); // insert it first so XHTML knows the namespace + span.innerHTML = contents + '
'; +} + +function debug(msg) +{ + _addSpan(msg); + if (_jsTestPreVerboseLogging) { + _logToConsole(msg); + } +} + +function escapeHTML(text) +{ + return text.replace(/&/g, "&").replace(/PASS ' + escapeHTML(msg) + ''); + if (_jsTestPreVerboseLogging) { + _logToConsole('PASS ' + msg); + } +} + +function testFailed(msg) +{ + reportTestResultsToHarness(false, msg); + _addSpan('FAIL ' + escapeHTML(msg) + ''); + _logToConsole('FAIL ' + msg); +} + +function areArraysEqual(_a, _b) +{ + try { + if (_a.length !== _b.length) + return false; + for (var i = 0; i < _a.length; i++) + if (_a[i] !== _b[i]) + return false; + } catch (ex) { + return false; + } + return true; +} + +function isMinusZero(n) +{ + // the only way to tell 0 from -0 in JS is the fact that 1/-0 is + // -Infinity instead of Infinity + return n === 0 && 1/n < 0; +} + +function isResultCorrect(_actual, _expected) +{ + if (_expected === 0) + return _actual === _expected && (1/_actual) === (1/_expected); + if (_actual === _expected) + return true; + if (typeof(_expected) == "number" && isNaN(_expected)) + return typeof(_actual) == "number" && isNaN(_actual); + if (Object.prototype.toString.call(_expected) == Object.prototype.toString.call([])) + return areArraysEqual(_actual, _expected); + return false; +} + +function stringify(v) +{ + if (v === 0 && 1/v < 0) + return "-0"; + else return "" + v; +} + +function evalAndLog(_a) +{ + if (typeof _a != "string") + debug("WARN: tryAndLog() expects a string argument"); + + // Log first in case things go horribly wrong or this causes a sync event. + debug(_a); + + var _av; + try { + _av = eval(_a); + } catch (e) { + testFailed(_a + " threw exception " + e); + } + return _av; +} + +function shouldBe(_a, _b, quiet) +{ + if (typeof _a != "string" || typeof _b != "string") + debug("WARN: shouldBe() expects string arguments"); + var exception; + var _av; + try { + _av = eval(_a); + } catch (e) { + exception = e; + } + var _bv = eval(_b); + + if (exception) + testFailed(_a + " should be " + _bv + ". Threw exception " + exception); + else if (isResultCorrect(_av, _bv)) { + if (!quiet) { + testPassed(_a + " is " + _b); + } + } else if (typeof(_av) == typeof(_bv)) + testFailed(_a + " should be " + _bv + ". Was " + stringify(_av) + "."); + else + testFailed(_a + " should be " + _bv + " (of type " + typeof _bv + "). Was " + _av + " (of type " + typeof _av + ")."); +} + +function shouldNotBe(_a, _b, quiet) +{ + if (typeof _a != "string" || typeof _b != "string") + debug("WARN: shouldNotBe() expects string arguments"); + var exception; + var _av; + try { + _av = eval(_a); + } catch (e) { + exception = e; + } + var _bv = eval(_b); + + if (exception) + testFailed(_a + " should not be " + _bv + ". Threw exception " + exception); + else if (!isResultCorrect(_av, _bv)) { + if (!quiet) { + testPassed(_a + " is not " + _b); + } + } else + testFailed(_a + " should not be " + _bv + "."); +} + +function shouldBeTrue(_a) { shouldBe(_a, "true"); } +function shouldBeFalse(_a) { shouldBe(_a, "false"); } +function shouldBeNaN(_a) { shouldBe(_a, "NaN"); } +function shouldBeNull(_a) { shouldBe(_a, "null"); } + +function shouldBeEqualToString(a, b) +{ + var unevaledString = '"' + b.replace(/"/g, "\"") + '"'; + shouldBe(a, unevaledString); +} + +function shouldEvaluateTo(actual, expected) { + // A general-purpose comparator. 'actual' should be a string to be + // evaluated, as for shouldBe(). 'expected' may be any type and will be + // used without being eval'ed. + if (expected == null) { + // Do this before the object test, since null is of type 'object'. + shouldBeNull(actual); + } else if (typeof expected == "undefined") { + shouldBeUndefined(actual); + } else if (typeof expected == "function") { + // All this fuss is to avoid the string-arg warning from shouldBe(). + try { + actualValue = eval(actual); + } catch (e) { + testFailed("Evaluating " + actual + ": Threw exception " + e); + return; + } + shouldBe("'" + actualValue.toString().replace(/\n/g, "") + "'", + "'" + expected.toString().replace(/\n/g, "") + "'"); + } else if (typeof expected == "object") { + shouldBeTrue(actual + " == '" + expected + "'"); + } else if (typeof expected == "string") { + shouldBe(actual, expected); + } else if (typeof expected == "boolean") { + shouldBe("typeof " + actual, "'boolean'"); + if (expected) + shouldBeTrue(actual); + else + shouldBeFalse(actual); + } else if (typeof expected == "number") { + shouldBe(actual, stringify(expected)); + } else { + debug(expected + " is unknown type " + typeof expected); + shouldBeTrue(actual, "'" +expected.toString() + "'"); + } +} + +function shouldBeNonZero(_a) +{ + var exception; + var _av; + try { + _av = eval(_a); + } catch (e) { + exception = e; + } + + if (exception) + testFailed(_a + " should be non-zero. Threw exception " + exception); + else if (_av != 0) + testPassed(_a + " is non-zero."); + else + testFailed(_a + " should be non-zero. Was " + _av); +} + +function shouldBeNonNull(_a) +{ + var exception; + var _av; + try { + _av = eval(_a); + } catch (e) { + exception = e; + } + + if (exception) + testFailed(_a + " should be non-null. Threw exception " + exception); + else if (_av != null) + testPassed(_a + " is non-null."); + else + testFailed(_a + " should be non-null. Was " + _av); +} + +function shouldBeUndefined(_a) +{ + var exception; + var _av; + try { + _av = eval(_a); + } catch (e) { + exception = e; + } + + if (exception) + testFailed(_a + " should be undefined. Threw exception " + exception); + else if (typeof _av == "undefined") + testPassed(_a + " is undefined."); + else + testFailed(_a + " should be undefined. Was " + _av); +} + +function shouldBeDefined(_a) +{ + var exception; + var _av; + try { + _av = eval(_a); + } catch (e) { + exception = e; + } + + if (exception) + testFailed(_a + " should be defined. Threw exception " + exception); + else if (_av !== undefined) + testPassed(_a + " is defined."); + else + testFailed(_a + " should be defined. Was " + _av); +} + +function shouldBeGreaterThanOrEqual(_a, _b) { + if (typeof _a != "string" || typeof _b != "string") + debug("WARN: shouldBeGreaterThanOrEqual expects string arguments"); + + var exception; + var _av; + try { + _av = eval(_a); + } catch (e) { + exception = e; + } + var _bv = eval(_b); + + if (exception) + testFailed(_a + " should be >= " + _b + ". Threw exception " + exception); + else if (typeof _av == "undefined" || _av < _bv) + testFailed(_a + " should be >= " + _b + ". Was " + _av + " (of type " + typeof _av + ")."); + else + testPassed(_a + " is >= " + _b); +} + +function expectTrue(v, msg) { + if (v) { + testPassed(msg); + } else { + testFailed(msg); + } +} + +function shouldThrow(_a, _e) +{ + var exception; + var _av; + try { + _av = eval(_a); + } catch (e) { + exception = e; + } + + var _ev; + if (_e) + _ev = eval(_e); + + if (exception) { + if (typeof _e == "undefined" || exception == _ev) + testPassed(_a + " threw exception " + exception + "."); + else + testFailed(_a + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Threw exception " + exception + "."); + } else if (typeof _av == "undefined") + testFailed(_a + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Was undefined."); + else + testFailed(_a + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Was " + _av + "."); +} + +function shouldBeType(_a, _type) { + var exception; + var _av; + try { + _av = eval(_a); + } catch (e) { + exception = e; + } + + var _typev = eval(_type); + + if(_typev === Number){ + if(_av instanceof Number){ + testPassed(_a + " is an instance of Number"); + } + else if(typeof(_av) === 'number'){ + testPassed(_a + " is an instance of Number"); + } + else{ + testFailed(_a + " is not an instance of Number"); + } + } + else if (_av instanceof _typev) { + testPassed(_a + " is an instance of " + _type); + } else { + testFailed(_a + " is not an instance of " + _type); + } +} + +function assertMsg(assertion, msg) { + if (assertion) { + testPassed(msg); + } else { + testFailed(msg); + } +} + +function gc() { + if (window.GCController) { + window.GCController.collect(); + return; + } + + if (window.opera && window.opera.collect) { + window.opera.collect(); + return; + } + + try { + window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils) + .garbageCollect(); + return; + } catch(e) {} + + function gcRec(n) { + if (n < 1) + return {}; + var temp = {i: "ab" + i + (i / 100000)}; + temp += "foo"; + gcRec(n-1); + } + for (var i = 0; i < 1000; i++) + gcRec(10); +} + +function finishTest() { + successfullyParsed = true; + var epilogue = document.createElement("script"); + var basePath = ""; + var expectedBase = "js-test-pre.js"; + var scripts = document.getElementsByTagName('script'); + for (var script, i = 0; script = scripts[i]; i++) { + var src = script.src; + var l = src.length; + if (src.substr(l - expectedBase.length) == expectedBase) { + basePath = src.substr(0, l - expectedBase.length); + break; + } + } + epilogue.src = basePath + "js-test-post.js"; + document.body.appendChild(epilogue); +} + diff --git a/tools/wave/webgl/resources/list_all_tests b/tools/wave/webgl/resources/list_all_tests new file mode 100644 index 000000000000000..1bfc60bd9d81f63 --- /dev/null +++ b/tools/wave/webgl/resources/list_all_tests @@ -0,0 +1,677 @@ +conformance/attribs/gl-bindAttribLocation-aliasing.html +conformance/attribs/gl-bindAttribLocation-matrix.html +conformance/attribs/gl-disabled-vertex-attrib.html +conformance/attribs/gl-enable-vertex-attrib.html +conformance/attribs/gl-matrix-attributes.html +conformance/attribs/gl-vertex-attrib.html +conformance/attribs/gl-vertexattribpointer.html +conformance/attribs/gl-vertexattribpointer-offsets.html +conformance/attribs/gl-vertex-attrib-render.html +conformance/attribs/gl-vertex-attrib-zero-issues.html +conformance/buffers/buffer-bind-test.html +conformance/buffers/buffer-data-array-buffer.html +conformance/buffers/buffer-data-array-buffer-delete.html +conformance/buffers/element-array-buffer-delete-recreate.html +conformance/buffers/index-validation-copies-indices.html +conformance/buffers/index-validation-crash-with-buffer-sub-data.html +conformance/buffers/index-validation-large-buffer.html +conformance/buffers/index-validation-verifies-too-many-indices.html +conformance/buffers/index-validation-with-resized-buffer.html +conformance/buffers/index-validation.html +conformance/canvas/buffer-offscreen-test.html +conformance/canvas/buffer-preserve-test.html +conformance/canvas/canvas-test.html +conformance/canvas/canvas-zero-size.html +conformance/canvas/drawingbuffer-static-canvas-test.html +conformance/canvas/drawingbuffer-hd-dpi-test.html +conformance/canvas/drawingbuffer-test.html +conformance/canvas/draw-webgl-to-canvas-test.html +conformance/canvas/draw-static-webgl-to-multiple-canvas-test.html +conformance/canvas/framebuffer-bindings-unaffected-on-resize.html +conformance/canvas/rapid-resizing.html +conformance/canvas/texture-bindings-unaffected-on-resize.html +conformance/canvas/to-data-url-test.html +conformance/canvas/viewport-unchanged-upon-resize.html +conformance/context/constants-and-properties.html +conformance/context/context-attribute-preserve-drawing-buffer.html +conformance/context/context-attributes-alpha-depth-stencil-antialias.html +conformance/context/context-creation-and-destruction.html +conformance/context/context-creation.html +conformance/context/context-eviction-with-garbage-collection.html +conformance/context/context-hidden-alpha.html +conformance/context/context-release-upon-reload.html +conformance/context/context-release-with-workers.html +conformance/context/context-lost-restored.html +conformance/context/context-lost.html +conformance/context/context-type-test.html +conformance/context/incorrect-context-object-behaviour.html +conformance/context/methods.html +conformance/context/premultiplyalpha-test.html +conformance/context/resource-sharing-test.html +conformance/extensions/angle-instanced-arrays.html +conformance/extensions/angle-instanced-arrays-out-of-bounds.html +conformance/extensions/ext-blend-minmax.html +conformance/extensions/ext-frag-depth.html +conformance/extensions/ext-shader-texture-lod.html +conformance/extensions/ext-sRGB.html +conformance/extensions/ext-texture-filter-anisotropic.html +conformance/extensions/get-extension.html +conformance/extensions/oes-standard-derivatives.html +conformance/extensions/oes-texture-float-with-canvas.html +conformance/extensions/oes-texture-float-with-image-data.html +conformance/extensions/oes-texture-float-with-image.html +conformance/extensions/oes-texture-float-with-video.html +conformance/extensions/oes-texture-float.html +conformance/extensions/oes-vertex-array-object.html +conformance/extensions/oes-vertex-array-object-bufferData.html +conformance/extensions/oes-texture-half-float.html +conformance/extensions/oes-texture-float-linear.html +conformance/extensions/oes-texture-half-float-linear.html +conformance/extensions/oes-texture-half-float-with-canvas.html +conformance/extensions/oes-texture-half-float-with-image-data.html +conformance/extensions/oes-texture-half-float-with-image.html +conformance/extensions/oes-texture-half-float-with-video.html +conformance/extensions/oes-element-index-uint.html +conformance/extensions/webgl-debug-renderer-info.html +conformance/extensions/webgl-debug-shaders.html +conformance/extensions/webgl-compressed-texture-atc.html +conformance/extensions/webgl-compressed-texture-pvrtc.html +conformance/extensions/webgl-compressed-texture-s3tc.html +conformance/extensions/webgl-compressed-texture-size-limit.html +conformance/extensions/webgl-depth-texture.html +conformance/extensions/webgl-draw-buffers.html +conformance/extensions/webgl-shared-resources.html +conformance/glsl/bugs/angle-d3d11-compiler-error.html +conformance/glsl/bugs/angle-dx-variable-bug.html +conformance/glsl/bugs/array-of-struct-with-int-first-position.html +conformance/glsl/bugs/compare-loop-index-to-uniform.html +conformance/glsl/bugs/complex-glsl-does-not-crash.html +conformance/glsl/bugs/conditional-discard-optimization.html +conformance/glsl/bugs/constant-precision-qualifier.html +conformance/glsl/bugs/floored-division-accuracy.html +conformance/glsl/bugs/fragcoord-linking-bug.html +conformance/glsl/bugs/long-expressions-should-not-crash.html +conformance/glsl/bugs/modulo-arithmetic-accuracy.html +conformance/glsl/bugs/multiplication-assignment.html +conformance/glsl/bugs/nested-functions-should-not-crash.html +conformance/glsl/bugs/sampler-array-using-loop-index.html +conformance/glsl/bugs/temp-expressions-should-not-crash.html +conformance/glsl/bugs/uniforms-should-not-lose-values.html +conformance/glsl/bugs/conditional-discard-in-loop.html +conformance/glsl/bugs/essl3-shaders-with-webgl1.html +conformance/glsl/constructors/glsl-construct-vec2.html +conformance/glsl/constructors/glsl-construct-vec3.html +conformance/glsl/constructors/glsl-construct-vec4.html +conformance/glsl/constructors/glsl-construct-ivec2.html +conformance/glsl/constructors/glsl-construct-ivec3.html +conformance/glsl/constructors/glsl-construct-ivec4.html +conformance/glsl/constructors/glsl-construct-bvec2.html +conformance/glsl/constructors/glsl-construct-bvec3.html +conformance/glsl/constructors/glsl-construct-bvec4.html +conformance/glsl/constructors/glsl-construct-mat2.html +conformance/glsl/constructors/glsl-construct-mat3.html +conformance/glsl/constructors/glsl-construct-mat4.html +conformance/glsl/constructors/glsl-construct-vec-mat-corner-cases.html +conformance/glsl/constructors/glsl-construct-vec-mat-index.html +conformance/glsl/functions/glsl-function.html +conformance/glsl/functions/glsl-function-abs.html +conformance/glsl/functions/glsl-function-acos.html +conformance/glsl/functions/glsl-function-asin.html +conformance/glsl/functions/glsl-function-atan.html +conformance/glsl/functions/glsl-function-atan-xy.html +conformance/glsl/functions/glsl-function-ceil.html +conformance/glsl/functions/glsl-function-clamp-float.html +conformance/glsl/functions/glsl-function-clamp-gentype.html +conformance/glsl/functions/glsl-function-cos.html +conformance/glsl/functions/glsl-function-cross.html +conformance/glsl/functions/glsl-function-distance.html +conformance/glsl/functions/glsl-function-dot.html +conformance/glsl/functions/glsl-function-faceforward.html +conformance/glsl/functions/glsl-function-floor.html +conformance/glsl/functions/glsl-function-fract.html +conformance/glsl/functions/glsl-function-length.html +conformance/glsl/functions/glsl-function-max-float.html +conformance/glsl/functions/glsl-function-max-gentype.html +conformance/glsl/functions/glsl-function-min-float.html +conformance/glsl/functions/glsl-function-min-gentype.html +conformance/glsl/functions/glsl-function-mix-float.html +conformance/glsl/functions/glsl-function-mix-gentype.html +conformance/glsl/functions/glsl-function-mod-float.html +conformance/glsl/functions/glsl-function-mod-gentype.html +conformance/glsl/functions/glsl-function-normalize.html +conformance/glsl/functions/glsl-function-reflect.html +conformance/glsl/functions/glsl-function-sign.html +conformance/glsl/functions/glsl-function-sin.html +conformance/glsl/functions/glsl-function-step-float.html +conformance/glsl/functions/glsl-function-step-gentype.html +conformance/glsl/functions/glsl-function-smoothstep-float.html +conformance/glsl/functions/glsl-function-smoothstep-gentype.html +conformance/glsl/implicit/add_int_float.vert.html +conformance/glsl/implicit/add_int_mat2.vert.html +conformance/glsl/implicit/add_int_mat3.vert.html +conformance/glsl/implicit/add_int_mat4.vert.html +conformance/glsl/implicit/add_int_vec2.vert.html +conformance/glsl/implicit/add_int_vec3.vert.html +conformance/glsl/implicit/add_int_vec4.vert.html +conformance/glsl/implicit/add_ivec2_vec2.vert.html +conformance/glsl/implicit/add_ivec3_vec3.vert.html +conformance/glsl/implicit/add_ivec4_vec4.vert.html +conformance/glsl/implicit/assign_int_to_float.vert.html +conformance/glsl/implicit/assign_ivec2_to_vec2.vert.html +conformance/glsl/implicit/assign_ivec3_to_vec3.vert.html +conformance/glsl/implicit/assign_ivec4_to_vec4.vert.html +conformance/glsl/implicit/construct_struct.vert.html +conformance/glsl/implicit/divide_int_float.vert.html +conformance/glsl/implicit/divide_int_mat2.vert.html +conformance/glsl/implicit/divide_int_mat3.vert.html +conformance/glsl/implicit/divide_int_mat4.vert.html +conformance/glsl/implicit/divide_int_vec2.vert.html +conformance/glsl/implicit/divide_int_vec3.vert.html +conformance/glsl/implicit/divide_int_vec4.vert.html +conformance/glsl/implicit/divide_ivec2_vec2.vert.html +conformance/glsl/implicit/divide_ivec3_vec3.vert.html +conformance/glsl/implicit/divide_ivec4_vec4.vert.html +conformance/glsl/implicit/equal_int_float.vert.html +conformance/glsl/implicit/equal_ivec2_vec2.vert.html +conformance/glsl/implicit/equal_ivec3_vec3.vert.html +conformance/glsl/implicit/equal_ivec4_vec4.vert.html +conformance/glsl/implicit/function_int_float.vert.html +conformance/glsl/implicit/function_ivec2_vec2.vert.html +conformance/glsl/implicit/function_ivec3_vec3.vert.html +conformance/glsl/implicit/function_ivec4_vec4.vert.html +conformance/glsl/implicit/greater_than.vert.html +conformance/glsl/implicit/greater_than_equal.vert.html +conformance/glsl/implicit/less_than.vert.html +conformance/glsl/implicit/less_than_equal.vert.html +conformance/glsl/implicit/multiply_int_float.vert.html +conformance/glsl/implicit/multiply_int_mat2.vert.html +conformance/glsl/implicit/multiply_int_mat3.vert.html +conformance/glsl/implicit/multiply_int_mat4.vert.html +conformance/glsl/implicit/multiply_int_vec2.vert.html +conformance/glsl/implicit/multiply_int_vec3.vert.html +conformance/glsl/implicit/multiply_int_vec4.vert.html +conformance/glsl/implicit/multiply_ivec2_vec2.vert.html +conformance/glsl/implicit/multiply_ivec3_vec3.vert.html +conformance/glsl/implicit/multiply_ivec4_vec4.vert.html +conformance/glsl/implicit/not_equal_int_float.vert.html +conformance/glsl/implicit/not_equal_ivec2_vec2.vert.html +conformance/glsl/implicit/not_equal_ivec3_vec3.vert.html +conformance/glsl/implicit/not_equal_ivec4_vec4.vert.html +conformance/glsl/implicit/subtract_int_float.vert.html +conformance/glsl/implicit/subtract_int_mat2.vert.html +conformance/glsl/implicit/subtract_int_mat3.vert.html +conformance/glsl/implicit/subtract_int_mat4.vert.html +conformance/glsl/implicit/subtract_int_vec2.vert.html +conformance/glsl/implicit/subtract_int_vec3.vert.html +conformance/glsl/implicit/subtract_int_vec4.vert.html +conformance/glsl/implicit/subtract_ivec2_vec2.vert.html +conformance/glsl/implicit/subtract_ivec3_vec3.vert.html +conformance/glsl/implicit/subtract_ivec4_vec4.vert.html +conformance/glsl/implicit/ternary_int_float.vert.html +conformance/glsl/implicit/ternary_ivec2_vec2.vert.html +conformance/glsl/implicit/ternary_ivec3_vec3.vert.html +conformance/glsl/implicit/ternary_ivec4_vec4.vert.html +conformance/glsl/literals/float_literal.vert.html +conformance/glsl/literals/literal_precision.html +conformance/glsl/literals/overflow_leak.vert.html +conformance/glsl/matrices/glsl-mat4-to-mat3.html +conformance/glsl/matrices/glsl-mat3-construction.html +conformance/glsl/misc/attrib-location-length-limits.html +conformance/glsl/misc/boolean_precision.html +conformance/glsl/misc/embedded-struct-definitions-forbidden.html +conformance/glsl/misc/empty_main.vert.html +conformance/glsl/misc/expression-list-in-declarator-initializer.html +conformance/glsl/misc/gl_position_unset.vert.html +conformance/glsl/misc/glsl-function-nodes.html +conformance/glsl/misc/glsl-vertex-branch.html +conformance/glsl/misc/glsl-long-variable-names.html +conformance/glsl/misc/non-ascii-comments.vert.html +conformance/glsl/misc/non-ascii.vert.html +conformance/glsl/misc/re-compile-re-link.html +conformance/glsl/misc/shader-precision-format-obeyed.html +conformance/glsl/misc/shader-struct-scope.html +conformance/glsl/misc/shader-uniform-packing-restrictions.html +conformance/glsl/misc/shader-varying-packing-restrictions.html +conformance/glsl/misc/shader-with-256-character-define.html +conformance/glsl/misc/shader-with-256-character-identifier.frag.html +conformance/glsl/misc/shader-with-257-character-define.html +conformance/glsl/misc/shader-with-257-character-identifier.frag.html +conformance/glsl/misc/shader-with-_webgl-identifier.vert.html +conformance/glsl/misc/shader-with-arbitrary-indexing.frag.html +conformance/glsl/misc/shader-with-arbitrary-indexing.vert.html +conformance/glsl/misc/shader-with-array-of-structs-containing-arrays.html +conformance/glsl/misc/shader-with-array-of-structs-uniform.html +conformance/glsl/misc/shader-with-attrib-array.vert.html +conformance/glsl/misc/shader-with-attrib-struct.vert.html +conformance/glsl/misc/shader-with-clipvertex.vert.html +conformance/glsl/misc/shader-with-conditional-scoping.html +conformance/glsl/misc/shader-with-conditional-scoping-negative.html +conformance/glsl/misc/shader-with-default-precision.frag.html +conformance/glsl/misc/shader-with-default-precision.vert.html +conformance/glsl/misc/shader-with-define-line-continuation.frag.html +conformance/glsl/misc/shader-with-dfdx-no-ext.frag.html +conformance/glsl/misc/shader-with-dfdx.frag.html +conformance/glsl/misc/shader-with-do-loop.html +conformance/glsl/misc/shader-with-error-directive.html +conformance/glsl/misc/shader-with-explicit-int-cast.vert.html +conformance/glsl/misc/shader-with-float-return-value.frag.html +conformance/glsl/misc/shader-with-for-scoping.html +conformance/glsl/misc/shader-with-for-loop.html +conformance/glsl/misc/shader-with-frag-depth.frag.html +conformance/glsl/misc/shader-with-function-recursion.frag.html +conformance/glsl/misc/shader-with-function-scoped-struct.html +conformance/glsl/misc/shader-with-functional-scoping.html +conformance/glsl/misc/shader-with-comma-assignment.html +conformance/glsl/misc/shader-with-comma-conditional-assignment.html +conformance/glsl/misc/shader-with-glcolor.vert.html +conformance/glsl/misc/shader-with-gles-1.frag.html +conformance/glsl/misc/shader-with-gles-symbol.frag.html +conformance/glsl/misc/shader-with-glprojectionmatrix.vert.html +conformance/glsl/misc/shader-with-implicit-vec3-to-vec4-cast.vert.html +conformance/glsl/misc/shader-with-include.vert.html +conformance/glsl/misc/shader-with-int-return-value.frag.html +conformance/glsl/misc/shader-with-invalid-identifier.frag.html +conformance/glsl/misc/shader-with-ivec2-return-value.frag.html +conformance/glsl/misc/shader-with-ivec3-return-value.frag.html +conformance/glsl/misc/shader-with-ivec4-return-value.frag.html +conformance/glsl/misc/shader-with-limited-indexing.frag.html +conformance/glsl/misc/shader-with-hex-int-constant-macro.html +conformance/glsl/misc/shader-with-long-line.html +conformance/glsl/misc/shader-with-non-ascii-error.frag.html +conformance/glsl/misc/shader-with-non-reserved-words.html +conformance/glsl/misc/shader-with-precision.frag.html +conformance/glsl/misc/shader-with-preprocessor-whitespace.html +conformance/glsl/misc/shader-with-quoted-error.frag.html +conformance/glsl/misc/shader-with-reserved-words.html +conformance/glsl/misc/shader-with-similar-uniform-array-names.html +conformance/glsl/misc/shader-with-too-many-uniforms.html +conformance/glsl/misc/shader-with-undefined-preprocessor-symbol.frag.html +conformance/glsl/misc/shader-with-uniform-in-loop-condition.vert.html +conformance/glsl/misc/shader-with-vec2-return-value.frag.html +conformance/glsl/misc/shader-with-vec3-return-value.frag.html +conformance/glsl/misc/shader-with-vec4-return-value.frag.html +conformance/glsl/misc/shader-with-vec4-vec3-vec4-conditional.html +conformance/glsl/misc/shader-with-version-100.frag.html +conformance/glsl/misc/shader-with-version-100.vert.html +conformance/glsl/misc/shader-with-version-120.vert.html +conformance/glsl/misc/shader-with-version-130.vert.html +conformance/glsl/misc/shader-with-webgl-identifier.vert.html +conformance/glsl/misc/shader-with-while-loop.html +conformance/glsl/misc/shader-without-precision.frag.html +conformance/glsl/misc/shaders-with-constant-expression-loop-conditions.html +conformance/glsl/misc/shaders-with-invariance.html +conformance/glsl/misc/shaders-with-name-conflicts.html +conformance/glsl/misc/shaders-with-mis-matching-uniforms.html +conformance/glsl/misc/shaders-with-mis-matching-varyings.html +conformance/glsl/misc/shaders-with-missing-varyings.html +conformance/glsl/misc/shaders-with-uniform-structs.html +conformance/glsl/misc/shaders-with-varyings.html +conformance/glsl/misc/shared.html +conformance/glsl/misc/struct-nesting-exceeds-maximum.html +conformance/glsl/misc/struct-nesting-under-maximum.html +conformance/glsl/misc/uniform-location-length-limits.html +conformance/glsl/misc/shader-with-short-circuiting-operators.html +conformance/glsl/misc/shader-with-global-variable-precision-mismatch.html +conformance/glsl/misc/large-loop-compile.html +conformance/glsl/misc/struct-equals.html +conformance/glsl/misc/struct-mixed-array-declarators.html +conformance/glsl/misc/struct-nesting-of-variable-names.html +conformance/glsl/misc/struct-specifiers-in-uniforms.html +conformance/glsl/misc/struct-unary-operators.html +conformance/glsl/misc/ternary-operators-in-global-initializers.html +conformance/glsl/misc/ternary-operators-in-initializers.html +conformance/glsl/reserved/_webgl_field.vert.html +conformance/glsl/reserved/_webgl_function.vert.html +conformance/glsl/reserved/_webgl_struct.vert.html +conformance/glsl/reserved/_webgl_variable.vert.html +conformance/glsl/reserved/webgl_field.vert.html +conformance/glsl/reserved/webgl_function.vert.html +conformance/glsl/reserved/webgl_struct.vert.html +conformance/glsl/reserved/webgl_variable.vert.html +conformance/glsl/samplers/glsl-function-texture2d-bias.html +conformance/glsl/samplers/glsl-function-texture2dlod.html +conformance/glsl/samplers/glsl-function-texture2dproj.html +conformance/glsl/samplers/glsl-function-texture2dprojlod.html +conformance/glsl/variables/gl-fragcoord.html +conformance/glsl/variables/gl-frontfacing.html +conformance/glsl/variables/gl-pointcoord.html +conformance/glsl/variables/glsl-built-ins.html +conformance/glsl/variables/gl-fragcoord-xy-values.html +conformance/glsl/variables/gl-fragdata-and-fragcolor.html +conformance/limits/gl-min-attribs.html +conformance/limits/gl-max-texture-dimensions.html +conformance/limits/gl-min-textures.html +conformance/limits/gl-min-uniforms.html +conformance/misc/bad-arguments-test.html +conformance/misc/boolean-argument-conversion.html +conformance/misc/delayed-drawing.html +conformance/misc/error-reporting.html +conformance/misc/instanceof-test.html +conformance/misc/invalid-passed-params.html +conformance/misc/is-object.html +conformance/misc/null-object-behaviour.html +conformance/misc/functions-returning-strings.html +conformance/misc/object-deletion-behaviour.html +conformance/misc/shader-precision-format.html +conformance/misc/type-conversion-test.html +conformance/misc/uninitialized-test.html +conformance/misc/webgl-specific.html +conformance/ogles/GL/abs/abs_001_to_006.html +conformance/ogles/GL/acos/acos_001_to_006.html +conformance/ogles/GL/all/all_001_to_004.html +conformance/ogles/GL/any/any_001_to_004.html +conformance/ogles/GL/array/array_001_to_006.html +conformance/ogles/GL/asin/asin_001_to_006.html +conformance/ogles/GL/atan/atan_001_to_008.html +conformance/ogles/GL/atan/atan_009_to_012.html +conformance/ogles/GL/biConstants/biConstants_001_to_008.html +conformance/ogles/GL/biConstants/biConstants_009_to_016.html +conformance/ogles/GL/biuDepthRange/biuDepthRange_001_to_002.html +conformance/ogles/GL/build/build_001_to_008.html +conformance/ogles/GL/build/build_009_to_016.html +conformance/ogles/GL/build/build_017_to_024.html +conformance/ogles/GL/build/build_025_to_032.html +conformance/ogles/GL/build/build_033_to_040.html +conformance/ogles/GL/build/build_041_to_048.html +conformance/ogles/GL/build/build_049_to_056.html +conformance/ogles/GL/build/build_057_to_064.html +conformance/ogles/GL/build/build_065_to_072.html +conformance/ogles/GL/build/build_073_to_080.html +conformance/ogles/GL/build/build_081_to_088.html +conformance/ogles/GL/build/build_089_to_096.html +conformance/ogles/GL/build/build_097_to_104.html +conformance/ogles/GL/build/build_105_to_112.html +conformance/ogles/GL/build/build_113_to_120.html +conformance/ogles/GL/build/build_121_to_128.html +conformance/ogles/GL/build/build_129_to_136.html +conformance/ogles/GL/build/build_137_to_144.html +conformance/ogles/GL/build/build_145_to_152.html +conformance/ogles/GL/build/build_153_to_160.html +conformance/ogles/GL/build/build_161_to_168.html +conformance/ogles/GL/build/build_169_to_176.html +conformance/ogles/GL/build/build_177_to_178.html +conformance/ogles/GL/built_in_varying_array_out_of_bounds/built_in_varying_array_out_of_bounds_001_to_001.html +conformance/ogles/GL/ceil/ceil_001_to_006.html +conformance/ogles/GL/clamp/clamp_001_to_006.html +conformance/ogles/GL/control_flow/control_flow_001_to_008.html +conformance/ogles/GL/control_flow/control_flow_009_to_010.html +conformance/ogles/GL/cos/cos_001_to_006.html +conformance/ogles/GL/cross/cross_001_to_002.html +conformance/ogles/GL/default/default_001_to_001.html +conformance/ogles/GL/degrees/degrees_001_to_006.html +conformance/ogles/GL/discard/discard_001_to_002.html +conformance/ogles/GL/distance/distance_001_to_006.html +conformance/ogles/GL/dot/dot_001_to_006.html +conformance/ogles/GL/equal/equal_001_to_008.html +conformance/ogles/GL/equal/equal_009_to_012.html +conformance/ogles/GL/exp/exp_001_to_008.html +conformance/ogles/GL/exp/exp_009_to_012.html +conformance/ogles/GL/exp2/exp2_001_to_008.html +conformance/ogles/GL/exp2/exp2_009_to_012.html +conformance/ogles/GL/faceforward/faceforward_001_to_006.html +conformance/ogles/GL/floor/floor_001_to_006.html +conformance/ogles/GL/fract/fract_001_to_006.html +conformance/ogles/GL/functions/functions_001_to_008.html +conformance/ogles/GL/functions/functions_009_to_016.html +conformance/ogles/GL/functions/functions_017_to_024.html +conformance/ogles/GL/functions/functions_025_to_032.html +conformance/ogles/GL/functions/functions_033_to_040.html +conformance/ogles/GL/functions/functions_041_to_048.html +conformance/ogles/GL/functions/functions_049_to_056.html +conformance/ogles/GL/functions/functions_057_to_064.html +conformance/ogles/GL/functions/functions_065_to_072.html +conformance/ogles/GL/functions/functions_073_to_080.html +conformance/ogles/GL/functions/functions_081_to_088.html +conformance/ogles/GL/functions/functions_089_to_096.html +conformance/ogles/GL/functions/functions_097_to_104.html +conformance/ogles/GL/functions/functions_105_to_112.html +conformance/ogles/GL/functions/functions_113_to_120.html +conformance/ogles/GL/functions/functions_121_to_126.html +conformance/ogles/GL/gl_FragCoord/gl_FragCoord_001_to_003.html +conformance/ogles/GL/gl_FrontFacing/gl_FrontFacing_001_to_001.html +conformance/ogles/GL/greaterThan/greaterThan_001_to_008.html +conformance/ogles/GL/greaterThanEqual/greaterThanEqual_001_to_008.html +conformance/ogles/GL/inversesqrt/inversesqrt_001_to_006.html +conformance/ogles/GL/length/length_001_to_006.html +conformance/ogles/GL/lessThan/lessThan_001_to_008.html +conformance/ogles/GL/lessThanEqual/lessThanEqual_001_to_008.html +conformance/ogles/GL/log/log_001_to_008.html +conformance/ogles/GL/log/log_009_to_012.html +conformance/ogles/GL/log2/log2_001_to_008.html +conformance/ogles/GL/log2/log2_009_to_012.html +conformance/ogles/GL/mat/mat_001_to_008.html +conformance/ogles/GL/mat/mat_009_to_016.html +conformance/ogles/GL/mat/mat_017_to_024.html +conformance/ogles/GL/mat/mat_025_to_032.html +conformance/ogles/GL/mat/mat_033_to_040.html +conformance/ogles/GL/mat/mat_041_to_046.html +conformance/ogles/GL/mat3/mat3_001_to_006.html +conformance/ogles/GL/matrixCompMult/matrixCompMult_001_to_004.html +conformance/ogles/GL/max/max_001_to_006.html +conformance/ogles/GL/min/min_001_to_006.html +conformance/ogles/GL/mix/mix_001_to_006.html +conformance/ogles/GL/mod/mod_001_to_008.html +conformance/ogles/GL/normalize/normalize_001_to_006.html +conformance/ogles/GL/not/not_001_to_004.html +conformance/ogles/GL/notEqual/notEqual_001_to_008.html +conformance/ogles/GL/notEqual/notEqual_009_to_012.html +conformance/ogles/GL/operators/operators_001_to_008.html +conformance/ogles/GL/operators/operators_009_to_016.html +conformance/ogles/GL/operators/operators_017_to_024.html +conformance/ogles/GL/operators/operators_025_to_026.html +conformance/ogles/GL/pow/pow_001_to_008.html +conformance/ogles/GL/pow/pow_009_to_016.html +conformance/ogles/GL/pow/pow_017_to_024.html +conformance/ogles/GL/radians/radians_001_to_006.html +conformance/ogles/GL/reflect/reflect_001_to_006.html +conformance/ogles/GL/refract/refract_001_to_006.html +conformance/ogles/GL/sign/sign_001_to_006.html +conformance/ogles/GL/sin/sin_001_to_006.html +conformance/ogles/GL/smoothstep/smoothstep_001_to_006.html +conformance/ogles/GL/sqrt/sqrt_001_to_006.html +conformance/ogles/GL/step/step_001_to_006.html +conformance/ogles/GL/struct/struct_001_to_008.html +conformance/ogles/GL/struct/struct_009_to_016.html +conformance/ogles/GL/struct/struct_017_to_024.html +conformance/ogles/GL/struct/struct_025_to_032.html +conformance/ogles/GL/struct/struct_033_to_040.html +conformance/ogles/GL/struct/struct_041_to_048.html +conformance/ogles/GL/struct/struct_049_to_056.html +conformance/ogles/GL/swizzlers/swizzlers_001_to_008.html +conformance/ogles/GL/swizzlers/swizzlers_009_to_016.html +conformance/ogles/GL/swizzlers/swizzlers_017_to_024.html +conformance/ogles/GL/swizzlers/swizzlers_025_to_032.html +conformance/ogles/GL/swizzlers/swizzlers_033_to_040.html +conformance/ogles/GL/swizzlers/swizzlers_041_to_048.html +conformance/ogles/GL/swizzlers/swizzlers_049_to_056.html +conformance/ogles/GL/swizzlers/swizzlers_057_to_064.html +conformance/ogles/GL/swizzlers/swizzlers_065_to_072.html +conformance/ogles/GL/swizzlers/swizzlers_073_to_080.html +conformance/ogles/GL/swizzlers/swizzlers_081_to_088.html +conformance/ogles/GL/swizzlers/swizzlers_089_to_096.html +conformance/ogles/GL/swizzlers/swizzlers_097_to_104.html +conformance/ogles/GL/swizzlers/swizzlers_105_to_112.html +conformance/ogles/GL/swizzlers/swizzlers_113_to_120.html +conformance/ogles/GL/tan/tan_001_to_006.html +conformance/ogles/GL/vec/vec_001_to_008.html +conformance/ogles/GL/vec/vec_009_to_016.html +conformance/ogles/GL/vec/vec_017_to_018.html +conformance/ogles/GL/vec3/vec3_001_to_008.html +conformance/programs/get-active-test.html +conformance/programs/gl-bind-attrib-location-test.html +conformance/programs/gl-bind-attrib-location-long-names-test.html +conformance/programs/gl-get-active-attribute.html +conformance/programs/gl-get-active-uniform.html +conformance/programs/gl-getshadersource.html +conformance/programs/gl-shader-test.html +conformance/programs/invalid-UTF-16.html +conformance/programs/program-test.html +conformance/programs/use-program-crash-with-discard-in-fragment-shader.html +conformance/reading/read-pixels-pack-alignment.html +conformance/reading/read-pixels-test.html +conformance/renderbuffers/feedback-loop.html +conformance/renderbuffers/framebuffer-object-attachment.html +conformance/renderbuffers/framebuffer-state-restoration.html +conformance/renderbuffers/framebuffer-test.html +conformance/renderbuffers/renderbuffer-initialization.html +conformance/rendering/culling.html +conformance/rendering/draw-arrays-out-of-bounds.html +conformance/rendering/draw-elements-out-of-bounds.html +conformance/rendering/framebuffer-switch.html +conformance/rendering/framebuffer-texture-switch.html +conformance/rendering/gl-clear.html +conformance/rendering/gl-drawarrays.html +conformance/rendering/gl-drawelements.html +conformance/rendering/gl-scissor-test.html +conformance/rendering/gl-scissor-fbo-test.html +conformance/rendering/gl-scissor-canvas-dimensions.html +conformance/rendering/gl-viewport-test.html +conformance/rendering/many-draw-calls.html +conformance/rendering/more-than-65536-indices.html +conformance/rendering/multisample-corruption.html +conformance/rendering/negative-one-index.html +conformance/rendering/point-no-attributes.html +conformance/rendering/point-size.html +conformance/rendering/point-with-gl-pointcoord-in-fragment-shader.html +conformance/rendering/polygon-offset.html +conformance/rendering/simple.html +conformance/rendering/triangle.html +conformance/rendering/line-loop-tri-fan.html +conformance/state/gl-enable-enum-test.html +conformance/state/gl-enum-tests.html +conformance/state/gl-get-calls.html +conformance/state/gl-geterror.html +conformance/state/gl-getstring.html +conformance/state/gl-object-get-calls.html +conformance/state/state-uneffected-after-compositing.html +conformance/textures/compressed-tex-image.html +conformance/textures/copy-tex-image-and-sub-image-2d.html +conformance/textures/copy-tex-image-2d-formats.html +conformance/textures/default-texture.html +conformance/textures/gl-get-tex-parameter.html +conformance/textures/gl-pixelstorei.html +conformance/textures/gl-teximage.html +conformance/textures/origin-clean-conformance.html +conformance/textures/tex-image-and-sub-image-2d-with-array-buffer-view.html +conformance/textures/tex-image-and-sub-image-2d-with-canvas.html +conformance/textures/tex-image-and-sub-image-2d-with-canvas-rgb565.html +conformance/textures/tex-image-and-sub-image-2d-with-canvas-rgba4444.html +conformance/textures/tex-image-and-sub-image-2d-with-canvas-rgba5551.html +conformance/textures/tex-image-and-sub-image-2d-with-image-data.html +conformance/textures/tex-image-and-sub-image-2d-with-image-data-rgb565.html +conformance/textures/tex-image-and-sub-image-2d-with-image-data-rgba4444.html +conformance/textures/tex-image-and-sub-image-2d-with-image-data-rgba5551.html +conformance/textures/tex-image-and-sub-image-2d-with-image.html +conformance/textures/tex-image-and-sub-image-2d-with-image-rgb565.html +conformance/textures/tex-image-and-sub-image-2d-with-image-rgba4444.html +conformance/textures/tex-image-and-sub-image-2d-with-image-rgba5551.html +conformance/textures/tex-image-and-sub-image-2d-with-svg-image.html +conformance/textures/tex-image-and-sub-image-2d-with-video.html +conformance/textures/tex-image-and-sub-image-2d-with-video-rgb565.html +conformance/textures/tex-image-and-sub-image-2d-with-video-rgba4444.html +conformance/textures/tex-image-and-sub-image-2d-with-video-rgba5551.html +conformance/textures/tex-image-and-sub-image-2d-with-webgl-canvas.html +conformance/textures/tex-image-and-sub-image-2d-with-webgl-canvas-rgb565.html +conformance/textures/tex-image-and-sub-image-2d-with-webgl-canvas-rgba4444.html +conformance/textures/tex-image-and-sub-image-2d-with-webgl-canvas-rgba5551.html +conformance/textures/tex-image-and-uniform-binding-bugs.html +conformance/textures/tex-image-canvas-corruption.html +conformance/textures/tex-image-webgl.html +conformance/textures/tex-image-with-format-and-type.html +conformance/textures/tex-image-with-invalid-data.html +conformance/textures/tex-input-validation.html +conformance/textures/tex-sub-image-2d-bad-args.html +conformance/textures/tex-sub-image-2d.html +conformance/textures/texparameter-test.html +conformance/textures/texture-active-bind-2.html +conformance/textures/texture-active-bind.html +conformance/textures/texture-attachment-formats.html +conformance/textures/texture-clear.html +conformance/textures/texture-complete.html +conformance/textures/texture-copying-feedback-loops.html +conformance/textures/texture-hd-dpi.html +conformance/textures/texture-formats-test.html +conformance/textures/texture-mips.html +conformance/textures/texture-npot-video.html +conformance/textures/texture-npot.html +conformance/textures/texture-size.html +conformance/textures/texture-size-cube-maps.html +conformance/textures/texture-size-limit.html +conformance/textures/texture-sub-image-cube-maps.html +conformance/textures/texture-transparent-pixels-initialized.html +conformance/textures/texture-upload-cube-maps.html +conformance/textures/texture-upload-size.html +conformance/textures/mipmap-fbo.html +conformance/textures/texture-fakeblack.html +conformance/textures/texture-draw-with-2d-and-cube.html +conformance/typedarrays/array-buffer-crash.html +conformance/typedarrays/array-buffer-view-crash.html +conformance/typedarrays/array-unit-tests.html +conformance/typedarrays/data-view-crash.html +conformance/typedarrays/data-view-test.html +conformance/typedarrays/typed-arrays-in-workers.html +conformance/typedarrays/array-large-array-tests.html +conformance/uniforms/gl-uniform-arrays.html +conformance/uniforms/gl-uniform-bool.html +conformance/uniforms/gl-uniformmatrix4fv.html +conformance/uniforms/gl-unknown-uniform.html +conformance/uniforms/null-uniform-location.html +conformance/uniforms/out-of-bounds-uniform-array-access.html +conformance/uniforms/uniform-default-values.html +conformance/uniforms/uniform-values-per-program.html +conformance/uniforms/uniform-location.html +conformance/uniforms/uniform-samplers-test.html +conformance/more/conformance/constants.html +conformance/more/conformance/getContext.html +conformance/more/conformance/methods.html +conformance/more/conformance/quickCheckAPI-A.html +conformance/more/conformance/quickCheckAPI-B1.html +conformance/more/conformance/quickCheckAPI-B2.html +conformance/more/conformance/quickCheckAPI-B3.html +conformance/more/conformance/quickCheckAPI-B4.html +conformance/more/conformance/quickCheckAPI-C.html +conformance/more/conformance/quickCheckAPI-D_G.html +conformance/more/conformance/quickCheckAPI-G_I.html +conformance/more/conformance/quickCheckAPI-L_S.html +conformance/more/conformance/quickCheckAPI-S_V.html +conformance/more/conformance/webGLArrays.html +conformance/more/functions/bindBuffer.html +conformance/more/functions/bindBufferBadArgs.html +conformance/more/functions/bindFramebufferLeaveNonZero.html +conformance/more/functions/bufferData.html +conformance/more/functions/bufferDataBadArgs.html +conformance/more/functions/bufferSubData.html +conformance/more/functions/bufferSubDataBadArgs.html +conformance/more/functions/copyTexImage2D.html +conformance/more/functions/copyTexImage2DBadArgs.html +conformance/more/functions/copyTexSubImage2D.html +conformance/more/functions/copyTexSubImage2DBadArgs.html +conformance/more/functions/deleteBufferBadArgs.html +conformance/more/functions/drawArrays.html +conformance/more/functions/drawArraysOutOfBounds.html +conformance/more/functions/drawElements.html +conformance/more/functions/isTests.html +conformance/more/functions/isTestsBadArgs.html +conformance/more/functions/readPixels.html +conformance/more/functions/readPixelsBadArgs.html +conformance/more/functions/texImage2D.html +conformance/more/functions/texImage2DBadArgs.html +conformance/more/functions/texImage2DHTML.html +conformance/more/functions/texImage2DHTMLBadArgs.html +conformance/more/functions/texSubImage2D.html +conformance/more/functions/texSubImage2DBadArgs.html +conformance/more/functions/texSubImage2DHTML.html +conformance/more/functions/texSubImage2DHTMLBadArgs.html +conformance/more/functions/uniformf.html +conformance/more/functions/uniformfBadArgs.html +conformance/more/functions/uniformfArrayLen1.html +conformance/more/functions/uniformi.html +conformance/more/functions/uniformiBadArgs.html +conformance/more/functions/uniformMatrix.html +conformance/more/functions/uniformMatrixBadArgs.html +conformance/more/functions/vertexAttrib.html +conformance/more/functions/vertexAttribBadArgs.html +conformance/more/functions/vertexAttribPointer.html +conformance/more/functions/vertexAttribPointerBadArgs.html +conformance/more/glsl/arrayOutOfBounds.html +conformance/more/glsl/uniformOutOfBounds.html \ No newline at end of file diff --git a/tools/wave/webgl/resources/unit.js b/tools/wave/webgl/resources/unit.js new file mode 100644 index 000000000000000..ee667ee83d0cc41 --- /dev/null +++ b/tools/wave/webgl/resources/unit.js @@ -0,0 +1,960 @@ +/* +Unit testing library for the OpenGL ES 2.0 HTML Canvas context +*/ + +/* +** 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. +*/ + +// Array containing the runned subtests. +// All the runned tests will be set to done once +// notifyFinishedToHarness is called. +var subTests = []; + +/* -- plaform specific code -- */ + +// WebKit +if (window.testRunner && !window.layoutTestController) { + window.layoutTestController = window.testRunner; +} + +if (window.layoutTestController) { + layoutTestController.overridePreference("WebKitWebGLEnabled", "1"); + layoutTestController.dumpAsText(); + layoutTestController.waitUntilDone(); + + // The WebKit testing system compares console output. + // Because the output of the WebGL Tests is GPU dependent + // we turn off console messages. + window.console.log = function() { }; + window.console.error = function() { }; + + // RAF doesn't work in LayoutTests. Disable it so the tests will + // use setTimeout instead. + window.requestAnimationFrame = undefined; + window.webkitRequestAnimationFrame = undefined; +} + +if (window.internals) { + window.internals.settings.setWebGLErrorsToConsoleEnabled(false); +} + +/* -- end platform specific code --*/ +Tests = { + autorun : true, + message : null, + delay : 0, + autoinit: true, + + startUnit : function(){ return []; }, + setup : function() { return arguments; }, + teardown : function() {}, + endUnit : function() {} +} + +var __testSuccess__ = true; +var __testFailCount__ = 0; +var __testLog__; +var __backlog__ = []; + +Object.toSource = function(a, seen){ + if (a == null) return "null"; + if (typeof a == 'boolean') return a ? "true" : "false"; + if (typeof a == 'string') return '"' + a.replace(/"/g, '\\"') + '"'; + if (a instanceof HTMLElement) return a.toString(); + if (a.width && a.height && a.data) return "[ImageData]"; + if (a instanceof Array) { + if (!seen) seen = []; + var idx = seen.indexOf(a); + if (idx != -1) return '#'+(idx+1)+'#'; + seen.unshift(a); + var srcs = a.map(function(o){ return Object.toSource(o,seen) }); + var prefix = ''; + idx = seen.indexOf(a); + if (idx != -1) prefix = '#'+(idx+1)+'='; + return prefix + '[' + srcs.join(", ") + ']'; + } + if (typeof a == 'object') { + if (!seen) seen = []; + var idx = seen.indexOf(a); + if (idx != -1) return '#'+(idx+1)+'#'; + seen.unshift(a); + var members = []; + var name; + try { + for (var i in a) { + if (i.search(/^[a-zA-Z0-9]+$/) != -1) + name = i; + else + name = '"' + i.replace(/"/g, '\\"') + '"'; + var ai; + try { ai = a[i]; } + catch(e) { ai = 'null /*ERROR_ACCESSING*/'; } + var s = name + ':' + Object.toSource(ai, seen); + members.push(s); + } + } catch (e) {} + var prefix = ''; + idx = seen.indexOf(a); + if (idx != -1) prefix = '#'+(idx+1)+'='; + return prefix + '{' + members.join(", ") + '}' + } + if (typeof a == 'function') + return '('+a.toString().replace(/\n/g, " ").replace(/\s+/g, " ")+')'; + return a.toString(); +} + +function formatError(e) { + if (window.console) console.log(e); + var pathSegs = location.href.toString().split("/"); + var currentDoc = e.lineNumber != null ? pathSegs[pathSegs.length - 1] : null; + var trace = (e.filename || currentDoc) + ":" + e.lineNumber + (e.trace ? "\n"+e.trace : ""); + return e.message + "\n" + trace; +} + +function runTests() { + var h = document.getElementById('test-status'); + if (h == null) { + h = document.createElement('h1'); + h.id = 'test-status'; + document.body.appendChild(h); + } + h.textContent = ""; + var log = document.getElementById('test-log'); + if (log == null) { + log = document.createElement('div'); + log.id = 'test-log'; + document.body.appendChild(log); + } + while (log.childNodes.length > 0) + log.removeChild(log.firstChild); + + var setup_args = []; + + if (Tests.startUnit != null) { + __testLog__ = document.createElement('div'); + try { + setup_args = Tests.startUnit(); + if (__testLog__.childNodes.length > 0) + log.appendChild(__testLog__); + } catch(e) { + testFailed("startUnit", formatError(e)); + log.appendChild(__testLog__); + printTestStatus(); + return; + } + } + + var testsRun = false; + var allTestsSuccessful = true; + + for (var i in Tests) { + if (i.substring(0,4) != "test") continue; + __testLog__ = document.createElement('div'); + __testSuccess__ = true; + try { + doTestNotify (i); + var args = setup_args; + if (Tests.setup != null) + args = Tests.setup.apply(Tests, setup_args); + Tests[i].apply(Tests, args); + if (Tests.teardown != null) + Tests.teardown.apply(Tests, args); + } + catch (e) { + testFailed(i, e.name, formatError(e)); + } + if (__testSuccess__ == false) { + ++__testFailCount__; + } + var h = document.createElement('h2'); + h.textContent = i; + __testLog__.insertBefore(h, __testLog__.firstChild); + log.appendChild(__testLog__); + allTestsSuccessful = allTestsSuccessful && __testSuccess__ == true; + reportTestResultsToHarness(__testSuccess__, i); + doTestNotify (i+"--"+(__testSuccess__?"OK":"FAIL")); + testsRun = true; + } + + printTestStatus(testsRun); + if (Tests.endUnit != null) { + __testLog__ = document.createElement('div'); + try { + Tests.endUnit.apply(Tests, setup_args); + if (__testLog__.childNodes.length > 0) + log.appendChild(__testLog__); + } catch(e) { + testFailed("endUnit", e.name, formatError(e)); + log.appendChild(__testLog__); + } + } + notifyFinishedToHarness(allTestsSuccessful, "finished tests"); +} + +function doTestNotify(name) { + //try { + // var xhr = new XMLHttpRequest(); + // xhr.open("GET", "http://localhost:8888/"+name, true); + // xhr.send(null); + //} catch(e) {} +} + +function testFailed(assertName, name) { + var d = document.createElement('div'); + var h = document.createElement('h3'); + var d1 = document.createElement("span"); + h.appendChild(d1); + d1.appendChild(document.createTextNode("FAIL: ")); + d1.style.color = "red"; + h.appendChild(document.createTextNode( + name==null ? assertName : name + " (in " + assertName + ")")); + d.appendChild(h); + var args = [] + for (var i=2; il[ii]) { + testFailed("assertArrayEqualsWithEpsilon", name, v, p, l); + return false; + } + } + testPassed("assertArrayEqualsWithEpsilon", name, v, p, l); + return true; +} + +function assertNotEquals(name, v, p) { + if (p == null) { p = v; v = name; name = null; } + if (compare(v, p)) { + testFailed("assertNotEquals", name, v, p) + return false; + } else { + testPassed("assertNotEquals", name, v, p) + return true; + } +} + +function time(elementId, f) { + var s = document.getElementById(elementId); + var t0 = new Date().getTime(); + f(); + var t1 = new Date().getTime(); + s.textContent = 'Elapsed: '+(t1-t0)+' ms'; +} + +function randomFloat () { + // note that in fuzz-testing, this can used as the size of a buffer to allocate. + // so it shouldn't return astronomic values. The maximum value 10000000 is already quite big. + var fac = 1.0; + var r = Math.random(); + if (r < 0.25) + fac = 10; + else if (r < 0.4) + fac = 100; + else if (r < 0.5) + fac = 1000; + else if (r < 0.6) + fac = 100000; + else if (r < 0.7) + fac = 10000000; + else if (r < 0.8) + fac = NaN; + return -0.5*fac + Math.random() * fac; +} +function randomFloatFromRange(lo, hi) { + var r = Math.random(); + if (r < 0.05) + return lo; + else if (r > 0.95) + return hi; + else + return lo + Math.random()*(hi-lo); +} +function randomInt (sz) { + if (sz != null) + return Math.floor(Math.random()*sz); + else + return Math.floor(randomFloat()); +} +function randomIntFromRange(lo, hi) { + return Math.floor(randomFloatFromRange(lo, hi)); +} +function randomLength () { + var l = Math.floor(Math.random() * 256); + if (Math.random < 0.5) l = l / 10; + if (Math.random < 0.3) l = l / 10; + return l; +} +function randomSmallIntArray () { + var l = randomLength(); + var s = new Array(l); + for (var i=0; i +