Re: [py-dev] RFC: V2 of the new resource setup/parametrization facilities

2012-07-05 Thread Carl Meyer
Hello Holger,

On 06/29/2012 04:55 AM, holger krekel wrote:
 I believe that the new resource parametrization facilities are a major
 step forward - they should allow test writers to much more seldomly having
 to resort to pytest_* hooks, and make access and working with parametrized 
 resources straight forward, irrespective of previous xUnit/pytest background.

I think so too! Very excited about the more straightforward
parametrization and cache-scope for resources.

I'm of course fairly new to pytest and so not as familiar with history
or internal implementation, but perhaps that new perspective is also
useful. On the whole I think the proposal is excellent.

I would also prefer to see pytest.mark.factory_scope and
pytest.mark.factory_parametrize combined into a single decorator, but I
don't like the name factoryattr. For one thing, I find the usage of
factory a bit confusing (there's no clarity about what sort of
factory). And second, I would go even further and allow this decorator
itself (even with no arguments) to mark a function as a funcarg, even if
the factory function is not named according to the pytest_funcarg__foo
convention. So for instance:

pytest.mark.resource()
def database(request):
# ...

(The decorator could also be pytest.mark.funcarg() if you're not ready
to move towards the resource naming. I think resource is more
intuitive than funcarg for newcomers, but I recognize the costs of
shifting.)

And then this decorator would of course also accept the scope and
parametrize keyword arguments. To my mind, this would be a really nice
unification and could become the canonical way to mark a test resource,
with the naming convention becoming either a deprecated
backwards-compatibility feature, or an approved and supported shortcut
for the no-extra-parameters case, depending how you feel about it.
(Personally I prefer explicit marking to naming conventions, but I
realize this preference may not be in the spirit of pytest).

Regarding the naming of scope and the dual scopes (lifecycle and
visibility), personally I don't think it's a big issue to just use the
name scope; does the name scope actually occur in the API related to
the visibility variety?

I find cachescope irritating and a bit misleading. If scope is
overloaded, what about lifetime? Speaking of the lifetime of an
instance of a test resource feels more natural to me than speaking of
caching it.

Carl
___
py-dev mailing list
py-dev@codespeak.net
http://codespeak.net/mailman/listinfo/py-dev


Re: [py-dev] RFC: V2 of the new resource setup/parametrization facilities

2012-07-02 Thread holger krekel
On Sat, Jun 30, 2012 at 12:26 +0100, Floris Bruynooghe wrote:
  On Sat, Jun 30, 2012 at 01:23 +0100, Floris Bruynooghe wrote:
   On Fri, Jun 29, 2012 at 10:55:23AM +, holger krekel wrote:

def setup_directory(db):
# called when the first test in the directory tree is about
to execute
   
   I think the naming of these functions break the py.test convention,
   normally the only functions picked up from conftest.py start with
   pytest_.  I can certainly imagine a conftest.py or plugin already
   having a setup_session function.  These are new functions and do not
   provide a compatibility API with other testing frameworks, so I think
   they would be better named pytest_setup_session and
   pytest_setup_directory.
  
  I think using pytest_* hooks also has consistency problems:
  
  * hooks cannot usually receive arbitrary funcargs
 
 This is why a signature with a request/node for these might be better::
 
def pytest_setup_session(session):
session.getresource('db')  # or .getfuncargvalue()?
...
 
  * xUnit-style consistency: consider explaining the new functions
to someone only knowing setup_module/ class etc.
 
 As I tried to say before, they do not come for xUnit so I don't think
 this is too important.  I think the consistency inside conftest.py is
 more important.

Well, pytest introduced setup_module/class and nose/unittest ported it.
I consider setup_directory (or setup_session) to be xUnit consistent
from a user perspective and maybe nose/unittest will also add it - i guess
there are extensions there implementing something like this already.

In generaly, if we go the route of making setup_X more powerful there
probably is less need for pytest_runtest_setup calls.  The only difference
would be that runtest_setup is called in plugins/conftest.py whereas 
setup_function/method need to be defined around the actual test code.
(TBH i wonder if setup_module/class/function/method could be allowed in
conftest.py files as well - in many cases there are easier to handle
than pytest_runtest_item(item) which does not even guarantee that
the item is a python function - could be a PEP8 checker Item).

  I am wondering, however, do we even need a setup_session? setup_directory
  should usually be enough, i guess, and it's more unlikely people used
  that name already (and we could warn about setup_session in 2.X to
  reserve introducing it in 2.X+1).
 
 Maybe not, but if you don't provide setup_session (or
 pytest_setup_session) then pytest_sessionstart will be used again
 when someone thinks of a reason to use it.  And that's what you wanted
 to avoid.

