mail archive of the barebox mailing list
 help / color / mirror / Atom feed
From: Ahmad Fatoum <a.fatoum@barebox.org>
To: barebox@lists.infradead.org
Cc: Lars Schmidt <l.schmidt@pengutronix.de>,
	Ahmad Fatoum <a.fatoum@barebox.org>
Subject: [PATCH 3/3] test: py: add bootchooser attempt decrement and locking test
Date: Mon, 23 Jun 2025 08:26:41 +0200	[thread overview]
Message-ID: <20250623062641.3529615-4-a.fatoum@barebox.org> (raw)
In-Reply-To: <20250623062641.3529615-1-a.fatoum@barebox.org>

Add a test to verify that counting down attempts work and that the new
locking feature indeed inhibits it.

Cc: Lars Schmidt <l.schmidt@pengutronix.de>
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 common/boards/qemu-virt/qemu-virt-flash.dtso |   5 +
 test/arm/virt@multi_v7_defconfig.yaml        |   1 +
 test/arm/virt@multi_v8_defconfig.yaml        |   1 +
 test/py/helper.py                            |  63 ++++++++
 test/py/test_bootchooser.py                  | 158 +++++++++++++++++++
 test/riscv/qemu-virt64@rv64i_defconfig.yaml  |   1 +
 test/riscv/qemu@virt32_defconfig.yaml        |   1 +
 7 files changed, 230 insertions(+)
 create mode 100644 test/py/test_bootchooser.py

diff --git a/common/boards/qemu-virt/qemu-virt-flash.dtso b/common/boards/qemu-virt/qemu-virt-flash.dtso
index 087568a26d2a..77b17456496d 100644
--- a/common/boards/qemu-virt/qemu-virt-flash.dtso
+++ b/common/boards/qemu-virt/qemu-virt-flash.dtso
@@ -94,6 +94,11 @@ last_chosen@10 {
 				reg = <0x10 0x4>;
 				type = "uint32";
 			};
+
+			attempts_locked@14 {
+				reg = <0x14 0x4>;
+				type = "uint32";
+			};
 		};
 	};
 };
diff --git a/test/arm/virt@multi_v7_defconfig.yaml b/test/arm/virt@multi_v7_defconfig.yaml
index 637d132d3967..2f68e35d69a8 100644
--- a/test/arm/virt@multi_v7_defconfig.yaml
+++ b/test/arm/virt@multi_v7_defconfig.yaml
@@ -15,6 +15,7 @@ targets:
     features:
       - virtio-mmio
       - testfs
+      - barebox-state
 images:
   barebox-dt-2nd.img: !template "$LG_BUILDDIR/images/barebox-dt-2nd.img"
 imports:
diff --git a/test/arm/virt@multi_v8_defconfig.yaml b/test/arm/virt@multi_v8_defconfig.yaml
index d018af06aa53..f49dbce1b631 100644
--- a/test/arm/virt@multi_v8_defconfig.yaml
+++ b/test/arm/virt@multi_v8_defconfig.yaml
@@ -16,6 +16,7 @@ targets:
       - virtio-mmio
       - network
       - testfs
+      - barebox-state
     runner:
       tuxmake_arch: arm64
 images:
diff --git a/test/py/helper.py b/test/py/helper.py
index cfcc9c337fec..04c2de9bc0da 100644
--- a/test/py/helper.py
+++ b/test/py/helper.py
@@ -1,6 +1,8 @@
 from labgrid.driver import BareboxDriver
 import pytest
 import os
+import re
+import shlex
 from itertools import filterfalse
 
 
@@ -45,6 +47,67 @@ def of_get_property(barebox, path):
     return False
 
 
+def devinfo(barebox, device):
+    info = {}
+    section = None
+    pattern = r'^\s*([^:]+):\s*(.*)$'
+
+    for line in barebox.run_check(f"devinfo {device}"):
+        line = line.rstrip()
+        if match := re.match(r"^([^: ]+):$", line):
+            section = match.group(1)
+            if section in ["Parameters"]:
+                info[section] = {}
+            else:
+                info[section] = []
+            continue
+
+        line = line.strip()
+        if section is None or isinstance(info[section], dict):
+            if match := re.match(pattern, line):
+                key = match.group(1).strip()
+                value = match.group(2).strip()
+                # TODO: coerce to type?
+                if section is None:
+                    info[section] = line
+                else:
+                    info[section][key] = value
+        elif section:
+            info[section].append(line)
+
+    return info
+
+
+def format_dict_with_prefix(varset: dict, prefix: str) -> str:
+    parts = []
+    for k, v in varset.items():
+        escaped_val = shlex.quote(str(v))
+        parts.append(f"{prefix}{k}={escaped_val}")
+    return " ".join(parts)
+
+
+def globalvars_set(barebox, varset: dict, create=True):
+    cmd, prefix = ("global ", "") if create else ("", "global.")
+    barebox.run_check(cmd + format_dict_with_prefix(varset, prefix))
+
+
+def nvvars_set(barebox, varset: dict, create=True):
+    cmd, prefix = ("nv ", "") if create else ("", "nv.")
+    barebox.run_check(cmd + format_dict_with_prefix(varset, prefix))
+
+
+def getenv_int(barebox, var):
+    return int(barebox.run_check(f"echo ${var}")[0])
+
+
+def getstate_int(barebox, var, prefix="state.bootstate"):
+    return getenv_int(barebox, f"{prefix}.{var}")
+
+
+def getparam_int(info, var):
+    return int(info["Parameters"][var].split()[0])
+
+
 def skip_disabled(config, *options):
     if bool(config):
         undefined = list(filterfalse(config.__contains__, options))
