All of lore.kernel.org
 help / color / mirror / Atom feed
From: Anthony PERARD <anthony@xenproject.org>
To: xen-devel@lists.xenproject.org
Cc: "Andrew Cooper" <andrew.cooper3@citrix.com>,
	"Marek Marczykowski-Górecki" <marmarek@invisiblethingslab.com>,
	"Anthony PERARD" <anthony.perard@vates.tech>,
	"Doug Goldstein" <cardoe@cardoe.com>,
	"Stefano Stabellini" <sstabellini@kernel.org>
Subject: [RFC XEN PATCH 05/13] pytest: Introduce reading the console, and add a test
Date: Tue, 22 Sep 2026 12:08:52 +0200	[thread overview]
Message-ID: <20260922100900.29129-6-anthony@xenproject.org> (raw)
In-Reply-To: <20260922100900.29129-1-anthony@xenproject.org>

From: Anthony PERARD <anthony.perard@vates.tech>

The test will do more than just checking dom0 booted in a later patch.

Signed-off-by: Anthony PERARD <anthony.perard@vates.tech>
---
 automation/pytest/lib/ipmi.py    | 86 ++++++++++++++++++++++++++++++++
 automation/pytest/pyproject.toml |  1 +
 automation/pytest/test_boot.py   | 24 +++++++++
 3 files changed, 111 insertions(+)

diff --git a/automation/pytest/lib/ipmi.py b/automation/pytest/lib/ipmi.py
index 0d3b085f704c..bda5d8077577 100644
--- a/automation/pytest/lib/ipmi.py
+++ b/automation/pytest/lib/ipmi.py
@@ -1,6 +1,8 @@
 import logging
 import paramiko
 import re
+import time
+from typing import Generator
 
 
 class IPMI:
@@ -67,3 +69,87 @@ class IPMI:
 
     def power_off(self):
         self._power('off force')
+
+    def get_console(self):
+        logging.info(f"Start console acquisition on {self.blade_node}")
+        ssh = self._new_client()
+        channel = ssh.invoke_shell()
+
+        output = ""
+        logging.debug("wait for prompt")
+        while True:
+            if channel.recv_ready():
+                output += channel.recv(9999).decode('utf-8')
+                # wait for prompt
+                logging.debug(output)
+                if output.strip().endswith('hpiLO->'):
+                    logging.debug("got prompt")
+                    break
+            elif channel.exit_status_ready():
+                raise Exception("Shell closed unexpectedly.")
+            else:
+                time.sleep(0.1)  # Wait a bit for more output
+
+        logging.debug("run connect node vsp acquire")
+        cmd = f"connect node vsp acquire {self.blade_node}\n"
+        channel.send(cmd)
+
+        self.channel = channel
+
+    def console_output_generator(self) -> Generator[str]:
+        if not self.channel:
+            raise Exception("Must call get_console")
+        channel = self.channel
+        previous_output = ''
+        # `count` when this reach 0, we had a line from the console that isn't
+        # finished, like a prompt, for sometime already, so display it in the
+        # debug.
+        count = 0
+        ansi_escape = re.compile(r'''
+            \x1B  # ESC
+            (?:   # 7-bit C1 Fe (except CSI)
+                [@-Z\\-_]
+            |     # or [ for CSI, followed by a control sequence
+                \[
+                [0-?]*  # Parameter bytes
+                [ -/]*  # Intermediate bytes
+                [@-~]   # Final byte
+            )
+        ''', re.VERBOSE)
+        while True:
+            if channel.recv_ready():
+                output = previous_output
+                output += channel.recv(9999).decode('utf-8')
+                output_lines = output.splitlines(keepends=True)
+                for line in output_lines[:-1]:
+                    line = ansi_escape.sub('', line)
+                    line = line.rstrip()
+                    if line in ['', '\r', '\r\n']:
+                        continue
+                    logging.debug(line)
+                    yield line
+                previous_output = output_lines[-1]
+                if previous_output.endswith('\n'):
+                    line = previous_output
+                    line = ansi_escape.sub('', line)
+                    line = line.rstrip()
+                    if line not in ['', '\r', '\r\n']:
+                        logging.debug(line)
+                    previous_output = ''
+                    count = 0
+                else:
+                    count = 20
+                # previous_output = output[-len(expect):]
+            # Check if the shell closed unexpectedly
+            elif channel.exit_status_ready():
+                raise Exception("Shell closed unexpectedly.")
+            else:
+                if count > 1:
+                    count -= 1
+                elif count == 1:
+                    line = previous_output
+                    line = ansi_escape.sub('', line)
+                    if line not in ['\r']:
+                        logging.debug("XXXXX output buffer: %s XXXXXX" % repr(line))
+                    count -= 1
+                time.sleep(0.5)  # Wait a bit for more output
diff --git a/automation/pytest/pyproject.toml b/automation/pytest/pyproject.toml
index 78f60c4f8e77..4ab7094e0d84 100644
--- a/automation/pytest/pyproject.toml
+++ b/automation/pytest/pyproject.toml
@@ -6,6 +6,7 @@ dependencies = [
     "paramiko>=4.0.0",
     "pip>=26.0.1",
     "pytest>=9.0.2",
+    "pytest-timeout>=2.4.0",
     "types-paramiko>=4.0.0.20260322",
 ]
 [dependency-groups]
