qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
To: qemu-devel@nongnu.org
Cc: "Thomas Huth" <thuth@redhat.com>,
	"Philippe Mathieu-Daudé" <philmd@linaro.org>,
	"Daniel P. Berrangé" <berrange@redhat.com>,
	"Gustavo Romero" <gustavo.romero@linaro.org>,
	"Pierrick Bouvier" <pierrick.bouvier@linaro.org>,
	"Alex Bennée" <alex.bennee@linaro.org>,
	"Paolo Bonzini" <pbonzini@redhat.com>,
	"Manos Pitsidianakis" <manos.pitsidianakis@linaro.org>
Subject: [PATCH v3 1/4] tests/functional: add --debug CLI arg
Date: Fri, 25 Jul 2025 12:41:22 +0300	[thread overview]
Message-ID: <20250725-functional_tests_debug_arg-v3-1-b89921baf810@linaro.org> (raw)
In-Reply-To: <20250725-functional_tests_debug_arg-v3-0-b89921baf810@linaro.org>

Add argument parsing to functional tests to improve developer experience
when running individual tests. All logs are printed to stdout
interspersed with TAP output.

Example usage, assuming current build directory with qemu source code in
the parent directory (see docs/devel/testing/functional.rst for details):

  $ export PYTHONPATH=../python:../tests/functional
  $ export QEMU_TEST_QEMU_BINARY="$(pwd)/qemu-system-aarch64"
  $ ./pyvenv/bin/python3 ../tests/functional/test_aarch64_virt.py --help
  usage: test_aarch64_virt [-h] [-d]

  QEMU Functional test

  options:
    -h, --help   show this help message and exit
    -d, --debug  Also print test and console logs on stdout. This will
                 make the TAP output invalid and is meant for debugging
                 only.

Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
---
 docs/devel/testing/functional.rst      |  2 ++
 tests/functional/qemu_test/testcase.py | 54 +++++++++++++++++++++++++++++++---
 2 files changed, 52 insertions(+), 4 deletions(-)

diff --git a/docs/devel/testing/functional.rst b/docs/devel/testing/functional.rst
index 3728bab6c0c4b0cbacd00744bdb5c1462c71d7a8..2725633e09104db3912ec6167bbda652f40aa969 100644
--- a/docs/devel/testing/functional.rst
+++ b/docs/devel/testing/functional.rst
@@ -63,6 +63,8 @@ directory should be your build folder. For example::
   $ export QEMU_TEST_QEMU_BINARY=$PWD/qemu-system-x86_64
   $ pyvenv/bin/python3 ../tests/functional/test_file.py
 
+By default, functional tests redirect informational logs and console output to
+log files. Specify the ``--debug`` flag to also print those to standard output.
 The test framework will automatically purge any scratch files created during
 the tests. If needing to debug a failed test, it is possible to keep these
 files around on disk by setting ``QEMU_TEST_KEEP_SCRATCH=1`` as an env
diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py
index 2a78e735f1604f21efd18e38ee0d586496d6b38c..eedca7f1ad29c9e654cf56535acf9639d679f5c4 100644
--- a/tests/functional/qemu_test/testcase.py
+++ b/tests/functional/qemu_test/testcase.py
@@ -11,6 +11,7 @@
 # This work is licensed under the terms of the GNU GPL, version 2 or
 # later.  See the COPYING file in the top-level directory.
 
+import argparse
 import logging
 import os
 from pathlib import Path
@@ -33,6 +34,28 @@
 
 
 class QemuBaseTest(unittest.TestCase):
