Skip to content

Commit

Permalink
Make the test definition directly usable as template parameters.
Browse files Browse the repository at this point in the history
This is done by better leveraging the Jinja templates, which removes
the need for manually processing the data in the Python script.
It also makes the generator more flexible because now, we can control
the template parameters directly form the Yaml test definition.
This unlocks very interesting follow-ups, like allowing fields in
the Yaml test definition to refer to each other using Jinja templating.

Change-Id: Iebbd125fff37d6d0c77a7188bcbc57c64c60115c
Bug: 1275750
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4556171
Reviewed-by: Yi Xu <yiyix@chromium.org>
Commit-Queue: Jean-Philippe Gravel <jpgravel@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1150253}
  • Loading branch information
graveljp authored and Chromium LUCI CQ committed May 29, 2023
1 parent beb57a3 commit 4f7cb9d
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 155 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
#
# * Test the tests, add new ones to Git, remove deleted ones from Git, etc.

from typing import Any, List, Mapping, MutableMapping, Optional, Set, Tuple
from typing import Any, List, Mapping, Optional, Set, Tuple

import re
import collections
Expand All @@ -40,7 +40,6 @@
import os
import pathlib
import sys
import textwrap

try:
import cairocffi as cairo # type: ignore
Expand Down Expand Up @@ -252,51 +251,33 @@ class TestConfig:
image_out_dir: str


_CANVAS_SIZE_REGEX = re.compile(r'(?P<width>.*), (?P<height>.*)',
re.MULTILINE | re.DOTALL)

def _validate_test(test: Mapping[str, Any]):
if test.get('expected', '') == 'green' and re.search(
r'@assert pixel .* 0,0,0,0;', test['code']):
print('Probable incorrect pixel test in %s' % test['name'])

def _get_canvas_size(test: Mapping[str, Any]):
size = test.get('size', '100, 50')
match = _CANVAS_SIZE_REGEX.match(size)
if not match:
if 'size' in test and (not isinstance(test['size'], list)
or len(test['size']) != 2):
raise InvalidTestDefinitionError(
'Invalid canvas size "%s" in test %s. Expected a string matching '
'this pattern: "%%s, %%s" %% (width, height)' %
(size, test['name']))
return match.group('width'), match.group('height')
f'Invalid canvas size "{test["size"]}" in test {test["name"]}. '
'Expected an array with two numbers.')

if 'test_type' in test and test['test_type'] != 'promise':
raise InvalidTestDefinitionError(
f'Test {test["name"]}\' test_type is invalid, it only accepts '
'"promise" now for creating promise test type in the template '
'file.')

def _write_reference_test(test: Mapping[str, Any],
jinja_env: jinja2.Environment,
template_params: MutableMapping[str, Any],
enabled_tests: Set[TestType],
canvas_path: str, offscreen_path: str):
name = template_params["name"]
js_ref = test.get('reference')
html_ref = test.get('html_reference')
if js_ref is not None and html_ref is not None:
if 'reference' in test and 'html_reference' in test:
raise InvalidTestDefinitionError(
f'Test {name} is invalid, "reference" and "html_reference" can\'t '
'both be specified at the same time.')
f'Test {test["name"]} is invalid, "reference" and "html_reference" '
'can\'t both be specified at the same time.')

ref_params = template_params.copy()
ref_params.update({'code': js_ref or html_ref})
ref_template_name = 'reftest_element.html' if js_ref else 'reftest.html'
if TestType.HTML_CANVAS in enabled_tests:
pathlib.Path(f'{canvas_path}-expected.html').write_text(
jinja_env.get_template(ref_template_name).render(ref_params),
'utf-8')
if {TestType.OFFSCREEN_CANVAS, TestType.WORKER} & enabled_tests:
pathlib.Path(f'{offscreen_path}-expected.html').write_text(
jinja_env.get_template(ref_template_name).render(ref_params),
'utf-8')

