diff mbox series

[RFC,7/7] selftests/nolibc: proof of concept for TAP output

Message ID 20230719-nolibc-ktap-tmp-v1-7-930bd0c52ff1@weissschuh.net
State New
Headers show
Series selftests/nolibc: KTAP output | expand

Commit Message

Thomas Weißschuh July 18, 2023, 10 p.m. UTC
Dirty proof of concept to show how (K)TAP output can look and how it can
be used.

Currently test selection is not supported and for simplicity only the
startup tests are enabled.

Example output:

$ ./nolibc-test
 KTAP version 1
 1..15
 ok 1 argc = 1
 ok 2 argv_addr = <0x7ffdc66173a8>
 ok 3 argv_environ = <0x7ffdc66173a8>
 ok 4 argv_total = 1
 ok 5 argv0_addr = <0x7ffdc6618bca>
 ok 6 argv0_str = <0x7ffdc6618bca>
 ok 7 argv0_len = 13
 ok 8 environ_addr = <0x7ffdc66173b8>
 ok 9 environ_envp = <0x7ffdc66173b8>
 ok 10 environ_auxv = <0x7ffdc66173b8>
 ok 11 environ_total = 271
 ok 12 environ_HOME = <0x7ffdc6618cc7>
 ok 13 auxv_addr = <0x7ffdc66174c8>
 ok 14 auxv_AT_UID = 1000
 ok 15 auxv_AT_PAGESZ = 4096
 # Exiting with status 0
 # Totals: pass:15 fail:0 xfail:0 xpass:0 skip:0 error:0

$ ./libc-test
 KTAP version 1
 1..15
 ok 1 argc = 1
 ok 2 argv_addr = <0x7ffd5f3d43e8>
 ok 3 argv_environ = <0x7ffd5f3d43e8>
 ok 4 argv_total = 1
 ok 5 argv0_addr = <0x7ffd5f3d5bd0>
 ok 6 argv0_str = <0x7ffd5f3d5bd0>
 ok 7 argv0_len = 11
 ok 8 environ_addr = <0x7ffd5f3d43f8>
 ok 9 environ_envp = <0x7ffd5f3d43f8>
 ok 10 environ_auxv # SKIP test_auxv != (void *)-1
 ok 11 environ_total # SKIP test_auxv != (void *)-1
 ok 12 environ_HOME = <0x7ffd5f3d5ccb>
 ok 13 auxv_addr # SKIP test_auxv != (void *)-1
 ok 14 auxv_AT_UID = 1000
 ok 15 auxv_AT_PAGESZ = 4096
 # Exiting with status 0
 # Totals: pass:12 fail:0 xfail:0 xpass:0 skip:3 error:0

