@@ -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)
-
@@ -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')
@@ -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])
@@ -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
@@ -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
@@ -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:
@@ -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