| Sorting per-resource |
| ----------------------------- |
| |
| for any given set of items: |
| |
| - collect items per session-scoped parametrized funcarg |
| - re-order until items no parametrizations are mixed |
| |
| examples: |
| |
| test() |
| test1(s1) |
| test1(s2) |
| test2() |
| test3(s1) |
| test3(s2) |
| |
| gets sorted to: |
| |
| test() |
| test2() |
| test1(s1) |
| test3(s1) |
| test1(s2) |
| test3(s2) |
| |
| |
| the new @setup functions |
| -------------------------------------- |
| |
| Consider a given @setup-marked function:: |
| |
| @pytest.mark.setup(maxscope=SCOPE) |
| def mysetup(request, arg1, arg2, ...) |
| ... |
| request.addfinalizer(fin) |
| ... |
| |
| then FUNCARGSET denotes the set of (arg1, arg2, ...) funcargs and |
| all of its dependent funcargs. The mysetup function will execute |
| for any matching test item once per scope. |
| |
| The scope is determined as the minimum scope of all scopes of the args |
| in FUNCARGSET and the given "maxscope". |
| |
| If mysetup has been called and no finalizers have been called it is |
| called "active". |
| |
| Furthermore the following rules apply: |
| |
| - if an arg value in FUNCARGSET is about to be torn down, the |
| mysetup-registered finalizers will execute as well. |
| |
| - There will never be two active mysetup invocations. |
| |
| Example 1, session scope:: |
| |
| @pytest.mark.funcarg(scope="session", params=[1,2]) |
| def db(request): |
| request.addfinalizer(db_finalize) |
| |
| @pytest.mark.setup |
| def mysetup(request, db): |
| request.addfinalizer(mysetup_finalize) |
| ... |
| |
| And a given test module: |
| |
| def test_something(): |
| ... |
| def test_otherthing(): |
| pass |
| |
| Here is what happens:: |
| |
| db(request) executes with request.param == 1 |
| mysetup(request, db) executes |
| test_something() executes |
| test_otherthing() executes |
| mysetup_finalize() executes |
| db_finalize() executes |
| db(request) executes with request.param == 2 |
| mysetup(request, db) executes |
| test_something() executes |
| test_otherthing() executes |
| mysetup_finalize() executes |
| db_finalize() executes |
| |
| Example 2, session/function scope:: |
| |
| @pytest.mark.funcarg(scope="session", params=[1,2]) |
| def db(request): |
| request.addfinalizer(db_finalize) |
| |
| @pytest.mark.setup(scope="function") |
| def mysetup(request, db): |
| ... |
| request.addfinalizer(mysetup_finalize) |
| ... |
| |
| And a given test module: |
| |
| def test_something(): |
| ... |
| def test_otherthing(): |
| pass |
| |
| Here is what happens:: |
| |
| db(request) executes with request.param == 1 |
| mysetup(request, db) executes |
| test_something() executes |
| mysetup_finalize() executes |
| mysetup(request, db) executes |
| test_otherthing() executes |
| mysetup_finalize() executes |
| db_finalize() executes |
| db(request) executes with request.param == 2 |
| mysetup(request, db) executes |
| test_something() executes |
| mysetup_finalize() executes |
| mysetup(request, db) executes |
| test_otherthing() executes |
| mysetup_finalize() executes |
| db_finalize() executes |
| |
| |
| Example 3 - funcargs session-mix |
| ---------------------------------------- |
| |
| Similar with funcargs, an example:: |
| |
| @pytest.mark.funcarg(scope="session", params=[1,2]) |
| def db(request): |
| request.addfinalizer(db_finalize) |
| |
| @pytest.mark.funcarg(scope="function") |
| def table(request, db): |
| ... |
| request.addfinalizer(table_finalize) |
| ... |
| |
| And a given test module: |
| |
| def test_something(table): |
| ... |
| def test_otherthing(table): |
| pass |
| def test_thirdthing(): |
| pass |
| |
| Here is what happens:: |
| |
| db(request) executes with param == 1 |
| table(request, db) |
| test_something(table) |
| table_finalize() |
| table(request, db) |
| test_otherthing(table) |
| table_finalize() |
| db_finalize |
| db(request) executes with param == 2 |
| table(request, db) |
| test_something(table) |
| table_finalize() |
| table(request, db) |
| test_otherthing(table) |
| table_finalize() |
| db_finalize |
| test_thirdthing() |
| |
| Data structures |
| -------------------- |
| |
| pytest internally maintains a dict of active funcargs with cache, param, |
| finalizer, (scopeitem?) information: |
| |
| active_funcargs = dict() |
| |
| if a parametrized "db" is activated: |
| |
| active_funcargs["db"] = FuncargInfo(dbvalue, paramindex, |
| FuncargFinalize(...), scopeitem) |
| |
| if a test is torn down and the next test requires a differently |
| parametrized "db": |
| |
| for argname in item.callspec.params: |
| if argname in active_funcargs: |
| funcarginfo = active_funcargs[argname] |
| if funcarginfo.param != item.callspec.params[argname]: |
| funcarginfo.callfinalizer() |
| del node2funcarg[funcarginfo.scopeitem] |
| del active_funcargs[argname] |
| nodes_to_be_torn_down = ... |
| for node in nodes_to_be_torn_down: |
| if node in node2funcarg: |
| argname = node2funcarg[node] |
| active_funcargs[argname].callfinalizer() |
| del node2funcarg[node] |
| del active_funcargs[argname] |
| |
| if a test is setup requiring a "db" funcarg: |
| |
| if "db" in active_funcargs: |
| return active_funcargs["db"][0] |
| funcarginfo = setup_funcarg() |
| active_funcargs["db"] = funcarginfo |
| node2funcarg[funcarginfo.scopeitem] = "db" |
| |
| Implementation plan for resources |
| ------------------------------------------ |
| |
| 1. Revert FuncargRequest to the old form, unmerge item/request |
| (done) |
| 2. make funcarg factories be discovered at collection time |
| 3. Introduce funcarg marker |
| 4. Introduce funcarg scope parameter |
| 5. Introduce funcarg parametrize parameter |
| 6. make setup functions be discovered at collection time |
| 7. (Introduce a pytest_fixture_protocol/setup_funcargs hook) |
| |
| methods and data structures |
| -------------------------------- |
| |
| A FuncarcManager holds all information about funcarg definitions |
| including parametrization and scope definitions. It implements |
| a pytest_generate_tests hook which performs parametrization as appropriate. |
| |
| as a simple example, let's consider a tree where a test function requires |
| a "abc" funcarg and its factory defines it as parametrized and scoped |
| for Modules. When collections hits the function item, it creates |
| the metafunc object, and calls funcargdb.pytest_generate_tests(metafunc) |
| which looks up available funcarg factories and their scope and parametrization. |
| This information is equivalent to what can be provided today directly |
| at the function site and it should thus be relatively straight forward |
| to implement the additional way of defining parametrization/scoping. |
| |
| conftest loading: |
| each funcarg-factory will populate the session.funcargmanager |
| |
| When a test item is collected, it grows a dictionary |
| (funcargname2factorycalllist). A factory lookup is performed |
| for each required funcarg. The resulting factory call is stored |
| with the item. If a function is parametrized multiple items are |
| created with respective factory calls. Else if a factory is parametrized |
| multiple items and calls to the factory function are created as well. |
| |
| At setup time, an item populates a funcargs mapping, mapping names |
| to values. If a value is funcarg factories are queried for a given item |
| test functions and setup functions are put in a class |
| which looks up required funcarg factories. |
| |
| |