From dcf194ebb8877da46af6fc97ce494e64dd1f766e Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 28 Jul 2009 14:26:32 +0200 Subject: [PATCH] simplify py.test.mark API, add more plugin docs --HG-- branch : 1.0.x --- CHANGELOG | 2 + doc/test/features.txt | 8 ++- doc/test/plugin/hooklog.txt | 35 +++++++++ doc/test/plugin/keyword.txt | 50 +++++++++++++ doc/test/plugin/pdb.txt | 35 +++++++++ makepluginlist.py | 2 + py/test/plugin/pytest_keyword.py | 120 +++++++++++++++++-------------- 7 files changed, 198 insertions(+), 54 deletions(-) create mode 100644 doc/test/plugin/hooklog.txt create mode 100644 doc/test/plugin/keyword.txt create mode 100644 doc/test/plugin/pdb.txt diff --git a/CHANGELOG b/CHANGELOG index 0538a5abe..14f888746 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ Changes between 1.0.0b8 and 1.0.0b9 ===================================== +* simplified py.test.mark API + * make assert-reinterpretation work better with comparisons not returning bools (reported with numpy from thanks maciej fijalkowski) diff --git a/doc/test/features.txt b/doc/test/features.txt index d7a71783d..2cfa5b384 100644 --- a/doc/test/features.txt +++ b/doc/test/features.txt @@ -237,11 +237,15 @@ class/function names of a test function are put into the set of keywords for a given test. You can specify additional kewords like this:: - @py.test.mark(webtest=True) + @py.test.mark.webtest def test_send_http(): ... -and then use those keywords to select tests. +and then use those keywords to select tests. See the `pytest_keyword`_ +plugin for more information. + +.. _`pytest_keyword`: plugin/keyword.html + disabling a test class ---------------------- diff --git a/doc/test/plugin/hooklog.txt b/doc/test/plugin/hooklog.txt new file mode 100644 index 000000000..371e07070 --- /dev/null +++ b/doc/test/plugin/hooklog.txt @@ -0,0 +1,35 @@ + +pytest_hooklog plugin +===================== + +log invocations of extension hooks to a file. + +.. contents:: + :local: + + + +command line options +-------------------- + + +``--hooklog=HOOKLOG`` + write hook calls to the given file. + +Start improving this plugin in 30 seconds +========================================= + + +Do you find the above documentation or the plugin itself lacking? + +1. Download `pytest_hooklog.py`_ plugin source code +2. put it somewhere as ``pytest_hooklog.py`` into your import path +3. a subsequent ``py.test`` run will use your local version + +Further information: extend_ documentation, other plugins_ or contact_. + +.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/2b8d56b82ce6966960cf41d38dc2b794797912ba/py/test/plugin/pytest_hooklog.py +.. _`extend`: ../extend.html +.. _`plugins`: index.html +.. _`contact`: ../../contact.html +.. _`checkout the py.test development version`: ../../download.html#checkout diff --git a/doc/test/plugin/keyword.txt b/doc/test/plugin/keyword.txt new file mode 100644 index 000000000..28d7486af --- /dev/null +++ b/doc/test/plugin/keyword.txt @@ -0,0 +1,50 @@ + +pytest_keyword plugin +===================== + +mark test functions with keywords that may hold values. + +.. contents:: + :local: + +Marking functions and setting rich attributes +---------------------------------------------------- + +By default, all filename parts and class/function names of a test +function are put into the set of keywords for a given test. You can +specify additional kewords like this:: + + @py.test.mark.webtest + def test_send_http(): + ... + +This will set an attribute 'webtest' on the given test function +and by default all such attributes signal keywords. You can +also set values in this attribute which you could read from +a hook in order to do something special with respect to +the test function:: + + @py.test.mark.timeout(seconds=5) + def test_receive(): + ... + +This will set the "timeout" attribute with a Marker object +that has a 'seconds' attribute. + +Start improving this plugin in 30 seconds +========================================= + + +Do you find the above documentation or the plugin itself lacking? + +1. Download `pytest_keyword.py`_ plugin source code +2. put it somewhere as ``pytest_keyword.py`` into your import path +3. a subsequent ``py.test`` run will use your local version + +Further information: extend_ documentation, other plugins_ or contact_. + +.. _`pytest_keyword.py`: http://bitbucket.org/hpk42/py-trunk/raw/2b8d56b82ce6966960cf41d38dc2b794797912ba/py/test/plugin/pytest_keyword.py +.. _`extend`: ../extend.html +.. _`plugins`: index.html +.. _`contact`: ../../contact.html +.. _`checkout the py.test development version`: ../../download.html#checkout diff --git a/doc/test/plugin/pdb.txt b/doc/test/plugin/pdb.txt new file mode 100644 index 000000000..2cb084b43 --- /dev/null +++ b/doc/test/plugin/pdb.txt @@ -0,0 +1,35 @@ + +pytest_pdb plugin +================= + +interactive debugging with the Python Debugger. + +.. contents:: + :local: + + + +command line options +-------------------- + + +``--pdb`` + start pdb (the Python debugger) on errors. + +Start improving this plugin in 30 seconds +========================================= + + +Do you find the above documentation or the plugin itself lacking? + +1. Download `pytest_pdb.py`_ plugin source code +2. put it somewhere as ``pytest_pdb.py`` into your import path +3. a subsequent ``py.test`` run will use your local version + +Further information: extend_ documentation, other plugins_ or contact_. + +.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/2b8d56b82ce6966960cf41d38dc2b794797912ba/py/test/plugin/pytest_pdb.py +.. _`extend`: ../extend.html +.. _`plugins`: index.html +.. _`contact`: ../../contact.html +.. _`checkout the py.test development version`: ../../download.html#checkout diff --git a/makepluginlist.py b/makepluginlist.py index 153c2409b..d88c9584b 100644 --- a/makepluginlist.py +++ b/makepluginlist.py @@ -10,6 +10,8 @@ plugins = [ 'unittest doctest oejskit restdoc'), ('Plugins for generic reporting and failure logging', 'pocoo resultlog terminal',), + ('internal plugins / core functionality', + 'pdb keyword hooklog') #('internal plugins / core functionality', # #'pdb keyword hooklog runner execnetcleanup # pytester', # 'pdb keyword hooklog runner execnetcleanup' # pytester', diff --git a/py/test/plugin/pytest_keyword.py b/py/test/plugin/pytest_keyword.py index 61c134a01..656b16ef9 100644 --- a/py/test/plugin/pytest_keyword.py +++ b/py/test/plugin/pytest_keyword.py @@ -1,70 +1,86 @@ """ - py.test.mark / keyword plugin +mark test functions with keywords that may hold values. + +Marking functions and setting rich attributes +---------------------------------------------------- + +By default, all filename parts and class/function names of a test +function are put into the set of keywords for a given test. You can +specify additional kewords like this:: + + @py.test.mark.webtest + def test_send_http(): + ... + +This will set an attribute 'webtest' on the given test function +and by default all such attributes signal keywords. You can +also set values in this attribute which you could read from +a hook in order to do something special with respect to +the test function:: + + @py.test.mark.timeout(seconds=5) + def test_receive(): + ... + +This will set the "timeout" attribute with a Marker object +that has a 'seconds' attribute. + """ import py def pytest_namespace(): - mark = KeywordDecorator({}) - return {'mark': mark} + return {'mark': Mark()} -class KeywordDecorator: - """ decorator for setting function attributes. """ - def __init__(self, keywords, lastname=None): - self._keywords = keywords - self._lastname = lastname - - def __call__(self, func=None, **kwargs): - if func is None: - kw = self._keywords.copy() - kw.update(kwargs) - return KeywordDecorator(kw) - elif not hasattr(func, 'func_dict'): - kw = self._keywords.copy() - name = self._lastname - if name is None: - name = "mark" - kw[name] = func - return KeywordDecorator(kw) - func.func_dict.update(self._keywords) - return func +class Mark(object): def __getattr__(self, name): if name[0] == "_": raise AttributeError(name) - kw = self._keywords.copy() - kw[name] = True - return self.__class__(kw, lastname=name) + return MarkerDecorator(name) + +class MarkerDecorator: + """ decorator for setting function attributes. """ + def __init__(self, name): + self.markname = name + + def __repr__(self): + d = self.__dict__.copy() + name = d.pop('markname') + return "" %(name, d) + + def __call__(self, *args, **kwargs): + if not args: + if hasattr(self, 'kwargs'): + raise TypeError("double mark-keywords?") + self.kwargs = kwargs.copy() + return self + else: + if not len(args) == 1 or not hasattr(args[0], 'func_dict'): + raise TypeError("need exactly one function to decorate, " + "got %r" %(args,)) + func = args[0] + mh = MarkHolder(getattr(self, 'kwargs', {})) + setattr(func, self.markname, mh) + return func + +class MarkHolder: + def __init__(self, kwargs): + self.__dict__.update(kwargs) + +def test_pytest_mark_api(): + mark = Mark() + py.test.raises(TypeError, "mark(x=3)") -def test_pytest_mark_getattr(): - mark = KeywordDecorator({}) def f(): pass - mark.hello(f) - assert f.hello == True + assert f.hello - mark.hello("test")(f) - assert f.hello == "test" + mark.world(x=3, y=4)(f) + assert f.world + assert f.world.x == 3 + assert f.world.y == 4 - py.test.raises(AttributeError, "mark._hello") - py.test.raises(AttributeError, "mark.__str__") - -def test_pytest_mark_call(): - mark = KeywordDecorator({}) - def f(): pass - mark(x=3)(f) - assert f.x == 3 - def g(): pass - mark(g) - assert not g.func_dict - - mark.hello(f) - assert f.hello == True - - mark.hello("test")(f) - assert f.hello == "test" - - mark("x1")(f) - assert f.mark == "x1" + py.test.raises(TypeError, "mark.some(x=3)(f=5)") def test_mark_plugin(testdir): p = testdir.makepyfile("""