[LTP] [PATCH v3 1/2] library: add cmd check handler in needs_cmds

Yang Xu
2021-12-09T03:21:16Z

Testcase ie statx05 needs mkfs.ext4 >= 1.43.0 because of encrypt feature.

As Cyril suggested, add cmd check handler in needs_cmd.

This tst_check_cmd not only check cmd whether exists but also check the cmd
version whether meets test's requirement.

In tst_check_cmd function, use strtok_r to split cmd_token,op_token,version_token.
It only supports six operations '>=' '<=' '>' '<' '==' '!='.

Currently, for the command version check, it only supports mkfs.ext4 command. If you
want to support more commands, just add your own .parser and .table_get methond in
version_parsers structure.

Suggested-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
---
v2-v3:
1. rename check_cmd to tst_check_cmd
2. move code into tst_cmd.c instead of tst_test.c
3. export tst_cmd_check function into tst_private.h
4. add test_needs_cmds cases into runtest.sh
5. fix typo
v1->v2
1. rename tst_version_parser to check_cmd
2. For mkfs_ext4_version_table_get method, use sscanf instead of strtok_r
3. use enum for cmd op
4. fix description
5. add more newlib test for this
doc/c-test-api.txt | 14 +++
include/tst_private.h | 9 ++
lib/newlib_tests/.gitignore | 8 ++
lib/newlib_tests/runtest.sh | 5 +-
lib/newlib_tests/test_needs_cmds01.c | 25 ++++
lib/newlib_tests/test_needs_cmds02.c | 24 ++++
lib/newlib_tests/test_needs_cmds03.c | 24 ++++
lib/newlib_tests/test_needs_cmds04.c | 24 ++++
lib/newlib_tests/test_needs_cmds05.c | 24 ++++
lib/newlib_tests/test_needs_cmds06.c | 24 ++++
lib/newlib_tests/test_needs_cmds07.c | 24 ++++
lib/newlib_tests/test_needs_cmds08.c | 27 +++++
lib/tst_cmd.c | 168 +++++++++++++++++++++++++++
lib/tst_test.c | 6 +-
14 files changed, 400 insertions(+), 6 deletions(-)
create mode 100644 lib/newlib_tests/test_needs_cmds01.c
create mode 100644 lib/newlib_tests/test_needs_cmds02.c
create mode 100644 lib/newlib_tests/test_needs_cmds03.c
create mode 100644 lib/newlib_tests/test_needs_cmds04.c
create mode 100644 lib/newlib_tests/test_needs_cmds05.c
create mode 100644 lib/newlib_tests/test_needs_cmds06.c
create mode 100644 lib/newlib_tests/test_needs_cmds07.c
create mode 100644 lib/newlib_tests/test_needs_cmds08.c

diff --git a/doc/c-test-api.txt b/doc/c-test-api.txt
index 64d0630ce..0555cd614 100644
--- a/doc/c-test-api.txt
+++ b/doc/c-test-api.txt
@@ -2013,6 +2013,20 @@ terminated array of strings such as:
},
-------------------------------------------------------------------------------

+Also can check required commands version whether is satisfied by using 'needs_cmds',
+
+[source,c]
+-------------------------------------------------------------------------------
+.needs_cmds = (const char *const []) {
+ "mkfs.ext4 >= 1.43.0",
+ NULL
+},
++-------------------------------------------------------------------------------
+
+Currently, we only support mkfs.ext4 command version check.
+If you want to support more commands, please fill your own .parser and .table_get
+method in the version_parsers structure of lib/tst_cmd.c.
+
1.36 Assert sys or proc file value
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using TST_ASSERT_INT/STR(path, val) to assert that integer value or string stored in
diff --git a/include/tst_private.h b/include/tst_private.h
index fe0955f3b..b02f91228 100644
--- a/include/tst_private.h
+++ b/include/tst_private.h
@@ -37,4 +37,13 @@ int tst_get_prefix(const char *ip_str, int is_ipv6);
*/
char tst_kconfig_get(const char *confname);

