diff mbox

[virt-test,6/7] virt: Adds possibility filter defaults variant from variants

Message ID 1364577250-2637-7-git-send-email-jzupka@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jiri Zupka March 29, 2013, 5:14 p.m. UTC
If default variant is not filtered by only or no filters then
only default variant is chosen. This behavior was used for optimizing
of speed of Cartesian config.
If variants don't have default variant then everything works as usual.
Default variant must be in variants with with_default exactly one times.
The default variant could be filtered by only, no filter. If default
variant is filtered from variants then variants works same as usual variants with
default variant.

For calling Cartesian config from command line is used option -d/--defaults:
   ../virttest/cartesian_config.py -d cfg/cc.cfg

For calling Cartesian config from python:
   c = Parser(args[0], defaults=options.defaults, debug=options.debug)

*********  example:
variants name=tests:
  - wait:
       run = "wait"
       variants:
         - long:
            time = short_time
         - short: long
            time = logn_time
  - test2:
       run = "test1"

variants name=virt_system, with_default:
  - @linux:
  - windows:

variants name=host_os, with_default:
  - linux:
       image = linux
       variants with_default:
            - ubuntu:
            - @fedora:
  - windows:
       image = windows
       variants:
            - @XP:
            - WIN7:

only host_os>windows

In this case is chosen from host_os variants windows variant.
host_os>windows was choosen because default variant linux was filtered.
Next step is select one variant from guest_os. There will be chosen only
default variant linux because not filtered and virt_system variant is
with with_default. There is no default variant in tests variants because
that all of tests will be chosen.

********  output:
dict    1:  host_os>windows.tests>wait.long
    dep = []
    host_os = windows
    image = windows
    name = host_os>windows.XP.virt_system>linux.tests>wait.long
    run = wait
    shortname = host_os>windows.tests>wait.long
    tests = wait
    time = short_time
    virt_system = linux
dict    2:  host_os>windows.tests>wait.short
    dep = ['host_os>windows.XP.virt_system>linux.tests>wait.long']
    host_os = windows
    image = windows
    name = host_os>windows.XP.virt_system>linux.tests>wait.short
    run = wait
    shortname = host_os>windows.tests>wait.short
    tests = wait
    time = logn_time
    virt_system = linux
dict    3:  host_os>windows.tests>test2
    dep = []
    host_os = windows
    image = windows
    name = host_os>windows.XP.virt_system>linux.tests>test2
    run = test1
    shortname = host_os>windows.tests>test2
    tests = test2
    virt_system = linux
dict    4:  host_os>windows.WIN7.tests>wait.long
    dep = []
    host_os = windows
    image = windows
    name = host_os>windows.WIN7.virt_system>linux.tests>wait.long
    run = wait
    shortname = host_os>windows.WIN7.tests>wait.long
    tests = wait
    time = short_time
    virt_system = linux
dict    5:  host_os>windows.WIN7.tests>wait.short
    dep = ['host_os>windows.WIN7.virt_system>linux.tests>wait.long']
    host_os = windows
    image = windows
    name = host_os>windows.WIN7.virt_system>linux.tests>wait.short
    run = wait
    shortname = host_os>windows.WIN7.tests>wait.short
    tests = wait
    time = logn_time
    virt_system = linux
dict    6:  host_os>windows.WIN7.tests>test2
    dep = []
    host_os = windows
    image = windows
    name = host_os>windows.WIN7.virt_system>linux.tests>test2
    run = test1
    shortname = host_os>windows.WIN7.tests>test2
    tests = test2
    virt_system = linux

Signed-off-by: Ji?í Župka <jzupka@redhat.com>
---
 virttest/cartesian_config.py | 82 ++++++++++++++++++++++++++++++++++++--------
 1 file changed, 68 insertions(+), 14 deletions(-)

Comments