We definitely need to provide prominent examples for whole-session setup
to avoid further usage of sessionstart or configure.

 [...]
  If a setup-function has no body, then tests could just require it themselves
  and that'd be enough.  If there is a need, we could introduce a marker for 
  requiring funcarg-resources such that tests do not need to require it 
  in their signature.
 
 I'm not sure what that would save, either the test function must
 request the resource or must be marked to need the resource.  If
 anything the second takes more work.

 
 As an aside however, one of my usecases for merged request/item
 objects was so I could put setup in a session-wide scoped funcarg but
 also automatically request this funcarg based on a mark::
 
def pytest_runtest_setup(item):
if 'needsdb' in item.keywords:  # or a more explicit API
   item.getresource('db')
 
 I understand that this will still be possible via::
 
def pytest_runtest_setup(item):
if 'needsdb' in item.keywords:
item.session.getresource('db')
 
 Or something similar to that.

It'd probably be best if this requirement is know at collection time so
--collectonly can present the complete picture.  This could look like this::

def pytest_itemcollected(item):
if 'needsdb' in item.keywords:
item.applymarker(pytest.mark.needsresource(db))

Also, the above issue of requiring a global resource could be expressed 
like this::

def pytest_collection_finish(session):
session.applymarker(pytest.mark.needsresource(db))

This should also in principle work well in case of a parametrized db
so that all tests requiring db can be run multiple times.
(I am not sure if the above already existing hooks fit well for that, however.)

best,
holger
___
py-dev mailing list
py-dev@codespeak.net
http://codespeak.net/mailman/listinfo/py-dev


Re: [py-dev] RFC: V2 of the new resource setup/parametrization facilities

2012-06-30 Thread holger krekel
Hi Floris,

some preliminary notes, i'll probably think some more about your feedback ...

On Sat, Jun 30, 2012 at 01:23 +0100, Floris Bruynooghe wrote:
 Hello Holger,
 
 On Fri, Jun 29, 2012 at 10:55:23AM +, holger krekel wrote:
 [...]
  Direct scoping of funcarg factories
 [...]
  Direct parametrization of funcarg factories 
 
 These two seem fine, but personally I would prefer them to use the
 same marker with keyword-only arguments::
 
@pytest.mark.factory(scope='session', parametrize=['mysql', 'pg'])
def pytest_funcarg__db(request):
...
 
 This seems like a more natural API which collects the different
 functions, certainly when using both for one funcarg.

I'll consider it, probably under the name of factoryattr or so. 

 However it bothers me that funcargs now have two types of scope: an
 implied scope derived from where it is defined and which defines their
 visibility (e.g. only inside a class, module, directory).  And then
 this new scope which is essentially a caching/teardown scope.  The
 fact that the ScopeMismatch exception is needed is a result of this I
 think.

previously, the scope-mismatch could happen as well and go unnoticed::

def pytest_funcarg__Y(request):
return request.function.__name__

def pytest_funcarg__X(request):
def setup():
return request.getfuncargvalue(Y)
return request.cached_setup(setup, scope=session)

The result will depend on which test function is first requested.
In the future, we might want to try raise a ScopeMismatchError here
as well.

 The previous resource/funcarg split avoided this confusion.

a) What about just naming it cachescope?
b) i moved register_factory/getresource to implementation details
   not the least because Carl Meyer as a relatively recent pytest user
   expressed his expectation of a consistent pytest_funcarg__ factory
   story - and if we are going to anyway have to support the existing ones, 
   i'd now like to focus on extending it and only go for a usage-level visible
   paradigm change if it's really needed. Does this make general
   sense to you?
 
 Lastly, when do scoped funcarg resources get invoked?  Only at the
 time a test function requests it or always at the time when the scope
 is entered?

