* [PATCH 0/7] test: Add framebuffer test
@ 2026-04-13 7:44 Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 1/7] ARM: multi_v8_defconfig: enable QEMU ramfb driver Ahmad Fatoum
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: Ahmad Fatoum @ 2026-04-13 7:44 UTC (permalink / raw)
To: barebox
QEMU allows taking screenshots of screen. Make use of this to enable
doing some rudimentary graphic tests.
Ahmad Fatoum (7):
ARM: multi_v8_defconfig: enable QEMU ramfb driver
test: enable VirtIO keyboard
commands: fbtest: add flush for single pattern
test: conftest: don't call .startswith on int
test: conftest: set -display none when non-interactive
test: conftest: add qemu feature
test: add framebuffer screenshot testing via QMP screendump
arch/arm/configs/multi_v8_defconfig | 1 +
commands/fbtest.c | 1 +
conftest.py | 37 +++++++-------
test/arm/virt@multi_v8_defconfig.yaml | 2 +
test/py/helper.py | 53 +++++++++++++++++++
test/py/test_fbtest.py | 74 +++++++++++++++++++++++++++
6 files changed, 149 insertions(+), 19 deletions(-)
create mode 100644 test/py/test_fbtest.py
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 1/7] ARM: multi_v8_defconfig: enable QEMU ramfb driver
2026-04-13 7:44 [PATCH 0/7] test: Add framebuffer test Ahmad Fatoum
@ 2026-04-13 7:44 ` Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 2/7] test: enable VirtIO keyboard Ahmad Fatoum
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Ahmad Fatoum @ 2026-04-13 7:44 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
Enable CONFIG_DRIVER_VIDEO_RAMFB so that barebox can drive the QEMU
ramfb display device. All dependencies (CONFIG_VIDEO,
CONFIG_QEMU_FW_CFG, CONFIG_FS_QEMU_FW_CFG) are already enabled.
This will allow framebuffer testing with --graphics on the QEMU Virt
platform later.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
arch/arm/configs/multi_v8_defconfig | 1 +
test/arm/virt@multi_v8_defconfig.yaml | 2 ++
2 files changed, 3 insertions(+)
diff --git a/arch/arm/configs/multi_v8_defconfig b/arch/arm/configs/multi_v8_defconfig
index a6cc49859620..d2f04d30a422 100644
--- a/arch/arm/configs/multi_v8_defconfig
+++ b/arch/arm/configs/multi_v8_defconfig
@@ -222,6 +222,7 @@ CONFIG_USB_GADGET_MASS_STORAGE=y
CONFIG_VIDEO=y
CONFIG_FRAMEBUFFER_CONSOLE=y
CONFIG_DRIVER_VIDEO_BOCHS_PCI=y
+CONFIG_DRIVER_VIDEO_RAMFB=y
CONFIG_SOUND=y
CONFIG_MCI=y
CONFIG_MCI_MMC_BOOT_PARTITIONS=y
diff --git a/test/arm/virt@multi_v8_defconfig.yaml b/test/arm/virt@multi_v8_defconfig.yaml
index 2e654359709e..4eb75da4610e 100644
--- a/test/arm/virt@multi_v8_defconfig.yaml
+++ b/test/arm/virt@multi_v8_defconfig.yaml
@@ -17,6 +17,8 @@ targets:
- network
- barebox-state
- testfs
+ env:
+ nv/dev.fb0.enable: 1
images:
barebox-qemu-virt.img: !template "$LG_BUILDDIR/images/barebox-qemu-virt.img"
imports:
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 2/7] test: enable VirtIO keyboard
2026-04-13 7:44 [PATCH 0/7] test: Add framebuffer test Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 1/7] ARM: multi_v8_defconfig: enable QEMU ramfb driver Ahmad Fatoum
@ 2026-04-13 7:44 ` Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 3/7] commands: fbtest: add flush for single pattern Ahmad Fatoum
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Ahmad Fatoum @ 2026-04-13 7:44 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
The QEMU ARM64 Virt machine has no input method by default.
As we already had VirtIO input for the web demo, enable it when
--graphic is enabled.
The barebox VirtIO input driver was ported from Linux and both support
only VirtIO v1 devices, so we need to explicitly set
virtio-mmio.force-legacy=false for this to work.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
conftest.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/conftest.py b/conftest.py
index b0d314f3f345..c27e7a9546a1 100644
--- a/conftest.py
+++ b/conftest.py
@@ -175,6 +175,7 @@ def strategy(request, target, pytestconfig): # noqa: max-complexity=30
if "virtio-mmio" in features:
virtio = "device"
+ strategy.append_qemu_args('-global virtio-mmio.force-legacy=false')
if "virtio-pci" in features:
virtio = "pci,disable-modern=off"
features.append("pci")
@@ -210,6 +211,7 @@ def strategy(request, target, pytestconfig): # noqa: max-complexity=30
graphics = '-device VGA'
elif virtio:
graphics = '-vga none -device ramfb'
+ graphics += f' -device virtio-keyboard-{virtio}'
else:
pytest.exit("--graphics unsupported for target\n", 1)
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 3/7] commands: fbtest: add flush for single pattern
2026-04-13 7:44 [PATCH 0/7] test: Add framebuffer test Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 1/7] ARM: multi_v8_defconfig: enable QEMU ramfb driver Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 2/7] test: enable VirtIO keyboard Ahmad Fatoum
@ 2026-04-13 7:44 ` Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 4/7] test: conftest: don't call .startswith on int Ahmad Fatoum
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Ahmad Fatoum @ 2026-04-13 7:44 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
The single-pattern else-branch calls gu_screen_blit() to copy the render
buffer to the framebuffer in-memory image, but never called fb_flush()
to make sure the image makes it to display RAM.
Add fb_flush(sc->info) after gu_screen_blit(), matching the cycling
path above.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
commands/fbtest.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/commands/fbtest.c b/commands/fbtest.c
index cee72b039371..40c97f72a442 100644
--- a/commands/fbtest.c
+++ b/commands/fbtest.c
@@ -297,6 +297,7 @@ static int do_fbtest(int argc, char *argv[])
} else {
pattern(sc, color);
gu_screen_blit(sc);
+ fb_flush(sc->info);
}
done:
fb_close(sc);
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 4/7] test: conftest: don't call .startswith on int
2026-04-13 7:44 [PATCH 0/7] test: Add framebuffer test Ahmad Fatoum
` (2 preceding siblings ...)
2026-04-13 7:44 ` [PATCH 3/7] commands: fbtest: add flush for single pattern Ahmad Fatoum
@ 2026-04-13 7:44 ` Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 5/7] test: conftest: set -display none when non-interactive Ahmad Fatoum
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Ahmad Fatoum @ 2026-04-13 7:44 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
The returned YAML value may be an integer if quotes are missing, which
will lead to an error when .startswith() is called on it.
Check that the value is a string first and while at it, combine the two
loops to reduce the duplicate code.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
conftest.py | 26 +++++++++-----------------
1 file changed, 9 insertions(+), 17 deletions(-)
diff --git a/conftest.py b/conftest.py
index c27e7a9546a1..6bf92f3afed7 100644
--- a/conftest.py
+++ b/conftest.py
@@ -226,25 +226,17 @@ def strategy(request, target, pytestconfig): # noqa: max-complexity=30
else:
pytest.exit("--blk unsupported for target\n", 1)
+ envopts = {}
+
for i, fw_cfg in enumerate(pytestconfig.option.qemu_fw_cfg):
+ value = fw_cfg.pop()
+ envpath = fw_cfg.pop() if fw_cfg else f"data/fw_cfg{i}"
+
+ envopts[envpath] = value
+
+ for envpath, value in (yaml_env | envopts).items():
if virtio:
- value = fw_cfg.pop()
- envpath = fw_cfg.pop() if fw_cfg else f"data/fw_cfg{i}"
-
- if value.startswith('@'):
- source = f"file='{value[1:]}'"
- else:
- source = f"string='{value}'"
-
- strategy.append_qemu_args(
- '-fw_cfg', f'name=opt/org.barebox.env/{envpath},{source}'
- )
- else:
- pytest.exit("--env unsupported for target\n", 1)
-
- for envpath, value in yaml_env.items():
- if virtio:
- if value.startswith('@'):
+ if isinstance(value, str) and value.startswith('@'):
source = f"file='{value[1:]}'"
else:
source = f"string='{value}'"
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 5/7] test: conftest: set -display none when non-interactive
2026-04-13 7:44 [PATCH 0/7] test: Add framebuffer test Ahmad Fatoum
` (3 preceding siblings ...)
2026-04-13 7:44 ` [PATCH 4/7] test: conftest: don't call .startswith on int Ahmad Fatoum
@ 2026-04-13 7:44 ` Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 6/7] test: conftest: add qemu feature Ahmad Fatoum
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Ahmad Fatoum @ 2026-04-13 7:44 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
When running QEMU with --graphic, but in a non graphical session,
running QEMU may fail because it fails to start e.g. the GTK window.
Allow headless tests for these configurations by adding -display none.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
conftest.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/conftest.py b/conftest.py
index 6bf92f3afed7..d02e5aff998f 100644
--- a/conftest.py
+++ b/conftest.py
@@ -215,6 +215,10 @@ def strategy(request, target, pytestconfig): # noqa: max-complexity=30
else:
pytest.exit("--graphics unsupported for target\n", 1)
+ if graphics is not None and \
+ pytestconfig.option.lg_initial_state != 'qemu_interactive':
+ graphics += ' -display none'
+
strategy.append_qemu_args(graphics)
for i, blk in enumerate(pytestconfig.option.qemu_block):
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 6/7] test: conftest: add qemu feature
2026-04-13 7:44 [PATCH 0/7] test: Add framebuffer test Ahmad Fatoum
` (4 preceding siblings ...)
2026-04-13 7:44 ` [PATCH 5/7] test: conftest: set -display none when non-interactive Ahmad Fatoum
@ 2026-04-13 7:44 ` Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 7/7] test: add framebuffer screenshot testing via QMP screendump Ahmad Fatoum
2026-04-22 8:01 ` [PATCH 0/7] test: Add framebuffer test Sascha Hauer
7 siblings, 0 replies; 9+ messages in thread
From: Ahmad Fatoum @ 2026-04-13 7:44 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
This will allow skipping tests that explicitly require qemu features,
like the incoming screendump in the grpahical tests.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
conftest.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/conftest.py b/conftest.py
index d02e5aff998f..72d2df792e6d 100644
--- a/conftest.py
+++ b/conftest.py
@@ -168,8 +168,9 @@ def strategy(request, target, pytestconfig): # noqa: max-complexity=30
try:
main = target.env.config.data["targets"]["main"]
qemu_bin = main["drivers"]["QEMUDriver"]["qemu_bin"]
+ features.append("qemu")
except KeyError:
- qemu_bin = None
+ pass
virtio = None
@@ -202,7 +203,7 @@ def strategy(request, target, pytestconfig): # noqa: max-complexity=30
else:
pytest.exit("barebox currently supports only a single extra virtio console\n", 1)
- if qemu_bin is not None:
+ if "qemu" in features:
if not pytestconfig.option.qemu_graphics:
graphics = '-nographic'
elif qemu_bin == "qemu-system-x86_64":
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 7/7] test: add framebuffer screenshot testing via QMP screendump
2026-04-13 7:44 [PATCH 0/7] test: Add framebuffer test Ahmad Fatoum
` (5 preceding siblings ...)
2026-04-13 7:44 ` [PATCH 6/7] test: conftest: add qemu feature Ahmad Fatoum
@ 2026-04-13 7:44 ` Ahmad Fatoum
2026-04-22 8:01 ` [PATCH 0/7] test: Add framebuffer test Sascha Hauer
7 siblings, 0 replies; 9+ messages in thread
From: Ahmad Fatoum @ 2026-04-13 7:44 UTC (permalink / raw)
To: barebox; +Cc: Claude Opus 4.6 (1M context), Ahmad Fatoum
Add screendump() and parse_ppm() helpers to capture and parse QEMU
framebuffer screenshots using the QMP screendump command. Use them in a
new test that draws a solid color with fbtest and verifies the pixels
match. The test auto-skips when no framebuffer is available (e.g. without
--graphics).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
test/py/helper.py | 53 ++++++++++++++++++++++++++++++
test/py/test_fbtest.py | 74 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 127 insertions(+)
create mode 100644 test/py/test_fbtest.py
diff --git a/test/py/helper.py b/test/py/helper.py
index ab615280048f..2e3bc489d88a 100644
--- a/test/py/helper.py
+++ b/test/py/helper.py
@@ -6,6 +6,7 @@ import os
import re
import shlex
import subprocess
+import tempfile
def parse_config(lines):
@@ -200,6 +201,58 @@ def skip_disabled(config, *options):
pytest.skip("skipping test due to disabled " + (",".join(undefined)) + " dependency")
+def parse_ppm(path):
+ """Parse a PPM P6 (binary) image file.
+
+ Returns (width, height, data) where data is bytes of RGB pixel values.
+ Pixel (x, y) starts at offset (y * width + x) * 3.
+ """
+ with open(path, 'rb') as f:
+ magic = f.readline().strip()
+ assert magic == b'P6', f"Expected P6, got {magic}"
+
+ line = f.readline()
+ while line.startswith(b'#'):
+ line = f.readline()
+
+ width, height = map(int, line.split())
+ maxval = int(f.readline().strip())
+ assert maxval == 255
+
+ data = f.read()
+ expected = width * height * 3
+ assert len(data) == expected, \
+ f"Expected {expected} bytes, got {len(data)}"
+
+ return width, height, data
+
+
+def screendump(qemu, path=None):
+ """Capture a QEMU framebuffer screenshot via QMP screendump.
+
+ Args:
+ qemu: A labgrid QEMUDriver instance.
+ path: Optional host path for the PPM file. If None, a temp file is used.
+
+ Returns:
+ (width, height, data) tuple from parse_ppm().
+ """
+ if qemu is None:
+ pytest.skip("screendump requires a QEMU target")
+
+ cleanup = path is None
+ if path is None:
+ fd, path = tempfile.mkstemp(suffix='.ppm')
+ os.close(fd)
+
+ try:
+ qemu.monitor_command('screendump', {'filename': path})
+ return parse_ppm(path)
+ finally:
+ if cleanup:
+ os.unlink(path)
+
+
def ensure_debian_iso(env, destdir):
"""
Extract Debian kernel and initrd from ISO into destdir.
diff --git a/test/py/test_fbtest.py b/test/py/test_fbtest.py
new file mode 100644
index 000000000000..58b70d7dc4ab
--- /dev/null
+++ b/test/py/test_fbtest.py
@@ -0,0 +1,74 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+import hashlib
+import pytest
+from .helper import skip_disabled, screendump
+
+
+@pytest.fixture(autouse=True)
+def check_fbtest_in_qemu(barebox, env, barebox_config):
+ skip_disabled(barebox_config, "CONFIG_CMD_FBTEST")
+
+ if 'qemu' not in env.get_target_features():
+ pytest.skip("fbtest tests only possible with QEMU")
+
+ _, _, ret = barebox.run("test -e /dev/fb0")
+ if ret != 0:
+ pytest.skip("no framebuffer device available")
+
+
+def assert_solid_color(data, width, height, color):
+ """Verify that sampled pixels in the lower half match the expected color."""
+ sample_points = [
+ (width // 2, height * 3 // 4),
+ (width // 4, height * 3 // 4),
+ (width * 3 // 4, height * 3 // 4),
+ (width // 2, height - 2),
+ ]
+
+ r_exp = (color >> 16) & 0xff
+ g_exp = (color >> 8) & 0xff
+ b_exp = color & 0xff
+
+ for x, y in sample_points:
+ off = (y * width + x) * 3
+ r, g, b = data[off], data[off + 1], data[off + 2]
+ assert abs(r - r_exp) < 10, f"pixel ({x},{y}): R={r}, expected {r_exp}"
+ assert abs(g - g_exp) < 10, f"pixel ({x},{y}): G={g}, expected {g_exp}"
+ assert abs(b - b_exp) < 10, f"pixel ({x},{y}): B={b}, expected {b_exp}"
+
+
+def test_fb_solid_color(barebox, barebox_config, strategy):
+ color = 0xff0000
+ barebox.run_check(f"fbtest -p solid -c {color:06x}")
+
+ width, height, data = screendump(strategy.qemu)
+ assert_solid_color(data, width, height, color)
+
+
+def screendump_hash(qemu):
+ """Capture a screenshot and return a hash of the pixel data."""
+ _, _, data = screendump(qemu)
+ return hashlib.sha256(data).hexdigest()
+
+
+def test_fb_patterns_distinct_and_stable(barebox, barebox_config, strategy):
+ patterns = ["solid", "geometry", "bars", "gradient"]
+
+ # Render each pattern twice and collect hashes
+ hashes = {p: [] for p in patterns}
+
+ for run in range(2):
+ for pattern in patterns:
+ barebox.run_check(f"fbtest -p {pattern} -c ffffff")
+ hashes[pattern].append(screendump_hash(strategy.qemu))
+
+ # Same pattern must produce the same output across runs
+ for pattern in patterns:
+ assert hashes[pattern][0] == hashes[pattern][1], \
+ f"pattern '{pattern}' produced different output across runs"
+
+ # Different patterns must produce different output
+ unique = set(hashes[p][0] for p in patterns)
+ assert len(unique) == len(patterns), \
+ f"expected {len(patterns)} distinct patterns, got {len(unique)}"
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 0/7] test: Add framebuffer test
2026-04-13 7:44 [PATCH 0/7] test: Add framebuffer test Ahmad Fatoum
` (6 preceding siblings ...)
2026-04-13 7:44 ` [PATCH 7/7] test: add framebuffer screenshot testing via QMP screendump Ahmad Fatoum
@ 2026-04-22 8:01 ` Sascha Hauer
7 siblings, 0 replies; 9+ messages in thread
From: Sascha Hauer @ 2026-04-22 8:01 UTC (permalink / raw)
To: barebox, Ahmad Fatoum
On Mon, 13 Apr 2026 09:44:43 +0200, Ahmad Fatoum wrote:
> QEMU allows taking screenshots of screen. Make use of this to enable
> doing some rudimentary graphic tests.
>
> Ahmad Fatoum (7):
> ARM: multi_v8_defconfig: enable QEMU ramfb driver
> test: enable VirtIO keyboard
> commands: fbtest: add flush for single pattern
> test: conftest: don't call .startswith on int
> test: conftest: set -display none when non-interactive
> test: conftest: add qemu feature
> test: add framebuffer screenshot testing via QMP screendump
>
> [...]
Applied, thanks!
[1/7] ARM: multi_v8_defconfig: enable QEMU ramfb driver
https://git.pengutronix.de/cgit/barebox/commit/?id=1e1b3b66677a (link may not be stable)
[2/7] test: enable VirtIO keyboard
https://git.pengutronix.de/cgit/barebox/commit/?id=50c2aabc574b (link may not be stable)
[3/7] commands: fbtest: add flush for single pattern
https://git.pengutronix.de/cgit/barebox/commit/?id=af2a6133e535 (link may not be stable)
[4/7] test: conftest: don't call .startswith on int
https://git.pengutronix.de/cgit/barebox/commit/?id=80554888f677 (link may not be stable)
[5/7] test: conftest: set -display none when non-interactive
https://git.pengutronix.de/cgit/barebox/commit/?id=74693d62342f (link may not be stable)
[6/7] test: conftest: add qemu feature
https://git.pengutronix.de/cgit/barebox/commit/?id=af5b226aa085 (link may not be stable)
[7/7] test: add framebuffer screenshot testing via QMP screendump
https://git.pengutronix.de/cgit/barebox/commit/?id=3fb451e0110a (link may not be stable)
Best regards,
--
Sascha Hauer <s.hauer@pengutronix.de>
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-04-22 8:02 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-04-13 7:44 [PATCH 0/7] test: Add framebuffer test Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 1/7] ARM: multi_v8_defconfig: enable QEMU ramfb driver Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 2/7] test: enable VirtIO keyboard Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 3/7] commands: fbtest: add flush for single pattern Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 4/7] test: conftest: don't call .startswith on int Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 5/7] test: conftest: set -display none when non-interactive Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 6/7] test: conftest: add qemu feature Ahmad Fatoum
2026-04-13 7:44 ` [PATCH 7/7] test: add framebuffer screenshot testing via QMP screendump Ahmad Fatoum
2026-04-22 8:01 ` [PATCH 0/7] test: Add framebuffer test Sascha Hauer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox