[LTP] [PATCH v3 4/4] testcaes/lib: Add shell loader

Cyril Hrubis
2024-08-27T12:05:10Z

This commit implements a shell loader so that we don't have to write a C
loader for each LTP shell test. The idea is simple, the loader parses
the shell test and prepares the tst_test structure accordingly, then
runs the actual shell test.

The format for the metadata in the shell test was choosen to be JSON
because:

- I didn't want to invent an adhoc format and JSON is perfect for
serializing data structures
- The metadata parser for shell test will be trivial, it will just pick
the JSON from the comment, no parsing will be required

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
Reviewed-by: Richard Palethorpe <io@richiejp.com>
---
include/tst_test.h | 2 +-
testcases/lib/.gitignore | 1 +
testcases/lib/Makefile | 6 +-
testcases/lib/run_tests.sh | 21 +
testcases/lib/tests/shell_loader.sh | 26 +
.../lib/tests/shell_loader_all_filesystems.sh | 27 +
.../lib/tests/shell_loader_filesystems.sh | 33 ++
.../lib/tests/shell_loader_invalid_block.sh | 26 +
.../tests/shell_loader_invalid_metadata.sh | 15 +
testcases/lib/tests/shell_loader_kconfigs.sh | 12 +
.../lib/tests/shell_loader_no_metadata.sh | 8 +
.../lib/tests/shell_loader_supported_archs.sh | 12 +
testcases/lib/tests/shell_loader_tags.sh | 15 +
testcases/lib/tests/shell_loader_tcnt.sh | 15 +
.../lib/tests/shell_loader_wrong_metadata.sh | 15 +
testcases/lib/tst_env.sh | 4 +
testcases/lib/tst_loader.sh | 11 +
testcases/lib/tst_run_shell.c | 491 ++++++++++++++++++
18 files changed, 738 insertions(+), 2 deletions(-)
create mode 100755 testcases/lib/tests/shell_loader.sh
create mode 100755 testcases/lib/tests/shell_loader_all_filesystems.sh
create mode 100755 testcases/lib/tests/shell_loader_filesystems.sh
create mode 100755 testcases/lib/tests/shell_loader_invalid_block.sh
create mode 100755 testcases/lib/tests/shell_loader_invalid_metadata.sh
create mode 100755 testcases/lib/tests/shell_loader_kconfigs.sh
create mode 100755 testcases/lib/tests/shell_loader_no_metadata.sh
create mode 100755 testcases/lib/tests/shell_loader_supported_archs.sh
create mode 100755 testcases/lib/tests/shell_loader_tags.sh
create mode 100755 testcases/lib/tests/shell_loader_tcnt.sh
create mode 100755 testcases/lib/tests/shell_loader_wrong_metadata.sh
create mode 100644 testcases/lib/tst_loader.sh
create mode 100644 testcases/lib/tst_run_shell.c

diff --git a/include/tst_test.h b/include/tst_test.h
index 9871676a5..d0fa84a71 100644
--- a/include/tst_test.h
+++ b/include/tst_test.h
@@ -274,7 +274,7 @@ struct tst_fs {
const char *const *mkfs_opts;
const char *mkfs_size_opt;

- const unsigned int mnt_flags;
+ unsigned int mnt_flags;
const void *mnt_data;
};

diff --git a/testcases/lib/.gitignore b/testcases/lib/.gitignore
index d0dacf62a..385f3c3ca 100644
--- a/testcases/lib/.gitignore
+++ b/testcases/lib/.gitignore
@@ -24,3 +24,4 @@
/tst_supported_fs
/tst_timeout_kill
/tst_res_
+/tst_run_shell
diff --git a/testcases/lib/Makefile b/testcases/lib/Makefile
index 928d76d62..b3a9181c1 100644
--- a/testcases/lib/Makefile
+++ b/testcases/lib/Makefile
@@ -4,6 +4,9 @@

top_srcdir ?= ../..

