[Avocado-devel] RFC: Static analysis vs. import to discover tests

Lukáš Doktor ldoktor at redhat.com
Tue Jan 19 16:19:00 UTC 2016


Hello guys,

as the topic suggests, we'll be talking about ways to discover whether 
given file is an Avocado test, unittest, or simple test.


History
=======

At the beginning, Avocado used `inspect` to get all module-level classes 
and queried if they were inherited from avocado.Test class. This worked 
brilliantly, BUT it requires the module, to be actually loaded, which 
means the module-level code is executed. This is fine when you intend to 
run the test anyway, but it's potentially dangerous, when you just list 
the tests. The problem is, that simple "avocado list /" could turn into 
'os.system("rm -rf / --no-preserve-root")' and we clearly don't want that.

So couple of months back, we started using AST and static analysis to 
discover tests.

The problem is, that even the best static analysis can't cover 
everything our users can come up with (for example creating Test classes 
dynamically during import). So we added a way to override this behavior, 
docstring "avocado: enable" and "avocado: disable". It can't handle 
dynamically created `test` methods, but it can help you with inheritance 
problems.

Anyway it's not really friendly and you can't execute inherited `test` 
methods, only the ones defined in your class. Recently we had questions 
from multiple sources asking "Why is my class not recognized?" Or "Why 
is my test executed as SIMPLE test?".


My solution
===========

I don't like static analysis. It's always tricky, it can never achieve 
100% certainty. So I'm proposing moving back to loading the code, while 
keeping the static analysis for listing purposes. So how would that work?


avocado list
------------

would use static analysis to discover tests. The output would be:

```
SIMPLE         /bin/true
INSTRUMENTED   examples/tests/gdbtest.py:GdbTest.test_start_exit
INSTRUMENTED   examples/tests/gdbtest.py:GdbTest.test_existing_commands_raw
INSTRUMENTED   examples/tests/gdbtest.py:GdbTest.test_existing_commands
INSTRUMENTED 
examples/tests/gdbtest.py:GdbTest.test_load_set_breakpoint_run_exit_raw
?INSTRUMENTED? multiple_inheritance.py:Inherited.*
?INSTRUMENTED? multiple_inheritance.py:BaseClass.*
?INSTRUMENTED? library.py:*
?INSTRUMENTED? simple_script.py:*
```

Where ?xxx? would be yellow and it stands for "It looks like 
instrumented test, but I can't tell".

When the user makes sure, he wants to run those tests and they are not 
nasty, he would use `avocado list --unsafe`, which would actually load 
the modules and give precise results:

```
SIMPLE       /bin/true
INSTRUMENTED examples/tests/gdbtest.py:GdbTest.test_start_exit
INSTRUMENTED examples/tests/gdbtest.py:GdbTest.test_existing_commands_raw
INSTRUMENTED examples/tests/gdbtest.py:GdbTest.test_existing_commands
INSTRUMENTED 
examples/tests/gdbtest.py:GdbTest.test_load_set_breakpoint_run_exit_raw
INSTRUMENTED multiple_inheritance.py:Inherited.test1
INSTRUMENTED multiple_inheritance.py:Inherited.test2
INSTRUMENTED multiple_inheritance.py:Inherited.test3
SIMPLE       simple_script.py
```

You can see that this removed the `library.py`, it also discovered that 
the `multiple_inheritance.py:BaseClass`` is not test. On the other hand, 
it discovered that `multiple_inheritance.py:Inherited` holds 3 tests. 
This result is, what `avocado run` would actually execute.


avocado run
-----------

I don't think `avocado run` should support safe and unsafe option, not 
even when running `--dry-run`. When one decides to run the tests, he 
wants to run them, so I think `avocado run` should always use the unsafe 
way using `inspect`. That way there are no surprises and complex 
workarounds to get things done.


Summary
=======

The current way is safe, `avocado list` and `avocado run` are always in 
sync, but it's strange to users as they write tests, they use multiple 
inheritance and they need BaseClasses. We have a solution, which 
requires docstrings, but it's just not flexible enough and can never be 
as flexible as actual loading.

The proposed solution allows using `avocado list` safely, while `avocado 
run` would execute what test developer wrote, without trying to be too 
smart. The only cons I see is, that in some cases the `avocado list` 
might fail to list all tests, but it should always notify about the 
possibility.

I hope it's understandable, feel free to add your comments and other 
possible solutions,

Lukáš




More information about the Avocado-devel mailing list