255 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
			
		
		
	
	
			255 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
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.
 | 
						|
 | 
						|
 |