./run-all-tests.sh | $SRC/tools/testing/kunit/kunit.py parse
 [23:47:26] ============================================================
 [23:47:26] ====================== (15 subtests) =======================
 [23:47:26] [PASSED] argc = 1
 [23:47:26] [PASSED] argv_addr = <0x7ffcac1b8bc8>
 [23:47:26] [PASSED] argv_environ = <0x7ffcac1b8bc8>
 [23:47:26] [PASSED] argv_total = 1
 [23:47:26] [PASSED] argv0_addr = <0x7ffcac1b9bd0>
 [23:47:26] [PASSED] argv0_str = <0x7ffcac1b9bd0>
 [23:47:26] [PASSED] argv0_len = 11
 [23:47:26] [PASSED] environ_addr = <0x7ffcac1b8bd8>
 [23:47:26] [PASSED] environ_envp = <0x7ffcac1b8bd8>
 [23:47:26] [SKIPPED] environ_auxv
 [23:47:26] [SKIPPED] environ_total
 [23:47:26] [PASSED] environ_HOME = <0x7ffcac1b9ccb>
 [23:47:26] [SKIPPED] auxv_addr
 [23:47:26] [PASSED] auxv_AT_UID = 1000
 [23:47:26] [PASSED] auxv_AT_PAGESZ = 4096
 [23:47:26] ====================== [PASSED] arm64 ======================
 [23:47:26] ====================== (15 subtests) =======================
 [23:47:26] [PASSED] argc = 1
 [23:47:26] [PASSED] argv_addr = <0x7ffdee178188>
 [23:47:26] [PASSED] argv_environ = <0x7ffdee178188>
 [23:47:26] [PASSED] argv_total = 1
 [23:47:26] [PASSED] argv0_addr = <0x7ffdee178bd0>
 [23:47:26] [PASSED] argv0_str = <0x7ffdee178bd0>
 [23:47:26] [PASSED] argv0_len = 11
 [23:47:26] [PASSED] environ_addr = <0x7ffdee178198>
 [23:47:26] [PASSED] environ_envp = <0x7ffdee178198>
 [23:47:26] [SKIPPED] environ_auxv
 [23:47:26] [SKIPPED] environ_total
 [23:47:26] [PASSED] environ_HOME = <0x7ffdee178ccb>
 [23:47:26] [SKIPPED] auxv_addr
 [23:47:26] [PASSED] auxv_AT_UID = 1000
 [23:47:26] [PASSED] auxv_AT_PAGESZ = 4096
 [23:47:26] ===================== [PASSED] x86_64 ======================
 [23:47:26] ====================== (15 subtests) =======================
 [23:47:26] [PASSED] argc = 1
 [23:47:26] [PASSED] argv_addr = <0x7ffc16bf3628>
 [23:47:26] [PASSED] argv_environ = <0x7ffc16bf3628>
 [23:47:26] [PASSED] argv_total = 1
 [23:47:26] [PASSED] argv0_addr = <0x7ffc16bf4bd0>
 [23:47:26] [PASSED] argv0_str = <0x7ffc16bf4bd0>
 [23:47:26] [PASSED] argv0_len = 11
 [23:47:26] [PASSED] environ_addr = <0x7ffc16bf3638>
 [23:47:26] [PASSED] environ_envp = <0x7ffc16bf3638>
 [23:47:26] [SKIPPED] environ_auxv
 [23:47:26] [SKIPPED] environ_total
 [23:47:26] [PASSED] environ_HOME = <0x7ffc16bf4ccb>
 [23:47:26] [SKIPPED] auxv_addr
 [23:47:26] [PASSED] auxv_AT_UID = 1000
 [23:47:26] [PASSED] auxv_AT_PAGESZ = 4096
 [23:47:26] ===================== [PASSED] riscv64 =====================
 [23:47:26] ============================================================
 [23:47:26] Testing complete. Ran 45 tests: passed: 36, skipped: 9

The output of kunit.py is colored after the test results.

Not-signed-off
---
 tools/testing/selftests/nolibc/nolibc-test.c    | 121 ++++++++----------------
 tools/testing/selftests/nolibc/run-all-tests.sh |  22 +++++
 2 files changed, 63 insertions(+), 80 deletions(-)

Comments

Zhangjin Wu July 20, 2023, 3:29 p.m. UTC | #1
Hi, Thomas

The result looks very similar and the change seems not that big, thanks.

I have a generic question: do we need to compile nolibc-test.c
independently or at least let users easily compile nolibc-test.c in the
other places no just in kernel source code, for example, the other libcs
may want to download and compile it directly.

The functions used in this change seems not many, is it able to provide
our clones for them or only provide the clones when we compile them
out-of-kernel.

for example:

    #ifdef NOLIBC_TEST_IN_KERNEL
    /* -DNOLIBC_TEST_IN_KERNEL from Makefile, for future compatibility */
    #include "../kselftest.h"
    #else
    // our clones of the used functions, for standalone usage
    #endif

Best regards,
Zhangjin

