-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Render widget decorations and compare to reference images. Should be useful to avoid any breakages!
- Loading branch information
1 parent
bd568db
commit 9d3072a
Showing
46 changed files
with
480 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Binary file added
BIN
+3.29 KB
test/resources/test_images/rect-default-group-filled-widget-background.png
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
# Copyright (c) 2023 elParaguayo | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# of this software and associated documentation files (the "Software"), to deal | ||
# in the Software without restriction, including without limitation the rights | ||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
# copies of the Software, and to permit persons to whom the Software is | ||
# 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 Software. | ||
# | ||
# THE SOFTWARE IS 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
# SOFTWARE. | ||
import re | ||
import subprocess | ||
import tempfile | ||
from pathlib import Path | ||
|
||
import cairocffi | ||
import pytest | ||
from libqtile.bar import Bar | ||
from libqtile.command.base import expose_command | ||
from libqtile.config import Screen | ||
|
||
from test.helpers import BareConfig | ||
|
||
# Regex pattern for extracting difference value from compare output | ||
# Matches a float including using scientific notation | ||
COMPARE_RESULT = re.compile(r"\((-?[\d.]+(?:e-?\d+)?)\)") | ||
|
||
RESOURCES = Path(__file__).parent / ".." / ".." / "resources" / "test_images" | ||
TEST_FOLDER = Path(__file__).parent.resolve().as_posix() | ||
|
||
# Required accuracy. Difference should be less than 0.5% (CI fonts render slightly differently) | ||
TOLERANCE = 0.005 | ||
|
||
|
||
@pytest.fixture(scope="session") | ||
def temp_images(): | ||
# with tempfile.TemporaryDirectory() as temp: | ||
# yield temp | ||
tempdir = tempfile.mkdtemp() | ||
yield tempdir | ||
|
||
|
||
@pytest.fixture(scope="function") | ||
def camera(manager_nospawn, request, temp_images): | ||
""" | ||
This fixture provides a means to take a picture of the current bar. | ||
The snapshot is then compared against a reference image to check that the | ||
content is still being rendered correctly. | ||
Reference images can be generated by passing the --generate option when | ||
calling pytest. The images will be saved in `test/widgets/decorations` and, | ||
if they are deemed correct (by manual verification), they should be moved to | ||
test/resources/test_images. | ||
""" | ||
# Check if we're generating reference images | ||
generate = request.config.getoption("--generate") | ||
image_dir = request.config.getoption("--generate-dir") | ||
ci = request.config.getoption("--generate-ci") | ||
|
||
# Get the configuration | ||
config = getattr(request, "param", False) | ||
if not config: | ||
assert False, "No configuration provided." | ||
|
||
# Get name of the decoration scenario | ||
name = config["name"] | ||
|
||
# Generate output filepath | ||
make_images = generate or ci | ||
if make_images: | ||
if ci: | ||
output_dir = ( | ||
(Path(__file__).parent / ".." / ".." / ".." / "decoration_images") | ||
.resolve() | ||
.as_posix() | ||
) | ||
else: | ||
output_dir = image_dir or TEST_FOLDER | ||
else: | ||
output_dir = temp_images | ||
|
||
output_file = f"{output_dir}/{name}.png" | ||
|
||
class ScreenshotBar(Bar): | ||
""" | ||
Subclass of Qtile's bar with additional methods to dump the | ||
Drawer to a png file. | ||
Both X11 and Wayland backends are supported. | ||
""" | ||
|
||
def x11_screenshot(self, name): | ||
"""Dumps X11 Drawer to png file.""" | ||
# Create a temporary image surface | ||
isurf = cairocffi.ImageSurface( | ||
cairocffi.FORMAT_ARGB32, self.drawer.width, self.drawer.height | ||
) | ||
|
||
# Create context for image surface | ||
with cairocffi.Context(isurf) as ctx: | ||
# Loop over widgets and copy widget's drawer to image surface | ||
# The context is translated to position contents correctly | ||
for w in self.widgets: | ||
ctx.save() | ||
ctx.set_operator(cairocffi.OPERATOR_SOURCE) | ||
ctx.translate(w.offsetx, w.offsety) | ||
ctx.rectangle(0, 0, w.width, w.height) | ||
ctx.set_source_surface(w.drawer._xcb_surface) | ||
ctx.fill() | ||
ctx.restore() | ||
|
||
# Check if we need to fill the end of the bar | ||
# (this happens if the bar doesn't have a SPACER widget) | ||
last_widget = self.widgets[-1] | ||
end = last_widget.offsetx + last_widget.width | ||
if end < self.width: | ||
ctx.save() | ||
ctx.translate(end, 0) | ||
ctx.set_source_surface(self.drawer._xcb_surface) | ||
ctx.rectangle(0, 0, self.drawer.width - end, self.drawer.height) | ||
ctx.fill() | ||
ctx.restore() | ||
|
||
# Dump surface to PNG file | ||
isurf.write_to_png(name) | ||
|
||
def wayland_screenshot(self, name): | ||
"""Dumps Wayland Drawer to png file.""" | ||
# Much easier: Wayland has an ImageSurface which we can access directly | ||
self.drawer._win.surface.write_to_png(name) | ||
|
||
@expose_command | ||
def take_screenshot(self, name): | ||
"""Exposed command to save bar contents to png file.""" | ||
if self.qtile.core.name == "x11": | ||
self.x11_screenshot(name) | ||
else: | ||
self.wayland_screenshot(name) | ||
|
||
class DecorationConfig(BareConfig): | ||
"""Simple configuration to add widgets to ScreenshotBar.""" | ||
|
||
fake_screens = [Screen(top=ScreenshotBar(config["widgets"], 50), width=400, height=400)] | ||
|
||
def screenshot(): | ||
"""Convenience function to call screenshot.""" | ||
manager_nospawn.c.bar["top"].take_screenshot(output_file) | ||
|
||
def assert_similar(): | ||
"""Check if generated images are similar to reference images.""" | ||
# If we're generating images then we don't need to compare them | ||
if generate: | ||
return | ||
|
||
# Build filepath to reference image | ||
reference = RESOURCES / f"{name}.png" | ||
|
||
# Command line | ||
cmd = ["compare", "-metric", "MSE", output_file, reference.resolve().as_posix(), "null:"] | ||
proc = subprocess.run(cmd, capture_output=True) | ||
|
||
# Check if our regex pattern can find a match in the output text | ||
search = COMPARE_RESULT.search(proc.stderr.decode()) | ||
if not search: | ||
assert False, proc.stderr.decode() | ||
|
||
# There's a match so let's convert to a float | ||
diff = float(search.group(1)) | ||
|
||
# Verify that difference is within acceptable tolerance | ||
assert diff < TOLERANCE | ||
|
||
# Start the manager and map convenience functions | ||
manager_nospawn.start(DecorationConfig) | ||
manager_nospawn.take_screenshot = screenshot | ||
manager_nospawn.assert_similar = assert_similar | ||
|
||
yield manager_nospawn |
Oops, something went wrong.