+/*
+ * If cmd argument is a single command, this function just checks command
+ * whether exists. If not, case skips.
+ * If cmd argument is a complex string ie 'mkfs.ext4 >= 1.43.0', this
+ * function checks command version whether meets this requirement.
+ * If not, case skips.
+ */
+void tst_check_cmd(const char *cmd);
+
#endif
diff --git a/lib/newlib_tests/.gitignore b/lib/newlib_tests/.gitignore
index cf467b5a0..a19fa22e8 100644
--- a/lib/newlib_tests/.gitignore
+++ b/lib/newlib_tests/.gitignore
@@ -46,4 +46,12 @@ test_macros06
tst_fuzzy_sync01
tst_fuzzy_sync02
tst_fuzzy_sync03
+test_needs_cmds01
+test_needs_cmds02
+test_needs_cmds03
+test_needs_cmds04
+test_needs_cmds05
+test_needs_cmds06
+test_needs_cmds07
+test_needs_cmds08
test_zero_hugepage
diff --git a/lib/newlib_tests/runtest.sh b/lib/newlib_tests/runtest.sh
index 05c76228b..da01f36d9 100755
--- a/lib/newlib_tests/runtest.sh
+++ b/lib/newlib_tests/runtest.sh
@@ -2,8 +2,9 @@
# Copyright (c) 2021 Petr Vorel <pvorel@suse.cz>

LTP_C_API_TESTS="${LTP_C_API_TESTS:-test05 test07 test09 test12 test15 test18
-tst_bool_expr test_exec test_timer tst_res_hexd tst_strstatus tst_fuzzy_sync03
-test_zero_hugepage.sh}"
+test_needs_cmds01 test_needs_cmds02 test_needs_cmds03 test_needs_cmds06
+test_needs_cmds07 tst_bool_expr test_exec test_timer tst_res_hexd tst_strstatus
+tst_fuzzy_sync03 test_zero_hugepage.sh}"

LTP_SHELL_API_TESTS="${LTP_SHELL_API_TESTS:-shell/tst_check_driver.sh shell/net/*.sh}"

