blob: 59a0c7cf12c57723ce87a4e3908db625be8a21f0 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (C) 2013 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Generates WebKit WebGL layout tests from the Khronos WebGL conformance tests
To use this, get a copy of the WebGL conformance tests then run this script
eg.
cd ~/temp
git clone git://github.com/KhronosGroup/WebGL.git
mkdir backup
mv ~/WebKit/LayoutTests/{http/,}webgl/{1.0.x,2.0.y,resources/webgl_test_files} backup
update-webgl-conformance-tests -c ~/temp/WebGL
run-webkit-tests --debug --webgl --order=random webgl
run-webkit-tests --release --webgl --order=random webgl
check-for-duplicated-platform-test-results -n 2>&1 | grep webgl
[ Update TestExpectations ]
[ Check in the result ]
"""
# Note: The script is currently not tested to be run on Windows.
#
# To add a new WebGL test suite, edit the main().
#
# python3 -m mypy main.py && python3 -m black main.py --line-length 200
import argparse
import os
import shutil
import sys
from distutils.version import StrictVersion as Version
from fnmatch import fnmatch
from pathlib import Path
from typing import Callable, Tuple, Iterable, Union, NamedTuple, Optional, Generator
_tool_dir = Path(__file__).parent
_layout_tests_dir = Path(__file__).parent.parent.parent.parent.parent / "LayoutTests"
_use_verbose_output = False
_clean_target_dirs = False
_IgnoredFilenameMatcher = Callable[[Path], bool]
# Creates an ignore file name pattern matcher based on list of match rules.
# Rule iteration order is the order of least specific to most specific rule.
# Empty list means do not ignore.
# If rule is str and it matches, it means ignore if no more specific rule matches.
# If rule is a tuple and its first element matches, it it means do not ignore if no more specific rule matches.
def _make_ignore_fnmatch_rule_matcher(rules: Iterable[Union[str, Tuple[str]]]) -> _IgnoredFilenameMatcher:
def match_rules(name: Path) -> bool:
should_filter = None
for rule in rules:
pattern: str = rule if isinstance(rule, str) else rule[0]
if fnmatch(str(name), pattern):
should_filter = isinstance(rule, str)
return should_filter is True
return match_rules
_file_patterns = _make_ignore_fnmatch_rule_matcher(
[
".git",
"*.pyc",
"tmp*",
"*/py/*",
"*.py",
"*/performance/*",
"*/*.md",
"*/extra/*",
"*/00_test_list.txt",
"*/CONFORMANCE_RULES.txt",
"*/deqp/build.py",
"*/deqp/compiler_additional_extern.js",
"*/deqp/compiler.jar",
"*/deqp/genHTMLfromTest.py",
"*/deqp/run-closure.sh",
"*/deqp/temp_externs*",
"*/deqp/test-webgl2.js",
"*/deqp/test-webgl2.sh",
"*/textures/misc/origin-clean-conformance-offscreencanvas.html",
"*/textures/misc/origin-clean-conformance.html",
"*/more/functions/readPixelsBadArgs.html",
"*/more/functions/texImage2DHTML.html",
"*/more/functions/texSubImage2DHTML.html",
]
)
_http_file_patterns = _make_ignore_fnmatch_rule_matcher(
[
"*",
("*/textures/misc/origin-clean-conformance-offscreencanvas.html", ),
("*/textures/misc/origin-clean-conformance.html", ),
("*/more/functions/readPixelsBadArgs.html", ),
("*/more/functions/texImage2DHTML.html", ),
("*/more/functions/texSubImage2DHTML.html", ),
("*/more/unit.css", ),
("*/more/unit.js", ),
("*/more/util.js", ),
("*/js/js-test-post.js", ),
("*/js/js-test-pre.js", ),
("*/js/webgl-test-utils.js", ),
("*/resources/js-test-style.css", ),
("*/resources/opengl_logo.jpg", ),
("*/resources/thunderbird-logo-64x64.png", ),
]
)
def _copy_tree(src: Path, dst: Path, ignore: Optional[_IgnoredFilenameMatcher] = None):
"""Recursively copy a directory tree"""
names = os.listdir(src)
for name in names:
src_name = src / name
dst_name = dst / name
if src_name.is_dir():
_copy_tree(src_name, dst_name, ignore)
else:
if ignore is not None and ignore(src_name):
if _use_verbose_output:
print("Ignoring: %s, matches ignore filter" % (src_name))
continue
if _use_verbose_output:
print("Copying: %s -> %s" % (src_name, dst_name))
dst.mkdir(parents=True, exist_ok=True)
shutil.copyfile(src_name, dst_name)
class _WebGLTest(NamedTuple):
path: Path
min_version: Version
max_version: Version
def _parse_webgl_tests(test_list_root: Path, test_list: Path, min_version: Version, max_version: Version) -> Generator[_WebGLTest, None, None]:
prefix = test_list.parent
with test_list.open("r") as f:
lines = f.readlines()
for line_number, line in enumerate(lines):
line = line.strip()
if len(line) <= 0 or line.startswith("#") or line.startswith(";") or line.startswith("//"):
continue
test_min_version = min_version
test_max_version = max_version
test_name = ""
args = iter(line.split())
for arg in args:
if arg.startswith("-"):
if arg == "--slow":
pass
elif arg == "--min-version":
test_min_version = Version(next(args))
elif arg == "--max-version":
test_max_version = Version(next(args))
else:
raise Exception("%s:%d unknown option '%s'" % (test_list, line_number, arg))
else:
test_name += arg
if test_name.endswith(".txt"):
for r in _parse_webgl_tests(test_list_root, prefix / test_name, test_min_version, test_max_version):
yield r
else:
yield _WebGLTest(
(prefix / test_name).relative_to(test_list_root),
test_min_version,
test_max_version,
)
def _filter_webgl_test_paths_for_suite_version(tests: Iterable[_WebGLTest], suite_version: Version) -> Generator[Path, None, None]:
for test in tests:
if test.min_version <= suite_version and test.max_version >= suite_version:
if _use_verbose_output:
print(f"Using test: {test.path} {test.min_version} <= {suite_version} <= {test.max_version}")
yield test.path
else:
if _use_verbose_output:
print(f"Ignoring test: {test.path} {test.min_version} <= {suite_version} <= {test.max_version}")
def _copy_webgl_test_files(
source_tests_dir: Path,
source_patterns: _IgnoredFilenameMatcher,
target_dir: Path,
):
target_test_files_dir = target_dir / "resources" / "webgl_test_files"
if _clean_target_dirs:
shutil.rmtree(target_test_files_dir)
_copy_tree(source_tests_dir, target_test_files_dir, source_patterns)
def _generate_webkit_webgl_tests(
source_tests_dir: Path,
suite_version: Version,
use_webgl2_context: bool,
target_dir: Path,
target_version_name: str,
):
target_test_files_dir = target_dir / "resources" / "webgl_test_files"
source_js_test_harness = _tool_dir / "webkit-webgl-test-harness-template.js"
target_js_test_harness = target_dir / "resources" / "webkit-webgl-test-harness.js"
target_js_test_harness.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source_js_test_harness, target_js_test_harness)
source_js_test_harness = _tool_dir / "js-test-pre-template.js"
target_js_test_harness = target_dir / "resources" / "js-test-pre.js"
shutil.copyfile(source_js_test_harness, target_js_test_harness)
test_template = (_tool_dir / "webgl-test-driver-template.html").read_text()
expectation_template = (_tool_dir / "webgl-expectation-template.txt").read_text()
tests = _parse_webgl_tests(source_tests_dir, (source_tests_dir / "00_test_list.txt"), suite_version, suite_version)
target_tests_dir = target_dir / target_version_name
if _clean_target_dirs:
shutil.rmtree(target_tests_dir)
for test_path in _filter_webgl_test_paths_for_suite_version(tests, suite_version):
target_test = target_tests_dir / test_path
target_test_impl = target_test_files_dir / test_path
if not target_test_impl.exists():
if _use_verbose_output:
print(f"Ignoring test: {test_path}, implementation does not exist at {target_test_impl}")
continue
test_to_impl = os.path.relpath(target_test_impl, target_test.parent)
test_file_url = f"{test_to_impl}?webglVersion=2" if use_webgl2_context else test_to_impl
subs = {"test_file_url": test_file_url, "test_name": test_path.name, "webgl_top_level_url": os.path.relpath(target_dir, target_test.parent)}
target_test.parent.mkdir(parents=True, exist_ok=True)
target_test.write_text(test_template % subs)
expectation = target_test.parent / f"{target_test.stem}-expected.txt"
expectation.write_text(expectation_template % subs)
def _find_expectations_for_removed_tests(layout_tests_dir: Path):
test_prefixes = set()
expectations = set()
for root, _, files in os.walk(layout_tests_dir):
for file_name in files:
relative_path = (Path(root) / file_name).relative_to(layout_tests_dir)
if relative_path.stem.endswith("-expected"):
expectations.add(relative_path)
else:
test_prefixes.add(relative_path.parent / relative_path.stem)
old_expectations = set([])
for expectation in expectations:
test_path = expectation.parent
while len(test_path.parts) >= 1:
test_prefix = test_path / expectation.stem[: -len("-expected")]
if test_prefix in test_prefixes:
break
test_path = Path("/".join(test_path.parts[1:]))
if len(test_path.parts) < 1:
old_expectations.add(expectation)
return old_expectations
def main():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument("-v", "--verbose", action="store_true", help="print verbose output.")
parser.add_argument("-c", "--clean-target-dirs", action="store_true", help="remove all files from target subdirectories before updating them.")
parser.add_argument("webgl_repository", help="path to WebGL conformance test repository root.")
parser.add_argument(
"-l",
"--layout-tests-dir",
help="base directory for searching for platform specific tests. Defaults to LayoutTests.",
default=_layout_tests_dir,
)
options = parser.parse_args()
global _use_verbose_output
_use_verbose_output = options.verbose or False
global _clean_target_dirs
_clean_target_dirs = options.clean_target_dirs or False
source_dir = Path(options.webgl_repository)
target_dir = Path(options.layout_tests_dir) / "webgl"
_copy_webgl_test_files(source_tests_dir=source_dir / "sdk" / "tests", source_patterns=_file_patterns, target_dir=target_dir)
_generate_webkit_webgl_tests(source_tests_dir=source_dir / "sdk" / "tests", suite_version=Version("1.0.4"), use_webgl2_context=False, target_dir=target_dir, target_version_name="1.0.x")
_generate_webkit_webgl_tests(source_tests_dir=source_dir / "sdk" / "tests", suite_version=Version("2.0.1"), use_webgl2_context=True, target_dir=target_dir, target_version_name="2.0.y")
if _use_verbose_output:
print("Updating http tests")
http_target_dir = Path(options.layout_tests_dir) / "http" / "tests" / "webgl"
_copy_webgl_test_files(source_tests_dir=source_dir / "sdk" / "tests", source_patterns=_http_file_patterns, target_dir=http_target_dir)
_generate_webkit_webgl_tests(source_tests_dir=source_dir / "sdk" / "tests", suite_version=Version("1.0.4"), use_webgl2_context=False, target_dir=http_target_dir, target_version_name="1.0.x")
_generate_webkit_webgl_tests(source_tests_dir=source_dir / "sdk" / "tests", suite_version=Version("2.0.1"), use_webgl2_context=True, target_dir=http_target_dir, target_version_name="2.0.y")
layout_tests_dir = Path(options.layout_tests_dir)
old_expectations = _find_expectations_for_removed_tests(layout_tests_dir)
old_expectations = [e for e in old_expectations if "webgl" in str(e)]
if old_expectations:
print("Old expectations maybe found. Maybe do:\n git rm " + " ".join(sorted([str((layout_tests_dir / e).relative_to(layout_tests_dir.parent)) for e in old_expectations])))
if __name__ == "__main__":
sys.exit(main())