Eduardo Habkost April 1, 2013, 2:40 p.m. UTC | #1
On Fri, Mar 29, 2013 at 06:14:09PM +0100, Ji?í Župka wrote:
> If default variant is not filtered by only or no filters then
> only default variant is chosen. This behavior was used for optimizing
> of speed of Cartesian config.
> If variants don't have default variant then everything works as usual.
> Default variant must be in variants with with_default exactly one times.
> The default variant could be filtered by only, no filter. If default
> variant is filtered from variants then variants works same as usual variants with
> default variant.
> 
> For calling Cartesian config from command line is used option -d/--defaults:
>    ../virttest/cartesian_config.py -d cfg/cc.cfg
> 
> For calling Cartesian config from python:
>    c = Parser(args[0], defaults=options.defaults, debug=options.debug)

I believe we have allow that to be per-variants-block, not
all-or-nothing.

For example: the default ./run behavior could be to automatically choose
defaults for every variants-block (guest OS, block format, etc), except
for the "subtests" variants-block.

I would like the API to look like this:

   # this would be the current behavior:
   c = Parser(..., expand_all_variants=True)
   # will expand only the "subtest" and "guest_os" variants-block, and
   # automatically choose defaults for every other variants-block:
   c = Parser(..., expand_variants=['subtest', 'guest_os'])
   # For somebody who wants to run the default tests with all Windows
   # versions:
   c = Parser(..., expand_variants=['subtest', 'windows_version'])
   # For somebody who wants to run the default tests with all CPU models,
   # and all guest OSes:
   c = Parser(..., expand_variants=['subtest', 'guest_os', 'cpu_model'])
   # (additional nice-to-have: to allow something like "guest_os.*" to
   # expand a variants-block and all its "sub-variants" (guest OS
   # version, guest OS architecture, etc)

We could also find a way to encode the "expand_variants" instruction
inside the config file syntax, so people could put that information in
their config file. But that can be done later, after we test if the
concept is really working in the Python API and command-line.