+    debug: bool = False
+
+    """
+    Class method that initializes class attributes from given command-line
+    arguments.
+    """
+    @staticmethod
+    def parse_args():
+        test_name = os.path.basename(sys.argv[0])[:-3]
+        parser = argparse.ArgumentParser(
+            prog=test_name, description="QEMU Functional test"
+        )
+        parser.add_argument(
+            "-d",
+            "--debug",
+            action="store_true",
+            help="Also print test and console logs on stdout. This will make "
+            "the TAP output invalid and is meant for debugging only.",
+        )
+        args = parser.parse_args()
+        QemuBaseTest.debug = args.debug
+        return
 
     '''
     @params compressed: filename, Asset, or file-like object to uncompress
@@ -197,6 +220,14 @@ def assets_available(self):
         return True
 
     def setUp(self):
+        self.stdout_handler = None
+        if QemuBaseTest.debug:
+            self.stdout_handler = logging.StreamHandler(sys.stdout)
+            self.stdout_handler.setLevel(logging.DEBUG)
+            formatter = logging.Formatter(
+                "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+            )
+            self.stdout_handler.setFormatter(formatter)
         self.qemu_bin = os.getenv('QEMU_TEST_QEMU_BINARY')
         self.assertIsNotNone(self.qemu_bin, 'QEMU_TEST_QEMU_BINARY must be set')
         self.arch = self.qemu_bin.split('-')[-1]
@@ -216,12 +247,17 @@ def setUp(self):
             '%(asctime)s - %(levelname)s: %(message)s')
         self._log_fh.setFormatter(fileFormatter)
         self.log.addHandler(self._log_fh)
+        if self.stdout_handler:
+            self.log.addHandler(self.stdout_handler)
 
         # Capture QEMUMachine logging
         self.machinelog = logging.getLogger('qemu.machine')
         self.machinelog.setLevel(logging.DEBUG)
         self.machinelog.addHandler(self._log_fh)
 
+        if self.stdout_handler:
+            self.machinelog.addHandler(self.stdout_handler)
+
         if not self.assets_available():
             self.skipTest('One or more assets is not available')
 
@@ -231,15 +267,19 @@ def tearDown(self):
         if self.socketdir is not None:
             shutil.rmtree(self.socketdir.name)
             self.socketdir = None
-        self.machinelog.removeHandler(self._log_fh)
-        self.log.removeHandler(self._log_fh)
-        self._log_fh.close()
+        for handler in [self._log_fh, self.stdout_handler]:
+            if handler is None:
+                continue
+            self.machinelog.removeHandler(handler)
+            self.log.removeHandler(handler)
+            handler.close()
 
     def main():
         warnings.simplefilter("default")
         os.environ["PYTHONWARNINGS"] = "default"
 
         path = os.path.basename(sys.argv[0])[:-3]
+        QemuBaseTest.parse_args()
 
         cache = os.environ.get("QEMU_TEST_PRECACHE", None)
         if cache is not None:
@@ -297,6 +337,8 @@ def setUp(self):
         fileFormatter = logging.Formatter('%(asctime)s: %(message)s')
         self._console_log_fh.setFormatter(fileFormatter)
         console_log.addHandler(self._console_log_fh)
+        if self.stdout_handler:
+            console_log.addHandler(self.stdout_handler)
 
     def set_machine(self, machinename):
         # TODO: We should use QMP to get the list of available machines
@@ -403,6 +445,10 @@ def set_vm_arg(self, arg, value):
     def tearDown(self):
         for vm in self._vms.values():
             vm.shutdown()
-        logging.getLogger('console').removeHandler(self._console_log_fh)
+        console_log = logging.getLogger("console")
+        console_log.removeHandler(self._console_log_fh)
         self._console_log_fh.close()
+        if self.stdout_handler:
+            console_log.removeHandler(self.stdout_handler)
+            self.stdout_handler.close()
         super().tearDown()

-- 
2.47.2



  reply	other threads:[~2025-07-25  9:42 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-07-25  9:41 [PATCH v3 0/4] tests/functional: add CLI args Manos Pitsidianakis
2025-07-25  9:41 ` Manos Pitsidianakis [this message]
2025-07-25 12:44   ` [PATCH v3 1/4] tests/functional: add --debug CLI arg Alex Bennée
2025-07-25  9:41 ` [PATCH v3 2/4] tests/functional: add --keep-scratch " Manos Pitsidianakis
2025-07-25  9:41 ` [PATCH v3 3/4] tests/functional: add --list-tests " Manos Pitsidianakis
2025-07-25  9:41 ` [PATCH v3 4/4] tests/functional: add -k TEST_NAME_PATTERN " Manos Pitsidianakis
2025-07-25 12:44   ` Alex Bennée
2025-07-25 13:25   ` Daniel P. Berrangé
2025-07-25 14:48     ` Daniel P. Berrangé
2025-07-26  6:54       ` Thomas Huth
2025-07-28  8:32         ` Daniel P. Berrangé

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250725-functional_tests_debug_arg-v3-1-b89921baf810@linaro.org \
    --to=manos.pitsidianakis@linaro.org \
    --cc=alex.bennee@linaro.org \
    --cc=berrange@redhat.com \
    --cc=gustavo.romero@linaro.org \
    --cc=pbonzini@redhat.com \
    --cc=philmd@linaro.org \
    --cc=pierrick.bouvier@linaro.org \
    --cc=qemu-devel@nongnu.org \
    --cc=thuth@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).