On 26/11/2014 20:56, Lukáš Doktor wrote:
>> This looks amazing and should be doable in YAML. The `mux-default`
>> should not be needed. Let me explain the idea here. Do you remember the
>> ways to execute test from my email:
>>
>> 1) default variant
>> 2) full test
>> 3) all related
>> 4) multiplex all
>>
>> And the places where variants are stored:
>>
>> A) yaml file added on cmdline (/hw, /os, ...)
>> B) each test's associated file (/tests/$name/duration,
>> /test/$name/function, ...)
>> Z) for default variables (new idea), let's say
>> `avocado.yaml` which would be 1 level yaml with all default variables
>> (image: "Fedora", passwd: "123456", ...)
>>
>> When you execute test as 1), it'd take default values from Z) and than
>> get only first level values from B)
>>
>> In case you execute 2), it'd again get default values from Z) and then
>> it'd parse the B) tree.
>>
>> The 3) would need to parse Z), A) and B) and multiplex only mux-choices
>> specified in the B).
>>
>> The 4) would parse Z), A) and B) and multiplex everything what is not
>> filtered out.

In my case you'd have:

1 and 2) command line switch --mux=none or --no-mux: take default values
from B and Z, apply mux-expands found in A (but ignore mux-expands
specified in B), filter.  The difference is simply that in case 1 you
have no mux-expands in A (a mux-expand can also be given on the command
line, BTW).

3) command line switch --mux=auto or just --mux: take default values
from B and Z, expand mux-choices specified in A and B via mux-expand

4) command line switch --mux=all: take default values from B and Z,
apply mux-expand to all choices, filter out.

>> ######################################################################
>> Lucas also pointed out, that I didn't explain the hooks sufficiently. So
>> how the params.get() should look like:
>>
>> def get(key, default):
>>      raw_value = super(Params, self).get(key, default)
>>      if key in self.hooks:
>>          return self.hooks[key](raw_value, default)
>>      else:
>>          return raw_value
>>
>> where hook could eg. means:
>>
>> def arch_hook(raw_value, default):
>>      # params.get("/hw/arch")
>>      if not raw_value:
>>          if default:
>>              return default
>>          else:
>>              return arch.get_supported_archs()[0]
>>      elif raw_value not in arch.get_supported_archs():
>>          if default:
>>              return default
>>          else:
>>              raise TestNAError("Architecture ... not supported")

I think actually it should always raise TestNAError in this second case,
giving the following very simple code for arch_hook:

    value = raw_value or default or arch.get_supported_archs()[0]
    if value not in arch.get_supported_archs():
        raise TestNAError("Architecture ... not supported")
    return value

However, I think it is important to have a declarative way to describe
the muxing and the defaults.  A declarative syntax makes it possible to
convert muxing constraints to Boolean formulas, and hence enumerate the
results with binary decision diagrams (BDDs).  In turn, this drops all
the complicated optimization logic of the cartesian config parser.

Now that I thought about it more, I recalled another idea from the old
thread, which is automatic expansion of filtered variants.  In this idea
you'd have two variants of mux-only and mux-no:

- mux-requires-only and mux-requires-no, mostly for use in B and Z, do
nothing special

- mux-only and mux-no, mostly for use in A, automatically expand the
choices that are mentioned.  In other words,

    - !mux-only { '/os': 'linux' }

is the same as

    - !mux-expand '/os'
    - !mux-requires-only { '/os': 'linux' }


The idea is that in A you'd say

    - !mux-no { '/hw/net/backend': 'vhost' }

meaning you want to test all backends except vhost.  In B you'd say instead

    - !mux-requires-no { '/hw/net/backend': 'user' }

meaning you want to skip this test, but not override the choice of the
user.  It's also possible to do this magically (i.e. mux-only in A
always implies mux-expand) and avoid proliferation of tags.  Still, the
difference in behavior should probably be taken into account.

Does this let you build arbitrary DNF (disjunctive normal form)
formulas?  You can do ANDs of terms like

    P | Q | R             ~(P | Q | R)

You can only have direct and negated variables in the same formula if
you replicate the mux-group/mux-choice structure in A; this is quite
unwieldy.  In the cartesian config this is provided by:

    Linux:
        only virtio
    Windows:
        only rtl8139

where Linux and Windows will match anywhere in the output tuple.  What
we can do then is add a !mux-if tag:

    - !mux-if { '/os': 'linux' }
        - !mux-only { '/hw/net/nic_model': 'virtio' }
    - !mux-if { '/os': 'windows' }
        - !mux-only { '/hw/net/nic_model': 'rtl8139' }

This translates to the following Boolean formula

     (os == linux => hw.net.nic_model == virtio) &&
     (os == windows => hw.net.nic_model == rtl8139)

where I've written '/os/distro' more easily as 'os.distro' (probably
it's a good idea to write it that way in mux-{if,only,no} as well).

Rewriting the implication A => B to ~A | B:

     (os != linux || hw.net.nic_model == virtio) &&
     (os != windows || hw.net.nic_model == rtl8139)