> 
> *********  example:
> variants name=tests:
>   - wait:
>        run = "wait"
>        variants:
>          - long:
>             time = short_time
>          - short: long
>             time = logn_time
>   - test2:
>        run = "test1"
> 
> variants name=virt_system, with_default:
>   - @linux:
>   - windows:
> 
> variants name=host_os, with_default:
>   - linux:
>        image = linux
>        variants with_default:
>             - ubuntu:
>             - @fedora:
>   - windows:
>        image = windows
>        variants:
>             - @XP:
>             - WIN7:
> 
> only host_os>windows
> 
> In this case is chosen from host_os variants windows variant.
> host_os>windows was choosen because default variant linux was filtered.
> Next step is select one variant from guest_os. There will be chosen only
> default variant linux because not filtered and virt_system variant is
> with with_default. There is no default variant in tests variants because
> that all of tests will be chosen.
> 
> ********  output:
> dict    1:  host_os>windows.tests>wait.long
>     dep = []
>     host_os = windows
>     image = windows
>     name = host_os>windows.XP.virt_system>linux.tests>wait.long
>     run = wait
>     shortname = host_os>windows.tests>wait.long
>     tests = wait
>     time = short_time
>     virt_system = linux
> dict    2:  host_os>windows.tests>wait.short
>     dep = ['host_os>windows.XP.virt_system>linux.tests>wait.long']
>     host_os = windows
>     image = windows
>     name = host_os>windows.XP.virt_system>linux.tests>wait.short
>     run = wait
>     shortname = host_os>windows.tests>wait.short
>     tests = wait
>     time = logn_time
>     virt_system = linux
> dict    3:  host_os>windows.tests>test2
>     dep = []
>     host_os = windows
>     image = windows
>     name = host_os>windows.XP.virt_system>linux.tests>test2
>     run = test1
>     shortname = host_os>windows.tests>test2
>     tests = test2
>     virt_system = linux
> dict    4:  host_os>windows.WIN7.tests>wait.long
>     dep = []
>     host_os = windows
>     image = windows
>     name = host_os>windows.WIN7.virt_system>linux.tests>wait.long
>     run = wait
>     shortname = host_os>windows.WIN7.tests>wait.long
>     tests = wait
>     time = short_time
>     virt_system = linux
> dict    5:  host_os>windows.WIN7.tests>wait.short
>     dep = ['host_os>windows.WIN7.virt_system>linux.tests>wait.long']
>     host_os = windows
>     image = windows
>     name = host_os>windows.WIN7.virt_system>linux.tests>wait.short
>     run = wait
>     shortname = host_os>windows.WIN7.tests>wait.short
>     tests = wait
>     time = logn_time
>     virt_system = linux
> dict    6:  host_os>windows.WIN7.tests>test2
>     dep = []
>     host_os = windows
>     image = windows
>     name = host_os>windows.WIN7.virt_system>linux.tests>test2
>     run = test1
>     shortname = host_os>windows.WIN7.tests>test2
>     tests = test2
>     virt_system = linux
> 
> Signed-off-by: Ji?í Župka <jzupka@redhat.com>
> ---
>  virttest/cartesian_config.py | 82 ++++++++++++++++++++++++++++++++++++--------
>  1 file changed, 68 insertions(+), 14 deletions(-)
> 
> diff --git a/virttest/cartesian_config.py b/virttest/cartesian_config.py
> index 04ed2b5..6cd0e88 100755
> --- a/virttest/cartesian_config.py
> +++ b/virttest/cartesian_config.py
> @@ -131,6 +131,10 @@ class ParserError:
>              return "%s (%s:%s)" % (self.msg, self.filename, self.linenum)
>  
>  
> +class MissingDefault:
> +    pass
> +
> +
>  class MissingIncludeError:
>      def __init__(self, line, filename, linenum):
>          self.line = line
> @@ -225,6 +229,7 @@ class Node(object):
>          self.labels = set()
>          self.append_to_shortname = False
>          self.failed_cases = collections.deque()
> +        self.default = False
>  
>  
>      def dump(self, indent, recurse=False):
> @@ -407,14 +412,17 @@ class Parser(object):
>  
>      @see: https://github.com/autotest/autotest/wiki/KVMAutotest-CartesianConfigParametersIntro
>      """
> -    def __init__(self, filename=None, debug=False):
> +    def __init__(self, filename=None, defaults=False, debug=False):
>          """
>          Initialize the parser and optionally parse a file.
>  
>          @param filename: Path of the file to parse.
> +        @param defaults: If True adds only defaults variant from variants
> +                         if there is some.
>          @param debug: Whether to turn on debugging output.
>          """
>          self.node = Node()
> +        self.defaults = defaults
>          self.debug = debug
>          if filename:
>              self.parse_file(filename)
> @@ -611,10 +619,18 @@ class Parser(object):
>  
>          # 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
> +        if self.defaults:
> +            for n in node.children:
> +                for d in self.get_dicts(n, ctx, new_content, shortname, dep):
> +                    count += 1
> +                    yield d
> +                if n.default and count:
> +                    break
> +        else:
> +            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")
> @@ -656,7 +672,8 @@ class Parser(object):
>          print s % args
>  
>  
> -    def _parse_variants(self, cr, node, prev_indent=-1, var_name=None):
> +    def _parse_variants(self, cr, node, prev_indent=-1, var_name=None,
> +                        with_default=False):
>          """
>          Read and parse lines from a FileReader object until a line with an
>          indent level lower than or equal to prev_indent is encountered.
> @@ -665,8 +682,10 @@ class Parser(object):
>          @param node: A node to operate on.
>          @param prev_indent: The indent level of the "parent" block.
>          @param var_name: Variants name
> +        @param with_default: Variants take only default variant.
>          @return: A node object.
>          """
> +        already_default = False
>          node4 = Node()
>  
>          while True:
> @@ -694,7 +713,9 @@ class Parser(object):
>              node2.labels = node.labels
>  
>              node3 = self._parse(cr, node2, prev_indent=indent)
> -            node3.name = [Label(var_name, n) for n in name.lstrip("@").split(".")]
> +            is_default = name.startswith("@")
> +            name = name.lstrip("@")
> +            node3.name = [Label(var_name, n) for n in name.split(".")]
>              node3.dep = [Label(var_name, d) for d in dep.replace(",", " ").split()]
>  
>              if var_name:
> @@ -702,12 +723,33 @@ class Parser(object):
>                  op_match = _ops_exp.search(l)
>                  node3.content += [(cr.filename, linenum, Op(l, op_match))]
>  
> -            is_default = name.startswith("@")
> -
> -            node4.children += [node3]
> +            node3.append_to_shortname = not is_default
> +
> +            if with_default and self.defaults:
> +                """
> +                Relevant only if defaults is True and
> +                variants is with default.
> +                """
> +                if is_default:
> +                    if not already_default:
> +                        node3.default = True
> +                        already_default = True
> +                    else:
> +                        raise MissingDefault
> +                if node3.default:
> +                    # Move default variant in front of rest of all variants.
> +                    # Speed optimization.
> +                    node4.children.insert(0, node3)
> +                else:
> +                    node4.children += [node3]
> +            else:
> +                node4.children += [node3]
>              node4.labels.update(node3.labels)
>              node4.labels.update(node3.name)
>  
> +        if with_default and not already_default:
> +            raise MissingDefault
> +
>          return node4
>  
>  
> @@ -751,6 +793,7 @@ class Parser(object):
>                                  char in "._-=,"):
>                              raise ParserError("Illegal characters in variants",
>                                                line, cr.filename, linenum)
> +                with_default = False
>                  var_name = None
>                  if name:
>                      block = name.split(",")
> @@ -761,12 +804,20 @@ class Parser(object):
>                                  raise ParserError("Missing name of variants",
>                                                    line, cr.filename, linenum)
>                              var_name = oper[1].strip()
> +                        elif "with_default" in oper[0]:
> +                            with_default = True
>                          else:
>                              raise ParserError("Ilegal variants param",
>                                                 line, cr.filename, linenum)
> -                node = self._parse_variants(cr, node, prev_indent=indent,
> -                                            var_name=var_name)
> -                                            var_name=name)
> +                try:
> +                    node = self._parse_variants(cr, node, prev_indent=indent,
> +                                                var_name=var_name,
> +                                                with_default=with_default)
> +                except MissingDefault:
> +                    raise ParserError("There must be exactly one default "
> +                                      "variant in variants with param "
> +                                      "with_default.",
> +                                      line, cr.filename, linenum)
>                  continue
>  
>              # Parse 'include' statements
> @@ -1040,12 +1091,15 @@ if __name__ == "__main__":
>                        help="show dict contents")
>      parser.add_option("-r", "--repr", dest="repr_mode", action="store_true",
>                        help="Output parsing results Python format")
> +    parser.add_option("-d", "--defaults", dest="defaults", action="store_true",
> +                      help="use only default variant of variants if there"
> +                           " is some")
>  
>      options, args = parser.parse_args()
>      if not args:
>          parser.error("filename required")
>  
> -    c = Parser(args[0], debug=options.debug)
> +    c = Parser(args[0], defaults=options.defaults, debug=options.debug)
>      for s in args[1:]:
>          c.parse_string(s)
>  
> -- 
> 1.8.1.4
>
diff mbox

Patch

diff --git a/virttest/cartesian_config.py b/virttest/cartesian_config.py
index 04ed2b5..6cd0e88 100755
--- a/virttest/cartesian_config.py
+++ b/virttest/cartesian_config.py
@@ -131,6 +131,10 @@  class ParserError:
             return "%s (%s:%s)" % (self.msg, self.filename, self.linenum)
 
 
+class MissingDefault:
+    pass
+
+
 class MissingIncludeError:
     def __init__(self, line, filename, linenum):
         self.line = line
@@ -225,6 +229,7 @@  class Node(object):
         self.labels = set()
         self.append_to_shortname = False
         self.failed_cases = collections.deque()
+        self.default = False
 
 
     def dump(self, indent, recurse=False):
@@ -407,14 +412,17 @@  class Parser(object):
 
     @see: https://github.com/autotest/autotest/wiki/KVMAutotest-CartesianConfigParametersIntro
     """