> Dirty proof of concept to show how (K)TAP output can look and how it can
> be used.
> 
> Currently test selection is not supported and for simplicity only the
> startup tests are enabled.
> 
> Example output:
> 
> $ ./nolibc-test
>  KTAP version 1
>  1..15
>  ok 1 argc = 1
>  ok 2 argv_addr = <0x7ffdc66173a8>
>  ok 3 argv_environ = <0x7ffdc66173a8>
>  ok 4 argv_total = 1
>  ok 5 argv0_addr = <0x7ffdc6618bca>
>  ok 6 argv0_str = <0x7ffdc6618bca>
>  ok 7 argv0_len = 13
>  ok 8 environ_addr = <0x7ffdc66173b8>
>  ok 9 environ_envp = <0x7ffdc66173b8>
>  ok 10 environ_auxv = <0x7ffdc66173b8>
>  ok 11 environ_total = 271
>  ok 12 environ_HOME = <0x7ffdc6618cc7>
>  ok 13 auxv_addr = <0x7ffdc66174c8>
>  ok 14 auxv_AT_UID = 1000
>  ok 15 auxv_AT_PAGESZ = 4096
>  # Exiting with status 0
>  # Totals: pass:15 fail:0 xfail:0 xpass:0 skip:0 error:0
> 
> $ ./libc-test
>  KTAP version 1
>  1..15
>  ok 1 argc = 1
>  ok 2 argv_addr = <0x7ffd5f3d43e8>
>  ok 3 argv_environ = <0x7ffd5f3d43e8>
>  ok 4 argv_total = 1
>  ok 5 argv0_addr = <0x7ffd5f3d5bd0>
>  ok 6 argv0_str = <0x7ffd5f3d5bd0>
>  ok 7 argv0_len = 11
>  ok 8 environ_addr = <0x7ffd5f3d43f8>
>  ok 9 environ_envp = <0x7ffd5f3d43f8>
>  ok 10 environ_auxv # SKIP test_auxv != (void *)-1
>  ok 11 environ_total # SKIP test_auxv != (void *)-1
>  ok 12 environ_HOME = <0x7ffd5f3d5ccb>
>  ok 13 auxv_addr # SKIP test_auxv != (void *)-1
>  ok 14 auxv_AT_UID = 1000
>  ok 15 auxv_AT_PAGESZ = 4096
>  # Exiting with status 0
>  # Totals: pass:12 fail:0 xfail:0 xpass:0 skip:3 error:0
> 
> ./run-all-tests.sh | $SRC/tools/testing/kunit/kunit.py parse
>  [23:47:26] ============================================================
>  [23:47:26] ====================== (15 subtests) =======================
>  [23:47:26] [PASSED] argc = 1
>  [23:47:26] [PASSED] argv_addr = <0x7ffcac1b8bc8>
>  [23:47:26] [PASSED] argv_environ = <0x7ffcac1b8bc8>
>  [23:47:26] [PASSED] argv_total = 1
>  [23:47:26] [PASSED] argv0_addr = <0x7ffcac1b9bd0>
>  [23:47:26] [PASSED] argv0_str = <0x7ffcac1b9bd0>
>  [23:47:26] [PASSED] argv0_len = 11
>  [23:47:26] [PASSED] environ_addr = <0x7ffcac1b8bd8>
>  [23:47:26] [PASSED] environ_envp = <0x7ffcac1b8bd8>
>  [23:47:26] [SKIPPED] environ_auxv
>  [23:47:26] [SKIPPED] environ_total
>  [23:47:26] [PASSED] environ_HOME = <0x7ffcac1b9ccb>
>  [23:47:26] [SKIPPED] auxv_addr
>  [23:47:26] [PASSED] auxv_AT_UID = 1000
>  [23:47:26] [PASSED] auxv_AT_PAGESZ = 4096
>  [23:47:26] ====================== [PASSED] arm64 ======================
>  [23:47:26] ====================== (15 subtests) =======================
>  [23:47:26] [PASSED] argc = 1
>  [23:47:26] [PASSED] argv_addr = <0x7ffdee178188>
>  [23:47:26] [PASSED] argv_environ = <0x7ffdee178188>
>  [23:47:26] [PASSED] argv_total = 1
>  [23:47:26] [PASSED] argv0_addr = <0x7ffdee178bd0>
>  [23:47:26] [PASSED] argv0_str = <0x7ffdee178bd0>
>  [23:47:26] [PASSED] argv0_len = 11
>  [23:47:26] [PASSED] environ_addr = <0x7ffdee178198>
>  [23:47:26] [PASSED] environ_envp = <0x7ffdee178198>
>  [23:47:26] [SKIPPED] environ_auxv
>  [23:47:26] [SKIPPED] environ_total
>  [23:47:26] [PASSED] environ_HOME = <0x7ffdee178ccb>
>  [23:47:26] [SKIPPED] auxv_addr
>  [23:47:26] [PASSED] auxv_AT_UID = 1000
>  [23:47:26] [PASSED] auxv_AT_PAGESZ = 4096
>  [23:47:26] ===================== [PASSED] x86_64 ======================
>  [23:47:26] ====================== (15 subtests) =======================
>  [23:47:26] [PASSED] argc = 1
>  [23:47:26] [PASSED] argv_addr = <0x7ffc16bf3628>
>  [23:47:26] [PASSED] argv_environ = <0x7ffc16bf3628>
>  [23:47:26] [PASSED] argv_total = 1
>  [23:47:26] [PASSED] argv0_addr = <0x7ffc16bf4bd0>
>  [23:47:26] [PASSED] argv0_str = <0x7ffc16bf4bd0>
>  [23:47:26] [PASSED] argv0_len = 11
>  [23:47:26] [PASSED] environ_addr = <0x7ffc16bf3638>
>  [23:47:26] [PASSED] environ_envp = <0x7ffc16bf3638>
>  [23:47:26] [SKIPPED] environ_auxv
>  [23:47:26] [SKIPPED] environ_total
>  [23:47:26] [PASSED] environ_HOME = <0x7ffc16bf4ccb>
>  [23:47:26] [SKIPPED] auxv_addr
>  [23:47:26] [PASSED] auxv_AT_UID = 1000
>  [23:47:26] [PASSED] auxv_AT_PAGESZ = 4096
>  [23:47:26] ===================== [PASSED] riscv64 =====================
>  [23:47:26] ============================================================
>  [23:47:26] Testing complete. Ran 45 tests: passed: 36, skipped: 9
> 
> The output of kunit.py is colored after the test results.
> 
> Not-signed-off
> ---
>  tools/testing/selftests/nolibc/nolibc-test.c    | 121 ++++++++----------------
>  tools/testing/selftests/nolibc/run-all-tests.sh |  22 +++++
>  2 files changed, 63 insertions(+), 80 deletions(-)
>
Thomas Weißschuh July 21, 2023, 1:06 p.m. UTC | #2
On 2023-07-20 23:29:40+0800, Zhangjin Wu wrote:
> The result looks very similar and the change seems not that big, thanks.
> 
> I have a generic question: do we need to compile nolibc-test.c
> independently or at least let users easily compile nolibc-test.c in the
> other places no just in kernel source code, for example, the other libcs
> may want to download and compile it directly.
> 
> The functions used in this change seems not many, is it able to provide
> our clones for them or only provide the clones when we compile them
> out-of-kernel.
> 
> for example:
> 
>     #ifdef NOLIBC_TEST_IN_KERNEL
>     /* -DNOLIBC_TEST_IN_KERNEL from Makefile, for future compatibility */
>     #include "../kselftest.h"
>     #else
>     // our clones of the used functions, for standalone usage
>     #endif