and adding conditions to govern expansion of the choices:

     (os != linux || hw.net.nic_model == virtio) &&
     (os != windows || hw.net.nic_model == rtl8139) &&
     expand(hw.net.nic_model)

Note that os only appears under a mux-if, so it is *not* expanded.

So, how are the mux directives translated to SAT formulas?  Pretty
easily, I might say.

A choice with N possibilities is mapped to N+1 SAT variables, N for the
possibilities and 1 for "should this choice be expanded".  So in the
case above there are six SAT variables:

     expand(os)
     os == linux
     os == windows
     expand(hw.net.nic_model)
     hw.net.nic_model == virtio
     hw.net.nic_model == rtl8139

Let's first define a "prefix" variable.  It represents the !mux-if and
!mux-choice above the currently parsed element of the tree.  So in the
case above, the prefix of the first !mux-if is "os == linux".

Similarly, when defining

    - !mux-choice os:
        - linux:
           - !mux-choice distro:
               - fedora:
                  - !mux-choice version:
                      - 13:
                      - 14:
               - debian:
                  - !mux-choice version:
                      - squeeze:
                      - jessie:
               - rhel:

the prefix while parsing fedora versions is "os == linux && os.distro ==
fedora"; the prefix while parsing debian versions is "os == linux &&
os.distro == debian"; and so on.

We'll use the negated prefix often, in order to build ORs instead of
implications.  It is derived simply with De Morgan laws.  The negation of

   "os == linux && os.distro == fedora"

is

   "os != linux || os.distro != fedora"

i.e. in terms of the variables we have defined above:

   "!(os == linux) || !(os.distro == fedora)".

The negated prefix will be written prefix'.

With this in mind, here is how you do the conversion to SAT.

- !mux-choice name
    A:
       - mux-default: True
       ...
    B:
       ...
    C:
       ...

Your variables are

    path.name == A
    path.name == B
    path.name == C
    expand(path.name)
    !expand(path.name)

which I'll write shortly as pA, pB, pC, pE, pE'.  You first need
N(N-1)/2 terms to express mutual exclusion, and N-1 terms to express the
defaults.  On the left they're written with implications, on the right
with disjunctions:

    pA => ~pB                   :::  ~pA | ~pB    (mutual exclusion)
    pA => ~pC                   :::  ~pA | ~pC
    pB => ~pC                   :::  ~pB | ~pC
    ~pE => ~pB                  :::  pE | ~pB     (default)
    ~pE => ~pC                  :::  pE | ~pC

In addition, as mentioned above, the prefix is modified during the
parse.  "& pA" is added to the prefix (and "| ~pA" to the negated
prefix) while parsing under "- A:", and so on.

- !mux-expand name

This is a simple term (left = implication, right = disjunction):

   prefix => expand(path.name)  :::  prefix' | expand(path.name)

This forces pE to be true and pE' to be false.

- !mux-if X

During the parse, X is added to the prefix, and ~X is added to the
negated prefix.  No terms are emitted.

- !mux-requires-only { 'X': 'Y', 'U': 'V' }

Let's write the "X==Y" variable as pXY and the "U==V" variable as pUV.
You have the following termv (again, left uses arbitrary Boolean
formulas while right uses disjunctions only):

   prefix => pXY | pUV          :::      prefix' | pXY | pUV

- !mux-requires-no { 'X': 'Y', 'U': 'V' }

In this case every member of the map produces a separate term:

   prefix => !pXY               :::      prefix' | !pXY
   prefix => !pUV               :::      prefix' | !pUV


In order to produce the result of the multiplexing, you unfortunately
cannot just find all solutions to the SAT problem.  This is because a
solution with pE = true will always be present; all the above does is
enforce no solution exists with pE = false when a choice is expanded.
There is thus a lot of redundancy in the solution computed so far.

Luckily, the BDD will represent cheaply the redundant solution _and_
will provide a way to easily cull the invalid solutions.  To do so, we
can perform the following steps:

- ensure that, in the BDD, the expand(foo) variable lies above the
foo==X variables

- when visiting the BDD, if the 0 branch of expand(foo) has a solution,
skip visiting the 1 branch altogether.

Paolo

>> Similarly you can handle -device support, default drive format, ...
>>
>> Anyway I'll take a deeper look at the mux domains you proposed, it's
>> named version of what I had in mind (and better supported in yaml).
>>
>> Thank you for your response,
>> Lukáš
>>
>>>
>>> I'm not 100% sure this is valid YAML, but should be close.
>>>
>>> Thanks,
>>>
>>> Paolo
>>>
>>
>> _______________________________________________
>> Virt-test-devel mailing list
>> [email protected]
>> https://www.redhat.com/mailman/listinfo/virt-test-devel
> 
> _______________________________________________
> Virt-test-devel mailing list
> [email protected]
> https://www.redhat.com/mailman/listinfo/virt-test-devel

_______________________________________________
Virt-test-devel mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/virt-test-devel

Reply via email to