Source code for univention.testing.internal

"""
Internal functions for test finding and setup.
"""
# Copyright 2013-2022 Univention GmbH
#
# https://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <https://www.gnu.org/licenses/>.

from __future__ import print_function

import logging
import operator
import os
import re
import sys
from typing import Any, Callable, Dict, Iterable, List, NoReturn, Optional, Tuple, Union  # noqa: F401

import six

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

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(): # type: () -> None """Setup runtime environment.""" os.environ['TESTLIBPATH'] = '/usr/share/ucs-test/lib' os.environ['PYTHONUNBUFFERED'] = '1'
[docs]def setup_debug(level): # type: (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): # type: (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(l) - len(l.lstrip()) for l in lines if l.lstrip())) return '\n'.join((l[indent:] for l in lines))
[docs]def get_sections(): # type: () -> 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): # type: (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 logger.debug('Adding file %s' % (fname,)) tests.append(fname) if tests: result[section] = tests return result
[docs]class UCSVersion(object): # pylint: disable-msg=R0903 """ 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, default_op='='): # type: (str, 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('Version does not match: "%s"' % (ver,)) rel = match.group(1) or default_op parts = tuple([int(_) if _ else INF for _ in match.groups()[1:]]) # type: Tuple[int, int, int, int] # 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('Unknown version match: "%s"' % (ver,)) def __init__(self, ver): # type: (Union[str, Tuple[int, int, int, int]]) -> None if isinstance(ver, six.string_types): 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): # type: () -> 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 '%s%s' % (rel, ver) def __repr__(self): # type: () -> str return '%s(%r)' % (self.__class__.__name__, self.__str__(),) def __lt__(self, other): # type: (Any) -> object return self.ver < other.ver if isinstance(other, UCSVersion) else NotImplemented def __le__(self, other): # type: (Any) -> object return self.ver <= other.ver if isinstance(other, UCSVersion) else NotImplemented def __eq__(self, other): # type: (Any) -> bool return self.ver == other.ver if isinstance(other, UCSVersion) else False def __ne__(self, other): # type: (Any) -> bool return self.ver != other.ver if isinstance(other, UCSVersion) else False def __ge__(self, other): # type: (Any) -> object return self.ver >= other.ver if isinstance(other, UCSVersion) else NotImplemented def __gt__(self, other): # type: (Any) -> object return self.ver > other.ver if isinstance(other, UCSVersion) else NotImplemented
[docs] def match(self, other): # type: (UCSVersion) -> 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 self_ver != INF and other_ver != INF ] return self.rel(*zip(*parts)) # pylint: disable-msg=W0142
if __name__ == '__main__': import doctest doctest.testmod() # vim: set fileencoding=utf-8 ft=python sw=4 ts=4 :