From: Daniel Latypov <dlatypov@google.com>
To: Kees Cook <keescook@chromium.org>
Cc: David Gow <davidgow@google.com>, Arnd Bergmann <arnd@arndb.de>,
Nathan Chancellor <nathan@kernel.org>,
Nick Desaulniers <ndesaulniers@google.com>,
linux-kernel@vger.kernel.org,
KUnit Development <kunit-dev@googlegroups.com>,
llvm@lists.linux.dev, linux-hardening@vger.kernel.org
Subject: Re: [PATCH] lib: stackinit: Convert to KUnit
Date: Thu, 24 Feb 2022 11:43:40 -0800 [thread overview]
Message-ID: <CAGS_qxrRi0zvGnoi-Ne=wp8xkuFKPzNj9d57eq=51gg5gwX=eA@mail.gmail.com> (raw)
In-Reply-To: <20220224055145.1853657-1-keescook@chromium.org>
On Wed, Feb 23, 2022 at 9:51 PM Kees Cook <keescook@chromium.org> wrote:
>
<snip>
> /* Userspace headers. */
> +#define _GNU_SOURCE
> #include <stdio.h>
> #include <stdint.h>
> +#include <stdlib.h>
> #include <string.h>
> #include <stdbool.h>
> #include <errno.h>
> #include <sys/types.h>
>
> /* Linux kernel-ism stubs for stand-alone userspace build. */
This is neat and esp. so that it works.
But may I ask, what's the value of using this vs UML?
Given this has changed into mainly just a KUnit-compatibility layer,
it feels like it can maybe live as a standalone file, if there's ever
interest in doing this for other tests.
It feels like something that will never quite be "supported", but I
find it neat enough I'd have fun sending some patches to make it more
realistic.
> -#define KBUILD_MODNAME "stackinit"
> -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> -#define pr_err(fmt, ...) fprintf(stderr, pr_fmt(fmt), ##__VA_ARGS__)
> -#define pr_warn(fmt, ...) fprintf(stderr, pr_fmt(fmt), ##__VA_ARGS__)
> -#define pr_info(fmt, ...) fprintf(stdout, pr_fmt(fmt), ##__VA_ARGS__)
> -#define __init /**/
> -#define __exit /**/
> +#define TEST_PASS 0
> +#define TEST_SKIP 1
> +#define TEST_FAIL 2
> +struct kunit {
> + int status;
> + char *msg;
> +};
> +struct kunit_case {
> + void (*run_case)(struct kunit *test);
> + const char *name;
> +};
> +struct kunit_suite {
> + const char *name;
> + const struct kunit_case *test_cases;
> +};
> +#define KUNIT_CASE(test_name) { .run_case = test_name, .name = #test_name }
> +
> +#define KUNIT_ASSERT_TRUE_MSG(test, expr, fmt, ...) \
> +do { \
> + if (!(expr)) { \
> + if (test->status != TEST_SKIP) \
> + test->status = TEST_FAIL; \
> + if (test->msg) \
> + free(test->msg); \
> + asprintf(&test->msg, fmt, ##__VA_ARGS__); \
> + } \
> +} while (0)
This looks more like KUNIT_EXPECT_TRUE_MSG(), since this macro won't
abort the test if the expectation fails.
Looking at the code, it looks like we do want the ability to abort.
Perhaps we can do what Googletest does in C++ and just add in a `return;`?
It has some annoying implications, like using them from helper
functions doesn't work as one would expect.
But people seem to be doing fine with that tradeoff in C++ land.
> +
> +#define KUNIT_ASSERT_EQ_MSG(test, left, right, fmt, ...) \
> + KUNIT_ASSERT_TRUE_MSG(test, (left) == (right), fmt, ##__VA_ARGS__)
Very optional:
It might be nice to show the expressions automatically on failure.
We could implement that via something like
KUNIT_ASSERT_TRUE_MSG(test, (left) == (right), #left " != " #right ":
" fmt, ##__VA_ARGS__);
E.g.
KUNIT_ASSERT_EQ_MSG(test, 2+2, 5, "math is broken")
=> 2+2 != 5: math is broken
But I can see that being a bit too complicated to want to do here.
And the failure messages we had before are already decent at giving context.
> +
> +#define kunit_skip(test, fmt, ...) \
> +do { \
> + test->status = TEST_SKIP; \
> + if (test->msg) \
> + free(test->msg); \
> + asprintf(&test->msg, fmt, ##__VA_ARGS__); \
> +} while (0)
Similarly, this has no control flow implications, so the current
semantics match kunit_mark_skipped().
But looking at the code, I think we want to abort early here too.
> +
> #define __user /**/
> #define noinline __attribute__((__noinline__))
> #define __aligned(x) __attribute__((__aligned__(x)))
> @@ -59,16 +102,44 @@ typedef uint16_t u16;
> typedef uint32_t u32;
> typedef uint64_t u64;
>
> -#define module_init(func) static int (*do_init)(void) = func
> -#define module_exit(func) static void (*do_exit)(void) = func
> -#define MODULE_LICENSE(str) int main(void) { \
> - int rc; \
> - /* License: str */ \
> - rc = do_init(); \
> - if (rc == 0) \
> - do_exit(); \
> - return rc; \
> - }
> +#define MODULE_LICENSE(str) /* */
> +
> +int do_kunit_test_suite(struct kunit_suite *suite)
> +{
> + const struct kunit_case *test_case;
> + int rc = 0;
> +
> + for (test_case = suite->test_cases; test_case->run_case; test_case++) {
> + struct kunit test = { };
> +
> + test_case->run_case(&test);
> + switch (test.status) {
> + default:
> + case TEST_FAIL:
> + fprintf(stderr, "FAIL: %s%s%s", test_case->name,
> + test.msg ? ": " : "",
> + test.msg ?: "\n");
> + rc = 1;
> + break;
> + case TEST_SKIP:
> + fprintf(stdout, "XFAIL: %s%s%s", test_case->name,
> + test.msg ? ": " : "",
> + test.msg ?: "\n");
> + break;
> + case TEST_PASS:
> + fprintf(stdout, "ok: %s\n", test_case->name);
> + break;
> + }
> + if (test.msg)
> + free(test.msg);
> + }
> + return rc;
> +}
> +
> +#define kunit_test_suite(suite) \
> +int main(void) { \
> + return do_kunit_test_suite(&(suite)); \
> +}
very optional:
emulating kunit_test_suites() might be more future-proof here, if we
ever want to setup more suites.
There's little reason to do so right now given the lack of init and
exit support.
Like the stuff above, it wouldn't be hard to do, but I can see it not
being worth the extra code, i.e.
#define kunit_test_suites(suites...) \
int main(void) {
static struct kunit_suite *suites =[] = { __VA_ARGS__ };
int i, ret = 0;
for (i = 0; i < ARRAY_SIZE(suites); ++i)
ret += do_kunit_test_suite(suites[i]);
return ret;
}
next prev parent reply other threads:[~2022-02-24 19:43 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-02-24 5:51 [PATCH] lib: stackinit: Convert to KUnit Kees Cook
2022-02-24 19:43 ` Daniel Latypov [this message]
2022-02-25 1:17 ` Kees Cook
2022-02-25 6:53 ` David Gow
2022-03-22 14:30 ` Geert Uytterhoeven
2022-03-23 15:46 ` Kees Cook
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='CAGS_qxrRi0zvGnoi-Ne=wp8xkuFKPzNj9d57eq=51gg5gwX=eA@mail.gmail.com' \
--to=dlatypov@google.com \
--cc=arnd@arndb.de \
--cc=davidgow@google.com \
--cc=keescook@chromium.org \
--cc=kunit-dev@googlegroups.com \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=llvm@lists.linux.dev \
--cc=nathan@kernel.org \
--cc=ndesaulniers@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).