diff --git a/lib/newlib_tests/test_needs_cmds01.c b/lib/newlib_tests/test_needs_cmds01.c
new file mode 100644
index 000000000..92305ee97
--- /dev/null
+++ b/lib/newlib_tests/test_needs_cmds01.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021 Yang Xu <xuyang2018.jy@fujitsu.com>
+ */
+
+#include <stdio.h>
+#include "tst_test.h"
+
+static void do_test(void)
+{
+ tst_res(TPASS, "Testing tst_check_cmd() functionality OK.");
+}
+
+static struct tst_test test = {
+ .test_all = do_test,
+ .needs_cmds = (const char *[]) {
+ "mkfs.ext4",
+ "mkfs.ext4 >= 1.0.0",
+ "mkfs.ext4 <= 2.0.0",
+ "mkfs.ext4 != 2.0.0",
+ "mkfs.ext4 > 1.0.0",
+ "mkfs.ext4 < 2.0.0",
+ NULL
+ }
+};
diff --git a/lib/newlib_tests/test_needs_cmds02.c b/lib/newlib_tests/test_needs_cmds02.c
new file mode 100644
index 000000000..1eeaf6351
--- /dev/null
+++ b/lib/newlib_tests/test_needs_cmds02.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021 Yang Xu <xuyang2018.jy@fujitsu.com>
+ */
+
+/*
+ * Test Illegal format by using non-existing cmd.
+ */
+
+#include <stdio.h>
+#include "tst_test.h"
+
+static void do_test(void)
+{
+ tst_res(TFAIL, "Nonexisting command is present!");
+}
+
+static struct tst_test test = {
+ .test_all = do_test,
+ .needs_cmds = (const char *[]) {
+ "mkfs.ext45 >= 1.43.0",
+ NULL
+ }
+};
diff --git a/lib/newlib_tests/test_needs_cmds03.c b/lib/newlib_tests/test_needs_cmds03.c
new file mode 100644
index 000000000..c50077f4e
--- /dev/null
+++ b/lib/newlib_tests/test_needs_cmds03.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021 Yang Xu <xuyang2018.jy@fujitsu.com>
+ */
+
+/*
+ * Test Illegal format by using Illegal operation.
+ */
+
+#include <stdio.h>
+#include "tst_test.h"
+
+static void do_test(void)
+{
+ tst_res(TFAIL, "Wrong operator was evaluated!");
+}
+
+static struct tst_test test = {
+ .test_all = do_test,
+ .needs_cmds = (const char *[]) {
+ "mkfs.ext4 ! 1.43.0",
+ NULL
+ }
+};
diff --git a/lib/newlib_tests/test_needs_cmds04.c b/lib/newlib_tests/test_needs_cmds04.c
new file mode 100644
index 000000000..5d05ed46d
--- /dev/null
+++ b/lib/newlib_tests/test_needs_cmds04.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021 Yang Xu <xuyang2018.jy@fujitsu.com>
+ */
+
+/*
+ * Test Illegal format by using incomplete version.
+ */
+
+#include <stdio.h>
+#include "tst_test.h"
+
+static void do_test(void)
+{
+ tst_res(TFAIL, "Incomplete version was parsed!");
+}
+
+static struct tst_test test = {
+ .test_all = do_test,
+ .needs_cmds = (const char *[]) {
+ "mkfs.ext4 > 1.43",
+ NULL
+ }
+};
diff --git a/lib/newlib_tests/test_needs_cmds05.c b/lib/newlib_tests/test_needs_cmds05.c
new file mode 100644
index 000000000..f4b509b68
--- /dev/null
+++ b/lib/newlib_tests/test_needs_cmds05.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021 Yang Xu <xuyang2018.jy@fujitsu.com>
+ */
+
+/*
+ * Test Illegal format by using version that has garbage.
+ */
+
+#include <stdio.h>
+#include "tst_test.h"
+
+static void do_test(void)
+{
+ tst_res(TFAIL, "Garbage version was parsed!");
+}
+
+static struct tst_test test = {
+ .test_all = do_test,
+ .needs_cmds = (const char *[]) {
+ "mkfs.ext4 > 1.43.0-1",
+ NULL
+ }
+};
diff --git a/lib/newlib_tests/test_needs_cmds06.c b/lib/newlib_tests/test_needs_cmds06.c
new file mode 100644
index 000000000..f1234820e
--- /dev/null
+++ b/lib/newlib_tests/test_needs_cmds06.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021 Yang Xu <xuyang2018.jy@fujitsu.com>
+ */
+
+/*
+ * Test Illegal format with garbage.
+ */
+
+#include <stdio.h>
+#include "tst_test.h"
+
+static void do_test(void)
+{
+ tst_res(TFAIL, "Garbage format was parsed!");
+}
+
+static struct tst_test test = {
+ .test_all = do_test,
+ .needs_cmds = (const char *[]) {
+ "mkfs.ext4 > 1.43.0 2",
+ NULL
+ }
+};
diff --git a/lib/newlib_tests/test_needs_cmds07.c b/lib/newlib_tests/test_needs_cmds07.c
new file mode 100644
index 000000000..e2d2643f4
--- /dev/null
+++ b/lib/newlib_tests/test_needs_cmds07.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021 Yang Xu <xuyang2018.jy@fujitsu.com>
+ */
+
+/*
+ * Test non-existed cmd whether still can be detected.
+ */
+
+#include <stdio.h>
+#include "tst_test.h"
+
+static void do_test(void)
+{
+ tst_res(TFAIL, "Nonexisting command is present!");
+}
+
+static struct tst_test test = {
+ .test_all = do_test,
+ .needs_cmds = (const char *[]) {
+ "mkfs.ext45",
+ NULL
+ }
+};
diff --git a/lib/newlib_tests/test_needs_cmds08.c b/lib/newlib_tests/test_needs_cmds08.c
new file mode 100644
index 000000000..acc28d926
--- /dev/null
+++ b/lib/newlib_tests/test_needs_cmds08.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021 Yang Xu <xuyang2018.jy@fujitsu.com>
+ */
+
+/*
+ * Test mkfs.xfs that it doesn't have own parser and table_get function
+ * at the version_parsers structure in lib/tst_cmd.c.
+ * So it should report parser function for this cmd is not implemented.
+ */
+
+#include <stdio.h>
+#include "tst_test.h"
+
+static void do_test(void)
+{
+ tst_res(TFAIL, "Nonexisting parser function for mkfs.xfs is present!");
+}
+
+static struct tst_test test = {
+ .test_all = do_test,
+ .needs_cmds = (const char *[]) {
+ "mkfs.xfs",
+ "mkfs.xfs >= 4.20.0",
+ NULL
+ }
+};
diff --git a/lib/tst_cmd.c b/lib/tst_cmd.c
index 7446249f9..229848c28 100644
--- a/lib/tst_cmd.c
+++ b/lib/tst_cmd.c
@@ -28,10 +28,21 @@
#include <signal.h>
#include "test.h"
#include "tst_cmd.h"
+#include "tst_private.h"

