| Index: client/tests/kvm/kvm_config.py
|
| diff --git a/client/tests/kvm/kvm_config.py b/client/tests/kvm/kvm_config.py
|
| index 807a20454626f87284f21c4da48e7dcc980f9be3..4dbb1d49b508643dc7935dbc6ed988f811725b6c 100755
|
| --- a/client/tests/kvm/kvm_config.py
|
| +++ b/client/tests/kvm/kvm_config.py
|
| @@ -5,7 +5,7 @@ KVM test configuration file parser
|
| @copyright: Red Hat 2008-2011
|
| """
|
|
|
| -import re, os, sys, optparse, collections, string
|
| +import re, os, sys, optparse, collections
|
|
|
|
|
| # Filter syntax:
|
| @@ -137,6 +137,14 @@ class NoOnlyFilter(Filter):
|
|
|
|
|
| class OnlyFilter(NoOnlyFilter):
|
| + def is_irrelevant(self, ctx, ctx_set, descendant_labels):
|
| + return self.match(ctx, ctx_set)
|
| +
|
| +
|
| + def requires_action(self, ctx, ctx_set, descendant_labels):
|
| + return not self.might_match(ctx, ctx_set, descendant_labels)
|
| +
|
| +
|
| def might_pass(self, failed_ctx, failed_ctx_set, ctx, ctx_set,
|
| descendant_labels):
|
| for word in self.filter:
|
| @@ -148,6 +156,14 @@ class OnlyFilter(NoOnlyFilter):
|
|
|
|
|
| class NoFilter(NoOnlyFilter):
|
| + def is_irrelevant(self, ctx, ctx_set, descendant_labels):
|
| + return not self.might_match(ctx, ctx_set, descendant_labels)
|
| +
|
| +
|
| + def requires_action(self, ctx, ctx_set, descendant_labels):
|
| + return self.match(ctx, ctx_set)
|
| +
|
| +
|
| def might_pass(self, failed_ctx, failed_ctx_set, ctx, ctx_set,
|
| descendant_labels):
|
| for word in self.filter:
|
| @@ -165,6 +181,13 @@ class Condition(NoFilter):
|
| self.content = []
|
|
|
|
|
| +class NegativeCondition(OnlyFilter):
|
| + def __init__(self, line):
|
| + Filter.__init__(self, line.lstrip("!").rstrip(":"))
|
| + self.line = line
|
| + self.content = []
|
| +
|
| +
|
| class Parser(object):
|
| """
|
| Parse an input file or string that follows the KVM Test Config File format
|
| @@ -226,27 +249,18 @@ class Parser(object):
|
| # filters first.
|
| for t in content:
|
| filename, linenum, obj = t
|
| - if type(obj) is str:
|
| + if type(obj) is Op:
|
| new_content.append(t)
|
| continue
|
| - elif type(obj) is OnlyFilter:
|
| - if not obj.might_match(ctx, ctx_set, labels):
|
| - self._debug(" filter did not pass: %r (%s:%s)",
|
| - obj.line, filename, linenum)
|
| - failed_filters.append(t)
|
| - return False
|
| - elif obj.match(ctx, ctx_set):
|
| - continue
|
| - elif type(obj) is NoFilter:
|
| - if obj.match(ctx, ctx_set):
|
| + # obj is an OnlyFilter/NoFilter/Condition/NegativeCondition
|
| + if obj.requires_action(ctx, ctx_set, labels):
|
| + # This filter requires action now
|
| + if type(obj) is OnlyFilter or type(obj) is NoFilter:
|
| self._debug(" filter did not pass: %r (%s:%s)",
|
| obj.line, filename, linenum)
|
| failed_filters.append(t)
|
| return False
|
| - elif not obj.might_match(ctx, ctx_set, labels):
|
| - continue
|
| - elif type(obj) is Condition:
|
| - if obj.match(ctx, ctx_set):
|
| + else:
|
| self._debug(" conditional block matches: %r (%s:%s)",
|
| obj.line, filename, linenum)
|
| # Check and unpack the content inside this Condition
|
| @@ -259,9 +273,12 @@ class Parser(object):
|
| failed_filters.append(t)
|
| return False
|
| continue
|
| - elif not obj.might_match(ctx, ctx_set, labels):
|
| - continue
|
| - new_content.append(t)
|
| + elif obj.is_irrelevant(ctx, ctx_set, labels):
|
| + # This filter is no longer relevant and can be removed
|
| + continue
|
| + else:
|
| + # Keep the filter and check it again later
|
| + new_content.append(t)
|
| return True
|
|
|
| def might_pass(failed_ctx,
|
| @@ -330,7 +347,7 @@ class Parser(object):
|
| self._debug(" reached leaf, returning it")
|
| d = {"name": name, "dep": dep, "shortname": ".".join(shortname)}
|
| for filename, linenum, op in new_content:
|
| - op.apply_to_dict(d, ctx, ctx_set)
|
| + op.apply_to_dict(d)
|
| yield d
|
| # If this node did not produce any dicts, remember the failed filters
|
| # of its descendants
|
| @@ -429,7 +446,8 @@ class Parser(object):
|
| # Parse 'variants'
|
| if line == "variants:":
|
| # 'variants' is not allowed inside a conditional block
|
| - if isinstance(node, Condition):
|
| + if (isinstance(node, Condition) or
|
| + isinstance(node, NegativeCondition)):
|
| raise ParserError("'variants' is not allowed inside a "
|
| "conditional block",
|
| None, cr.filename, linenum)
|
| @@ -441,11 +459,10 @@ class Parser(object):
|
| if len(words) < 2:
|
| raise ParserError("Syntax error: missing parameter",
|
| line, cr.filename, linenum)
|
| - if not isinstance(cr, FileReader):
|
| - raise ParserError("Cannot include because no file is "
|
| - "currently open",
|
| - line, cr.filename, linenum)
|
| - filename = os.path.join(os.path.dirname(cr.filename), words[1])
|
| + filename = os.path.expanduser(words[1])
|
| + if isinstance(cr, FileReader) and not os.path.isabs(filename):
|
| + filename = os.path.join(os.path.dirname(cr.filename),
|
| + filename)
|
| if not os.path.isfile(filename):
|
| self._warn("%r (%s:%s): file doesn't exist or is not a "
|
| "regular file", line, cr.filename, linenum)
|
| @@ -471,28 +488,34 @@ class Parser(object):
|
| node.content += [(cr.filename, linenum, f)]
|
| continue
|
|
|
| + # Look for operators
|
| + op_match = _ops_exp.search(line)
|
| +
|
| # Parse conditional blocks
|
| - if line.endswith(":"):
|
| - try:
|
| - cond = Condition(line)
|
| - except ParserError, e:
|
| - e.line = line
|
| - e.filename = cr.filename
|
| - e.linenum = linenum
|
| - raise
|
| - self._parse(cr, cond, prev_indent=indent)
|
| - node.content += [(cr.filename, linenum, cond)]
|
| - continue
|
| + if ":" in line:
|
| + index = line.index(":")
|
| + if not op_match or index < op_match.start():
|
| + index += 1
|
| + cr.set_next_line(line[index:], indent, linenum)
|
| + line = line[:index]
|
| + try:
|
| + if line.startswith("!"):
|
| + cond = NegativeCondition(line)
|
| + else:
|
| + cond = Condition(line)
|
| + except ParserError, e:
|
| + e.line = line
|
| + e.filename = cr.filename
|
| + e.linenum = linenum
|
| + raise
|
| + self._parse(cr, cond, prev_indent=indent)
|
| + node.content += [(cr.filename, linenum, cond)]
|
| + continue
|
|
|
| # Parse regular operators
|
| - try:
|
| - op = Op(line)
|
| - except ParserError, e:
|
| - e.line = line
|
| - e.filename = cr.filename
|
| - e.linenum = linenum
|
| - raise
|
| - node.content += [(cr.filename, linenum, op)]
|
| + if not op_match:
|
| + raise ParserError("Syntax error", line, cr.filename, linenum)
|
| + node.content += [(cr.filename, linenum, Op(line, op_match))]
|
|
|
| return node
|
|
|
| @@ -557,26 +580,17 @@ _ops_exp = re.compile("|".join([op[0] for op in _ops.values()]))
|
|
|
|
|
| class Op(object):
|
| - def __init__(self, line):
|
| - m = re.search(_ops_exp, line)
|
| - if not m:
|
| - raise ParserError("Syntax error: missing operator")
|
| - left = line[:m.start()].strip()
|
| + def __init__(self, line, m):
|
| + self.func = _ops[m.group()][1]
|
| + self.key = line[:m.start()].strip()
|
| value = line[m.end():].strip()
|
| - if value and ((value[0] == '"' and value[-1] == '"') or
|
| - (value[0] == "'" and value[-1] == "'")):
|
| + if value and (value[0] == value[-1] == '"' or
|
| + value[0] == value[-1] == "'"):
|
| value = value[1:-1]
|
| - filters_and_key = map(str.strip, left.split(":"))
|
| - self.filters = [Filter(f) for f in filters_and_key[:-1]]
|
| - self.key = filters_and_key[-1]
|
| self.value = value
|
| - self.func = _ops[m.group()][1]
|
|
|
|
|
| - def apply_to_dict(self, d, ctx, ctx_set):
|
| - for f in self.filters:
|
| - if not f.match(ctx, ctx_set):
|
| - return
|
| + def apply_to_dict(self, d):
|
| self.func(d, self.key, self.value)
|
|
|
|
|
| @@ -595,6 +609,7 @@ class StrReader(object):
|
| self.filename = "<string>"
|
| self._lines = []
|
| self._line_index = 0
|
| + self._stored_line = None
|
| for linenum, line in enumerate(s.splitlines()):
|
| line = line.rstrip().expandtabs()
|
| stripped_line = line.lstrip()
|
| @@ -608,14 +623,17 @@ class StrReader(object):
|
|
|
| def get_next_line(self, prev_indent):
|
| """
|
| - Get the next non-empty, non-comment line in the string, whose
|
| - indentation level is higher than prev_indent.
|
| + Get the next line in the current block.
|
|
|
| @param prev_indent: The indentation level of the previous block.
|
| @return: (line, indent, linenum), where indent is the line's
|
| indentation level. If no line is available, (None, -1, -1) is
|
| returned.
|
| """
|
| + if self._stored_line:
|
| + ret = self._stored_line
|
| + self._stored_line = None
|
| + return ret
|
| if self._line_index >= len(self._lines):
|
| return None, -1, -1
|
| line, indent, linenum = self._lines[self._line_index]
|
| @@ -625,6 +643,16 @@ class StrReader(object):
|
| return line, indent, linenum
|
|
|
|
|
| + def set_next_line(self, line, indent, linenum):
|
| + """
|
| + Make the next call to get_next_line() return the given line instead of
|
| + the real next line.
|
| + """
|
| + line = line.strip()
|
| + if line:
|
| + self._stored_line = line, indent, linenum
|
| +
|
| +
|
| class FileReader(StrReader):
|
| """
|
| Preprocess an input file for easy reading.
|
| @@ -640,7 +668,9 @@ class FileReader(StrReader):
|
|
|
|
|
| if __name__ == "__main__":
|
| - parser = optparse.OptionParser("usage: %prog [options] <filename>")
|
| + parser = optparse.OptionParser('usage: %prog [options] filename '
|
| + '[extra code] ...\n\nExample:\n\n '
|
| + '%prog tests.cfg "only my_set" "no qcow2"')
|
| parser.add_option("-v", "--verbose", dest="debug", action="store_true",
|
| help="include debug messages in console output")
|
| parser.add_option("-f", "--fullname", dest="fullname", action="store_true",
|
| @@ -653,6 +683,9 @@ if __name__ == "__main__":
|
| parser.error("filename required")
|
|
|
| c = Parser(args[0], debug=options.debug)
|
| + for s in args[1:]:
|
| + c.parse_string(s)
|
| +
|
| for i, d in enumerate(c.get_dicts()):
|
| if options.fullname:
|
| print "dict %4d: %s" % (i + 1, d["name"])
|
|
|