From patchwork Thu Sep 9 00:10: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: 508925 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.2 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED,USER_AGENT_GIT,USER_IN_DEF_DKIM_WL autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 203F8C433F5 for ; Thu, 9 Sep 2021 00:11:13 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 037AA6109F for ; Thu, 9 Sep 2021 00:11:12 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1348007AbhIIAMU (ORCPT ); Wed, 8 Sep 2021 20:12:20 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:50334 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1348223AbhIIAMS (ORCPT ); Wed, 8 Sep 2021 20:12:18 -0400 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 3DF91C06175F for ; Wed, 8 Sep 2021 17:11:10 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id i189-20020a256dc6000000b005a04d42ebf2so65542ybc.22 for ; Wed, 08 Sep 2021 17:11:10 -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=ZRkA7X9CP3l5beQkJa8BoUDI4oCL6IQ1glt1mJkTtT4=; b=Vef2IVJOjGk+OzwOrQ+nU5PfmgVydwRU8TGLBffxUU0uOFRBIZR98a4gIDOhrQNj/M g6MXKBVc0dG1qcfdLiCxaAnj6nJsSlYyJqsSTMK8E9djS0DF1s52/5BXVP6MegJBjeQx sa9lH6QIDG5NtgzM3aVE66+OhpGzdplTwiTRMxk1KiwOaKcz44rlCr3+E9S0qudWhDRo jKqVpzs+CtElkIfCl+pMyYuZjBxbpd/8ccYyezIOAOzVit4eURbqMkgUo2jyuW1M+gy/ /e/j2DJCaJ6Q2Kv226jKPLxDUZSb4OJcFykJEeuky7j53Ae7KH8NnvBqBiqF5qvVTPYd SXuQ== 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=ZRkA7X9CP3l5beQkJa8BoUDI4oCL6IQ1glt1mJkTtT4=; b=HJUbsZlMEyuidPtbP6BNCsx+yuiPDDJoc56RB6npvOcdstipNRxJcPqjPfMf2t/xcN BHwgL8kaPuCrAvCbudZfiYRw+QMCiNrs4j83ZQ9IDS0nomLpncr4truvhyWJ0ntUEnk+ GA99IvBjzqJ/1Cd9pDSnEdxGfjlei61IxeU6TxBFhpZ/983AOxAQyaoSF2nY35BAWb4E HiV54dcVw39SwnXnPcXN6NNT3MuIfPWt1wToXvY23UxLR7RydZOyB0Ul7LjNlXCJ1ddF D6zZx/M/v6x5zIay/FLIyeqkLyU09p3eGUpj1v8hsX9t+JSZ0bHWVrC37Kw1xjeC9ePV YFmA== X-Gm-Message-State: AOAM5302QL7c+pkWYQasUF5gqydJiI2Snvvo4UwZnAC/cAMy3ynt+X31 HvKj101uBnvH190chVulgwlTl8lVijrobg== X-Google-Smtp-Source: ABdhPJxCjBtKsCPNiXGUhr6V24T2WTYh78DvKqPmch/VaPghbmUyP1Gdpy8IvNfVSy/Hqbw62E0uvXp55cSueQ== X-Received: from dlatypov.svl.corp.google.com ([2620:15c:2cd:202:68b6:f792:6cb6:9052]) (user=dlatypov job=sendgmr) by 2002:a05:6902:1029:: with SMTP id x9mr141039ybt.420.1631146269514; Wed, 08 Sep 2021 17:11:09 -0700 (PDT) Date: Wed, 8 Sep 2021 17:10:35 -0700 In-Reply-To: <20210909001037.2842954-1-dlatypov@google.com> Message-Id: <20210909001037.2842954-2-dlatypov@google.com> Mime-Version: 1.0 References: <20210909001037.2842954-1-dlatypov@google.com> X-Mailer: git-send-email 2.33.0.309.g3052b89438-goog Subject: [PATCH 1/3] 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 and fake 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 ouptut 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 --- lib/kunit/executor.c | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/kunit/executor.c b/lib/kunit/executor.c index 50aa73ebe0e6..4a3dd7c05245 100644 --- a/lib/kunit/executor.c +++ b/lib/kunit/executor.c @@ -202,9 +202,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. */ + kunit_print_tap_header(suite_set); + + 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, @@ -213,10 +239,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 Thu Sep 9 00:10: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: 508924 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.2 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED,USER_AGENT_GIT,USER_IN_DEF_DKIM_WL autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 65F25C433FE for ; Thu, 9 Sep 2021 00:11:14 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 5123C60F6C for ; Thu, 9 Sep 2021 00:11:14 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1348307AbhIIAMV (ORCPT ); Wed, 8 Sep 2021 20:12:21 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:50348 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1348277AbhIIAMV (ORCPT ); Wed, 8 Sep 2021 20:12:21 -0400 Received: from mail-qv1-xf4a.google.com (mail-qv1-xf4a.google.com [IPv6:2607:f8b0:4864:20::f4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DF0ECC061575 for ; Wed, 8 Sep 2021 17:11:12 -0700 (PDT) Received: by mail-qv1-xf4a.google.com with SMTP id bp12-20020a05621407ec00b003773f981838so1048811qvb.22 for ; Wed, 08 Sep 2021 17:11:12 -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=EOeek9YqCE1WEuNfo23pe4dEHJdLY52SFh/RSmUKBng=; b=g6ksaohA3UCJXkzsI+7UM5IOL8c9GGz+Ma3ngTc5JLHVP7AelpozHm7Gu18F/2C26W 0DYbYO9YmxfOhu/dtV38jsInWRwzLkVJ2W6MLXZB5+/xqdhuZ23hhHol1IzBLmdVd9ZM m6QwEbG6c+AzS61dkHQNaCEN9u9E5IZMzxFICAmS+MVhskEMROHYP/ALM4qqeav6+gQ3 aWbHDigQ1Qr4WvytJfB3qL4JFb2ypdLM2hRJcUuA3z5y77KLepWbuHIY84OifoGY9Wxe X0rHOtfAJvNrpghCNXZ+1HVMcmxI+/5eqM7bkhk0Hs8qkkshpcks0IzdZbRW4feUm+5K rGJA== 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=EOeek9YqCE1WEuNfo23pe4dEHJdLY52SFh/RSmUKBng=; b=PrUqX9V2m2fYVqZj3uwyd/65mea8jlVxpJ8pAVbMnhS1ku03UyUjQEggfUZajaAYVL RZy/k4JgzbmPQdeO6yKgjBAZv1Q+EP2uyoJ89TUL5ZboHvhaCl9SQdy44ao4YcjCqMWu Ng45iuKo4AwBx55u0wiVb22ZouXvfJMRIY3V4rCiuIVWsjRTlFaeFX2xxLOTHKlyacox mGliWQZf67f6coS7nMx4mES3x1E7n6sQLT0Iugewxy4Y614gJ02p2g7WYrh5XweJpeWj xdZ1A60mOXWoAxcc8czyJIYxN6LqEICoZgeN6QqP5mtBbzMZrrncJvTveje/J1u7EQ+K M3Sw== X-Gm-Message-State: AOAM530hZZWLbrgnnk85jqCYk7X6MWGC9JnlYCp5BFnUpweLT/XyHbew mYrC2pttD8//02Fk23OamQ2tzd9YTWd+MQ== X-Google-Smtp-Source: ABdhPJxqjsJ7Y7+RMTPBvhwl7RjMzMsy/tTTixbuFrz4dfsigmrLEpOES/2Fdcu1UC5aCVzl4peSbTnr5BsT1g== X-Received: from dlatypov.svl.corp.google.com ([2620:15c:2cd:202:68b6:f792:6cb6:9052]) (user=dlatypov job=sendgmr) by 2002:a0c:9c48:: with SMTP id w8mr1007484qve.65.1631146271816; Wed, 08 Sep 2021 17:11:11 -0700 (PDT) Date: Wed, 8 Sep 2021 17:10:36 -0700 In-Reply-To: <20210909001037.2842954-1-dlatypov@google.com> Message-Id: <20210909001037.2842954-3-dlatypov@google.com> Mime-Version: 1.0 References: <20210909001037.2842954-1-dlatypov@google.com> X-Mailer: git-send-email 2.33.0.309.g3052b89438-goog Subject: [PATCH 2/3] 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 --- 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 5a931456e718..55c25f7a715c 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', @@ -382,15 +380,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': @@ -400,10 +395,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 Thu Sep 9 00:10:37 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Latypov X-Patchwork-Id: 508545 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.2 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED,USER_AGENT_GIT,USER_IN_DEF_DKIM_WL autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id CE1EDC433FE for ; Thu, 9 Sep 2021 00:11:16 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B7AD660698 for ; Thu, 9 Sep 2021 00:11:16 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1348297AbhIIAMY (ORCPT ); Wed, 8 Sep 2021 20:12:24 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:50360 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1348359AbhIIAMX (ORCPT ); Wed, 8 Sep 2021 20:12:23 -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 0EE32C061757 for ; Wed, 8 Sep 2021 17:11:15 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id 131-20020a251489000000b0059bdeb10a84so94048ybu.15 for ; Wed, 08 Sep 2021 17:11:15 -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=p+2wsvetj5UtuL8uRpzcfKCWjv9f6yatx39Qfr7ll3s=; b=OsAUyDM1X5XXMqdRY+EZdqcHS3MVLY2RF2tHxMPOaUs9AAce+MM3oZu2K9zZdErAaf DaI1JiAC6nXr+8mzFBhhHjHzEHZsxERJG7TrDAUC2VTskllUQJeIlS0pAQa/Ip7wGsdv N+uUTBb7tJYAxC61PP/1jNU8eekaeZNfkuRL/I9Lb4RMYVDb/tz09/Nv2jiHGd13cYA7 DFwO+m5htNVHBlp7vEA5BcGlt0XOuWtuwPaeuwSUM4sKiyBbAeH28Y6xPquj+/0mZumE 28WM1MyWqGxWhmLySGuQZMgD6Bjk34XY1FAYeTWS7qgILgvsOyvVqcsfQ7Xk9EcUNmGJ JSHQ== 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=p+2wsvetj5UtuL8uRpzcfKCWjv9f6yatx39Qfr7ll3s=; b=lcURlj3149EBR4v8zcvU0j8gDy3uK48b93MjbSk1XRvtp0jY1tOZL6lSYmtNH1wZsR 4F7cvTpYNfSFJ5vqhUD5N+QprAGbmKNApKEObr51v9iSc4BSONT1AalTrPu7SeeYeTSa yxFuCwunEYKE4LCn+/7g8Fs+MFpYgnKFwLoZybuR8PK6+7Vz+0K5jSGWhd+FyhzgosMG guKw5+rFqb7bYSi3oAn7DRZoe0MGsG2urMIZlPIW/dOIKdRr/U3Nu5HXRK0Clf/BQqIQ t6rEDTfvSR/zHw2K3cFQUEuTvGeW7/V52pjPaA37gsSAL0OQ4QZ3EdsUyXOHQVgLEGLT r42w== X-Gm-Message-State: AOAM532yvtMo7F3MTzw0H7UPIi2L77wAkZF/7o/B35Vq3OmGIY0kfu0A xSRjJ5luNtiyLCV6SL8oIW4EK60sIV1E0A== X-Google-Smtp-Source: ABdhPJx1auokodHC/QZJJPkw7wTRCjDxlRpXQgPwicV7NU0CRp02TsxXV9tF4QxBIiYAVU4K+MsHFC+s1t9Zow== X-Received: from dlatypov.svl.corp.google.com ([2620:15c:2cd:202:68b6:f792:6cb6:9052]) (user=dlatypov job=sendgmr) by 2002:a25:5243:: with SMTP id g64mr161375ybb.278.1631146274326; Wed, 08 Sep 2021 17:11:14 -0700 (PDT) Date: Wed, 8 Sep 2021 17:10:37 -0700 In-Reply-To: <20210909001037.2842954-1-dlatypov@google.com> Message-Id: <20210909001037.2842954-4-dlatypov@google.com> Mime-Version: 1.0 References: <20210909001037.2842954-1-dlatypov@google.com> X-Mailer: git-send-email 2.33.0.309.g3052b89438-goog Subject: [PATCH 3/3] 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 surface as an 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 --- tools/testing/kunit/kunit.py | 91 ++++++++++++++++++++------ tools/testing/kunit/kunit_tool_test.py | 40 +++++++++++ 2 files changed, 112 insertions(+), 19 deletions(-) diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py index 55c25f7a715c..060655d60533 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,21 +91,66 @@ def build_tests(linux: kunit_kernel.LinuxSourceTree, 'built kernel successfully', build_end - build_start) -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) +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) + output = kunit_parser.extract_tap_lines(output) + # Hack! Drop the TAP version header and top-level test plan. + output.pop() + output.pop() + return list(output) + +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 + - 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) +def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest, + parse_request: KunitParseRequest) -> KunitResult: + 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 + 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) + + test_end = time.time() + exec_time += test_end - test_start + + result = parse_tests(parse_request, run_result) + overall_status = kunit_parser.max_status(overall_status, result.status) return KunitResult(status=result.status, result=result.result, elapsed_time=exec_time) @@ -166,7 +211,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) @@ -251,6 +296,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. ' @@ -324,6 +375,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) @@ -379,7 +431,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..bcfdc6664c9d 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', '1..0'] + 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()