This is a reimplementation of the dict generator. It is much faster than the current implementation and uses a very small amount of memory. Running time and memory usage scale polynomially with the number of defined variants, compared to exponentially in the current implementation. Instead of regular expressions in the filters, the following syntax is used: , means OR .. means AND . means IMMEDIATELY-FOLLOWED-BY Example: only qcow2..Fedora.14, RHEL.6..raw..boot, smp2..qcow2..migrate..ide means select all dicts whose names have: (qcow2 AND (Fedora IMMEDIATELY-FOLLOWED-BY 14)) OR ((RHEL IMMEDIATELY-FOLLOWED-BY 6) AND raw AND boot) OR (smp2 AND qcow2 AND migrate AND ide) 'qcow2..Fedora.14' is equivalent to 'Fedora.14..qcow2'. 'qcow2..Fedora.14' is not equivalent to 'qcow2..14.Fedora'. 'ide, scsi' is equivalent to 'scsi, ide'. Filters can be used in 3 ways: only <filter> no <filter> <filter>: The last one starts a conditional block, e.g. Fedora.14..qcow2: no migrate, reboot foo = bar Interface changes: - The main class is now called 'Parser' instead of 'config'. - fork_and_parse() has been removed. parse_file() and parse_string() should be used instead. - When run as a standalone program, kvm_config.py just prints the shortnames of the generated dicts by default, and can optionally print the full names and contents of the dicts. - By default, debug messages are not printed, but they can be enabled by passing debug=True to Parser's constructor, or by running kvm_config.py -v. - The 'depend' key has been renamed to 'dep'. Signed-off-by: Michael Goldish <mgoldish@xxxxxxxxxx> Signed-off-by: Uri Lublin <ulublin@xxxxxxxxxx> --- client/tests/kvm/control | 28 +- client/tests/kvm/control.parallel | 12 +- client/tests/kvm/kvm_config.py | 1051 ++++++++++++++------------------ client/tests/kvm/kvm_scheduler.py | 9 +- client/tests/kvm/kvm_utils.py | 2 +- client/tests/kvm/tests.cfg.sample | 13 +- client/tests/kvm/tests_base.cfg.sample | 46 +- 7 files changed, 513 insertions(+), 648 deletions(-) diff --git a/client/tests/kvm/control b/client/tests/kvm/control index d226adf..be37678 100644 --- a/client/tests/kvm/control +++ b/client/tests/kvm/control @@ -35,13 +35,11 @@ str = """ # build configuration here. For example: #release_tag = 84 """ -build_cfg = kvm_config.config() -# As the base test config is quite large, in order to save memory, we use the -# fork_and_parse() method, that creates another parser process and destroys it -# at the end of the parsing, so the memory spent can be given back to the OS. -build_cfg_path = os.path.join(kvm_test_dir, "build.cfg") -build_cfg.fork_and_parse(build_cfg_path, str) -if not kvm_utils.run_tests(build_cfg.get_generator(), job): + +parser = kvm_config.Parser() +parser.parse_file(os.path.join(kvm_test_dir, "build.cfg")) +parser.parse_string(str) +if not kvm_utils.run_tests(parser.get_dicts(), job): logging.error("KVM build step failed, exiting.") sys.exit(1) @@ -49,10 +47,11 @@ str = """ # This string will be parsed after tests.cfg. Make any desired changes to the # test configuration here. For example: #display = sdl -#install|setup: timeout_multiplier = 3 +#install, setup: timeout_multiplier = 3 """ -tests_cfg = kvm_config.config() -tests_cfg_path = os.path.join(kvm_test_dir, "tests.cfg") + +parser = kvm_config.Parser() +parser.parse_file(os.path.join(kvm_test_dir, "tests.cfg")) if args: # We get test parameters from command line @@ -67,11 +66,12 @@ if args: str += "%s = %s\n" % (key, value) except IndexError: pass -tests_cfg.fork_and_parse(tests_cfg_path, str) +parser.parse_string(str) -# Run the tests -kvm_utils.run_tests(tests_cfg.get_generator(), job) +logging.info("Selected tests:") +for i, d in enumerate(parser.get_dicts()): + logging.info("Test %4d: %s" % (i + 1, d["shortname"])) +kvm_utils.run_tests(parser.get_dicts(), job) # Generate a nice HTML report inside the job's results dir kvm_utils.create_report(kvm_test_dir, job.resultdir) - diff --git a/client/tests/kvm/control.parallel b/client/tests/kvm/control.parallel index ac84638..640ccf5 100644 --- a/client/tests/kvm/control.parallel +++ b/client/tests/kvm/control.parallel @@ -163,16 +163,15 @@ import kvm_config str = """ # This string will be parsed after tests.cfg. Make any desired changes to the # test configuration here. For example: -#install|setup: timeout_multiplier = 3 -#only fc8_quick +#install, setup: timeout_multiplier = 3 #display = sdl """ -cfg = kvm_config.config() -filename = os.path.join(pwd, "tests.cfg") -cfg.fork_and_parse(filename, str) -tests = cfg.get_list() +parser = kvm_config.Parser() +parser.parse_file(os.path.join(pwd, "tests.cfg")) +parser.parse_string(str) +tests = list(parser.get_dicts()) # ------------- # Run the tests @@ -192,7 +191,6 @@ s = kvm_scheduler.scheduler(tests, num_workers, total_cpus, total_mem, pwd) job.parallel([s.scheduler], *[(s.worker, i, job.run_test) for i in range(num_workers)]) - # create the html report in result dir reporter = os.path.join(pwd, 'make_html_report.py') html_file = os.path.join(job.resultdir,'results.html') diff --git a/client/tests/kvm/kvm_config.py b/client/tests/kvm/kvm_config.py index 13cdfe2..1b27181 100755 --- a/client/tests/kvm/kvm_config.py +++ b/client/tests/kvm/kvm_config.py @@ -1,18 +1,149 @@ #!/usr/bin/python """ -KVM configuration file utility functions. +KVM test configuration file parser -@copyright: Red Hat 2008-2010 +@copyright: Red Hat 2008-2011 """ -import logging, re, os, sys, optparse, array, traceback, cPickle -import common -import kvm_utils -from autotest_lib.client.common_lib import error -from autotest_lib.client.common_lib import logging_manager +import re, os, sys, optparse, collections + + +# Filter syntax: +# , means OR +# .. means AND +# . means IMMEDIATELY-FOLLOWED-BY + +# Example: +# qcow2..Fedora.14, RHEL.6..raw..boot, smp2..qcow2..migrate..ide +# means match all dicts whose names have: +# (qcow2 AND (Fedora IMMEDIATELY-FOLLOWED-BY 14)) OR +# ((RHEL IMMEDIATELY-FOLLOWED-BY 6) AND raw AND boot) OR +# (smp2 AND qcow2 AND migrate AND ide) + +# Note: +# 'qcow2..Fedora.14' is equivalent to 'Fedora.14..qcow2'. +# 'qcow2..Fedora.14' is not equivalent to 'qcow2..14.Fedora'. +# 'ide, scsi' is equivalent to 'scsi, ide'. + +# Filters can be used in 3 ways: +# only <filter> +# no <filter> +# <filter>: +# The last one starts a conditional block. + + +num_failed_cases = 5 + + +class Node(object): + def __init__(self): + self.name = [] + self.dep = [] + self.content = [] + self.children = [] + self.labels = set() + self.append_to_shortname = False + self.failed_cases = collections.deque() + + +# Filter must inherit from object (otherwise type() won't work) +class Filter(object): + def __init__(self, s): + self.filter = [] + for word in s.replace(",", " ").split(): + word = [block.split(".") for block in word.split("..")] + self.filter += [word] + + + def match_adjacent(self, block, ctx, ctx_set): + # TODO: explain what this function does + if block[0] not in ctx_set: + return 0 + if len(block) == 1: + return 1 + if block[1] not in ctx_set: + return int(ctx[-1] == block[0]) + k = 0 + i = ctx.index(block[0]) + while i < len(ctx): + if k > 0 and ctx[i] != block[k]: + i -= k - 1 + k = 0 + if ctx[i] == block[k]: + k += 1 + if k >= len(block): + break + if block[k] not in ctx_set: + break + i += 1 + return k + + + def might_match_adjacent(self, block, ctx, ctx_set, descendant_labels): + matched = self.match_adjacent(block, ctx, ctx_set) + for elem in block[matched:]: + if elem not in descendant_labels: + return False + return True + + + def match(self, ctx, ctx_set): + for word in self.filter: + for block in word: + if self.match_adjacent(block, ctx, ctx_set) != len(block): + break + else: + return True + return False + + + def might_match(self, ctx, ctx_set, descendant_labels): + for word in self.filter: + for block in word: + if not self.might_match_adjacent(block, ctx, ctx_set, + descendant_labels): + break + else: + return True + return False + + +class NoOnlyFilter(Filter): + def __init__(self, line): + Filter.__init__(self, line.split(None, 1)[1]) + self.line = line + + +class OnlyFilter(NoOnlyFilter): + def might_pass(self, failed_ctx, failed_ctx_set, ctx, ctx_set, + descendant_labels): + for word in self.filter: + for block in word: + if (self.match_adjacent(block, ctx, ctx_set) > + self.match_adjacent(block, failed_ctx, failed_ctx_set)): + return self.might_match(ctx, ctx_set, descendant_labels) + return False + +class NoFilter(NoOnlyFilter): + def might_pass(self, failed_ctx, failed_ctx_set, ctx, ctx_set, + descendant_labels): + for word in self.filter: + for block in word: + if (self.match_adjacent(block, ctx, ctx_set) < + self.match_adjacent(block, failed_ctx, failed_ctx_set)): + return not self.match(ctx, ctx_set) + return False -class config: + +class Condition(NoFilter): + def __init__(self, line): + Filter.__init__(self, line.rstrip(":")) + self.line = line + self.content = [] + + +class Parser(object): """ Parse an input file or string that follows the KVM Test Config File format and generate a list of dicts that will be later used as configuration @@ -21,17 +152,14 @@ class config: @see: http://www.linux-kvm.org/page/KVM-Autotest/Test_Config_File """ - def __init__(self, filename=None, debug=True): + def __init__(self, filename=None, debug=False): """ - Initialize the list and optionally parse a file. + Initialize the parser and optionally parse a file. - @param filename: Path of the file that will be taken. + @param filename: Path of the file to parse. @param debug: Whether to turn on debugging output. """ - self.list = [array.array("H", [4, 4, 4, 4])] - self.object_cache = [] - self.object_cache_indices = {} - self.regex_cache = {} + self.node = Node() self.debug = debug if filename: self.parse_file(filename) @@ -39,689 +167,436 @@ class config: def parse_file(self, filename): """ - Parse file. If it doesn't exist, raise an IOError. + Parse a file. @param filename: Path of the configuration file. """ - if not os.path.exists(filename): - raise IOError("File %s not found" % filename) - str = open(filename).read() - self.list = self.parse(configreader(filename, str), self.list) + self.node = self._parse(FileReader(filename), self.node) - def parse_string(self, str): + def parse_string(self, s): """ Parse a string. - @param str: String to parse. + @param s: String to parse. """ - self.list = self.parse(configreader('<string>', str, real_file=False), self.list) + self.node = self._parse(StrReader(s), self.node) - def fork_and_parse(self, filename=None, str=None): - """ - Parse a file and/or a string in a separate process to save memory. - - Python likes to keep memory to itself even after the objects occupying - it have been destroyed. If during a call to parse_file() or - parse_string() a lot of memory is used, it can only be freed by - terminating the process. This function works around the problem by - doing the parsing in a forked process and then terminating it, freeing - any unneeded memory. - - Note: if an exception is raised during parsing, its information will be - printed, and the resulting list will be empty. The exception will not - be raised in the process calling this function. - - @param filename: Path of file to parse (optional). - @param str: String to parse (optional). - """ - r, w = os.pipe() - r, w = os.fdopen(r, "r"), os.fdopen(w, "w") - pid = os.fork() - if not pid: - # Child process - r.close() - try: - if filename: - self.parse_file(filename) - if str: - self.parse_string(str) - except: - traceback.print_exc() - self.list = [] - # Convert the arrays to strings before pickling because at least - # some Python versions can't pickle/unpickle arrays - l = [a.tostring() for a in self.list] - cPickle.dump((l, self.object_cache), w, -1) - w.close() - os._exit(0) - else: - # Parent process - w.close() - (l, self.object_cache) = cPickle.load(r) - r.close() - os.waitpid(pid, 0) - self.list = [] - for s in l: - a = array.array("H") - a.fromstring(s) - self.list.append(a) - - - def get_generator(self): + def get_dicts(self, node=None, ctx=[], content=[], shortname=[], dep=[]): """ Generate dictionaries from the code parsed so far. This should - probably be called after parsing something. + be called after parsing something. @return: A dict generator. """ - for a in self.list: - name, shortname, depend, content = _array_get_all(a, - self.object_cache) - dict = {"name": name, "shortname": shortname, "depend": depend} - self._apply_content_to_dict(dict, content) - yield dict - - - def get_list(self): - """ - Generate a list of dictionaries from the code parsed so far. - This should probably be called after parsing something. + def apply_ops_to_dict(d, content): + for filename, linenum, s in content: + op_found = None + op_pos = len(s) + for op in ops: + if op in s: + pos = s.index(op) + if pos < op_pos: + op_found = op + op_pos = pos + if not op_found: + continue + left, value = map(str.strip, s.split(op_found, 1)) + if value and ((value[0] == '"' and value[-1] == '"') or + (value[0] == "'" and value[-1] == "'")): + value = value[1:-1] + filters_and_key = map(str.strip, left.split(":")) + for f in filters_and_key[:-1]: + if not Filter(f).match(ctx, ctx_set): + break + else: + key = filters_and_key[-1] + ops[op_found](d, key, value) + + def process_content(content, failed_filters): + # 1. Check that the filters in content are OK with the current + # context (ctx). + # 2. Move the parts of content that are still relevant into + # new_content and unpack conditional blocks if appropriate. + # For example, if an 'only' statement fully matches ctx, it + # becomes irrelevant and is not appended to new_content. + # If a conditional block fully matches, its contents are + # unpacked into new_content. + # 3. Move failed filters into failed_filters, so that next time we + # reach this node or one of its ancestors, we'll check those + # filters first. + for t in content: + filename, linenum, obj = t + if type(obj) is str: + 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): + 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): + self._debug(" conditional block matches: %r (%s:%s)", + obj.line, filename, linenum) + # Check and unpack the content inside this Condition + # object (note: the failed filters should go into + # new_internal_filters because we don't expect them to + # come from outside this node, even if the Condition + # itself was external) + if not process_content(obj.content, + new_internal_filters): + failed_filters.append(t) + return False + continue + elif not obj.might_match(ctx, ctx_set, labels): + continue + new_content.append(t) + return True + + def might_pass(failed_ctx, + failed_ctx_set, + failed_external_filters, + failed_internal_filters): + for t in failed_external_filters: + if t not in content: + return True + filename, linenum, filter = t + if filter.might_pass(failed_ctx, failed_ctx_set, ctx, ctx_set, + labels): + return True + for t in failed_internal_filters: + filename, linenum, filter = t + if filter.might_pass(failed_ctx, failed_ctx_set, ctx, ctx_set, + labels): + return True + return False + + def add_failed_case(): + node.failed_cases.appendleft((ctx, ctx_set, + new_external_filters, + new_internal_filters)) + if len(node.failed_cases) > num_failed_cases: + node.failed_cases.pop() + + node = node or self.node + # Update dep + for d in node.dep: + temp = ctx + [d] + dep = dep + [".".join([s for s in temp if s])] + # Update ctx + ctx = ctx + node.name + ctx_set = set(ctx) + labels = node.labels + # Get the current name + name = ".".join([s for s in ctx if s]) + 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): + self._debug(" this subtree has failed before") + del node.failed_cases[i] + node.failed_cases.appendleft(failed_case) + return + # Check content and unpack it into new_content + new_content = [] + new_external_filters = [] + new_internal_filters = [] + if (not process_content(node.content, new_internal_filters) or + not process_content(content, new_external_filters)): + add_failed_case() + return + # Update shortname + if node.append_to_shortname: + shortname = shortname + node.name + # Recurse into children + count = 0 + for n in node.children: + for d in self.get_dicts(n, ctx, new_content, shortname, dep): + count += 1 + yield d + # Reached leaf? + if not node.children: + self._debug(" reached leaf, returning it") + d = {"name": name, "dep": dep, + "shortname": ".".join([s for s in shortname if s])} + apply_ops_to_dict(d, new_content) + yield d + # If this node did not produce any dicts, remember the failed filters + # of its descendants + elif not count: + new_external_filters = [] + new_internal_filters = [] + for n in node.children: + (failed_ctx, + failed_ctx_set, + failed_external_filters, + failed_internal_filters) = n.failed_cases[0] + for obj in failed_internal_filters: + if obj not in new_internal_filters: + new_internal_filters.append(obj) + for obj in failed_external_filters: + if obj in content: + if obj not in new_external_filters: + new_external_filters.append(obj) + else: + if obj not in new_internal_filters: + new_internal_filters.append(obj) + add_failed_case() - @return: A list of dicts. - """ - return list(self.get_generator()) + def _debug(self, s, *args): + if self.debug: + s = "DEBUG: %s" % s + print s % args - def count(self, filter=".*"): - """ - Return the number of dictionaries whose names match filter. - @param filter: A regular expression string. - """ - exp = self._get_filter_regex(filter) - count = 0 - for a in self.list: - name = _array_get_name(a, self.object_cache) - if exp.search(name): - count += 1 - return count + def _warn(self, s, *args): + s = "WARNING: %s" % s + print s % args - def parse_variants(self, cr, list, subvariants=False, prev_indent=-1): + def _parse_variants(self, cr, node, prev_indent=-1): """ - Read and parse lines from a configreader object until a line with an + Read and parse lines from a FileReader object until a line with an indent level lower than or equal to prev_indent is encountered. - @brief: Parse a 'variants' or 'subvariants' block from a configreader - object. - @param cr: configreader object to be parsed. - @param list: List of arrays to operate on. - @param subvariants: If True, parse in 'subvariants' mode; - otherwise parse in 'variants' mode. + @param cr: A FileReader/StrReader object. + @param node: A node to operate on. @param prev_indent: The indent level of the "parent" block. - @return: The resulting list of arrays. + @return: A node object. """ - new_list = [] + node4 = Node() while True: - pos = cr.tell() - (indented_line, line, indent) = cr.get_next_line() - if indent <= prev_indent: - cr.seek(pos) + line, indent, linenum = cr.get_next_line(prev_indent) + if not line: break - # Get name and dependencies - (name, depend) = map(str.strip, line.lstrip("- ").split(":")) + name, dep = map(str.strip, line.lstrip("- ").split(":")) - # See if name should be added to the 'shortname' field - add_to_shortname = not name.startswith("@") - name = name.lstrip("@") + node2 = Node() + node2.children = [node] + node2.labels = node.labels - # Store name and dependencies in cache and get their indices - n = self._store_str(name) - d = self._store_str(depend) + 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("@") - # Make a copy of list - temp_list = [a[:] for a in list] + node4.children += [node3] + node4.labels.update(node3.labels) + node4.labels.update(node3.name) - if subvariants: - # If we're parsing 'subvariants', first modify the list - if add_to_shortname: - for a in temp_list: - _array_append_to_name_shortname_depend(a, n, d) - else: - for a in temp_list: - _array_append_to_name_depend(a, n, d) - temp_list = self.parse(cr, temp_list, restricted=True, - prev_indent=indent) - else: - # If we're parsing 'variants', parse before modifying the list - if self.debug: - _debug_print(indented_line, - "Entering variant '%s' " - "(variant inherits %d dicts)" % - (name, len(list))) - temp_list = self.parse(cr, temp_list, restricted=False, - prev_indent=indent) - if add_to_shortname: - for a in temp_list: - _array_prepend_to_name_shortname_depend(a, n, d) - else: - for a in temp_list: - _array_prepend_to_name_depend(a, n, d) + return node4 - new_list += temp_list - return new_list - - - def parse(self, cr, list, restricted=False, prev_indent=-1): + def _parse(self, cr, node, prev_indent=-1): """ - Read and parse lines from a configreader object until a line with an + Read and parse lines from a StrReader object until a line with an indent level lower than or equal to prev_indent is encountered. - @brief: Parse a configreader object. - @param cr: A configreader object. - @param list: A list of arrays to operate on (list is modified in - place and should not be used after the call). - @param restricted: If True, operate in restricted mode - (prohibit 'variants'). + @param cr: A FileReader/StrReader object. + @param node: A Node or a Condition object to operate on. @param prev_indent: The indent level of the "parent" block. - @return: The resulting list of arrays. - @note: List is destroyed and should not be used after the call. - Only the returned list should be used. + @return: A node object. """ - current_block = "" - while True: - pos = cr.tell() - (indented_line, line, indent) = cr.get_next_line() - if indent <= prev_indent: - cr.seek(pos) - self._append_content_to_arrays(list, current_block) + line, indent, linenum = cr.get_next_line(prev_indent) + if not line: break - len_list = len(list) - - # Parse assignment operators (keep lines in temporary buffer) - if "=" in line: - if self.debug and not restricted: - _debug_print(indented_line, - "Parsing operator (%d dicts in current " - "context)" % len_list) - current_block += line + "\n" - continue - - # Flush the temporary buffer - self._append_content_to_arrays(list, current_block) - current_block = "" - words = line.split() - # Parse 'no' and 'only' statements - if words[0] == "no" or words[0] == "only": - if len(words) <= 1: - continue - filters = map(self._get_filter_regex, words[1:]) - filtered_list = [] - if words[0] == "no": - for a in list: - name = _array_get_name(a, self.object_cache) - for filter in filters: - if filter.search(name): - break - else: - filtered_list.append(a) - if words[0] == "only": - for a in list: - name = _array_get_name(a, self.object_cache) - for filter in filters: - if filter.search(name): - filtered_list.append(a) - break - list = filtered_list - if self.debug and not restricted: - _debug_print(indented_line, - "Parsing no/only (%d dicts in current " - "context, %d remain)" % - (len_list, len(list))) - continue - # Parse 'variants' if line == "variants:": - # 'variants' not allowed in restricted mode - # (inside an exception or inside subvariants) - if restricted: - e_msg = "Using variants in this context is not allowed" - cr.raise_error(e_msg) - if self.debug and not restricted: - _debug_print(indented_line, - "Entering variants block (%d dicts in " - "current context)" % len_list) - list = self.parse_variants(cr, list, subvariants=False, - prev_indent=indent) - continue - - # Parse 'subvariants' (the block is parsed for each dict - # separately) - if line == "subvariants:": - if self.debug and not restricted: - _debug_print(indented_line, - "Entering subvariants block (%d dicts in " - "current context)" % len_list) - new_list = [] - # Remember current position - pos = cr.tell() - # Read the lines in any case - self.parse_variants(cr, [], subvariants=True, - prev_indent=indent) - # Iterate over the list... - for index in xrange(len(list)): - # Revert to initial position in this 'subvariants' block - cr.seek(pos) - # Everything inside 'subvariants' should be parsed in - # restricted mode - new_list += self.parse_variants(cr, list[index:index+1], - subvariants=True, - prev_indent=indent) - list = new_list + # 'variants' is not allowed inside a conditional block + if isinstance(node, Condition): + raise ValueError("'variants' is not allowed inside a " + "conditional block (%s:%s)" % + (cr.filename, linenum)) + node = self._parse_variants(cr, node, prev_indent=indent) continue # Parse 'include' statements if words[0] == "include": - if len(words) <= 1: + if len(words) < 2: + self._warn("%r (%s:%s): missing parameter. What are you " + "including?", line, cr.filename, linenum) continue - if self.debug and not restricted: - _debug_print(indented_line, "Entering file %s" % words[1]) - - cur_filename = cr.real_filename() - if cur_filename is None: - cr.raise_error("'include' is valid only when parsing a file") - - filename = os.path.join(os.path.dirname(cur_filename), - words[1]) - if not os.path.exists(filename): - cr.raise_error("Cannot include %s -- file not found" % (filename)) - - str = open(filename).read() - list = self.parse(configreader(filename, str), list, restricted) - if self.debug and not restricted: - _debug_print("", "Leaving file %s" % words[1]) + if not isinstance(cr, FileReader): + self._warn("%r (%s:%s): cannot include because no file is " + "currently open", line, cr.filename, linenum) + continue + filename = os.path.join(os.path.dirname(cr.filename), words[1]) + 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) + continue + node = self._parse(FileReader(filename), node) + continue + # Parse 'only' and 'no' filters + if words[0] in ("only", "no"): + if len(words) < 2: + self._warn("%r (%s:%s): missing parameter", line, + cr.filename, linenum) + continue + if words[0] == "only": + node.content += [(cr.filename, linenum, OnlyFilter(line))] + elif words[0] == "no": + node.content += [(cr.filename, linenum, NoFilter(line))] continue - # Parse multi-line exceptions - # (the block is parsed for each dict separately) + # Parse conditional blocks if line.endswith(":"): - if self.debug and not restricted: - _debug_print(indented_line, - "Entering multi-line exception block " - "(%d dicts in current context outside " - "exception)" % len_list) - line = line[:-1] - new_list = [] - # Remember current position - pos = cr.tell() - # Read the lines in any case - self.parse(cr, [], restricted=True, prev_indent=indent) - # Iterate over the list... - exp = self._get_filter_regex(line) - for index in xrange(len(list)): - name = _array_get_name(list[index], self.object_cache) - if exp.search(name): - # Revert to initial position in this exception block - cr.seek(pos) - # Everything inside an exception should be parsed in - # restricted mode - new_list += self.parse(cr, list[index:index+1], - restricted=True, - prev_indent=indent) - else: - new_list.append(list[index]) - list = new_list + cond = Condition(line) + self._parse(cr, cond, prev_indent=indent) + node.content += [(cr.filename, linenum, cond)] continue - return list + node.content += [(cr.filename, linenum, line)] + continue - - def _get_filter_regex(self, filter): - """ - Return a regex object corresponding to a given filter string. - - All regular expressions given to the parser are passed through this - function first. Its purpose is to make them more specific and better - suited to match dictionary names: it forces simple expressions to match - only between dots or at the beginning or end of a string. For example, - the filter 'foo' will match 'foo.bar' but not 'foobar'. - """ - try: - return self.regex_cache[filter] - except KeyError: - exp = re.compile(r"(\.|^)(%s)(\.|$)" % filter) - self.regex_cache[filter] = exp - return exp - - - def _store_str(self, str): - """ - Store str in the internal object cache, if it isn't already there, and - return its identifying index. - - @param str: String to store. - @return: The index of str in the object cache. - """ - try: - return self.object_cache_indices[str] - except KeyError: - self.object_cache.append(str) - index = len(self.object_cache) - 1 - self.object_cache_indices[str] = index - return index - - - def _append_content_to_arrays(self, list, content): - """ - Append content (config code containing assignment operations) to a list - of arrays. - - @param list: List of arrays to operate on. - @param content: String containing assignment operations. - """ - if content: - str_index = self._store_str(content) - for a in list: - _array_append_to_content(a, str_index) - - - def _apply_content_to_dict(self, dict, content): - """ - Apply the operations in content (config code containing assignment - operations) to a dict. - - @param dict: Dictionary to operate on. Must have 'name' key. - @param content: String containing assignment operations. - """ - for line in content.splitlines(): - op_found = None - op_pos = len(line) - for op in ops: - pos = line.find(op) - if pos >= 0 and pos < op_pos: - op_found = op - op_pos = pos - if not op_found: - continue - (left, value) = map(str.strip, line.split(op_found, 1)) - if value and ((value[0] == '"' and value[-1] == '"') or - (value[0] == "'" and value[-1] == "'")): - value = value[1:-1] - filters_and_key = map(str.strip, left.split(":")) - filters = filters_and_key[:-1] - key = filters_and_key[-1] - for filter in filters: - exp = self._get_filter_regex(filter) - if not exp.search(dict["name"]): - break - else: - ops[op_found](dict, key, value) + return node # Assignment operators -def _op_set(dict, key, value): - dict[key] = value +def _op_set(d, key, value): + d[key] = value -def _op_append(dict, key, value): - dict[key] = dict.get(key, "") + value +def _op_append(d, key, value): + d[key] = d.get(key, "") + value -def _op_prepend(dict, key, value): - dict[key] = value + dict.get(key, "") +def _op_prepend(d, key, value): + d[key] = value + d.get(key, "") -def _op_regex_set(dict, exp, value): +def _op_regex_set(d, exp, value): exp = re.compile("^(%s)$" % exp) - for key in dict: + for key in d: if exp.match(key): - dict[key] = value + d[key] = value -def _op_regex_append(dict, exp, value): +def _op_regex_append(d, exp, value): exp = re.compile("^(%s)$" % exp) - for key in dict: + for key in d: if exp.match(key): - dict[key] += value + d[key] += value -def _op_regex_prepend(dict, exp, value): +def _op_regex_prepend(d, exp, value): exp = re.compile("^(%s)$" % exp) - for key in dict: + for key in d: if exp.match(key): - dict[key] = value + dict[key] - + d[key] = value + d[key] -ops = { - "=": _op_set, - "+=": _op_append, - "<=": _op_prepend, - "?=": _op_regex_set, - "?+=": _op_regex_append, - "?<=": _op_regex_prepend, -} - - -# Misc functions - -def _debug_print(str1, str2=""): - """ - Nicely print two strings and an arrow. - @param str1: First string. - @param str2: Second string. - """ - if str2: - str = "%-50s ---> %s" % (str1, str2) - else: - str = str1 - logging.debug(str) +ops = {"=": _op_set, + "+=": _op_append, + "<=": _op_prepend, + "?=": _op_regex_set, + "?+=": _op_regex_append, + "?<=": _op_regex_prepend} -# configreader +# StrReader and FileReader -class configreader: +class StrReader(object): """ - Preprocess an input string and provide file-like services. - This is intended as a replacement for the file and StringIO classes, - whose readline() and/or seek() methods seem to be slow. + Preprocess an input string for easy reading. """ - - def __init__(self, filename, str, real_file=True): + def __init__(self, s): """ Initialize the reader. - @param filename: the filename we're parsing - @param str: The string to parse. - @param real_file: Indicates if filename represents a real file. Defaults to True. + @param s: The string to parse. """ - self.filename = filename - self.is_real_file = real_file - self.line_index = 0 - self.lines = [] - self.real_number = [] - for num, line in enumerate(str.splitlines()): + self.filename = "<string>" + self._lines = [] + self._line_index = 0 + for linenum, line in enumerate(s.splitlines()): line = line.rstrip().expandtabs() - stripped_line = line.strip() + stripped_line = line.lstrip() indent = len(line) - len(stripped_line) if (not stripped_line or stripped_line.startswith("#") or stripped_line.startswith("//")): continue - self.lines.append((line, stripped_line, indent)) - self.real_number.append(num + 1) - - - def real_filename(self): - """Returns the filename we're reading, in case it is a real file - - @returns the filename we are parsing, or None in case we're not parsing a real file - """ - if self.is_real_file: - return self.filename - - def get_next_line(self): - """ - Get the next non-empty, non-comment line in the string. + self._lines.append((stripped_line, indent, linenum + 1)) - @param file: File like object. - @return: (line, stripped_line, indent), where indent is the line's - indent level or -1 if no line is available. - """ - try: - if self.line_index < len(self.lines): - return self.lines[self.line_index] - else: - return (None, None, -1) - finally: - self.line_index += 1 - - def tell(self): - """ - Return the current line index. - """ - return self.line_index - - - def seek(self, index): - """ - Set the current line index. + def get_next_line(self, prev_indent): """ - self.line_index = index + Get the next non-empty, non-comment line in the string, whose + indentation level is higher than prev_indent. - def raise_error(self, msg): - """Raise an error related to the last line returned by get_next_line() + @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.line_index == 0: # nothing was read. shouldn't happen, but... - line_id = 'BEGIN' - elif self.line_index >= len(self.lines): # past EOF - line_id = 'EOF' - else: - # line_index is the _next_ line. get the previous one - line_id = str(self.real_number[self.line_index-1]) - raise error.AutotestError("%s:%s: %s" % (self.filename, line_id, msg)) - - -# Array structure: -# ---------------- -# The first 4 elements contain the indices of the 4 segments. -# a[0] -- Index of beginning of 'name' segment (always 4). -# a[1] -- Index of beginning of 'shortname' segment. -# a[2] -- Index of beginning of 'depend' segment. -# a[3] -- Index of beginning of 'content' segment. -# The next elements in the array comprise the aforementioned segments: -# The 'name' segment begins with a[a[0]] and ends with a[a[1]-1]. -# The 'shortname' segment begins with a[a[1]] and ends with a[a[2]-1]. -# The 'depend' segment begins with a[a[2]] and ends with a[a[3]-1]. -# The 'content' segment begins with a[a[3]] and ends at the end of the array. - -# The following functions append/prepend to various segments of an array. - -def _array_append_to_name_shortname_depend(a, name, depend): - a.insert(a[1], name) - a.insert(a[2] + 1, name) - a.insert(a[3] + 2, depend) - a[1] += 1 - a[2] += 2 - a[3] += 3 - - -def _array_prepend_to_name_shortname_depend(a, name, depend): - a[1] += 1 - a[2] += 2 - a[3] += 3 - a.insert(a[0], name) - a.insert(a[1], name) - a.insert(a[2], depend) - + if self._line_index >= len(self._lines): + return None, -1, -1 + line, indent, linenum = self._lines[self._line_index] + if indent <= prev_indent: + return None, -1, -1 + self._line_index += 1 + return line, indent, linenum -def _array_append_to_name_depend(a, name, depend): - a.insert(a[1], name) - a.insert(a[3] + 1, depend) - a[1] += 1 - a[2] += 1 - a[3] += 2 - -def _array_prepend_to_name_depend(a, name, depend): - a[1] += 1 - a[2] += 1 - a[3] += 2 - a.insert(a[0], name) - a.insert(a[2], depend) - - -def _array_append_to_content(a, content): - a.append(content) - - -def _array_get_name(a, object_cache): - """ - Return the name of a dictionary represented by a given array. - - @param a: Array representing a dictionary. - @param object_cache: A list of strings referenced by elements in the array. +class FileReader(StrReader): """ - return ".".join([object_cache[i] for i in a[a[0]:a[1]]]) - - -def _array_get_all(a, object_cache): + Preprocess an input file for easy reading. """ - Return a 4-tuple containing all the data stored in a given array, in a - format that is easy to turn into an actual dictionary. + def __init__(self, filename): + """ + Initialize the reader. - @param a: Array representing a dictionary. - @param object_cache: A list of strings referenced by elements in the array. - @return: A 4-tuple: (name, shortname, depend, content), in which all - members are strings except depend which is a list of strings. - """ - name = ".".join([object_cache[i] for i in a[a[0]:a[1]]]) - shortname = ".".join([object_cache[i] for i in a[a[1]:a[2]]]) - content = "".join([object_cache[i] for i in a[a[3]:]]) - depend = [] - prefix = "" - for n, d in zip(a[a[0]:a[1]], a[a[2]:a[3]]): - for dep in object_cache[d].split(): - depend.append(prefix + dep) - prefix += object_cache[n] + "." - return name, shortname, depend, content + @parse filename: The name of the input file. + """ + StrReader.__init__(self, open(filename).read()) + self.filename = filename if __name__ == "__main__": - parser = optparse.OptionParser("usage: %prog [options] [filename]") - parser.add_option('--verbose', dest="debug", action='store_true', - help='include debug messages in console output') + parser = optparse.OptionParser("usage: %prog [options] <filename>") + 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", + help="show full dict names instead of short names") + parser.add_option("-c", "--contents", dest="contents", action="store_true", + help="show dict contents") options, args = parser.parse_args() - debug = options.debug - if args: - filenames = args - else: - filenames = [os.path.join(os.path.dirname(sys.argv[0]), "tests.cfg")] - - # Here we configure the stand alone program to use the autotest - # logging system. - logging_manager.configure_logging(kvm_utils.KvmLoggingConfig(), - verbose=debug) - cfg = config(debug=debug) - for fn in filenames: - cfg.parse_file(fn) - dicts = cfg.get_generator() - for i, dict in enumerate(dicts): - print "Dictionary #%d:" % (i) - keys = dict.keys() - keys.sort() - for key in keys: - print " %s = %s" % (key, dict[key]) + if not args: + parser.error("filename required") + + c = Parser(args[0], debug=options.debug) + for i, d in enumerate(c.get_dicts()): + if options.fullname: + print "dict %4d: %s" % (i + 1, d["name"]) + else: + print "dict %4d: %s" % (i + 1, d["shortname"]) + if options.contents: + keys = d.keys() + keys.sort() + for key in keys: + print " %s = %s" % (key, d[key]) diff --git a/client/tests/kvm/kvm_scheduler.py b/client/tests/kvm/kvm_scheduler.py index 95282e4..b96bb32 100644 --- a/client/tests/kvm/kvm_scheduler.py +++ b/client/tests/kvm/kvm_scheduler.py @@ -63,7 +63,6 @@ class scheduler: test_index = int(cmd[1]) test = self.tests[test_index].copy() test.update(self_dict) - test = kvm_utils.get_sub_pool(test, index, self.num_workers) test_iterations = int(test.get("iterations", 1)) status = run_test_func("kvm", params=test, tag=test.get("shortname"), @@ -129,7 +128,7 @@ class scheduler: # If the test failed, mark all dependent tests as "failed" too if not status: for i, other_test in enumerate(self.tests): - for dep in other_test.get("depend", []): + for dep in other_test.get("dep", []): if dep in test["name"]: test_status[i] = "fail" @@ -154,7 +153,7 @@ class scheduler: continue # Make sure the test's dependencies are satisfied dependencies_satisfied = True - for dep in test["depend"]: + for dep in test["dep"]: dependencies = [j for j, t in enumerate(self.tests) if dep in t["name"]] bad_status_deps = [j for j in dependencies @@ -200,14 +199,14 @@ class scheduler: used_mem[worker] = test_used_mem # Assign all related tests to this worker for j, other_test in enumerate(self.tests): - for other_dep in other_test["depend"]: + for other_dep in other_test["dep"]: # All tests that depend on this test if other_dep in test["name"]: test_worker[j] = worker break # ... and all tests that share a dependency # with this test - for dep in test["depend"]: + for dep in test["dep"]: if dep in other_dep or other_dep in dep: test_worker[j] = worker break diff --git a/client/tests/kvm/kvm_utils.py b/client/tests/kvm/kvm_utils.py index 44ebb88..9e25a0a 100644 --- a/client/tests/kvm/kvm_utils.py +++ b/client/tests/kvm/kvm_utils.py @@ -1101,7 +1101,7 @@ def run_tests(test_list, job): if dict.get("skip") == "yes": continue dependencies_satisfied = True - for dep in dict.get("depend"): + for dep in dict.get("dep"): for test_name in status_dict.keys(): if not dep in test_name: continue diff --git a/client/tests/kvm/tests.cfg.sample b/client/tests/kvm/tests.cfg.sample index bde7aba..4b3b965 100644 --- a/client/tests/kvm/tests.cfg.sample +++ b/client/tests/kvm/tests.cfg.sample @@ -18,10 +18,9 @@ include cdkeys.cfg image_name(_.*)? ?<= /tmp/kvm_autotest_root/images/ cdrom(_.*)? ?<= /tmp/kvm_autotest_root/ floppy ?<= /tmp/kvm_autotest_root/ -Linux: - unattended_install: - kernel ?<= /tmp/kvm_autotest_root/ - initrd ?<= /tmp/kvm_autotest_root/ +Linux..unattended_install: + kernel ?<= /tmp/kvm_autotest_root/ + initrd ?<= /tmp/kvm_autotest_root/ # Here are the test sets variants. The variant 'qemu_kvm_windows_quick' is # fully commented, the following ones have comments only on noteworthy points @@ -49,7 +48,7 @@ variants: # Operating system choice only Win7.64 # Subtest choice. You can modify that line to add more subtests - only unattended_install.cdrom boot shutdown + only unattended_install.cdrom, boot, shutdown # Runs qemu, f14 64 bit guest OS, install, boot, shutdown - @qemu_f14_quick: @@ -65,7 +64,7 @@ variants: only no_pci_assignable only smallpages only Fedora.14.64 - only unattended_install.cdrom boot shutdown + only unattended_install.cdrom, boot, shutdown # qemu needs -enable-kvm on the cmdline extra_params += ' -enable-kvm' @@ -81,7 +80,7 @@ variants: only no_pci_assignable only smallpages only Fedora.14.64 - only unattended_install.cdrom boot shutdown + only unattended_install.cdrom, boot, shutdown # You may provide information about the DTM server for WHQL tests here: #whql: diff --git a/client/tests/kvm/tests_base.cfg.sample b/client/tests/kvm/tests_base.cfg.sample index 80362db..e65bed2 100644 --- a/client/tests/kvm/tests_base.cfg.sample +++ b/client/tests/kvm/tests_base.cfg.sample @@ -1722,8 +1722,8 @@ variants: # Windows section - @Windows: - no autotest linux_s3 vlan ioquit unattended_install.(url|nfs|remote_ks) - no jumbo nicdriver_unload nic_promisc multicast mac_change ethtool clock_getres + no autotest, linux_s3, vlan, ioquit, unattended_install.url, unattended_install.nfs, unattended_install.remote_ks + no jumbo, nicdriver_unload, nic_promisc, multicast, mac_change, ethtool, clock_getres shutdown_command = shutdown /s /f /t 0 reboot_command = shutdown /r /f /t 0 @@ -1747,7 +1747,7 @@ variants: mem_chk_cmd = wmic memphysical mem_chk_cur_cmd = wmic memphysical - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: timeout = 7200 finish_program = deps/finish.exe cdroms += " winutils" @@ -1857,7 +1857,7 @@ variants: steps = WinXP-32.steps setup: steps = WinXP-32-rss.steps - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/WindowsXP-sp2-vlk.iso md5sum_cd1 = 743450644b1d9fe97b3cf379e22dceb0 md5sum_1m_cd1 = b473bf75af2d1269fec8958cf0202bfd @@ -1890,7 +1890,7 @@ variants: steps = WinXP-64.steps setup: steps = WinXP-64-rss.steps - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/WindowsXP-64.iso md5sum_cd1 = 8d3f007ec9c2060cec8a50ee7d7dc512 md5sum_1m_cd1 = e812363ff427effc512b7801ee70e513 @@ -1928,7 +1928,7 @@ variants: steps = Win2003-32.steps setup: steps = Win2003-32-rss.steps - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/Windows2003_r2_VLK.iso md5sum_cd1 = 03e921e9b4214773c21a39f5c3f42ef7 md5sum_1m_cd1 = 37c2fdec15ac4ec16aa10fdfdb338aa3 @@ -1960,7 +1960,7 @@ variants: steps = Win2003-64.steps setup: steps = Win2003-64-rss.steps - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/Windows2003-x64.iso md5sum_cd1 = 5703f87c9fd77d28c05ffadd3354dbbd md5sum_1m_cd1 = 439393c384116aa09e08a0ad047dcea8 @@ -2008,7 +2008,7 @@ variants: steps = Win-Vista-32.steps setup: steps = WinVista-32-rss.steps - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/WindowsVista-32.iso md5sum_cd1 = 1008f323d5170c8e614e52ccb85c0491 md5sum_1m_cd1 = c724e9695da483bc0fd59e426eaefc72 @@ -2025,7 +2025,7 @@ variants: - sp2: image_name += -sp2-32 - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/en_windows_vista_with_sp2_x86_dvd_342266.iso md5sum_cd1 = 19ca90a425667812977bab6f4ce24175 md5sum_1m_cd1 = 89c15020e0e6125be19acf7a2e5dc614 @@ -2059,7 +2059,7 @@ variants: steps = Win-Vista-64.steps setup: steps = WinVista-64-rss.steps - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/WindowsVista-64.iso md5sum_cd1 = 11e2010d857fffc47813295e6be6d58d md5sum_1m_cd1 = 0947bcd5390546139e25f25217d6f165 @@ -2076,7 +2076,7 @@ variants: - sp2: image_name += -sp2-64 - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/en_windows_vista_sp2_x64_dvd_342267.iso md5sum_cd1 = a1c024d7abaf34bac3368e88efbc2574 md5sum_1m_cd1 = 3d84911a80f3df71d1026f7adedc2181 @@ -2112,7 +2112,7 @@ variants: steps = Win2008-32.steps setup: steps = Win2008-32-rss.steps - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/Windows2008-x86.iso md5sum=0bfca49f0164de0a8eba236ced47007d md5sum_1m=07d7f5006393f74dc76e6e2e943e2440 @@ -2127,7 +2127,7 @@ variants: - sp2: image_name += -sp2-32 - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/en_windows_server_2008_datacenter_enterprise_standard_sp2_x86_dvd_342333.iso md5sum_cd1 = b9201aeb6eef04a3c573d036a8780bdf md5sum_1m_cd1 = b7a9d42e55ea1e85105a3a6ad4da8e04 @@ -2156,7 +2156,7 @@ variants: passwd = 1q2w3eP setup: steps = Win2008-64-rss.steps - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/Windows2008-x64.iso md5sum=27c58cdb3d620f28c36333a5552f271c md5sum_1m=efdcc11d485a1ef9afa739cb8e0ca766 @@ -2171,7 +2171,7 @@ variants: - sp2: image_name += -sp2-64 - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/en_windows_server_2008_datacenter_enterprise_standard_sp2_x64_dvd_342336.iso md5sum_cd1 = e94943ef484035b3288d8db69599a6b5 md5sum_1m_cd1 = ee55506823d0efffb5532ddd88a8e47b @@ -2188,7 +2188,7 @@ variants: - r2: image_name += -r2-64 - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/en_windows_server_2008_r2_standard_enterprise_datacenter_and_web_x64_dvd_x15-59754.iso md5sum_cd1 = 0207ef392c60efdda92071b0559ca0f9 md5sum_1m_cd1 = a5a22ce25008bd7109f6d830d627e3ed @@ -2216,7 +2216,7 @@ variants: variants: - 32: image_name += -32 - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/en_windows_7_ultimate_x86_dvd_x15-65921.iso md5sum_cd1 = d0b8b407e8a3d4b75ee9c10147266b89 md5sum_1m_cd1 = 2b0c2c22b1ae95065db08686bf83af93 @@ -2249,7 +2249,7 @@ variants: steps = Win7-64.steps setup: steps = Win7-64-rss.steps - unattended_install.cdrom|whql.support_vm_install: + unattended_install.cdrom, whql.support_vm_install: cdrom_cd1 = isos/windows/en_windows_7_ultimate_x64_dvd_x15-65922.iso md5sum_cd1 = f43d22e4fb07bf617d573acd8785c028 md5sum_1m_cd1 = b44d8cf99dbed2a5cb02765db8dfd48f @@ -2329,7 +2329,7 @@ variants: md5sum_cd1 = 9fae22f2666369968a76ef59e9a81ced -whql.support_vm_install|whql.client_install.support_vm: +whql.support_vm_install, whql.client_install.support_vm: image_name += -supportvm @@ -2352,7 +2352,7 @@ variants: drive_format=virtio -virtio_net|virtio_blk|e1000|balloon_check: +virtio_net, virtio_blk, e1000, balloon_check: only Fedora.11 Fedora.12 Fedora.13 Fedora.14 RHEL.5 RHEL.6 OpenSUSE.11 SLES.11 Ubuntu-8.10-server # only WinXP Win2003 Win2008 WinVista Win7 Fedora.11 Fedora.12 Fedora.13 Fedora.14 RHEL.5 RHEL.6 OpenSUSE.11 SLES.11 Ubuntu-8.10-server @@ -2365,15 +2365,9 @@ variants: check_image = yes - vmdk: no ioquit - only Fedora Ubuntu Windows - only smp2 - only rtl8139 image_format = vmdk - raw: no ioquit - only Fedora Ubuntu Windows - only smp2 - only rtl8139 image_format = raw -- 1.7.3.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