diff --git a/test/py/test_bootchooser.py b/test/py/test_bootchooser.py
new file mode 100644
index 000000000000..a5f2b25d6550
--- /dev/null
+++ b/test/py/test_bootchooser.py
@@ -0,0 +1,158 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import pytest
+from .helper import globalvars_set, devinfo, getparam_int, \
+                    getstate_int, getenv_int
+
+
+def fit_name(suffix):
+    return f"/mnt/9p/testfs/barebox-{suffix}.fit"
+
+
+def generate_bootscript(barebox, command, name="test"):
+    barebox.run_check(f"echo -o /env/boot/{name} '#!/bin/sh'")
+    barebox.run_check(f"echo -a /env/boot/{name} '{command}'")
+    return name
+
+
+def assert_state(barebox, s0p, s0a, s1p, s1a):
+    info = devinfo(barebox, "state")
+
+    assert getparam_int(info, "bootstate.system0.priority") == s0p
+    assert getparam_int(info, "bootstate.system0.remaining_attempts") == s0a
+    assert getparam_int(info, "bootstate.system1.priority") == s1p
+    assert getparam_int(info, "bootstate.system1.remaining_attempts") == s1a
+
+
+def assert_boot(barebox, system_num, s0p, s0a, s1p, s1a):
+    expected = system_num
+    unexpected = system_num ^ 1
+
+    stdout, _, _ = barebox.run("boot bootchooser")
+    assert f"Test is booting system{expected}" in stdout
+    assert f"Test is booting system{unexpected}" not in stdout
+
+    assert_state(barebox, s0p, s0a, s1p, s1a)
+
+
+@pytest.fixture(scope="function")
+def bootchooser(barebox, env):
+    if 'barebox-state' not in env.get_target_features():
+        return None
+
+    system0 = generate_bootscript(barebox, "echo Test is booting system0",
+                                  "system0")
+    system1 = generate_bootscript(barebox, "echo Test is booting system1",
+                                  "system1")
+
+    globals_chg = {
+        "bootchooser.default_attempts": "3",
+        "bootchooser.retry": "0",
+        "bootchooser.state_prefix": "state.bootstate",
+        "bootchooser.targets": "system0 system1",
+    }
+
+    globals_new = {
+        "bootchooser.system0.boot": system0,
+        "bootchooser.system0.default_priority": "20",
+        "bootchooser.system1.boot": system1,
+        "bootchooser.system1.default_priority": "21"
+    }
+
+    globalvars_set(barebox, globals_chg)
+    globalvars_set(barebox, globals_new, create=True)
+
+    barebox.run_check("bootchooser -a default -p default")
+    return True
+
+
+def test_bootchooser(barebox, env, bootchooser):
+    if bootchooser is None:
+        pytest.xfail("barebox-state feature not specified")
+
+    assert_state(barebox, s0p=20, s0a=3, s1p=21, s1a=3)
+
+    assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=2)
+    assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=1)
+    assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=0)
+    assert_boot(barebox, 0, s0p=20, s0a=2, s1p=21, s1a=0)
+    assert_boot(barebox, 0, s0p=20, s0a=1, s1p=21, s1a=0)
+    assert_boot(barebox, 0, s0p=20, s0a=0, s1p=21, s1a=0)
+
+    barebox.run_check("bootchooser -a default -p default")
+
+    assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=2)
+    assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=1)
+    assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=0)
+    assert_boot(barebox, 0, s0p=20, s0a=2, s1p=21, s1a=0)
+    assert_boot(barebox, 0, s0p=20, s0a=1, s1p=21, s1a=0)
+    assert_boot(barebox, 0, s0p=20, s0a=0, s1p=21, s1a=0)
+
+
+def test_bootchooser_lock_command(barebox, env, bootchooser):
+    if bootchooser is None:
+        pytest.xfail("barebox-state feature not specified")
+
+    assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=2)
+
+    stdout = barebox.run_check("bootchooser -i")
+    assert 'Locking of boot attempt counter: disabled' in stdout[-3:]
+    assert getstate_int(barebox, "attempts_locked") == 0
+
+    barebox.run_check("bootchooser -l")
+
+    stdout = barebox.run_check("bootchooser -i")
+    assert 'Locking of boot attempt counter: enabled' in stdout[-3:]
+    assert getstate_int(barebox, "attempts_locked") == 0
+
+    for i in range(4):
+        assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=2)
+
+    barebox.run_check("bootchooser -L")
+
+    stdout = barebox.run_check("bootchooser -i")
+    assert 'Locking of boot attempt counter: disabled' in stdout[-3:]
+    assert getstate_int(barebox, "attempts_locked") == 0
+
+    assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=1)
+    assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=0)
+    assert_boot(barebox, 0, s0p=20, s0a=2, s1p=21, s1a=0)
+    assert_boot(barebox, 0, s0p=20, s0a=1, s1p=21, s1a=0)
+    assert_boot(barebox, 0, s0p=20, s0a=0, s1p=21, s1a=0)
+
+
+def test_bootchooser_lock_state(barebox, env, bootchooser):
+    if bootchooser is None:
+        pytest.xfail("barebox-state feature not specified")
+
+    assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=2)
+
+    stdout = barebox.run_check("bootchooser -i")
+    assert 'Locking of boot attempt counter: disabled' in stdout[-3:]
+    assert getstate_int(barebox, "attempts_locked") == 0
+
+    barebox.run_check("state.bootstate.attempts_locked=1")
+    assert getenv_int(barebox, "state.dirty") == 1
+    stdout = barebox.run_check("state -s")
+    assert getenv_int(barebox, "state.dirty") == 0
+
+    stdout = barebox.run_check("bootchooser -i")
+    assert 'Locking of boot attempt counter: enabled' in stdout[-3:]
+    assert getstate_int(barebox, "attempts_locked") == 1
+
+    for i in range(4):
+        assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=2)
+
+    assert getenv_int(barebox, "state.dirty") == 0
+
+    barebox.run_check("state.bootstate.attempts_locked=0")
+
+    stdout = barebox.run_check("bootchooser -i")
+    assert 'Locking of boot attempt counter: disabled' in stdout[-3:]
+    assert getstate_int(barebox, "attempts_locked") == 0
+
+    assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=1)
+    assert_boot(barebox, 1, s0p=20, s0a=3, s1p=21, s1a=0)
+    assert_boot(barebox, 0, s0p=20, s0a=2, s1p=21, s1a=0)
+    assert_boot(barebox, 0, s0p=20, s0a=1, s1p=21, s1a=0)
+    assert_boot(barebox, 0, s0p=20, s0a=0, s1p=21, s1a=0)
diff --git a/test/riscv/qemu-virt64@rv64i_defconfig.yaml b/test/riscv/qemu-virt64@rv64i_defconfig.yaml
index c920413a17bf..83cdac6c8b9d 100644
--- a/test/riscv/qemu-virt64@rv64i_defconfig.yaml
+++ b/test/riscv/qemu-virt64@rv64i_defconfig.yaml
@@ -14,6 +14,7 @@ targets:
       BareboxTestStrategy: {}
     features:
       - virtio-mmio
