******** example: variants name=tests: - wait: run = "wait" variants: - long: time = short_time - short: long time = logn_time - test2: run = "test1" variants name=virt_system: - linux: - windows_XP: variants name=host_os: - linux: image = linux - windows_XP: image = windows tests>wait.short: shutdown = destroy only host_os>linux ******** output: dict 1: host_os>linux.virt_system>linux.tests>wait.long dep = [] host_os = linux image = linux name = host_os>linux.virt_system>linux.tests>wait.long run = wait shortname = host_os>linux.virt_system>linux.tests>wait.long tests = wait time = short_time virt_system = linux dict 2: host_os>linux.virt_system>linux.tests>wait.short dep = ['host_os>linux.virt_system>linux.tests>wait.long'] host_os = linux image = linux name = host_os>linux.virt_system>linux.tests>wait.short run = wait shortname = host_os>linux.virt_system>linux.tests>wait.short shutdown = destroy tests = wait time = logn_time virt_system = linux dict 3: host_os>linux.virt_system>linux.tests>test2 dep = [] host_os = linux image = linux name = host_os>linux.virt_system>linux.tests>test2 run = test1 shortname = host_os>linux.virt_system>linux.tests>test2 tests = test2 virt_system = linux dict 4: host_os>linux.virt_system>windows_XP.tests>wait.long dep = [] host_os = linux image = linux name = host_os>linux.virt_system>windows_XP.tests>wait.long run = wait shortname = host_os>linux.virt_system>windows_XP.tests>wait.long tests = wait time = short_time virt_system = windows_XP dict 5: host_os>linux.virt_system>windows_XP.tests>wait.short dep = ['host_os>linux.virt_system>windows_XP.tests>wait.long'] host_os = linux image = linux name = host_os>linux.virt_system>windows_XP.tests>wait.short run = wait shortname = host_os>linux.virt_system>windows_XP.tests>wait.short shutdown = destroy tests = wait time = logn_time virt_system = windows_XP dict 6: host_os>linux.virt_system>windows_XP.tests>test2 dep = [] host_os = linux image = linux name = host_os>linux.virt_system>windows_XP.tests>test2 run = test1 shortname = host_os>linux.virt_system>windows_XP.tests>test2 tests = test2 virt_system = windows_XP For filtering of named variants is used character ">" because there was problem with conflict with = in expression key = value. The char ">" could be changed to something better but it should be different from "=" for optimization of speed. Additionally named variant adds keys to final dictionary in case of example is it (virt_system = linux). It should reduce size of config file. Keys defined in config and keys defined by named variants are in same name space. Signed-off-by: Jiří Župka <jzupka@xxxxxxxxxx> --- virttest/cartesian_config.py | 138 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 124 insertions(+), 14 deletions(-) diff --git a/virttest/cartesian_config.py b/virttest/cartesian_config.py index ef91051..04ed2b5 100755 --- a/virttest/cartesian_config.py +++ b/virttest/cartesian_config.py @@ -145,6 +145,74 @@ class MissingIncludeError: num_failed_cases = 5 +class Label(object): + __slots__ = ["name", "var_name", "long_name", "hash_val", "hash_var"] + + def __init__(self, name, next_name=None): + if next_name is None: + self.name = name + self.var_name = None + else: + self.name = next_name + self.var_name = name + + if self.var_name is None: + self.long_name = "%s" % (self.name) + else: + self.long_name = "%s>%s" % (self.var_name, self.name) + + self.hash_val = self.hash_name() + self.hash_var = None + if self.var_name: + self.hash_var = self.hash_variant() + + + def __str__(self): + return self.long_name + + + def __repr__(self): + return self.long_name + + + def __eq__(self, o): + """ + The comparison is asymmetric due to optimization. + """ + if o.var_name: + if self.long_name == o.long_name: + return True + else: + if self.name == o.name: + return True + return False + + + def __ne__(self, o): + """ + The comparison is asymmetric due to optimization. + """ + if o.var_name: + if self.long_name != o.long_name: + return True + else: + if self.name != o.name: + return True + return False + + + def __hash__(self): + return self.hash_val + + + def hash_name(self): + return sum([i + 1 * ord(x) for i, x in enumerate(self.name)]) + + + def hash_variant(self): + return sum([i + 1 * ord(x) for i, x in enumerate(str(self))]) + + class Node(object): __slots__ = ["name", "dep", "content", "children", "labels", "append_to_shortname", "failed_cases", "default"] @@ -212,18 +280,19 @@ class Filter(object): def __init__(self, s): self.filter = [] for char in s: - if not (char.isalnum() or char.isspace() or char in ".,_-"): + if not (char.isalnum() or char.isspace() or char in ".,_->"): raise ParserError("Illegal characters in filter") for word in s.replace(",", " ").split(): # OR word = [block.split(".") for block in word.split("..")] # AND - for word in s.replace(",", " ").split(): - word = [block.split(".") for block in word.split("..")] - for block in word: + words = [] for block in word: # . + b = [] for elem in block: if not elem: raise ParserError("Syntax error") - self.filter += [word] + b.append(Label(*elem.split(">"))) + words.append(b) + self.filter += [words] def match(self, ctx, ctx_set): @@ -506,15 +575,16 @@ class Parser(object): # node.dump(0) # Update dep for d in node.dep: - dep = dep + [".".join(ctx + [d])] + dep = dep + [".".join([str(label) for label in ctx + [d]])] # Update ctx ctx = ctx + node.name ctx_set = set(ctx) labels = node.labels # Get the current name - name = ".".join(ctx) + name = ".".join([str(label) for label in ctx]) if node.name: self._debug("checking out %r", name) + # Check previously failed filters for i, failed_case in enumerate(node.failed_cases): if not might_pass(*failed_case): @@ -534,9 +604,11 @@ class Parser(object): add_failed_case() self._debug("Failed_cases %s", node.failed_cases) return + # Update shortname if node.append_to_shortname: shortname = shortname + node.name + # Recurse into children count = 0 for n in node.children: @@ -546,7 +618,8 @@ class Parser(object): # Reached leaf? if not node.children: self._debug(" reached leaf, returning it") - d = {"name": name, "dep": dep, "shortname": ".".join(shortname)} + d = {"name": name, "dep": dep, + "shortname": ".".join([str(sn) for sn in shortname])} for _, _, op in new_content: op.apply_to_dict(d) yield d @@ -583,7 +656,7 @@ class Parser(object): print s % args - def _parse_variants(self, cr, node, prev_indent=-1): + def _parse_variants(self, cr, node, prev_indent=-1, var_name=None): """ Read and parse lines from a FileReader object until a line with an indent level lower than or equal to prev_indent is encountered. @@ -591,6 +664,7 @@ class Parser(object): @param cr: A FileReader/StrReader object. @param node: A node to operate on. @param prev_indent: The indent level of the "parent" block. + @param var_name: Variants name @return: A node object. """ node4 = Node() @@ -620,9 +694,15 @@ class Parser(object): node2.labels = node.labels node3 = self._parse(cr, node2, prev_indent=indent) - node3.name = name.lstrip("@").split(".") - node3.dep = dep.replace(",", " ").split() - node3.append_to_shortname = not name.startswith("@") + node3.name = [Label(var_name, n) for n in name.lstrip("@").split(".")] + node3.dep = [Label(var_name, d) for d in dep.replace(",", " ").split()] + + if var_name: + l = "%s = %s" % (var_name, name) + op_match = _ops_exp.search(l) + node3.content += [(cr.filename, linenum, Op(l, op_match))] + + is_default = name.startswith("@") node4.children += [node3] node4.labels.update(node3.labels) @@ -649,14 +729,44 @@ class Parser(object): words = line.split(None, 1) # Parse 'variants' - if line == "variants:": + if line.startswith("variants"): # 'variants' is not allowed inside a conditional block if (isinstance(node, Condition) or isinstance(node, NegativeCondition)): raise ParserError("'variants' is not allowed inside a " "conditional block", None, cr.filename, linenum) - node = self._parse_variants(cr, node, prev_indent=indent) + name = None + if not words[0] in ["variants", "variants:"]: + raise ParserError("Illegal characters in variants", + line, cr.filename, linenum) + if words[0] == "variants": + try: + name, _ = words[1].split(":") # split name and comment + except ValueError: + raise ParserError("Missing : in variants expression", + line, cr.filename, linenum) + for char in name: + if not (char.isalnum() or char.isspace() or + char in "._-=,"): + raise ParserError("Illegal characters in variants", + line, cr.filename, linenum) + var_name = None + if name: + block = name.split(",") + for b in block: + oper = b.split("=") + if oper[0] == "name": + if len(oper) == 1: + raise ParserError("Missing name of variants", + line, cr.filename, linenum) + var_name = oper[1].strip() + else: + raise ParserError("Ilegal variants param", + line, cr.filename, linenum) + node = self._parse_variants(cr, node, prev_indent=indent, + var_name=var_name) + var_name=name) continue # Parse 'include' statements -- 1.8.1.4 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html