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')