-    def __init__(self, filename=None, debug=False):
+    def __init__(self, filename=None, defaults=False, debug=False):
         """
         Initialize the parser and optionally parse a file.
 
         @param filename: Path of the file to parse.
+        @param defaults: If True adds only defaults variant from variants
+                         if there is some.
         @param debug: Whether to turn on debugging output.
         """
         self.node = Node()
+        self.defaults = defaults
         self.debug = debug
         if filename:
             self.parse_file(filename)
@@ -611,10 +619,18 @@  class Parser(object):
 
         # 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
+        if self.defaults:
+            for n in node.children:
+                for d in self.get_dicts(n, ctx, new_content, shortname, dep):
+                    count += 1
+                    yield d
+                if n.default and count:
+                    break
+        else:
+            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")
@@ -656,7 +672,8 @@  class Parser(object):
         print s % args
 
 
-    def _parse_variants(self, cr, node, prev_indent=-1, var_name=None):
+    def _parse_variants(self, cr, node, prev_indent=-1, var_name=None,
+                        with_default=False):
         """
         Read and parse lines from a FileReader object until a line with an
         indent level lower than or equal to prev_indent is encountered.
@@ -665,8 +682,10 @@  class Parser(object):
         @param node: A node to operate on.
         @param prev_indent: The indent level of the "parent" block.
         @param var_name: Variants name
+        @param with_default: Variants take only default variant.
         @return: A node object.
         """
