Source code for univention.testing.internal

# SPDX-FileCopyrightText: 2013-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only

"""Internal functions for test finding and setup."""


import logging
import operator
import os
import re
import sys
from collections.abc import Callable, Iterable
from typing import Any, Self


__all__ = [
    'LOG_BASE',
    'TEST_BASE',
    'UCSVersion',
    'get_sections',
    'get_tests',
    'setup_debug',
    'setup_environment',
    'strip_indent',
]

TEST_BASE = os.environ.get('UCS_TESTS', '/usr/share/ucs-test')
RE_SECTION = re.compile(r'^[0-9]{2}_(.+)$')
RE_PREFIX = re.compile(r'^[0-9]{2,3}_?(.+)')
RE_SUFFIX = re.compile(r'(?:~|\.(?:lib|sh|py[co]|bak|mo|po|png|jpg|jpeg|xml|csv|inst|uinst))$')
LOG_BASE = '/var/log/univention/test_%d.log'
S4CONNECTOR_INIT_SCRIPT = '/etc/init.d/univention-s4-connector'
INF = sys.maxsize


[docs] def setup_environment() -> None: """Setup runtime environment.""" os.environ['TESTLIBPATH'] = '/usr/share/ucs-test/lib' os.environ['PYTHONUNBUFFERED'] = '1'
[docs] def setup_debug(level: int) -> None: """Setup Python logging.""" level = _TAB.get(level, logging.DEBUG) FORMAT = '%(asctime)-15s ' + logging.BASIC_FORMAT logging.basicConfig(stream=sys.stderr, level=level, format=FORMAT)
_TAB = { # pylint: disable-msg=W0612 None: logging.WARNING, 0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG, }
[docs] def strip_indent(text: str) -> str: """Strip common indent.""" lines = text.splitlines() while lines and not lines[0].strip(): del lines[0] while lines and not lines[-1].strip(): del lines[-1] indent = min(len(line) - len(line.lstrip()) for line in lines if line.lstrip()) return '\n'.join(line[indent:] for line in lines)
[docs] def get_sections() -> dict[str, str]: """Return dictionary section-name -> section-directory.""" section_dirs = os.listdir(TEST_BASE) sections = {dirname[3:]: TEST_BASE + os.path.sep + dirname for dirname in section_dirs if RE_SECTION.match(dirname)} return sections
[docs] def get_tests(sections: Iterable[str]) -> dict[str, list[str]]: """Return dictionary of section -> [filenames].""" result = {} logger = logging.getLogger('test.find') all_sections = get_sections() for section in sections: dirname = all_sections[section] logger.debug("Processing directory %s", dirname) tests = [] files = os.listdir(dirname) for filename in sorted(files): fname = os.path.join(dirname, filename) if not RE_PREFIX.match(filename): logger.debug("Skipped file %s", fname) continue if RE_SUFFIX.search(filename): logger.debug("Skipped file %s", fname) continue if not os.path.exists(fname): logger.debug("Skipped file %s", fname) continue logger.debug("Adding file %s", fname) tests.append(fname) if tests: result[section] = tests return result
[docs] class UCSVersion: # pylint: disable-msg=R0903 # noqa: PLW1641 """ UCS version. >>> UCSVersion("1.0") < UCSVersion("2.0") True >>> UCSVersion("1.0") < UCSVersion("1.0") False >>> UCSVersion("1.0") <= UCSVersion("1.0") True >>> UCSVersion("2.0") <= UCSVersion("1.0") False >>> UCSVersion("1.0") == UCSVersion("1.0") True >>> UCSVersion("1.0") == UCSVersion("2.0") False >>> UCSVersion("1.0") != UCSVersion("2.0") True >>> UCSVersion("1.0") != UCSVersion("1.0") False >>> UCSVersion("1.0") >= UCSVersion("1.0") True >>> UCSVersion("1.0") >= UCSVersion("2.0") False >>> UCSVersion("2.0") > UCSVersion("1.0") True >>> UCSVersion("1.0") > UCSVersion("1.0") False >>> UCSVersion("1.0") == UCSVersion((1, 0, INF, INF)) True >>> UCSVersion("1.0-0-0") == UCSVersion((1, 0, 0, 0)) True >>> UCSVersion("") Traceback (most recent call last): ... ValueError: Version does not match: "" >>> UCSVersion("0") Traceback (most recent call last): ... ValueError: Version does not match: "0" >>> UCSVersion("1") Traceback (most recent call last): ... ValueError: Version does not match: "1" >>> UCSVersion("1.2") UCSVersion('=1.2') >>> UCSVersion("1.2-3") UCSVersion('=1.2-3') >>> UCSVersion("1.2-3-4") UCSVersion('=1.2-3-4') >>> UCSVersion("1.2-3-4-5") Traceback (most recent call last): ... ValueError: Version does not match: "1.2-3-4-5" >>> UCSVersion(None) Traceback (most recent call last): ... TypeError: None >>> UCSVersion(1) Traceback (most recent call last): ... TypeError: 1 >>> UCSVersion(1.5) Traceback (most recent call last): ... TypeError: 1.5 """ RE_VERSION = re.compile(r"^(<|<<|<=|=|==|>=|>|>>)?([1-9][0-9]*)\.([0-9]+)(?:-([0-9]*)(?:-([0-9]+))?)?$") @classmethod def _parse(cls, ver: str, default_op: str = '=') -> tuple[Callable[[Any, Any], Any], tuple[int, int, int, int]]: """ Parse UCS-version range and return two-tuple (operator, version) >>> UCSVersion._parse('11.22') # doctest: +ELLIPSIS (<built-in function eq>, (11, 22, ..., ...)) >>> UCSVersion._parse('11.22-33') # doctest: +ELLIPSIS (<built-in function eq>, (11, 22, 33, ...)) >>> UCSVersion._parse('11.22-33-44') (<built-in function eq>, (11, 22, 33, 44)) >>> UCSVersion._parse('<1.2-3') # doctest: +ELLIPSIS (<built-in function lt>, (1, 2, 3, ...)) >>> UCSVersion._parse('<<1.2-3') # doctest: +ELLIPSIS (<built-in function lt>, (1, 2, 3, ...)) >>> UCSVersion._parse('<=1.2-3') # doctest: +ELLIPSIS (<built-in function le>, (1, 2, 3, ...)) >>> UCSVersion._parse('=1.2-3') # doctest: +ELLIPSIS (<built-in function eq>, (1, 2, 3, ...)) >>> UCSVersion._parse('==1.2-3') # doctest: +ELLIPSIS (<built-in function eq>, (1, 2, 3, ...)) >>> UCSVersion._parse('>=1.2-3') # doctest: +ELLIPSIS (<built-in function ge>, (1, 2, 3, ...)) >>> UCSVersion._parse('>>1.2-3') # doctest: +ELLIPSIS (<built-in function gt>, (1, 2, 3, ...)) >>> UCSVersion._parse('>1.2-3') # doctest: +ELLIPSIS (<built-in function gt>, (1, 2, 3, ...)) """ match = cls.RE_VERSION.match(ver) if not match: raise ValueError(f'Version does not match: "{ver}"') rel = match.group(1) or default_op parts: tuple[int, int, int, int] = tuple(int(_) if _ else INF for _ in match.groups()[1:]) # type: ignore if rel in ('<', '<<'): return (operator.lt, parts) if rel in ('<=',): return (operator.le, parts) if rel in ('=', '=='): return (operator.eq, parts) if rel in ('>=',): return (operator.ge, parts) if rel in ('>', '>>'): return (operator.gt, parts) raise ValueError(f'Unknown version match: "{ver}"') def __init__(self, ver: str | tuple[int, int, int, int]) -> None: if isinstance(ver, str): self.rel, self.ver = self._parse(ver) elif isinstance(ver, tuple): self.rel = operator.eq assert all(isinstance(_, int) for _ in ver) self.ver = ver else: raise TypeError(ver) def __str__(self) -> str: rel = { operator.lt: '<', operator.le: '<=', operator.eq: '=', operator.ge: '>=', operator.gt: '>', }[self.rel] ver = '%d.%d' % self.ver[0:2] # type: ignore skipped = 0 for part in self.ver[2:]: skipped += 1 if part != INF: ver += '%s%d' % ('-' * skipped, part) skipped = 0 return f'{rel}{ver}' def __repr__(self) -> str: return f'{self.__class__.__name__}({self.__str__()!r})' def __lt__(self, other: object) -> object: return self.ver < other.ver if isinstance(other, UCSVersion) else NotImplemented def __le__(self, other: object) -> object: return self.ver <= other.ver if isinstance(other, UCSVersion) else NotImplemented def __eq__(self, other: object) -> bool: return self.ver == other.ver if isinstance(other, UCSVersion) else False def __ne__(self, other: object) -> bool: return self.ver != other.ver if isinstance(other, UCSVersion) else False def __ge__(self, other: object) -> object: return self.ver >= other.ver if isinstance(other, UCSVersion) else NotImplemented def __gt__(self, other: object) -> object: return self.ver > other.ver if isinstance(other, UCSVersion) else NotImplemented
[docs] def match(self, other: Self) -> bool: """ Check if other matches the criterion. >>> UCSVersion('>1.2-3').match(UCSVersion('1.2-4')) True >>> UCSVersion('>1.2-3').match(UCSVersion('1.2-3-4')) False >>> UCSVersion('>1.2-3-5').match(UCSVersion('1.2-3-4')) False >>> UCSVersion('>=1.2-3').match(UCSVersion('1.2-3-4')) True """ parts = [ (other_ver, self_ver) for self_ver, other_ver in zip(self.ver, other.ver) if INF not in (self_ver, other_ver) ] return self.rel(*zip(*parts)) # pylint: disable-msg=W0142
if __name__ == '__main__': import doctest doctest.testmod()