lib.Distro: Fix os-release and cascade macros #104
4 changed files with 170 additions and 2 deletions
|
|
@ -49,7 +49,7 @@ class Distro(abc.ABC):
|
||||||
'name',
|
'name',
|
||||||
'codename',
|
'codename',
|
||||||
'gnu-triplet',
|
'gnu-triplet',
|
||||||
'os-cascade',
|
'cascade',
|
||||||
'os-release',
|
'os-release',
|
||||||
'pkg-ext',
|
'pkg-ext',
|
||||||
]
|
]
|
||||||
|
|
@ -210,6 +210,12 @@ class Distro(abc.ABC):
|
||||||
)
|
)
|
||||||
return self.__os_release_str
|
return self.__os_release_str
|
||||||
|
|
||||||
|
# -- expand_macros() looks up macro names via getattr with '-' turned
|
||||||
|
# into '_', so 'os-release' needs this alias
|
||||||
|
@property
|
||||||
|
def os_release(self) -> str:
|
||||||
|
return self.os_release_str
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return self.os_release_field('NAME')
|
return self.os_release_field('NAME')
|
||||||
|
|
@ -284,6 +290,8 @@ class Distro(abc.ABC):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def macros(cls) -> list[str]:
|
def macros(cls) -> list[str]:
|
||||||
|
# -- The doubled percent is not a typo: the macros are embedded
|
||||||
|
# in an argparse help string, where %% escapes to a literal %
|
||||||
return ['%%{' + name + '}' for name in cls.macro_names]
|
return ['%%{' + name + '}' for name in cls.macro_names]
|
||||||
|
|
||||||
def expand_macros(self, fmt: str | Iterable[str]) -> str | list[str]:
|
def expand_macros(self, fmt: str | Iterable[str]) -> str | list[str]:
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ Retrieve information about target platform
|
||||||
options:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--format FORMAT Format string, expanding macros %{os}, %{id}, %{name},
|
--format FORMAT Format string, expanding macros %{os}, %{id}, %{name},
|
||||||
%{codename}, %{gnu-triplet}, %{os-cascade}, %{os-release},
|
%{codename}, %{gnu-triplet}, %{cascade}, %{os-release},
|
||||||
%{pkg-ext} (default: %{cascade})
|
%{pkg-ext} (default: %{cascade})
|
||||||
============= Running: jw-pkg.py -t ../../../.. --log-level info posix --help
|
============= Running: jw-pkg.py -t ../../../.. --log-level info posix --help
|
||||||
usage: jw-pkg.py posix [-h] ...
|
usage: jw-pkg.py posix [-h] ...
|
||||||
|
|
|
||||||
7
test/unit/python/jw/pkg/lib/Distro/Makefile
Normal file
7
test/unit/python/jw/pkg/lib/Distro/Makefile
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
TOPDIR = ../../../../../../..
|
||||||
|
|
||||||
|
include $(TOPDIR)/make/proj.mk
|
||||||
|
include $(JWBDIR)/make/py-run.mk
|
||||||
|
|
||||||
|
all:
|
||||||
|
test: run
|
||||||
153
test/unit/python/jw/pkg/lib/Distro/test.py
Normal file
153
test/unit/python/jw/pkg/lib/Distro/test.py
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Iterable, cast, override
|
||||||
|
|
||||||
|
from jw.pkg.lib.Distro import Distro
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from jw.pkg.lib.ExecContext import ExecContext
|
||||||
|
from jw.pkg.lib.Package import Package
|
||||||
|
|
||||||
|
OS_RELEASE_UBUNTU = """\
|
||||||
|
NAME="Ubuntu"
|
||||||
|
VERSION="24.04.2 LTS (Noble Numbat)"
|
||||||
|
ID=ubuntu
|
||||||
|
ID_LIKE=debian
|
||||||
|
PRETTY_NAME="Ubuntu 24.04.2 LTS"
|
||||||
|
VERSION_ID="24.04"
|
||||||
|
VERSION_CODENAME=noble
|
||||||
|
HOME_URL="https://www.ubuntu.com/"
|
||||||
|
"""
|
||||||
|
|
||||||
|
OS_RELEASE_SUSE = """\
|
||||||
|
NAME="openSUSE Tumbleweed"
|
||||||
|
# VERSION="20260901"
|
||||||
|
ID=opensuse-tumbleweed
|
||||||
|
ID_LIKE="opensuse suse"
|
||||||
|
PRETTY_NAME="openSUSE Tumbleweed"
|
||||||
|
VERSION_ID="20260901"
|
||||||
|
HOME_URL="https://www.opensuse.org/"
|
||||||
|
"""
|
||||||
|
|
||||||
|
class DummyDistro(Distro):
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def _ref(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def _dup(self, download_only: bool) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def _reboot_required(self, verbose: bool) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def _select_by_name(self, names: Iterable[str]) -> Iterable[Package]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def _install(self, names: Iterable[str], only_update: bool) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def _delete(self, names: Iterable[str]) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def _pkg_files(self, name: str) -> Iterable[str]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def distro(os_release_str: str) -> Distro:
|
||||||
|
# -- The execution context is stored but not used by the properties
|
||||||
|
# under test, so a dummy object is sufficient
|
||||||
|
return DummyDistro(
|
||||||
|
ec = cast('ExecContext', object()),
|
||||||
|
id = Distro.parse_os_release_field_id(os_release_str),
|
||||||
|
os_release_str = os_release_str
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- parse_os_release_field --
|
||||||
|
|
||||||
|
assert Distro.parse_os_release_field('ID', OS_RELEASE_UBUNTU) == 'ubuntu'
|
||||||
|
assert Distro.parse_os_release_field('VERSION_CODENAME', OS_RELEASE_UBUNTU) == \
|
||||||
|
'noble'
|
||||||
|
# Quoted values are de-quoted
|
||||||
|
assert Distro.parse_os_release_field('NAME', OS_RELEASE_UBUNTU) == 'Ubuntu'
|
||||||
|
# ID must not match ID_LIKE
|
||||||
|
assert Distro.parse_os_release_field('ID_LIKE', OS_RELEASE_UBUNTU) == 'debian'
|
||||||
|
|
||||||
|
try:
|
||||||
|
Distro.parse_os_release_field('NO_SUCH_KEY', OS_RELEASE_UBUNTU)
|
||||||
|
assert False, 'Should have raised'
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# -- parse_os_release_field_id --
|
||||||
|
|
||||||
|
assert Distro.parse_os_release_field_id(OS_RELEASE_UBUNTU) == 'ubuntu'
|
||||||
|
assert Distro.parse_os_release_field_id(OS_RELEASE_SUSE) == 'suse'
|
||||||
|
|
||||||
|
# -- Properties --
|
||||||
|
|
||||||
|
d = distro(OS_RELEASE_UBUNTU)
|
||||||
|
assert d.id == 'ubuntu'
|
||||||
|
assert d.name == 'Ubuntu'
|
||||||
|
assert d.codename == 'noble'
|
||||||
|
assert d.os == 'ubuntu-noble'
|
||||||
|
assert d.pkg_ext == 'debian'
|
||||||
|
assert d.os_cascade == ['os', 'linux', 'pkg-debian', 'pm-apt', 'ubuntu', 'ubuntu-noble']
|
||||||
|
assert d.cascade == 'os linux pkg-debian pm-apt ubuntu ubuntu-noble'
|
||||||
|
|
||||||
|
s = distro(OS_RELEASE_SUSE)
|
||||||
|
assert s.id == 'suse'
|
||||||
|
assert s.codename == 'tumbleweed'
|
||||||
|
assert s.os == 'suse-tumbleweed'
|
||||||
|
assert s.pkg_ext == 'rpm'
|
||||||
|
assert s.os_cascade == [
|
||||||
|
'os', 'linux', 'pkg-rpm', 'pm-zypper', 'suse', 'suse-tumbleweed'
|
||||||
|
]
|
||||||
|
|
||||||
|
# A codename with a version number cascades down to its prefixes
|
||||||
|
v = distro('ID=fake\nVERSION_CODENAME=1.2.3\n')
|
||||||
|
assert v.os_cascade == ['os', 'fake-1.2', 'fake-1', 'fake', 'fake-1.2.3']
|
||||||
|
|
||||||
|
# -- expand_macros --
|
||||||
|
|
||||||
|
assert d.expand_macros('%{id}') == 'ubuntu'
|
||||||
|
assert d.expand_macros('%{name}') == 'Ubuntu'
|
||||||
|
assert d.expand_macros('%{os}') == 'ubuntu-noble'
|
||||||
|
assert d.expand_macros('%{codename}') == 'noble'
|
||||||
|
assert d.expand_macros('%{pkg-ext}') == 'debian'
|
||||||
|
assert d.expand_macros('%{cascade}') == d.cascade
|
||||||
|
# %{os-release} expands to the whole /etc/os-release content
|
||||||
|
assert d.expand_macros('%{os-release}') == d.os_release_str
|
||||||
|
assert d.expand_macros('%{os-release}') == OS_RELEASE_UBUNTU
|
||||||
|
assert d.os_release == d.os_release_str
|
||||||
|
|
||||||
|
# A mix of macros and literal text
|
||||||
|
assert d.expand_macros('running on %{id} (%{codename}) now') == \
|
||||||
|
'running on ubuntu (noble) now'
|
||||||
|
|
||||||
|
# List input expands entry by entry
|
||||||
|
assert d.expand_macros(['%{id}', '%{name}']) == ['ubuntu', 'Ubuntu']
|
||||||
|
|
||||||
|
# An unknown macro raises
|
||||||
|
try:
|
||||||
|
d.expand_macros('%{no-such-macro}')
|
||||||
|
assert False, 'Should have raised'
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# -- macros --
|
||||||
|
|
||||||
|
# -- The doubled percent escapes to a single one when the macros are
|
||||||
|
# embedded in an argparse help string
|
||||||
|
assert Distro.macros() == ['%%{' + name + '}' for name in Distro.macro_names]
|
||||||
|
for macro in Distro.macros():
|
||||||
|
# -- Every advertised macro must expand without error once the
|
||||||
|
# argparse help escaping has been undone
|
||||||
|
d.expand_macros(macro.replace('%%', '%'))
|
||||||
|
|
||||||
|
print('All Distro tests passed')
|
||||||
Loading…
Reference in a new issue