[Concept,7/9] test: Add a flag to emit per-test result lines

Message ID 20251229160611.3899708-8-sjg@u-boot.org
State New
Headers
Series test: Various improvements to unit-test infrastructure |

Commit Message

Simon Glass Dec. 29, 2025, 4:06 p.m. UTC
  From: Simon Glass <simon.glass@canonical.com>

The ut command shows test output but does not provide a machine-readable
indication of whether each individual test passed or failed. External
tools must rely on heuristics like scanning for failure patterns in the
output.

Add a -E flag that emits an explicit result line after each test:
  Result: PASS: test_name: file.c
  Result: FAIL: test_name: file.c

This allows tools to reliably determine per-test pass/fail status
without fragile pattern matching. The flag is optional to maintain
backward compatibility with existing scripts.

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

 doc/usage/cmd/ut.rst | 7 ++++++-
 include/test/test.h  | 2 ++
 test/cmd_ut.c        | 8 +++++++-
 test/test-main.c     | 9 +++++++++
 4 files changed, 24 insertions(+), 2 deletions(-)
  

Patch

diff --git a/doc/usage/cmd/ut.rst b/doc/usage/cmd/ut.rst
index 07cd970da6c..c40928dece9 100644
--- a/doc/usage/cmd/ut.rst
+++ b/doc/usage/cmd/ut.rst
@@ -11,7 +11,7 @@  Synopsis
 
 ::
 
-    ut [-fmr<runs>] [-R] [-I<n>:<one_test>] [<suite> | all [<test>]] [<args>...]
+    ut [-Efmr<runs>] [-R] [-I<n>:<one_test>] [<suite> | all [<test>]] [<args>...]
     ut [-s] info
 
 Description
@@ -26,6 +26,11 @@  suite
 test
     Speciifes a particular test to run, within a suite, or all suites
 
+-E
+    Emit a result line after each test, in the format
+    `Result: PASS|FAIL|SKIP: <test_name>: <file>`. This is useful for
+    automated parsing of test results.
+
 -f, -m
     Force running of manual tests. Manual tests have the `_norun` suffix and
     are normally skipped because they require external setup (e.g., creating
diff --git a/include/test/test.h b/include/test/test.h
index 5ae90e39e00..74225a70e54 100644
--- a/include/test/test.h
+++ b/include/test/test.h
@@ -97,6 +97,7 @@  struct ut_arg {
  * @arg_count: Number of parsed arguments
  * @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
  * @priv: Private data for tests to use as needed
  */
 struct unit_test_state {
@@ -128,6 +129,7 @@  struct unit_test_state {
 	int arg_count;
 	bool arg_error;
 	bool keep_record;
+	bool emit_result;
 	char priv[UT_PRIV_SIZE];
 };
 
diff --git a/test/cmd_ut.c b/test/cmd_ut.c
index 37144242099..d6e591916ce 100644
--- a/test/cmd_ut.c
+++ b/test/cmd_ut.c
@@ -255,6 +255,7 @@  static int do_ut(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 	bool show_suites = false;
 	bool force_run = false;
 	bool keep_record = false;
+	bool emit_result = false;
 	int runs_per_text = 1;
 	struct suite *ste;
 	char *name;
@@ -269,6 +270,9 @@  static int do_ut(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 
 		for (str++; *str; str++) {
 			switch (*str) {
+			case 'E':
+				emit_result = true;
+				break;
 			case 'r':
 				runs_per_text = dectoul(str + 1, NULL);
 				goto next_arg;
@@ -299,6 +303,7 @@  next_arg:
 
 	ut_init_state(&uts);
 	uts.keep_record = keep_record;
+	uts.emit_result = emit_result;
 	name = argv[0];
 	select_name = cmd_arg1(argc, argv);
 
@@ -344,7 +349,8 @@  next_arg:
 }
 
 U_BOOT_LONGHELP(ut,
-	"[-fmrs] [-R] [-I<n>:<one_test>] <suite> [<test> [<args>...]] - run unit tests\n"
+	"[-Efmrs] [-R] [-I<n>:<one_test>] <suite> [<test> [<args>...]] - run unit tests\n"
+	"   -E         Emit result line after each test\n"
 	"   -r<runs>   Number of times to run each test\n"
 	"   -f/-m      Force 'manual' tests to run as well\n"
 	"   -I         Test to run after <n> other tests have run\n"
diff --git a/test/test-main.c b/test/test-main.c
index c9e164da678..2524a154186 100644
--- a/test/test-main.c
+++ b/test/test-main.c
@@ -624,6 +624,7 @@  static int ut_run_test(struct unit_test_state *uts, struct unit_test *test,
 {
 	const char *fname = strrchr(test->file, '/') + 1;
 	const char *note = "";
+	int old_fail_count;
 	int ret;
 
 	if ((test->flags & UTF_DM) && !uts->of_live)
@@ -639,6 +640,7 @@  static int ut_run_test(struct unit_test_state *uts, struct unit_test *test,
 	if (ret)
 		return ret;
 
+	old_fail_count = uts->cur.fail_count;
 	uts->arg_error = false;
 	ret = test->func(uts);
 	if (ret == -EAGAIN)
@@ -650,6 +652,13 @@  static int ut_run_test(struct unit_test_state *uts, struct unit_test *test,
 
 	ut_set_state(NULL);
 
+	if (uts->emit_result) {
+		bool passed = uts->cur.fail_count == old_fail_count;
+
+		printf("Result: %s: %s: %s%s\n", passed ? "PASS" : "FAIL",
+		       test_name, fname, note);
+	}
+
 	return 0;
 }