From f18331dd1e2ff0772c71abf4c444de6487dbce87 Mon Sep 17 00:00:00 2001 From: Cyril Hrubis Date: Tue, 1 Nov 2016 15:07:12 +0100 Subject: [PATCH] lib: Add new tst_kvcmp for shell Add tst_kvcmp inspired by the shell test syntax. Now we can write the kernel version checks as: if tst_kvcmp -ge 3.8.0 -a -le 4.3.2; then tst_brk TCONF "Needs kernel older than 3.8.0 or newer than 4.3.2" fi Signed-off-by: Cyril Hrubis --- doc/test-writing-guidelines.txt | 38 +++++-- include/tst_kvercmp.h | 2 +- lib/tst_kvercmp.c | 41 ++++--- lib/tst_test.c | 6 +- testcases/lib/.gitignore | 1 + testcases/lib/Makefile | 2 +- testcases/lib/tst_kvcmp.c | 191 ++++++++++++++++++++++++++++++++ 7 files changed, 254 insertions(+), 27 deletions(-) create mode 100644 testcases/lib/tst_kvcmp.c diff --git a/doc/test-writing-guidelines.txt b/doc/test-writing-guidelines.txt index 004b169142c..c2c9aa5cf5c 100644 --- a/doc/test-writing-guidelines.txt +++ b/doc/test-writing-guidelines.txt @@ -1682,21 +1682,41 @@ and 'tst_resm TFAIL' otherwise. 'EXPECT_FAIL' does vice versa. Output redirection rules are the same as for the 'ROD' function. In addition to that, 'EXPECT_FAIL' always redirects the command's stderr to '/dev/null'. -tst_kvercmp -+++++++++++ +tst_kvcmp ++++++++++ -This command takes the kernel version as a parameter and compares the it with -the currently running kernel. It works mostly as the C function but the result -is mapped into the exit value as follows: +This command compares the currently running kernel version given conditions +with syntax similar to the shell test command. + +[source,sh] +------------------------------------------------------------------------------- +# Exit the test if kernel version is older or equal to 2.6.8 +if tst_kvcmp -le 2.6.8; then + tst_brk TCONF "Kernel newer than 2.6.8 is needed" +fi + +# Exit the test if kernel is newer than 3.8 and older than 4.0.1 +if tst_kvcmp -gt 3.8 -a -lt 4.0.1 + tst_brk TCONF "Kernel must be older than 3.8 or newer than 4.0.1" +fi +------------------------------------------------------------------------------- [options="header"] |======================================================================= -| C function return | Shell command exit value | Explanation -| < 0 | 0 | Kernel is older -| 0 | 1 | Kernel version is equal -| > 0 | 2 | Kernel is newer +| expression | description +| -eq kver | Returns true if kernel version is equal +| -ne kver | Returns true if kernel version is not equal +| -gt kver | Returns true if kernel version is greater +| -ge kver | Returns true if kernel version is greater or equal +| -lt kver | Returns true if kernel version is lesser +| -le kver | Returns true if kernel version is lesser or equal +| -a | Does logical and between two expressions +| -o | Does logical or between two expressions |======================================================================= +The format for kernel version has to either be with one dot e.g. '2.6' or with +two dots e.g. '4.8.1'. + .tst_fs_has_free [source,sh] ------------------------------------------------------------------------------- diff --git a/include/tst_kvercmp.h b/include/tst_kvercmp.h index e229d66d303..6b0414a5fbb 100644 --- a/include/tst_kvercmp.h +++ b/include/tst_kvercmp.h @@ -39,7 +39,7 @@ * * If version has only two numbers, i.e. 2.4 the third integer is set to zero. */ -void tst_parse_kver(const char *str_kver, int *v1, int *v2, int *v3); +int tst_parse_kver(const char *str_kver, int *v1, int *v2, int *v3); /* * Compare given kernel version with currently running kernel. diff --git a/lib/tst_kvercmp.c b/lib/tst_kvercmp.c index 068befce4c6..c497f5d64aa 100644 --- a/lib/tst_kvercmp.c +++ b/lib/tst_kvercmp.c @@ -39,13 +39,10 @@ static char *parse_digit(const char *str, int *d) *d = v; - if (*end != '.') - return NULL; - - return end + 1; + return end; } -void tst_parse_kver(const char *str_kver, int *v1, int *v2, int *v3) +int tst_parse_kver(const char *str_kver, int *v1, int *v2, int *v3) { const char *str = str_kver; @@ -54,21 +51,31 @@ void tst_parse_kver(const char *str_kver, int *v1, int *v2, int *v3) *v3 = 0; if (!(str = parse_digit(str, v1))) - goto err; + return 1; + + if (*(str++) != '.') + return 1; if (!(str = parse_digit(str, v2))) - goto err; + return 1; + + /* + * Check for a short version e.g '2.4' + */ + if (*str == ' ' || *str == '\0') + return 0; + + if (*(str++) != '.') + return 1; /* - * We ignore all errors here in order not to fail with versions as - * "2.4" or "2.6.18". + * Ignore rest of the string in order not to break on versions as + * 4.8.1-52-default. */ - parse_digit(str, v3); + if (!parse_digit(str, v3)) + return 1; - return; -err: - tst_resm(TWARN, - "Invalid kernel version %s, expected %%d.%%d.%%d", str_kver); + return 0; } int tst_kvercmp(int r1, int r2, int r3) @@ -78,7 +85,11 @@ int tst_kvercmp(int r1, int r2, int r3) struct utsname uval; uname(&uval); - tst_parse_kver(uval.release, &a1, &a2, &a3); + if (tst_parse_kver(uval.release, &a1, &a2, &a3)) { + tst_resm(TWARN, + "Invalid kernel version %s, expected %%d.%%d.%%d", + uval.release); + } testver = (r1 << 16) + (r2 << 8) + r3; currver = (a1 << 16) + (a2 << 8) + a3; diff --git a/lib/tst_test.c b/lib/tst_test.c index 06f0339a3be..c48d718779d 100644 --- a/lib/tst_test.c +++ b/lib/tst_test.c @@ -519,7 +519,11 @@ void check_kver(void) { int v1, v2, v3; - tst_parse_kver(tst_test->min_kver, &v1, &v2, &v3); + if (tst_parse_kver(tst_test->min_kver, &v1, &v2, &v3)) { + tst_res(TWARN, + "Invalid kernel version %s, expected %%d.%%d.%%d", + tst_test->min_kver); + } if (tst_kvercmp(v1, v2, v3) < 0) { tst_brk(TCONF, "The test requires kernel %s or newer", diff --git a/testcases/lib/.gitignore b/testcases/lib/.gitignore index 0a28d6151f5..77acb57a259 100644 --- a/testcases/lib/.gitignore +++ b/testcases/lib/.gitignore @@ -2,3 +2,4 @@ tst_sleep tst_random tst_checkpoint tst_rod +tst_kvcmp diff --git a/testcases/lib/Makefile b/testcases/lib/Makefile index 4c7a61ce510..0bb0bce2f10 100644 --- a/testcases/lib/Makefile +++ b/testcases/lib/Makefile @@ -26,6 +26,6 @@ include $(top_srcdir)/include/mk/testcases.mk INSTALL_TARGETS := *.sh -MAKE_TARGETS := tst_sleep tst_random tst_checkpoint tst_rod +MAKE_TARGETS := tst_sleep tst_random tst_checkpoint tst_rod tst_kvcmp include $(top_srcdir)/include/mk/generic_leaf_target.mk diff --git a/testcases/lib/tst_kvcmp.c b/testcases/lib/tst_kvcmp.c new file mode 100644 index 00000000000..fbae90108fd --- /dev/null +++ b/testcases/lib/tst_kvcmp.c @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2016 Cyril Hrubis + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define TST_NO_DEFAULT_MAIN +#include +#include +#include + +enum op { + EQ, + NE, + GE, + GT, + LE, + LT, + AND, + OR, + ERR, +}; + +static enum op strtop(const char *op) +{ + if (!strcmp(op, "-eq")) + return EQ; + + if (!strcmp(op, "-ne")) + return NE; + + if (!strcmp(op, "-ge")) + return GE; + + if (!strcmp(op, "-gt")) + return GT; + + if (!strcmp(op, "-le")) + return LE; + + if (!strcmp(op, "-lt")) + return LT; + + if (!strcmp(op, "-a")) + return AND; + + if (!strcmp(op, "-o")) + return OR; + + return ERR; +} + +static void help(const char *fname) +{ + printf("usage: %s -eq|-ne|-gt|-ge|-lt|-le kver [-a|-o] ...\n\n", fname); + printf("-eq kver\tReturns true if kernel version is equal\n"); + printf("-ne kver\tReturns true if kernel version is not equal\n"); + printf("-gt kver\tReturns true if kernel version is greater\n"); + printf("-ge kver\tReturns true if kernel version is greater or equal\n"); + printf("-lt kver\tReturns true if kernel version is lesser\n"); + printf("-le kver\tReturns true if kernel version is lesser or equal\n"); + printf("-a \t\tDoes logical and between two expressions\n"); + printf("-o \t\tDoes logical or between two expressions\n\n"); + printf("Kernel version format has either one or two dots: "); + printf("'2.6' or '4.8.1'\n"); +} + +int main(int argc, char *argv[]) +{ + int i = 1; + int ret = -1; + int v1, v2, v3; + enum op prev_op = ERR; + + if (argc <= 1 || !strcmp(argv[1], "-h")) { + help(argv[0]); + return 0; + } + + while (i < argc) { + const char *strop = argv[i++]; + const char *strkver; + int res; + + enum op op = strtop(strop); + + switch (op) { + case EQ: + case NE: + case GE: + case GT: + case LE: + case LT: + if (ret != -1 && prev_op == ERR) { + fprintf(stderr, "Expected -a or -o\n"); + return 2; + } + + if (i >= argc) { + fprintf(stderr, + "Expected kernel version after '%s'\n", + strop); + return 2; + } + + strkver = argv[i++]; + + if (tst_parse_kver(strkver, &v1, &v2, &v3)) { + fprintf(stderr, + "Invalid kernel version '%s'\n", + strkver); + return 2; + } + break; + case AND: + case OR: + if (ret == -1) { + fprintf(stderr, + "The %s must follow expression\n", + strop); + return 2; + } + prev_op = op; + continue; + break; + case ERR: + fprintf(stderr, "Invalid operation %s\n", argv[i]); + return 2; + } + + res = tst_kvercmp(v1, v2, v3); + + switch (op) { + case EQ: + res = (res == 0); + break; + case NE: + res = (res != 0); + break; + case GE: + res = (res >= 0); + break; + case GT: + res = (res > 0); + break; + case LE: + res = (res <= 0); + break; + case LT: + res = (res < 0); + break; + default: + break; + } + + switch (prev_op) { + case ERR: + ret = res; + break; + case AND: + ret = ret && res; + prev_op = ERR; + break; + case OR: + ret = ret || res; + prev_op = ERR; + break; + default: + break; + } + } + + if (prev_op != ERR) { + fprintf(stderr, "Useless -a or -o at the end\n"); + return 2; + } + + return !ret; +}