+        already_default = False
         node4 = Node()
 
         while True:
@@ -694,7 +713,9 @@  class Parser(object):
             node2.labels = node.labels
 
             node3 = self._parse(cr, node2, prev_indent=indent)
-            node3.name = [Label(var_name, n) for n in name.lstrip("@").split(".")]
+            is_default = name.startswith("@")
+            name = name.lstrip("@")
+            node3.name = [Label(var_name, n) for n in name.split(".")]
             node3.dep = [Label(var_name, d) for d in dep.replace(",", " ").split()]
 
             if var_name:
@@ -702,12 +723,33 @@  class Parser(object):
                 op_match = _ops_exp.search(l)
                 node3.content += [(cr.filename, linenum, Op(l, op_match))]
 
-            is_default = name.startswith("@")
-
-            node4.children += [node3]
+            node3.append_to_shortname = not is_default
+
+            if with_default and self.defaults:
+                """
+                Relevant only if defaults is True and
+                variants is with default.
+                """
+                if is_default:
+                    if not already_default:
+                        node3.default = True
+                        already_default = True
+                    else:
+                        raise MissingDefault
+                if node3.default:
+                    # Move default variant in front of rest of all variants.
+                    # Speed optimization.
+                    node4.children.insert(0, node3)
+                else:
+                    node4.children += [node3]
+            else:
+                node4.children += [node3]
             node4.labels.update(node3.labels)
             node4.labels.update(node3.name)
 
+        if with_default and not already_default:
+            raise MissingDefault
+
         return node4
 
 
@@ -751,6 +793,7 @@  class Parser(object):
                                 char in "._-=,"):
                             raise ParserError("Illegal characters in variants",
                                               line, cr.filename, linenum)
+                with_default = False
                 var_name = None
                 if name:
                     block = name.split(",")
@@ -761,12 +804,20 @@  class Parser(object):
                                 raise ParserError("Missing name of variants",
                                                   line, cr.filename, linenum)
                             var_name = oper[1].strip()
+                        elif "with_default" in oper[0]:
+                            with_default = True
                         else:
                             raise ParserError("Ilegal variants param",
                                                line, cr.filename, linenum)
-                node = self._parse_variants(cr, node, prev_indent=indent,
-                                            var_name=var_name)
-                                            var_name=name)
+                try:
+                    node = self._parse_variants(cr, node, prev_indent=indent,
+                                                var_name=var_name,
+                                                with_default=with_default)
+                except MissingDefault:
+                    raise ParserError("There must be exactly one default "
+                                      "variant in variants with param "
+                                      "with_default.",
+                                      line, cr.filename, linenum)
                 continue
 
             # Parse 'include' statements
@@ -1040,12 +1091,15 @@  if __name__ == "__main__":
                       help="show dict contents")
     parser.add_option("-r", "--repr", dest="repr_mode", action="store_true",
                       help="Output parsing results Python format")
+    parser.add_option("-d", "--defaults", dest="defaults", action="store_true",
+                      help="use only default variant of variants if there"
+                           " is some")
 
     options, args = parser.parse_args()
     if not args:
         parser.error("filename required")
 
-    c = Parser(args[0], debug=options.debug)
+    c = Parser(args[0], defaults=options.defaults, debug=options.debug)
     for s in args[1:]:
         c.parse_string(s)