[Concept,05/19] test: Display message at top right during video test delay

Message ID 20260130035849.3580212-6-simon.glass@canonical.com
State New
Headers
Series Enhanced command-line editing with undo/redo support |

Commit Message

Simon Glass Jan. 30, 2026, 3:58 a.m. UTC
  When a message is passed to video_compress_fb_() and a delay is
active (via -V flag or LOG_DEBUG), display the message at the top
right of the screen. The affected framebuffer region is saved before
drawing and restored afterwards, so the message is only visible
during the delay.

Add save_video() and restore_video() functions in test/dm/video.c to
handle saving and restoring framebuffer regions. The save buffer is
allocated on first use and stored in uts->video_save, then freed
in ut_uninit_state().

Guard the message display with CONFIG_UNIT_TEST since the unit_test_state
structure is only available when unit tests are enabled.

This helps identify which frame check is being displayed when
debugging video tests.

Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: Simon Glass <simon.glass@canonical.com>
---

 doc/develop/expo.rst |   7 +++
 include/test/test.h  |   5 ++
 test/dm/video.c      | 123 ++++++++++++++++++++++++++++++++++++++++---
 test/test-main.c     |   2 +
 4 files changed, 130 insertions(+), 7 deletions(-)
  

Patch

diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst
index 7ef714be3da..71e227c532d 100644
--- a/doc/develop/expo.rst
+++ b/doc/develop/expo.rst
@@ -824,6 +824,13 @@  For example, to watch an expo test render with a visible display::
 
     ./u-boot -T -l -V 500 --video_frames /tmp/good -c "ut bootstd expo_render_image"
 
+When using ``-V`` or with ``LOG_DEBUG`` enabled, some video tests call
+ut_check_video() to display a message at the top right corner of the screen
+identifying the current frame check. This helps identify which assertion is
+being displayed when debugging test failures. The message is automatically
+removed after the delay, so it does not affect the framebuffer checksums used by
+video tests.
+
 The :doc:`../usage/cmd/sb` ``grid`` subcommand can be used to overlay a grid on
 the display, to help with checking alignment of objects. The grid size defaults
 to 0x20 pixels but can be specified as a parameter.
diff --git a/include/test/test.h b/include/test/test.h
index 56e25f6fa9d..c3b251e2cd4 100644
--- a/include/test/test.h
+++ b/include/test/test.h
@@ -6,6 +6,7 @@ 
 #ifndef __TEST_TEST_H
 #define __TEST_TEST_H
 
+#include <abuf.h>
 #include <malloc.h>
 #include <linux/bitops.h>
 
