diff --git a/.python-version b/.python-version new file mode 100644 index 00000000000000..cc1923a40b1a5e --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.8 diff --git a/tools/ci/ci_tools_integration_test.sh b/tools/ci/ci_tools_integration_test.sh index 76f682e254d313..2eae4303f13d04 100755 --- a/tools/ci/ci_tools_integration_test.sh +++ b/tools/ci/ci_tools_integration_test.sh @@ -13,11 +13,6 @@ main() { cd tools/wpt tox cd $WPT_ROOT - - # WMAS test runner integration tests - cd tools/wave - tox - cd $WPT_ROOT } main diff --git a/tools/wave/configuration_loader.py b/tools/wave/configuration_loader.py index 2e2aa331511741..ea406bb738bc3d 100644 --- a/tools/wave/configuration_loader.py +++ b/tools/wave/configuration_loader.py @@ -91,7 +91,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/session.py b/tools/wave/data/session.py index bb1b932dae60b1..365cc400d5df5f 100644 --- a/tools/wave/data/session.py +++ b/tools/wave/data/session.py @@ -9,6 +9,9 @@ PENDING = "pending" UNKNOWN = "unknown" +WMAS = "wmas" +DPCTF = "dpctf" + class Session: def __init__( @@ -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 00000000000000..96d51477fb7952 --- /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 00000000000000..43e7509fe2288b --- /dev/null +++ b/tools/wave/ecmascript/generate-tests.js @@ -0,0 +1,307 @@ +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); + 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) || []; + 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 00000000000000..679bd83bc70926 --- /dev/null +++ b/tools/wave/ecmascript/test-template.html @@ -0,0 +1,12 @@ + +{{ 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 00000000000000..67c8d375f6c0b8 --- /dev/null +++ b/tools/wave/ecmascript/test-template.iframe.html @@ -0,0 +1,15 @@ + +{{ TEST_TITLE }} + + + + + + +{{ INCLUDES }} + + + + diff --git a/tools/wave/ecmascript/webplatform-adapter.js b/tools/wave/ecmascript/webplatform-adapter.js new file mode 100644 index 00000000000000..bfa6e9fe7f78c9 --- /dev/null +++ b/tools/wave/ecmascript/webplatform-adapter.js @@ -0,0 +1,104 @@ +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; diff --git a/tools/wave/network/api/results_api_handler.py b/tools/wave/network/api/results_api_handler.py index a9da0df10f3f7a..1c619f6927f7ee 100644 --- a/tools/wave/network/api/results_api_handler.py +++ b/tools/wave/network/api/results_api_handler.py @@ -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.query 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 9eb896b80702e9..c9c5d2a9e687a2 100644 --- a/tools/wave/network/api/sessions_api_handler.py +++ b/tools/wave/network/api/sessions_api_handler.py @@ -20,7 +20,7 @@ def __init__( results_manager, event_dispatcher, web_root, - read_sessions_enabled + read_sessions_enabled, ): super().__init__(web_root) self._sessions_manager = sessions_manager @@ -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 38035837711ab6..5126be99230ed0 100644 --- a/tools/wave/network/api/tests_api_handler.py +++ b/tools/wave/network/api/tests_api_handler.py @@ -111,7 +111,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, @@ -265,13 +266,7 @@ def _generate_test_url(self, hostname, test, token, test_timeout): test = split[0] test_query = split[1] - query = "token={}&timeout={}&https_port={}&web_root={}&{}".format( - token, - test_timeout, - self._wpt_ssl_port, - self._web_root, - test_query - ) + query = f"token={token}&timeout={test_timeout}&https_port={self._wpt_ssl_port}&web_root={self._web_root}&{test_query}" return self._generate_url( protocol=protocol, diff --git a/tools/wave/network/http_handler.py b/tools/wave/network/http_handler.py index b76f711cf1d7c6..446d0641d2002a 100644 --- a/tools/wave/network/http_handler.py +++ b/tools/wave/network/http_handler.py @@ -12,14 +12,14 @@ class HttpHandler: 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 +89,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 +99,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,7 +112,7 @@ 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]) diff --git a/tools/wave/requirements.txt b/tools/wave/requirements.txt index 5404a6916bd6c3..3bb476fd968b2d 100644 --- a/tools/wave/requirements.txt +++ b/tools/wave/requirements.txt @@ -1,3 +1,2 @@ ua-parser==0.18.0 python-dateutil==2.9.0.post0 -types-python-dateutil==2.9.0.20241206 diff --git a/tools/wave/testing/devices_manager.py b/tools/wave/testing/devices_manager.py index 935782d1372b67..fb0323e01ace76 100644 --- a/tools/wave/testing/devices_manager.py +++ b/tools/wave/testing/devices_manager.py @@ -23,7 +23,7 @@ def initialize(self, event_dispatcher): def create_device(self, user_agent): browser = parse_user_agent(user_agent) - name = "{} {}".format(browser["name"], browser["version"]) + name = f"{browser['name']} {browser['version']}" token = str(uuid.uuid1()) last_active = int(time.time() * 1000) diff --git a/tools/wave/testing/results_manager.py b/tools/wave/testing/results_manager.py index 13dfcbad7d8515..a36e19262feeb9 100644 --- a/tools/wave/testing/results_manager.py +++ b/tools/wave/testing/results_manager.py @@ -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 @@ -22,7 +21,6 @@ WAVE_SRC_DIR = "./tools/wave" RESULTS_FILE_REGEX = r"^\w\w\d\d\d?\.json$" RESULTS_FILE_PATTERN = re.compile(RESULTS_FILE_REGEX) -SESSION_RESULTS_TIMEOUT = 60*30 # 30min class ResultsManager: @@ -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,7 +213,6 @@ 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) @@ -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) @@ -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)) @@ -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 093c3cffe8507a..a4992632cabe2d 100644 --- a/tools/wave/testing/sessions_manager.py +++ b/tools/wave/testing/sessions_manager.py @@ -15,6 +15,7 @@ from ..data.exceptions.not_found_exception import NotFoundException from ..data.exceptions.invalid_data_exception import InvalidDataException from ..utils.deserializer import deserialize_session +from ..utils.deserializer import iso_to_millis DEFAULT_TEST_TYPES = [AUTOMATIC, MANUAL] DEFAULT_TEST_PATHS = ["/"] @@ -23,13 +24,15 @@ class SessionsManager: - def initialize(self, - test_loader, - event_dispatcher, - tests_manager, - results_directory, - results_manager, - configuration): + 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 = {} @@ -76,18 +78,24 @@ def create_session( if test_type != "automatic" and test_type != "manual": raise InvalidDataException(f"Unknown type '{test_type}'") + if expiration_date is not None and not isinstance(expiration_date, int): + expiration_date = iso_to_millis(expiration_date) + if not isinstance(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 = "" diff --git a/tools/wave/testing/test_loader.py b/tools/wave/testing/test_loader.py index 8d751260d97656..0d3a7d0fb197c7 100644 --- a/tools/wave/testing/test_loader.py +++ b/tools/wave/testing/test_loader.py @@ -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,22 @@ 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: + 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 +117,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 +160,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 +173,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 8187eb4a7db4f1..69ce96105135c1 100644 --- a/tools/wave/testing/tests_manager.py +++ b/tools/wave/testing/tests_manager.py @@ -2,6 +2,7 @@ import re from threading import Timer +import functools from .event_dispatcher import TEST_COMPLETED_EVENT @@ -10,19 +11,14 @@ class TestsManager: - def initialize( - self, - test_loader, - sessions_manager, - results_manager, - event_dispatcher - ): + def initialize(self, test_loader, sessions_manager, results_manager, event_dispatcher): self._test_loader = test_loader self._sessions_manager = sessions_manager self._results_manager = results_manager self._event_dispatcher = event_dispatcher self._timeouts = [] + self._logs = {} def next_test(self, session): if session.status == COMPLETED or session.status == ABORTED: @@ -53,10 +49,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 +103,7 @@ 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 +114,29 @@ 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 +151,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: @@ -224,10 +211,8 @@ def skip_to(self, test_list, test): 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 +268,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 +294,11 @@ 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 - ) + self._event_dispatcher.dispatch_event(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 +336,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 +344,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 b84119ed852666..acf57a2e7c157d 100644 --- a/tools/wave/testing/wpt_report.py +++ b/tools/wave/testing/wpt_report.py @@ -12,7 +12,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 +28,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: + raise Exception("Failed to execute wptreport: " + " ".join(command)) def generate_multi_report( @@ -40,6 +44,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 +61,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 diff --git a/tools/wave/tests/test_wave.py b/tools/wave/tests/test_wave.py deleted file mode 100644 index a7d87a38e1b01f..00000000000000 --- a/tools/wave/tests/test_wave.py +++ /dev/null @@ -1,55 +0,0 @@ -# mypy: allow-untyped-defs - -import errno -import os -import socket -import subprocess -import time - -from urllib.request import urlopen -from urllib.error import URLError - -from tools.wpt import wpt - -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: - if e.errno == errno.EADDRINUSE: - return True - else: - raise e - finally: - s.close() - return False - -def test_serve(): - if is_port_8080_in_use(): - assert False, "WAVE Test Runner failed: Port 8080 already in use." - - p = subprocess.Popen([os.path.join(wpt.localpaths.repo_root, "wpt"), - "serve-wave", - "--config", - os.path.join(wpt.localpaths.repo_root, "tools/wave/tests/config.json")], - preexec_fn=os.setsid) - - start = time.time() - try: - while True: - if p.poll() is not None: - assert False, "WAVE Test Runner failed: Server not running." - if time.time() - start > 60: - assert False, "WAVE Test Runner failed: Server did not start responding within 60s." - try: - resp = urlopen("http://web-platform.test:8080/_wave/api/sessions/public") - print(resp) - except URLError: - print("URLError") - time.sleep(1) - else: - assert resp.code == 200 - break - finally: - os.killpg(p.pid, 15) - p.wait(10) diff --git a/tools/wave/utils/deserializer.py b/tools/wave/utils/deserializer.py index 28d1054f6ba77a..c1352a40733ab5 100644 --- a/tools/wave/utils/deserializer.py +++ b/tools/wave/utils/deserializer.py @@ -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 995365081db0ce..91d832f79e1830 100644 --- a/tools/wave/utils/serializer.py +++ b/tools/wave/utils/serializer.py @@ -23,7 +23,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/wave-cli.py b/tools/wave/wave-cli.py new file mode 100644 index 00000000000000..3119b0feab04c9 --- /dev/null +++ b/tools/wave/wave-cli.py @@ -0,0 +1,122 @@ +# mypy: allow-untyped-defs +# mypy: allow-untyped-calls + +import sys +import os +import urllib +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(f"Unknown operation {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(f"Unknown option {argument}") + + if "configuration_file_path" not in parameters: + configuration_file_path = os.path.join(os.getcwd(), "config.json") + parameters["configuration_file_path"] = configuration_file_path + + return parameters + + +def download_file(url, file_path): + response = urllib.request.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(f"Downloading {browser['name']} results ...") + dest_path = os.path.join(result_directory, browser["zip"]) + download_file(browser["url"], dest_path) + print(" done.") + + for id in REFERENCE_BROWSERS: + browser = REFERENCE_BROWSERS[id] + printt(f"Extracting {browser['name']} results ...") + dest_path = os.path.join(result_directory, browser["name"]) + zip_file = zipfile.ZipFile(dest_path) + 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/webgl/prepare-tests.js b/tools/wave/webgl/prepare-tests.js new file mode 100644 index 00000000000000..9ac8012ad70d67 --- /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 00000000000000..47ef89f737aa8c --- /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 00000000000000..1bfc60bd9d81f6 --- /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 00000000000000..ee667ee83d0cc4 --- /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 +