diff --git a/avocado/multiplexer.py b/avocado/multiplexer.py index 6efc5cf7cd6f37bd34f68a4b344e0cbf67a1e109..471d2031d8aa8fc9e8463b5d4a350153ead84685 100644 --- a/avocado/multiplexer.py +++ b/avocado/multiplexer.py @@ -253,63 +253,14 @@ class AvocadoParams(object): logging.getLogger("avocado.test").warn(msg) return self.get(attr) - def get(self, *args, **kwargs): - """ - Retrieve params - - Old API: ``params.get(key, failobj=None)`` (any matching param) - New API: ``params.get(key, path=$MUX_ENTRY/*, default=None)`` - - As old and new API overlaps, you must use all 3 arguments or - explicitely use key argument "path" or "default". - """ - def compatibility(args, kwargs): - """ - Be 100% compatible with old API while allow _SOME OF_ the new APIs - calls: - OLD: get(key), get(key, default), get(key, failobj=default) - NEW: get(key, path, default), get(key, path=path), - get(key, default=default) - - :warning: We are unable to distinguish old get(key, default) vs. - new get(key, path), therefor if you want to use the new - API you must specify path/default using named arguments - or supply all 3 arguments: - get(key, path, default), get(key, path=path), - get(key, default=default). - This will be removed in final version. - """ - if len(args) < 1: - raise TypeError("Incorrect arguments: params.get(%s, %s)" - % (args, kwargs)) - elif 'failobj' in kwargs: - return [args[0], '/*', kwargs['failobj']] # Old API - elif len(args) > 2 or 'default' in kwargs or 'path' in kwargs: - try: - if 'default' in kwargs: - default = kwargs['default'] - elif len(args) > 2: - default = args[2] - else: - default = None - if 'path' in kwargs: - path = kwargs['path'] - elif len(args) > 1: - path = args[1] - else: - path = None - key = args[0] - return [key, path, default] - except IndexError: - raise TypeError("Incorrect arguments: params.get(%s, %s)" - % (args, kwargs)) - else: # Old API - if len(args) == 1: - return [args[0], '/*', None] - else: - return [args[0], '/*', args[1]] - - key, path, default = compatibility(args, kwargs) + def get(self, key, path=None, default=None): + """ + Retrieve value associated with key from params + :param key: Key you're looking for + :param path: namespace ['*'] + :param default: default value when not found + :raise KeyError: In case of multiple different values (params clash) + """ if path is None: # default path is any relative path path = '*' try: @@ -368,10 +319,6 @@ class AvocadoParam(object): """ This is a single slice params. It can contain multiple leaves and tries to find matching results. - Currently it doesn't care about params origin, it requires single result - or failure. In future it'll get the origin from LeafParam and if it's the - same it'll proceed, otherwise raise exception (as it can't decide which - variable is desired) """ def __init__(self, leaves, name): diff --git a/docs/source/MultiplexConfig.rst b/docs/source/MultiplexConfig.rst index 1eb8fb3e7da2e2491ede49aead9f23c1f34c3753..2064e3bc86961c3e07d4e57635a4193722869c3c 100644 --- a/docs/source/MultiplexConfig.rst +++ b/docs/source/MultiplexConfig.rst @@ -4,197 +4,91 @@ Multiplex Configuration ======================= -Multiplex Configuration is a specialized way of providing lists -of key/value pairs within combination's of various categories, -that will be passed to avocado test as parameters in a dictionary -called ``params``. The format simplifies and condenses complex -multidimensional arrays of test parameters into a flat list. The -combinatorial result can be filtered and adjusted prior to testing. +In order to get a good coverage one always needs to execute the same test +with different parameters or in various environments. Avocado uses the +term ``Multiplexation`` to generate multiple variants of the same test with +different values. To define these variants and values +`YAML `_ files are used. The benefit of using YAML +file is the visible separation of different scopes. Even very advanced setups +are still human readable, unlike traditional sparse, multi-dimensional-matrices +of parameters. + +Let's start with an example (line numbers added at the beginning):: + + 1 hw: + 2 cpu: !mux + 3 intel: + 4 cpu_CFLAGS: '-march=core2' + 5 amd: + 6 cpu_CFLAGS: '-march=athlon64' + 7 arm: + 8 cpu_CFLAGS: '-mabi=apcs-gnu -march=armv8-a -mtune=arm8' + 9 disk: !mux + 10 scsi: + 11 disk_type: 'scsi' + 12 virtio: + 13 disk_type: 'virtio' + 14 distro: !mux + 15 fedora: + 16 init: 'systemd' + 17 mint: + 18 init: 'systemv' + 19 env: !mux + 20 debug: + 21 opt_CFLAGS: '-O0 -g' + 22 prod: + 23 opt_CFLAGS: '-O2' + + +There are couple of key=>value pairs (4,6,8,11,13,...) and there are +named nodes which define scope (1,2,3,5,7,9,...). There are also additional +flags (2, 9, 14, 19) which modifies the behavior. -The parser relies on `YAML `_, a human friendly -markup language. The YAML format allows one to create, manually or -with code automation, multiple configurations for the tests. You can use any -text editor to write YAML files, but choose one that supports syntax -enhancements and basic validation, it saves time! - -Here is how a simple and valid multiplex configuration looks like:: - - # Multiplex config example, file sleep.yaml - short: - sleep_length: 1 - medium: - sleep_length: 60 - long: - sleep_length: 600 - -The key concepts here are ``nodes`` (provides context and scope), ``keys`` (think of variables) and ``values`` (scalar or lists). - -In the next section, we will describe these concepts in more details. - -.. _nodes: Nodes ===== -Nodes servers for two purposes, to name or describe a discrete point of information -and to store in a set of key/values (possibly empty). Basically nodes can contains -other nodes, so will have the parent and child relationship in a tree structure. - -The tree node structure can be obtained by using the command line -``avocado multiplex --tree `` and for previous example, -it looks just like this:: - - avocado multiplex --tree sleep.yaml - Config file tree structure: - - /-short - | - ----|-medium - | - \-long - -It helps if you see the tree structure as a set of paths -separated by ``/``, much like paths in the file system. - -In the example we have being working on, there are only three paths: - -- ``//short`` -- ``//medium`` -- ``//long`` - -The ending nodes (the leafs on the tree) will become part of all lower-level -(i.e. further indented) variant stanzas (see section variants_). -However, the precedence is evaluated in top-down or ``last defined`` order. -In other words, the last parsed has precedence over earlier definitions. - -When you provide multiple files they are processed and merged together using -the common root (`/`). When certain paths overlap (`$file1:/my/path`, -`$file2:/my/path`), we first create the tree of `$file1` and then process -`$file2`. This means all children of `/my/path` of the first file are in -correct order and `$file2` either updates values or appends new children -as next ones. This of course happens recursively so you update valures and add -children of all the nodes beneath. - -During this merge it's also possible to remove nodes using python regular -expressions, which can be useful when extending upstream file using downstream -yaml files. This is done by `!remove_node : $value_name` directive:: - - os: - fedora: - windows: - 3.11: - 95: - os: - !remove_node : windows - windows: - win3.11: - win95: - -Removes the `windows` node from structure. It's different from `filter-out` -as it really removes the node (and all children) from the tree and -it can be replaced by you new structure as shown in the example. It removes -`windows` with all children and then replaces this structure with slightly -modified version. +They define context of the key=>value pairs allowing us to easily identify +for what this values might be used for and also it makes possible to define +multiple values of the same keys with different scope. + +Nodes are organized in parent-child relationship and together they create +a tree. To view this structure use ``avocado multiplex --tree ``:: + + /-intel + | + /cpu-<>--amd + | | + /hw \-arm + | | + | | /-scsi + | \disk-<> + | \-virtio + -| + | /-fedora + |-distro-<> + | \-mint + | + | /-debug + \env-<> + \-prod + +You can see that ``hw`` has 2 children ``cpu`` and ``disk``. All parameters +defined in parent node are inherited to children and extended/overwritten by +their values up to the leaf nodes. The leaf nodes (``intel``, ``amd``, ``arm``, +``scsi``, ...) are the most important as after multiplexation they form the +parameters available in tests. -As `!remove_node` is processed during merge, when you reverse the order, -windows is not removed and you end-up with `/windows/{win3.11,win95,3.11,95}` -nodes. - -Due to yaml nature, it's __mandatory__ to put space between `!remove_node` -and `:`! - -Additionally you can prepend multiple nodes to the given node by using -`!using : $prepended/path`. This is useful when extending complex structure, -for example imagine having distro variants in separate ymal files. In the -end you want to merge them into the `/os` node. The main file can be simply:: - - # main.yaml - os: - !include : os/fedora/21.yaml - .... - -And each file can look either like this:: - - # fedora/21.yaml - fedora: - 21: - some: value - -or you can use `!using` which prepends the `fedora/21`:: - - # fedora/21.yaml - !using : /fedora/21 - some: value - -To be precise there is a way to define the structure in the main yaml file:: - - # main.yaml - os: - fedora: - 21: - !include : fedora_21.yaml - -Or use recursive `!include` (slower):: - - # main.yaml - os: - fedora: - !include : os/fedora.yaml - # os/fedora.yaml - 21: - !include : fedora/21.yaml - # os/fedora/21.yaml - some: value - -Due to yaml nature, it's __mandatory__ to put space between `!using` and `:`! - -.. _keys_and_values: Keys and Values =============== -Keys and values are the most basic useful facility provided by the -format. A statement in the form ``: `` sets ```` to -````. - -Values are numbers, strings and lists. Some examples of literal values: - -- Booleans: ``true`` and ``false``. -- Numbers: 123 (integer), 3.1415 (float point) -- Strings: 'This is a string' - -And lists:: - - cflags: - - '-O2' - - '-g' - - '-Wall' - -The list above will become ``['-O2', '-g', '-Wall']`` to Python. In fact, -YAML is compatible to JSON. - -It's also possible to remove key using python's regexp, which can be useful -when extending upstream file using downstream yaml files. This is done by -`!remove_value : $value_name` directive:: - - debug: - CFLAGS: '-O0 -g' - debug: - !remove_value: CFLAGS - -removes the CFLAGS value completely from the debug node. This happens during -the merge and only once. So if you switch the two, CFLAGS would be defined. +Every value other than dict (4,6,8,11) is used as value of the antecedent +node. -Due to yaml nature, it's __mandatory__ to put space between `!remove_value` -and `:`! - -.. _environment: - -Environment -=========== - -The environment is a set of key/values constructed by the moment -we walk the path (beginning from the root) until we reach a specific node. +Each node can define key/value pairs (lines 4,6,8,11,...). Additionally +each children node inherits values of it's parent and the result is called +node ``environment``. Given the node structure bellow:: @@ -223,14 +117,111 @@ The environment created for the nodes ``fedora`` and ``osx`` are: - Node ``//devtools/fedora`` environment ``compiler: 'gcc'``, ``flags: ['-O2', '-Wall']`` - None ``//devtools/osx`` environment ``compiler: 'clang'``, ``flags: ['-O2', '-arch i386', '-arch x86_64']`` -.. _multiple_files: + +Variants +======== + +In the end all leafs are gathered and turned into parameters, more specifically into +``AvocadoParams``:: + + setup: + graphic: + user: "guest" + password: "pass" + text: + user: "root" + password: "123456" + +produces ``[graphic, text]``. In the test code you'll be able to query only +those leaves. Intermediary or root nodes are available. + +The example above generates a single test execution with parameters separated +by path. But the most powerful multiplexer feature is that it can generate +multiple variants. To do that you need to tag a node whose children are +ment to be multiplexed. Effectively it returns only leaves of one child at the +time.In order to generate all possible variants multiplexer creates cartesian +product of all of these variants:: + + cpu: !mux + intel: + amd: + arm: + fmt: !mux + qcow2: + raw: + +Produces 6 variants:: + + /cpu/intel, /fmt/qcow2 + /cpu/intel, /fmt/raw + ... + /cpu/arm, /fmt/raw + +The !mux evaluation is recursive so one variant can expand to multiple +ones:: + + fmt: !mux + qcow: !mux + 2: + 2v3: + raw: + +Results in:: + + /fmt/qcow2/2 + /fmt/qcow2/2v3 + /raw + + +Resolution order +================ + +You can see that only leaves are part of the test parameters. It might happen +that some of these leaves contain different values of the same key. Then +you need to make sure your queries separate them by different paths. When +the path matches multiple results with different origin, an exception is raised +as it's impossible to guess which key was originally intended. + +To avoid these problems it's recommended to use unique names in test parameters if +possible, to avoid the mentioned clashes. It also makes it easier to extend or mix +multiple YAML files for a test. + +For multiplex YAML files that are part of a framework, contain default +configurations, or serve as plugin configurations and other advanced setups it is +possible and commonly desirable to use non-unique names. But always keep those points +in mind and provide sensible paths. + +Multiplexer also supports something called "multiplex entry points" or +"resolution order". By default it's ``/tests/*`` but it can be overridden by +``--mux-entry``, which accepts multiple arguments. What it does it splits +leaves by the provided paths. Each query goes one by one through those +sub-trees and first one to hit the match returns the result. It might not solve +all problems, but it can help to combine existing YAML files with your ones:: + + qa: # large and complex read-only file, content injected into /qa + tests: + timeout: 10 + ... + my_variants: !mux # your YAML file injected into /my_variants + short: + timeout: 1 + long: + timeout: 1000 + +You want to use an existing test which uses ``params.get('timeout', '*')``. Then you +can use ``--mux-entry '/my_variants/*' '/qa/*'`` and it'll first look in your +variants. If no matches are found, then it would proceed to ``/qa/*`` + +Keep in mind that only slices defined in mux-entry are taken into account for +relative paths (the ones starting with ``*``) + Multiple files ============== You can provide multiple files. In such scenario final tree is a combination of the provided files where later nodes with the same name override values of -the precending corresponding node. New nodes are appended as new children:: +the preceding corresponding node. New nodes are appended as new children:: file-1.yaml: debug: @@ -253,8 +244,8 @@ results in:: fast: CFLAGS: '-Ofast' # appended -It's also possilbe to include existing file into other file's node. This -is done by `!include : $path` directive:: +It's also possible to include existing file into another a given node in another +file. This is done by the `!include : $path` directive:: os: fedora: @@ -262,117 +253,161 @@ is done by `!include : $path` directive:: gentoo: !include : gentoo.yaml -Due to yaml nature, it's __mandatory__ to put space between `!include` and `:`! +Due to YAML nature, it's __mandatory__ to put space between `!include` and `:`! -The file location can be either absolute path or relative path to the yaml +The file location can be either absolute path or relative path to the YAML file where the `!include` is called (even when it's nested). Whole file is __merged__ into the node where it's defined. -.. _variants: -Variants -======== +Advanced YAML tags +================== -When tree parsing and filtering is finished, we create set of variants. -Each variant uses one leaf of each sibling group. For example:: +There are additional features related to YAML files. Most of them require values +separated by ``:``. Again, in all such cases it's mandatory to add a white space (`` +``) between the tag and the ``:``, otherwise ``:`` is part of the tag name and the +parsing fails. - cpu: - intel: - amd: - arm: - fmt: - qcow2: - raw: +!include +-------- -Produces 2 groups `[intel, amd, arm]` and `[qcow2, raw]`, which results in -6 variants (all combinations; product of the groups) +Includes other file and injects it into the node it's specified in:: -It's also possible to join current node and its children by `!join` tag:: + my_other_file: + !include : other.yaml - fmt: !join - qcow: - 2: - 2v3: - raw: +The content of ``/my_other_file`` would be parsed from the ``other.yaml``. It's +the hardcoded equivalent of the ``-m $using:$path``. -Without the join this would produce 2 groups `[2, 2v3]` and `[raw]` resulting -in 2 variants `[2, raw]` and `[2v3, raw]`, which is really not useful. -But we said that `fmt` children should join this sibling group -so it results in one group `[qcow/2, qcow/2v3, raw]` resulting in 3 variants -each of different fmt. This is useful when some -of the variants share some common key. These keys are set inside the -parent, for example here `qcow2.0` and `qcow2.2v3` share the same key -`type: qcow2` and `qcow2.2v3` adds `extra_params` into his params:: +Relative paths start from the original file's directory. - fmt: - qcow2: - type: qcow2 - 0: - v3: - extra_params: "compat=1.1" - raw: - type: raw - -Complete example:: - - hw: - cpu: - intel: - amd: - arm: - fmt: !join - qcow: - qcow2: - qcow2v3: - raw: - os: !join - linux: !join - Fedora: - 19: - Gentoo: +!using +------ + +Prepends path to the node it's defined in:: + + !using : /foo + bar: + !using : baz + +``bar`` is put into ``baz`` becoming ``/baz/bar`` and everything is put into +``/foo``. So the final path of ``bar`` is ``/foo/baz/bar``. + +!remove_node +------------ + +Removes node if it existed during the merge. It can be used to extend +incompatible YAML files:: + + os: + fedora: windows: 3.11: + 95: + os: + !remove_node : windows + windows: + win3.11: + win95: + +Removes the `windows` node from structure. It's different from `filter-out` +as it really removes the node (and all children) from the tree and +it can be replaced by you new structure as shown in the example. It removes +`windows` with all children and then replaces this structure with slightly +modified version. + +As `!remove_node` is processed during merge, when you reverse the order, +windows is not removed and you end-up with `/windows/{win3.11,win95,3.11,95}` +nodes. -While preserving names and environment values. Then all combinations are -created resulting into 27 unique variants covering all possible combinations -of given tree:: - - Variant 1: /hw/cpu/intel, /hw/fmt/qcow/qcow2, /os/linux/Fedora/19 - Variant 2: /hw/cpu/intel, /hw/fmt/qcow/qcow2, /os/linux/Gentoo - Variant 3: /hw/cpu/intel, /hw/fmt/qcow/qcow2, /os/windows/3.11 - Variant 4: /hw/cpu/intel, /hw/fmt/qcow/qcow2v3, /os/linux/Fedora/19 - Variant 5: /hw/cpu/intel, /hw/fmt/qcow/qcow2v3, /os/linux/Gentoo - Variant 6: /hw/cpu/intel, /hw/fmt/qcow/qcow2v3, /os/windows/3.11 - Variant 7: /hw/cpu/intel, /hw/fmt/raw, /os/linux/Fedora/19 - Variant 8: /hw/cpu/intel, /hw/fmt/raw, /os/linux/Gentoo - Variant 9: /hw/cpu/intel, /hw/fmt/raw, /os/windows/3.11 - Variant 10: /hw/cpu/amd, /hw/fmt/qcow/qcow2, /os/linux/Fedora/19 - Variant 11: /hw/cpu/amd, /hw/fmt/qcow/qcow2, /os/linux/Gentoo - Variant 12: /hw/cpu/amd, /hw/fmt/qcow/qcow2, /os/windows/3.11 - Variant 13: /hw/cpu/amd, /hw/fmt/qcow/qcow2v3, /os/linux/Fedora/19 - Variant 14: /hw/cpu/amd, /hw/fmt/qcow/qcow2v3, /os/linux/Gentoo - Variant 15: /hw/cpu/amd, /hw/fmt/qcow/qcow2v3, /os/windows/3.11 - Variant 16: /hw/cpu/amd, /hw/fmt/raw, /os/linux/Fedora/19 - Variant 17: /hw/cpu/amd, /hw/fmt/raw, /os/linux/Gentoo - Variant 18: /hw/cpu/amd, /hw/fmt/raw, /os/windows/3.11 - Variant 19: /hw/cpu/arm, /hw/fmt/qcow/qcow2, /os/linux/Fedora/19 - Variant 20: /hw/cpu/arm, /hw/fmt/qcow/qcow2, /os/linux/Gentoo - Variant 21: /hw/cpu/arm, /hw/fmt/qcow/qcow2, /os/windows/3.11 - Variant 22: /hw/cpu/arm, /hw/fmt/qcow/qcow2v3, /os/linux/Fedora/19 - Variant 23: /hw/cpu/arm, /hw/fmt/qcow/qcow2v3, /os/linux/Gentoo - Variant 24: /hw/cpu/arm, /hw/fmt/qcow/qcow2v3, /os/windows/3.11 - Variant 25: /hw/cpu/arm, /hw/fmt/raw, /os/linux/Fedora/19 - Variant 26: /hw/cpu/arm, /hw/fmt/raw, /os/linux/Gentoo - Variant 27: /hw/cpu/arm, /hw/fmt/raw, /os/windows/3.11 - -You can generate this list yourself by executing:: - - avocado multiplex /path/to/multiplex.yaml [-c] - -Note that there's no need to put extensions to a multiplex file, although -doing so helps with organization. The optional -c param is used to provide -the contents of the dictionaries generated, not only their shortnames. - -With Nodes, Keys, Values & Filters, we have most of what you -actually need to construct most multiplex files. +!remove_value +------------- + +It's similar to `!remove_node`_ only with values. + +!mux +---- + +Children of this node will be multiplexed. This means that in first variant +it'll return leaves of the first child, in second the leaves of the second +child, etc. Example is in section `Variants`_ + + +Complete example +================ + +Let's take a second look at the first example:: + + 1 hw: + 2 cpu: !mux + 3 intel: + 4 cpu_CFLAGS: '-march=core2' + 5 amd: + 6 cpu_CFLAGS: '-march=athlon64' + 7 arm: + 8 cpu_CFLAGS: '-mabi=apcs-gnu -march=armv8-a -mtune=arm8' + 9 disk: !mux + 10 scsi: + 11 disk_type: 'scsi' + 12 virtio: + 13 disk_type: 'virtio' + 14 distro: !mux + 15 fedora: + 16 init: 'systemd' + 17 mint: + 18 init: 'systemv' + 19 env: !mux + 20 debug: + 21 opt_CFLAGS: '-O0 -g' + 22 prod: + 23 opt_CFLAGS: '-O2' + +After filters are applied (simply removes non-matching variants), leaves +are gathered and all variants are generated:: + + ./scripts/avocado multiplex examples/mux-environment.yaml + Variants generated: + Variant 1: /hw/cpu/intel, /hw/disk/scsi, /distro/fedora, /env/debug + Variant 2: /hw/cpu/intel, /hw/disk/scsi, /distro/fedora, /env/prod + Variant 3: /hw/cpu/intel, /hw/disk/scsi, /distro/mint, /env/debug + Variant 4: /hw/cpu/intel, /hw/disk/scsi, /distro/mint, /env/prod + Variant 5: /hw/cpu/intel, /hw/disk/virtio, /distro/fedora, /env/debug + Variant 6: /hw/cpu/intel, /hw/disk/virtio, /distro/fedora, /env/prod + Variant 7: /hw/cpu/intel, /hw/disk/virtio, /distro/mint, /env/debug + Variant 8: /hw/cpu/intel, /hw/disk/virtio, /distro/mint, /env/prod + Variant 9: /hw/cpu/amd, /hw/disk/scsi, /distro/fedora, /env/debug + Variant 10: /hw/cpu/amd, /hw/disk/scsi, /distro/fedora, /env/prod + Variant 11: /hw/cpu/amd, /hw/disk/scsi, /distro/mint, /env/debug + Variant 12: /hw/cpu/amd, /hw/disk/scsi, /distro/mint, /env/prod + Variant 13: /hw/cpu/amd, /hw/disk/virtio, /distro/fedora, /env/debug + Variant 14: /hw/cpu/amd, /hw/disk/virtio, /distro/fedora, /env/prod + Variant 15: /hw/cpu/amd, /hw/disk/virtio, /distro/mint, /env/debug + Variant 16: /hw/cpu/amd, /hw/disk/virtio, /distro/mint, /env/prod + Variant 17: /hw/cpu/arm, /hw/disk/scsi, /distro/fedora, /env/debug + Variant 18: /hw/cpu/arm, /hw/disk/scsi, /distro/fedora, /env/prod + Variant 19: /hw/cpu/arm, /hw/disk/scsi, /distro/mint, /env/debug + Variant 20: /hw/cpu/arm, /hw/disk/scsi, /distro/mint, /env/prod + Variant 21: /hw/cpu/arm, /hw/disk/virtio, /distro/fedora, /env/debug + Variant 22: /hw/cpu/arm, /hw/disk/virtio, /distro/fedora, /env/prod + Variant 23: /hw/cpu/arm, /hw/disk/virtio, /distro/mint, /env/debug + Variant 24: /hw/cpu/arm, /hw/disk/virtio, /distro/mint, /env/prod + +Where the first variant contains:: + + /hw/cpu/intel/ => cpu_CFLAGS: -march=core2 + /hw/disk/ => disk_type: scsi + /distro/fedora/ => init: systemd + /env/debug/ => opt_CFLAGS: -O0 -g + +The second one:: + + /hw/cpu/intel/ => cpu_CFLAGS: -march=core2 + /hw/disk/ => disk_type: scsi + /distro/fedora/ => init: systemd + /env/prod/ => opt_CFLAGS: -O2 + +From this example you can see that querying for ``/env/debug`` works only in +the first variant, but returns nothing in the second variant. Keep this in mind +and when you use the ``!mux`` flag always query for the pre-mux path, +``/env/*`` in this example. diff --git a/docs/source/WritingTests.rst b/docs/source/WritingTests.rst index 787711933e727882e399473df92c2006241c7613..4601a2a97409db16eea84aaef7931ac6c7c15d85 100644 --- a/docs/source/WritingTests.rst +++ b/docs/source/WritingTests.rst @@ -110,34 +110,66 @@ to analyze that particular benchmark result). Accessing test parameters ========================= -Each test has a set of parameters that can be accessed through ``self.params.[param-name]``. -Avocado finds and populates ``self.params`` with all parameters you define on a Multiplex -Config file (see :doc:`MultiplexConfig`), in a way that they are available as attributes, -not just dict keys. This has the advantage of reducing the boilerplate code necessary to -access those parameters. As an example, consider the following multiplex file for sleeptest:: - - variants: - - sleeptest: - sleep_length_type = float - variants: - - short: - sleep_length = 0.5 - - medium: - sleep_length = 1 - - long: - sleep_length = 5 - -You may notice some things here: there is one test param to sleeptest, called ``sleep_length``. We could have named it -``length`` really, but I prefer to create a param namespace of sorts here. Then, I defined -``sleep_length_type``, that is used by the config system to convert a value (by default a -:class:`basestring`) to an appropriate value type (in this case, we need to pass a :class:`float` -to :func:`time.sleep` anyway). Note that this is an optional feature, and you can always use -:func:`float` to convert the string value coming from the configuration anyway. - -Another important design detail is that sometimes we might not want to use the config system -at all (for example, when we run an avocado test as a stand alone test). To account for this -case, we have to specify a ``default_params`` dictionary that contains the default values -for when we are not providing config from a multiplex file. +Each test has a set of parameters that can be accessed through +``self.params.get($name, $path=None, $default=None)``. +Avocado finds and populates ``self.params`` with all parameters you define on +a Multiplex Config file (see :doc:`MultiplexConfig`). As an example, consider +the following multiplex file for sleeptest:: + + test: + sleeptest: + type: "builtin" + short: + sleep_length: 0.5 + medium: + sleep_length: 1 + long: + sleep_length: 5 + +In this example 3 variants are executed (see :doc:`MultiplexConfig` for +details). All of them contain variable "type" and "sleep_length". To obtain +current value, you need the name ("sleep_length") and its path. The path +differs for each variant so it's needed to use the most suitable portion +of the path, in this example: "/test/sleeptest/*" or perhaps "sleeptest/*" +might be enough. It depends on how your setups looks like. + +The default value is optional, but always keep in mind to handle them nicely. +Someone might be executing your test with different params or without any +params at all. It should work fine. + +So the complete example on how to access the "sleep_length" would be:: + + self.params.get("sleep_length", "/*/sleeptest/*", 1) + +There is one way to make this even simpler. It's possible to define resolution +order, then for simple queries you can simply omit the path:: + + self.params.get("sleep_length", None, 1) + self.params.get("sleep_length", '*', 1) + self.params.get("sleep_length", default=1) + +One should always try to avoid param clashes (multiple matching keys for given +path with different origin). If it's not possible (eg. when +you use multiple yaml files) you can modify the resolution order by modifying +``--mux-entry``. What it does is it slices the params and iterates through the +paths one by one. When there is a match in the first slice it returns +it without trying the other slices. Although relative queries only match +from ``--mux-entry`` slices. + +There are many ways to use paths to separate clashing params or just to make +more clear what your query for. Usually in tests the usage of '*' is sufficient +and the namespacing is not necessarily, but it helps make advanced usage +clearer and easier to follow. + +When thinking of the path always think about users. It's common to extend +default config with additional variants or combine them with different +ones to generate just the right scenarios they need. People might +simply inject the values elsewhere (eg. `/test/sleeptest` => +`/upstream/test/sleeptest`) or they can merge other clashing file into the +default path, which won't generate clash, but would return their values +instead. Then you need to clarify the path (eg. `'*'` => `sleeptest/*`) + +More details on that are in :doc:`MultiplexConfig` Using a multiplex file ====================== @@ -562,7 +594,7 @@ impact your test grid. You can account for that possibility and set up a :: - $ avocado run sleeptest --multiplex /test:/tmp/sleeptest-example.mplx + $ avocado run sleeptest --multiplex /test:/tmp/sleeptest-example.yaml JOB ID : 6d5a2ff16bb92395100fbc3945b8d253308728c9 JOB LOG : $HOME/avocado/job-results/job-2014-08-12T15.52-6d5a2ff1/job.log JOB HTML : $HOME/avocado/job-results/job-2014-08-12T15.52-6d5a2ff1/html/results.html @@ -583,8 +615,8 @@ impact your test grid. You can account for that possibility and set up a 15:52:51 test L0144 DEBUG| 15:52:51 test L0145 DEBUG| Test log: $HOME/avocado/job-results/job-2014-08-12T15.52-6d5a2ff1/sleeptest.1/test.log 15:52:51 test L0146 DEBUG| Test instance parameters: - 15:52:51 test L0153 DEBUG| _name_map_file = {'sleeptest-example.mplx': 'sleeptest'} - 15:52:51 test L0153 DEBUG| _short_name_map_file = {'sleeptest-example.mplx': 'sleeptest'} + 15:52:51 test L0153 DEBUG| _name_map_file = {'sleeptest-example.yaml': 'sleeptest'} + 15:52:51 test L0153 DEBUG| _short_name_map_file = {'sleeptest-example.yaml': 'sleeptest'} 15:52:51 test L0153 DEBUG| dep = [] 15:52:51 test L0153 DEBUG| id = sleeptest 15:52:51 test L0153 DEBUG| name = sleeptest diff --git a/examples/tests/sleeptenmin.py.data/sleeptenmin.mplx b/examples/tests/sleeptenmin.py.data/sleeptenmin.yaml similarity index 100% rename from examples/tests/sleeptenmin.py.data/sleeptenmin.mplx rename to examples/tests/sleeptenmin.py.data/sleeptenmin.yaml diff --git a/examples/tests/synctest.py b/examples/tests/synctest.py index 4f50179e957e4031e4d18f886fb50e4433198073..6ef24d7fe8d896291333b704b3f47af528e7f7a2 100755 --- a/examples/tests/synctest.py +++ b/examples/tests/synctest.py @@ -21,7 +21,7 @@ class SyncTest(test.Test): """ self.cwd = os.getcwd() tarball_path = self.get_data_path(self.params.get('sync_tarball', '*', - default='synctest.tar.bz2')) + 'synctest.tar.bz2')) archive.extract(tarball_path, self.srcdir) self.srcdir = os.path.join(self.srcdir, 'synctest') if self.params.get('debug_symbols', default=True): diff --git a/selftests/all/unit/avocado/multiplexer_unittest.py b/selftests/all/unit/avocado/multiplexer_unittest.py index 2f07de55981dcb9c7ec49b55dbbfcaa50216642f..a9a292504f55c1d15adb7eefbcb9d7e89a4692a7 100644 --- a/selftests/all/unit/avocado/multiplexer_unittest.py +++ b/selftests/all/unit/avocado/multiplexer_unittest.py @@ -92,12 +92,6 @@ class TestAvocadoParams(unittest.TestCase): str(multiplexer.AvocadoParams([], 'Unittest', None, [], {})) self.assertEqual(26, sum([1 for _ in self.params1.iteritems()])) - def test_get_old_api(self): - self.assertEqual(self.params1.get('unique1'), 'unique1') - self.assertEqual(self.params1.get('missing'), None) - self.assertEqual(self.params1.get('missing', 'aaa'), 'aaa') - self.assertEqual(self.params1.root, 'root') - def test_get_abs_path(self): # /ch0/ is not leaf thus it's not queryable self.assertEqual(self.params1.get('root', '/ch0/', 'bbb'), 'bbb')