factories are invoked when a test function or one of its involved setup 
methods needs it.  A scope is only entered if there is a test to be executed
within it. Does this clarify?

  support for setup_session and setup_directory
  --
 [...]
  # content of conftest.py
  def setup_session(db):
  ... use db resource or do some initial global init stuff
  ... before any test is run.
  
  def setup_directory(db):
  # called when the first test in the directory tree is about
  to execute
 
 I think the naming of these functions break the py.test convention,
 normally the only functions picked up from conftest.py start with
 pytest_.  I can certainly imagine a conftest.py or plugin already
 having a setup_session function.  These are new functions and do not
 provide a compatibility API with other testing frameworks, so I think
 they would be better named pytest_setup_session and
 pytest_setup_directory.

I think using pytest_* hooks also has consistency problems:

* hooks cannot usually receive arbitrary funcargs
* xUnit-style consistency: consider explaining the new functions
  to someone only knowing setup_module/ class etc.

I am wondering, however, do we even need a setup_session? setup_directory
should usually be enough, i guess, and it's more unlikely people used
that name already (and we could warn about setup_session in 2.X to
reserve introducing it in 2.X+1).

And what what about putting setup_directory into an __init__.py file?
I don't really like requiring __init__ files, but am fine to go with it if
you and others prefer that.  I would guess, that using the already 
directory-scoped conftest.py file feels fine to someone coming new to pytest.

 It also feels slightly weird that they do not get their respective
 Node passed in.  This is a little inconsistent with the current
 setup_X method which all take a module, class or method argument.  I
 can't think of an immediate use for it as you can push out pretty much
 everything you need to do to a properly scoped funcarg resource.  

We can certainly add modulenode, classnode etc. to the respective
setup-methods because they participate in the funcarg-protocol 
(which allows accepting less parameters than are available).

 And following that reasoning the setup function would end up having no
 body at all, which also seems weird.

If a setup-function has no body, then tests could just require it themselves
and that'd be enough.  If there is a need, we could introduce a marker for 
requiring funcarg-resources such that tests do not need to require it 
in their signature.

  Implementation level 
  

Re: [py-dev] RFC: V2 of the new resource setup/parametrization facilities

2012-06-30 Thread Floris Bruynooghe
On Sat, Jun 30, 2012 at 08:08:41AM +, holger krekel wrote:
 On Sat, Jun 30, 2012 at 01:23 +0100, Floris Bruynooghe wrote:
  On Fri, Jun 29, 2012 at 10:55:23AM +, holger krekel wrote:
 previously, the scope-mismatch could happen as well and go unnoticed::
 
 def pytest_funcarg__Y(request):
 return request.function.__name__
 
 def pytest_funcarg__X(request):
 def setup():
 return request.getfuncargvalue(Y)
 return request.cached_setup(setup, scope=session)
 
 The result will depend on which test function is first requested.
 In the future, we might want to try raise a ScopeMismatchError here
 as well.

Oh, never noticed this.  That's an improvement then.

  The previous resource/funcarg split avoided this confusion.
 
 a) What about just naming it cachescope?

Maybe.  I wouldn't take my word for it that just scope is not
sufficient, see what other people say.  I'd probably get annoyed at
the extra typing for cachescope after a while, maybe even
@pytest.mark.factory(cache=session) is an option?  It would avoid
having two things called scope.


 b) i moved register_factory/getresource to implementation details
not the least because Carl Meyer as a relatively recent pytest user
expressed his expectation of a consistent pytest_funcarg__ factory
story - and if we are going to anyway have to support the existing ones, 
i'd now like to focus on extending it and only go for a usage-level visible
paradigm change if it's really needed. Does this make general
sense to you?

Yes this does make sense, in general I do like this approach.


  Lastly, when do scoped funcarg resources get invoked?  Only at the
  time a test function requests it or always at the time when the scope
  is entered?
 
 factories are invoked when a test function or one of its involved setup 
 methods needs it.  A scope is only entered if there is a test to be executed
 within it. Does this clarify?

It does.

   support for setup_session and setup_directory
   --
  [...]
   # content of conftest.py
   def setup_session(db):
   ... use db resource or do some initial global init stuff
   ... before any test is run.
   
   def setup_directory(db):
   # called when the first test in the directory tree is about
   to execute
  
  I think the naming of these functions break the py.test convention,
  normally the only functions picked up from conftest.py start with
  pytest_.  I can certainly imagine a conftest.py or plugin already
  having a setup_session function.  These are new functions and do not
  provide a compatibility API with other testing frameworks, so I think
  they would be better named pytest_setup_session and
  pytest_setup_directory.
 
 I think using pytest_* hooks also has consistency problems:
 
 * hooks cannot usually receive arbitrary funcargs

