forked from Mirror/pmbootstrap
tests: add tests for Arch and Chroot types (MR 2252)
Add some exhaustive unit testing to validate that these types behave as expected. And fix a few bugs uncovered by the tests. Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
This commit is contained in:
parent
6087a9df8f
commit
ca722b499e
8 changed files with 334 additions and 57 deletions
125
pmb/conftest.py
125
pmb/conftest.py
|
@ -1,50 +1,55 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
from contextlib import contextmanager
|
||||
import shutil
|
||||
|
||||
@contextmanager
|
||||
def _fixture_context(val):
|
||||
yield val
|
||||
import pmb.core
|
||||
from pmb.types import PmbArgs
|
||||
from pmb.helpers.args import init as init_args
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def config_file_session(tmp_path_factory):
|
||||
_testdir = Path(__file__).parent / "data/tests"
|
||||
|
||||
@pytest.fixture
|
||||
def config_file(tmp_path_factory):
|
||||
"""Fixture to create a temporary pmbootstrap.cfg file."""
|
||||
tmp_path = tmp_path_factory.mktemp("pmbootstrap")
|
||||
file = tmp_path / "pmbootstrap.cfg"
|
||||
out_file = tmp_path / "pmbootstrap.cfg"
|
||||
workdir = tmp_path / "work"
|
||||
workdir.mkdir()
|
||||
contents = """[pmbootstrap]
|
||||
build_default_device_arch = True
|
||||
ccache_size = 5G
|
||||
device = qemu-amd64
|
||||
extra_packages = neofetch,neovim,reboot-mode
|
||||
hostname = qemu-amd64
|
||||
is_default_channel = False
|
||||
jobs = 8
|
||||
kernel = edge
|
||||
locale = C.UTF-8
|
||||
ssh_keys = True
|
||||
sudo_timer = True
|
||||
systemd = always
|
||||
timezone = Europe/Berlin
|
||||
ui = gnome
|
||||
work = {0}
|
||||
|
||||
[providers]
|
||||
file = _testdir / "pmbootstrap.cfg"
|
||||
contents = open(file).read().format(workdir)
|
||||
|
||||
[mirrors]
|
||||
""".format(workdir)
|
||||
|
||||
open(file, "w").write(contents)
|
||||
return file
|
||||
open(out_file, "w").write(contents)
|
||||
return out_file
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_file(config_file_session):
|
||||
"""Fixture to create a temporary pmbootstrap.cfg file."""
|
||||
with _fixture_context(config_file_session) as val:
|
||||
yield val
|
||||
def device_package(config_file):
|
||||
"""Fixture to create a temporary deviceinfo file."""
|
||||
MOCK_DEVICE = "qemu-amd64"
|
||||
pkgdir = config_file.parent / f"device-{MOCK_DEVICE}"
|
||||
pkgdir.mkdir()
|
||||
|
||||
for file in ["APKBUILD", "deviceinfo"]:
|
||||
shutil.copy(_testdir / f"{file}.{MOCK_DEVICE}",
|
||||
pkgdir / file)
|
||||
|
||||
return pkgdir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_devices_find_path(device_package, monkeypatch):
|
||||
"""Fixture to mock pmb.helpers.devices.find_path()"""
|
||||
def mock_find_path(device, file=''):
|
||||
print(f"mock_find_path({device}, {file})")
|
||||
out = device_package / file
|
||||
if not out.exists():
|
||||
return None
|
||||
|
||||
return out
|
||||
|
||||
monkeypatch.setattr("pmb.helpers.devices.find_path", mock_find_path)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
@ -67,30 +72,44 @@ def setup_mock_ask(monkeypatch):
|
|||
monkeypatch.setattr(pmb.helpers.cli, "ask", mock_ask)
|
||||
|
||||
|
||||
# FIXME: get/set_context() is a bad hack :(
|
||||
@pytest.fixture
|
||||
def mock_context(monkeypatch):
|
||||
"""Mock set_context() to bypass sanity checks. Ideally we would
|
||||
mock get_context() as well, but since every submodule of pmb imports
|
||||
it like "from pmb.core.context import get_context()", we can't
|
||||
actually override it with monkeypatch.setattr(). So this is the
|
||||
best we can do... set_context() is only called from one place and is
|
||||
done so with the full namespace, so this works."""
|
||||
|
||||
def mock_set_context(ctx):
|
||||
print(f"mock_set_context({ctx})")
|
||||
setattr(pmb.core.context, "__context", ctx)
|
||||
|
||||
monkeypatch.setattr("pmb.core.context.set_context", mock_set_context)
|
||||
|
||||
|
||||
# FIXME: get_context() at runtime somehow doesn't return the
|
||||
# custom context we set up here.
|
||||
# @pytest.fixture(scope="session")
|
||||
# def pmb_args(config_file_session):
|
||||
# """This is (still) a hack, since a bunch of the codebase still
|
||||
# expects some global state to be initialised. We do that here."""
|
||||
@pytest.fixture
|
||||
def pmb_args(config_file, mock_context):
|
||||
"""This is (still) a hack, since a bunch of the codebase still
|
||||
expects some global state to be initialised. We do that here."""
|
||||
|
||||
# from pmb.types import PmbArgs
|
||||
# from pmb.helpers.args import init as init_args
|
||||
args = PmbArgs()
|
||||
args.config = config_file
|
||||
args.aports = None
|
||||
args.timeout = 900
|
||||
args.details_to_stdout = False
|
||||
args.quiet = False
|
||||
args.verbose = False
|
||||
args.offline = False
|
||||
args.action = "init"
|
||||
args.cross = False
|
||||
args.log = Path()
|
||||
|
||||
# args = PmbArgs()
|
||||
# args.config = config_file_session
|
||||
# args.aports = None
|
||||
# args.timeout = 900
|
||||
# args.details_to_stdout = False
|
||||
# args.quiet = False
|
||||
# args.verbose = False
|
||||
# args.offline = False
|
||||
# args.action = "init"
|
||||
# args.cross = False
|
||||
# args.log = Path()
|
||||
|
||||
# print("init_args")
|
||||
# return init_args(args)
|
||||
print("init_args")
|
||||
init_args(args)
|
||||
|
||||
@pytest.fixture
|
||||
def foreign_arch():
|
||||
|
|
|
@ -43,7 +43,9 @@ class Arch(enum.Enum):
|
|||
try:
|
||||
return Arch(arch)
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid architecture: {arch}")
|
||||
raise ValueError(f"Invalid architecture: '{arch}',"
|
||||
" expected something like:"
|
||||
f" {', '.join([str(a) for a in Arch.supported()])}")
|
||||
|
||||
|
||||
@staticmethod
|
||||
|
@ -52,7 +54,6 @@ class Arch(enum.Enum):
|
|||
"i686": Arch.x86,
|
||||
"x86_64": Arch.x86_64,
|
||||
"aarch64": Arch.aarch64,
|
||||
"arm64": Arch.aarch64,
|
||||
"armv6l": Arch.armhf,
|
||||
"armv7l": Arch.armv7,
|
||||
"armv8l": Arch.armv7,
|
||||
|
@ -93,9 +94,12 @@ class Arch(enum.Enum):
|
|||
Arch.x86: "x86",
|
||||
Arch.x86_64: "x86_64",
|
||||
Arch.armhf: "arm",
|
||||
Arch.armv7: "arm",
|
||||
Arch.aarch64: "arm64",
|
||||
Arch.riscv64: "riscv",
|
||||
Arch.ppc64le: "powerpc",
|
||||
Arch.ppc64: "powerpc",
|
||||
Arch.ppc: "powerpc",
|
||||
Arch.s390x: "s390",
|
||||
}
|
||||
return mapping.get(self, self.value)
|
||||
|
@ -105,12 +109,12 @@ class Arch(enum.Enum):
|
|||
Arch.x86: "i386",
|
||||
Arch.armhf: "arm",
|
||||
Arch.armv7: "arm",
|
||||
Arch.ppc64le: "ppc64",
|
||||
}
|
||||
return mapping.get(self, self.value)
|
||||
|
||||
|
||||
def alpine_triple(self):
|
||||
"""Get the cross compiler triple for this architecture on Alpine."""
|
||||
mapping = {
|
||||
Arch.aarch64: "aarch64-alpine-linux-musl",
|
||||
Arch.armel: "armv5-alpine-linux-musleabi",
|
||||
|
|
73
pmb/core/test_arch.py
Normal file
73
pmb/core/test_arch.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
import pytest
|
||||
|
||||
from .arch import Arch
|
||||
|
||||
def test_valid_arches():
|
||||
# Silly test
|
||||
assert Arch.native().is_native()
|
||||
|
||||
# Test constructor interface
|
||||
assert Arch.from_str("x86") == Arch.x86
|
||||
assert Arch.from_str("x86_64") == Arch.x86_64
|
||||
assert Arch.from_str("aarch64") == Arch.aarch64
|
||||
assert Arch.from_str("armhf") == Arch.armhf
|
||||
|
||||
# Test from_machine_type
|
||||
assert Arch.from_machine_type("i686") == Arch.x86
|
||||
assert Arch.from_machine_type("x86_64") == Arch.x86_64
|
||||
assert Arch.from_machine_type("aarch64") == Arch.aarch64
|
||||
|
||||
# Check supported architectures
|
||||
assert Arch.x86 in Arch.supported()
|
||||
assert Arch.x86_64 in Arch.supported()
|
||||
assert Arch.aarch64 in Arch.supported()
|
||||
assert Arch.armhf in Arch.supported()
|
||||
assert Arch.armv7 in Arch.supported()
|
||||
|
||||
# kernel arch
|
||||
assert Arch.x86.kernel() == "x86"
|
||||
assert Arch.x86_64.kernel() == "x86_64"
|
||||
assert Arch.aarch64.kernel() == "arm64" # The fun one
|
||||
assert Arch.armhf.kernel() == "arm"
|
||||
assert Arch.armv7.kernel() == "arm"
|
||||
|
||||
# qemu arch
|
||||
assert Arch.x86.qemu() == "i386"
|
||||
assert Arch.x86_64.qemu() == "x86_64"
|
||||
assert Arch.aarch64.qemu() == "aarch64"
|
||||
assert Arch.armhf.qemu() == "arm"
|
||||
assert Arch.armv7.qemu() == "arm"
|
||||
assert Arch.ppc64.qemu() == "ppc64"
|
||||
assert Arch.ppc64le.qemu() == "ppc64le"
|
||||
|
||||
# Check that Arch.cpu_emulation_required() works
|
||||
assert Arch.native() == Arch.x86_64 or Arch.x86_64.cpu_emulation_required()
|
||||
assert Arch.native() == Arch.aarch64 or Arch.aarch64.cpu_emulation_required()
|
||||
|
||||
# Check that every arch has a target triple
|
||||
for arch in Arch:
|
||||
assert arch.alpine_triple() is not None
|
||||
|
||||
# Arch-as-path magic
|
||||
assert Arch.aarch64 / Path("beep") == Path("aarch64/beep")
|
||||
assert os.fspath(Arch.aarch64 / "beep") == "aarch64/beep"
|
||||
assert isinstance(Arch.aarch64 / "beep", Path)
|
||||
assert (Arch.aarch64 / "beep").name == "beep"
|
||||
assert Path("boop") / Arch.aarch64 == Path("boop/aarch64")
|
||||
|
||||
def test_invalid_arches():
|
||||
excinfo: Any
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
Arch.from_str("invalid")
|
||||
assert "Invalid architecture: 'invalid'" in str(excinfo.value)
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
Arch.aarch64 / 5
|
||||
assert "unsupported operand type(s) for /: 'Arch' and 'int'" in str(excinfo.value)
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
"bap" / Arch.aarch64
|
||||
assert "unsupported operand type(s) for /: 'str' and 'Arch'" in str(excinfo.value)
|
57
pmb/core/test_chroot.py
Normal file
57
pmb/core/test_chroot.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
import pytest
|
||||
|
||||
from .arch import Arch
|
||||
from .context import get_context
|
||||
from .chroot import Chroot, ChrootType
|
||||
|
||||
def test_valid_chroots(pmb_args, mock_devices_find_path):
|
||||
"""Test that Chroot objects work as expected"""
|
||||
|
||||
work = get_context().config.work
|
||||
|
||||
chroot = Chroot.native()
|
||||
assert chroot.type == ChrootType.NATIVE
|
||||
assert chroot.name == ""
|
||||
assert chroot.arch in Arch.supported()
|
||||
assert not chroot.exists() # Shouldn't be created
|
||||
assert chroot.path == work / "chroot_native"
|
||||
assert str(chroot) == "native"
|
||||
|
||||
chroot = Chroot.buildroot(Arch.aarch64)
|
||||
assert chroot.type == ChrootType.BUILDROOT
|
||||
assert chroot.name == "aarch64"
|
||||
assert chroot.arch == Arch.aarch64
|
||||
assert not chroot.exists() # Shouldn't be created
|
||||
assert chroot.path == work / "chroot_buildroot_aarch64"
|
||||
assert str(chroot) == "buildroot_aarch64"
|
||||
|
||||
# FIXME: implicily assumes that we're mocking the qemu-amd64 deviceinfo
|
||||
chroot = Chroot(ChrootType.ROOTFS, "qemu-amd64")
|
||||
assert chroot.type == ChrootType.ROOTFS
|
||||
assert chroot.name == "qemu-amd64"
|
||||
assert chroot.arch == Arch.x86_64
|
||||
assert not chroot.exists() # Shouldn't be created
|
||||
assert chroot.path == work / "chroot_rootfs_qemu-amd64"
|
||||
assert str(chroot) == "rootfs_qemu-amd64"
|
||||
|
||||
|
||||
# mypy: ignore-errors
|
||||
def test_invalid_chroots(pmb_args):
|
||||
"""Test that we can't create invalid chroots."""
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
Chroot(ChrootType.BUILDROOT, "BAD_ARCH")
|
||||
assert str(excinfo.value) == "Invalid buildroot suffix: 'BAD_ARCH'"
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
Chroot(ChrootType.NATIVE, "aarch64")
|
||||
assert str(excinfo.value) == "The native suffix can't have a name but got: 'aarch64'"
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
Chroot("beep boop")
|
||||
assert str(excinfo.value) == "Invalid chroot type: 'beep boop'"
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
Chroot(5)
|
||||
assert str(excinfo.value) == "Invalid chroot type: '5'"
|
||||
|
77
pmb/data/tests/APKBUILD.qemu-amd64
Normal file
77
pmb/data/tests/APKBUILD.qemu-amd64
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Reference: <https://postmarketos.org/devicepkg>
|
||||
# Maintainer: Minecrell <minecrell@minecrell.net>
|
||||
# Co-Maintainer: Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
pkgname=device-qemu-amd64
|
||||
pkgver=6
|
||||
pkgrel=3
|
||||
pkgdesc="Simulated device in QEMU (x86_64)"
|
||||
url="https://postmarketos.org"
|
||||
arch="x86_64"
|
||||
license="MIT"
|
||||
depends="postmarketos-base systemd-boot"
|
||||
makedepends="devicepkg-dev"
|
||||
# First kernel subpackage is default in pmbootstrap init!
|
||||
subpackages="
|
||||
$pkgname-kernel-lts:kernel_lts
|
||||
$pkgname-kernel-virt:kernel_virt
|
||||
$pkgname-kernel-edge:kernel_edge
|
||||
$pkgname-kernel-none:kernel_none
|
||||
$pkgname-mce
|
||||
$pkgname-sway
|
||||
"
|
||||
source="
|
||||
deviceinfo
|
||||
modules-initfs
|
||||
mce-display-blanking.conf
|
||||
"
|
||||
options="!check !archcheck"
|
||||
|
||||
build() {
|
||||
devicepkg_build $startdir $pkgname
|
||||
}
|
||||
|
||||
package() {
|
||||
devicepkg_package $startdir $pkgname
|
||||
}
|
||||
|
||||
mce() {
|
||||
pkgdesc="Prevents screen blanking for UI's using mce (Glacier, Asteroid)"
|
||||
install_if="$pkgname=$pkgver-r$pkgrel mce"
|
||||
install -Dm644 "$srcdir"/mce-display-blanking.conf \
|
||||
"$subpkgdir"/etc/mce/50display-blanking.conf
|
||||
}
|
||||
|
||||
sway() {
|
||||
install_if="$pkgname=$pkgver-r$pkgrel postmarketos-ui-sway"
|
||||
depends="postmarketos-ui-sway-logo-key-alt"
|
||||
mkdir "$subpkgdir"
|
||||
}
|
||||
|
||||
kernel_none() {
|
||||
pkgdesc="No kernel (does not boot! can be used during pmbootstrap testing to save time)"
|
||||
devicepkg_subpackage_kernel $startdir $pkgname $subpkgname
|
||||
}
|
||||
|
||||
kernel_virt() {
|
||||
pkgdesc="Alpine Virt kernel (minimal, no audio/mouse/network)"
|
||||
depends="linux-virt"
|
||||
devicepkg_subpackage_kernel $startdir $pkgname $subpkgname
|
||||
}
|
||||
|
||||
kernel_lts() {
|
||||
pkgdesc="Alpine LTS kernel (recommended)"
|
||||
depends="linux-lts linux-firmware-none"
|
||||
devicepkg_subpackage_kernel $startdir $pkgname $subpkgname
|
||||
}
|
||||
|
||||
kernel_edge() {
|
||||
pkgdesc="Alpine Edge kernel"
|
||||
depends="linux-edge linux-firmware-none"
|
||||
devicepkg_subpackage_kernel $startdir $pkgname $subpkgname
|
||||
}
|
||||
|
||||
sha512sums="
|
||||
94f8f9ad44ba6ffe55a07a5bce11351f89a7d97e0c52931c573ea21ff7416cc3ede800da94611e04ebfed6914f8d5edb9614f8c0847b53b09ae168a8607f6195 deviceinfo
|
||||
29766094e64a7ce881c8e96433203ea538057b8fd1d577fc69b9add6bc1217af04ddf60cbcf82333811c627897eda7537b0b1f862899e1fdfd93403b3f6425d7 modules-initfs
|
||||
99d32eed6c5cda59e91516e982c5bd5165ff718133e2411a0dbba04e2057d1dfad49a75e5cc67140d0e0adcbe1383671bd2892335929b782a5b19f5472e635ad mce-display-blanking.conf
|
||||
"
|
27
pmb/data/tests/deviceinfo.qemu-amd64
Normal file
27
pmb/data/tests/deviceinfo.qemu-amd64
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Reference: <https://postmarketos.org/deviceinfo>
|
||||
# Please use double quotes only. You can source this file in shell scripts.
|
||||
|
||||
deviceinfo_format_version="0"
|
||||
deviceinfo_name="QEMU amd64"
|
||||
deviceinfo_manufacturer="QEMU"
|
||||
deviceinfo_codename="qemu-amd64"
|
||||
deviceinfo_arch="x86_64"
|
||||
|
||||
# Device related
|
||||
deviceinfo_gpu_accelerated="true"
|
||||
deviceinfo_chassis="vm"
|
||||
deviceinfo_keyboard="true"
|
||||
deviceinfo_external_storage="true"
|
||||
deviceinfo_screen_width="1024"
|
||||
deviceinfo_screen_height="768"
|
||||
deviceinfo_getty="ttyS0;115200"
|
||||
deviceinfo_dev_internal_storage="/dev/vdb"
|
||||
deviceinfo_dev_internal_storage_repartition="true"
|
||||
|
||||
# Bootloader related
|
||||
deviceinfo_flash_method="none"
|
||||
deviceinfo_kernel_cmdline="console=tty1 console=ttyS0 PMOS_NO_OUTPUT_REDIRECT PMOS_FORCE_PARTITION_RESIZE"
|
||||
deviceinfo_disable_dhcpd="true"
|
||||
deviceinfo_partition_type="gpt"
|
||||
deviceinfo_boot_filesystem="fat32"
|
||||
deviceinfo_generate_systemd_boot="true"
|
20
pmb/data/tests/pmbootstrap.cfg
Normal file
20
pmb/data/tests/pmbootstrap.cfg
Normal file
|
@ -0,0 +1,20 @@
|
|||
[pmbootstrap]
|
||||
build_default_device_arch = True
|
||||
ccache_size = 5G
|
||||
device = qemu-amd64
|
||||
extra_packages = neofetch,neovim,reboot-mode
|
||||
hostname = qemu-amd64
|
||||
is_default_channel = False
|
||||
jobs = 8
|
||||
kernel = edge
|
||||
locale = C.UTF-8
|
||||
ssh_keys = True
|
||||
sudo_timer = True
|
||||
systemd = always
|
||||
timezone = Europe/Berlin
|
||||
ui = gnome
|
||||
work = {0}
|
||||
|
||||
[providers]
|
||||
|
||||
[mirrors]
|
|
@ -254,7 +254,7 @@ class Deviceinfo:
|
|||
# if key not in Deviceinfo.__annotations__.keys():
|
||||
# logging.warning(f"deviceinfo: {key} is not a known attribute")
|
||||
if key == "arch":
|
||||
setattr(self, key, Arch(value))
|
||||
setattr(self, key, Arch.from_str(value))
|
||||
else:
|
||||
setattr(self, key, value)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue