Received: by 2002:a05:6a10:9e8c:0:0:0:0 with SMTP id y12csp659429pxx; Wed, 28 Oct 2020 13:41:13 -0700 (PDT) X-Google-Smtp-Source: ABdhPJyO8MZlNqOpYfmxkrfSpxMcLjipVpDApdfU+zbzHP0L0HIiPRqI1Ad3J3pNaPlykJ+HFmPh X-Received: by 2002:a17:906:8546:: with SMTP id h6mr828752ejy.479.1603917673407; Wed, 28 Oct 2020 13:41:13 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1603917673; cv=none; d=google.com; s=arc-20160816; b=HPnUH3+/WCqvhTI/TOEgqvn/Hvk2eIvc7Xgh/dPz6LQGLpTAFyVuDqvadrwxOXdBMh RC4Qw30TmkhBDvahTs5Kv/1/4+UgX+WNCqGAfOWUHKMu075p5PAvBP+p/OnIrcpNaGju D9tS0cqrb5b178jTlAAPP4rW1yp4cRj7g2TklJioEL/EdrybMbiNuKtRFSmOHWc4u3rT u5fbIucGMMehBfs3KBRDsFpPHFJuxO3IVq5gPuxzxIUZul+vLHEtzs9RxEQZ6u37l+X2 V+oRjitXvwNbYKA8i+QXtdUAJs/78XBmC+CLhn1O/KVLdWDkQh9QAT8V6XOIQNypzjlB jppg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:cc:to:subject:message-id:date:from:in-reply-to :references:mime-version:dkim-signature; bh=nq9zEmsra/I9FskgMrWZwW4jiUKeh83zVbLmEJ7frGY=; b=NuzTTfMM1BWOS3RBTXqyvqmlm9Gg02cKfHnv4NfsC9yTIiaHirIKVWGs/zvQUAzub7 Sk1OcnB0P16LojGhVMts4DMDZBUtqqYkQyLbqbrVW2I0ms5o7Yz1TrqagXT1SJWMQuEH NFqXuybc4/QXSZ2yFTchn2cOJopWnH/IBG+EbecxbfODBFVeAXfSw0faU7K5DK9SynI/ ueDuiQZSr98F6HdKIdZ34dFo1ghghG1n6i3i8utGkV9Er1t4vtmGsemGstZIPxZLeHp3 R2g3qDYlsrSl6f2AnYL3ie47aw4jVeqc3LXOESYKXnF8nX9jRZKFnycLjzj8pyaA2NML 9qJg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=hvjUrxeT; spf=pass (google.com: domain of linux-ext4-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-ext4-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id y6si18713edm.295.2020.10.28.13.40.41; Wed, 28 Oct 2020 13:41:13 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-ext4-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=hvjUrxeT; spf=pass (google.com: domain of linux-ext4-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-ext4-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1829837AbgJ0TWM (ORCPT + 99 others); Tue, 27 Oct 2020 15:22:12 -0400 Received: from mail-ot1-f68.google.com ([209.85.210.68]:35358 "EHLO mail-ot1-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1829805AbgJ0TVU (ORCPT ); Tue, 27 Oct 2020 15:21:20 -0400 Received: by mail-ot1-f68.google.com with SMTP id n11so2164972ota.2 for ; Tue, 27 Oct 2020 12:21:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :cc; bh=nq9zEmsra/I9FskgMrWZwW4jiUKeh83zVbLmEJ7frGY=; b=hvjUrxeTnpKW11JqdRolGFlbjn/yYSrDZSBljizuyw98mqP/+42EJQeRg9IQ5N+mbt YK2asUVglRI0DGIMMA0SukSkVKO7BBMoEon9z5v8NkWZ9NvpMjDliPv4EDItaiS4FvwQ QgYoAx6QZCybILjrpxVneAnrCX85O3TAAA+oqfx7fhQxAhGcMxJfgaiq9Rt0Ezd82qdo rY7s7IViuNeP83O+ESncQvl8OwQZRhroog8poJljzzQIzrwxTnEwKDThGOkxQb3BPAxe W094rE9JA/5P+q8mqfIk8HX63+GoKCRylqUyl98aSIgjs2n5ZsAZ4D4STjBTdagEkVgd fH1A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=nq9zEmsra/I9FskgMrWZwW4jiUKeh83zVbLmEJ7frGY=; b=NImDCwX0YYM22jicB59Oq5VBUQE8HdRU7/9oIn9jx2iG6sbfxn2HQj76o++RRq5IZQ 9Sy0g5rYJTPnPAw8vUV0cBF6JeDVPqC+TvgqHfluIjIYZD0HpGyD3yASqHNyIF29wSbz vvvSiVpyztLjlyCx/iXjkNcrfZebao19VF4DFr+CeRhpSFQYu8J2QYUUqnr5k+ps6cAt mEkW20v3fnIgPeeqTcdtjlegAd0FrxHcVinDJcb3RYgVbeURhSUKHN2Uw+1o/5SaXHWM 0K9BGjfQrY6tHp0d+F06/HAwD6e5HcsBG42qL+lpLXej4QbOgu2iUs4waqe34w5W5DoQ k21Q== X-Gm-Message-State: AOAM532ZgiPVNJrTOpwFUpu794cT/HwdBXe8Iy8JQVe4cEyD+hMNea9d Zf4Lz9+DPxrMpVua6B7VrOHneo4zINZEC4ZmzO7Ucg== X-Received: by 2002:a9d:649:: with SMTP id 67mr2645328otn.233.1603826477947; Tue, 27 Oct 2020 12:21:17 -0700 (PDT) MIME-Version: 1.0 References: <20201027174630.85213-1-98.arpi@gmail.com> In-Reply-To: <20201027174630.85213-1-98.arpi@gmail.com> From: Marco Elver Date: Tue, 27 Oct 2020 20:21:05 +0100 Message-ID: Subject: Re: [PATCH v4 1/2] kunit: Support for Parameterized Testing To: Arpitha Raghunandan <98.arpi@gmail.com> Cc: Brendan Higgins , skhan@linuxfoundation.org, Iurii Zaikin , "Theodore Ts'o" , Andreas Dilger , "open list:KERNEL SELFTEST FRAMEWORK" , KUnit Development , LKML , linux-kernel-mentees@lists.linuxfoundation.org, linux-ext4@vger.kernel.org Content-Type: text/plain; charset="UTF-8" Precedence: bulk List-ID: X-Mailing-List: linux-ext4@vger.kernel.org On Tue, 27 Oct 2020 at 18:47, Arpitha Raghunandan <98.arpi@gmail.com> wrote: > > Implementation of support for parameterized testing in KUnit. > This approach requires the creation of a test case using the > KUNIT_CASE_PARAM macro that accepts a generator function as input. > This generator function should return the next parameter given the > previous parameter in parameterized tests. It also provides > a macro to generate common-case generators. > > Signed-off-by: Arpitha Raghunandan <98.arpi@gmail.com> > Co-developed-by: Marco Elver > Signed-off-by: Marco Elver > --- > Changes v3->v4: > - Rename kunit variables > - Rename generator function helper macro > - Add documentation for generator approach > - Display test case name in case of failure along with param index > Changes v2->v3: > - Modifictaion of generator macro and method > Changes v1->v2: > - Use of a generator method to access test case parameters > > include/kunit/test.h | 34 ++++++++++++++++++++++++++++++++++ > lib/kunit/test.c | 21 ++++++++++++++++++++- > 2 files changed, 54 insertions(+), 1 deletion(-) > > diff --git a/include/kunit/test.h b/include/kunit/test.h > index 9197da792336..ec2307ee9bb0 100644 > --- a/include/kunit/test.h > +++ b/include/kunit/test.h > @@ -107,6 +107,13 @@ struct kunit; > * > * @run_case: the function representing the actual test case. > * @name: the name of the test case. > + * @generate_params: the generator function for parameterized tests. > + * > + * The generator function is used to lazily generate a series of > + * arbitrarily typed values that fit into a void*. The argument @prev > + * is the previously returned value, which should be used to derive the > + * next value; @prev is set to NULL on the initial generator call. > + * When no more values are available, the generator must return NULL. > * Hmm, should this really be the first paragraph? I think it should be the paragraph before "Example:" maybe. But then that paragraph should refer to generate_params e.g. "The generator function @generate_params is used to ........". The other option you have is to move this paragraph to the kernel-doc comment for KUNIT_CASE_PARAM, which seems to be missing a kernel-doc comment. > * A test case is a function with the signature, > * ``void (*)(struct kunit *)`` > @@ -141,6 +148,7 @@ struct kunit; > struct kunit_case { > void (*run_case)(struct kunit *test); > const char *name; > + void* (*generate_params)(void *prev); > > /* private: internal use only. */ > bool success; > @@ -162,6 +170,9 @@ static inline char *kunit_status_to_string(bool status) > * &struct kunit_case for an example on how to use it. > */ > #define KUNIT_CASE(test_name) { .run_case = test_name, .name = #test_name } I.e. create a new kernel-doc comment for KUNIT_CASE_PARAM here, and simply move the paragraph describing the generator protocol into that comment. > +#define KUNIT_CASE_PARAM(test_name, gen_params) \ > + { .run_case = test_name, .name = #test_name, \ > + .generate_params = gen_params } > > /** > * struct kunit_suite - describes a related collection of &struct kunit_case > @@ -208,6 +219,15 @@ struct kunit { > const char *name; /* Read only after initialization! */ > char *log; /* Points at case log after initialization */ > struct kunit_try_catch try_catch; > + /* param_value points to test case parameters in parameterized tests */ Hmm, not quite: param_value is the current parameter value for a test case. Most likely it's a pointer, but it doesn't need to be. > + void *param_value; > + /* > + * param_index stores the index of the parameter in > + * parameterized tests. param_index + 1 is printed > + * to indicate the parameter that causes the test > + * to fail in case of test failure. > + */ I think this comment needs to be reformatted, because you can use at the very least use 80 cols per line. (If you use vim, visual select and do 'gq'.) > + int param_index; > /* > * success starts as true, and may only be set to false during a > * test case; thus, it is safe to update this across multiple > @@ -1742,4 +1762,18 @@ do { \ > fmt, \ > ##__VA_ARGS__) > > +/** > + * KUNIT_ARRAY_PARAM() - Helper method for test parameter generators > + * required in parameterized tests. > + * @name: prefix of the name for the test parameter generator function. > + * It will be suffixed by "_gen_params". > + * @array: a user-supplied pointer to an array of test parameters. > + */ > +#define KUNIT_ARRAY_PARAM(name, array) \ > + static void *name##_gen_params(void *prev) \ > + { \ > + typeof((array)[0]) * __next = prev ? ((typeof(__next)) prev) + 1 : (array); \ > + return __next - (array) < ARRAY_SIZE((array)) ? __next : NULL; \ > + } > + > #endif /* _KUNIT_TEST_H */ > diff --git a/lib/kunit/test.c b/lib/kunit/test.c > index 750704abe89a..8ad908b61494 100644 > --- a/lib/kunit/test.c > +++ b/lib/kunit/test.c > @@ -127,6 +127,12 @@ unsigned int kunit_test_case_num(struct kunit_suite *suite, > } > EXPORT_SYMBOL_GPL(kunit_test_case_num); > > +static void kunit_print_failed_param(struct kunit *test) > +{ > + kunit_err(test, "\n\tTest failed at:\n\ttest case: %s\n\tparameter: %d\n", > + test->name, test->param_index + 1); > +} Hmm, perhaps I wasn't clear, but I think I also misunderstood how the test case successes are presented: they are not, and it's all bunched into a single test case. Firstly, kunit_err() already prints the test name, so if we want something like " # : the_test_case_name: failed at parameter #X", simply having kunit_err(test, "failed at parameter #%d\n", test->param_index + 1) would be what you want. But I think I missed that parameters do not actually produce a set of test cases (sorry for noticing late). I think in their current form, the parameterized tests would not be useful for my tests, because each of my tests have test cases that have specific init and exit functions. For each parameter, these would also need to run. Ideally, each parameter produces its own independent test case "test_case#param_index". That way, CI systems will also be able to logically separate different test case params, simply because each param produced its own distinct test case. So, for example, we would get a series of test cases from something like KUNIT_CASE_PARAM(test_case, foo_gen_params), and in the output we'd see: ok X - test_case#1 ok X - test_case#2 ok X - test_case#3 ok X - test_case#4 .... Would that make more sense? That way we'd ensure that test-case specific initialization and cleanup done in init and exit functions is properly taken care of, and you wouldn't need kunit_print_failed_param(). AFAIK, for what I propose you'd have to modify kunit_print_ok_not_ok() (show param_index if parameterized test) and probably kunit_run_case_catch_errors() (generate params and set test->param_value and param_index). Was there a reason why each param cannot be a distinct test case? If not, I think this would be more useful. > static void kunit_print_string_stream(struct kunit *test, > struct string_stream *stream) > { > @@ -168,6 +174,8 @@ static void kunit_fail(struct kunit *test, struct kunit_assert *assert) > assert->format(assert, stream); > > kunit_print_string_stream(test, stream); > + if (test->param_value) > + kunit_print_failed_param(test); > > WARN_ON(string_stream_destroy(stream)); > } > @@ -239,7 +247,18 @@ static void kunit_run_case_internal(struct kunit *test, > } > } > > - test_case->run_case(test); > + if (!test_case->generate_params) { > + test_case->run_case(test); > + } else { > + test->param_value = test_case->generate_params(NULL); > + test->param_index = 0; > + > + while (test->param_value) { > + test_case->run_case(test); > + test->param_value = test_case->generate_params(test->param_value); > + test->param_index++; > + } > + } Thanks, -- Marco