From 3e9105b3aaacb8693ca34e1bfc65a7142a1c607c Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Fri, 1 Dec 2017 11:08:39 +0100 Subject: [PATCH] import cnfvar and unit tests cnfvar.py <- intranator/backup-crypto cnfvar_unittest.py <- autotest-intranator/backup-crypto Consequently, cnfvar.py has already been adapted for Python 3, the unit tests have not. --- src/cnfvar.py | 632 +++++++++++++++++++++++++++++++++++++++++++++++ test/cnfvar_unittest.py | 359 +++++++++++++++++++++++++++ 2 files changed, 991 insertions(+), 0 deletions(-) create mode 100644 src/cnfvar.py create mode 100755 test/cnfvar_unittest.py diff --git a/src/cnfvar.py b/src/cnfvar.py new file mode 100644 index 0000000..0418a7e --- /dev/null +++ b/src/cnfvar.py @@ -0,0 +1,632 @@ +#!/usr/bin/env python + +""" + +SUMMARY +------------------------------------------------------ +cnfvar + +Copyright: Intra2net AG + + +CONTENTS +------------------------------------------------------ +Represent CNF_VARs as recursive structures. + +.. todo:: Decide on some facility for automatic fixup of line number values. The + internal representation is recursive so line numbers are not needed to + establish a variable hierarchy. They might as well be omitted from the + json input and only added when writing cnf. For the time being a lack + of the "number" field is interpreted as an error. Though it should at + least optionally be possible to omit the numbers entirely and have a + function add them after the fact. This might as well be added to + :py:func:`is_cnf` though it would be counterintuitive to have a predicate + mutate its argument. So maybe it could return a second argument to + indicate a valid structure that needs fixup or something like that. + +.. note:: The variable values of get_cnf seems to be encoded in latin1, and + set_cnf seems to require latin1-encoded values (not var names). + Function :py:func:`read_cnf` converts this to unicode and functions + :py:func:`dump_cnf_string`, :py:func:`print_cnf`, and + :py:func:`write_cnf` convert unicode back to latin1. + + +INTERFACE +------------------------------------------------------ +""" + +import functools +import sys +import json +import re +import io + +# +# helpers +# + +# Sadly, the Intranator is still stuck with one leg in the 90s. +# + + +def to_latin1(s): + """ take given unicode value and convert it to a latin1-encoded string """ + return s.encode("latin-1") + + +def from_latin1(s): + """ take given latin1-encoded string value and convert it to unicode """ + return s.decode("latin-1") + +# +# traversal +# + + +class InvalidCNF(Exception): + + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return "Malformed CNF_VAR: \"%s\"" % self.msg + + +def walk_cnf(cnf, nested, fun, acc): + """ + :type cnf: cnf list + :type nested: bool + :type fun: ('a -> bool -> (cnf stuff) -> 'a) + :type acc: 'a + :rtype: 'a + """ + for var in cnf: + acc = fun(acc, + nested, + comment=var.get("comment", None), + data=var.get("data", None), + instance=var.get("instance", None), + number=var.get("number", None), + parent=var.get("parent", None), + varname=var.get("varname", None)) + children = var.get("children", None) + if children is not None: + acc = walk_cnf(children, True, fun, acc) + return acc + + +# +# validation +# + +def is_string(s): + return isinstance(s, str) or isinstance(s, unicode) + + +def is_valid(acc, + nested, + comment, + data, + instance, + number, + parent, + varname): + if varname is None: + raise InvalidCNF("CNF_VAR lacks a name.") + elif not is_string(varname): + raise InvalidCNF("Varname field of CNF_VAR \"%s\" is not a string." + % varname) + elif varname == "": + raise InvalidCNF("Varname field of CNF_VAR is the empty string.") + + if comment is not None: + if not is_string(comment): + raise InvalidCNF("Comment field of CNF_VAR \"%s\" is not a string." + % varname) + + if data is None: + raise InvalidCNF("Data field of CNF_VAR \"%s\" is empty." + % varname) + elif not is_string(data): + raise InvalidCNF("Data field of CNF_VAR \"%s\" is not a string." + % varname) + + if instance is None: + raise InvalidCNF("Instance field of CNF_VAR \"%s\" is empty." + % varname) + elif not isinstance(instance, int): + raise InvalidCNF("Instance field of CNF_VAR \"%s\" is not an integer." + % varname) + + if number is None: + raise InvalidCNF("Number field of CNF_VAR \"%s\" is empty." + % varname) + elif not isinstance(number, int): + raise InvalidCNF("Number field of CNF_VAR \"%s\" is not an integer." + % varname) + elif number < 1: + raise InvalidCNF("Number field of CNF_VAR \"%s\" must be positive, not %d." + % (varname, number)) + else: + other = acc.get(number, None) + if other is not None: # already in use + raise InvalidCNF("Number field of CNF_VAR \"%s\" already used by variable %s." + % (varname, other)) + acc[number] = varname + + if nested is True: + if parent is None: + raise InvalidCNF("Parent field of nested CNF_VAR \"%s\" is empty." + % varname) + elif not isinstance(parent, int): + raise InvalidCNF("Parent field of CNF_VAR \"%s\" is not an integer." + % varname) + else: + if parent is not None: + raise InvalidCNF("Flat CNF_VAR \"%s\" has nonsensical parent field \"%s\"." + % (varname, parent)) + return acc + + +def is_cnf(root): + """ + is_cnf -- Predicate testing "CNF_VAR-ness". Folds the :py:func:`is_valid` + predicate over the argument which can be either a well-formed CNF + dictionary or a list of CNF_VARs. + + :type root: cnfvar or cnf list + :rtype: bool + + Not that if it returns at all, ``is_cnf()`` returns ``True``. Any non + well-formed member of the argument will cause the predicate to bail out + with an exception during traversal. + """ + t_root = type(root) + if t_root is list: + cnf = root + elif t_root is dict: + cnf = root["cnf"] + else: + raise InvalidCNF(root) + return walk_cnf(cnf, False, is_valid, {}) is not None + +# +# deserialization +# + +# the easy part: JSON reader for get_cnf -j + + +def read_cnf_json(cnfdata): + cnf_json = json.loads(cnfdata) + if is_cnf(cnf_json) is False: + raise TypeError("Invalid CNF_VAR.") + return cnf_json + +# the moderately more complicated part: CNF reader + + +def advance(cns): + current, next, stream = cns + if next is None: # reached end of stream + return None + current = next + + try: + next = stream.pop() + next = next.strip() + except IndexError: + next = None + + if current == "": # skip blank lines + return advance((current, next, stream)) + return (current, next, stream) + + +def prepare(raw): + """ + :type raw: str + :rtype: (str * str option * str list) option + """ + lines = raw.splitlines() + lines.reverse() + try: + first = lines.pop() + except IndexError: + return None + + try: + second = lines.pop() + except IndexError: + second = None + + first = first.strip() + if first == "": + return advance((first, second, lines)) + + return (first, second, lines) + + +def peek(cns): + _, next, _ = cns + return next + + +def get(cnf): + current, _, _ = cns + return current + + +class MalformedCNF(Exception): + + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return "Malformed CNF file: \"%s\"" % self.msg + +grab_parent_pattern = re.compile(""" + ^ # match from start + \d+ # line number + \s+ # spaces + \((\d+)\) # parent + """, + re.VERBOSE) + + +def get_parent(line): + match = re.match(grab_parent_pattern, line) + if match is None: # -> no parent + return None + return int(match.groups()[0]) + +base_line_pattern = re.compile(""" + ^ # match from start + \s* # optional spaces + (\d+) # line number + \s+ # spaces + ([A-Z][A-Z0-9_]*) # varname + \s* # optional spaces + , # delimiter + \s* # optional spaces + (-1|\d+) # instance + \s* # optional spaces + : # delimiter + \s* # optional spaces + \"([^\"]*)\" # quoted string (data) + \s* # optional spaces + ( # bgroup + \# # comment leader + \s* # optional spaces + .* # string (comment) + )? # egroup, optional + $ # eol + """, + re.VERBOSE) + + +def read_base_line(line): + if len(line.strip()) == 0: + return None # ignore empty lines + if line[0] == "#": + return None # ignore comments + + match = re.match(base_line_pattern, line) + if match is None: + raise MalformedCNF("Syntax error in line \"\"\"%s\"\"\"" % line) + number, varname, instance, data, comment = match.groups() + return { + "number": int(number), + "varname": varname, + "instance": int(instance), + "data": data and from_latin1(data) or "", + "comment": comment and from_latin1(comment[1:].strip()) or None, + } + +child_line_pattern = re.compile(""" + ^ # match from start + \s* # optional spaces + (\d+) # line number + \s+ # spaces + \((\d+)\) # parent + \s+ # spaces + ([A-Z][A-Z0-9_]*) # varname + \s* # optional spaces + , # delimiter + \s* # optional spaces + (-1|\d+) # instance + \s* # optional spaces + : # delimiter + \s* # optional spaces + \"([^\"]*)\" # quoted string (data) + \s* # optional spaces + ( # bgroup + \# # comment leader + \s* # optional spaces + .* # string (comment) + )? # egroup, optional + $ # eol + """, + re.VERBOSE) + + +def read_child_line(line): + if len(line.strip()) == 0: + return None # ignore empty lines + if line[0] == "#": + return None # ignore comments + + match = re.match(child_line_pattern, line) + if match is None: + raise MalformedCNF("Syntax error in child line \"\"\"%s\"\"\"" % line) + number, parent, varname, instance, data, comment = match.groups() + return { + "number": int(number), + "parent": int(parent), + "varname": varname, + "instance": int(instance), + "data": data and from_latin1(data) or "", + "comment": comment and from_latin1(comment[1:].strip()) or None, + } + + +def parse_cnf_children(state, parent): + lines = [] + current = get(state) + while True: + cnf_line = read_child_line(current) + if cnf_line is not None: + lines.append(cnf_line) + state = advance(state) + if state is None: + break + current = get(state) + new_parent = get_parent(current) + if new_parent is None: + # drop stack + return (state, lines, None) + if new_parent > parent: + # parent is further down in hierarchy -> new level + (state, children, new_parent) = parse_cnf_children( + state, new_parent) + cnf_line["children"] = children + current = get(state) + if new_parent < parent: + # parent is further up in hierarchy -> pop level + return (state, lines, new_parent) + # new_parent == parent -> continue parsing on same level + return (state, lines, parent) + + +def parse_cnf_root(state): + lines = [] + current = get(state) + while state: + cnf_line = read_base_line(current) + if cnf_line is not None: + lines.append(cnf_line) + state = advance(state) + if state is None: # -> nothing left to do + break + current = get(state) + parent = get_parent(current) # peek at next line + if parent is not None: # -> recurse into children + (state, children, _parent) = parse_cnf_children(state, parent) + cnf_line["children"] = children + if state is None: + break + current = get(state) + return lines + + +def read_cnf(data): + state = prepare(data) + if state is None: + raise InvalidCNF("Empty input string.") + + cnf = parse_cnf_root(state) + if is_cnf(cnf) is False: + raise TypeError("Invalid CNF_VAR.") + return {"cnf": cnf} + + +def renumber_vars(root, parent=None): + """ + renumber_vars -- Number cnfvars linearly. + """ + if isinstance(root, dict): + root = root["cnf"] + i = parent or 0 + for var in root: + i += 1 + var["number"] = i + if parent is not None: + var["parent"] = parent + children = var.get("children", None) + if children is not None: + i = renumber_vars(children, i) + return i + +# +# serialization +# + +cnf_line_nest_indent = " " +cnf_line_base_fmt = "%d %s,%d: \"%s\"" +cnf_line_child_fmt = "%d %s(%d) %s,%d: \"%s\"" + + +def format_cnf_vars(da, var): + """ + Return a list of formatted cnf_line strings. + """ + + depth, acc = da + line = None + if depth > 0: + line = cnf_line_child_fmt \ + % (var["number"], + cnf_line_nest_indent * depth, + var["parent"], + to_latin1(var["varname"]).decode (), + var["instance"], + to_latin1(var["data"]).decode ()) + else: + line = cnf_line_base_fmt \ + % (var["number"], + to_latin1(var["varname"]).decode (), + var["instance"], + to_latin1(var["data"]).decode ()) + + comment = var.get("comment", None) + if comment and comment is not "": + line = line + (" # %s" % to_latin1(comment)) + + acc.append(line) + + children = var.get("children", None) + if children is not None: + (_, acc) = functools.reduce(format_cnf_vars, children, (depth + 1, acc)) + return (depth, acc) + + +def cnf_root(root): + if not isinstance(root, dict): + raise TypeError( + "Expected dictionary of CNF_VARs, got %s." % type(root)) + cnf = root.get("cnf", None) + if not isinstance(cnf, list): + raise TypeError("Expected list of CNF_VARs, got %s." % type(cnf)) + return cnf + + +def print_cnf_raw(root, out=None): + if root is not None: + out.write(root) + + +def write_cnf_raw(*argv, **kw_argv): + print_cnf_raw(*argv, **kw_argv) + + +def output_cnf(root, out, renumber=False): + cnf = cnf_root(root) + # Cthulhu (a.k.a Autotest) hates us so much he replaced the innocuous + # stdout with some chimera that isn't derived from ``file``, rendering + # the following sanity check moot. + # if not isinstance(out, file): + # raise TypeError("%s (%s) is not a stream." % (out, type(out))) + if renumber is True: + _count = renumber(root) + if is_cnf(cnf) is True: + (_, lines) = functools.reduce(format_cnf_vars, cnf, (0, [])) + out.write("\n".join(lines)) + out.write("\n") + + +def dump_cnf_string(root, renumber=False): + """ + dump_cnf_string -- Serialize CNF var structure, returning the result as a + string. + """ + cnf = cnf_root(root) + out = io.StringIO() + output_cnf(root, out, renumber=renumber) + res = out.getvalue() + out.close() + return res + + +def print_cnf(root, out=None, renumber=False): + if root is not None: + output_cnf(root, out or sys.stdout, renumber=renumber) + + +def write_cnf(*argv, **kw_argv): + print_cnf(*argv, **kw_argv) + + +def output_json(root, out, renumber=False): + # Sanity check incompatible with Autotest. Who needs a seatbelt anyways? + # if not isinstance(out, file): + #raise TypeError("%s (%s) is not a stream." % (out, type(out))) + if renumber is True: + _count = renumber_vars(root) + if is_cnf(root) is True: + data = json.dumps(root) + out.write(data) + out.write("\n") + + +def dump_json_string(root, renumber=False): + """ + dump_json_string -- Serialize CNF var structure as JSON, returning the + result as a string. + """ + out = io.StringIO() + output_json(root, out, renumber=renumber) + res = out.getvalue() + out.close() + return res + + +def print_cnf_json(root, out=None, renumber=False): + if root is not None: + output_json(root, out or sys.stdout, renumber=renumber) + + +def write_cnf_json(*argv, **kw_argv): + print_cnf_json(*argv, **kw_argv) + +# +# querying +# + + +def get_vars(cnf, data=None, instance=None): + """ + get_vars -- Query a CNF_VAR structure. Skims the *toplevel* of the CNF_VAR + structure for entries with a matching `data` or `instance` field. + + :type cnf: CNF_VAR + :type data: str + :type instance: int + :rtype: CNF_VAR + :returns: The structure containing only references to the + matching variables. Containing an empty list of + variables in case there is no match. + """ + cnf = cnf["cnf"] + if cnf: + criterion = lambda _var: False + if data: + if instance: + criterion = lambda var: var[ + "data"] == data and var["instance"] == instance + else: + criterion = lambda var: var["data"] == data + elif instance: + criterion = lambda var: var["instance"] == instance + + return {"cnf": [var for var in cnf if criterion(var) is True]} + + return {"cnf": []} + +# +# entry point for development +# + + +def main(argv): + if len(argv) > 1: + first = argv[1] + if first == "-": + cnf = read_cnf(sys.stdin.read()) + print_cnf(cnf) + elif first == "test": + cnf = read_cnf(sys.stdin.read()) + cnff = get_vars(cnf, instance=2, data="FAX") + print_cnf(cnff) + +if __name__ == "__main__": + main(sys.argv) diff --git a/test/cnfvar_unittest.py b/test/cnfvar_unittest.py new file mode 100755 index 0000000..37e5fe3 --- /dev/null +++ b/test/cnfvar_unittest.py @@ -0,0 +1,359 @@ +#!/usr/bin/env python +# This Python file uses the following encoding: utf-8 + +import unittest +import cnfvar + +# +# test data +# + +# model cnf tree +demo_cnfvar = {"cnf": [ + { + "varname": "MY_FAVORITE_CNF_VAR", + "instance": 1337, + "number": 42, + "data": "string conf content", + "comment": "" + }, + { + "varname": "SOME_NESTED_CNF_VAR", + "instance": 0, + "number": 23, + "data": "999", + "comment": "", + "children": [ + { + "varname": "SOME_CHILD_VAR", + "instance": 1, + "number": 24, + "data": "2014", + "parent": 23, + "comment": "" + } + ] + }, +]} + +# duplicate line number +demo_invalid_cnfvar = {"cnf": [ + { + "varname": "SOME_PARTICULARLY_TASTY_CNF_VAR", + "instance": 1337, + "number": 23, + "data": "classic wingers", + "comment": "" + }, + { + "varname": "EXTRAORDINARILY_FANCY_CNF_VAR", + "instance": 1, + "number": 42, + "data": "ab mentions", + "comment": "" + }, + { + "varname": "ANOTHER_POPULAR_CNF_VAR", + "instance": 0, + "number": 42, + "data": "notches", + "comment": "" + } +]} + +demo_jsoncnf = """ +{ + "cnf" : [ + { + "children" : [ + { + "comment" : "", + "data" : "1", + "instance" : 0, + "number" : 2, + "parent" : 1, + "varname" : "FIREWALL_SERVICEGROUP_PREDEFINED_ID" + }, + { + "children" : [ + { + "comment" : "", + "data" : "", + "instance" : 0, + "number" : 4, + "parent" : 3, + "varname" : "FIREWALL_SERVICEGROUP_TYPE_COMMENT" + }, + { + "comment" : "", + "data" : "80", + "instance" : 0, + "number" : 5, + "parent" : 3, + "varname" : "FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT" + } + ], + "comment" : "", + "data" : "TCP", + "instance" : 0, + "number" : 3, + "parent" : 1, + "varname" : "FIREWALL_SERVICEGROUP_TYPE" + } + ], + "comment" : "", + "data" : "http", + "instance" : 1, + "number" : 1, + "varname" : "FIREWALL_SERVICEGROUP" + }, + { + "children" : [ + { + "comment" : "", + "data" : "2", + "instance" : 0, + "number" : 7, + "parent" : 6, + "varname" : "FIREWALL_SERVICEGROUP_PREDEFINED_ID" + }, + { + "children" : [ + { + "comment" : "", + "data" : "", + "instance" : 0, + "number" : 9, + "parent" : 8, + "varname" : "FIREWALL_SERVICEGROUP_TYPE_COMMENT" + }, + { + "comment" : "", + "data" : "443", + "instance" : 0, + "number" : 10, + "parent" : 8, + "varname" : "FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT" + } + ], + "comment" : "", + "data" : "TCP", + "instance" : 0, + "number" : 8, + "parent" : 6, + "varname" : "FIREWALL_SERVICEGROUP_TYPE" + } + ], + "comment" : "", + "data" : "https", + "instance" : 2, + "number" : 6, + "varname" : "FIREWALL_SERVICEGROUP" + } + ] +} +""" + +demo_latin1crap = r""" +{ "cnf" : [ + { + "children" : [ + { + "comment" : "", + "data" : "0", + "instance" : 0, + "number" : 2, + "parent" : 1, + "varname" : "USER_DISABLED" + }, + { + "comment" : "", + "data" : "Administrator", + "instance" : 0, + "number" : 3, + "parent" : 1, + "varname" : "USER_FULLNAME" + }, + { + "comment" : "", + "data" : "INBOX/Kalender", + "instance" : 0, + "number" : 4, + "parent" : 1, + "varname" : "USER_GROUPWARE_FOLDER_CALENDAR" + }, + { + "comment" : "", + "data" : "INBOX/Kontakte", + "instance" : 0, + "number" : 5, + "parent" : 1, + "varname" : "USER_GROUPWARE_FOLDER_CONTACTS" + }, + { + "comment" : "", + "data" : "INBOX/Entwürfe", + "instance" : 0, + "number" : 6, + "parent" : 1, + "varname" : "USER_GROUPWARE_FOLDER_DRAFTS" + }, + { + "comment" : "", + "data" : "INBOX/Notizen", + "instance" : 0, + "number" : 7, + "parent" : 1, + "varname" : "USER_GROUPWARE_FOLDER_NOTES" + }, + { + "comment" : "", + "data" : "INBOX/Gesendete Elemente", + "instance" : 0, + "number" : 8, + "parent" : 1, + "varname" : "USER_GROUPWARE_FOLDER_OUTBOX" + }, + { + "comment" : "", + "data" : "INBOX/Aufgaben", + "instance" : 0, + "number" : 9, + "parent" : 1, + "varname" : "USER_GROUPWARE_FOLDER_TASKS" + }, + + { + "comment" : "", + "data" : "INBOX/Gelöschte Elemente", + "instance" : 0, + "number" : 10, + "parent" : 1, + "varname" : "USER_GROUPWARE_FOLDER_TRASH" + }, + { + "comment" : "", + "data" : "1", + "instance" : 0, + "number" : 11, + "parent" : 1, + "varname" : "USER_GROUP_MEMBER_REF" + }, + { + "comment" : "", + "data" : "2", + "instance" : 1, + "number" : 12, + "parent" : 1, + "varname" : "USER_GROUP_MEMBER_REF" + }, + { + "comment" : "", + "data" : "", + "instance" : 0, + "number" : 13, + "parent" : 1, + "varname" : "USER_LOCALE" + }, + { + "comment" : "", + "data" : "idkfa", + "instance" : 0, + "number" : 14, + "parent" : 1, + "varname" : "USER_PASSWORD" + }, + { + "comment" : "", + "data" : "30", + "instance" : 0, + "number" : 15, + "parent" : 1, + "varname" : "USER_TRASH_DELETEDAYS" + } + ], + "comment" : "", + "data" : "admin", + "instance" : 1, + "number" : 1, + "varname" : "USER" + } +]} +""" + +demo_cnf_group = """ +1 GROUP,1: "Administratoren" +2 (1) GROUP_ACCESS_GO_ONLINE_ALLOWED,0: "1" +3 (1) GROUP_EMAILFILTER_BAN_FILTERLIST_REF,0: "-1" +4 (1) GROUP_EMAIL_RELAY_RIGHTS,0: "RELAY_FROM_INTRANET" +5 (1) GROUP_PROXY_PROFILE_REF,0: "1" +""" + +demo_cnf_filter = """ +1 EMAILFILTER_BAN_FILTERLIST,1: "Vordefiniert: Alles verboten" +2 (1) EMAILFILTER_BAN_FILTERLIST_ENCRYPTED,0: "BLOCK" +3 (1) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS,0: "" +4 (1) EMAILFILTER_BAN_FILTERLIST_MIMETYPES,0: "" +5 (4) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,0: "text/plain" +6 (4) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,1: "text/html" +7 (1) EMAILFILTER_BAN_FILTERLIST_MODE,0: "ALLOW" +8 (1) EMAILFILTER_BAN_FILTERLIST_PREDEFINED_ID,0: "1" +""" + +demo_cnf_comments = """ +1 EMAILFILTER_BAN_FILTERLIST,1: "Vordefiniert: Alles verboten" +2 (1) EMAILFILTER_BAN_FILTERLIST_ENCRYPTED,0: "BLOCK" +3 (1) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS,0: "" +4 (1) EMAILFILTER_BAN_FILTERLIST_MIMETYPES,0: "" # foo +5 (4) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,0: "text/plain"#bar +6 (4) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,1: "text/html" +7 (1) EMAILFILTER_BAN_FILTERLIST_MODE,0: "ALLOW" # baz +8 (1) EMAILFILTER_BAN_FILTERLIST_PREDEFINED_ID,0: "1" +""" + + +# +# test class +# + +class CnfVarUnittest(unittest.TestCase): + + def test_print_cnf(self): + with open("/dev/null", "w") as devnull: + cnfvar.print_cnf(demo_cnfvar, out=devnull) + + def test_parse_cnf_simple(self): + cnf = cnfvar.read_cnf(demo_cnf_group) + with open("/dev/null", "w") as devnull: + cnfvar.print_cnf_json(cnf, out=devnull) + + def test_parse_cnf_nested(self): + cnf = cnfvar.read_cnf(demo_cnf_filter) + with open("/dev/null", "w") as devnull: + cnfvar.print_cnf_json(cnf, out=devnull) + + def test_parse_cnf_comments(self): + cnf = cnfvar.read_cnf(demo_cnf_comments) + with open("/dev/null", "w") as devnull: + cnfvar.print_cnf_json(cnf, out=devnull) + + def test_print_cnf_garbage(self): + try: + with open("/dev/null", "w") as devnull: + cnfvar.print_cnf(demo_invalid_cnfvar, out=devnull) + except cnfvar.InvalidCNF, exn: + print "Caught the duplicate line, bravo!" + + def test_read_json(self): + cnf = cnfvar.read_cnf_json(demo_jsoncnf) + with open("/dev/null", "w") as devnull: + cnfvar.print_cnf(cnf, out=devnull) + + def test_read_json_nonascii(self): + cnf = cnfvar.read_cnf_json(demo_latin1crap) + with open("/dev/null", "w") as devnull: + cnfvar.print_cnf(cnf, out=devnull) + + +if __name__ == '__main__': + unittest.main() -- 1.7.1