Drop previous simple cnf module and class throughout the codebase
authorPlamen Dimitrov <plamen.dimitrov@intra2net.com>
Sat, 9 Apr 2022 05:06:21 +0000 (08:06 +0300)
committerChristian Herdtweck <christian.herdtweck@intra2net.com>
Thu, 19 May 2022 09:13:27 +0000 (11:13 +0200)
Within pyi2ncommon this is only a matter of isolated narrowly used
code within the dial module.

src/dial.py
src/simple_cnf.py [deleted file]
test/test_simple_cnf.py [deleted file]

index a87117e..1b53fa9 100644 (file)
@@ -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 (file)
index 572866e..0000000
+++ /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 <info@intra2net.com>
-
-"""
-
-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 (executable)
index 4ad0486..0000000
+++ /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 <info@intra2net.com>
-
-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()