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"]) |