From: Plamen Dimitrov Date: Sat, 9 Apr 2022 05:06:21 +0000 (+0300) Subject: Drop previous simple cnf module and class throughout the codebase X-Git-Tag: v1.7.1~3^2~5 X-Git-Url: http://developer.intra2net.com/git/?p=pyi2ncommon;a=commitdiff_plain;h=e5c2b9ebf1689ffb6eb6435b1720c81c74c70039 Drop previous simple cnf module and class throughout the codebase Within pyi2ncommon this is only a matter of isolated narrowly used code within the dial module. --- diff --git a/src/dial.py b/src/dial.py index a87117e..1b53fa9 100644 --- a/src/dial.py +++ b/src/dial.py @@ -66,7 +66,7 @@ import logging log = logging.getLogger('pyi2ncommon.dial') from . import arnied_wrapper -from . import simple_cnf +from . import cnfvar from . import sysmisc HAVE_IPADDRESS = True @@ -171,12 +171,11 @@ def arnied_dial_permanent(prid="P1", block=False): """ log.debug("requested connd_dial_online%s" % " (blocking)" if block else "") - cnf = simple_cnf.SimpleCnf() - cnf.add("DIALOUT_MODE", DIALOUT_MODE_CNF[DIALOUT_MODE_PERMANENT]) - cnf.add("DIALOUT_DEFAULTPROVIDER_REF", "1") + cnf = cnfvar.CnfList([("dialout_mode", DIALOUT_MODE_CNF[DIALOUT_MODE_PERMANENT]), + ("dialout_defaultprovider_ref", "1")]) def aux(): - return arnied_wrapper.set_cnf_pipe(cnf, block=block), "", None + return arnied_wrapper.set_cnf_pipe(cnf.to_cnf_structure(), block=block), "", None if block is False: succ = aux() diff --git a/src/simple_cnf.py b/src/simple_cnf.py deleted file mode 100644 index 572866e..0000000 --- a/src/simple_cnf.py +++ /dev/null @@ -1,660 +0,0 @@ -# The software in this package is distributed under the GNU General -# Public License version 2 (with a special exception described below). -# -# A copy of GNU General Public License (GPL) is included in this distribution, -# in the file COPYING.GPL. -# -# As a special exception, if other files instantiate templates or use macros -# or inline functions from this file, or you compile this file and link it -# with other works to produce a work based on this file, this file -# does not by itself cause the resulting work to be covered -# by the GNU General Public License. -# -# However the source code for this file must still be made available -# in accordance with section (3) of the GNU General Public License. -# -# This exception does not invalidate any other reasons why a work based -# on this file might be covered by the GNU General Public License. -# -# Copyright (c) 2016-2018 Intra2net AG - -""" - -SUMMARY ------------------------------------------------------- -Read / write / merge guest cnf var sets, even on host. - -.. note:: DEPRECATED! Please do not extend this or add new uses of this module, - use :py:mod:`pyi2ncommon.arnied_api` or :py:mod:`pyi2ncommon.cnfvar` - instead! - -Copyright: Intra2net AG - - -CONTENTS ------------------------------------------------------- - -This module can be viewed as a convenience-wrapper around module -:py:mod:`cnfvar`. It uses many of its function but provides some higher-level -interfaces, most of all class :py:class:`SimpleCnf`. It is completely -independent of the :py:mod:`cnfline` package and its included subclasses -(modules in `shared.cnfline`, starting with ``build_`` and ``configure_``). - -Class :py:class:`SimpleCnf` represents a hierarchical set of conf vars and -provides functions to deal with that hierarchy. Under the hood, all functions -here (and probably also in :py:mod:`cnfvar`) work with conf vars represented as -dictionaries and lists thereof. Conf var dicts have keys `number`, `varname`, -`instance`, `data`, `comment` and possibly `parent` and/or `children`. -`varname` is a regular lower-case string, `data` is a (utf8) string, `comment` -is usually None, `number`, `parent` and `instance` are int. If a conf var has -children, then this is a list of conf var dicts. `parent` is only present if a -conf-var is a child. Several conf vars, if not wrapped in a -:py:class:`SimpleCnf`, appear either as simple list of conf var dicts or as a -dict with a single key `cnf` whose value is a list of conf var dicts. (Function -:py:func:`get_cnf` returns the former given the latter). - -.. todo:: Exceptions Invalid[Json]Cnf are used inconsistently (e.g. check type - of function arguments `-->` should be ValueError) and difference - between them is unclear. Also name differs only in case from - :py:class:`cnfvar.InvalidCNF` - - -INTERFACE ------------------------------------------------------- -""" - -import os -import json -import tempfile -import time -import logging -log = logging.getLogger('pyi2ncommon.simple_cnf') - -from . import arnied_wrapper -from . import cnfvar_old -from . import sysmisc - -############################################################################### -# constants -############################################################################### - -#: timeout for copying temporary config files to VM objects (seconds) -COPY_FILES_TIMEOUT = 15 - -#: additional location of configuration files -ADD_CNFFILE_PATH = "/tmp/configs" - - -############################################################################### -# EXCEPTIONS -############################################################################### - - -class InvalidCnf(Exception): - """Exception that indicates a general problem with conf var processing.""" - - def __init__(self, m): - """Create an invalid config exception.""" - msg = "Invalid CNF_VAR: %s" % m - super(InvalidCnf, self).__init__(msg) - self.msg = msg - self.pfx = "[CNF]" - - def __str__(self): - """Get a string version of the exception message.""" - return "%s %s" % (self.pfx, self.msg) - - -class InvalidJsonCnf(InvalidCnf): - """Exception that indicates a general problem with conf var processing.""" - - def __init__(self, m): - """Create an invalid JSON config exception.""" - super(InvalidJsonCnf, self).__init__(m) - self.pfx = "[CNF:JSON]" - - -############################################################################### -# auxiliary functions -############################################################################### - - -def get_cnf(cnf): - """ - "Convert" a config dict to a list of conf var dicts. - - This just removes the top-level 'cnf' key and returns its value. - - :param cnf: config dictionary - :type cnf: {str, [dict]} - :returns: list of cnf var dicts - :rtype: [{str, int or str or None}] - :raises: :py:class:`InvalidJsonCnf` if there is no `cnf` field found - """ - cnf_vars = cnf.get("cnf") - if cnf_vars is None: - raise InvalidJsonCnf("toplevel \"cnf\" field required") - return cnf_vars - - -def gen_tmpname(): - """ - Get a (quite) safe temporary file name for config file. - - :returns: temporary file name - :rtype: str - """ - now = time.time() - file_handle, file_name = tempfile.mkstemp(prefix="simple_%d_" % int(now), - suffix=".cnf") - os.close(file_handle) - os.unlink(file_name) - return file_name - - -def set_values(cnf_vars, replacements): - """ - Recursively replace values in configuration - - Works in-place, meaning that no new configuration is created and returned - but instead argument `cnf_vars` is modified (and nothing returned). - - :param cnf_vars: config where replacements are to be made - :type cnf_vars: [{str, int or str or None}] or {str, [dict]} - :param replacements: what to replace and what to replace it with - :type replacements: {str, str} or [(str, str)] - :raises: :py:class:`InvalidJsonCnf` if cnf_vars is neither dict or list - """ - # determine set replace_me of keys to replace and function get that returns - # value for key or empty string if key not in replacements - replace_me = None - get = None - if isinstance(replacements, dict): - replace_me = set(k.lower() for k in replacements.keys()) - get = lambda var: str(replacements.get(var, "")) # pylint: disable=function-redefined - elif isinstance(replacements, list): - replace_me = set(r[0].lower() for r in replacements) - - def get(var): # pylint: disable=function-redefined - """Get replacement value for given variable name.""" - try: - return str(next(r[1] for r in replacements if r[0] == var)) - except StopIteration: - return "" - else: - raise TypeError("replacements must be dictionary or key-value list") - - # check type of arg "cnf_vars" - if isinstance(cnf_vars, dict): - cnf_vars = cnf_vars["cnf"] # operate on the var list - if not isinstance(cnf_vars, list): - raise InvalidJsonCnf("ill-formed CNF_VAR: expected list, got %s (%s)" - % (type(cnf_vars), cnf_vars)) - - def aux(varlist): - """Internal recursive function to replace values.""" - for var in varlist: - varname = var["varname"].lower() - if varname in replace_me: - var["data"] = str(get(varname)) - children = var.get("children", None) - if children is not None: - aux(children) - - # apply function on complete cnf_vars - aux(cnf_vars) - - -def lookup_cnf_file(fname): - """ - Searches for config file with given name in default locations. - - :param str fname: file name of config file (without path) - :returns: first existing config file found in default locations - :rtype: str - :raises: :py:class:`IOError` if no such config file was found - """ - locations = [arnied_wrapper.SRC_CONFIG_DIR, ADD_CNFFILE_PATH] - for path in locations: - fullpath = os.path.join(path, fname) - if os.path.isfile(fullpath): - return fullpath - raise IOError("config file %s does not exist in any of the readable " - "locations %s" % (fname, locations)) - - -############################################################################### -# primary class -############################################################################### - - -class SimpleCnf(object): - """ - Representation of hierarchical configuration of variables. - - Based on C++ `cnf_vars` as visualized by *get_cnf*. - - Internal data representation: list of conf var dicts; see module doc for - details - """ - - def __init__(self, cnf=None): - """ - Creates a simple configuration. - - Does not check whether given cnf list contains only valid data. - Does not recurse into dicts. - - :param cnf: initial set of conf var data (default: None = empty conf) - :type cnf: list or anything that :py:func:`get_cnf` can read - """ - if cnf is None: - self.__cnfvars = [] - elif isinstance(cnf, list): - self.__cnfvars = cnf - elif isinstance(cnf, dict): - self.__cnfvars = get_cnf(cnf) - else: - raise InvalidCnf ("cannot handle %s type inputs" % type (cnf)) - - def _find_new_number(self, cnf_vars): - """Recursive helper function to find new unique (line) number.""" - if not cnf_vars: - return 1 - new_numbers = [1, ] # in case cnf_vars is empty - for cnf_var in cnf_vars: - new_numbers.append(cnf_var['number'] + 1) - try: - new_numbers.append(self._find_new_number(cnf_var['children'])) - except KeyError: - pass - return max(new_numbers) # this is max(all numbers) + 1 - - def _find_new_instance(self, varname): - """ - Find an instance number for variable with non-unique varname. - - Will only check on top level, is not recursive. - - :param str varname: name of conf var; will be converted to lower-case - :returns: instance number for which there is no other conf var of same - name (0 if there is not other conf var with that name) - :rtype: int - """ - result = 0 - varname = varname.lower() - for entry in self.__cnfvars: - if entry['varname'] == varname: - result = max(result, entry['number']+1) - return result - - def add(self, varname, data='', number=None, instance=None, children=None): - """ - Add a cnf var to config on top level. - - :param str varname: name of conf var; only required arg; case ignored - :param str data: conf var's value - :param int number: line number of that conf var; if given as None - (default) the function looks through config to find - a new number that is not taken; must be positive! - Value will be ignored if children are given. - :param int instance: Instance of the new conf var or None (default). - If None, then function looks through config to - find a new unique instance number - :param children: child confs for given conf var. Children's parent - and line attributes will be set in this function - :type children: :py:class:`SimpleCnf` - """ - if instance is None: - instance = self._find_new_instance(varname) - if children: - number = self._find_new_number(self.__cnfvars) # need top number - new_children = [] - for child in children: - new_dict = child.get_single_dict() - new_dict['parent'] = number - new_children.append(new_dict) - cnfvar_old.renumber_vars({'cnf':new_children}, number) - children = new_children - elif number is None: - number = self._find_new_number(self.__cnfvars) - - new_var = dict(varname=varname.lower(), data=data, - number=number, comment=None, instance=instance) - if children: - new_var['children'] = children # only add if non-empty - self.__cnfvars.append(new_var) - - def add_single(self, varname, data=u'', number=None): - """ - Add a single cnf var to config on top level. - - Compatibility API. - """ - return self.add (varname, data=data, number=number) - - def append_file_generic(self, reader, cnf, replacements=None): - """ - Append conf var data from file. - - If `replacements` are given, calls :py:meth:`set_values` with these - before adding values to config. - - :param cnf: file name or dictionary of conf vars - :type cnf: str or {str, int or str or None} - :param replacements: see help in :py:meth:`set_values` - """ - log.info("append CNF_VARs from file") - new_vars = None - if callable(reader) is False: - raise TypeError("append_file_generic: reader must be callable, " - "not %s" % type(reader)) - if isinstance(cnf, dict): - new_vars = get_cnf(cnf) - elif isinstance(cnf, str): - fullpath = lookup_cnf_file(cnf) - with open(fullpath, "rb") as chan: - cnfdata = chan.read() - tmp = reader(cnfdata) - new_vars = get_cnf(tmp) - if new_vars is None: - raise InvalidCnf("Cannot append object \"%s\" of type \"%s\"." - % (cnf, type(cnf))) - - if replacements is not None: - set_values(new_vars, replacements) - - self.__cnfvars.extend(new_vars) - - def append_file(self, cnf, replacements=None): - """Append conf var data from file.""" - return self.append_file_generic(cnfvar_old.read_cnf, cnf, - replacements=replacements) - - def append_file_json(self, cnf, replacements=None): - """Append conf var data from json file.""" - return self.append_file_generic(cnfvar_old.read_cnf_json, cnf, - replacements=replacements) - - def append_guest_vars(self, vm=None, varname=None, replacements=None): - """ - Append content from machine's "real" config to this object. - - Runs `get_cnf -j [varname]` on local host or VM (depending on arg - `vm`), converts output and appends it to this objects' conf var set. - If replacements are given, runs :py:meth:`set_values`, first. - - :param vm: a guest vm or None to run on local host - :type vm: VM object or None - :param str varname: optional root of conf vars to append. If given as - None (default), append complete conf - :param replacements: see help in :py:meth:`set_values` - """ - cnf = arnied_wrapper.get_cnfvar(varname=varname, vm=vm) - new_vars = get_cnf(cnf) - - log.info("apply substitutions to extracted CNF_VARs") - if replacements is not None: - set_values(new_vars, replacements) - - current = self.__cnfvars - current.extend(new_vars) - - def save(self, filename=None): - """ - Saves this object's configuration data to a file. - - The output file's content can be interpreted by `set_cnf -j`. - - :param str filename: name of file to write config to; if None (default) - the config will be written to a temporary file - :returns: filename that was written to - :rtype: str - """ - log.info("save configuration") - current = self.__cnfvars - if not current: - raise InvalidCnf("No variables to write.") - - if filename is None: - # create temporary filename - filename = arnied_wrapper.generate_config_path(dumped=True) - - with open(filename, 'w') as out: - cnfvar_old.output_json({"cnf": current}, out, renumber=True) - - return filename - - def apply(self, vm=None, renumber=True): - """ - Apply object's config on VM or local host. - - Runs a `set_cnf` with complete internal config data, possibly waits for - generate to finish afterwards. - - :param vm: a guest vm or None to apply on local host - :type vm: VM object or None - :param bool renumber: re-number conf vars before application - """ - current = self.__cnfvars - if renumber: - log.info("enforce consistent CNF_LINE numbering") - cnfvar_old.renumber_vars(current) - log.info("inject configuration %s" % "into guest" if vm else "in place") - arnied_wrapper.set_cnf_dynamic({"cnf": current}, - config_file=gen_tmpname(), vm=vm) - - def __str__(self): - """ - Get a config in json format, ready for `set_cnf -j`. - - :returns: config in json format - :rtype: str - """ - return cnfvar_old.dump_json_string({"cnf": self.__cnfvars}, renumber=True) - - def pretty_print(self, print_func=None): - """ - Get a string representation of this simple_cnf that is human-readable - - Result is valid json with nice line breaks and indentation but not - renumbered (so may not be fit for parsing) - """ - for line in json.dumps({"cnf": self.__cnfvars}, check_circular=False, - indent=4, sort_keys=True).splitlines(): - if print_func is None: - print(line) - else: - print_func(line) - - def __iter__(self): - """ - Return an iterator over the contents of this simple cnf. - - The iteration might not be ordered by line number nor entry nor - anything else. No guarantees made! - - The entries returned by the iterator are :py:class:`SimpleCnf`. - - Example:: - - for cnf_list in iter(my_cnf['PROXY_ACCESSLIST']): - print('checking proxy list {0} with {1} children' - .format(cnf_list.get_value(), len(cnf_list))) - """ - # self.__cnfvars is a list of dicts, each with the same fields - for dict_entry in self.__cnfvars: - yield SimpleCnf([dict_entry, ]) - - def __getitem__(self, item): - """ - Called by `cnf['key']` or `cnf[line_number]`; returns subset of cnf. - - Processing time is O(n) where n is the number of top-level entries in - simple cnf. - - Examples (on VM):: - - all = SimpleCnf() - all.append_guest_vars() - len(all) # --> probably huge - len(all['user']) # should give the number of users - - # should result in the same as all['user']: - users = SimpleCnf() - users.append_guest_vars(varname='user') - - :param item: line number or value to specify a cnf subset; - if string value, will be converted to lower case - :type item: int or str - :returns: another simple cnf that contains a subset of this simple cnf - :rtype: :py:class:`SimpleCnf` - - .. seealso:: method :py:func:`get` (more general than this) - """ - # determine whether arg 'item' is a key name or a line number - if isinstance(item, int): # is line number - dict_key = 'number' - else: # assume key name - dict_key = 'varname' - item = item.lower() - - # search all entries for matches - results = [dict_entry for dict_entry in self.__cnfvars - if dict_entry[dict_key] == item] - - # convert result to a simple cnf - return SimpleCnf(results) - - def __len__(self): - """ - Get the number of top-level entries in cnf. - - :returns: number of top-level entries in cnf - :rtype: int - """ - return len(self.__cnfvars) - - def get(self, name=None, value=None, instance=None, line=None): - """ - Get a subset of this config that matches ALL of given criteria. - - For example, if :py:func:`get_cnf` contains the line - '1121 USER,1: "admin"', all of these examples will result in the same - simple cnf:: - - cnf.get(name='user', value='admin') - cnf.get(name='user', instance=1) - cnf.get(name='user').get(value='admin') - cnf.get(line=1121) - - :param str name: conf var name (key) or None to not use this criterion; - will be converted to lower case - :param str value: value of conf var or None to not use this criterion - :param int instance: instance number of value in a list (e.g. USERS) - or None to not use this criterion - :param int line: line number of None to not use this criterion - :returns: a simple cnf that contains only entries that match ALL of the - given criteria. If nothing matches the given criteria, an - empty simple cnf will be returned - :rtype: :py:class:`SimpleCnf` - - .. seealso:: method :py:func:`__getitem__` (less general than this) - """ - if name is None: - name_test = lambda test_val: True - else: - name = name.lower() - name_test = lambda test_val: name == test_val['varname'] - - if value is None: - value_test = lambda test_val: True - else: - value = str(value) - value_test = lambda test_val: test_val['data'] == value - - if instance is None: - instance_test = lambda test_val: True - elif not isinstance(instance, int): - raise ValueError('expect int value for instance!') - else: - instance_test = lambda test_val: instance == test_val['instance'] - - if line is None: - line_test = lambda test_val: True - elif not isinstance(line, int): - raise ValueError('expect int value for line number!') - else: - line_test = lambda test_val: test_val['number'] == line - - return SimpleCnf(list(entry for entry in self.__cnfvars - if name_test(entry) and value_test(entry) - and instance_test(entry) and line_test(entry))) - - def get_children(self): - """ - Get children of simple cnf of just 1 entry. - - :returns: simple cnf children or an empty simple cnf if entry has - no children - :rtype: :py:class:`SimpleCnf` - :raises: :py:class:`ValueError` if this simple cnf has more - than 1 entry - """ - if len(self) != 1: - raise ValueError('get_children only possible if len == 1 (is {0})!' - .format(len(self))) - try: - result = self.__cnfvars[0]['children'] - except KeyError: - return SimpleCnf() - - for entry in result: - try: - del entry['parent'] - except KeyError: - pass - return SimpleCnf(result) - - def get_value(self): - """ - Get a value of a simple cnf of just 1 entry. - - :returns: str cnf value/data - :rtype: str - :raises: :py:class:`ValueError` if this simple cnf has more - than 1 entry - """ - if len(self) != 1: - raise ValueError('get_value only possible if len == 1 (is {0})!' - .format(len(self))) - return self.__cnfvars[0]['data'] - - def get_single_dict(self): - """ - Get a dictionary of a simple cnf of just 1 entry. - - :returns: dictionary of a simple cnf - :rtype: {str, int or str or None} - """ - if len(self) != 1: - raise ValueError('get_single_dict only possible if len == 1 (is {0})!' - .format(len(self))) - return self.__cnfvars[0] - - def __eq__(self, other_cnf): - """ - Determine wether `self` == `other_cnf`. - - :param other_cnf: cnf to compare with - :type other_cnf: :py:class:`SimpleCnf` - :returns: whether all cnf var entries are equal - :rtype: bool - """ - key_func = lambda cnf_var_entry: cnf_var_entry['number'] - - if isinstance (other_cnf, SimpleCnf) is False: - return False - - return sorted(self.__cnfvars, key=key_func) \ - == sorted(other_cnf.__cnfvars, key=key_func) # pylint: disable=protected-access diff --git a/test/test_simple_cnf.py b/test/test_simple_cnf.py deleted file mode 100755 index 4ad0486..0000000 --- a/test/test_simple_cnf.py +++ /dev/null @@ -1,282 +0,0 @@ -#!/usr/bin/env python -# This Python file uses the following encoding: utf-8 - -# The software in this package is distributed under the GNU General -# Public License version 2 (with a special exception described below). -# -# A copy of GNU General Public License (GPL) is included in this distribution, -# in the file COPYING.GPL. -# -# As a special exception, if other files instantiate templates or use macros -# or inline functions from this file, or you compile this file and link it -# with other works to produce a work based on this file, this file -# does not by itself cause the resulting work to be covered -# by the GNU General Public License. -# -# However the source code for this file must still be made available -# in accordance with section (3) of the GNU General Public License. -# -# This exception does not invalidate any other reasons why a work based -# on this file might be covered by the GNU General Public License. -# -# Copyright (c) 2016-2018 Intra2net AG - -import unittest -from traceback import print_exc -from tempfile import mkstemp -import os -import sys - -from src.simple_cnf import SimpleCnf -from src.simple_cnf import InvalidCnf - -TEST_SET = """ -1 USER,1: "admin" -2 (1) USER_DISABLED,0: "0" -3 (1) USER_FULLNAME,0: "Administrator" -4 (1) USER_GROUPWARE_FOLDER_CALENDAR,0: "INBOX/Kalender" -5 (1) USER_GROUPWARE_FOLDER_CONTACTS,0: "INBOX/Kontakte" -6 (1) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe" -7 (1) USER_GROUPWARE_FOLDER_NOTES,0: "INBOX/Notizen" -8 (1) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Elemente" -9 (1) USER_GROUPWARE_FOLDER_TASKS,0: "INBOX/Aufgaben" -10 (1) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente" -11 (1) USER_GROUP_MEMBER_REF,0: "1" -12 (1) USER_GROUP_MEMBER_REF,1: "2" -13 (1) USER_LOCALE,0: "" -14 (1) USER_PASSWORD,0: "test1234" -15 (1) USER_TRASH_DELETEDAYS,0: "30" -16 USER,2: "test" -17 (16) USER_DISABLED,0: "0" -18 (16) USER_EMAIL_VACATION,0: "ON" -19 (16) USER_EMAIL_VACATION_AUTOMATIC_END,0: "" -20 (16) USER_EMAIL_VACATION_AUTOMATIC_START,0: "" -21 (16) USER_EMAIL_VACATION_AUTOMATIC_STATE,0: "UNKNOWN" -22 (16) USER_EMAIL_VACATION_REPLYDAYS,0: "1" -23 (16) USER_EMAIL_VACATION_TEXT,0: "Bin im Urlaub" -24 (16) USER_FULLNAME,0: "testnutzer" -25 (16) USER_GROUPWARE_FOLDER_CALENDAR,0: "INBOX/Kalender" -26 (16) USER_GROUPWARE_FOLDER_CONTACTS,0: "INBOX/Kontakte" -27 (16) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe" -28 (16) USER_GROUPWARE_FOLDER_NOTES,0: "INBOX/Notizen" -29 (16) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Elemente" -30 (16) USER_GROUPWARE_FOLDER_TASKS,0: "INBOX/Aufgaben" -31 (16) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente" -32 (16) USER_GROUP_MEMBER_REF,0: "2" -33 (16) USER_GROUP_MEMBER_REF,1: "3" -34 (16) USER_LOCALE,0: "" -35 (16) USER_PASSWORD,0: "test1234" -36 (16) USER_TRASH_DELETEDAYS,0: "30" -37 (16) USER_WEBMAIL_MESSAGES_PER_PAGE,0: "25" -38 (16) USER_WEBMAIL_SIGNATURE,0: "" -39 USER,3: "mueller" -40 (39) USER_DISABLED,0: "0" -41 (39) USER_FULLNAME,0: "Kärößü" -42 (39) USER_GROUPWARE_FOLDER_CALENDAR,0: "INBOX/Kalender" -43 (39) USER_GROUPWARE_FOLDER_CONTACTS,0: "INBOX/Kontakte" -44 (39) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe" -45 (39) USER_GROUPWARE_FOLDER_NOTES,0: "INBOX/Notizen" -46 (39) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Elemente" -47 (39) USER_GROUPWARE_FOLDER_TASKS,0: "INBOX/Aufgaben" -48 (39) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente" -49 (39) USER_GROUP_MEMBER_REF,0: "2" -50 (39) USER_GROUP_MEMBER_REF,1: "3" -51 (39) USER_LOCALE,0: "" -52 (39) USER_PASSWORD,0: "grmpfl" -53 (39) USER_TRASH_DELETEDAYS,0: "30" -54 (39) USER_WEBMAIL_MESSAGES_PER_PAGE,0: "25" -55 (39) USER_WEBMAIL_SIGNATURE,0: "" -56 BACKUP_COMPRESS_ENABLE,0: "1" -57 BACKUP_CRON,0: "0123456" -58 (57) BACKUP_CRON_BEGIN,0: "7200" -59 BACKUP_ENCRYPT_ENABLE,0: "0" -60 BACKUP_ENCRYPT_PASSWORD,0: "" -61 TESTVAR,0: "test" -""" - -#: encoding for text files, set in :py:func:`setUpModule` -ENCODING = None - - -def setUpModule(): - """Called once before all tests; determines encoding - - This test might run in very limited build environments without locale, so - auto-selection of temp-file text encoding may return 'ASCII'. - Therefore, do the encoding "manually". - """ - global ENCODING - ENCODING = sys.getfilesystemencoding() - if ENCODING.lower() in ('ascii', 'ansi_x3.4-1968'): - ENCODING = 'utf8' - print('\nWARNING: Using fallback encoding {} for temp file creation' - .format(ENCODING)) - - -class SimpleCnfTest(unittest.TestCase): - """ The one and only test case in this module `-->` see module doc """ - - # setup and cleanup - - import_file = None - cnf = None - - @classmethod - def _import_cnf(cls): - """ import conf var data from temp file """ - cls.cnf = SimpleCnf() - cls.cnf.append_file(cls.import_file) - - @classmethod - def setUpClass(cls): - """ before running tests: write conf var string to temp file """ - try: - sys_file_descriptor, cls.import_file = mkstemp() - os.close(sys_file_descriptor) - with open(cls.import_file, 'wb') as file_handle: - file_handle.write(TEST_SET.encode(ENCODING)) - except Exception: - print('\nWARNING: exception creating temp file:') - print_exc() - - # clean up - cls.tearDownClass() - - # re-raise - raise - - cls._import_cnf() - - @classmethod - def tearDownClass(cls): - """ after all tests have run, delete temp file """ - if cls.import_file is not None: - try: - os.unlink(cls.import_file) - except Exception: - print('\nWARNING: exception deleting temp file:') - print_exc() - - # tests - - def test_ctor_ok (self): - """ - Test initializer :py:meth:`SimpleCnf.__init__` must succeed on sane - inputs. - """ - self.assertNotEqual (SimpleCnf ({"cnf": []}), None) - self.assertNotEqual (SimpleCnf ([]), None) - self.assertNotEqual (SimpleCnf (), None) - - def test_ctor_fail (self): - """ - Test initializer :py:meth:`SimpleCnf.__init__` must reject inputs that - ``cnfvar.py`` does not handle. - """ - with self.assertRaises (InvalidCnf): - _void = SimpleCnf (b"junk") - with self.assertRaises (InvalidCnf): - _void = SimpleCnf (42) - with self.assertRaises (InvalidCnf): - _void = SimpleCnf (tuple ([1701, 1337])) - with self.assertRaises (InvalidCnf): - _void = SimpleCnf (set (["one", "two"])) - - def test_eq(self): - """ test method :py:meth:`SimpleCnf.__eq__` """ - self.assertEqual(self.cnf[61], self.cnf[61]) - self.assertEqual(self.cnf['testvar'], self.cnf[61]) - self.assertEqual(self.cnf, self.cnf) - self.assertNotEqual(self.cnf[56], self.cnf[57]) - self.assertNotEqual(SimpleCnf ({"cnf": []}), None) - self.assertNotEqual(SimpleCnf ({"cnf": []}), 42) - self.assertNotEqual(SimpleCnf ({"cnf": []}), "got wood?") - self.assertEqual(SimpleCnf (), SimpleCnf ({"cnf": []})) - - def test_len(self): - """ test method :py:meth:`SimpleCnf.__len__` """ - self.assertEqual(len(self.cnf), 8) - - def test_getitem(self): - """ test method :py:meth:`SimpleCnf.__item__` """ - self.assertEqual(len(self.cnf['user']), 3) - self.assertEqual(self.cnf['USER'], self.cnf['user']) - self.assertEqual(len(self.cnf['backup_encrypt_password']), 1) - self.assertEqual(len(self.cnf[12232]), 0) - self.assertEqual(len(self.cnf[55]), 0) - self.assertEqual(len(self.cnf[61]), 1) - self.assertEqual(len(self.cnf['user_webmail_signature']), 0) - - def test_get(self): - """ test method :py:meth:`SimpleCnf.get` """ - self.assertEqual(len(self.cnf.get()), 8) - self.assertEqual(len(self.cnf.get(name='user')), 3) - - def test_get_value(self): - """ test method :py:meth:`SimpleCnf.get_value` """ - - with self.assertRaises(ValueError): - self.cnf.get_value() - - self.assertEqual(self.cnf[56].get_value(), '1') - self.assertEqual(self.cnf[61].get_value(), 'test') - - def test_get_children(self): - """ test method :py:meth:`SimpleCnf.get_children` """ - with self.assertRaises(ValueError): - self.cnf.get_children() - self.assertEqual(len(self.cnf.get(name='user', value='mueller') - .get_children()), 16) - self.assertEqual(self.cnf.get(name='user'), - self.cnf.get(name='USER')) - self.assertEqual(self.cnf.get(name='user', value='mueller'), - self.cnf.get(name='USER', value='mueller')) - self.assertEqual(len(self.cnf[57].get_children()), 1) - self.assertEqual(self.cnf[57].get_children().get_value(), '7200') - - def test_add_alone(self): - """ test method :py:meth:`SimpleCnf.add` on empty conf """ - # do not use self.cnf since that would void other test methods - cnf = SimpleCnf() - self.assertEqual(len(cnf), 0) - cnf.add('new_var', 'new_value') - self.assertEqual(len(cnf), 1) - cnf_var = cnf.get(name='new_var').get_single_dict() - self.assertEqual(cnf_var['data'], 'new_value') - self.assertIsInstance(cnf_var['data'], str) - self.assertEqual(cnf_var['number'], 1) - - def test_add_on_top(self): - """ test method :py:meth:`SimpleCnf.add` on regular conf """ - cnf = SimpleCnf() - self.assertEqual(len(cnf), 0) - cnf.append_file(self.import_file) - self.assertEqual(len(cnf), 8) - - cnf.add('new_var', 'new_value') - self.assertEqual(len(cnf), 9) - cnf_var = cnf.get(name='new_var').get_single_dict() - self.assertEqual(cnf_var['data'], 'new_value') - self.assertIsInstance(cnf_var['data'], str) - self.assertEqual(cnf_var['number'], 62) - - def test_add_with_children(self): - """ test method :py:meth:`SimpleCnf.add` by adding var with children""" - # load config - cnf = SimpleCnf() - cnf.append_file(self.import_file) - self.assertEqual(len(cnf['user']), 3) - - # get a certain user with all its sub config - user_cnf = cnf.get(name='user', value='admin') - self.assertEqual(len(user_cnf), 1) - - # copy as new user with different name but same children - cnf.add('user', 'admin2', children=user_cnf.get_children()) - self.assertEqual(len(cnf['user']), 4) - self.assertEqual(len(cnf.get(name='user', value='admin2')), 1) - self.assertEqual(len(cnf.get(name='user', value='admin2').get_children()), 14) - - -if __name__ == '__main__': - unittest.main()