public inbox for isar-users@googlegroups.com
 help / color / mirror / Atom feed
From: Uladzimir Bely <ubely@ilbers.de>
To: isar-users@googlegroups.com
Subject: [PATCH 3/8] cibuilder.py: Split vm_start function to smaller subfunctions
Date: Fri, 18 Aug 2023 09:07:01 +0200	[thread overview]
Message-ID: <20230818070706.27913-4-ubely@ilbers.de> (raw)
In-Reply-To: <20230818070706.27913-1-ubely@ilbers.de>

vm_start is a pretty large function that does the following:
- starts machine in qemu;
- parses machine output (e.g., serial console);
- runs remote command over ssh, if requested;
- kills qemu process.

Splitting it to smaller sub-functions improves code readability and
potentially allows to reuse these sub-functions results in different
tests tests.

Signed-off-by: Uladzimir Bely <ubely@ilbers.de>
---
 testsuite/cibuilder.py | 186 +++++++++++++++++++++++------------------
 1 file changed, 103 insertions(+), 83 deletions(-)

diff --git a/testsuite/cibuilder.py b/testsuite/cibuilder.py
index 80b3aa88..8f28d05a 100755
--- a/testsuite/cibuilder.py
+++ b/testsuite/cibuilder.py
@@ -346,47 +346,70 @@ class CIBuilder(Test):
         self.fail('No command to run specified')
 
 
-    def vm_start(self, arch='amd64', distro='buster',
-                 enforce_pcbios=False, skip_modulecheck=False,
-                 image='isar-image-base', cmd=None, script=None):
-        time_to_wait = self.params.get('time_to_wait', default=DEF_VM_TO_SEC)
-
-        self.log.info('===================================================')
-        self.log.info('Running Isar VM boot test for (' + distro + '-' + arch + ')')
-        self.log.info('Remote command is ' + str(cmd))
-        self.log.info('Remote script is ' + str(script))
-        self.log.info('Isar build folder is: ' + self.build_dir)
-        self.log.info('===================================================')
-
-        self.check_init()
-
+    def vm_turn_on(self, arch='amd64', distro='buster', image='isar-image-base',
+                   enforce_pcbios=False):
         logdir = '%s/vm_start' % self.build_dir
         if not os.path.exists(logdir):
             os.mkdir(logdir)
         prefix = '%s-vm_start_%s_%s_' % (time.strftime('%Y%m%d-%H%M%S'),
                                          distro, arch)
-        fd, output_file = tempfile.mkstemp(suffix='_log.txt', prefix=prefix,
+        fd, boot_log = tempfile.mkstemp(suffix='_log.txt', prefix=prefix,
                                            dir=logdir, text=True)
-        os.chmod(output_file, 0o644)
+        os.chmod(boot_log, 0o644)
         latest_link = '%s/vm_start_%s_%s_latest.txt' % (logdir, distro, arch)
         if os.path.exists(latest_link):
             os.unlink(latest_link)
-        os.symlink(os.path.basename(output_file), latest_link)
+        os.symlink(os.path.basename(boot_log), latest_link)
 
         cmdline = start_vm.format_qemu_cmdline(arch, self.build_dir, distro, image,
-                                               output_file, None, enforce_pcbios)
+                                               boot_log, None, enforce_pcbios)
         cmdline.insert(1, '-nographic')
 
         self.log.info('QEMU boot line:\n' + ' '.join(cmdline))
+        self.log.info('QEMU boot log:\n' + boot_log)
+
+        p1 = subprocess.Popen('exec ' + ' '.join(cmdline), shell=True,
+                              stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+                              universal_newlines=True)
+        self.log.debug("Started VM with pid %s" % (p1.pid))
 
