pmbootstrap-meow/test/test_install.py
Caleb Connolly 198f302a36
treewide: add a Chroot type and adopt pathlib.Path (MR 2252)
Introduce a new module: pmb.core to contain explicitly typed pmbootstrap
API. The first component being Suffix and SuffixType. This explicitly
defines what suffixes are possible, future changes should aim to further
constrain this API (e.g. by validating against available device
codenames or architectures for buildroot suffixes).

Additionally, migrate the entire codebase over to using pathlib.Path.
This is a relatively new part of the Python standard library that uses a
more object oriented model for path handling. It also uses strong type
hinting and has other features that make it much cleaner and easier to
work with than pure f-strings. The Chroot class overloads the "/"
operator the same way the Path object does, allowing one to write paths
relative to a given chroot as:

builddir = chroot / "home/pmos/build"

The Chroot class also has a string representation ("native", or
"rootfs_valve-jupiter"), and a .path property for directly accessing the
absolute path (as a Path object).

The general idea here is to encapsulate common patterns into type hinted
code, and gradually reduce the amount of assumptions made around the
codebase so that future changes are easier to implement.

As the chroot suffixes are now part of the Chroot class, we also
implement validation for them, this encodes the rules on suffix naming
and will cause a runtime exception if a suffix doesn't follow the rules.
2024-06-23 12:38:37 +02:00

173 lines
6.3 KiB
Python

# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import pytest
import sys
import os
import shutil
import pmb_test
import pmb_test.const
import pmb.aportgen.device
import pmb.config
import pmb.config.init
import pmb.helpers.logging
import pmb.install._install
from pmb.core import Suffix, SuffixType
@pytest.fixture
def args(tmpdir, request):
import pmb.parse
sys.argv = ["pmbootstrap.py", "init"]
args = pmb.parse.arguments()
args.log = args.work + "/log_testsuite.txt"
pmb.helpers.logging.init(args)
request.addfinalizer(pmb.helpers.logging.logfd.close)
return args
def test_get_nonfree_packages(args):
args.aports = pmb_test.const.testdata + "/init_questions_device/aports"
func = pmb.install._install.get_nonfree_packages
# Device without any non-free subpackages
assert func(args, "lg-mako") == []
# Device with non-free firmware and userland
device = "nonfree-firmware-and-userland"
assert func(args, device) == ["device-" + device + "-nonfree-firmware",
"device-" + device + "-nonfree-userland"]
# Device with non-free userland
device = "nonfree-userland"
assert func(args, device) == ["device-" + device + "-nonfree-userland"]
def test_get_recommends(args):
args.aports = pmb_test.const.testdata + "/pmb_recommends"
func = pmb.install._install.get_recommends
# UI: none
args.install_recommends = True
assert func(args, ["postmarketos-ui-none"]) == []
# UI: test, --no-recommends
args.install_recommends = False
assert func(args, ["postmarketos-ui-test"]) == []
# UI: test
args.install_recommends = True
assert func(args, ["postmarketos-ui-test"]) == ["plasma-camera",
"plasma-angelfish"]
# UI: test + test-extras
args.install_recommends = True
assert func(args, ["postmarketos-ui-test",
"postmarketos-ui-test-extras"]) == ["plasma-camera",
"plasma-angelfish",
"buho", "kaidan",
"test-app", "foot",
"htop"]
# Non-UI package
args.install_recommends = True
args.ui_extras = False
assert func(args, ["test-app"]) == ["foot", "htop"]
def test_get_groups(args):
args.aports = f"{pmb_test.const.testdata}/pmb_groups"
func = pmb.install.ui.get_groups
# UI: none:
args.ui = "none"
assert func(args) == []
# UI: test, without -extras
args.ui = "test"
args.ui_extras = False
assert func(args) == ["feedbackd"]
# UI: test, with -extras
args.ui = "test"
args.ui_extras = True
assert func(args) == ["feedbackd", "extra"]
# UI: invalid
args.ui = "invalid"
with pytest.raises(RuntimeError) as e:
func(args)
assert str(e.value).startswith("Could not find aport for package")
def test_generate_binary_list(args):
suffix = Suffix(SuffixType.ROOTFS, "mysuffix")
args.work = "/tmp"
func = pmb.install._install.generate_binary_list
binary_dir = os.path.join(args.work, f"{suffix.chroot()}", "usr/share")
os.makedirs(binary_dir, exist_ok=True)
step = 1024
binaries = [f"{pmb_test.const.testdata}/pmb_install/small.bin",
f"{pmb_test.const.testdata}/pmb_install/full.bin",
f"{pmb_test.const.testdata}/pmb_install/big.bin",
f"{pmb_test.const.testdata}/pmb_install/overrun.bin",
f"{pmb_test.const.testdata}/pmb_install/binary2.bin"]
for b in binaries:
shutil.copy(b, binary_dir)
# Binary that is small enough to fit the partition of 10 blocks
# of 512 bytes each
binaries = "small.bin:1,binary2.bin:11"
args.deviceinfo = {"sd_embed_firmware": binaries,
"boot_part_start": "128"}
assert func(args, suffix, step) == [('small.bin', 1), ('binary2.bin', 11)]
# Binary that is fully filling the partition of 10 blocks of 512 bytes each
binaries = "full.bin:1,binary2.bin:11"
args.deviceinfo = {"sd_embed_firmware": binaries,
"boot_part_start": "128"}
assert func(args, suffix, step) == [('full.bin', 1), ('binary2.bin', 11)]
# Binary that is too big to fit the partition of 10 blocks
# of 512 bytes each
binaries = "big.bin:1,binary2.bin:2"
args.deviceinfo = {"sd_embed_firmware": binaries,
"boot_part_start": "128"}
with pytest.raises(RuntimeError) as e:
func(args, suffix, step)
assert str(e.value).startswith("The firmware overlaps with at least one")
# Binary that overruns the first partition
binaries = "overrun.bin:1"
args.deviceinfo = {"sd_embed_firmware": binaries,
"boot_part_start": "1"}
with pytest.raises(RuntimeError) as e:
func(args, suffix, step)
assert str(e.value).startswith("The firmware is too big to embed in")
# Binary does not exist
binaries = "does-not-exist.bin:1,binary2.bin:11"
args.deviceinfo = {"sd_embed_firmware": binaries,
"boot_part_start": "128"}
with pytest.raises(RuntimeError) as e:
func(args, suffix, step)
assert str(e.value).startswith("The following firmware binary does not")
# Binaries are touching but not overlapping
# boot_part_start is at 2 sectors (1024 b)
# |-----|---------------------|-------------------|-------------------
# | … | binary2.bin (100 b) | small.bin (600 b) | /boot part start …
# |-----|---------------------|-------------------|-------------------
# 0 324 424 1024
step = 1
binaries = "binary2.bin:324,small.bin:424"
args.deviceinfo = {"sd_embed_firmware": binaries,
"boot_part_start": "2"}
assert func(args, suffix, step) == [('binary2.bin', 324),
('small.bin', 424)]
# Same layout written with different order in sd_embed_firmware
binaries = "small.bin:424,binary2.bin:324"
args.deviceinfo = {"sd_embed_firmware": binaries,
"boot_part_start": "2"}
assert func(args, suffix, step) == [('small.bin', 424),
('binary2.bin', 324)]