+      - barebox-state
 images:
   barebox-dt-2nd.img: !template "$LG_BUILDDIR/images/barebox-dt-2nd.img"
 imports:
diff --git a/test/riscv/qemu@virt32_defconfig.yaml b/test/riscv/qemu@virt32_defconfig.yaml
index da1bb5bddd3e..03d5ef281592 100644
--- a/test/riscv/qemu@virt32_defconfig.yaml
+++ b/test/riscv/qemu@virt32_defconfig.yaml
@@ -15,6 +15,7 @@ targets:
       BareboxTestStrategy: {}
     features:
       - virtio-mmio
+      - barebox-state
     runner:
       download:
         opensbi-riscv32-generic-fw_dynamic.bin: https://github.com/qemu/qemu/blob/v5.2.0/pc-bios/opensbi-riscv32-generic-fw_dynamic.bin?raw=true
-- 
2.39.5




  parent reply	other threads:[~2025-06-23  6:40 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-06-23  6:26 [PATCH 0/3] test: test recent regressions/features Ahmad Fatoum
2025-06-23  6:26 ` [PATCH 1/3] test: self: idr: add test for idr_remove in idr_for_each_entry Ahmad Fatoum
2025-06-23  6:26 ` [PATCH 2/3] test: py: test mount/unmount of pstore file system Ahmad Fatoum
2025-06-23  8:37   ` Ahmad Fatoum
2025-06-23  9:26     ` Sascha Hauer
2025-06-23  6:26 ` Ahmad Fatoum [this message]
2025-06-23  8:35 ` [PATCH 0/3] test: test recent regressions/features Sascha Hauer

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=20250623062641.3529615-4-a.fatoum@barebox.org \
    --to=a.fatoum@barebox.org \
    --cc=barebox@lists.infradead.org \
    --cc=l.schmidt@pengutronix.de \
    /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