#define OPEN_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define OPEN_FLAGS (O_WRONLY | O_APPEND | O_CREAT)

+enum cmd_op {
+ OP_GE, /* >= */
+ OP_GT, /* > */
+ OP_LE, /* <= */
+ OP_LT, /* < */
+ OP_EQ, /* == */
+ OP_NE, /* != */
+};
+
+
int tst_cmd_fds_(void (cleanup_fn)(void),
const char *const argv[],
int stdout_fd,
@@ -176,3 +187,160 @@ int tst_system(const char *command)
signal(SIGCHLD, old_handler);
return ret;
}
+
+static int mkfs_ext4_version_parser(void)
+{
+ FILE *f;
+ int rc, major, minor, patch;
+
+ f = popen("mkfs.ext4 -V 2>&1", "r");
+ if (!f) {
+ tst_resm(TWARN, "Could not run mkfs.ext4 -V 2>&1 cmd");
+ return -1;
+ }
+
+ rc = fscanf(f, "mke2fs %d.%d.%d", &major, &minor, &patch);
+ pclose(f);
+ if (rc != 3) {
+ tst_resm(TWARN, "Unable to parse mkfs.ext4 version");
+ return -1;
+ }
+
+ return major * 10000 + minor * 100 + patch;
+}
+
+static int mkfs_ext4_version_table_get(char *version)
+{
+ int major, minor, patch;
+ int len;
+
+ if (sscanf(version, "%u.%u.%u %n", &major, &minor, &patch, &len) != 3) {
+ tst_resm(TWARN, "Illegal version(%s), should use format like 1.43.0", version);
+ return -1;
+ }
+
+ if (len != (int)strlen(version)) {
+ tst_resm(TWARN, "Grabage after version");
+ return -1;
+ }
+
+ return major * 10000 + minor * 100 + patch;
+}
+
+static struct version_parser {
+ const char *cmd;
+ int (*parser)(void);
+ int (*table_get)(char *version);
+} version_parsers[] = {
+ {"mkfs.ext4", mkfs_ext4_version_parser, mkfs_ext4_version_table_get},
+ {},
+};
+
+void tst_check_cmd(const char *cmd)
+{
+ struct version_parser *p;
+ char *cmd_token, *op_token, *version_token, *next, *str;
+ char path[PATH_MAX];
+ char parser_cmd[100];
+ int ver_parser, ver_get;
+ int op_flag = 0;
+
+ strcpy(parser_cmd, cmd);
+
+ cmd_token = strtok_r(parser_cmd, " ", &next);
+ op_token = strtok_r(NULL, " ", &next);
+ version_token = strtok_r(NULL, " ", &next);
+ str = strtok_r(NULL, " ", &next);
+
+ if (tst_get_path(cmd_token, path, sizeof(path)))
+ tst_brkm(TCONF, NULL, "Couldn't find '%s' in $PATH", cmd_token);
+
+ if (!op_token)
+ return;
+
+ if (!strcmp(op_token, ">="))
+ op_flag = OP_GE;
+ else if (!strcmp(op_token, ">"))
+ op_flag = OP_GT;
+ else if (!strcmp(op_token, "<="))
+ op_flag = OP_LE;
+ else if (!strcmp(op_token, "<"))
+ op_flag = OP_LT;
+ else if (!strcmp(op_token, "=="))
+ op_flag = OP_EQ;
+ else if (!strcmp(op_token, "!="))
+ op_flag = OP_NE;
+ else
+ tst_brkm(TCONF, NULL, "Invalid op(%s)", op_token);
+
+ if (!version_token || str) {
+ tst_brkm(TCONF, NULL,
+ "Illegal format(%s), should use format like mkfs.ext4 >= 1.43.0",
+ cmd);
+ }
+
+ for (p = &version_parsers[0]; p->cmd; p++) {
+ if (!strcmp(p->cmd, cmd_token)) {
+ tst_resm(TINFO, "Parsing %s version", p->cmd);
+ break;
+ }
+ }
+
+ if (!p->cmd) {
+ tst_brkm(TBROK, NULL, "No version parser for %s implemented!",
+ cmd_token);
+ }
+
+ ver_parser = p->parser();
+ if (ver_parser < 0)
+ tst_brkm(TBROK, NULL, "Failed to parse %s version", p->cmd);
+
+ ver_get = p->table_get(version_token);
+ if (ver_get < 0)
+ tst_brkm(TBROK, NULL, "Failed to get %s version", p->cmd);
+
+ switch (op_flag) {
+ case OP_GE:
+ if (ver_parser < ver_get) {
+ tst_brkm(TCONF, NULL, "%s required >= %d, but got %d, "
+ "the version is required in order run the test.",
+ cmd, ver_get, ver_parser);
+ }
+ break;
+ case OP_GT:
+ if (ver_parser <= ver_get) {
+ tst_brkm(TCONF, NULL, "%s required > %d, but got %d, "
+ "the version is required in order run the test.",
+ cmd, ver_get, ver_parser);
+ }
+ break;
+ case OP_LE:
+ if (ver_parser > ver_get) {
+ tst_brkm(TCONF, NULL, "%s required <= %d, but got %d, "
+ "the version is required in order run the test.",
+ cmd, ver_get, ver_parser);
+ }
+ break;
+ case OP_LT:
+ if (ver_parser >= ver_get) {
+ tst_brkm(TCONF, NULL, "%s required < %d, but got %d, "
+ "the version is required in order run the test.",
+ cmd, ver_get, ver_parser);
+ }
+ break;
+ case OP_EQ:
+ if (ver_parser != ver_get) {
+ tst_brkm(TCONF, NULL, "%s required == %d, but got %d, "
+ "the version is required in order run the test.",
+ cmd, ver_get, ver_parser);
+ }
+ break;
+ case OP_NE:
+ if (ver_parser == ver_get) {
+ tst_brkm(TCONF, NULL, "%s required != %d, but got %d, "
+ "the version is required in order run the test.",
+ cmd, ver_get, ver_parser);
+ }
+ break;
+ }
+}
diff --git a/lib/tst_test.c b/lib/tst_test.c
index a79275722..c49e30f5b 100644
--- a/lib/tst_test.c
+++ b/lib/tst_test.c
@@ -29,7 +29,7 @@
#include "tst_wallclock.h"
#include "tst_sys_conf.h"
#include "tst_kconfig.h"
-
+#include "tst_private.h"
#include "old_resource.h"
#include "old_device.h"
#include "old_tmpdir.h"
@@ -987,12 +987,10 @@ static void do_setup(int argc, char *argv[])

if (tst_test->needs_cmds) {
const char *cmd;
- char path[PATH_MAX];
int i;

for (i = 0; (cmd = tst_test->needs_cmds[i]); ++i)
- if (tst_get_path(cmd, path, sizeof(path)))
- tst_brk(TCONF, "Couldn't find '%s' in $PATH", cmd);
+ tst_check_cmd(cmd);
}

if (tst_test->needs_drivers) {
--
2.23.0