mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 08:12:05 +00:00
Bug 1921829 [wpt PR 48382] - Add a new canvas WPT template using pycairo-generated reference images, a=testonly
Automatic update from web-platform-tests Add a new canvas WPT template using pycairo-generated reference images Using the "cairo_reference:" key in the YAML config, we can now express the expected result of a test using pycairo code. Contrary to the existing "expected:" key which produces an image that is only used for "informational" purpose, "cairo_reference:" produces a reference test, meaning that the test runner automatically compares the test result with that generated image and fails the test if the result differs. Both single variant tests (non-variant or tests with a single variant per file) and variant grids are supported. For single variant, the generated reference file is an HMTL page with a single <img> tag pointing to the PNG generated with pycairo. To cut down on the number of files generated for variant grids, all the reference images for all of the cells of the grid are packed into a single PNG file, 3d-model-texture-style. The reference HTML file is a grid of <img> tags, each framing a different portion of the PNG image. Bug: 364549423 Change-Id: Icb3c246b22347054b7cc9b5e5cc40d21b30565bd Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5894774 Reviewed-by: Andres Ricardo Perez <andresrperez@chromium.org> Commit-Queue: Jean-Philippe Gravel <jpgravel@chromium.org> Cr-Commit-Position: refs/heads/main@{#1369396} -- wpt-commits: 2af17a84e98cd28940d2882c68ff824d6e98f4b7 wpt-pr: 48382
This commit is contained in:
parent
a965622d9c
commit
19fc588c80
@ -39,6 +39,7 @@ import dataclasses
|
||||
import enum
|
||||
import importlib
|
||||
import itertools
|
||||
import math
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
@ -257,6 +258,7 @@ class _CanvasType(str, enum.Enum):
|
||||
class _TemplateType(str, enum.Enum):
|
||||
REFERENCE = 'reference'
|
||||
HTML_REFERENCE = 'html_reference'
|
||||
CAIRO_REFERENCE = 'cairo_reference'
|
||||
TESTHARNESS = 'testharness'
|
||||
|
||||
|
||||
@ -452,15 +454,21 @@ class _Variant():
|
||||
return frozenset(_CanvasType(t) for t in canvas_types)
|
||||
|
||||
def _get_template_type(self) -> _TemplateType:
|
||||
if 'reference' in self.params and 'html_reference' in self.params:
|
||||
reference_types = (('reference' in self.params) +
|
||||
('html_reference' in self.params) +
|
||||
('cairo_reference' in self.params))
|
||||
if reference_types > 1:
|
||||
raise InvalidTestDefinitionError(
|
||||
f'Test {self.params["name"]} is invalid, "reference" and '
|
||||
'"html_reference" can\'t both be specified at the same time.')
|
||||
f'Test {self.params["name"]} is invalid, only one of '
|
||||
'"reference", "html_reference" or "cairo_reference" can be '
|
||||
'specified at the same time.')
|
||||
|
||||
if 'reference' in self.params:
|
||||
return _TemplateType.REFERENCE
|
||||
if 'html_reference' in self.params:
|
||||
return _TemplateType.HTML_REFERENCE
|
||||
if 'cairo_reference' in self.params:
|
||||
return _TemplateType.CAIRO_REFERENCE
|
||||
return _TemplateType.TESTHARNESS
|
||||
|
||||
def finalize_params(self, jinja_env: jinja2.Environment,
|
||||
@ -476,13 +484,10 @@ class _Variant():
|
||||
if isinstance(self._params['size'], list):
|
||||
self._params['size'] = tuple(self._params['size'])
|
||||
|
||||
if 'reference' in self._params:
|
||||
self._params['reference'] = _preprocess_code(
|
||||
jinja_env, self._params['reference'], self._params)
|
||||
|
||||
if 'html_reference' in self._params:
|
||||
self._params['html_reference'] = _preprocess_code(
|
||||
jinja_env, self._params['html_reference'], self._params)
|
||||
for ref_type in {'reference', 'html_reference', 'cairo_reference'}:
|
||||
if ref_type in self._params:
|
||||
self._params[ref_type] = _preprocess_code(
|
||||
jinja_env, self._params[ref_type], self._params)
|
||||
|
||||
code_params = dict(self.params)
|
||||
if _CanvasType.HTML_CANVAS in self.params['canvas_types']:
|
||||
@ -503,7 +508,7 @@ class _Variant():
|
||||
_validate_test(self._params)
|
||||
|
||||
def generate_expected_image(self, output_dirs: _OutputPaths) -> None:
|
||||
"""Creates a reference image using Cairo and save filename in params."""
|
||||
"""Creates an expected image using Cairo and save filename in params."""
|
||||
expected = self.params['expected']
|
||||
|
||||
if expected == 'green':
|
||||
@ -660,7 +665,8 @@ class _VariantGrid:
|
||||
'fonts': self._param_set('fonts'),
|
||||
}
|
||||
if self.template_type in (_TemplateType.REFERENCE,
|
||||
_TemplateType.HTML_REFERENCE):
|
||||
_TemplateType.HTML_REFERENCE,
|
||||
_TemplateType.CAIRO_REFERENCE):
|
||||
grid_params['desc'] = self._unique_param('desc')
|
||||
return grid_params
|
||||
|
||||
@ -692,9 +698,12 @@ class _VariantGrid:
|
||||
f'{output_files.offscreen}.w.html')
|
||||
|
||||
params['is_test_reference'] = True
|
||||
is_html_ref = self.template_type == _TemplateType.HTML_REFERENCE
|
||||
ref_template_name = (f'reftest{grid}.html'
|
||||
if is_html_ref else f'reftest_element{grid}.html')
|
||||
templates = {
|
||||
_TemplateType.REFERENCE: f'reftest_element{grid}.html',
|
||||
_TemplateType.HTML_REFERENCE: f'reftest{grid}.html',
|
||||
_TemplateType.CAIRO_REFERENCE: f'reftest_img{grid}.html'
|
||||
}
|
||||
ref_template_name = templates[self.template_type]
|
||||
|
||||
if _CanvasType.HTML_CANVAS in self.canvas_types:
|
||||
_render(jinja_env, ref_template_name, params,
|
||||
@ -727,9 +736,72 @@ class _VariantGrid:
|
||||
_render(jinja_env, f'testharness_worker{grid}.js', self.params,
|
||||
f'{output_files.offscreen}.worker.js')
|
||||
|
||||
def _generate_cairo_reference_grid(self,
|
||||
output_dirs: _OutputPaths) -> None:
|
||||
"""Generate this grid's expected image from Cairo code, if needed.
|
||||
|
||||
In order to cut on the number of files generated, the expected image
|
||||
of all the variants in this grid are packed into a single PNG. The
|
||||
expected HTML then contains a grid of <img> tags, each showing a portion
|
||||
of the PNG file."""
|
||||
if not any(v.params.get('cairo_reference') for v in self.variants):
|
||||
return
|
||||
|
||||
width, height = self._unique_param('size')
|
||||
cairo_code = ''
|
||||
|
||||
# First generate a function producing a Cairo surface with the expected
|
||||
# image for each variant in the grid. The function is needed to provide
|
||||
# a scope isolating the variant code from each other.
|
||||
for idx, variant in enumerate(self._variants):
|
||||
cairo_ref = variant.params.get('cairo_reference')
|
||||
if not cairo_ref:
|
||||
raise InvalidTestDefinitionError(
|
||||
'When used, "cairo_reference" must be specified for all '
|
||||
'test variants.')
|
||||
cairo_code += textwrap.dedent(f'''\
|
||||
def draw_ref{idx}():
|
||||
surface = cairo.ImageSurface(
|
||||
cairo.FORMAT_ARGB32, {width}, {height})
|
||||
cr = cairo.Context(surface)
|
||||
{{}}
|
||||
return surface
|
||||
''').format(textwrap.indent(cairo_ref, ' '))
|
||||
|
||||
# Write all variant images into the final surface.
|
||||
surface_width = width * self._grid_width
|
||||
surface_height = (height *
|
||||
math.ceil(len(self._variants) / self._grid_width))
|
||||
cairo_code += textwrap.dedent(f'''\
|
||||
surface = cairo.ImageSurface(
|
||||
cairo.FORMAT_ARGB32, {surface_width}, {surface_height})
|
||||
cr = cairo.Context(surface)
|
||||
''')
|
||||
for idx, variant in enumerate(self._variants):
|
||||
x_pos = int(idx % self._grid_width) * width
|
||||
y_pos = int(idx / self._grid_width) * height
|
||||
cairo_code += textwrap.dedent(f'''\
|
||||
cr.set_source_surface(draw_ref{idx}(), {x_pos}, {y_pos})
|
||||
cr.paint()
|
||||
''')
|
||||
|
||||
img_filename = f'{self.file_name}.png'
|
||||
_write_cairo_images(cairo_code, output_dirs.sub_path(img_filename),
|
||||
self.canvas_types)
|
||||
self._params['img_reference'] = img_filename
|
||||
|
||||
def _generate_cairo_images(self, output_dirs: _OutputPaths) -> None:
|
||||
"""Generates the pycairo images found in the YAML test definition."""
|
||||
if any(v.params.get('expected') for v in self._variants):
|
||||
has_expected = any(v.params.get('expected') for v in self._variants)
|
||||
has_cairo_reference = any(
|
||||
v.params.get('cairo_reference') for v in self._variants)
|
||||
|
||||
if has_expected and has_cairo_reference:
|
||||
raise InvalidTestDefinitionError(
|
||||
'Parameters "expected" and "cairo_reference" can\'t be both '
|
||||
'used at the same time.')
|
||||
|
||||
if has_expected:
|
||||
if len(self.variants) != 1:
|
||||
raise InvalidTestDefinitionError(
|
||||
'Parameter "expected" is not supported for variant grids.')
|
||||
@ -738,6 +810,8 @@ class _VariantGrid:
|
||||
'Parameter "expected" is not supported in reference '
|
||||
'tests.')
|
||||
self.variants[0].generate_expected_image(output_dirs)
|
||||
elif has_cairo_reference:
|
||||
self._generate_cairo_reference_grid(output_dirs)
|
||||
|
||||
def generate_test(self, jinja_env: jinja2.Environment,
|
||||
output_dirs: _OutputPaths) -> None:
|
||||
@ -747,7 +821,8 @@ class _VariantGrid:
|
||||
output_files = output_dirs.sub_path(self.file_name)
|
||||
|
||||
if self.template_type in (_TemplateType.REFERENCE,
|
||||
_TemplateType.HTML_REFERENCE):
|
||||
_TemplateType.HTML_REFERENCE,
|
||||
_TemplateType.CAIRO_REFERENCE):
|
||||
self._write_reference_test(jinja_env, output_files)
|
||||
else:
|
||||
self._write_testharness_test(jinja_env, output_files)
|
||||
@ -803,6 +878,9 @@ def _get_variant_grids(test: Mapping[str, Any],
|
||||
jinja_env: jinja2.Environment) -> List[_VariantGrid]:
|
||||
base_variant = _Variant.create_with_defaults(test)
|
||||
grid_width = base_variant.params.get('grid_width', 1)
|
||||
if not isinstance(grid_width, int):
|
||||
raise InvalidTestDefinitionError('"grid_width" must be an integer.')
|
||||
|
||||
grids = [_VariantGrid([base_variant], grid_width=grid_width)]
|
||||
for dimension in _get_variant_dimensions(test):
|
||||
variants = dimension.variants
|
||||
|
@ -0,0 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
|
||||
<title>Canvas test: {{ name }}</title>
|
||||
<h1>{{ name }}</h1>
|
||||
<p>{{ desc }}</p>
|
||||
{% if notes %}<p>{{ notes }}</p>{% endif %}
|
||||
|
||||
<img src="{{ img_reference }}">
|
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
|
||||
<title>Canvas test: {{ name }}</title>
|
||||
<h1 style="font-size: 20px;">{{ name }}</h1>
|
||||
<p>{{ desc }}</p>
|
||||
{% if notes %}<p>{{ notes }}</p>{% endif %}
|
||||
|
||||
<div style="display: grid; grid-gap: 4px;
|
||||
grid-template-columns: repeat({{ grid_width }}, max-content);
|
||||
font-size: 13px; text-align: center;">
|
||||
{% for variant in element_variants %}
|
||||
<span>
|
||||
{% for variant_name in variant.grid_variant_names %}
|
||||
<div>{{ variant_name }}</div>
|
||||
{% endfor %}
|
||||
{% set x_pos = ((loop.index0 % grid_width) | int) * variant.size[0] %}
|
||||
{% set y_pos = ((loop.index0 / grid_width) | int) * variant.size[1] %}
|
||||
<img src="{{ img_reference }}"
|
||||
style="outline: 1px solid;
|
||||
width: {{ variant.size[0] }}px;
|
||||
height: {{ variant.size[1] }}px;
|
||||
object-position: {{ -x_pos }}px {{ -y_pos }}px;
|
||||
object-fit: none;">
|
||||
</span>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user