I'd rather just disable the functionality instead. Willy also asked to
stick to the current formatting by default and enable KTAP with a
switch.
So everything will be behind generic wrappers anyways, so we can drop
the KTAP functionality from those wrappers easiy.

Thomas
diff mbox series

Patch

diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index 03f64ce1dda6..f8064cd58783 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -41,12 +41,13 @@ 
 #endif
 #endif
 
+#define inline __inline__
+#include "../kselftest.h"
+
 /* for the type of int_fast16_t and int_fast32_t, musl differs from glibc and nolibc */
 #define SINT_MAX_OF_TYPE(type) (((type)1 << (sizeof(type) * 8 - 2)) - (type)1 + ((type)1 << (sizeof(type) * 8 - 2)))
 #define SINT_MIN_OF_TYPE(type) (-SINT_MAX_OF_TYPE(type) - 1)
 
-#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
-
 /* will be used to test initialization of environ */
 static char **test_envp;
 
@@ -59,6 +60,8 @@  static int test_argc;
 /* will be used by some test cases as readable file, please don't write it */
 static const char *argv0;
 
+static const char *test_name;
+
 /* definition of a series of tests */
 struct test {
 	const char *name;              /* test name */
@@ -197,15 +200,11 @@  static int expect_nz(int expr, int llen)
 
 
 #define EXPECT_EQ(cond, expr, val)				\
-	do { if (!(cond)) result(llen, SKIPPED); else ret += expect_eq(expr, llen, val); } while (0)
+	do { if (!(cond)) ksft_test_result_skip2(test_name, "%s\n", #cond); else expect_eq(expr, val); } while (0)
 
-static int expect_eq(uint64_t expr, int llen, uint64_t val)
+static int expect_eq(uint64_t expr, uint64_t val)
 {
-	int ret = !(expr == val);
-
-	llen += printf(" = %lld ", (long long)expr);
-	result(llen, ret ? FAIL : OK);
-	return ret;
+	ksft_test_result(expr = val, "%s = %lld\n", test_name, (long long) expr);
 }
 
 
@@ -223,15 +222,11 @@  static int expect_ne(int expr, int llen, int val)
 
 
 #define EXPECT_GE(cond, expr, val)				\
-	do { if (!(cond)) result(llen, SKIPPED); else ret += expect_ge(expr, llen, val); } while (0)
+	do { if (!(cond)) ksft_test_result_skip2(test_name, "%s\n", #cond); else expect_ge(expr, val); } while (0)
 
-static int expect_ge(int expr, int llen, int val)
+static int expect_ge(int expr, int val)
 {
-	int ret = !(expr >= val);
-
-	llen += printf(" = %d ", expr);
-	result(llen, ret ? FAIL : OK);
-	return ret;
+	ksft_test_result(expr >= val, "%s = %d\n", test_name, expr);
 }
 
 
@@ -376,37 +371,19 @@  static int expect_ptrzr(const void *expr, int llen)
 
 
 #define EXPECT_PTRNZ(cond, expr)				\
-	do { if (!(cond)) result(llen, SKIPPED); else ret += expect_ptrnz(expr, llen); } while (0)
+	do { if (!(cond)) ksft_test_result_skip("%s: %s\n", test_name, #cond); else expect_ptrnz(expr); } while (0)
 
-static int expect_ptrnz(const void *expr, int llen)
+static int expect_ptrnz(const void *expr)
 {
-	int ret = 0;
-
-	llen += printf(" = <%p> ", expr);
-	if (!expr) {
-		ret = 1;
-		result(llen, FAIL);
-	} else {
-		result(llen, OK);
-	}
-	return ret;
+	ksft_test_result(expr, "%s = <%p>\n", test_name, expr);
 }
 
 #define EXPECT_PTREQ(cond, expr, cmp)				\
-	do { if (!(cond)) result(llen, SKIPPED); else ret += expect_ptreq(expr, llen, cmp); } while (0)
+	do { if (!(cond)) ksft_test_result_skip("%s: %s\n", test_name, #cond); else expect_ptreq(expr, cmp); } while (0)
 
-static int expect_ptreq(const void *expr, int llen, const void *cmp)
+static void expect_ptreq(const void *expr, const void *cmp)
 {
-	int ret = 0;
-
-	llen += printf(" = <%p> ", expr);
-	if (expr != cmp) {
-		ret = 1;
-		result(llen, FAIL);
-	} else {
-		result(llen, OK);
-	}
-	return ret;
+	ksft_test_result(expr == cmp, "%s = <%p>\n", test_name, expr);
 }
 
 #define EXPECT_PTRNE(cond, expr, cmp)				\
@@ -439,41 +416,28 @@  static int expect_ptrge(const void *expr, int llen, const void *cmp)
 }
 
 #define EXPECT_PTRGT(cond, expr, cmp)				\
-	do { if (!(cond)) result(llen, SKIPPED); else ret += expect_ptrgt(expr, llen, cmp); } while (0)
+	do { if (!(cond)) ksft_test_result_skip2(test_name, "%s\n", #cond); else expect_ptrgt(expr, cmp); } while (0)
 
-static int expect_ptrgt(const void *expr, int llen, const void *cmp)
+static void expect_ptrgt(const void *expr, const void *cmp)
 {
-	int ret = !(expr > cmp);
-
-	llen += printf(" = <%p> ", expr);
-	result(llen, ret ? FAIL : OK);
-	return ret;
+	ksft_test_result(expr > cmp, "%s = <%p>\n", test_name, expr);
 }
 
-
 #define EXPECT_PTRLE(cond, expr, cmp)				\
-	do { if (!(cond)) result(llen, SKIPPED); else ret += expect_ptrle(expr, llen, cmp); } while (0)
+	do { if (!(cond)) ksft_test_result_skip("%s: %s\n", test_name, #cond); else expect_ptrle(expr, cmp); } while (0)
 
 static int expect_ptrle(const void *expr, int llen, const void *cmp)
 {
-	int ret = !(expr <= cmp);
-
-	llen += printf(" = <%p> ", expr);
-	result(llen, ret ? FAIL : OK);
-	return ret;
+	ksft_test_result(expr <= cmp, "%s = <%p>\n", test_name, expr);
 }
 
 
 #define EXPECT_PTRLT(cond, expr, cmp)				\
-	do { if (!(cond)) result(llen, SKIPPED); else ret += expect_ptrlt(expr, llen, cmp); } while (0)
+	do { if (!(cond)) ksft_test_result_skip2(test_name, "%s\n", #cond); else expect_ptrlt(expr, cmp); } while (0)
 
-static int expect_ptrlt(const void *expr, int llen, const void *cmp)
+static int expect_ptrlt(const void *expr, const void *cmp)
 {
-	int ret = !(expr < cmp);
-
-	llen += printf(" = <%p> ", expr);
-	result(llen, ret ? FAIL : OK);
-	return ret;
+	ksft_test_result(expr < cmp, "%s = <%p>\n", test_name, expr);
 }
 
 #define EXPECT_PTRER2(cond, expr, expret, experr1, experr2)		\
@@ -520,20 +484,11 @@  static int expect_strzr(const char *expr, int llen)
 
 
 #define EXPECT_STRNZ(cond, expr)				\
-	do { if (!(cond)) result(llen, SKIPPED); else ret += expect_strnz(expr, llen); } while (0)
+	do { if (!(cond)) ksft_test_result_skip("%s: %s\n", test_name, #cond); else expect_strnz(expr); } while (0)
 
-static int expect_strnz(const char *expr, int llen)
+static int expect_strnz(const char *expr)
 {
-	int ret = 0;
-
-	llen += printf(" = <%s> ", expr);
-	if (!expr) {
-		ret = 1;
-		result(llen, FAIL);
-	} else {
-		result(llen, OK);
-	}
-	return ret;
+	ksft_test_result(expr, "%s = <%p>\n", test_name, expr);
 }
 
 
@@ -575,7 +530,7 @@  static int expect_strne(const char *expr, int llen, const char *cmp)
 
 /* declare tests based on line numbers. There must be exactly one test per line. */
 #define CASE_TEST(name) \
-	case __LINE__: llen += printf("%d %s", test, #name);
+	case __LINE__: test_name = #name;
 
 #define SWITCH_TEST \
 	int _tests_start = __LINE__; switch (test + __LINE__ + 1) {
@@ -1187,10 +1142,12 @@  int prepare(void)
 static const struct test test_names[] = {
 	/* add new tests here */
 	{ .name = "startup",    .func = run_startup    },
+	/*
 	{ .name = "syscall",    .func = run_syscall    },
 	{ .name = "stdlib",     .func = run_stdlib     },
 	{ .name = "vfprintf",   .func = run_vfprintf   },
 	{ .name = "protection", .func = run_protection },
+	*/
 };
 
 int is_setting_valid(char *test)
@@ -1317,16 +1274,19 @@  int main(int argc, char **argv, char **envp)
 		} while (test && *test);
 	} else {
 		/* no test mentioned, run everything */
+
+		ksft_print_header_ktap();
+		int total = 0;
+		for (idx = 0; idx < ARRAY_SIZE(test_names); idx++)
+			total += count_subtests(&test_names[idx]);
+		ksft_set_plan(total);
+
 		for (idx = 0; idx < ARRAY_SIZE(test_names); idx++) {
-			printf("Running test '%s'\n", test_names[idx].name);
 			err = test_names[idx].func(min, max);
 			ret += err;
-			printf("Errors during this test: %d\n\n", err);
 		}
 	}
 
-	printf("Total number of errors: %d\n", ret);
-
 	if (getpid() == 1) {
 		/* we're running as init, there's no other process on the
 		 * system, thus likely started from a VM for a quick check.
@@ -1335,7 +1295,7 @@  int main(int argc, char **argv, char **envp)
 		 * cleanly will often be reported as a success. This allows
 		 * to use the output of this program for bisecting kernels.
 		 */
-		printf("Leaving init with final status: %d\n", !!ret);
+		ksft_print_msg("Leaving init with final status: %d\n", !!ret);
 		if (ret == 0)
 			reboot(RB_POWER_OFF);
 #if defined(__x86_64__)
@@ -1349,6 +1309,7 @@  int main(int argc, char **argv, char **envp)
 #endif
 	}
 
-	printf("Exiting with status %d\n", !!ret);
+	ksft_print_msg("Exiting with status %d\n", !!ret);
+	ksft_finished();
 	return !!ret;
 }
diff --git a/tools/testing/selftests/nolibc/run-all-tests.sh b/tools/testing/selftests/nolibc/run-all-tests.sh
new file mode 100755
index 000000000000..8f37b8b36ef4
--- /dev/null
+++ b/tools/testing/selftests/nolibc/run-all-tests.sh
@@ -0,0 +1,22 @@ 
+#!/bin/bash
+
+set -o pipefail
+
+archs=(arm64 x86_64 riscv64)
+
+echo "KTAP version 1"
+echo "1..${#archs[@]}"
+
+for i in "${!archs[@]}"; do
+	arch="${archs[$i]}"
+	./libc-test | sed -e 's/^/  /'
+	rc=$?
+
+	if [ $rc -eq 0 ]; then
+		res=ok
+	else
+		res="not ok"
+	fi
+
+	echo "$res $(( i + 1 )) $arch"
+done