From patchwork Wed Sep 29 19:54:33 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Latypov X-Patchwork-Id: 12526541 X-Patchwork-Delegate: brendanhiggins@google.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 134E2C433EF for ; Wed, 29 Sep 2021 19:54:54 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id E1C9D6154B for ; Wed, 29 Sep 2021 19:54:53 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345894AbhI2T4d (ORCPT ); Wed, 29 Sep 2021 15:56:33 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34588 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237351AbhI2T42 (ORCPT ); Wed, 29 Sep 2021 15:56:28 -0400 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7C8F9C061764 for ; Wed, 29 Sep 2021 12:54:46 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id l11-20020a056902072b00b005a776eefb28so5003661ybt.5 for ; Wed, 29 Sep 2021 12:54:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=cmuN5/kREXwmVWvA5tkze8AF++qHoi0QEen4hqx19c0=; b=OQHtExlktX+92wICLzjUgzDTBZXxoI3WVRWnYw6oTo6d32fdZqoGi9IHUBAHn2oj4K 0hEWVIRSE51HT1Gub1QEXkwvpqMapvSpu+7zFTA6mbFwdUDYBtpv11MP9+t2mz7GUoOC eG5GLlhPd3RaRKmPqJBEqsm71YNFDWgOOVJ02VaTkhiNNReE6wNfW7/T3lNQ0Hg2VVCs FNGOkLKPWKf1ikZO9FIaX41IvBC+fmkh14nYm520bJLVq9amS5HrOEYTZupjlaWM7zo+ JKQayfjXJBiY6ruROIu96vOMlB6uF65BSL65cyEpzGn7EFzWTXdRD7QEcRetr5GWgqy3 8JbA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=cmuN5/kREXwmVWvA5tkze8AF++qHoi0QEen4hqx19c0=; b=5WqVEotbWdaeXdoKb0pyZoAZ+f/Ib5YzuXMlgs4CA8wOw0+lC3eTWTjvBV4M9Oxh96 Q5tKcXW7Oe27AIwhLqW+VKLTujME9nrBSlklUIZ+xiZYweQa7c7HYaTpLIbdfnv2JZ8Z 1kWOu0ykQ4LkjbAdn6Qc7SNG8LEKcnHfbNywOFkGdcESask6ZDbem2y4HNV95BnNq1OY bbcCcOSRW8l3CnGHk6JJF4NhGraFmInLWVlBiwCsu5iPy7iP9bn0O54SCKvSzRvUjpVe i/WUmjBElNMdbO+ief8PLmsM/TF3MQtu3uamBwFjzGdk4v9mMdh2lhnFDwpg5ZH+Wc8G FtPg== X-Gm-Message-State: AOAM532+95XAQbzsbNH03o/TZLiX+tllBOipsZFmLzCVglN6PkLchV5G HCBUUhlMOVTR8wnlwf+470R03hC0vY/z1w== X-Google-Smtp-Source: ABdhPJwDlPdBEhkctOrLWQ+LWC6bVlq6tpkkvhzzv0QqtngCuM4KIeiEpbL0u9KO8juX9vrO40pJog6erGTP4A== X-Received: from dlatypov.svl.corp.google.com ([2620:15c:2cd:202:e473:7cd7:9986:85b7]) (user=dlatypov job=sendgmr) by 2002:a05:6902:102e:: with SMTP id x14mr2272641ybt.410.1632945285749; Wed, 29 Sep 2021 12:54:45 -0700 (PDT) Date: Wed, 29 Sep 2021 12:54:33 -0700 In-Reply-To: <20210929195436.1405996-1-dlatypov@google.com> Message-Id: <20210929195436.1405996-2-dlatypov@google.com> Mime-Version: 1.0 References: <20210929195436.1405996-1-dlatypov@google.com> X-Mailer: git-send-email 2.33.0.685.g46640cef36-goog Subject: [PATCH v3 1/4] kunit: add 'kunit.action' param to allow listing out tests From: Daniel Latypov To: brendanhiggins@google.com, davidgow@google.com Cc: linux-kernel@vger.kernel.org, kunit-dev@googlegroups.com, linux-kselftest@vger.kernel.org, skhan@linuxfoundation.org, Daniel Latypov Precedence: bulk List-ID: X-Mailing-List: linux-kselftest@vger.kernel.org Context: It's difficult to map a given .kunitconfig => set of enabled tests. Letting kunit.py figure that out would be useful. This patch: * is intended to be an implementation detail used only by kunit.py * adds a kunit.action module param with one valid non-null value, "list" * for the "list" action, it simply prints out "." * leaves the kunit.py changes to make use of this for another patch. Note: kunit.filter_glob is respected for this and all future actions. Hack: we print a TAP header (but no test plan) to allow kunit.py to use the same code to pick up KUnit output that it does for normal tests. Since this is intended to be an implementation detail, it seems fine for now. Maybe in the future we output each test as SKIPPED or the like. Go with a more generic "action" param, since it seems like we might eventually have more modes besides just running or listing tests, e.g. * perhaps a benchmark mode that reruns test cases and reports timing * perhaps a deflake mode that reruns test cases that failed * perhaps a mode where we randomize test order to try and catch hermeticity bugs like "test a only passes if run after test b" Tested: $ ./tools/testing/kunit/kunit.py run --kernel_arg=kunit.action=list --raw_output=kunit ... TAP version 14 1..1 example.example_simple_test example.example_skip_test example.example_mark_skipped_test reboot: System halted Signed-off-by: Daniel Latypov Reviewed-by: David Gow --- lib/kunit/executor.c | 45 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/lib/kunit/executor.c b/lib/kunit/executor.c index bab3ab940acc..ce1f57a44ab9 100644 --- a/lib/kunit/executor.c +++ b/lib/kunit/executor.c @@ -15,9 +15,16 @@ extern struct kunit_suite * const * const __kunit_suites_end[]; #if IS_BUILTIN(CONFIG_KUNIT) static char *filter_glob_param; +static char *action_param; + module_param_named(filter_glob, filter_glob_param, charp, 0); MODULE_PARM_DESC(filter_glob, "Filter which KUnit test suites/tests run at boot-time, e.g. list* or list*.*del_test"); +module_param_named(action, action_param, charp, 0); +MODULE_PARM_DESC(action, + "Changes KUnit executor behavior, valid values are:\n" + ": run the tests like normal\n" + "'list' to list test names instead of running them.\n"); /* glob_match() needs NULL terminated strings, so we need a copy of filter_glob_param. */ struct kunit_test_filter { @@ -196,9 +203,35 @@ static void kunit_print_tap_header(struct suite_set *suite_set) pr_info("1..%d\n", num_of_suites); } -int kunit_run_all_tests(void) +static void kunit_exec_run_tests(struct suite_set *suite_set) { struct kunit_suite * const * const *suites; + + kunit_print_tap_header(suite_set); + + for (suites = suite_set->start; suites < suite_set->end; suites++) + __kunit_test_suites_init(*suites); +} + +static void kunit_exec_list_tests(struct suite_set *suite_set) +{ + unsigned int i; + struct kunit_suite * const * const *suites; + struct kunit_case *test_case; + + /* Hack: print a tap header so kunit.py can find the start of KUnit output. */ + pr_info("TAP version 14\n"); + + for (suites = suite_set->start; suites < suite_set->end; suites++) + for (i = 0; (*suites)[i] != NULL; i++) { + kunit_suite_for_each_test_case((*suites)[i], test_case) { + pr_info("%s.%s\n", (*suites)[i]->name, test_case->name); + } + } +} + +int kunit_run_all_tests(void) +{ struct suite_set suite_set = { .start = __kunit_suites_start, .end = __kunit_suites_end, @@ -207,10 +240,12 @@ int kunit_run_all_tests(void) if (filter_glob_param) suite_set = kunit_filter_suites(&suite_set, filter_glob_param); - kunit_print_tap_header(&suite_set); - - for (suites = suite_set.start; suites < suite_set.end; suites++) - __kunit_test_suites_init(*suites); + if (!action_param) + kunit_exec_run_tests(&suite_set); + else if (strcmp(action_param, "list") == 0) + kunit_exec_list_tests(&suite_set); + else + pr_err("kunit executor: unknown action '%s'\n", action_param); if (filter_glob_param) { /* a copy was made of each array */ kunit_free_suite_set(suite_set); From patchwork Wed Sep 29 19:54:34 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Latypov X-Patchwork-Id: 12526543 X-Patchwork-Delegate: brendanhiggins@google.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id EEAE9C433FE for ; Wed, 29 Sep 2021 19:54:54 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id CCD546152A for ; Wed, 29 Sep 2021 19:54:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1346010AbhI2T4f (ORCPT ); Wed, 29 Sep 2021 15:56:35 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34606 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345843AbhI2T4a (ORCPT ); Wed, 29 Sep 2021 15:56:30 -0400 Received: from mail-qk1-x749.google.com (mail-qk1-x749.google.com [IPv6:2607:f8b0:4864:20::749]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id BFF32C061767 for ; Wed, 29 Sep 2021 12:54:48 -0700 (PDT) Received: by mail-qk1-x749.google.com with SMTP id j6-20020a05620a288600b0045e5d85ca17so10647104qkp.16 for ; Wed, 29 Sep 2021 12:54:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=9sOOAbpo+CDeDN2zgZAi4uwZL9FbPXFHzEInK3wMaN8=; b=MjEUyYEAPg62Uua1oTEbR7gL326WtcE7HZiyPUyqsvxymoVlqFIqASJRtboj1BPbyg n9/TU6pcpp2bVgAswzJc6ESN8yGo1/1dWGt4am0SdaMZQToHyFQTa2zjupdjm3PXPLse zywmrq/UWjwZ8VJCD7o5iapFL17oroEiaAzc1fxWm+otUO4hE1R3ZQ17WqLdLstL7Jn8 QYaAvm6w5tQrme3tvmGikGmt89/pqMGNTfk/16RqLgSefnRKtE0b7vG8JQl2sdX4lavC HzWvw2HEhZAwR7tN9Qpa00Ti0xXBUsAwR4Yxyhs1ua/7a9IhivOU1v6OwXEpXexA1yNP Ae3g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=9sOOAbpo+CDeDN2zgZAi4uwZL9FbPXFHzEInK3wMaN8=; b=kjhVTnDH8YyQAxnD/it/hscco2hKZs1Jd6s3BTuuIip8a4tJIiUlZ7KA4nJGn1rCPS /+QjnubSp/4yI7s7b5Xr6XPs7xHQs/RTSbNBN4bqj512+tsNgJ16xvXDEAsxET11y6Iq 0yNO0oqBtt1SjaM+/pY0A2WOLVv7klD3/fczSjDGVsttLsLRDbV418HsTG9HYnnOld7L phBLIqQ8kDmc+k4rNA3Afjjkqdo1jB/l7YDuAzvOtfv0ieF1aDb8BlBRScpjrAzcYDAy eLN8Q3/6MWzH4i/oKzGAREXGrxbTKJ+shIcs0Fb1rjuiVQVRpGBSvtZT/QX+vrqeAblC iqrw== X-Gm-Message-State: AOAM530NQh8LjL+HDK3YP5CXo0cYN6ZUj2amB8B/aTDXdYI9nyLQ7o+W PVfQjM9v69wipf2RIGNf7alsX+JRJkeWHQ== X-Google-Smtp-Source: ABdhPJx6QuIgvNNe4a2Cw8n9tyR1htGGPtY2/CjwI4hQMAPvoGr8HaN4rjiWgODRe9Q0Sq5NLdItrzNKmX3JGQ== X-Received: from dlatypov.svl.corp.google.com ([2620:15c:2cd:202:e473:7cd7:9986:85b7]) (user=dlatypov job=sendgmr) by 2002:a05:6214:2d1:: with SMTP id g17mr242890qvu.63.1632945287995; Wed, 29 Sep 2021 12:54:47 -0700 (PDT) Date: Wed, 29 Sep 2021 12:54:34 -0700 In-Reply-To: <20210929195436.1405996-1-dlatypov@google.com> Message-Id: <20210929195436.1405996-3-dlatypov@google.com> Mime-Version: 1.0 References: <20210929195436.1405996-1-dlatypov@google.com> X-Mailer: git-send-email 2.33.0.685.g46640cef36-goog Subject: [PATCH v3 2/4] kunit: tool: factor exec + parse steps into a function From: Daniel Latypov To: brendanhiggins@google.com, davidgow@google.com Cc: linux-kernel@vger.kernel.org, kunit-dev@googlegroups.com, linux-kselftest@vger.kernel.org, skhan@linuxfoundation.org, Daniel Latypov Precedence: bulk List-ID: X-Mailing-List: linux-kselftest@vger.kernel.org Currently this code is copy-pasted between the normal "run" subcommand and the "exec" subcommand. Given we don't have any interest in just executing the tests without giving the user any indication what happened (i.e. parsing the output), make a function that does both this things and can be reused. This will be useful when we allow more complicated ways of running tests, e.g. invoking the kernel multiple times instead of just once, etc. We remove input_data from the ParseRequest so the callers don't have to pass in a dummy value for this field. Named tuples are also immutable, so if they did pass in a dummy, exec_tests() would need to make a copy to call parse_tests(). Removing it also makes KunitParseRequest match the other *Request types, as they only contain user arguments/flags, not data. Signed-off-by: Daniel Latypov Reviewed-by: David Gow --- tools/testing/kunit/kunit.py | 44 ++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py index 66f67af97971..31eec9f6ecc3 100755 --- a/tools/testing/kunit/kunit.py +++ b/tools/testing/kunit/kunit.py @@ -34,7 +34,7 @@ KunitExecRequest = namedtuple('KunitExecRequest', ['timeout', 'build_dir', 'alltests', 'filter_glob', 'kernel_args']) KunitParseRequest = namedtuple('KunitParseRequest', - ['raw_output', 'input_data', 'build_dir', 'json']) + ['raw_output', 'build_dir', 'json']) KunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs', 'build_dir', 'alltests', 'filter_glob', 'kernel_args', 'json', 'make_options']) @@ -91,23 +91,25 @@ def build_tests(linux: kunit_kernel.LinuxSourceTree, 'built kernel successfully', build_end - build_start) -def exec_tests(linux: kunit_kernel.LinuxSourceTree, - request: KunitExecRequest) -> KunitResult: +def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest, + parse_request: KunitParseRequest) -> KunitResult: kunit_parser.print_with_timestamp('Starting KUnit Kernel ...') test_start = time.time() - result = linux.run_kernel( + run_result = linux.run_kernel( args=request.kernel_args, timeout=None if request.alltests else request.timeout, - filter_glob=request.filter_glob, + filter_glob=request.filter_glob, build_dir=request.build_dir) test_end = time.time() + exec_time = test_end - test_start - return KunitResult(KunitStatus.SUCCESS, - result, - test_end - test_start) + # Named tuples are immutable, so we rebuild them here manually + result = parse_tests(parse_request, run_result) + + return KunitResult(status=result.status, result=result.result, elapsed_time=exec_time) -def parse_tests(request: KunitParseRequest) -> KunitResult: +def parse_tests(request: KunitParseRequest, input_data: Iterable[str]) -> KunitResult: parse_start = time.time() test_result = kunit_parser.TestResult(kunit_parser.TestStatus.SUCCESS, @@ -115,7 +117,7 @@ def parse_tests(request: KunitParseRequest) -> KunitResult: 'Tests not Parsed.') if request.raw_output: - output: Iterable[str] = request.input_data + output: Iterable[str] = input_data if request.raw_output == 'all': pass elif request.raw_output == 'kunit': @@ -126,7 +128,7 @@ def parse_tests(request: KunitParseRequest) -> KunitResult: print(line.rstrip()) else: - test_result = kunit_parser.parse_run_tests(request.input_data) + test_result = kunit_parser.parse_run_tests(input_data) parse_end = time.time() if request.json: @@ -165,15 +167,11 @@ def run_tests(linux: kunit_kernel.LinuxSourceTree, exec_request = KunitExecRequest(request.timeout, request.build_dir, request.alltests, request.filter_glob, request.kernel_args) - exec_result = exec_tests(linux, exec_request) - if exec_result.status != KunitStatus.SUCCESS: - return exec_result - parse_request = KunitParseRequest(request.raw_output, - exec_result.result, request.build_dir, request.json) - parse_result = parse_tests(parse_request) + + exec_result = exec_tests(linux, exec_request, parse_request) run_end = time.time() @@ -184,7 +182,7 @@ def run_tests(linux: kunit_kernel.LinuxSourceTree, config_result.elapsed_time, build_result.elapsed_time, exec_result.elapsed_time)) - return parse_result + return exec_result def add_common_opts(parser) -> None: parser.add_argument('--build_dir', @@ -381,15 +379,12 @@ def main(argv, linux=None): cli_args.alltests, cli_args.filter_glob, cli_args.kernel_args) - exec_result = exec_tests(linux, exec_request) parse_request = KunitParseRequest(cli_args.raw_output, - exec_result.result, cli_args.build_dir, cli_args.json) - result = parse_tests(parse_request) + result = exec_tests(linux, exec_request, parse_request) kunit_parser.print_with_timestamp(( - 'Elapsed time: %.3fs\n') % ( - exec_result.elapsed_time)) + 'Elapsed time: %.3fs\n') % (result.elapsed_time)) if result.status != KunitStatus.SUCCESS: sys.exit(1) elif cli_args.subcommand == 'parse': @@ -399,10 +394,9 @@ def main(argv, linux=None): with open(cli_args.file, 'r') as f: kunit_output = f.read().splitlines() request = KunitParseRequest(cli_args.raw_output, - kunit_output, None, cli_args.json) - result = parse_tests(request) + result = parse_tests(request, kunit_output) if result.status != KunitStatus.SUCCESS: sys.exit(1) else: From patchwork Wed Sep 29 19:54:35 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Latypov X-Patchwork-Id: 12526545 X-Patchwork-Delegate: brendanhiggins@google.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 82E70C433F5 for ; Wed, 29 Sep 2021 19:54:55 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 594436154B for ; Wed, 29 Sep 2021 19:54:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345777AbhI2T4f (ORCPT ); Wed, 29 Sep 2021 15:56:35 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34622 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345882AbhI2T4d (ORCPT ); Wed, 29 Sep 2021 15:56:33 -0400 Received: from mail-qk1-x74a.google.com (mail-qk1-x74a.google.com [IPv6:2607:f8b0:4864:20::74a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DB072C06176A for ; Wed, 29 Sep 2021 12:54:50 -0700 (PDT) Received: by mail-qk1-x74a.google.com with SMTP id m1-20020a05620a290100b0045e5e0b11e6so10724199qkp.23 for ; Wed, 29 Sep 2021 12:54:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=cmJIpf0DwizpXlrk/HvWpomrFuR6W2E4zR+m6Uv+l5U=; b=MT9OV30DeaVmPZQ81HvXbzyPi4vSansk777G+fi0QTkGXydBM5FiCyPyk7TNhjUwkN j2j9tdDMyFiXc1etAueGqimaGOYRwwbd68plfIAiL5HuLskDZRFEdC3sSgvSlDSzNNpe MbZc8wXMVolQBaPuiyuc5zxO+WrizsKJ6yqgWDoYRudjUAPN3pyUyYKkX056/DLKM+1v egcOi9yRZS0XQt/a/eAK+FRItN3tpyxTAAfgfqjxM5CplupNNBy6HD+2KvytGKp7xYf3 B+bazfnOoBbQB3e3NiC3GKwoCDiQJ/Ber/Fw8QGMhszpnvKm5cH7sBGZoNLLN6mIw2hu 6fVA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=cmJIpf0DwizpXlrk/HvWpomrFuR6W2E4zR+m6Uv+l5U=; b=R5TRB5QNGOGzJ1m+S2Uqe2FAU9EYXWwXegX+MfOm24sZ3Dg0c9k0tKjiMU1AQ2NjxK 8kjbJcQFjrGIJRw4k/ZTM9/+XLCTX2FHozAEssUs2E5DMbxisFG3pFGpPtEI8FW73NTK opSMfDf20QoqRL1KxFRHBno5s68FjYbLlPlaG1An213tJKNdHAr1cpkqirvqtAq3HkR6 Ba5AYHwGVrsAY5H6QNYCg5xkp+E5bAo3N8dKb+ZsMPNYivq8pc6cFgAELpFdPNpz2f0l bRTSR9VJiVDFJuD1spDtiNxe+B0np/J7L+Me4grDd2+l9SNaTX5QhYSvt6/W+1jFP4S5 JMSw== X-Gm-Message-State: AOAM531RXAZWY+yeVXjLYQ27srUrOtK62JABKi6G7RUOExC8X+veLwfW OFALBNxp56De/wnXLjNLC43nFrA75PfREg== X-Google-Smtp-Source: ABdhPJxfeptZl2iUjrSyGrbe8zmkprqvzOOpOKXK7kDNqkmogEgEmmhaSKjTNdHiQbPquSGIZPk2N3KWNJhILw== X-Received: from dlatypov.svl.corp.google.com ([2620:15c:2cd:202:e473:7cd7:9986:85b7]) (user=dlatypov job=sendgmr) by 2002:a0c:cb10:: with SMTP id o16mr249962qvk.57.1632945290110; Wed, 29 Sep 2021 12:54:50 -0700 (PDT) Date: Wed, 29 Sep 2021 12:54:35 -0700 In-Reply-To: <20210929195436.1405996-1-dlatypov@google.com> Message-Id: <20210929195436.1405996-4-dlatypov@google.com> Mime-Version: 1.0 References: <20210929195436.1405996-1-dlatypov@google.com> X-Mailer: git-send-email 2.33.0.685.g46640cef36-goog Subject: [PATCH v3 3/4] kunit: tool: actually track how long it took to run tests From: Daniel Latypov To: brendanhiggins@google.com, davidgow@google.com Cc: linux-kernel@vger.kernel.org, kunit-dev@googlegroups.com, linux-kselftest@vger.kernel.org, skhan@linuxfoundation.org, Daniel Latypov Precedence: bulk List-ID: X-Mailing-List: linux-kselftest@vger.kernel.org This is a long standing bug in kunit tool. Since these files were added, run_kernel() has always yielded lines. That means, the call to run_kernel() returns before the kernel finishes executing tests, potentially before a single line of output is even produced. So code like this time_start = time.time() result = linux.run_kernel(...) time_end = time.time() would only measure the time taken for python to give back the generator object. From a caller's perspective, the only way to know the kernel has exited is for us to consume all the output from the `result` generator object. Alternatively, we could change run_kernel() to try and do its own book keeping and return the total time, but that doesn't seem worth it. This change makes us record `time_end` after we're doing parsing all the output (which should mean we've consumed all of it, or errored out). That means we're including in the parsing time as well, but that should be quite small, and it's better than claiming it took 0s to run tests. Let's use this as an example: $ ./tools/testing/kunit/kunit.py run --kunitconfig=lib/kunit example Before: Elapsed time: 7.684s total, 0.001s configuring, 4.692s building, 0.000s running After: Elapsed time: 6.283s total, 0.001s configuring, 3.202s building, 3.079s running Signed-off-by: Daniel Latypov Reviewed-by: David Gow --- tools/testing/kunit/kunit.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py index 31eec9f6ecc3..5e717594df5b 100755 --- a/tools/testing/kunit/kunit.py +++ b/tools/testing/kunit/kunit.py @@ -101,12 +101,14 @@ def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest, filter_glob=request.filter_glob, build_dir=request.build_dir) + result = parse_tests(parse_request, run_result) + + # run_kernel() doesn't block on the kernel exiting. + # That only happens after we get the last line of output from `run_result`. + # So exec_time here actually contains parsing + execution time, which is fine. test_end = time.time() exec_time = test_end - test_start - # Named tuples are immutable, so we rebuild them here manually - result = parse_tests(parse_request, run_result) - return KunitResult(status=result.status, result=result.result, elapsed_time=exec_time) def parse_tests(request: KunitParseRequest, input_data: Iterable[str]) -> KunitResult: From patchwork Wed Sep 29 19:54:36 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Latypov X-Patchwork-Id: 12526547 X-Patchwork-Delegate: brendanhiggins@google.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id EA4F7C43219 for ; Wed, 29 Sep 2021 19:54:55 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B3C3F61555 for ; Wed, 29 Sep 2021 19:54:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345790AbhI2T4g (ORCPT ); Wed, 29 Sep 2021 15:56:36 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34630 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345829AbhI2T4e (ORCPT ); Wed, 29 Sep 2021 15:56:34 -0400 Received: from mail-qt1-x84a.google.com (mail-qt1-x84a.google.com [IPv6:2607:f8b0:4864:20::84a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id EE4B1C06161C for ; Wed, 29 Sep 2021 12:54:52 -0700 (PDT) Received: by mail-qt1-x84a.google.com with SMTP id 62-20020aed2044000000b002a6aa209efaso9645142qta.18 for ; Wed, 29 Sep 2021 12:54:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=17Y/LeccnjgJAuhIupUYVLSx3q088XLrBRGkCK9p+Yk=; b=YGL88XpK45DV+JaI47b9MadnkIekRCi1d3ZyeytfAoQEmTGpgx9Ocmgmjz4rw4sj8c pn/IRQxKeIe8/lnZEcUt/PVkhGd3QXS+hJ+VFQRp23t99mTZG8Zq6EMZ6R8C6aFX7MvH QzCTyz3TijmRkygRkqmOc42vJ2bRhDpADC+26HHWNCYP9r2NpckSLmY2bi5nFtwzAhHg OBvyBxXkrnXf3gbTTq5l7WPO9a+mJKVTDETDyoGB+QPDXFrvimVl4TJYV0elEcvH6L80 7jY7AZZNBdnC1rcgrLNwIxyD4G2JawIsAbgEY0PgbQfxElJgsBzftFzutGV0J/iuTkCf O6Vg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=17Y/LeccnjgJAuhIupUYVLSx3q088XLrBRGkCK9p+Yk=; b=W+LjzU+1QvDBey3+uF5KYdL/KeRoXWZ69XJNx5/kqV1DFyrNZ+uPvG2sds96WSnNWY 97b5lljdVrKcKxZ1HwuI048p1XmLlgDIpB7Z1beHnEHJM3jtewjHh9wISJY0QA+1MCRD XI/L1HtJIbk4Xi44AxkDyyqVNvM0nWdgleH55H1FnNxWYBzZj0XKQhL9SIPePin0OYuo HXsAUf0ARtvg9gQFb0VvJ6tivV6hmx/z9mghwKoor0ds2/TmY7S0ZvBlhWiQrhMcHZIX IQISmguEFTAWmRe5nfETJ6/r1hlHVSeymdiU6VxMpo9nCvj7Rs0PrmbnPxXC/XYyWK7o B2BQ== X-Gm-Message-State: AOAM530z3oNQTH/Cp5kF8VQD9IskQ0NXoqahK907ygcHBvGu1QFoWeLf 39OJlPrsEw0XkUWT7o8zlhqmdfVAsrN80g== X-Google-Smtp-Source: ABdhPJyRhtNNCcvzZACSoIaQoNg1DvjGPtMy+CUljVz5HZUlUykT++LSRMMOFh9jpNlcJPLOf5uA0xp7L5eyBw== X-Received: from dlatypov.svl.corp.google.com ([2620:15c:2cd:202:e473:7cd7:9986:85b7]) (user=dlatypov job=sendgmr) by 2002:a05:6214:98d:: with SMTP id dt13mr164615qvb.13.1632945292131; Wed, 29 Sep 2021 12:54:52 -0700 (PDT) Date: Wed, 29 Sep 2021 12:54:36 -0700 In-Reply-To: <20210929195436.1405996-1-dlatypov@google.com> Message-Id: <20210929195436.1405996-5-dlatypov@google.com> Mime-Version: 1.0 References: <20210929195436.1405996-1-dlatypov@google.com> X-Mailer: git-send-email 2.33.0.685.g46640cef36-goog Subject: [PATCH v3 4/4] kunit: tool: support running each suite/test separately From: Daniel Latypov To: brendanhiggins@google.com, davidgow@google.com Cc: linux-kernel@vger.kernel.org, kunit-dev@googlegroups.com, linux-kselftest@vger.kernel.org, skhan@linuxfoundation.org, Daniel Latypov Precedence: bulk List-ID: X-Mailing-List: linux-kselftest@vger.kernel.org The new --run_isolated flag makes the tool boot the kernel once per suite or test, preventing leftover state from one suite to impact the other. This can be useful as a starting point to debugging test hermeticity issues. Note: it takes a lot longer, so people should not use it normally. Consider the following very simplified example: bool disable_something_for_test = false; void function_being_tested() { ... if (disable_something_for_test) return; ... } static void test_before(struct kunit *test) { disable_something_for_test = true; function_being_tested(); /* oops, we forgot to reset it back to false */ } static void test_after(struct kunit *test) { /* oops, now "fixing" test_before can cause test_after to fail! */ function_being_tested(); } Presented like this, the issues are obvious, but it gets a lot more complicated to track down as the amount of test setup and helper functions increases. Another use case is memory corruption. It might not be surfaced as a failure/crash in the test case or suite that caused it. I've noticed in kunit's own unit tests, the 3rd suite after might be the one to finally crash after an out-of-bounds write, for example. Example usage: Per suite: $ ./tools/testing/kunit/kunit.py run --kunitconfig=lib/kunit --run_isolated=suite ... Starting KUnit Kernel (1/7)... ============================================================ ======== [PASSED] kunit_executor_test ======== .... Testing complete. 5 tests run. 0 failed. 0 crashed. 0 skipped. Starting KUnit Kernel (2/7)... ============================================================ ======== [PASSED] kunit-try-catch-test ======== ... Per test: $ ./tools/testing/kunit/kunit.py run --kunitconfig=lib/kunit --run_isolated=test Starting KUnit Kernel (1/23)... ============================================================ ======== [PASSED] kunit_executor_test ======== [PASSED] parse_filter_test ============================================================ Testing complete. 1 tests run. 0 failed. 0 crashed. 0 skipped. Starting KUnit Kernel (2/23)... ============================================================ ======== [PASSED] kunit_executor_test ======== [PASSED] filter_subsuite_test ... It works with filters as well: $ ./tools/testing/kunit/kunit.py run --kunitconfig=lib/kunit --run_isolated=suite example ... Starting KUnit Kernel (1/1)... ============================================================ ======== [PASSED] example ======== ... It also handles test filters, '*.*skip*' runs these 3 tests: kunit_status.kunit_status_mark_skipped_test example.example_skip_test example.example_mark_skipped_test Signed-off-by: Daniel Latypov Reviewed-by: David Gow --- tools/testing/kunit/kunit.py | 95 ++++++++++++++++++++------ tools/testing/kunit/kunit_tool_test.py | 40 +++++++++++ 2 files changed, 114 insertions(+), 21 deletions(-) diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py index 5e717594df5b..b9d63f558765 100755 --- a/tools/testing/kunit/kunit.py +++ b/tools/testing/kunit/kunit.py @@ -16,7 +16,7 @@ assert sys.version_info >= (3, 7), "Python version is too old" from collections import namedtuple from enum import Enum, auto -from typing import Iterable +from typing import Iterable, List import kunit_config import kunit_json @@ -31,13 +31,13 @@ KunitBuildRequest = namedtuple('KunitBuildRequest', ['jobs', 'build_dir', 'alltests', 'make_options']) KunitExecRequest = namedtuple('KunitExecRequest', - ['timeout', 'build_dir', 'alltests', - 'filter_glob', 'kernel_args']) + ['timeout', 'build_dir', 'alltests', + 'filter_glob', 'kernel_args', 'run_isolated']) KunitParseRequest = namedtuple('KunitParseRequest', ['raw_output', 'build_dir', 'json']) KunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs', 'build_dir', 'alltests', 'filter_glob', - 'kernel_args', 'json', 'make_options']) + 'kernel_args', 'run_isolated', 'json', 'make_options']) KernelDirectoryPath = sys.argv[0].split('tools/testing/kunit/')[0] @@ -91,23 +91,68 @@ def build_tests(linux: kunit_kernel.LinuxSourceTree, 'built kernel successfully', build_end - build_start) +def _list_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> List[str]: + args = ['kunit.action=list'] + if request.kernel_args: + args.extend(request.kernel_args) + + output = linux.run_kernel(args=args, + timeout=None if request.alltests else request.timeout, + filter_glob=request.filter_glob, + build_dir=request.build_dir) + lines = kunit_parser.extract_tap_lines(output) + # Hack! Drop the dummy TAP version header that the executor prints out. + lines.pop() + return list(lines) + +def _suites_from_test_list(tests: List[str]) -> List[str]: + """Extracts all the suites from an ordered list of tests.""" + suites = [] # type: List[str] + for t in tests: + parts = t.split('.', maxsplit=2) + if len(parts) != 2: + raise ValueError(f'internal KUnit error, test name should be of the form ".", got "{t}"') + suite, case = parts + if not suites or suites[-1] != suite: + suites.append(suite) + return suites + + + def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest, parse_request: KunitParseRequest) -> KunitResult: - kunit_parser.print_with_timestamp('Starting KUnit Kernel ...') - test_start = time.time() - run_result = linux.run_kernel( - args=request.kernel_args, - timeout=None if request.alltests else request.timeout, - filter_glob=request.filter_glob, - build_dir=request.build_dir) - - result = parse_tests(parse_request, run_result) - - # run_kernel() doesn't block on the kernel exiting. - # That only happens after we get the last line of output from `run_result`. - # So exec_time here actually contains parsing + execution time, which is fine. - test_end = time.time() - exec_time = test_end - test_start + filter_globs = [request.filter_glob] + if request.run_isolated: + tests = _list_tests(linux, request) + if request.run_isolated == 'test': + filter_globs = tests + if request.run_isolated == 'suite': + filter_globs = _suites_from_test_list(tests) + # Apply the test-part of the user's glob, if present. + if '.' in request.filter_glob: + test_glob = request.filter_glob.split('.', maxsplit=2)[1] + filter_globs = [g + '.'+ test_glob for g in filter_globs] + + overall_status = kunit_parser.TestStatus.SUCCESS + exec_time = 0.0 + for i, filter_glob in enumerate(filter_globs): + kunit_parser.print_with_timestamp('Starting KUnit Kernel ({}/{})...'.format(i+1, len(filter_globs))) + + test_start = time.time() + run_result = linux.run_kernel( + args=request.kernel_args, + timeout=None if request.alltests else request.timeout, + filter_glob=filter_glob, + build_dir=request.build_dir) + + result = parse_tests(parse_request, run_result) + # run_kernel() doesn't block on the kernel exiting. + # That only happens after we get the last line of output from `run_result`. + # So exec_time here actually contains parsing + execution time, which is fine. + test_end = time.time() + exec_time += test_end - test_start + + overall_status = kunit_parser.max_status(overall_status, result.status) return KunitResult(status=result.status, result=result.result, elapsed_time=exec_time) @@ -168,7 +213,7 @@ def run_tests(linux: kunit_kernel.LinuxSourceTree, exec_request = KunitExecRequest(request.timeout, request.build_dir, request.alltests, request.filter_glob, - request.kernel_args) + request.kernel_args, request.run_isolated) parse_request = KunitParseRequest(request.raw_output, request.build_dir, request.json) @@ -252,6 +297,12 @@ def add_exec_opts(parser) -> None: parser.add_argument('--kernel_args', help='Kernel command-line parameters. Maybe be repeated', action='append') + parser.add_argument('--run_isolated', help='If set, boot the kernel for each ' + 'individual suite/test. This is can be useful for debugging ' + 'a non-hermetic test, one that might pass/fail based on ' + 'what ran before it.', + type=str, + choices=['suite', 'test']), def add_parse_opts(parser) -> None: parser.add_argument('--raw_output', help='If set don\'t format output from kernel. ' @@ -325,6 +376,7 @@ def main(argv, linux=None): cli_args.alltests, cli_args.filter_glob, cli_args.kernel_args, + cli_args.run_isolated, cli_args.json, cli_args.make_options) result = run_tests(linux, request) @@ -380,7 +432,8 @@ def main(argv, linux=None): cli_args.build_dir, cli_args.alltests, cli_args.filter_glob, - cli_args.kernel_args) + cli_args.kernel_args, + cli_args.run_isolated) parse_request = KunitParseRequest(cli_args.raw_output, cli_args.build_dir, cli_args.json) diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py index 619c4554cbff..1ff35c08d2c8 100755 --- a/tools/testing/kunit/kunit_tool_test.py +++ b/tools/testing/kunit/kunit_tool_test.py @@ -477,6 +477,46 @@ class KUnitMainTest(unittest.TestCase): args=['a=1','b=2'], build_dir='.kunit', filter_glob='', timeout=300) self.print_mock.assert_any_call(StrContains('Testing complete.')) + def test_list_tests(self): + want = ['suite.test1', 'suite.test2', 'suite2.test1'] + self.linux_source_mock.run_kernel.return_value = ['TAP version 14'] + want + + got = kunit._list_tests(self.linux_source_mock, + kunit.KunitExecRequest(300, '.kunit', False, 'suite*', None, 'suite')) + + self.assertEqual(got, want) + # Should respect the user's filter glob when listing tests. + self.linux_source_mock.run_kernel.assert_called_once_with( + args=['kunit.action=list'], build_dir='.kunit', filter_glob='suite*', timeout=300) + + + @mock.patch.object(kunit, '_list_tests') + def test_run_isolated_by_suite(self, mock_tests): + mock_tests.return_value = ['suite.test1', 'suite.test2', 'suite2.test1'] + kunit.main(['exec', '--run_isolated=suite', 'suite*.test*'], self.linux_source_mock) + + # Should respect the user's filter glob when listing tests. + mock_tests.assert_called_once_with(mock.ANY, + kunit.KunitExecRequest(300, '.kunit', False, 'suite*.test*', None, 'suite')) + self.linux_source_mock.run_kernel.assert_has_calls([ + mock.call(args=None, build_dir='.kunit', filter_glob='suite.test*', timeout=300), + mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test*', timeout=300), + ]) + + @mock.patch.object(kunit, '_list_tests') + def test_run_isolated_by_test(self, mock_tests): + mock_tests.return_value = ['suite.test1', 'suite.test2', 'suite2.test1'] + kunit.main(['exec', '--run_isolated=test', 'suite*'], self.linux_source_mock) + + # Should respect the user's filter glob when listing tests. + mock_tests.assert_called_once_with(mock.ANY, + kunit.KunitExecRequest(300, '.kunit', False, 'suite*', None, 'test')) + self.linux_source_mock.run_kernel.assert_has_calls([ + mock.call(args=None, build_dir='.kunit', filter_glob='suite.test1', timeout=300), + mock.call(args=None, build_dir='.kunit', filter_glob='suite.test2', timeout=300), + mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test1', timeout=300), + ]) + if __name__ == '__main__': unittest.main()