Re: [LTP] [PATCH 1/2] lib: add cmd parse handler in needs_cmd

xuyang2018.jy
2021-12-07T01:20:46Z

Hi Cyril
> Hi!
>> Testcase ie statx05 needs mkfs.ext4>= 1.43.0 because of encrypt feature.
>>
>> As Cyril suggested, add cmd parser handler in needs_cmd.
>>
>> The difference logic about from before, we don't report not-found error and think it is
>> cmd version string(need to use tst_version_parser) if tst_get_path fails in tst_test.c.
>>
>> In tst_version_parser function, use strtok_r to split cmd_token,op_token,version_token.
>> It only supports six operations '>=' '<=' '>''<' '==' '!='.
>>
>> Currently, this tst_version_parser 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>
>> ---
>> doc/c-test-api.txt | 14 +++
>> include/tst_test.h | 1 +
>> include/tst_version_parser.h | 15 +++
>> lib/newlib_tests/.gitignore | 5 +
>> lib/newlib_tests/test_version_parser01.c | 25 ++++
>> lib/newlib_tests/test_version_parser02.c | 24 ++++
>> lib/newlib_tests/test_version_parser03.c | 24 ++++
>> lib/newlib_tests/test_version_parser04.c | 24 ++++
>> lib/newlib_tests/test_version_parser05.c | 24 ++++
>> lib/tst_test.c | 2 +-
>> lib/tst_version_parser.c | 148 +++++++++++++++++++++++
>> 11 files changed, 305 insertions(+), 1 deletion(-)
>> create mode 100644 include/tst_version_parser.h
>> create mode 100644 lib/newlib_tests/test_version_parser01.c
>> create mode 100644 lib/newlib_tests/test_version_parser02.c
>> create mode 100644 lib/newlib_tests/test_version_parser03.c
>> create mode 100644 lib/newlib_tests/test_version_parser04.c
>> create mode 100644 lib/newlib_tests/test_version_parser05.c
>> create mode 100644 lib/tst_version_parser.c
>>
>> diff --git a/doc/c-test-api.txt b/doc/c-test-api.txt
>> index 64d0630ce..fde6dce0c 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. If you want to support more commands,
>> +please fill your own .parser and .table_get method in the version_parsers structure
>> +of lib/tst_version_parser.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_test.h b/include/tst_test.h
>> index c06a4729b..fd3d4cfee 100644
>> --- a/include/tst_test.h
>> +++ b/include/tst_test.h
>> @@ -44,6 +44,7 @@
>> #include "tst_taint.h"
>> #include "tst_memutils.h"
>> #include "tst_arch.h"
>> +#include "tst_version_parser.h"
>
> Since this is internal functionality that does not make sense to be
> exposed to the tests there is no point in exposing this here, we should
> include it in the tst_test.c instead.
>
Ok, will move into tst_test.c.
>> /*
>> * Reports testcase result.
>> diff --git a/include/tst_version_parser.h b/include/tst_version_parser.h
>> new file mode 100644
>> index 000000000..145043929
>> --- /dev/null
>> +++ b/include/tst_version_parser.h
>> @@ -0,0 +1,15 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Copyright (c) 2021 Yang Xu<xuyang2018.jy@fujitsu.com>
>> + */
>> +
>> +#ifndef TST_VERSION_PARSER_H__
>> +#define TST_VERSION_PARSER_H__
>> +
>> +/*
>> + * Parse the cmd version requirement in needs_cmds member of tst_test
>> + * structure whether is satisfied.
>> + */
>> +void tst_version_parser(const char *cmd);
>
> This should probably be called tst_cmd_check() or something similar.
OK.
>
>> +#endif /* TST_VERSION_PARSER_H__ */
>> diff --git a/lib/newlib_tests/.gitignore b/lib/newlib_tests/.gitignore
>> index cf467b5a0..ef8694d08 100644
>> --- a/lib/newlib_tests/.gitignore
>> +++ b/lib/newlib_tests/.gitignore
>> @@ -46,4 +46,9 @@ test_macros06
>> tst_fuzzy_sync01
>> tst_fuzzy_sync02
>> tst_fuzzy_sync03
>> +test_version_parser01
>> +test_version_parser02
>> +test_version_parser03
>> +test_version_parser04
>> +test_version_parser05
>> test_zero_hugepage
>> diff --git a/lib/newlib_tests/test_version_parser01.c b/lib/newlib_tests/test_version_parser01.c
>> new file mode 100644
>> index 000000000..9e1bd3962
>> --- /dev/null
>> +++ b/lib/newlib_tests/test_version_parser01.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, "Tesing tst_version_parser() functionality OK.");
>> +}
>> +
>> +static struct tst_test test = {
>> + .test_all = do_test,
>> + .needs_cmds = (const char *[]) {
>> + "mkfs.ext4",
>> + "mkfs.ext4>= 1.43.0",
>> + "mkfs.ext4<= 2.0.0",
>> + "mkfs.ext4 != 2.0.0",
>> + "mkfs.ext4> 1.43.0",
>> + "mkfs.ext4< 2.0.0",
>> + NULL
>> + }
>> +};
>> diff --git a/lib/newlib_tests/test_version_parser02.c b/lib/newlib_tests/test_version_parser02.c
>> new file mode 100644
>> index 000000000..5aedaa28f
>> --- /dev/null
>> +++ b/lib/newlib_tests/test_version_parser02.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 cmd.
>> + */
>> +
>> +#include<stdio.h>
>> +#include "tst_test.h"
>> +
>> +static void do_test(void)
>> +{
>> + tst_res(TFAIL, "Tesing tst_version_parser() functionality bad.");
> ^
> "Nonexisting command is present!"
>
>
> Or something along these line.
OK.
>
>> +}
>> +
>> +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_version_parser03.c b/lib/newlib_tests/test_version_parser03.c
>> new file mode 100644
>> index 000000000..8f96e68d2
>> --- /dev/null
>> +++ b/lib/newlib_tests/test_version_parser03.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, "Tesing tst_version_parser() functionality bad.");
> ^
> "Wrong operator was evaluated!"
OK.
>
>> +}
>> +
>> +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_version_parser04.c b/lib/newlib_tests/test_version_parser04.c
>> new file mode 100644
>> index 000000000..461f673df
>> --- /dev/null
>> +++ b/lib/newlib_tests/test_version_parser04.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 version.
>> + */
>> +
>> +#include<stdio.h>
>> +#include "tst_test.h"
>> +
>> +static void do_test(void)
>> +{
>> + tst_res(TFAIL, "Tesing tst_version_parser() functionality bad.");
> ^
> "Incomplete version was parsed!"
OK.
>> +}
>> +
>> +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_version_parser05.c b/lib/newlib_tests/test_version_parser05.c
>> new file mode 100644
>> index 000000000..1bfe24f73
>> --- /dev/null
>> +++ b/lib/newlib_tests/test_version_parser05.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, "Tesing tst_version_parser() functionality bad.");
> ^
> "Nonexisting command is present!"
OK.
>> +}
>> +
>> +static struct tst_test test = {
>> + .test_all = do_test,
>> + .needs_cmds = (const char *[]) {
>> + "mkfs.ext45",
>> + NULL
>> + }
>> +};
>> diff --git a/lib/tst_test.c b/lib/tst_test.c
>> index a79275722..a9e95a3d7 100644
>> --- a/lib/tst_test.c
>> +++ b/lib/tst_test.c
>> @@ -992,7 +992,7 @@ static void do_setup(int argc, char *argv[])
>>
>> 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_version_parser(cmd);
>
> Why do we keep the tst_get_path() here? We can as well get rid of that
> and just call the function and handle the case without the version
> there.
Yes, will use tst_cmd_check directly here.
>
>> }
>>
>> if (tst_test->needs_drivers) {
>> diff --git a/lib/tst_version_parser.c b/lib/tst_version_parser.c
>> new file mode 100644
>> index 000000000..296e25ea2
>> --- /dev/null
>> +++ b/lib/tst_version_parser.c
>> @@ -0,0 +1,148 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Copyright (c) 2021 Yang Xu<xuyang2018.jy@fujitsu.com>
>> + */
>> +
>> +#include<stdio.h>
>> +#include<string.h>
>> +#include<stdlib.h>
>> +
>> +#define TST_NO_DEFAULT_MAIN
>> +#include "tst_version_parser.h"
>> +#include "tst_test.h"
>> +
>> +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_res(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_res(TWARN, "Unable to parse mkfs.ext4 version");
>> + return -1;
>> + }
>> +
>> + return major * 10000 + minor * 100 + patch;
>> +}
>> +
>> +static int mkfs_ext4_version_table_get(char *version)
>> +{
>> + char *major, *minor, *patch, *next, *str;
>> + char table_version[100];
>> +
>> + strcpy(table_version, version);
>> +
>> + major = strtok_r(table_version, ".",&next);
>> + minor = strtok_r(NULL, ".",&next);
>> + patch = strtok_r(NULL, ".",&next);
>> + str = strtok_r(NULL, ".",&next);
>
> I wonder if we should do a sscanf() here. Note that with the atoi() we
> will pass on strings that have garbage after the numbers such as:
>
> "1ab.2c.3dd"
>
> What about:
>
> unsigned int maj, min, patch;
> int len;
>
> if (sscanf(version, "%u.%u.%u %n",&maj,&min,&patch, len) != 3) {
> tst_res(TWARN, "Illegal version ...");
> return -1;
> }
>
> if (len != strlen(version)) {
> tst_res(TWARN, "Garbage after version");
> return -1;
> }
>
> return maj * 10000 + min * 100 + patch;
Yes, it looks better. I will try.
>
>> + if (!major || !minor || !patch || str) {
>> + tst_res(TWARN, "Illegal version(%s), should use format like 1.43.0", version);
>> + return -1;
>> + }
>> +
>> + return atoi(major) * 10000 + atoi(minor) * 100 + atoi(patch);
>> +}
>> +
>> +static struct version_parser {
>> + const char *cmd;
>> + int (*parser)(void);
>> + int (*table_get)(char *version);
>> +} version_parsers[] = {
>> + {.cmd = "mkfs.ext4", .parser = mkfs_ext4_version_parser, .table_get = mkfs_ext4_version_table_get},
>> + {},
>> +};
>> +
>> +void tst_version_parser(const char *cmd)
>> +{
>> + struct version_parser *p;
>> + char *cmd_token, *op, *version, *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 = strtok_r(NULL, " ",&next);
>
> Shouldn't we just handle the case where op == NULL as a single command
> here? That would make the code much cleaner.
>
Yes.
> I.e.:
> if (tst_get_path(cmd_token, path, sizeof(path)))
> tst_brk(TCONF, "Couldn't find '%s' in $PATH", cmd_token);
>
> if (!op)
> return;
>
>> + version = strtok_r(NULL, " ",&next);
>> + str = strtok_r(NULL, " ",&next);
>
>
>
>> + if (!cmd_token || !op || !version || str)
>> + tst_brk(TCONF,
>> + "Illegal fomart(%s), should use format like mkfs.ext4>= 1.43.0", cmd);
>
> Then we can just check the version and str here as:
>
> if (!version || str)
> tst_brk(TCONF, "Illegal format ...");
>
>> + if (tst_get_path(cmd_token, path, sizeof(path)))
>> + tst_brk(TCONF, "Couldn't find '%s' in $PATH", cmd_token);
>> +
>> + if (!strcmp(op, ">="))
>> + op_flag = 1;
>> +
>> + if (!strcmp(op, ">"))
>> + op_flag = 2;
>> +
>> + if (!strcmp(op, "<="))
>> + op_flag = 3;
>> +
>> + if (!strcmp(op, "<"))
>> + op_flag = 4;
>> +
>> + if (!strcmp(op, "=="))
>> + op_flag = 5;
>> +
>> + if (!strcmp(op, "!="))
>> + op_flag = 6;
>> +
>> + if (!op_flag)
>> + tst_brk(TCONF, "Invalid op(%s)", op);
>
> This is usually structured as else if (..) in C as:
>
> if (!strcmp(op, ">="))
> op_flag = ...
> else if (!strcmp(op, ">")
> op_flag = ...
> else ....
Ok.
>
> Also maybe an enum would make the code easier to read as:
>
> enum op {
> OP_GE, /*>= */
> OP_GT, /*> */
> OP_LE, /*<= */
> OP_LT, /*< */
> OP_EQ, /* == */
> OP_NE, /* != */
> };
>
It looks better.
> Then we would do:
>
> if (!strcmp(op, ">="))
> op_flag = OP_GE;
> else ...
>
>> + for (p =&version_parsers[0]; p; p++) {
>> + if (!strcmp(p->cmd, cmd_token))
>> + tst_res(TINFO, "Parsing %s version", p->cmd);
>> + break;
>
> This does not work without curly braces around the blok, right?
Yes,
>
>> + }
>> +
>> + if (!p->cmd)
>> + tst_brk(TBROK, "No version parser for %s implemented!", cmd_token);
>> +
>> + ver_parser = p->parser();
>> + if (ver_parser< 0)
>> + tst_brk(TBROK, "Failed to parse %s version", p->cmd);
>> +
>> + ver_get = p->table_get(version);
>> + if (ver_get< 0)
>> + tst_brk(TBROK, "Failed to get %s version", p->cmd);
>> +
>> + switch (op_flag) {
>> + case 1:
>> + if (ver_parser< ver_get)
>> + tst_brk(TCONF, "cmd(%s) expected>= %d, but got %d", cmd, ver_get, ver_parser);
> ^
> "%s required>= %d, but got %d", ...
>
> The version is required in order run the test.
Got it.
>
>> + break;
>> + case 2:
>> + if (ver_parser<= ver_get)
>> + tst_brk(TCONF, "cmd(%s) expected> %d, but got %d", cmd, ver_get, ver_parser);
>> + break;
>> + case 3:
>> + if (ver_parser> ver_get)
>> + tst_brk(TCONF, "cmd(%s) expected<= %d, but got %d", cmd, ver_get, ver_parser);
>> + break;
>> + case 4:
>> + if (ver_parser>= ver_get)
>> + tst_brk(TCONF, "cmd(%s) expected< %d, but got %d", cmd, ver_get, ver_parser);
>> + break;
>> + case 5:
>> + if (ver_parser != ver_get)
>> + tst_brk(TCONF, "cmd(%s) expected == %d, but got %d", cmd, ver_get, ver_parser);
>> + break;
>> + case 6:
>> + if (ver_parser == ver_get)
>> + tst_brk(TCONF, "cmd(%s) expected != %d, but got %d", cmd, ver_get, ver_parser);
>> + break;
>> + }
>
>
> And the same for the rest of the messages.
Wil change.
>
>> +}
>
> Looks mostly good, minus the minor things I've pointed out.
Thanks for your review, will send a v2 as soon as possible.

Best Regards
Yang Xu
>