-        login_prompt = b'isar login:'
+        return p1, cmdline, boot_log
+
+
+    def vm_wait_boot(self, p1, timeout):
+        login_prompt = b' login:'
+
+        poller = select.poll()
+        poller.register(p1.stdout, select.POLLIN)
+        poller.register(p1.stderr, select.POLLIN)
+
+        while time.time() < timeout and p1.poll() is None:
+            events = poller.poll(1000 * (timeout - time.time()))
+            for fd, event in events:
+                if event != select.POLLIN:
+                    continue
+                if fd == p1.stdout.fileno():
+                    # Wait for the complete string if it is read in chunks
+                    # like "i", "sar", " login:"
+                    time.sleep(0.01)
+                    data = os.read(fd, 1024)
+                    if login_prompt in data:
+                        self.log.debug('Got login prompt')
+                        return 0
+                if fd == p1.stderr.fileno():
+                    app_log.error(p1.stderr.readline().rstrip())
+
+        self.log.error("Didn't get login prompt")
+        return 1
+
+
+    def vm_parse_output(self, boot_log, bb_output, skip_modulecheck):
         # the printk of recipes-kernel/example-module
         module_output = b'Just an example'
         resize_output = None
-
-        bb_output = start_vm.get_bitbake_env(arch, distro, image).decode()
         image_fstypes = start_vm.get_bitbake_var(bb_output, 'IMAGE_FSTYPES')
         wks_file = start_vm.get_bitbake_var(bb_output, 'WKS_FILE')
+
         # only the first type will be tested in start_vm.py
         if image_fstypes.split()[0] == 'wic':
             if wks_file:
@@ -399,72 +422,69 @@ class CIBuilder(Test):
                         resize_output = b'resized filesystem to'
                     if "sdimage-efi-btrfs" in wks_file:
                         resize_output = b': resize device '
+        rc = 0
+        if os.path.exists(boot_log) and os.path.getsize(boot_log) > 0:
+            with open(boot_log, "rb") as f1:
+                data = f1.read()
+                if (module_output in data or skip_modulecheck):
+                    if resize_output and not resize_output in data:
+                        rc = 1
+                        self.log.error("No resize output while expected")
+                else:
+                    rc = 2
+                    self.log.error("No example module output while expected")
+        return rc
 
-        timeout = time.time() + int(time_to_wait)
 
-        p1 = subprocess.Popen('exec ' + ' '.join(cmdline), shell=True,
-                              stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
-                              universal_newlines=True)
+    def vm_turn_off(self, p1):
+        if p1.poll() is None:
+            p1.kill()
+        p1.wait()
 
-        if cmd is not None or script is not None:
-            try:
-                user='ci'
-                host='localhost'
-                port = 22
-                for arg in cmdline:
-                    match = re.match(r".*hostfwd=tcp::(\d*).*", arg)
-                    if match:
-                        port = match.group(1)
-                        break
-
-                rc = self.remote_run(user, host, port, cmd, script, p1, timeout)
-
-            finally:
-                if p1.poll() is None:
-                    self.log.debug('Killing qemu...')
-                    p1.kill()
-                p1.wait()
+        self.log.debug("Stopped VM with pid %s" % (p1.pid))
 
-            if rc != 0:
-                self.fail('Log ' + output_file)
 
-            return
+    def vm_start(self, arch='amd64', distro='buster',
+                 enforce_pcbios=False, skip_modulecheck=False,
+                 image='isar-image-base', cmd=None, script=None):
+        time_to_wait = self.params.get('time_to_wait', default=DEF_VM_TO_SEC)
 