params = template_params.copy()
params.update({
'ref_link': f'{name}-expected.html',
'fuzzy': test.get('fuzzy')
})
def _write_reference_test(jinja_env: jinja2.Environment,
params: Mapping[str, Any],
enabled_tests: Set[TestType],
canvas_path: str, offscreen_path: str):
if TestType.HTML_CANVAS in enabled_tests:
pathlib.Path(f'{canvas_path}.html').write_text(
jinja_env.get_template("reftest_element.html").render(params),
Expand All @@ -310,46 +291,57 @@ def _write_reference_test(test: Mapping[str, Any],
jinja_env.get_template("reftest_worker.html").render(params),
'utf-8')

js_ref = params.get('reference')
html_ref = params.get('html_reference')
ref_params = dict(params)
ref_params.update({
'is_test_reference': True,
'code': js_ref or html_ref
})
ref_template_name = 'reftest_element.html' if js_ref else 'reftest.html'
if TestType.HTML_CANVAS in enabled_tests:
pathlib.Path(f'{canvas_path}-expected.html').write_text(
jinja_env.get_template(ref_template_name).render(ref_params),
'utf-8')
if {TestType.OFFSCREEN_CANVAS, TestType.WORKER} & enabled_tests:
pathlib.Path(f'{offscreen_path}-expected.html').write_text(
jinja_env.get_template(ref_template_name).render(ref_params),
'utf-8')


def _write_testharness_test(jinja_env: jinja2.Environment,
template_params: MutableMapping[str, Any],
params: Mapping[str, Any],
enabled_tests: Set[TestType],
canvas_path: str,
offscreen_path: str):
# Create test cases for canvas and offscreencanvas.
if TestType.HTML_CANVAS in enabled_tests:
pathlib.Path(f'{canvas_path}.html').write_text(
jinja_env.get_template("testharness_element.html").render(
template_params), 'utf-8')

template_params['done_needed'] = ('then(t_pass, t_fail);'
not in template_params['code'])
jinja_env.get_template("testharness_element.html").render(params),
'utf-8')

if TestType.OFFSCREEN_CANVAS in enabled_tests:
pathlib.Path(f'{offscreen_path}.html').write_text(
jinja_env.get_template("testharness_offscreen.html").render(
template_params), 'utf-8')
params), 'utf-8')

if TestType.WORKER in enabled_tests:
pathlib.Path(f'{offscreen_path}.worker.js').write_text(
jinja_env.get_template("testharness_worker.js").render(
template_params), 'utf-8')
jinja_env.get_template("testharness_worker.js").render(params),
'utf-8')


def _generate_test(test: Mapping[str, Any], jinja_env: jinja2.Environment,
sub_dir: str, enabled_tests: Set[TestType],
html_canvas_cfg: TestConfig,
offscreen_canvas_cfg: TestConfig) -> None:
name = test['name']
_validate_test(test)

if test.get('expected', '') == 'green' and re.search(
r'@assert pixel .* 0,0,0,0;', test['code']):
print('Probable incorrect pixel test in %s' % name)
name = test['name']

