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.
 | |
| 
 | |
| 
 |