diff --git a/automation/pytest/test_boot.py b/automation/pytest/test_boot.py
index b7610736fa25..158f1a30dcb6 100644
--- a/automation/pytest/test_boot.py
+++ b/automation/pytest/test_boot.py
@@ -1,3 +1,4 @@
+import logging
 import os
 from pathlib import Path
 import pytest
@@ -53,8 +54,31 @@ def machine_on_ipmi(prepare_netboot: None) -> Generator[IPMI]:
             password=os.environ['MOON_CONSOLE_PASS'],
             blade_node=os.environ['IPMI_BLADE_NODE'],
             )
+    # start console
+    ipmi.get_console()
     ipmi.power_off()
     ipmi.power_status()
     ipmi.power_on()
     yield ipmi
     ipmi.power_off()
+
+
+@pytest.mark.timeout(600)
+def test_xen_tools_tests(machine_on_ipmi: IPMI,
+                         ) -> None:
+    expect_string = "Dom0 ready"
+    for line in machine_on_ipmi.console_output_generator():
+        #
+        # Wait for `expect_string` for the test to succeed
+        #
+        if expect_string in line:
+            logging.info(f"got expect_string '{line}'.")
+            break
+        #
+        # Some common expected or not console outputs.
+        #
+        if "Latest ChangeSet:" in line:
+            logging.info(f"Test xen build from '{line}'.")
+        if "Manual reset required ('noreboot' specified)" in line:
+            # Test failed, could wait a bit before failing the test.
+            logging.error("Xen panic")
-- 
Anthony PERARD



  parent reply	other threads:[~2026-09-22 10:09 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-22 10:08 [RFC XEN PATCH 00/13] Running bare-metal tests with "pytest" Anthony PERARD
2026-09-22 10:08 ` [RFC XEN PATCH 01/13] pytest: Add fixture to prepare rootfs Anthony PERARD
2026-09-22 10:08 ` [RFC XEN PATCH 02/13] Creating new python project Anthony PERARD
2026-09-22 10:08 ` [RFC XEN PATCH 03/13] pytest: Add fixture to prepare the tftp server Anthony PERARD
2026-09-22 10:08 ` [RFC XEN PATCH 04/13] pytest: Add fixture that boot a machine Anthony PERARD
2026-09-22 10:08 ` Anthony PERARD [this message]
2026-09-22 10:08 ` [RFC XEN PATCH 06/13] We need an older version of paramiko Anthony PERARD
2026-09-22 10:08 ` [RFC XEN PATCH 07/13] pytest: Add random string to look for in the logs Anthony PERARD
2026-09-22 10:08 ` [RFC XEN PATCH 08/13] pytest: Run run-tools-tests over ssh on the test machine Anthony PERARD
2026-09-22 10:08 ` [RFC XEN PATCH 09/13] pytest: Generate a rootfs for a guest Anthony PERARD
2026-09-22 10:08 ` [RFC XEN PATCH 10/13] pytest: Add guest test Anthony PERARD
2026-09-22 10:08 ` [RFC XEN PATCH 11/13] pytest: Add config to enable live log, and disable ssh/paramiko logging Anthony PERARD
2026-09-22 10:08 ` [RFC XEN PATCH 12/13] pytest: Add example .env for options for the tests Anthony PERARD
2026-09-22 10:09 ` [RFC XEN PATCH 13/13] gitlab-ci: Add job for moonshot with pytest Anthony PERARD
2026-09-22 11:01 ` [RFC XEN PATCH 00/13] Running bare-metal tests with "pytest" Marek Marczykowski-Górecki
2026-09-22 14:31 ` Baptiste Le Duc

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=20260922100900.29129-6-anthony@xenproject.org \
    --to=anthony@xenproject.org \
    --cc=andrew.cooper3@citrix.com \
    --cc=anthony.perard@vates.tech \
    --cc=cardoe@cardoe.com \
    --cc=marmarek@invisiblethingslab.com \
    --cc=sstabellini@kernel.org \
    --cc=xen-devel@lists.xenproject.org \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.