This is why a signature with a request/node for these might be better::

   def pytest_setup_session(session):
   session.getresource('db')  # or .getfuncargvalue()?
   ...

 * xUnit-style consistency: consider explaining the new functions
   to someone only knowing setup_module/ class etc.

As I tried to say before, they do not come for xUnit so I don't think
this is too important.  I think the consistency inside conftest.py is
more important.


 I am wondering, however, do we even need a setup_session? setup_directory
 should usually be enough, i guess, and it's more unlikely people used
 that name already (and we could warn about setup_session in 2.X to
 reserve introducing it in 2.X+1).

Maybe not, but if you don't provide setup_session (or
pytest_setup_session) then pytest_sessionstart will be used again
when someone thinks of a reason to use it.  And that's what you wanted
to avoid.

 And what what about putting setup_directory into an __init__.py file?
 I don't really like requiring __init__ files, but am fine to go with it if
 you and others prefer that.  I would guess, that using the already 
 directory-scoped conftest.py file feels fine to someone coming new to pytest.

I agree, requiring __init__.py is worse then just putting it in
conftest.py.  I think it would be best if it fits inside conftest.py.


 If a setup-function has no body, then tests could just require it themselves
 and that'd be enough.  If there is a need, we could introduce a marker for 
 requiring funcarg-resources such that tests do not need to require it 
 in their signature.

I'm not sure what that would save, either the test function must
request the resource or must be marked to need the resource.  If
anything the second takes more work.

As an aside however, one of my usecases for merged request/item
objects was so I could put setup in a session-wide scoped funcarg but
also automatically request this funcarg based on a mark::

   def pytest_runtest_setup(item):
   if 'needsdb' in item.keywords:  # or a more explicit API

[py-dev] RFC: V2 of the new resource setup/parametrization facilities

2012-06-29 Thread holger krekel
Hi all, particularly Floris and Carl,

i have finally arrived at the V2 resource-API draft based on the very valuable
feedback you gave to the first version.  The document implements a largely
changed approach, see the Changes from V1 to V2 at the beginning, and
focuses on usage-level documentation instead of internal details.

I have also uploaded this doc as HTML which makes it a bit more colorful
to read, and also contains some cross-references:

http://pytest.org/dev/resources.html

Please find the the source txt-file also attached for your
inline-commenting usage.  Before i target the actual (substantial)
refactoring, i'd actually be very grateful for some more of your time
and comments on this new version.

I believe that the new resource parametrization facilities are a major
step forward - they should allow test writers to much more seldomly having
to resort to pytest_* hooks, and make access and working with parametrized 
resources straight forward, irrespective of previous xUnit/pytest background.
Plugin writers, of course, may still use the hooks for good value.

best  thanks,
holger


V2: Creating and working with parametrized test resources
===

Abstract: pytest-2.X provides generalized scoping and parametrization
of resource setup.  It does so by introducing new scoping and parametrization
capabilities directly to to funcarg factories and by enhancing
xUnit-style setup_X methods to directly accept funcarg resources.
Moreover, new xUnit setup_directory() and setup_session() methods allow
fixture code (and resource usage) at previously unavailable scopes.
Pre-existing test suites and plugins written to work for previous pytest
versions shall run unmodified.

This V2 draft is based on incorporating feedback provided by Floris Bruynooghe, 
Carl Meyer and Ronny Pfannschmidt. It remains as draft documentation, pending 
further refinements and changes according to implementation or backward 
compatibility issues. The main changes to V1 are:

* changed approach: now based on improving ``pytest_funcarg__``
  factories and extending setup_X methods to directly accept
  funcarg resources, also including a new per-directory
  setup_directory() and setup_session() function for respectively
  scoped setup.
* the funcarg versus resource naming issue is disregarded in favour
  of keeping with funcargs and talking about funcarg resources 
  to ease a later possible renaming (whose value is questionable)
* The register_factory/getresource methods are moved to an
  implementation section for now, drawing a clear boundary between
  usage-level docs and impl-level ones.
* use 2.X as the version for introduction (might be 2.3, else 2.4)

.. currentmodule:: _pytest


Shortcomings of the previous pytest_funcarg__ mechanism
-

