Received: by 2002:a05:6a10:d5a5:0:0:0:0 with SMTP id gn37csp4129220pxb; Mon, 4 Oct 2021 18:16:04 -0700 (PDT) X-Google-Smtp-Source: ABdhPJz11QyJh+A/DbM7iuUJ1yYTbXgIfKcTxiqjsbn3ifwNPr6Jj9QX8XA4tu3j6IFEiQFbylXs X-Received: by 2002:a17:906:8312:: with SMTP id j18mr20082927ejx.530.1633396564252; Mon, 04 Oct 2021 18:16:04 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1633396564; cv=none; d=google.com; s=arc-20160816; b=L1RF0cwdoXAVcsCWKaLjDEe7ETLAv+Gk62W+mdrpqWpeXNJH9IB7Oj/jvfd4F+IUGv DkIVD5vM6AfMWznsQghGAT0SLZo84VznMe6nlhvYCay3PsudcuJ2vtpahx1I8dporcV3 zt/GtBm6Ye8+KyGWL/9vJnrXPZ+YmBMCWxtVSvqxTIO6poiVwdvLUjuYcY3XLOkfJPDj Ebl2Z+o/dXhnplfZQRwB+WddVW8V86cewq6ON7qvd3Uh3Lj0zfgDJqKUe4olc2ZQF8YL dJNZFr4ZzRoUwNk2tMphrh37qSLUazcV+iCICFsPKcG+gwWYDmKQ/OHcn/L6W9ZxOCS3 WNWw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:cc:to:from:subject:mime-version:message-id:date :dkim-signature; bh=PskLaVlu3EvLnhUt+DKXJ60aRt8aTWENBT4fm4khzhI=; b=WtVHES8ijmiLVw5dI61R9pA1H7TKecXrqQTNoqEA4yD6HSIhorDCZJifx/pma2XXDc dJafUypE+QuccBmZCueTi5uXqRHvYjvrNyJWMETxcWw/VKb02+tSNTW3OlKmOrfcFAed OrC6BqvZ5RviL43eMKZgJfCY5VyfZUENI3EqSuFoqtDsZARrCcZzmnpBRSYfi4yKoGM0 8aEtxVoDShme4XuzkUdQmNpoeXYp1BBsD9Aqd7vxbDOkf6eNS5ojQt1OnUuD1nXpHNZH sjRNN99kmO6S8BarBbrOqnHh4o50XDk/dAvl9sZaWutOt0Mjr2Z/rrQhd4Y+Lb01MldQ UgFg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@google.com header.s=20210112 header.b=BCEj+Fi9; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-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 dj1si5657255edb.462.2021.10.04.18.15.41; Mon, 04 Oct 2021 18:16:04 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-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=20210112 header.b=BCEj+Fi9; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-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 S230402AbhJEBPg (ORCPT + 99 others); Mon, 4 Oct 2021 21:15:36 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58650 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230405AbhJEBPf (ORCPT ); Mon, 4 Oct 2021 21:15:35 -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 188C7C061745 for ; Mon, 4 Oct 2021 18:13:46 -0700 (PDT) Received: by mail-qk1-x749.google.com with SMTP id v17-20020ae9e311000000b0045ecf8d62fdso1544015qkf.22 for ; Mon, 04 Oct 2021 18:13:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=date:message-id:mime-version:subject:from:to:cc; bh=PskLaVlu3EvLnhUt+DKXJ60aRt8aTWENBT4fm4khzhI=; b=BCEj+Fi9962Qvds1QBfK8ZZr5QbVRxEYyRgYxhlguhj9I6DidgTfOEYgSOLW0C6RF9 eS5DXCKp0JAB0Fs7MIMOQI2mZbBo4peFTPMtBlbQaZ5uRYS3BP8MgMLMD+I3x0HoTHeK khsGVprTYE+GZS2ClyYi5eYxX7+YwVqMNtlq1Ich9+GpHyPlq5IfuGKMRKRNtNsXxbLj Zetf2TV0p3ZnursJ6cDO/2tgkx7LPom72IKYFmuLDwmRi0yGT2Dw3oKSbaERwtnAwuLn +uEQuM6VKG0PQSO/9mcX5fyAVpe547fZaUJXHRYzj1MLirvnPc12LoYyb52B/kEX0T7Z jQAg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:message-id:mime-version:subject:from:to:cc; bh=PskLaVlu3EvLnhUt+DKXJ60aRt8aTWENBT4fm4khzhI=; b=tZAjLK0daAMkgVUWDZBDHHXUx/QZ0G4po4dbuddJ3b200GhDRpgnYFjDyzW9w/qdw+ dTGIfUJU2klJjoF5RXnCwCm9FYXppcj+2+52lkEdaUAvfdyiJaSgcnlFGWUKR1FQrHpq WCkFjTAnh6Z7CA1KmP351fIHTu5bN5D8OVi3mT/RYnc47/tq28cYWDis45uQxiEHtDOv pS0jBy/GRZAWRbVaJCDgHKCgxx7uNwyIpMjHwHEEw/U7g86oQkwYtiFEFBengUBNHDd/ 4hxaMRLqh3vHxajXgSKQ2qc8BGaXzNejA/up1mZQMkHLErV5D1ZPJORoFfOrTbdpbh2l NNBg== X-Gm-Message-State: AOAM530/tya8S40I/VnUyG3CjjmuPjTG5bmjrTM4d7taEbcUPx0C9DSq QSnwlOcBNPXO4KAfkO6vi/CSrsIVB2SwXA== X-Received: from dlatypov.svl.corp.google.com ([2620:15c:2cd:202:ee03:329e:90be:b395]) (user=dlatypov job=sendgmr) by 2002:a05:6214:2026:: with SMTP id 6mr13866408qvf.61.1633396425279; Mon, 04 Oct 2021 18:13:45 -0700 (PDT) Date: Mon, 4 Oct 2021 18:13:40 -0700 Message-Id: <20211005011340.2826268-1-dlatypov@google.com> Mime-Version: 1.0 X-Mailer: git-send-email 2.33.0.800.g4c38ced690-goog Subject: [PATCH v2] kunit: tool: yield output from run_kernel in real time 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 Content-Type: text/plain; charset="UTF-8" Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Currently, `run_kernel()` dumps all the kernel output to a file (.kunit/test.log) and then opens the file and yields it to callers. This made it easier to respect the requested timeout, if any. But it means that we can't yield the results in real time, either to the parser or to stdout (if --raw_output is set). This change spins up a background thread to enforce the timeout, which allows us to yield the kernel output in real time, while also copying it to the .kunit/test.log file. It's also careful to ensure that the .kunit/test.log file is complete, even in the kunit_parser throws an exception/otherwise doesn't consume every line, see the new `finally` block and unit test. For example: $ ./tools/testing/kunit/kunit.py run --arch=x86_64 --raw_output ... This does not currently have a visible effect when --raw_output is not passed, as kunit_parser.py currently only outputs everything at the end. But that could change, and this patch is a necessary step towards showing parsed test results in real time. Signed-off-by: Daniel Latypov --- v1 -> v2: restore stdin=subprocess.PIPE to avoid a UML (?) bug. https://lore.kernel.org/linux-kselftest/CABVgOSnXBtCHEMDYYqrbXWvKcrSbY9BXP4MJjsT+vzZS6W4J=w@mail.gmail.com/ --- tools/testing/kunit/kunit_kernel.py | 75 +++++++++++++++----------- tools/testing/kunit/kunit_tool_test.py | 17 ++++++ 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/tools/testing/kunit/kunit_kernel.py b/tools/testing/kunit/kunit_kernel.py index 2c6f916ccbaf..450f1c386e01 100644 --- a/tools/testing/kunit/kunit_kernel.py +++ b/tools/testing/kunit/kunit_kernel.py @@ -12,7 +12,8 @@ import subprocess import os import shutil import signal -from typing import Iterator, Optional, Tuple +import threading +from typing import Iterator, List, Optional, Tuple from contextlib import ExitStack @@ -103,8 +104,8 @@ class LinuxSourceTreeOperations(object): if stderr: # likely only due to build warnings print(stderr.decode()) - def run(self, params, timeout, build_dir, outfile) -> None: - pass + def start(self, params: List[str], build_dir: str) -> subprocess.Popen: + raise RuntimeError('not implemented!') class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): @@ -123,7 +124,7 @@ class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): kconfig.parse_from_string(self._kconfig) base_kunitconfig.merge_in_entries(kconfig) - def run(self, params, timeout, build_dir, outfile): + def start(self, params: List[str], build_dir: str) -> subprocess.Popen: kernel_path = os.path.join(build_dir, self._kernel_path) qemu_command = ['qemu-system-' + self._qemu_arch, '-nodefaults', @@ -134,18 +135,11 @@ class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): '-nographic', '-serial stdio'] + self._extra_qemu_params print('Running tests with:\n$', ' '.join(qemu_command)) - with open(outfile, 'w') as output: - process = subprocess.Popen(' '.join(qemu_command), - stdin=subprocess.PIPE, - stdout=output, - stderr=subprocess.STDOUT, - text=True, shell=True) - try: - process.wait(timeout=timeout) - except Exception as e: - print(e) - process.terminate() - return process + return subprocess.Popen(' '.join(qemu_command), + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, shell=True) class LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations): """An abstraction over command line operations performed on a source tree.""" @@ -175,17 +169,14 @@ class LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations): kunit_parser.print_with_timestamp( 'Starting Kernel with all configs takes a few minutes...') - def run(self, params, timeout, build_dir, outfile): + def start(self, params: List[str], build_dir: str) -> subprocess.Popen: """Runs the Linux UML binary. Must be named 'linux'.""" linux_bin = get_file_path(build_dir, 'linux') - outfile = get_outfile_path(build_dir) - with open(outfile, 'w') as output: - process = subprocess.Popen([linux_bin] + params, - stdin=subprocess.PIPE, - stdout=output, - stderr=subprocess.STDOUT, - text=True) - process.wait(timeout) + return subprocess.Popen([linux_bin] + params, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True) def get_kconfig_path(build_dir) -> str: return get_file_path(build_dir, KCONFIG_PATH) @@ -330,12 +321,36 @@ class LinuxSourceTree(object): args.extend(['mem=1G', 'console=tty', 'kunit_shutdown=halt']) if filter_glob: args.append('kunit.filter_glob='+filter_glob) - outfile = get_outfile_path(build_dir) - self._ops.run(args, timeout, build_dir, outfile) - subprocess.call(['stty', 'sane']) - with open(outfile, 'r') as file: - for line in file: + + process = self._ops.start(args, build_dir) + assert process.stdout is not None # tell mypy it's set + + # Enforce the timeout in a background thread. + def _wait_proc(): + try: + process.wait(timeout=timeout) + except Exception as e: + print(e) + process.terminate() + process.wait() + waiter = threading.Thread(target=_wait_proc) + waiter.start() + + output = open(get_outfile_path(build_dir), 'w') + try: + # Tee the output to the file and to our caller in real time. + for line in process.stdout: + output.write(line) yield line + # This runs even if our caller doesn't consume every line. + finally: + # Flush any leftover output to the file + output.write(process.stdout.read()) + output.close() + process.stdout.close() + + waiter.join() + subprocess.call(['stty', 'sane']) def signal_handler(self, sig, frame) -> None: logging.error('Build interruption occurred. Cleaning console.') diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py index 619c4554cbff..f9a7398a9584 100755 --- a/tools/testing/kunit/kunit_tool_test.py +++ b/tools/testing/kunit/kunit_tool_test.py @@ -14,6 +14,7 @@ import tempfile, shutil # Handling test_tmpdir import itertools import json import signal +import subprocess import os import kunit_config @@ -291,6 +292,22 @@ class LinuxSourceTreeTest(unittest.TestCase): pass tree = kunit_kernel.LinuxSourceTree('', kunitconfig_path=dir) + def test_run_kernel_hits_exception(self): + def fake_start(unused_args, unused_build_dir): + return subprocess.Popen(['echo "hi\nbye"'], shell=True, text=True, stdout=subprocess.PIPE) + + with tempfile.TemporaryDirectory('') as build_dir: + tree = kunit_kernel.LinuxSourceTree(build_dir, load_config=False) + mock.patch.object(tree._ops, 'start', side_effect=fake_start).start() + + with self.assertRaises(ValueError): + for line in tree.run_kernel(build_dir=build_dir): + self.assertEqual(line, 'hi\n') + raise ValueError('uh oh, did not read all output') + + with open(kunit_kernel.get_outfile_path(build_dir), 'rt') as outfile: + self.assertEqual(outfile.read(), 'hi\nbye\n', msg='Missing some output') + # TODO: add more test cases. base-commit: 3b29021ddd10cfb6b2565c623595bd3b02036f33 -- 2.33.0.800.g4c38ced690-goog