@@ -100,6 +101,8 @@  struct ut_arg {
  * @arg_error: Set if ut_str/int/bool() detects a type mismatch
  * @keep_record: Preserve console recording when ut_fail() is called
  * @emit_result: Emit result line after each test completes
+ * @video_ctx: Vidconsole context for test message display (allocated on use)
+ * @video_save: Saved framebuffer region for video tests
  * @priv: Private data for tests to use as needed
  */
 struct unit_test_state {
@@ -134,6 +137,8 @@  struct unit_test_state {
 	bool arg_error;
 	bool keep_record;
 	bool emit_result;
+	void *video_ctx;
+	struct abuf video_save;
 	char priv[UT_PRIV_SIZE];
 };
 
diff --git a/test/dm/video.c b/test/dm/video.c
index 421d50df064..f97e2183a64 100644
--- a/test/dm/video.c
+++ b/test/dm/video.c
@@ -138,8 +138,106 @@  static int video_write_bmp(struct unit_test_state *uts, struct udevice *dev,
 	return ret;
 }
 
-static int video_compress_fb_(struct unit_test_state *uts, struct udevice *dev,
-			      bool use_copy, const char *msg)
+/**
+ * save_video() - Save a portion of the framebuffer
+ *
+ * Saves the top portion of the framebuffer so it can be restored later.
+ * The buffer is allocated on first use and stored in uts->video_save.
+ *
+ * @uts: Unit test state
+ * @dev: Video device
+ * @lines: Number of lines to save
+ * Return: 0 on success, -ve on error
+ */
+static int save_video(struct unit_test_state *uts, struct udevice *dev,
+		      uint lines)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+	int size;
+
+	size = priv->line_length * lines;
+	if (size > priv->fb_size)
+		return -EINVAL;
+
+	/* Allocate/resize the save buffer to exact size needed */
+	if (!abuf_realloc(&uts->video_save, size))
+		return -ENOMEM;
+
+	memcpy(abuf_data(&uts->video_save), priv->fb, size);
+
+	return 0;
+}
+
+/**
+ * restore_video() - Restore a saved portion of the framebuffer
+ *
+ * Restores the framebuffer region previously saved by save_video().
+ *
+ * @uts: Unit test state
+ * @dev: Video device
+ * Return: 0 on success, -ve on error
+ */
+static int restore_video(struct unit_test_state *uts, struct udevice *dev)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+
+	if (!abuf_size(&uts->video_save))
+		return -ENOENT;
+
+	memcpy(priv->fb, abuf_data(&uts->video_save),
+	       abuf_size(&uts->video_save));
+
+	return 0;
+}
+
+/**
+ * show_test_msg() - Display a test message at the top right of the screen
+ *
+ * @uts: Unit test state
+ * @dev: Video device
+ * @msg: Message to display
+ * Return: true if framebuffer was saved and needs restoring, false otherwise
+ */
+static bool show_test_msg(struct unit_test_state *uts, struct udevice *dev,
+			  const char *msg)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+	struct vidconsole_ctx *ctx = uts->video_ctx;
+	struct udevice *con;
+	bool saved = false;
+	int len = strlen(msg);
+	int x, ret;
+
+	ret = uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &con);
+	if (ret)
+		return false;
+
+	/* Allocate context on first use and select 8x16 font */
+	if (!ctx) {
+		ret = vidconsole_ctx_new(con, (void **)&ctx);
+		if (ret)
+			return false;
+		uts->video_ctx = ctx;
+		vidconsole_select_font(con, ctx, "8x16", 0);
+	}
+
+	/* Calculate position at top right */
+	x = priv->xsize - (len + 2) * ctx->x_charsize;
+
+	/* Save the affected region (one line) */
+	if (!save_video(uts, dev, ctx->y_charsize))
+		saved = true;
+
+	/* Draw the message */
+	vidconsole_set_cursor_pos(con, ctx, x, 0);
+	vidconsole_put_string(con, ctx, msg);
+	video_manual_sync(dev, VIDSYNC_COPY | VIDSYNC_FLUSH);
+
+	return saved;
+}
+
+int video_compress_fb_(struct unit_test_state *uts, struct udevice *dev,
+		       bool use_copy, const char *msg)
 {
 	struct sandbox_state *state = state_get_current();
 	struct video_priv *priv = dev_get_uclass_priv(dev);
@@ -176,10 +274,21 @@  static int video_compress_fb_(struct unit_test_state *uts, struct udevice *dev,
 	}
 
 	/* provide a useful delay if -V flag is used or LOG_DEBUG is set */
-	if (state->video_test)
-		mdelay(state->video_test);
-	else if (_DEBUG)
-		mdelay(300);
+	if (state->video_test || _DEBUG) {
+		int delay = state->video_test ? state->video_test : 300;
+		bool saved = false;
+
+		if (msg)
+			saved = show_test_msg(uts, dev, msg);
+
+		mdelay(delay);
+
+		/* Restore the framebuffer region */
+		if (saved) {
+			restore_video(uts, dev);
+			video_manual_sync(dev, VIDSYNC_COPY | VIDSYNC_FLUSH);
+		}
+	}
 
 	return destlen;
 }
@@ -213,7 +322,7 @@  int ut_check_video(struct unit_test_state *uts, const char *msg)
 	if (ret)
 		return ret;
 
-	return video_compress_fb(uts, dev, false);
+	return video_compress_fb_(uts, dev, false, msg);
 }
 
 /*
diff --git a/test/test-main.c b/test/test-main.c
index a2c4e32423b..5db35b59760 100644
--- a/test/test-main.c
+++ b/test/test-main.c
@@ -87,6 +87,8 @@  void ut_uninit_state(struct unit_test_state *uts)
 		os_free(uts->fdt_copy);
 		os_free(uts->other_fdt);
 	}
+	/* video_ctx is freed when the vidconsole is unbound */
+	abuf_uninit(&uts->video_save);
 }
 
 /**