The previous funcarg mechanism calls a factory each time a
funcarg for a test function is requested.  If a factory wants
t re-use a resource across different scopes, it often used 
the ``request.cached_setup()`` helper to manage caching of 
resources.  Here is a basic example how we could implement 
a per-session Database object::

# content of conftest.py 
class Database:
def __init__(self):
print (database instance created)
def destroy(self):
print (database instance destroyed)

def pytest_funcarg__db(request):
return request.cached_setup(setup=DataBase, 
teardown=lambda db: db.destroy,
scope=session)

There are some problems with this approach:

1. Scoping resource creation is not straight forward, instead one must
   understand the intricate cached_setup() method mechanics.

2. parametrizing the db resource is not straight forward: 
   you need to apply a parametrize decorator or implement a
   :py:func:`~hookspec.pytest_generate_tests` hook 
   calling :py:func:`~python.Metafunc.parametrize` which
   performs parametrization at the places where the resource 
   is used.  Moreover, you need to modify the factory to use an 
   ``extrakey`` parameter containing ``request.param`` to the 
   :py:func:`~python.Request.cached_setup` call.

3. the current implementation is inefficient: it performs factory discovery
   each time a db argument is required.  This discovery wrongly happens at 
   setup-time.

4. there is no way how you can use funcarg factories, let alone 
   parametrization, when your tests use the xUnit setup_X approach.

5. there is no way to specify a per-directory scope for caching.

In the following sections, API extensions are presented to solve 
each of these problems. 


Direct scoping of funcarg factories


Instead of calling cached_setup(), you can decorate your factory
to state its scope::

@pytest.mark.factory_scope(session)
def pytest_funcarg__db(request):

Re: [py-dev] RFC: V2 of the new resource setup/parametrization facilities

2012-06-29 Thread Floris Bruynooghe
Hello Holger,

On Fri, Jun 29, 2012 at 10:55:23AM +, holger krekel wrote:
[...]
 Direct scoping of funcarg factories
[...]
 Direct parametrization of funcarg factories 

These two seem fine, but personally I would prefer them to use the
same marker with keyword-only arguments::

   @pytest.mark.factory(scope='session', parametrize=['mysql', 'pg'])
   def pytest_funcarg__db(request):
   ...

This seems like a more natural API which collects the different
functions, certainly when using both for one funcarg.


However it bothers me that funcargs now have two types of scope: an
implied scope derived from where it is defined and which defines their
visibility (e.g. only inside a class, module, directory).  And then
this new scope which is essentially a caching/teardown scope.  The
fact that the ScopeMismatch exception is needed is a result of this I
think.

The previous resource/funcarg split avoided this confusion.


Lastly, when do scoped funcarg resources get invoked?  Only at the
time a test function requests it or always at the time when the scope
is entered?


 support for setup_session and setup_directory
 --
[...]
 # content of conftest.py
 def setup_session(db):
 ... use db resource or do some initial global init stuff
 ... before any test is run.
 
 def setup_directory(db):
 # called when the first test in the directory tree is about
 to execute

I think the naming of these functions break the py.test convention,
normally the only functions picked up from conftest.py start with
pytest_.  I can certainly imagine a conftest.py or plugin already
having a setup_session function.  These are new functions and do not
provide a compatibility API with other testing frameworks, so I think
they would be better named pytest_setup_session and
pytest_setup_directory.

It also feels slightly weird that they do not get their respective
Node passed in.  This is a little inconsistent with the current
setup_X method which all take a module, class or method argument.  I
can't think of an immediate use for it as you can push out pretty much
everything you need to do to a properly scoped funcarg resource.  And
following that reasoning the setup function would end up having no
body at all, which also seems weird.



 Implementation level 
 ===
[...]
 the request object incorporates scope-specific behaviour
 --
[...]
 In fact, the request object is likely going to provide a node 
 attribute, denoting the current collection node on which it internally
 operates.  (Prior to pytest-2.3 there already was an internal
 _pyfuncitem).

Does this mean you will revert the currently checked-in behaviour
where a Node is actually a Request subclass and is the object passed
to the funcarg resource factories?



Hope this was helpful feedback,
Floris


-- 
Debian GNU/Linux -- The Power of Freedom
www.debian.org | www.gnu.org | www.kernel.org
___
py-dev mailing list
py-dev@codespeak.net
http://codespeak.net/mailman/listinfo/py-dev