Cover silent and nested configfiles, output after redirection, active and 
inactive EFI terminals, empty output, explicit clears, and the verbose sleep 
cursor ordering.

Signed-off-by: Sean Rhodes <[email protected]>
---
 grub-core/Makefile.core.def           |   5 +
 grub-core/tests/deferred_clear_test.c | 317 ++++++++++++++++++++++++++
 grub-core/tests/lib/functional_test.c |   1 +
 3 files changed, 323 insertions(+)
 create mode 100644 grub-core/tests/deferred_clear_test.c

diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 1a91e53..6cead25 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -2258,6 +2258,11 @@ module = {
   common = tests/sleep_test.c;
 };
 
+module = {
+  name = deferred_clear_test;
+  common = tests/deferred_clear_test.c;
+};
+
 module = {
   name = xnu_uuid_test;
   common = tests/xnu_uuid_test.c;
diff --git a/grub-core/tests/deferred_clear_test.c 
b/grub-core/tests/deferred_clear_test.c
new file mode 100644
index 0000000..1e6baa5
--- /dev/null
+++ b/grub-core/tests/deferred_clear_test.c
@@ -0,0 +1,317 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2026 Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/command.h>
+#include <grub/dl.h>
+#include <grub/misc.h>
+#include <grub/normal.h>
+#include <grub/procfs.h>
+#include <grub/test.h>
+#include <grub/term.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static const char silent_config[] = "true\n";
+static const char nested_config[] =
+  "configfile (proc)/deferred-clear-silent.cfg\n";
+static unsigned int silent_config_reads;
+static unsigned int nested_config_reads;
+
+static char *
+get_silent_config (grub_size_t *size)
+{
+  silent_config_reads++;
+  *size = sizeof (silent_config) - 1;
+  return grub_strdup (silent_config);
+}
+
+static char *
+get_nested_config (grub_size_t *size)
+{
+  nested_config_reads++;
+  *size = sizeof (nested_config) - 1;
+  return grub_strdup (nested_config);
+}
+
+static struct grub_procfs_entry silent_config_entry =
+  {
+    .name = "deferred-clear-silent.cfg",
+    .get_contents = get_silent_config
+  };
+
+static struct grub_procfs_entry nested_config_entry =
+  {
+    .name = "deferred-clear-nested.cfg",
+    .get_contents = get_nested_config
+  };
+
+struct fake_state
+{
+  struct grub_term_coordinate pos;
+  unsigned int chars;
+  unsigned int clears;
+  unsigned int chars_before_clear;
+  struct grub_term_coordinate first_gotoxy;
+  int have_gotoxy;
+  int gotoxy_changed;
+};
+
+static void
+fake_clear (struct grub_term_output *term)
+{
+  struct fake_state *state = term->data;
+
+  if (state->clears == 0)
+    state->chars_before_clear = state->chars;
+  state->clears++;
+  state->chars = 0;
+  state->pos = (struct grub_term_coordinate) { 0, 0 };
+}
+
+static grub_err_t
+fake_prepare (struct grub_term_output *term)
+{
+  if (term->flags & GRUB_TERM_OUTPUT_ACTIVE)
+    return GRUB_ERR_NONE;
+
+  term->flags |= GRUB_TERM_OUTPUT_ACTIVE;
+  if (term->flags & GRUB_TERM_CLEAR_PENDING)
+    {
+      term->flags &= ~GRUB_TERM_CLEAR_PENDING;
+      fake_clear (term);
+    }
+
+  return GRUB_ERR_NONE;
+}
+
+static void
+fake_putchar (struct grub_term_output *term,
+             const struct grub_unicode_glyph *c)
+{
+  struct fake_state *state = term->data;
+
+  fake_prepare (term);
+  state->chars++;
+  if (c->base == '\n')
+    {
+      state->pos.x = 0;
+      state->pos.y++;
+    }
+  else
+    state->pos.x++;
+}
+
+static struct grub_term_coordinate
+fake_getwh (struct grub_term_output *term)
+{
+  fake_prepare (term);
+  return (struct grub_term_coordinate) { 80, 25 };
+}
+
+static struct grub_term_coordinate
+fake_getxy (struct grub_term_output *term)
+{
+  struct fake_state *state = term->data;
+
+  return state->pos;
+}
+
+static void
+fake_gotoxy (struct grub_term_output *term, struct grub_term_coordinate pos)
+{
+  struct fake_state *state = term->data;
+
+  fake_prepare (term);
+  if (!state->have_gotoxy)
+    {
+      state->first_gotoxy = pos;
+      state->have_gotoxy = 1;
+    }
+  else if (state->first_gotoxy.x != pos.x || state->first_gotoxy.y != pos.y)
+    state->gotoxy_changed = 1;
+  state->pos = pos;
+}
+
+static void
+fake_cls (struct grub_term_output *term)
+{
+  term->flags &= ~GRUB_TERM_CLEAR_PENDING;
+  fake_prepare (term);
+  fake_clear (term);
+}
+
+static void
+reset_term (struct grub_term_output *term, struct fake_state *state,
+           int active)
+{
+  grub_memset (state, 0, sizeof (*state));
+  state->pos = (struct grub_term_coordinate) { 7, 4 };
+  term->flags &= ~(GRUB_TERM_OUTPUT_ACTIVE | GRUB_TERM_CLEAR_PENDING);
+  if (active)
+    term->flags |= GRUB_TERM_OUTPUT_ACTIVE;
+}
+
+static void
+run_command (const char *name, int argc, char **argv)
+{
+  grub_command_t cmd = grub_command_find (name);
+
+  grub_test_assert (cmd != NULL, "%s command is unavailable", name);
+  if (cmd)
+    grub_command_execute (name, argc, argv);
+}
+
+static void
+deferred_clear_test (void)
+{
+  struct grub_term_output *saved_outputs = grub_term_outputs;
+  struct fake_state first_state, second_state;
+  struct grub_term_output second = {
+    .name = "deferred-clear-second",
+    .prepare = fake_prepare,
+    .putchar = fake_putchar,
+    .getwh = fake_getwh,
+    .getxy = fake_getxy,
+    .gotoxy = fake_gotoxy,
+    .cls = fake_cls,
+    .data = &second_state
+  };
+  struct grub_term_output first = {
+    .next = &second,
+    .name = "deferred-clear-first",
+    .prepare = fake_prepare,
+    .putchar = fake_putchar,
+    .getwh = fake_getwh,
+    .getxy = fake_getxy,
+    .gotoxy = fake_gotoxy,
+    .cls = fake_cls,
+    .data = &first_state
+  };
+  char sleep_verbose_option[] = "--verbose";
+  char sleep_value[] = "1";
+  char echo_no_newline[] = "-n";
+  char echo_value[] = "";
+  char *sleep_verbose[] = { sleep_verbose_option, sleep_value };
+  char *sleep_quiet[] = { sleep_value };
+  char *echo_empty[] = { echo_no_newline, echo_value };
+  char config_path[] = "(proc)/deferred-clear-silent.cfg";
+  char nested_config_path[] = "(proc)/deferred-clear-nested.cfg";
+  char *config[] = { config_path };
+  char *nested[] = { nested_config_path };
+
+  grub_procfs_register ("deferred-clear-silent.cfg", &silent_config_entry);
+  grub_procfs_register ("deferred-clear-nested.cfg", &nested_config_entry);
+  grub_dl_load ("configfile");
+  grub_term_outputs = &first;
+
+  reset_term (&first, &first_state, 0);
+  reset_term (&second, &second_state, 0);
+  grub_cls_deferred ();
+  run_command ("help", 0, NULL);
+  grub_test_assert (first_state.clears == 1,
+                   "help did not prepare the first terminal");
+  grub_test_assert (first_state.chars_before_clear == 0,
+                   "help output was cleared after rendering started");
+  grub_test_assert (second_state.clears == 1 && second_state.chars > 0 &&
+                   second_state.chars_before_clear == 0,
+                   "help output was cleared late on the second terminal");
+
+  reset_term (&first, &first_state, 0);
+  reset_term (&second, &second_state, 1);
+  silent_config_reads = 0;
+  run_command ("configfile", ARRAY_SIZE (config), config);
+  grub_test_assert (silent_config_reads == 1,
+                   "configfile did not read its target configuration");
+  grub_test_assert (first_state.clears == 0 && first_state.chars == 0 &&
+                   (first.flags & GRUB_TERM_CLEAR_PENDING),
+                   "silent configfile cleared the untouched terminal");
+  grub_test_assert (second_state.clears == 1 && second_state.chars == 0,
+                   "silent configfile did not clear the active terminal");
+  grub_xnputs ("visible", sizeof ("visible") - 1);
+  grub_test_assert (first_state.clears == 1 &&
+                   first_state.chars_before_clear == 0 &&
+                   first_state.chars == sizeof ("visible") - 1,
+                   "configfile clear did not precede later output");
+  grub_test_assert (second_state.clears == 1 &&
+                   second_state.chars == sizeof ("visible") - 1,
+                   "configfile cleared the active terminal again before 
output");
+
+  reset_term (&first, &first_state, 0);
+  reset_term (&second, &second_state, 0);
+  silent_config_reads = 0;
+  nested_config_reads = 0;
+  run_command ("configfile", ARRAY_SIZE (nested), nested);
+  grub_test_assert (nested_config_reads == 1 && silent_config_reads == 1,
+                   "nested configfile redirect did not read both 
configurations");
+  grub_test_assert (first_state.clears == 0 && first_state.chars == 0 &&
+                   (first.flags & GRUB_TERM_CLEAR_PENDING),
+                   "nested silent configfile cleared the untouched terminal");
+
+  reset_term (&first, &first_state, 0);
+  reset_term (&second, &second_state, 0);
+  grub_cls_deferred ();
+  grub_xnputs ("direct", sizeof ("direct") - 1);
+  grub_printf (" formatted\n");
+  grub_test_assert (first_state.clears == 1 &&
+                   first_state.chars_before_clear == 0 &&
+                   first_state.chars > sizeof ("direct") - 1,
+                   "formatted output erased direct output");
+
+  reset_term (&first, &first_state, 0);
+  reset_term (&second, &second_state, 0);
+  grub_cls_deferred ();
+  run_command ("sleep", ARRAY_SIZE (sleep_verbose), sleep_verbose);
+  grub_test_assert (first_state.clears == 1 &&
+                   first_state.chars_before_clear == 0,
+                   "verbose sleep prepared the display too late");
+  grub_test_assert (!first_state.gotoxy_changed,
+                   "verbose sleep restored different cursor positions");
+  grub_test_assert (first_state.have_gotoxy &&
+                   first_state.first_gotoxy.x == 0 &&
+                   first_state.first_gotoxy.y == 0 &&
+                   second_state.have_gotoxy &&
+                   second_state.first_gotoxy.x == 0 &&
+                   second_state.first_gotoxy.y == 0,
+                   "verbose sleep saved its cursor position before 
preparation");
+
+  reset_term (&first, &first_state, 0);
+  reset_term (&second, &second_state, 0);
+  grub_cls_deferred ();
+  run_command ("sleep", ARRAY_SIZE (sleep_quiet), sleep_quiet);
+  grub_test_assert (first_state.clears == 0 &&
+                   (first.flags & GRUB_TERM_CLEAR_PENDING),
+                   "non-verbose sleep prepared the display");
+  run_command ("echo", ARRAY_SIZE (echo_empty), echo_empty);
+  grub_test_assert (first_state.clears == 0 && first_state.chars == 0,
+                   "empty echo prepared the display (%u clears, %u chars)",
+                   first_state.clears, first_state.chars);
+
+  reset_term (&first, &first_state, 0);
+  reset_term (&second, &second_state, 0);
+  grub_cls_deferred ();
+  grub_cls ();
+  grub_test_assert (first_state.clears == 1 &&
+                   !(first.flags & GRUB_TERM_CLEAR_PENDING),
+                   "explicit clear did not replace the deferred clear");
+
+  grub_procfs_unregister (&nested_config_entry);
+  grub_procfs_unregister (&silent_config_entry);
+  grub_term_outputs = saved_outputs;
+}
+
+GRUB_FUNCTIONAL_TEST (deferred_clear_test, deferred_clear_test);
diff --git a/grub-core/tests/lib/functional_test.c 
b/grub-core/tests/lib/functional_test.c
index 4214332..8429eff 100644
--- a/grub-core/tests/lib/functional_test.c
+++ b/grub-core/tests/lib/functional_test.c
@@ -75,6 +75,7 @@ grub_functional_all_tests (grub_extcmd_context_t ctxt 
__attribute__ ((unused)),
   grub_dl_load ("signature_test");
   grub_dl_load ("appended_signature_test");
   grub_dl_load ("sleep_test");
+  grub_dl_load ("deferred_clear_test");
   grub_dl_load ("bswap_test");
   grub_dl_load ("ctz_test");
   grub_dl_load ("cmp_test");

_______________________________________________
Grub-devel mailing list
[email protected]
https://lists.gnu.org/mailman/listinfo/grub-devel

Reply via email to