expectation_html = ''
expected_img = None
if 'expected' in test and test['expected'] is not None:
expected = test['expected']
expected_img = None
if expected == 'green':
expected_img = '/images/green-100x50.png'
elif expected == 'clear':
Expand Down Expand Up @@ -379,65 +371,30 @@ def _generate_test(test: Mapping[str, Any], jinja_env: jinja2.Environment,

expected_img = '%s.png' % name

if expected_img:
expectation_html = (
'<p class="output expectedtext">Expected output:<p>'
'<img src="%s" class="output expected" id="expected" '
'alt="">' % expected_img)

width, height = _get_canvas_size(test)

images = ''
for src in test.get('images', []):
img_id = src.split('/')[-1]
if '/' not in src:
src = '../images/%s' % src
images += '<img src="%s" id="%s" class="resource">\n' % (src, img_id)
for src in test.get('svgimages', []):
img_id = src.split('/')[-1]
if '/' not in src:
src = '../images/%s' % src
images += ('<svg><image xlink:href="%s" id="%s" class="resource">'
'</svg>\n' % (src, img_id))
images = images.replace('../images/', '/images/')

is_promise_test = False
if 'test_type' in test:
if test['test_type'] == 'promise':
is_promise_test = True
else:
raise InvalidTestDefinitionError(
f'Test {name}\' test_type is invalid, it only accepts '
'"promise" now for creating promise test type in the template '
'file.')

template_params = {
'name': name,
'desc': test.get('desc', ''),
'notes': test.get('notes', ''),
'images': images,
'timeout': test.get('timeout'),
'canvas': test.get('canvas', ''),
'width': width,
'height': height,
'expected': expectation_html,
'code': _expand_test_code(test['code']),
'attributes': test.get('attributes', ''),
'promise_test': is_promise_test
# Defaults:
params = {
'desc': '',
'size': [100, 50],
}

params.update(test)
params.update({
'code': _expand_test_code(test['code']),
'expected_img': expected_img
})

canvas_path = os.path.join(html_canvas_cfg.out_dir, sub_dir, name)
offscreen_path = os.path.join(offscreen_canvas_cfg.out_dir, sub_dir, name)
if 'manual' in test:
canvas_path += '-manual'
offscreen_path += '-manual'

if 'reference' in test or 'html_reference' in test:
_write_reference_test(test, jinja_env, template_params, enabled_tests,
_write_reference_test(jinja_env, params, enabled_tests,
canvas_path, offscreen_path)
else:
_write_testharness_test(jinja_env, template_params, enabled_tests,
canvas_path, offscreen_path)
_write_testharness_test(jinja_env, params, enabled_tests, canvas_path,
offscreen_path)


def genTestUtils_union(NAME2DIRFILE: str) -> None:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
{% if ref_link %}<link rel="match" href="{{ ref_link }}">
{% endif %}
{% if fuzzy %}<meta name=fuzzy content="{{ fuzzy }}">
{% endif %}
{% if timeout %}<meta name="timeout" content="{{ timeout }}">
{% endif %}
<title>Canvas test: {{ name }}</title>
<h1>{{ name }}</h1>
<p class="desc">{{ desc }}</p>
{% if notes %}<p class="notes">{{ notes }}{% endif %}

{{ code | trim }}
{{ images -}}
{% for image in images %}
<img src="/images/{{ image }}" id="{{ image }}" class="resource">
{% endfor -%}
{% for svgimage in svgimages %}
<svg><image xlink:href="/images/{{ svgimage }}" id="{{ svgimage }}" class="resource"></svg>
{% endfor -%}
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
{% if promise_test %}<html class="reftest-wait">
{% endif %}
{% if ref_link %}<link rel="match" href="{{ ref_link }}">
{% if test_type == 'promise' %}<html class="reftest-wait">
{% endif %}
{% if not is_test_reference %}
<link rel="match" href="{{ name }}-expected.html">
{% if fuzzy %}<meta name=fuzzy content="{{ fuzzy }}">
{% endif %}
{% endif %}
{% if timeout %}<meta name="timeout" content="{{ timeout }}">
{% endif %}
<title>Canvas test: {{ name }}</title>
<h1>{{ name }}</h1>
<p class="desc">{{ desc }}</p>
{% if notes %}<p class="notes">{{ notes }}{% endif %}
<canvas id="canvas" width="{{ width }}" height="{{ height }}"{{ canvas }}>
<canvas id="canvas" width="{{ size[0] }}" height="{{ size[1] }}"{{ canvas }}>
<p class="fallback">FAIL (fallback content)</p>
</canvas>
<script{% if promise_test %} type="module"{% endif %}>
<script{% if test_type == 'promise' %} type="module"{% endif %}>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext('2d'{% if attributes %}, {{ attributes }}{% endif %});

{{ code | trim | indent(2) }}
{% if promise_test %}
{% if test_type == 'promise' %}
document.documentElement.classList.remove("reftest-wait");
{% endif %}
</script>
{{ images -}}
{% if promise_test %}</html>{% endif %}
{% for image in images %}
<img src="/images/{{ image }}" id="{{ image }}" class="resource">
{% endfor -%}
{% for svgimage in svgimages %}
<svg><image xlink:href="/images/{{ svgimage }}" id="{{ svgimage }}" class="resource"></svg>
{% endfor -%}
{% if test_type == 'promise' %}</html>{% endif %}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
{% if promise_test %}<html class="reftest-wait">
{% endif %}
{% if ref_link %}<link rel="match" href="{{ ref_link }}">
{% if test_type == 'promise' %}<html class="reftest-wait">
{% endif %}
<link rel="match" href="{{ name }}-expected.html">
{% if fuzzy %}<meta name=fuzzy content="{{ fuzzy }}">
{% endif %}
{% if timeout %}<meta name="timeout" content="{{ timeout }}">
Expand All @@ -12,20 +11,19 @@
<h1>{{ name }}</h1>
<p class="desc">{{ desc }}</p>
{% if notes %}<p class="notes">{{ notes }}{% endif %}
<canvas id="canvas" width="{{ width }}" height="{{ height }}"{{ canvas }}>
<canvas id="canvas" width="{{ size[0] }}" height="{{ size[1] }}"{{ canvas }}>
<p class="fallback">FAIL (fallback content)</p>
</canvas>
<script{% if promise_test %} type="module"{% endif %}>
const canvas = new OffscreenCanvas({{ width }}, {{ height }});
<script{% if test_type == 'promise' %} type="module"{% endif %}>
const canvas = new OffscreenCanvas({{ size[0] }}, {{ size[1] }});
const ctx = canvas.getContext('2d'{% if attributes %}, {{ attributes }}{% endif %});

{{ code | trim | indent(2) }}

const outputCanvas = document.getElementById("canvas");
outputCanvas.getContext('2d'{% if attributes %}, {{ attributes }}{% endif %}).drawImage(canvas, 0, 0);
{% if promise_test %}
{% if test_type == 'promise' %}
document.documentElement.classList.remove("reftest-wait");
{% endif %}
</script>
{{ images -}}
{% if promise_test %}</html>{% endif %}
{% if test_type == 'promise' %}</html>{% endif %}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
<html class="reftest-wait">
{% if ref_link %}<link rel="match" href="{{ ref_link }}">
{% endif %}
<link rel="match" href="{{ name }}-expected.html">
{% if fuzzy %}<meta name=fuzzy content="{{ fuzzy }}">
{% endif %}
{% if timeout %}<meta name="timeout" content="{{ timeout }}">
Expand All @@ -11,12 +10,12 @@
<h1>{{ name }}</h1>
<p class="desc">{{ desc }}</p>
{% if notes %}<p class="notes">{{ notes }}{% endif %}
<canvas id="canvas" width="{{ width }}" height="{{ height }}"{{ canvas }}>
<canvas id="canvas" width="{{ size[0] }}" height="{{ size[1] }}"{{ canvas }}>
<p class="fallback">FAIL (fallback content)</p>
</canvas>
<script id='myWorker' type='text/worker'>
self.onmessage = {% if promise_test %}async {% endif %}function(e) {
const canvas = new OffscreenCanvas({{ width }}, {{ height }});
self.onmessage = {% if test_type == 'promise' %}async {% endif %}function(e) {
const canvas = new OffscreenCanvas({{ size[0] }}, {{ size[1] }});
const ctx = canvas.getContext('2d'{% if attributes %}, {{ attributes }}{% endif %});

{{ code | trim | indent(4) }}
Expand All @@ -35,5 +34,4 @@ <h1>{{ name }}</h1>
});
worker.postMessage(null);
</script>
{{ images -}}
</html>

0 comments on commit 4f7cb9d

Please sign in to comment.