-        try:
-            poller = select.poll()
-            poller.register(p1.stdout, select.POLLIN)
-            poller.register(p1.stderr, select.POLLIN)
-            while time.time() < timeout and p1.poll() is None:
-                events = poller.poll(1000 * (timeout - time.time()))
-                for fd, event in events:
-                    if event != select.POLLIN:
-                        continue
-                    if fd == p1.stdout.fileno():
-                        # Wait for the complete string if it is read in chunks
-                        # like "i", "sar", " login:"
-                        time.sleep(0.01)
-                        data = os.read(fd, 1024)
-                        if login_prompt in data:
-                            raise CanBeFinished
-                    if fd == p1.stderr.fileno():
-                        app_log.error(p1.stderr.readline().rstrip())
-        except CanBeFinished:
-            self.log.debug('Got login prompt')
-        finally:
-            if p1.poll() is None:
-                p1.kill()
-            p1.wait()
+        self.log.info('===================================================')
+        self.log.info('Running Isar VM boot test for (' + distro + '-' + arch + ')')
+        self.log.info('Remote command is ' + str(cmd))
+        self.log.info('Remote script is ' + str(script))
+        self.log.info('Isar build folder is: ' + self.build_dir)
+        self.log.info('===================================================')
 
-        if os.path.exists(output_file) and os.path.getsize(output_file) > 0:
-            with open(output_file, "rb") as f1:
-                data = f1.read()
-                if (module_output in data or skip_modulecheck) \
-                   and login_prompt in data:
-                    if resize_output:
-                        if resize_output in data:
-                            return
-                    else:
-                        return
-                app_log.error(data.decode(errors='replace'))
-
-        self.fail('Log ' + output_file)
+        self.check_init()
+
+        timeout = time.time() + int(time_to_wait)
+
+        p1, cmdline, boot_log = self.vm_turn_on(arch, distro, image, enforce_pcbios)
+
+        rc = self.vm_wait_boot(p1, timeout)
+        if rc != 0:
+            self.vm_turn_off(p1)
+            self.fail('Failed to boot qemu machine')
+
+        if cmd is not None or script is not None:
+            user='ci'
+            host='localhost'
+            port = 22
+            for arg in cmdline:
+                match = re.match(r".*hostfwd=tcp::(\d*).*", arg)
+                if match:
+                    port = match.group(1)
+                    break
+            rc = self.remote_run(user, host, port, cmd, script, p1, timeout)
+            if rc != 0:
+                self.vm_turn_off(p1)
+                self.fail('Failed to run test over ssh')
+        else:
+            bb_output = start_vm.get_bitbake_env(arch, distro, image).decode()
+            rc = self.vm_parse_output(boot_log, bb_output, skip_modulecheck)
+            if rc != 0:
+                self.vm_turn_off(p1)
+                self.fail('Failed to parse output')
+
+        self.vm_turn_off(p1)
-- 
2.20.1


  parent reply	other threads:[~2023-08-18  7:07 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-08-18  7:06 [PATCH 0/8] Testsuite improvements for SSH-based tests Uladzimir Bely
2023-08-18  7:06 ` [PATCH 1/8] cibuilder.py: Support custom arguments passing to CI scripts Uladzimir Bely
2023-08-18  7:07 ` [PATCH 2/8] meta-isar: Add more extra space to qemu ext4 images Uladzimir Bely
2023-08-18  7:07 ` Uladzimir Bely [this message]
2023-08-18  7:07 ` [PATCH 4/8] cibuilder.py: Simplify remote_run command Uladzimir Bely
2023-08-18  7:07 ` [PATCH 5/8] cibuilder.py: Reuse the same qemu machine in ssh-based tests Uladzimir Bely
2023-08-18  7:07 ` [PATCH 6/8] citest.py: Adapt tests to qemu reuse Uladzimir Bely
2023-08-18  7:07 ` [PATCH 7/8] cibuilder.py: enable output from remote scripts Uladzimir Bely
2023-08-18  7:07 ` [PATCH 8/8] testsuite: Switch to remote scripts with arguments Uladzimir Bely
2023-08-24 15:34 ` [PATCH 0/8] Testsuite improvements for SSH-based tests Uladzimir Bely

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=20230818070706.27913-4-ubely@ilbers.de \
    --to=ubely@ilbers.de \
    --cc=isar-users@googlegroups.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