+LTPLIBS = ujson
+tst_run_shell: LTPLDLIBS = -lujson
+
include $(top_srcdir)/include/mk/testcases.mk

INSTALL_TARGETS := *.sh
@@ -13,6 +16,7 @@ MAKE_TARGETS := tst_sleep tst_random tst_checkpoint tst_rod tst_kvcmp\
tst_getconf tst_supported_fs tst_check_drivers tst_get_unused_port\
tst_get_median tst_hexdump tst_get_free_pids tst_timeout_kill\
tst_check_kconfigs tst_cgctl tst_fsfreeze tst_ns_create tst_ns_exec\
- tst_ns_ifmove tst_lockdown_enabled tst_secureboot_enabled tst_res_
+ tst_ns_ifmove tst_lockdown_enabled tst_secureboot_enabled tst_res_\
+ tst_run_shell

include $(top_srcdir)/include/mk/generic_trunk_target.mk
diff --git a/testcases/lib/run_tests.sh b/testcases/lib/run_tests.sh
index 60e7d1bcf..e30065f1d 100755
--- a/testcases/lib/run_tests.sh
+++ b/testcases/lib/run_tests.sh
@@ -9,3 +9,24 @@ for i in `seq -w 01 06`; do
echo
./tests/shell_test$i
done
+
+for i in shell_loader.sh shell_loader_all_filesystems.sh shell_loader_no_metadata.sh \
+ shell_loader_wrong_metadata.sh shell_loader_invalid_metadata.sh\
+ shell_loader_supported_archs.sh shell_loader_filesystems.sh\
+ shell_loader_tcnt.sh shell_loader_kconfigs.sh shell_loader_tags.sh \
+ shell_loader_invalid_block.sh; do
+ echo
+ echo "*** Running $i ***"
+ echo
+ $i
+done
+
+echo
+echo "*** Testing LTP test -h option ***"
+echo
+shell_loader.sh -h
+
+echo
+echo "*** Testing LTP test -i option ***"
+echo
+shell_loader.sh -i 2
diff --git a/testcases/lib/tests/shell_loader.sh b/testcases/lib/tests/shell_loader.sh
new file mode 100755
index 000000000..df7f0c0af
--- /dev/null
+++ b/testcases/lib/tests/shell_loader.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# ---
+# doc
+#
+# [Description]
+#
+# This is a simple shell test loader example.
+# ---
+#
+# ---
+# env
+# {
+# "needs_tmpdir": true
+# }
+# ---
+
+. tst_loader.sh
+
+tst_res TPASS "Shell loader works fine!"
+case "$PWD" in
+ /tmp/*)
+ tst_res TPASS "We are running in temp directory in $PWD";;
+ *)
+ tst_res TFAIL "We are not running in temp directory but $PWD";;
+esac
diff --git a/testcases/lib/tests/shell_loader_all_filesystems.sh b/testcases/lib/tests/shell_loader_all_filesystems.sh
new file mode 100755
index 000000000..d5943c335
--- /dev/null
+++ b/testcases/lib/tests/shell_loader_all_filesystems.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# ---
+# env
+# {
+# "needs_root": true,
+# "mount_device": true,
+# "all_filesystems": true,
+# "mntpoint": "ltp_mntpoint"
+# }
+# ---
+
+. tst_loader.sh
+
+tst_res TINFO "In shell"
+
+mntpath=$(realpath ltp_mntpoint)
+mounted=$(grep $mntpath /proc/mounts)
+
+if [ -n "$mounted" ]; then
+ device=$(echo $mounted |cut -d' ' -f 1)
+ path=$(echo $mounted |cut -d' ' -f 2)
+
+ tst_res TPASS "$device mounted at $path"
+else
+ tst_res TFAIL "Device not mounted!"
+fi
diff --git a/testcases/lib/tests/shell_loader_filesystems.sh b/testcases/lib/tests/shell_loader_filesystems.sh
new file mode 100755
index 000000000..5d8aa9808
--- /dev/null
+++ b/testcases/lib/tests/shell_loader_filesystems.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# ---
+# env
+# {
+# "mount_device": true,
+# "mntpoint": "ltp_mntpoint",
+# "filesystems": [
+# {
+# "type": "btrfs"
+# },
+# {
+# "type": "xfs",
+# "mkfs_opts": ["-m", "reflink=1"]
+# }
+# ]
+# }
+# ---
+
+. tst_loader.sh
+
+tst_res TINFO "In shell"
+
+mntpoint=$(realpath ltp_mntpoint)
+mounted=$(grep $mntpoint /proc/mounts)
+
+if [ -n "$mounted" ]; then
+ fs=$(echo $mounted |cut -d' ' -f 3)
+
+ tst_res TPASS "Mounted device formatted with $fs"
+else
+ tst_res TFAIL "Device not mounted!"
+fi
diff --git a/testcases/lib/tests/shell_loader_invalid_block.sh b/testcases/lib/tests/shell_loader_invalid_block.sh
new file mode 100755
index 000000000..f41de04fd
--- /dev/null
+++ b/testcases/lib/tests/shell_loader_invalid_block.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# ---
+# doc
+#
+# [Description]
+#
+# This is a simple shell test loader example.
+# ---
+#
+# ---
+# env
+# {
+# "needs_tmpdir": true
+# }
+# ---
+#
+# ---
+# inv
+#
+# This is an invalid block that breaks the test.
+# ---
+
+. tst_loader.sh
+
+tst_res TPASS "This should pass!"
diff --git a/testcases/lib/tests/shell_loader_invalid_metadata.sh b/testcases/lib/tests/shell_loader_invalid_metadata.sh
new file mode 100755
index 000000000..c10b00f1b
--- /dev/null
+++ b/testcases/lib/tests/shell_loader_invalid_metadata.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# This test has wrong metadata and should not be run
+#
+# ---
+# env
+# {
+# {"needs_tmpdir": 42,
+# }
+# ---
+#
+
+. tst_loader.sh
+
+tst_res TFAIL "Shell loader should TBROK the test"
diff --git a/testcases/lib/tests/shell_loader_kconfigs.sh b/testcases/lib/tests/shell_loader_kconfigs.sh
new file mode 100755
index 000000000..7e9a1dce7
--- /dev/null
+++ b/testcases/lib/tests/shell_loader_kconfigs.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+#
+# ---
+# env
+# {
+# "needs_kconfigs": ["CONFIG_NUMA=y"]
+# }
+# ---
+
+. tst_loader.sh
+
+tst_res TPASS "Shell loader works fine!"
diff --git a/testcases/lib/tests/shell_loader_no_metadata.sh b/testcases/lib/tests/shell_loader_no_metadata.sh
new file mode 100755
index 000000000..60ba8b889
--- /dev/null
+++ b/testcases/lib/tests/shell_loader_no_metadata.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# This test has no metadata and should not be executed
+#
+
+. tst_loader.sh
+
+tst_res TFAIL "Shell loader should TBROK the test"
diff --git a/testcases/lib/tests/shell_loader_supported_archs.sh b/testcases/lib/tests/shell_loader_supported_archs.sh
new file mode 100755
index 000000000..45213f840
--- /dev/null
+++ b/testcases/lib/tests/shell_loader_supported_archs.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+#
+# ---
+# env
+# {
+# "supported_archs": ["x86", "ppc64", "x86_64"]
+# }
+# ---
+
+. tst_loader.sh
+
+tst_res TPASS "We are running on supported architecture"
diff --git a/testcases/lib/tests/shell_loader_tags.sh b/testcases/lib/tests/shell_loader_tags.sh
new file mode 100755
index 000000000..a6278c37d
--- /dev/null
+++ b/testcases/lib/tests/shell_loader_tags.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# ---
+# env
+# {
+# "tags": [
+# ["linux-git", "832478cd342ab"],
+# ["CVE", "2099-999"]
+# ]
+# }
+# ---
+
+. tst_loader.sh
+
+tst_res TFAIL "Fails the test so that tags are shown."
diff --git a/testcases/lib/tests/shell_loader_tcnt.sh b/testcases/lib/tests/shell_loader_tcnt.sh
new file mode 100755
index 000000000..81fc08179
--- /dev/null
+++ b/testcases/lib/tests/shell_loader_tcnt.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# The script should be executed tcnt times and the iteration number should be in $1
+#
+# ---
+# env
+# {
+# "tcnt": 2
+# }
+# ---
+#
+
+. tst_loader.sh
+
+tst_res TPASS "Iteration $1"
diff --git a/testcases/lib/tests/shell_loader_wrong_metadata.sh b/testcases/lib/tests/shell_loader_wrong_metadata.sh
new file mode 100755
index 000000000..752e25eea
--- /dev/null
+++ b/testcases/lib/tests/shell_loader_wrong_metadata.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# This test has wrong metadata and should not be run
+#
+# ---
+# env
+# {
+# "needs_tmpdir": 42,
+# }
+# ---
+#
+
+. tst_loader.sh
+
+tst_res TFAIL "Shell loader should TBROK the test"
diff --git a/testcases/lib/tst_env.sh b/testcases/lib/tst_env.sh
index 948bc5024..67ba80744 100644
--- a/testcases/lib/tst_env.sh
+++ b/testcases/lib/tst_env.sh
@@ -1,4 +1,8 @@
#!/bin/sh
+#
+# This is a minimal test environment for a shell scripts executed from C by
+# tst_run_shell() function. Shell tests must use the tst_loader.sh instead!
+#

tst_script_name=$(basename $0)

diff --git a/testcases/lib/tst_loader.sh b/testcases/lib/tst_loader.sh
new file mode 100644
index 000000000..ed04d0340
--- /dev/null
+++ b/testcases/lib/tst_loader.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+#
+# This is a loader for shell tests that use the C test library.
+#
+
+if [ -z "$LTP_IPC_PATH" ]; then
+ tst_run_shell $(basename "$0") "$@"
+ exit $?
+else
+ . tst_env.sh
+fi
diff --git a/testcases/lib/tst_run_shell.c b/testcases/lib/tst_run_shell.c
new file mode 100644
index 000000000..8ed0f21b6
--- /dev/null
+++ b/testcases/lib/tst_run_shell.c
@@ -0,0 +1,491 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2024 Cyril Hrubis <chrubis@suse.cz>
+ */
+#include <sys/mount.h>
+
+#define TST_NO_DEFAULT_MAIN
+#include "tst_test.h"
+#include "tst_safe_stdio.h"
+#include "ujson.h"
+
+static char *shell_filename;
+
+static void run_shell(void)
+{
+ tst_run_script(shell_filename, NULL);
+}
+
+static void run_shell_tcnt(unsigned int n)
+{
+ char buf[128];
+ char *const params[] = {buf, NULL};
+
+ snprintf(buf, sizeof(buf), "%u", n);
+
+ tst_run_script(shell_filename, params);
+}
+
+struct tst_test test = {
+ .runs_script = 1,
+};
+
+static void print_help(void)
+{
+ printf("Usage: tst_shell_loader ltp_shell_test.sh ...\n");
+}
+
+static char *metadata;
+static size_t metadata_size;
+static size_t metadata_used;
+
+static void metadata_append(const char *line)
+{
+ size_t linelen = strlen(line);
+
+ if (metadata_size - metadata_used < linelen + 1) {
+ metadata_size += 4096;
+ metadata = SAFE_REALLOC(metadata, metadata_size);
+ }
+
+ strcpy(metadata + metadata_used, line);
+ metadata_used += linelen;
+}
+
+enum test_attr_ids {
+ ALL_FILESYSTEMS,
+ DEV_MIN_SIZE,
+ FILESYSTEMS,
+ FORMAT_DEVICE,
+ MIN_CPUS,
+ MIN_MEM_AVAIL,
+ MIN_KVER,
+ MIN_SWAP_AVAIL,
+ MNTPOINT,
+ MOUNT_DEVICE,
+ NEEDS_ABI_BITS,
+ NEEDS_CMDS,
+ NEEDS_DEVFS,
+ NEEDS_DEVICE,
+ NEEDS_DRIVERS,
+ NEEDS_HUGETLBFS,
+ NEEDS_KCONFIGS,
+ NEEDS_ROFS,
+ NEEDS_ROOT,
+ NEEDS_TMPDIR,
+ RESTORE_WALLCLOCK,
+ SKIP_FILESYSTEMS,
+ SKIP_IN_COMPAT,
+ SKIP_IN_LOCKDOWN,
+ SKIP_IN_SECUREBOOT,
+ SUPPORTED_ARCHS,
+ TAGS,
+ TAINT_CHECK,
+ TCNT,
+};
+
+static ujson_obj_attr test_attrs[] = {
+ UJSON_OBJ_ATTR_IDX(ALL_FILESYSTEMS, "all_filesystems", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(DEV_MIN_SIZE, "dev_min_size", UJSON_INT),
+ UJSON_OBJ_ATTR_IDX(FILESYSTEMS, "filesystems", UJSON_ARR),
+ UJSON_OBJ_ATTR_IDX(FORMAT_DEVICE, "format_device", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(MIN_CPUS, "min_cpus", UJSON_INT),
+ UJSON_OBJ_ATTR_IDX(MIN_MEM_AVAIL, "min_mem_avail", UJSON_INT),
+ UJSON_OBJ_ATTR_IDX(MIN_KVER, "min_kver", UJSON_STR),
+ UJSON_OBJ_ATTR_IDX(MIN_SWAP_AVAIL, "min_swap_avail", UJSON_INT),
+ UJSON_OBJ_ATTR_IDX(MNTPOINT, "mntpoint", UJSON_STR),
+ UJSON_OBJ_ATTR_IDX(MOUNT_DEVICE, "mount_device", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(NEEDS_ABI_BITS, "needs_abi_bits", UJSON_INT),
+ UJSON_OBJ_ATTR_IDX(NEEDS_CMDS, "needs_cmds", UJSON_ARR),
+ UJSON_OBJ_ATTR_IDX(NEEDS_DEVFS, "needs_devfs", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(NEEDS_DEVICE, "needs_device", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(NEEDS_DRIVERS, "needs_drivers", UJSON_ARR),
+ UJSON_OBJ_ATTR_IDX(NEEDS_HUGETLBFS, "needs_hugetlbfs", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(NEEDS_KCONFIGS, "needs_kconfigs", UJSON_ARR),
+ UJSON_OBJ_ATTR_IDX(NEEDS_ROFS, "needs_rofs", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(NEEDS_ROOT, "needs_root", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(NEEDS_TMPDIR, "needs_tmpdir", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(RESTORE_WALLCLOCK, "restore_wallclock", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(SKIP_FILESYSTEMS, "skip_filesystems", UJSON_ARR),
+ UJSON_OBJ_ATTR_IDX(SKIP_IN_COMPAT, "skip_in_compat", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(SKIP_IN_LOCKDOWN, "skip_in_lockdown", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(SKIP_IN_SECUREBOOT, "skip_in_secureboot", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(SUPPORTED_ARCHS, "supported_archs", UJSON_ARR),
+ UJSON_OBJ_ATTR_IDX(TAGS, "tags", UJSON_ARR),
+ UJSON_OBJ_ATTR_IDX(TAINT_CHECK, "taint_check", UJSON_BOOL),
+ UJSON_OBJ_ATTR_IDX(TCNT, "tcnt", UJSON_INT)
+};
+
+static ujson_obj test_obj = {
+ .attrs = test_attrs,
+ .attr_cnt = UJSON_ARRAY_SIZE(test_attrs),
+};
+
+static const char *const *parse_strarr(ujson_reader *reader, ujson_val *val)
+{
+ unsigned int cnt = 0, i = 0;
+ char **ret;
+
+ ujson_reader_state state = ujson_reader_state_save(reader);
+
+ UJSON_ARR_FOREACH(reader, val) {
+ if (val->type != UJSON_STR) {
+ ujson_err(reader, "Expected string!");
+ return NULL;
+ }
+
+ cnt++;
+ }
+
+ ujson_reader_state_load(reader, state);
+
+ ret = SAFE_MALLOC(sizeof(char*) * (cnt + 1));
+
+ UJSON_ARR_FOREACH(reader, val) {
+ ret[i++] = strdup(val->val_str);
+ }
+
+ ret[i] = NULL;
+
+ return (const char *const *)ret;
+}
+
+enum fs_ids {
+ MKFS_OPTS,
+ MKFS_SIZE_OPT,
+ MNT_FLAGS,
+ TYPE,
+};
+
+static ujson_obj_attr fs_attrs[] = {
+ UJSON_OBJ_ATTR_IDX(MKFS_OPTS, "mkfs_opts", UJSON_ARR),
+ UJSON_OBJ_ATTR_IDX(MKFS_SIZE_OPT, "mkfs_size_opt", UJSON_STR),
+ UJSON_OBJ_ATTR_IDX(MNT_FLAGS, "mnt_flags", UJSON_ARR),
+ UJSON_OBJ_ATTR_IDX(TYPE, "type", UJSON_STR),
+};
+
+static ujson_obj fs_obj = {
+ .attrs = fs_attrs,
+ .attr_cnt = UJSON_ARRAY_SIZE(fs_attrs),
+};
+
+static int parse_mnt_flags(ujson_reader *reader, ujson_val *val)
+{
+ int ret = 0;
+
+ UJSON_ARR_FOREACH(reader, val) {
+ if (val->type != UJSON_STR) {
+ ujson_err(reader, "Expected string!");
+ return ret;
+ }
+
+ if (!strcmp(val->val_str, "RDONLY"))
+ ret |= MS_RDONLY;
+ else if (!strcmp(val->val_str, "NOATIME"))
+ ret |= MS_NOATIME;
+ else if (!strcmp(val->val_str, "NOEXEC"))
+ ret |= MS_NOEXEC;
+ else if (!strcmp(val->val_str, "NOSUID"))
+ ret |= MS_NOSUID;
+ else
+ ujson_err(reader, "Invalid mount flag");
+ }
+
+ return ret;
+}
+
+static struct tst_fs *parse_filesystems(ujson_reader *reader, ujson_val *val)
+{
+ unsigned int i = 0, cnt = 0;
+ struct tst_fs *ret;
+
+ ujson_reader_state state = ujson_reader_state_save(reader);
+
+ UJSON_ARR_FOREACH(reader, val) {
+ if (val->type != UJSON_OBJ) {
+ ujson_err(reader, "Expected object!");
+ return NULL;
+ }
+ ujson_obj_skip(reader);
+ cnt++;
+ }
+
+ ujson_reader_state_load(reader, state);
+
+ ret = SAFE_MALLOC(sizeof(struct tst_fs) * (cnt + 1));
+ memset(ret, 0, sizeof(*ret) * (cnt+1));
+
+ UJSON_ARR_FOREACH(reader, val) {
+ UJSON_OBJ_FOREACH_FILTER(reader, val, &fs_obj, ujson_empty_obj) {
+ switch ((enum fs_ids)val->idx) {
+ case MKFS_OPTS:
+ ret[i].mkfs_opts = parse_strarr(reader, val);
+ break;
+ case MKFS_SIZE_OPT:
+ ret[i].mkfs_size_opt = strdup(val->val_str);
+ break;
+ case MNT_FLAGS:
+ ret[i].mnt_flags = parse_mnt_flags(reader, val);
+ break;
+ case TYPE:
+ ret[i].type = strdup(val->val_str);
+ break;
+ }
+
+ }
+
+ i++;
+ }
+
+ return ret;
+}
+
+static struct tst_tag *parse_tags(ujson_reader *reader, ujson_val *val)
+{
+ unsigned int i = 0, cnt = 0;
+ struct tst_tag *ret;
+
+ ujson_reader_state state = ujson_reader_state_save(reader);
+
+ UJSON_ARR_FOREACH(reader, val) {
+ if (val->type != UJSON_ARR) {
+ ujson_err(reader, "Expected array!");
+ return NULL;
+ }
+ ujson_arr_skip(reader);
+ cnt++;
+ }
+
+ ujson_reader_state_load(reader, state);
+
+ ret = SAFE_MALLOC(sizeof(struct tst_tag) * (cnt + 1));
+ memset(&ret[cnt], 0, sizeof(ret[cnt]));
+
+ UJSON_ARR_FOREACH(reader, val) {
+ char *name = NULL;
+ char *value = NULL;
+
+ UJSON_ARR_FOREACH(reader, val) {
+ if (val->type != UJSON_STR) {
+ ujson_err(reader, "Expected string!");
+ return NULL;
+ }
+
+ if (!name) {
+ name = strdup(val->val_str);
+ } else if (!value) {
+ value = strdup(val->val_str);
+ } else {
+ ujson_err(reader, "Expected only two members!");
+ return NULL;
+ }
+ }
+
+ ret[i].name = name;
+ ret[i].value = value;
+ i++;
+ }
+
+ return ret;
+}
+
+static void parse_metadata(void)
+{
+ ujson_reader reader = UJSON_READER_INIT(metadata, metadata_used, UJSON_READER_STRICT);
+ char str_buf[128];
+ ujson_val val = UJSON_VAL_INIT(str_buf, sizeof(str_buf));
+
+ UJSON_OBJ_FOREACH_FILTER(&reader, &val, &test_obj, ujson_empty_obj) {
+ switch ((enum test_attr_ids)val.idx) {
+ case ALL_FILESYSTEMS:
+ test.all_filesystems = val.val_bool;
+ break;
+ case DEV_MIN_SIZE:
+ if (val.val_int <= 0)
+ ujson_err(&reader, "Device size must be > 0");
+ else
+ test.dev_min_size = val.val_int;
+ break;
+ case FILESYSTEMS:
+ test.filesystems = parse_filesystems(&reader, &val);
+ break;
+ case FORMAT_DEVICE:
+ test.format_device = val.val_bool;
+ break;
+ case MIN_CPUS:
+ if (val.val_int <= 0)
+ ujson_err(&reader, "Minimal number of cpus must be > 0");
+ else
+ test.min_cpus = val.val_int;
+ break;
+ case MIN_MEM_AVAIL:
+ if (val.val_int <= 0)
+ ujson_err(&reader, "Minimal available memory size must be > 0");
+ else
+ test.min_mem_avail = val.val_int;
+ break;
+ case MIN_KVER:
+ test.min_kver = strdup(val.val_str);
+ break;
+ case MIN_SWAP_AVAIL:
+ if (val.val_int <= 0)
+ ujson_err(&reader, "Minimal available swap size must be > 0");
+ else
+ test.min_swap_avail = val.val_int;
+ break;
+ case MNTPOINT:
+ test.mntpoint = strdup(val.val_str);
+ break;
+ case MOUNT_DEVICE:
+ test.mount_device = val.val_bool;
+ break;
+ case NEEDS_ABI_BITS:
+ if (val.val_int == 32 || val.val_int == 64)
+ test.needs_abi_bits = val.val_int;
+ else
+ ujson_err(&reader, "ABI bits must be 32 or 64");
+ break;
+ case NEEDS_CMDS:
+ test.needs_cmds = parse_strarr(&reader, &val);
+ break;
+ case NEEDS_DEVFS:
+ test.needs_devfs = val.val_bool;
+ break;
+ case NEEDS_DEVICE:
+ test.needs_device = val.val_bool;
+ break;
+ case NEEDS_DRIVERS:
+ test.needs_drivers = parse_strarr(&reader, &val);
+ break;
+ case NEEDS_HUGETLBFS:
+ test.needs_hugetlbfs = val.val_bool;
+ break;
+ case NEEDS_KCONFIGS:
+ test.needs_kconfigs = parse_strarr(&reader, &val);
+ break;
+ case NEEDS_ROFS:
+ test.needs_rofs = val.val_bool;
+ break;
+ case NEEDS_ROOT:
+ test.needs_root = val.val_bool;
+ break;
+ case NEEDS_TMPDIR:
+ test.needs_tmpdir = val.val_bool;
+ break;
+ case RESTORE_WALLCLOCK:
+ test.restore_wallclock = val.val_bool;
+ break;
+ case SKIP_FILESYSTEMS:
+ test.skip_filesystems = parse_strarr(&reader, &val);
+ break;
+ case SKIP_IN_COMPAT:
+ test.skip_in_compat = val.val_bool;
+ break;
+ case SKIP_IN_LOCKDOWN:
+ test.skip_in_lockdown = val.val_bool;
+ break;
+ case SKIP_IN_SECUREBOOT:
+ test.skip_in_secureboot = val.val_bool;
+ break;
+ case SUPPORTED_ARCHS:
+ test.supported_archs = parse_strarr(&reader, &val);
+ break;
+ case TAGS:
+ test.tags = parse_tags(&reader, &val);
+ break;
+ case TAINT_CHECK:
+ test.taint_check = val.val_bool;
+ break;
+ case TCNT:
+ if (val.val_int <= 0)
+ ujson_err(&reader, "Number of tests must be > 0");
+ else
+ test.tcnt = val.val_int;
+ break;
+ }
+ }
+
+ ujson_reader_finish(&reader);
+
+ if (ujson_reader_err(&reader))
+ tst_brk(TBROK, "Invalid metadata");
+}
+
+enum parser_state {
+ PAR_NONE,
+ PAR_ESC,
+ PAR_DOC,
+ PAR_ENV,
+};
+
+static void extract_metadata(void)
+{
+ FILE *f;
+ char line[4096];
+ char path[4096];
+ enum parser_state state = PAR_NONE;
+
+ if (tst_get_path(shell_filename, path, sizeof(path)) == -1)
+ tst_brk(TBROK, "Failed to find %s in $PATH", shell_filename);
+
+ f = SAFE_FOPEN(path, "r");
+
+ while (fgets(line, sizeof(line), f)) {
+ switch (state) {
+ case PAR_NONE:
+ if (!strcmp(line, "# ---\n"))
+ state = PAR_ESC;
+ break;
+ case PAR_ESC:
+ if (!strcmp(line, "# env\n"))
+ state = PAR_ENV;
+ else if (!strcmp(line, "# doc\n"))
+ state = PAR_DOC;
+ else
+ tst_brk(TBROK, "Unknown comment block %s", line);
+ break;
+ case PAR_ENV:
+ if (!strcmp(line, "# ---\n"))
+ state = PAR_NONE;
+ else
+ metadata_append(line + 2);
+ break;
+ case PAR_DOC:
+ if (!strcmp(line, "# ---\n"))
+ state = PAR_NONE;
+ break;
+ }
+ }
+
+ fclose(f);
+}
+
+static void prepare_test_struct(void)
+{
+ extract_metadata();
+
+ if (metadata)
+ parse_metadata();
+ else
+ tst_brk(TBROK, "No metadata found!");
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc < 2)
+ goto help;
+
+ shell_filename = argv[1];
+
+ prepare_test_struct();
+
+ if (test.tcnt)
+ test.test = run_shell_tcnt;
+ else
+ test.test_all = run_shell;
+
+ tst_run_tcases(argc - 1, argv + 1, &test);
+help:
+ print_help();
+ return 1;
+}
--
2.44.2