From commits-noreply at bitbucket.org Sat Sep 1 09:59:24 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Sat, 01 Sep 2012 07:59:24 -0000 Subject: [py-svn] commit/pytest: 3 new changesets Message-ID: <20120901075924.32174.11308@bitbucket05.managed.contegix.com> 3 new commits in pytest: https://bitbucket.org/hpk42/pytest/changeset/15afc660b59c/ changeset: 15afc660b59c user: hpk42 date: 2012-08-26 16:30:01 summary: add talk from brianna and me from 2012 affected #: 1 file diff -r 60f144dcdcf4a488ef1114ead43a7d59df8f4663 -r 15afc660b59c97a2152f1371a1dfbba1464c4cf7 doc/en/talks.txt --- a/doc/en/talks.txt +++ b/doc/en/talks.txt @@ -12,8 +12,12 @@ Basic usage and funcargs: +- `pycon australia 2012 pytest talk from Brianna Laugher + `_ (`video `_, `slides `_, `code `_) +- `pycon 2012 US talk video from Holger Krekel `_ - `pycon 2010 tutorial PDF`_ and `tutorial1 repository`_ + Function arguments: - :ref:`mysetup` https://bitbucket.org/hpk42/pytest/changeset/99d9e72b0732/ changeset: 99d9e72b0732 user: hpk42 date: 2012-09-01 09:59:11 summary: merge affected #: 4 files diff -r 15afc660b59c97a2152f1371a1dfbba1464c4cf7 -r 99d9e72b0732d843b71ee9edec63d450fe27bef3 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -35,6 +35,8 @@ - fix issue128: show captured output when capsys/capfd are used +- fix issue179: propperly show the dependency chain of factories + - pluginmanager.register(...) now raises ValueError if the plugin has been already registered or the name is taken diff -r 15afc660b59c97a2152f1371a1dfbba1464c4cf7 -r 99d9e72b0732d843b71ee9edec63d450fe27bef3 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -781,7 +781,7 @@ # we want to catch a AssertionError # replace our subclass with the builtin one # see https://bitbucket.org/hpk42/pytest/issue/176/pytestraises - from exceptions import AssertionError as ExpectedException + from _pytest.assertion.util import BuiltinAssertionError as ExpectedException if not args: return RaisesContext(ExpectedException) @@ -1211,8 +1211,14 @@ def toterminal(self, tw): tw.line() - for line in self.factblines or []: - tw.line(line) + if self.factblines: + tw.line(' dependency of:') + for factorydef in self.factblines: + tw.line(' %s in %s' % ( + factorydef.argname, + factorydef.baseid, + )) + tw.line() for line in self.deflines: tw.line(" " + line.strip()) for line in self.errorstring.split("\n"): @@ -1308,16 +1314,14 @@ obj = getattr(holderobj, name) if not callable(obj): continue - # to avoid breaking on magic global callables - # we explicitly check if we get a sane code object - # else having mock.call in the globals fails for example - code = py.code.getrawcode(obj) - if not inspect.iscode(code): - continue # resource factories either have a pytest_funcarg__ prefix # or are "funcarg" marked marker = getattr(obj, "_pytestfactory", None) if marker is not None: + if not isinstance(marker, FactoryMarker): + # magic globals with __getattr__ + # give us something thats wrong for that case + continue assert not name.startswith(self._argprefix) argname = name scope = marker.scope diff -r 15afc660b59c97a2152f1371a1dfbba1464c4cf7 -r 99d9e72b0732d843b71ee9edec63d450fe27bef3 testing/test_junitxml.py --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -162,7 +162,7 @@ import pytest @pytest.mark.parametrize('arg1', "<&'", ids="<&'") def test_func(arg1): - print arg1 + print(arg1) assert 0 """) result, dom = runandparse(testdir) diff -r 15afc660b59c97a2152f1371a1dfbba1464c4cf7 -r 99d9e72b0732d843b71ee9edec63d450fe27bef3 testing/test_python.py --- a/testing/test_python.py +++ b/testing/test_python.py @@ -1422,9 +1422,9 @@ def test_raises_flip_builtin_AssertionError(self): # we replace AssertionError on python level # however c code might still raise the builtin one - import exceptions + from _pytest.assertion.util import BuiltinAssertionError pytest.raises(AssertionError,""" - raise exceptions.AssertionError + raise BuiltinAssertionError """) @pytest.mark.skipif('sys.version < "2.5"') @@ -1664,6 +1664,30 @@ "*2 passed*" ]) + def test_factory_uses_unknown_funcarg_as_dependency_error(self, testdir): + testdir.makepyfile(""" + import pytest + + @pytest.factory() + def fail(missing): + return + + @pytest.factory() + def call_fail(fail): + return + + def test_missing(call_fail): + pass + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + "*dependency of:*", + "*call_fail*", + "*def fail(*", + "*LookupError: no factory found for argument 'missing'", + ]) + + class TestResourceIntegrationFunctional: def test_parametrize_with_ids(self, testdir): https://bitbucket.org/hpk42/pytest/changeset/00fb4f72c38a/ changeset: 00fb4f72c38a user: hpk42 date: 2012-09-01 09:58:10 summary: fix issue185 monkeypatching time.time does not cause pytest to fail affected #: 6 files diff -r 99d9e72b0732d843b71ee9edec63d450fe27bef3 -r 00fb4f72c38ac921cd9ea3612f0de15878987536 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Changes between 2.2.4 and 2.3.0.dev ----------------------------------- +- fix issue185 monkeypatching time.time does not cause pytest to fail - fix issue172 duplicate call of pytest.setup-decoratored setup_module functions - fix junitxml=path construction so that if tests change the diff -r 99d9e72b0732d843b71ee9edec63d450fe27bef3 -r 00fb4f72c38ac921cd9ea3612f0de15878987536 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.3.0.dev10' +__version__ = '2.3.0.dev11' diff -r 99d9e72b0732d843b71ee9edec63d450fe27bef3 -r 00fb4f72c38ac921cd9ea3612f0de15878987536 _pytest/runner.py --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -1,6 +1,7 @@ """ basic collect and runtest protocol implementations """ -import py, sys, time +import py, sys +from time import time from py._code.code import TerminalRepr def pytest_namespace(): @@ -114,7 +115,7 @@ #: context of invocation: one of "setup", "call", #: "teardown", "memocollect" self.when = when - self.start = time.time() + self.start = time() try: try: self.result = func() @@ -123,7 +124,7 @@ except: self.excinfo = py.code.ExceptionInfo() finally: - self.stop = time.time() + self.stop = time() def __repr__(self): if self.excinfo: diff -r 99d9e72b0732d843b71ee9edec63d450fe27bef3 -r 00fb4f72c38ac921cd9ea3612f0de15878987536 doc/en/funcargs.txt --- a/doc/en/funcargs.txt +++ b/doc/en/funcargs.txt @@ -599,6 +599,35 @@ Basic ``pytest_generate_tests`` example --------------------------------------------- +.. XXX + + > line 598 "Basic ``pytest_generate_tests`` example" - I think this is + > not a very basic example! I think it is copied from parametrize.txt + > page, where it might make more sense. Here is what I would consider a + > basic example. + > + > # code + > def isSquare(n): + > n = n ** 0.5 + > return int(n) == n + > + > # test file + > def pytest_generate_tests(metafunc): + > squares = [1, 4, 9, 16, 25, 36, 49] + > for n in range(1, 50): + > expected = n in squares + > if metafunc.function.__name__ == 'test_isSquare': + > metafunc.addcall(id=n, funcargs=dict(n=n, + > expected=expected)) + > + > + > def test_isSquare(n, expected): + > assert isSquare(n) == expected + + +.. XXX + consider adding more examples, also mixed (factory-parametrized/test-function-parametrized, see mail from Brianna) + The ``pytest_generate_tests`` hook is typically used if you want to go beyond what ``@pytest.mark.parametrize`` offers. For example, let's say we want to execute a test with different computation diff -r 99d9e72b0732d843b71ee9edec63d450fe27bef3 -r 00fb4f72c38ac921cd9ea3612f0de15878987536 setup.py --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.3.0.dev10', + version='2.3.0.dev11', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff -r 99d9e72b0732d843b71ee9edec63d450fe27bef3 -r 00fb4f72c38ac921cd9ea3612f0de15878987536 testing/test_monkeypatch.py --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -193,3 +193,16 @@ tmpdir.chdir() mp.undo() assert os.getcwd() == tmpdir.strpath + +def test_issue185_time_breaks(testdir): + testdir.makepyfile(""" + import time + def test_m(monkeypatch): + def f(): + raise Exception + monkeypatch.setattr(time, "time", f) + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(""" + *1 passed* + """) Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 3 09:54:16 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Mon, 03 Sep 2012 07:54:16 -0000 Subject: [py-svn] commit/pytest: RonnyPfannschmidt: adapt the junit xml escaping test to my escaping changes Message-ID: <20120903075416.1741.2365@bitbucket23.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/6dcfb470cfb7/ changeset: 6dcfb470cfb7 user: RonnyPfannschmidt date: 2012-09-03 09:54:02 summary: adapt the junit xml escaping test to my escaping changes affected #: 1 file diff -r 00fb4f72c38ac921cd9ea3612f0de15878987536 -r 6dcfb470cfb7ddba4bec8fb486408abe4be0603e testing/test_junitxml.py --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -374,14 +374,14 @@ for i in invalid: - got = bin_xml_escape(unichr(i)) + got = bin_xml_escape(unichr(i)).uniobj if i <= 0xFF: expected = '#x%02X' % i else: expected = '#x%04X' % i assert got == expected for i in valid: - assert chr(i) == bin_xml_escape(unichr(i)) + assert chr(i) == bin_xml_escape(unichr(i)).uniobj def test_logxml_path_expansion(): from _pytest.junitxml import LogXML Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 3 10:12:40 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Mon, 03 Sep 2012 08:12:40 -0000 Subject: [py-svn] commit/pytest: RonnyPfannschmidt: fix issue 182: testdir.inprocess_run now considers passed plugins Message-ID: <20120903081240.1740.81347@bitbucket23.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/a5e7a5fa3c7e/ changeset: a5e7a5fa3c7e user: RonnyPfannschmidt date: 2012-09-03 10:12:30 summary: fix issue 182: testdir.inprocess_run now considers passed plugins affected #: 3 files diff -r 6dcfb470cfb7ddba4bec8fb486408abe4be0603e -r a5e7a5fa3c7e8aef9306f60c9e0d42c01e4252c7 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -54,6 +54,8 @@ - factory discovery no longer fails with magic global callables that provide no sane __code__ object (mock.call for example) +- fix issue 182: testdir.inprocess_run now considers passed plugins + - reporting refinements: - pytest_report_header now receives a "startdir" so that diff -r 6dcfb470cfb7ddba4bec8fb486408abe4be0603e -r a5e7a5fa3c7e8aef9306f60c9e0d42c01e4252c7 _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -355,7 +355,7 @@ if not plugins: plugins = [] plugins.append(Collect()) - ret = self.pytestmain(list(args), plugins=[Collect()]) + ret = self.pytestmain(list(args), plugins=plugins) reprec = rec[0] reprec.ret = ret assert len(rec) == 1 diff -r 6dcfb470cfb7ddba4bec8fb486408abe4be0603e -r a5e7a5fa3c7e8aef9306f60c9e0d42c01e4252c7 testing/test_pytester.py --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -123,3 +123,13 @@ except NameError: unichr = chr testdir.makepyfile(unichr(0xfffd)) + +def test_inprocess_plugins(testdir): + class Plugin(object): + configured = False + def pytest_configure(self, config): + self.configured = True + plugin = Plugin() + testdir.inprocess_run([], [plugin]) + + assert plugin.configured Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Sep 15 15:21:06 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Sat, 15 Sep 2012 13:21:06 -0000 Subject: [py-svn] commit/pytest: RonnyPfannschmidt: fix issue 188 - ensure sys.exc_info on py2 is clear before calling into a test Message-ID: <20120915132106.20602.64951@bitbucket24.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/7ae546be7b31/ changeset: 7ae546be7b31 user: RonnyPfannschmidt date: 2012-09-15 15:20:49 summary: fix issue 188 - ensure sys.exc_info on py2 is clear before calling into a test affected #: 3 files diff -r a5e7a5fa3c7e8aef9306f60c9e0d42c01e4252c7 -r 7ae546be7b31ac1fb585dbfb85771ac194e2d72c CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -56,6 +56,9 @@ - fix issue 182: testdir.inprocess_run now considers passed plugins +- fix issue 188: ensure sys.exc_info is clear on python2 + before calling into a test + - reporting refinements: - pytest_report_header now receives a "startdir" so that diff -r a5e7a5fa3c7e8aef9306f60c9e0d42c01e4252c7 -r 7ae546be7b31ac1fb585dbfb85771ac194e2d72c _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -114,11 +114,18 @@ def pytest_runtestloop(session): if session.config.option.collectonly: return True + + def getnextitem(i): + # this is a function to avoid python2 + # keeping sys.exc_info set when calling into a test + # python2 keeps sys.exc_info till the frame is left + try: + return session.items[i+1] + except IndexError: + return None + for i, item in enumerate(session.items): - try: - nextitem = session.items[i+1] - except IndexError: - nextitem = None + nextitem = getnextitem(i) item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) if session.shouldstop: raise session.Interrupted(session.shouldstop) diff -r a5e7a5fa3c7e8aef9306f60c9e0d42c01e4252c7 -r 7ae546be7b31ac1fb585dbfb85771ac194e2d72c testing/test_runner.py --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -180,7 +180,12 @@ raise Exception() def test_func(): - pass + import sys + # on python2 exc_info is keept till a function exits + # so we would end up calling test functions while + # sys.exc_info would return the indexerror + # from guessing the lastitem + assert sys.exc_info()[0] is None def teardown_function(func): raise ValueError(42) """) Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 17 08:41:38 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Mon, 17 Sep 2012 06:41:38 -0000 Subject: [py-svn] commit/pytest: 2 new changesets Message-ID: <20120917064138.22447.72517@bitbucket22.managed.contegix.com> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/changeset/80e95cadda16/ changeset: 80e95cadda16 user: hpk42 date: 2012-09-12 12:51:45 summary: modify detection of factories located in plugins, allowing pytest's own test functions to access plugin defined funcargs even if they use internal machinery instead of a full test run affected #: 1 file diff -r a5e7a5fa3c7e8aef9306f60c9e0d42c01e4252c7 -r 80e95cadda16514dbe6934a0f8e4c53fddd0cb2a _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1235,14 +1235,17 @@ self.session = session self.config = session.config self.arg2facspec = {} - session.config.pluginmanager.register(self, "funcmanage") + self._seenplugins = set() self._holderobjseen = set() self.setuplist = [] self._arg2finish = {} + session.config.pluginmanager.register(self, "funcmanage") ### XXX this hook should be called for historic events like pytest_configure - ### so that we don't have to do the below pytest_collection hook + ### so that we don't have to do the below pytest_configure hook def pytest_plugin_registered(self, plugin): + if plugin in self._seenplugins: + return #print "plugin_registered", plugin nodeid = "" try: @@ -1253,10 +1256,11 @@ if p.basename.startswith("conftest.py"): nodeid = p.dirpath().relto(self.session.fspath) self._parsefactories(plugin, nodeid) + self._seenplugins.add(plugin) @pytest.mark.tryfirst - def pytest_collection(self, session): - plugins = session.config.pluginmanager.getplugins() + def pytest_configure(self, config): + plugins = config.pluginmanager.getplugins() for plugin in plugins: self.pytest_plugin_registered(plugin) https://bitbucket.org/hpk42/pytest/changeset/9b4c51b4a2a0/ changeset: 9b4c51b4a2a0 user: hpk42 date: 2012-09-17 08:41:04 summary: merge affected #: 3 files diff -r 80e95cadda16514dbe6934a0f8e4c53fddd0cb2a -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -56,6 +56,9 @@ - fix issue 182: testdir.inprocess_run now considers passed plugins +- fix issue 188: ensure sys.exc_info is clear on python2 + before calling into a test + - reporting refinements: - pytest_report_header now receives a "startdir" so that diff -r 80e95cadda16514dbe6934a0f8e4c53fddd0cb2a -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -114,11 +114,18 @@ def pytest_runtestloop(session): if session.config.option.collectonly: return True + + def getnextitem(i): + # this is a function to avoid python2 + # keeping sys.exc_info set when calling into a test + # python2 keeps sys.exc_info till the frame is left + try: + return session.items[i+1] + except IndexError: + return None + for i, item in enumerate(session.items): - try: - nextitem = session.items[i+1] - except IndexError: - nextitem = None + nextitem = getnextitem(i) item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) if session.shouldstop: raise session.Interrupted(session.shouldstop) diff -r 80e95cadda16514dbe6934a0f8e4c53fddd0cb2a -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 testing/test_runner.py --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -180,7 +180,12 @@ raise Exception() def test_func(): - pass + import sys + # on python2 exc_info is keept till a function exits + # so we would end up calling test functions while + # sys.exc_info would return the indexerror + # from guessing the lastitem + assert sys.exc_info()[0] is None def teardown_function(func): raise ValueError(42) """) Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 17 17:32:36 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Mon, 17 Sep 2012 15:32:36 -0000 Subject: [py-svn] commit/pytest: 2 new changesets Message-ID: <20120917153236.14126.52951@bitbucket25.managed.contegix.com> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/changeset/6a73adc3a5f0/ changeset: 6a73adc3a5f0 user: hpk42 date: 2012-09-17 16:36:10 summary: drops special testcontext object in favour of "old" request object, simplifying communication and code for the 2.2-2.3 transition. also modify docs and examples. affected #: 10 files diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -9,19 +9,9 @@ it is constructed correctly from the original current working dir. - fix "python setup.py test" example to cause a proper "errno" return - fix issue165 - fix broken doc links and mention stackoverflow for FAQ -- fix issue139 - merge FuncargRequest and Item API such that - funcarg-functionality is now directly available on the "item" - object passed to the various pytest_runtest hooks. This allows more - sensitive behaviour of e.g. the pytest-django plugin which previously - had no full access to all instantiated funcargs. - This internal API re-organisation is a fully backward compatible - change: existing factories accepting a "request" object will - get a Function "item" object which carries the same API. In fact, - the FuncargRequest API (or rather then a ResourceRequestAPI) - could be available for all collection and item nodes but this is - left for later consideration because it would render the documentation - invalid and the "funcarg" naming sounds odd in context of - directory, file, class, etc. nodes. +- fix issue139 - introduce @pytest.factory which allows direct scoping + and parametrization of funcarg factories. Introduce new @pytest.setup + marker to allow the writing of setup functions which accept funcargs. - catch unicode-issues when writing failure representations to terminal to prevent the whole session from crashing - fix xfail/skip confusion: a skip-mark or an imperative pytest.skip diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -912,31 +912,51 @@ def __hash__(self): return hash((self.parent, self.name)) +scope2props = dict(session=()) +scope2props["module"] = ("fspath", "module") +scope2props["class"] = scope2props["module"] + ("cls",) +scope2props["instance"] = scope2props["class"] + ("instance", ) +scope2props["function"] = scope2props["instance"] + ("function", "keywords") + +def scopeproperty(name=None, doc=None): + def decoratescope(func): + scopename = name or func.__name__ + def provide(self): + if func.__name__ in scope2props[self.scope]: + return func(self) + raise AttributeError("%s not available in %s-scoped context" % ( + scopename, self.scope)) + return property(provide, None, None, func.__doc__) + return decoratescope + +def pytest_funcarg__request(__request__): + return __request__ + +#def pytest_funcarg__testcontext(__request__): +# return __request__ class FuncargRequest: - """ (old-style) A request for function arguments from a test function. + """ A request for function arguments from a test or setup function. - Note that there is an optional ``param`` attribute in case - there was an invocation to metafunc.addcall(param=...). - If no such call was done in a ``pytest_generate_tests`` - hook, the attribute will not be present. Note that - as of pytest-2.3 you probably rather want to use the - testcontext object and mark your factory with a ``@pytest.factory`` - marker. + A request object gives access to attributes of the requesting + test context. It has an optional ``param`` attribute in case + of parametrization. """ def __init__(self, pyfuncitem): self._pyfuncitem = pyfuncitem if hasattr(pyfuncitem, '_requestparam'): self.param = pyfuncitem._requestparam + #: Scope string, one of "function", "cls", "module", "session" + self.scope = "function" self.getparent = pyfuncitem.getparent self._funcargs = self._pyfuncitem.funcargs.copy() + self._funcargs["__request__"] = self self._name2factory = {} self.funcargmanager = pyfuncitem.session.funcargmanager self._currentarg = None self.funcargnames = getfuncargnames(self.function) self.parentid = pyfuncitem.parent.nodeid - self.scope = "function" self._factorystack = [] def _getfaclist(self, argname): @@ -956,21 +976,42 @@ self.parentid) return facdeflist - def raiseerror(self, msg): - """ raise a FuncargLookupError with the given message. """ - raise self.funcargmanager.FuncargLookupError(self.function, msg) + @property + def config(self): + """ the pytest config object associated with this request. """ + return self._pyfuncitem.config - @property + + @scopeproperty() def function(self): - """ function object of the test invocation. """ + """ test function object if the request has a per-function scope. """ return self._pyfuncitem.obj + @scopeproperty("class") + def cls(self): + """ class (can be None) where the test function was collected. """ + clscol = self._pyfuncitem.getparent(pytest.Class) + if clscol: + return clscol.obj + + @scopeproperty() + def instance(self): + """ instance (can be None) on which test function was collected. """ + return py.builtin._getimself(self.function) + + @scopeproperty() + def module(self): + """ python module object where the test function was collected. """ + return self._pyfuncitem.getparent(pytest.Module).obj + + @scopeproperty() + def fspath(self): + """ the file system path of the test module which collected this test. """ + return self._pyfuncitem.fspath + @property def keywords(self): - """ keywords of the test function item. - - .. versionadded:: 2.0 - """ + """ keywords of the test function item. """ return self._pyfuncitem.keywords @property @@ -978,41 +1019,23 @@ """ pytest session object. """ return self._pyfuncitem.session - @property - def module(self): - """ module where the test function was collected. """ - return self._pyfuncitem.getparent(pytest.Module).obj - @property - def cls(self): - """ class (can be None) where the test function was collected. """ - clscol = self._pyfuncitem.getparent(pytest.Class) - if clscol: - return clscol.obj - @property - def instance(self): - """ instance (can be None) on which test function was collected. """ - return py.builtin._getimself(self.function) - @property - def config(self): - """ the pytest config object associated with this request. """ - return self._pyfuncitem.config + def addfinalizer(self, finalizer): + """add finalizer/teardown function to be called after the + last test within the requesting test context finished + execution. """ + self._addfinalizer(finalizer, scope=self.scope) - @property - def fspath(self): - """ the file system path of the test module which collected this test. """ - return self._pyfuncitem.fspath - - def _fillfuncargs(self): - if self.funcargnames: - assert not getattr(self._pyfuncitem, '_args', None), ( - "yielded functions cannot have funcargs") - while self.funcargnames: - argname = self.funcargnames.pop(0) - if argname not in self._pyfuncitem.funcargs: - self._pyfuncitem.funcargs[argname] = \ - self.getfuncargvalue(argname) + def _addfinalizer(self, finalizer, scope): + if scope != "function" and hasattr(self, "param"): + # parametrized resources are sorted by param + # so we rather store finalizers per (argname, param) + colitem = (self._currentarg, self.param) + else: + colitem = self._getscopeitem(scope) + self._pyfuncitem.session._setupstate.addfinalizer( + finalizer=finalizer, colitem=colitem) def applymarker(self, marker): """ Apply a marker to a single test function invocation. @@ -1026,11 +1049,33 @@ raise ValueError("%r is not a py.test.mark.* object") self._pyfuncitem.keywords[marker.markname] = marker + def raiseerror(self, msg): + """ raise a FuncargLookupError with the given message. """ + raise self.funcargmanager.FuncargLookupError(self.function, msg) + + + def _fillfuncargs(self): + if self.funcargnames: + assert not getattr(self._pyfuncitem, '_args', None), ( + "yielded functions cannot have funcargs") + while self.funcargnames: + argname = self.funcargnames.pop(0) + if argname not in self._pyfuncitem.funcargs: + self._pyfuncitem.funcargs[argname] = \ + self.getfuncargvalue(argname) + + + def _callsetup(self): + self.funcargmanager.ensure_setupcalls(self) + def cached_setup(self, setup, teardown=None, scope="module", extrakey=None): - """ Return a testing resource managed by ``setup`` & + """ (deprecated) Return a testing resource managed by ``setup`` & ``teardown`` calls. ``scope`` and ``extrakey`` determine when the ``teardown`` function will be called so that subsequent calls to - ``setup`` would recreate the resource. + ``setup`` would recreate the resource. With pytest-2.3 you + do not need ``cached_setup()`` as you can directly declare a scope + on a funcarg factory and register a finalizer through + ``request.addfinalizer()``. :arg teardown: function receiving a previously setup resource. :arg setup: a no-argument function creating a resource. @@ -1061,16 +1106,19 @@ return val - def _callsetup(self): - self.funcargmanager.ensure_setupcalls(self) def getfuncargvalue(self, argname): - """ Retrieve a function argument by name for this test + """ (deprecated) Retrieve a function argument by name for this test function invocation. This allows one function argument factory to call another function argument factory. If there are two funcarg factories for the same test function argument the first factory may use ``getfuncargvalue`` to call the second one and do something additional with the resource. + + **Note**, however, that starting with + pytest-2.3 it is easier and better to directly state the needed + funcarg in the factory signature. This will also work seemlessly + with parametrization and the new resource setup optimizations. """ try: return self._funcargs[argname] @@ -1091,12 +1139,7 @@ factory_kwargs = {} def fillfactoryargs(): for newname in newnames: - if newname == "testcontext": - val = TestContextResource(self) - elif newname == "request" and not factorydef.new: - val = self - else: - val = self.getfuncargvalue(newname) + val = self.getfuncargvalue(newname) factory_kwargs[newname] = val node = self._pyfuncitem @@ -1161,21 +1204,6 @@ return self._pyfuncitem.getparent(pytest.Module) raise ValueError("unknown finalization scope %r" %(scope,)) - def addfinalizer(self, finalizer): - """add finalizer function to be called after test function - finished execution. """ - self._addfinalizer(finalizer, scope=self.scope) - - def _addfinalizer(self, finalizer, scope): - if scope != "function" and hasattr(self, "param"): - # parametrized resources are sorted by param - # so we rather store finalizers per (argname, param) - colitem = (self._currentarg, self.param) - else: - colitem = self._getscopeitem(scope) - self._pyfuncitem.session._setupstate.addfinalizer( - finalizer=finalizer, colitem=colitem) - def __repr__(self): return "" %(self._pyfuncitem) @@ -1396,17 +1424,13 @@ if setupcall.active: continue request._factorystack.append(setupcall) + mp = monkeypatch() try: - testcontext = TestContextSetup(request, setupcall) + #mp.setattr(request, "_setupcall", setupcall, raising=False) + mp.setattr(request, "scope", setupcall.scope) kwargs = {} for name in setupcall.funcargnames: - try: - kwargs[name] = request.getfuncargvalue(name) - except FuncargLookupError: - if name == "testcontext": - kwargs[name] = testcontext - else: - raise + kwargs[name] = request.getfuncargvalue(name) scope = setupcall.scope or "function" scol = setupcall.scopeitem = request._getscopeitem(scope) self.session._setupstate.addfinalizer(setupcall.finish, scol) @@ -1414,6 +1438,7 @@ self.addargfinalizer(setupcall.finish, argname) setupcall.execute(kwargs) finally: + mp.undo() request._factorystack.remove(setupcall) def addargfinalizer(self, finalizer, argname): @@ -1427,69 +1452,6 @@ except ValueError: pass -scope2props = dict(session=()) -scope2props["module"] = ("fspath", "module") -scope2props["class"] = scope2props["module"] + ("cls",) -scope2props["function"] = scope2props["class"] + ("function", "keywords") - -def scopeprop(attr, name=None, doc=None): - if doc is None: - doc = ("%s of underlying test context, may not exist " - "if the testcontext has a higher scope" % (attr,)) - name = name or attr - def get(self): - if name in scope2props[self.scope]: - return getattr(self._request, name) - raise AttributeError("%s not available in %s-scoped context" % ( - name, self.scope)) - return property(get, doc=doc) - -def rprop(attr, doc=None): - if doc is None: - doc = "%s of underlying test context" % attr - return property(lambda x: getattr(x._request, attr), doc=doc) - -class TestContext(object): - """ Basic objects of the current testing context. """ - def __init__(self, request, scope): - self._request = request - self.scope = scope - - # no getfuncargvalue(), cached_setup, applymarker helpers here - # on purpose - - config = rprop("config", "pytest config object.") - session = rprop("session", "pytest session object.") - - function = scopeprop("function") - module = scopeprop("module") - cls = scopeprop("class", "cls") - instance = scopeprop("instance") - fspath = scopeprop("fspath") - #keywords = scopeprop("keywords") - -class TestContextSetup(TestContext): - def __init__(self, request, setupcall): - self._setupcall = setupcall - self._finalizers = [] - super(TestContextSetup, self).__init__(request, setupcall.scope) - - def addfinalizer(self, finalizer): - """ Add a finalizer to be called after the last test in the - test context executes. """ - self._setupcall.addfinalizer(finalizer) - -class TestContextResource(TestContext): - param = rprop("param") - - def __init__(self, request): - super(TestContextResource, self).__init__(request, request.scope) - - def addfinalizer(self, finalizer): - """ Add a finalizer to be called after the last test in the - test context executes. """ - self._request.addfinalizer(finalizer) - class SetupCall: """ a container/helper for managing calls to setup functions. """ diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 doc/en/example/costlysetup/conftest.py --- a/doc/en/example/costlysetup/conftest.py +++ b/doc/en/example/costlysetup/conftest.py @@ -2,9 +2,9 @@ import pytest @pytest.factory("session") -def setup(testcontext): +def setup(request): setup = CostlySetup() - testcontext.addfinalizer(setup.finalize) + request.addfinalizer(setup.finalize) return setup class CostlySetup: diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 doc/en/example/multipython.py --- a/doc/en/example/multipython.py +++ b/doc/en/example/multipython.py @@ -6,13 +6,13 @@ pythonlist = ['python2.4', 'python2.5', 'python2.6', 'python2.7', 'python2.8'] @pytest.factory(params=pythonlist) -def python1(testcontext, tmpdir): +def python1(request, tmpdir): picklefile = tmpdir.join("data.pickle") - return Python(testcontext.param, picklefile) + return Python(request.param, picklefile) @pytest.factory(params=pythonlist) -def python2(testcontext, python1): - return Python(testcontext.param, python1.picklefile) +def python2(request, python1): + return Python(request.param, python1.picklefile) class Python: def __init__(self, version, picklefile): diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 doc/en/funcarg_compare.txt --- a/doc/en/funcarg_compare.txt +++ b/doc/en/funcarg_compare.txt @@ -66,15 +66,15 @@ Instead of calling cached_setup(), you can use the :ref:`@pytest.factory <@pytest.factory>` decorator and directly state the scope:: @pytest.factory(scope="session") - def db(testcontext): + def db(request): # factory will only be invoked once per session - db = DataBase() - testcontext.addfinalizer(db.destroy) # destroy when session is finished + request.addfinalizer(db.destroy) # destroy when session is finished return db This factory implementation does not need to call ``cached_setup()`` anymore because it will only be invoked once per session. Moreover, the -``testcontext.addfinalizer()`` registers a finalizer according to the specified +``request.addfinalizer()`` registers a finalizer according to the specified resource scope on which the factory function is operating. @@ -88,29 +88,29 @@ sets. pytest-2.3 introduces a decorator for use on the factory itself:: @pytest.factory(params=["mysql", "pg"]) - def db(testcontext): - ... # use testcontext.param + def db(request): + ... # use request.param Here the factory will be invoked twice (with the respective "mysql" -and "pg" values set as ``testcontext.param`` attributes) and and all of +and "pg" values set as ``request.param`` attributes) and and all of the tests requiring "db" will run twice as well. The "mysql" and "pg" values will also be used for reporting the test-invocation variants. This new way of parametrizing funcarg factories should in many cases allow to re-use already written factories because effectively -``testcontext.param`` are already the parametrization attribute for test +``request.param`` are already the parametrization attribute for test functions/classes were parametrized via :py:func:`~_pytest.python.Metafunc.parametrize(indirect=True)` calls. Of course it's perfectly fine to combine parametrization and scoping:: @pytest.factory(scope="session", params=["mysql", "pg"]) - def db(testcontext): - if testcontext.param == "mysql": + def db(request): + if request.param == "mysql": db = MySQL() - elif testcontext.param == "pg": + elif request.param == "pg": db = PG() - testcontext.addfinalizer(db.destroy) # destroy when session is finished + request.addfinalizer(db.destroy) # destroy when session is finished return db This would execute all tests requiring the per-session "db" resource twice, @@ -126,7 +126,7 @@ argument:: @pytest.factory() - def db(testcontext): + def db(request): ... The name under which the funcarg resource can be requested is ``db``. diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 doc/en/funcargs.txt --- a/doc/en/funcargs.txt +++ b/doc/en/funcargs.txt @@ -9,44 +9,45 @@ .. note:: - pytest-2.3 introduces major refinements to the original funcarg - mechanism introduced to pytest-2.0. While the old way - remains fully supported, it is recommended to use the refined - mechanisms. See also the `compatibility notes`_ and the detailed - :ref:`reasoning for the new funcarg and setup functions `. + pytest-2.3 introduces major refinements to the test setup and funcarg + mechanisms introduced to pytest-2.0. All pre-2.3 usages remain + supported and several use cases, among them scoping and parametrization + of funcarg resources, are now easier to accomplish. For more background, + see `compatibility notes`_ and the detailed :ref:`reasoning for the new + funcarg and setup functions `. .. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection Introduction ==================== -py.test supports the injection of objects into test and setup functions +pytest supports the injection of test resources into test and setup functions and flexibly control their life cycle in relation to the overall test -execution. Moreover, you can run a test function multiple times -injecting different objects. +execution. Moreover, tests can get executed multiple times if you have +different variants of test resources to test with. The basic mechanism for injecting objects is called the *funcarg -mechanism* because objects are **injected** when a test or setup -function states it as an **argument**. The injected argument is -created by a call to a registered **funcarg factory**. This approach is -an example of `Dependency Injection`_ and helps to de-couple test code -from the setup of required objects: at test writing time you do not need -to care for the details of where and how your required resources are -constructed, if they are shared on a per-class, module or session basis, -or if your test function is invoked multiple times with differently -configured resource instances. +mechanism* because objects are injected when a test or setup +**function** states it as an **argument**. The injected argument +is created by a call to a registered **funcarg factory** for each argument +name. This mechanism is an example of `Dependency Injection`_ +and helps to de-couple test code from the setup of required +objects: at test writing time you do not need to care for the details of +where and how your required test resources are constructed, if they are +shared on a per-class, module or session basis, or if your test function +is invoked multiple times with differently configured resource +instances. + +Funcarg dependency injection allows to organise test resources +in a modular explicit way so that test functions state their needs +in their signature. pytest additionally offers powerful xunit-style +:ref:`setup functions ` for the cases where you need +to create implicit test state that is not passed explicitely to test functions. When a test function is invoked multiple times with different arguments we -speak of **parametrized testing**. This is useful if you want to test -e.g. against different database backends or want to write a parametrized -test function, checking that certain inputs lead to certain outputs. -You can parametrize funcarg factories, parametrize test function -arguments or even implement your own parametrization scheme through a -plugin hook. - -pytest additionally offers powerful xunit-style :ref:`setup functions ` for the cases where you need to create implicit test state -that is not passed explicitely to test functions. +speak of **parametrized testing**. You can use it e. g. to repeatedly run test +functions against different database backends or to check that certain +inputs lead to certain outputs. Concretely, there are three main means of funcarg management: @@ -54,7 +55,7 @@ their scoping and parametrization. Factories can themselves receive resources through their function arguments, easing the setup of `interdependent resources`_. Factories can use - the special `testcontext`_ object to access details from where + the special `request`_ object to access details from where the factory or setup function is called and for registering finalizers. * a `@pytest.mark.parametrize`_ marker for executing test functions @@ -103,8 +104,8 @@ $ py.test test_simplefactory.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 - plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 1 items test_simplefactory.py F @@ -119,7 +120,7 @@ E assert 42 == 17 test_simplefactory.py:8: AssertionError - ========================= 1 failed in 0.02 seconds ========================= + ========================= 1 failed in 0.01 seconds ========================= This shows that the test function was called with a ``myfuncarg`` argument value of ``42`` and the assert fails as expected. Here is @@ -186,7 +187,7 @@ import smtplib @pytest.factory(scope="session") - def smtp(testcontext): + def smtp(request): return smtplib.SMTP("merlinux.eu") The name of the factory is ``smtp`` (the factory function name) @@ -214,7 +215,7 @@ ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -226,7 +227,7 @@ test_module.py:5: AssertionError ________________________________ test_noop _________________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -235,7 +236,7 @@ E assert 0 test_module.py:10: AssertionError - 2 failed in 0.20 seconds + 2 failed in 0.26 seconds you see the two ``assert 0`` failing and can also see that the same (session-scoped) object was passed into the two test functions. @@ -247,7 +248,7 @@ Extending the previous example, we can flag the factory to create two ``smtp`` values which will cause all tests using it to run twice with two different values. The factory function gets -access to each parameter through the special `testcontext`_ object:: +access to each parameter through the special `request`_ object:: # content of conftest.py import pytest @@ -255,11 +256,11 @@ @pytest.factory(scope="session", params=["merlinux.eu", "mail.python.org"]) - def smtp(testcontext): - return smtplib.SMTP(testcontext.param) + def smtp(request): + return smtplib.SMTP(request.param) The main change is the definition of a ``params`` list in the -``factory``-marker and the ``testcontext.param`` access within the +``factory``-marker and the ``request.param`` access within the factory function. No test function code needs to change. So let's just do another run:: @@ -269,7 +270,7 @@ ================================= FAILURES ================================= __________________________ test_ehlo[merlinux.eu] __________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -281,7 +282,7 @@ test_module.py:5: AssertionError __________________________ test_noop[merlinux.eu] __________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -292,7 +293,7 @@ test_module.py:10: AssertionError ________________________ test_ehlo[mail.python.org] ________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -303,7 +304,7 @@ test_module.py:4: AssertionError ________________________ test_noop[mail.python.org] ________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -312,7 +313,7 @@ E assert 0 test_module.py:10: AssertionError - 4 failed in 6.00 seconds + 4 failed in 6.17 seconds We get four failures because we are running the two tests twice with different ``smtp`` instantiations as defined on the factory. @@ -324,7 +325,7 @@ Further extending the ``smtp`` example, we now want to properly close a smtp server connection after the last test using it -has been run. We can do this by calling the ``testcontext.addfinalizer()`` +has been run. We can do this by calling the ``request.addfinalizer()`` helper:: # content of conftest.py @@ -333,12 +334,12 @@ @pytest.factory(scope="session", params=["merlinux.eu", "mail.python.org"]) - def smtp(testcontext): - smtp = smtplib.SMTP(testcontext.param) + def smtp(request): + smtp = smtplib.SMTP(request.param) def fin(): print ("finalizing %s" % smtp) smtp.close() - testcontext.addfinalizer(fin) + request.addfinalizer(fin) return smtp We also add a print call and then run py.test without the default @@ -347,9 +348,9 @@ $ py.test -s -q --tb=no collecting ... collected 4 items FFFF - 4 failed in 5.62 seconds - finalizing - finalizing + 4 failed in 6.40 seconds + finalizing + finalizing We see that the two ``smtp`` instances are finalized appropriately. @@ -360,8 +361,8 @@ $ py.test --collectonly =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 - plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 4 items @@ -369,7 +370,7 @@ - ============================= in 0.02 seconds ============================= + ============================= in 0.01 seconds ============================= Note that pytest orders your test run by resource usage, minimizing the number of active resources at any given time. @@ -404,15 +405,15 @@ $ py.test -v test_appsetup.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 -- /home/hpk/venv/1/bin/python - cachedir: /home/hpk/tmp/doc-exec-414/.cache - plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python + cachedir: /home/hpk/tmp/doc-exec-423/.cache + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 2 items test_appsetup.py:12: test_exists[merlinux.eu] PASSED test_appsetup.py:12: test_exists[mail.python.org] PASSED - ========================= 2 passed in 5.37 seconds ========================= + ========================= 2 passed in 6.82 seconds ========================= Due to the parametrization of ``smtp`` the test will run twice with two different ``App`` instances and respective smtp servers. There is no @@ -445,17 +446,17 @@ import pytest @pytest.factory(scope="module", params=["mod1", "mod2"]) - def modarg(testcontext): - param = testcontext.param + def modarg(request): + param = request.param print "create", param def fin(): print "fin", param - testcontext.addfinalizer(fin) + request.addfinalizer(fin) return param @pytest.factory(scope="function", params=[1,2]) - def otherarg(testcontext): - return testcontext.param + def otherarg(request): + return request.param def test_0(otherarg): print " test0", otherarg @@ -468,9 +469,9 @@ $ py.test -v -s test_module.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 -- /home/hpk/venv/1/bin/python - cachedir: /home/hpk/tmp/doc-exec-414/.cache - plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python + cachedir: /home/hpk/tmp/doc-exec-423/.cache + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 8 items test_module.py:16: test_0[1] PASSED @@ -501,26 +502,29 @@ resource was executed before the ``mod2`` resource was setup. .. currentmodule:: _pytest.python -.. _`testcontext`: +.. _`request`: -``testcontext``: interacting with test context ---------------------------------------------------- +``request``: interacting with test invocation context +------------------------------------------------------- -The ``testcontext`` object may be used by `@pytest.factory`_ or -:ref:`@pytest.setup ` marked functions. It contains -information relating to the test context within which the marked -function executes. Moreover, you can call -``testcontext.addfinalizer(myfinalizer)`` in order to trigger a call to -``myfinalizer`` after the last test in the test context has executed. -If passed to a parametrized factory ``testcontext.param`` will contain a -parameter (one value out of the ``params`` list specified with the -`@pytest.factory`_ marker). +The ``request`` object may be received by `@pytest.factory`_ or +:ref:`@pytest.setup ` marked functions and provides methods -.. autoclass:: _pytest.python.TestContext() +* to inspect attributes of the requesting test context, such as + ``function``, ``cls``, ``module``, ``session`` and the pytest + ``config`` object. A request object passed to a parametrized factory + will also carry a ``request.param`` object (A parametrized factory and + all of its dependent tests will be called with each of the factory-specified + ``params``). + +* to add finalizers/teardowns to be invoked when the last + test of the requesting test context executes + + +.. autoclass:: _pytest.python.FuncargRequest() :members: - .. _`test generators`: .. _`parametrizing-tests`: .. _`parametrized test functions`: @@ -566,7 +570,6 @@ will be run three times:: $ py.test -q - collecting ... collected 13 items ....F........ ================================= FAILURES ================================= @@ -585,7 +588,7 @@ E + where 54 = eval('6*9') test_expectation.py:8: AssertionError - 1 failed, 12 passed in 5.76 seconds + 1 failed, 12 passed in 6.41 seconds As expected only one pair of input/output values fails the simple test function. As usual you can see the ``input`` and ``output`` values in the traceback. @@ -662,11 +665,11 @@ $ py.test -q test_compute.py collecting ... collected 2 items .. - 2 passed in 0.02 seconds + 2 passed in 0.01 seconds And we run five tests if we add the ``--all`` option:: - $ py.test -q --all + $ py.test -q --all test_compute.py collecting ... collected 5 items ....F ================================= FAILURES ================================= @@ -679,7 +682,7 @@ E assert 4 < 4 test_compute.py:3: AssertionError - 1 failed, 4 passed in 0.03 seconds + 1 failed, 4 passed in 0.02 seconds As expected when running the full range of ``param1`` values we'll get an error on the last one. @@ -748,13 +751,4 @@ use remains fully supported and existing code using it should run unmodified. -.. _request: -The request object passed to old-style factories ------------------------------------------------------------------ - -Old-style funcarg factory definitions can receive a :py:class:`~_pytest.python.FuncargRequest` object which -provides methods to manage caching and finalization in the context of the -test invocation as well as several attributes of the the underlying test item. - - diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 doc/en/plugins.txt --- a/doc/en/plugins.txt +++ b/doc/en/plugins.txt @@ -331,9 +331,6 @@ Reference of objects involved in hooks =========================================================== -.. autoclass:: _pytest.python.FuncargRequest() - :members: - .. autoclass:: _pytest.config.Config() :members: diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 doc/en/setup.txt --- a/doc/en/setup.txt +++ b/doc/en/setup.txt @@ -33,7 +33,7 @@ - fully interoperate with parametrized resources, - can be defined in a plugin or :ref:`conftest.py ` file and get called on a per-session, per-module, per-class or per-function basis, -- can access the :ref:`testcontext ` for which the setup is called, +- can access the :ref:`request ` for which the setup is called, - can precisely control teardown by registering one or multiple teardown functions as soon as they have performed some actions which need undoing, eliminating the no need for a separate @@ -140,8 +140,8 @@ return GlobalResource() @pytest.setup(scope="module") - def setresource(testcontext, globresource): - testcontext.module.globresource = globresource + def setresource(request, globresource): + request.module.globresource = globresource Now any test module can access ``globresource`` as a module global:: @@ -178,16 +178,16 @@ self.param = param @pytest.factory(scope="session", params=[1,2]) - def globresource(testcontext): - g = GlobalResource(testcontext.param) + def globresource(request): + g = GlobalResource(request.param) def fin(): print "finalizing", g - testcontext.addfinalizer(fin) + request.addfinalizer(fin) return g @pytest.setup(scope="module") - def setresource(testcontext, globresource): - testcontext.module.globresource = globresource + def setresource(request, globresource): + request.module.globresource = globresource And then re-run our test module:: diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 testing/test_python.py --- a/testing/test_python.py +++ b/testing/test_python.py @@ -1590,8 +1590,8 @@ result = testdir.makeconftest(""" import pytest @pytest.setup() - def mysetup(testcontext): - testcontext.uses_funcarg("db") + def mysetup(request): + request.uses_funcarg("db") """) result = testdir.runpytest() assert result.ret == 0 @@ -1647,9 +1647,9 @@ import pytest l = [] @pytest.factory(params=[1,2]) - def arg1(testcontext): + def arg1(request): l.append(1) - return testcontext.param + return request.param @pytest.factory() def arg2(arg1): @@ -1758,7 +1758,7 @@ testdir.makeconftest(""" import pytest @pytest.setup() - def perfunction(testcontext, tmpdir): + def perfunction(request, tmpdir): pass @pytest.factory() @@ -1782,10 +1782,10 @@ setupcalls, allnames = fm.getsetuplist(item.nodeid) assert len(setupcalls) == 2 assert setupcalls[0].func.__name__ == "perfunction" - assert "testcontext" in setupcalls[0].funcargnames + assert "request" in setupcalls[0].funcargnames assert "tmpdir" in setupcalls[0].funcargnames assert setupcalls[1].func.__name__ == "perfunction2" - assert "testcontext" not in setupcalls[1].funcargnames + assert "request" not in setupcalls[1].funcargnames assert "arg1" in setupcalls[1].funcargnames assert "tmpdir" not in setupcalls[1].funcargnames #assert "tmpdir" in setupcalls[1].depfuncargs @@ -1842,8 +1842,8 @@ import pytest l = [] @pytest.factory(params=[1,2]) - def arg(testcontext): - return testcontext.param + def arg(request): + return request.param @pytest.setup() def something(arg): @@ -1868,12 +1868,12 @@ l = [] @pytest.factory(scope="session", params=[1,2]) - def arg(testcontext): - return testcontext.param + def arg(request): + return request.param @pytest.setup(scope="function") - def append(testcontext, arg): - if testcontext.function.__name__ == "test_some": + def append(request, arg): + if request.function.__name__ == "test_some": l.append(arg) def test_some(): @@ -1894,18 +1894,18 @@ l = [] @pytest.factory(scope="function", params=[1,2]) - def farg(testcontext): - return testcontext.param + def farg(request): + return request.param @pytest.factory(scope="class", params=list("ab")) - def carg(testcontext): - return testcontext.param + def carg(request): + return request.param - @pytest.setup(scope="class") - def append(testcontext, farg, carg): + @pytest.setup(scope="function") + def append(request, farg, carg): def fin(): l.append("fin_%s%s" % (carg, farg)) - testcontext.addfinalizer(fin) + request.addfinalizer(fin) """) testdir.makepyfile(""" import pytest @@ -1950,8 +1950,8 @@ testdir.makepyfile(""" import pytest @pytest.factory(params=["a", "b", "c"]) - def arg(testcontext): - return testcontext.param + def arg(request): + return request.param l = [] def test_param(arg): l.append(arg) @@ -2011,10 +2011,10 @@ finalized = [] created = [] @pytest.factory(scope="module") - def arg(testcontext): + def arg(request): created.append(1) - assert testcontext.scope == "module" - testcontext.addfinalizer(lambda: finalized.append(1)) + assert request.scope == "module" + request.addfinalizer(lambda: finalized.append(1)) def pytest_funcarg__created(request): return len(created) def pytest_funcarg__finalized(request): @@ -2050,14 +2050,14 @@ finalized = [] created = [] @pytest.factory(scope="function") - def arg(testcontext): + def arg(request): pass """) testdir.makepyfile( test_mod1=""" import pytest @pytest.factory(scope="session") - def arg(testcontext): + def arg(request): %s def test_1(arg): pass @@ -2091,8 +2091,8 @@ testdir.makepyfile(""" import pytest @pytest.factory(scope="module", params=["a", "b", "c"]) - def arg(testcontext): - return testcontext.param + def arg(request): + return request.param l = [] def test_param(arg): l.append(arg) @@ -2109,7 +2109,7 @@ testdir.makeconftest(""" import pytest @pytest.factory(scope="function") - def arg(testcontext): + def arg(request): pass """) testdir.makepyfile(""" @@ -2131,8 +2131,8 @@ import pytest @pytest.factory(scope="module", params=[1, 2]) - def arg(testcontext): - return testcontext.param + def arg(request): + return request.param l = [] def test_1(arg): @@ -2198,18 +2198,18 @@ l = [] @pytest.factory(scope="function", params=[1,2]) - def farg(testcontext): - return testcontext.param + def farg(request): + return request.param @pytest.factory(scope="class", params=list("ab")) - def carg(testcontext): - return testcontext.param + def carg(request): + return request.param - @pytest.setup(scope="class") - def append(testcontext, farg, carg): + @pytest.setup(scope="function") + def append(request, farg, carg): def fin(): l.append("fin_%s%s" % (carg, farg)) - testcontext.addfinalizer(fin) + request.addfinalizer(fin) """) testdir.makepyfile(""" import pytest @@ -2244,18 +2244,18 @@ import pytest @pytest.factory(scope="function", params=[1, 2]) - def arg(testcontext): - param = testcontext.param - testcontext.addfinalizer(lambda: l.append("fin:%s" % param)) + def arg(request): + param = request.param + request.addfinalizer(lambda: l.append("fin:%s" % param)) l.append("create:%s" % param) - return testcontext.param + return request.param @pytest.factory(scope="module", params=["mod1", "mod2"]) - def modarg(testcontext): - param = testcontext.param - testcontext.addfinalizer(lambda: l.append("fin:%s" % param)) + def modarg(request): + param = request.param + request.addfinalizer(lambda: l.append("fin:%s" % param)) l.append("create:%s" % param) - return testcontext.param + return request.param l = [] def test_1(arg): @@ -2288,11 +2288,11 @@ import pytest @pytest.factory(scope="module", params=[1, 2]) - def arg(testcontext): - testcontext.config.l = l # to access from outer - x = testcontext.param - testcontext.addfinalizer(lambda: l.append("fin%s" % x)) - return testcontext.param + def arg(request): + request.config.l = l # to access from outer + x = request.param + request.addfinalizer(lambda: l.append("fin%s" % x)) + return request.param l = [] def test_1(arg): @@ -2317,10 +2317,10 @@ import pytest @pytest.factory(scope="function", params=[1, 2]) - def arg(testcontext): - x = testcontext.param - testcontext.addfinalizer(lambda: l.append("fin%s" % x)) - return testcontext.param + def arg(request): + x = request.param + request.addfinalizer(lambda: l.append("fin%s" % x)) + return request.param l = [] def test_1(arg): @@ -2339,12 +2339,12 @@ import pytest @pytest.factory(scope="module", params=[1, 2]) - def arg(testcontext): - return testcontext.param + def arg(request): + return request.param @pytest.setup(scope="module") - def mysetup(testcontext, arg): - testcontext.addfinalizer(lambda: l.append("fin%s" % arg)) + def mysetup(request, arg): + request.addfinalizer(lambda: l.append("fin%s" % arg)) l.append("setup%s" % arg) l = [] @@ -2376,32 +2376,32 @@ testdir.makepyfile(""" import pytest @pytest.setup(scope=%r) - def myscoped(testcontext): + def myscoped(request): for x in %r: - assert hasattr(testcontext, x) + assert hasattr(request, x) for x in %r: pytest.raises(AttributeError, lambda: - getattr(testcontext, x)) - assert testcontext.session - assert testcontext.config + getattr(request, x)) + assert request.session + assert request.config def test_func(): pass """ %(scope, ok.split(), error.split())) - reprec = testdir.inline_run() + reprec = testdir.inline_run("-l") reprec.assertoutcome(passed=1) def test_funcarg(self, testdir, scope, ok, error): testdir.makepyfile(""" import pytest @pytest.factory(scope=%r) - def arg(testcontext): + def arg(request): for x in %r: - assert hasattr(testcontext, x) + assert hasattr(request, x) for x in %r: pytest.raises(AttributeError, lambda: - getattr(testcontext, x)) - assert testcontext.session - assert testcontext.config + getattr(request, x)) + assert request.session + assert request.config def test_func(arg): pass """ %(scope, ok.split(), error.split())) @@ -2414,7 +2414,7 @@ testdir.makepyfile(""" import pytest @pytest.factory() - def gen(request): + def gen(qwe123): return 1 def test_something(gen): pass @@ -2422,8 +2422,8 @@ result = testdir.runpytest() assert result.ret != 0 result.stdout.fnmatch_lines([ - "*def gen(request):*", - "*no factory*request*", + "*def gen(qwe123):*", + "*no factory*qwe123*", "*1 error*", ]) @@ -2431,7 +2431,7 @@ testdir.makepyfile(""" import pytest @pytest.setup() - def gen(request): + def gen(qwe123): return 1 def test_something(): pass @@ -2439,14 +2439,14 @@ result = testdir.runpytest() assert result.ret != 0 result.stdout.fnmatch_lines([ - "*def gen(request):*", - "*no factory*request*", + "*def gen(qwe123):*", + "*no factory*qwe123*", "*1 error*", ]) class TestTestContextVarious: - def test_newstyle_no_request(self, testdir): + def test_newstyle_with_request(self, testdir): testdir.makepyfile(""" import pytest @pytest.factory() @@ -2455,21 +2455,19 @@ def test_1(arg): pass """) - result = testdir.runpytest() - result.stdout.fnmatch_lines([ - "*no factory found*request*", - ]) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) def test_setupcontext_no_param(self, testdir): testdir.makepyfile(""" import pytest @pytest.factory(params=[1,2]) - def arg(testcontext): - return testcontext.param + def arg(request): + return request.param @pytest.setup() - def mysetup(testcontext, arg): - assert not hasattr(testcontext, "param") + def mysetup(request, arg): + assert not hasattr(request, "param") def test_1(arg): assert arg in (1,2) """) @@ -2505,3 +2503,16 @@ """) reprec = testdir.inline_run() reprec.assertoutcome(passed=3) + +def test_request_can_be_overridden(testdir): + testdir.makepyfile(""" + import pytest + @pytest.factory() + def request(request): + request.a = 1 + return request + def test_request(request): + assert request.a == 1 + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 tox.ini --- a/tox.ini +++ b/tox.ini @@ -62,6 +62,9 @@ [testenv:py32] deps=py>=1.4.0 +[testenv:py33] +deps=py>=1.4.0 + [testenv:jython] changedir=testing commands= https://bitbucket.org/hpk42/pytest/changeset/d8c3798f196e/ changeset: d8c3798f196e user: hpk42 date: 2012-09-17 17:32:23 summary: introduce a new "markers" attribute to nodes and the request object. It is a dynamic class making holdin affected #: 8 files diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Changes between 2.2.4 and 2.3.0.dev ----------------------------------- +- introduce a generic "markers" object on Nodes and request objects + to allow for reading and manipulation of MarkInfo objects previously + set with calls to pytest.mark.* classes. - fix issue185 monkeypatching time.time does not cause pytest to fail - fix issue172 duplicate call of pytest.setup-decoratored setup_module functions diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.3.0.dev11' +__version__ = '2.3.0.dev12' diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -181,14 +181,21 @@ #: filesystem path where this node was collected from (can be None) self.fspath = getattr(parent, 'fspath', None) - #: keywords on this node (node name is always contained) - self.keywords = {self.name: True} - #: fspath sensitive hook proxy used to call pytest hooks self.ihook = self.session.gethookproxy(self.fspath) + bases = parent and (parent.markers,) or () + + #: marker class with markers from all scopes accessible as attributes + self.markers = type("dynmarker", bases, {self.name: True}) + #self.extrainit() + @property + def keywords(self): + """ dictionary of Keywords / markers on this node. """ + return vars(self.markers) + #def extrainit(self): # """"extra initialization after Node is initialized. Implemented # by some subclasses. """ diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -859,9 +859,11 @@ self.obj = callobj startindex = int(self.cls is not None) self.funcargnames = getfuncargnames(self.obj, startindex=startindex) - self.keywords.update(py.builtin._getfuncdict(self.obj) or {}) + for name, val in (py.builtin._getfuncdict(self.obj) or {}).items(): + setattr(self.markers, name, val) if keywords: - self.keywords.update(keywords) + for name, val in keywords.items(): + setattr(self.markers, name, val) @property def function(self): @@ -959,6 +961,10 @@ self.parentid = pyfuncitem.parent.nodeid self._factorystack = [] + @property + def markers(self): + return self._getscopeitem(self.scope).markers + def _getfaclist(self, argname): facdeflist = self._name2factory.get(argname, None) if facdeflist is None: @@ -1011,7 +1017,7 @@ @property def keywords(self): - """ keywords of the test function item. """ + """ dictionary of markers (readonly). """ return self._pyfuncitem.keywords @property @@ -1019,8 +1025,6 @@ """ pytest session object. """ return self._pyfuncitem.session - - def addfinalizer(self, finalizer): """add finalizer/teardown function to be called after the last test within the requesting test context finished @@ -1047,7 +1051,8 @@ """ if not isinstance(marker, py.test.mark.XYZ.__class__): raise ValueError("%r is not a py.test.mark.* object") - self._pyfuncitem.keywords[marker.markname] = marker + setattr(self.markers, marker.markname, marker) + #self._pyfuncitem.keywords[marker.markname] = marker def raiseerror(self, msg): """ raise a FuncargLookupError with the given message. """ diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -26,29 +26,29 @@ $ py.test -v -m webtest =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 -- /home/hpk/venv/1/bin/python - cachedir: /home/hpk/tmp/doc-exec-305/.cache - plugins: xdist, bugzilla, cache, oejskit, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python + cachedir: /home/hpk/tmp/doc-exec-426/.cache + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 2 items test_server.py:3: test_send_http PASSED =================== 1 tests deselected by "-m 'webtest'" =================== - ================== 1 passed, 1 deselected in 0.02 seconds ================== + ================== 1 passed, 1 deselected in 0.01 seconds ================== Or the inverse, running all tests except the webtest ones:: $ py.test -v -m "not webtest" =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 -- /home/hpk/venv/1/bin/python - cachedir: /home/hpk/tmp/doc-exec-305/.cache - plugins: xdist, bugzilla, cache, oejskit, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python + cachedir: /home/hpk/tmp/doc-exec-426/.cache + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 2 items test_server.py:6: test_something_quick PASSED ================= 1 tests deselected by "-m 'not webtest'" ================= - ================== 1 passed, 1 deselected in 0.02 seconds ================== + ================== 1 passed, 1 deselected in 0.01 seconds ================== Registering markers ------------------------------------- @@ -69,6 +69,8 @@ $ py.test --markers @pytest.mark.webtest: mark a test as a webtest. + @pytest.mark.timeout(timeout, method=None): Set a timeout and timeout method on just one test item. The first argument, *timeout*, is the timeout in seconds while the keyword, *method*, takes the same values as the --timeout_method option. + @pytest.mark.skipif(*conditions): skip the given test function if evaluation of all conditions has a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. @pytest.mark.xfail(*conditions, reason=None, run=True): mark the the test function as an expected failure. Optionally specify a reason and run=False if you don't even want to execute the test function. Any positional condition strings will be evaluated (like with skipif) and if one is False the marker will not be applied. @@ -147,8 +149,8 @@ $ py.test -k send_http # running with the above defined examples =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 - plugins: xdist, bugzilla, cache, oejskit, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 4 items test_server.py . @@ -160,8 +162,8 @@ $ py.test -k-send_http =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 - plugins: xdist, bugzilla, cache, oejskit, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 4 items test_mark_classlevel.py .. @@ -174,8 +176,8 @@ $ py.test -kTestClass =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 - plugins: xdist, bugzilla, cache, oejskit, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 4 items test_mark_classlevel.py .. @@ -208,10 +210,8 @@ "env(name): mark test to run only on named environment") def pytest_runtest_setup(item): - if not isinstance(item, pytest.Function): - return - if hasattr(item.obj, 'env'): - envmarker = getattr(item.obj, 'env') + envmarker = getattr(item.markers, 'env', None) + if envmarker is not None: envname = envmarker.args[0] if envname != item.config.option.env: pytest.skip("test requires env %r" % envname) @@ -230,31 +230,33 @@ $ py.test -E stage2 =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 - plugins: xdist, bugzilla, cache, oejskit, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 1 items test_someenv.py s - ======================== 1 skipped in 0.02 seconds ========================= + ======================== 1 skipped in 0.01 seconds ========================= and here is one that specifies exactly the environment needed:: $ py.test -E stage1 =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 - plugins: xdist, bugzilla, cache, oejskit, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 1 items test_someenv.py . - ========================= 1 passed in 0.02 seconds ========================= + ========================= 1 passed in 0.01 seconds ========================= The ``--markers`` option always gives you a list of available markers:: $ py.test --markers @pytest.mark.env(name): mark test to run only on named environment + @pytest.mark.timeout(timeout, method=None): Set a timeout and timeout method on just one test item. The first argument, *timeout*, is the timeout in seconds while the keyword, *method*, takes the same values as the --timeout_method option. + @pytest.mark.skipif(*conditions): skip the given test function if evaluation of all conditions has a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. @pytest.mark.xfail(*conditions, reason=None, run=True): mark the the test function as an expected failure. Optionally specify a reason and run=False if you don't even want to execute the test function. Any positional condition strings will be evaluated (like with skipif) and if one is False the marker will not be applied. @@ -264,7 +266,7 @@ @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. - + Reading markers which were set from multiple places ---------------------------------------------------- @@ -293,7 +295,7 @@ import sys def pytest_runtest_setup(item): - g = getattr(item.obj, 'glob', None) + g = getattr(item.markers, "glob", None) if g is not None: for info in g: print ("glob args=%s kwargs=%s" %(info.args, info.kwargs)) @@ -307,7 +309,7 @@ glob args=('class',) kwargs={'x': 2} glob args=('module',) kwargs={'x': 1} . - 1 passed in 0.02 seconds + 1 passed in 0.01 seconds marking platform specific tests with pytest -------------------------------------------------------------- @@ -330,7 +332,7 @@ def pytest_runtest_setup(item): if isinstance(item, item.Function): plat = sys.platform - if not hasattr(item.obj, plat): + if not hasattr(item.markers, plat): if ALL.intersection(set(item.obj.__dict__)): pytest.skip("cannot run on platform %s" %(plat)) @@ -360,13 +362,13 @@ $ py.test -rs # this option reports skip reasons =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 - plugins: xdist, bugzilla, cache, oejskit, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 4 items test_plat.py s.s. ========================= short test summary info ========================== - SKIP [2] /home/hpk/tmp/doc-exec-305/conftest.py:12: cannot run on platform linux2 + SKIP [2] /home/hpk/tmp/doc-exec-426/conftest.py:12: cannot run on platform linux2 =================== 2 passed, 2 skipped in 0.02 seconds ==================== @@ -374,13 +376,13 @@ $ py.test -m linux2 =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 - plugins: xdist, bugzilla, cache, oejskit, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 4 items test_plat.py . =================== 3 tests deselected by "-m 'linux2'" ==================== - ================== 1 passed, 3 deselected in 0.02 seconds ================== + ================== 1 passed, 3 deselected in 0.01 seconds ================== then the unmarked-tests will not be run. It is thus a way to restrict the run to the specific tests. diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 setup.py --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.3.0.dev11', + version='2.3.0.dev12', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 testing/test_mark.py --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -318,7 +318,7 @@ def pytest_pycollect_makeitem(__multicall__, name): if name == "TestClass": item = __multicall__.execute() - item.keywords['xxx'] = True + item.markers.xxx = True return item """) for keyword in ('xxx', 'xxx test_2', 'TestClass', 'xxx -test_1', diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 testing/test_python.py --- a/testing/test_python.py +++ b/testing/test_python.py @@ -609,7 +609,7 @@ """) req = funcargs.FuncargRequest(item) assert req.function == item.obj - assert req.keywords is item.keywords + assert req.keywords == item.keywords assert hasattr(req.module, 'test_func') assert req.cls is None assert req.function.__name__ == "test_func" @@ -716,24 +716,63 @@ req = funcargs.FuncargRequest(item) assert req.fspath == modcol.fspath -def test_applymarker(testdir): - item1,item2 = testdir.getitems(""" - def pytest_funcarg__something(request): - pass - class TestClass: - def test_func1(self, something): +class TestMarking: + def test_applymarker(self, testdir): + item1,item2 = testdir.getitems(""" + def pytest_funcarg__something(request): pass - def test_func2(self, something): - pass - """) - req1 = funcargs.FuncargRequest(item1) - assert 'xfail' not in item1.keywords - req1.applymarker(pytest.mark.xfail) - assert 'xfail' in item1.keywords - assert 'skipif' not in item1.keywords - req1.applymarker(pytest.mark.skipif) - assert 'skipif' in item1.keywords - pytest.raises(ValueError, "req1.applymarker(42)") + class TestClass: + def test_func1(self, something): + pass + def test_func2(self, something): + pass + """) + req1 = funcargs.FuncargRequest(item1) + assert 'xfail' not in item1.keywords + req1.applymarker(pytest.mark.xfail) + assert 'xfail' in item1.keywords + assert 'skipif' not in item1.keywords + req1.applymarker(pytest.mark.skipif) + assert 'skipif' in item1.keywords + pytest.raises(ValueError, "req1.applymarker(42)") + + def test_accessmarker_function(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.factory() + def markers(request): + return request.markers + @pytest.mark.XYZ + def test_function(markers): + assert markers.XYZ is not None + assert not hasattr(markers, "abc") + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) + + def test_accessmarker_dynamic(self, testdir): + testdir.makeconftest(""" + import pytest + @pytest.factory() + def markers(request): + return request.markers + + @pytest.setup(scope="class") + def marking(request): + request.applymarker(pytest.mark.XYZ("hello")) + """) + testdir.makepyfile(""" + import pytest + def test_fun1(markers): + assert markers.XYZ is not None + assert not hasattr(markers, "abc") + def test_fun2(markers): + assert markers.XYZ is not None + assert not hasattr(markers, "abc") + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=2) + class TestRequestCachedSetup: def test_request_cachedsetup_defaultmodule(self, testdir): @@ -2365,13 +2404,14 @@ reprec = testdir.inline_run("-v") reprec.assertoutcome(passed=6) - at pytest.mark.parametrize(("scope", "ok", "error"),[ - ["session", "", "fspath class function module"], - ["module", "module fspath", "cls function"], - ["class", "module fspath cls", "function"], - ["function", "module fspath cls function", ""] -]) class TestTestContextScopeAccess: + pytestmark = pytest.mark.parametrize(("scope", "ok", "error"),[ + ["session", "", "fspath class function module"], + ["module", "module fspath", "cls function"], + ["class", "module fspath cls", "function"], + ["function", "module fspath cls function", ""] + ]) + def test_setup(self, testdir, scope, ok, error): testdir.makepyfile(""" import pytest Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 17 17:36:15 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Mon, 17 Sep 2012 15:36:15 -0000 Subject: [py-svn] commit/pytest: hpk42: fix @funcarg to @factory Message-ID: <20120917153615.15786.92093@bitbucket24.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/ea10a307d8bc/ changeset: ea10a307d8bc user: hpk42 date: 2012-09-17 17:36:08 summary: fix @funcarg to @factory affected #: 1 file diff -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 -r ea10a307d8bcee96d40ad50d67d77e67c111c2c9 doc/en/funcarg_compare.txt --- a/doc/en/funcarg_compare.txt +++ b/doc/en/funcarg_compare.txt @@ -121,7 +121,7 @@ No ``pytest_funcarg__`` prefix when using @factory decorator ------------------------------------------------------------------- -When using the ``@funcarg`` decorator the name of the function +When using the ``@factory`` decorator the name of the function denotes the name under which the resource can be accessed as a function argument:: Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 17 20:43:55 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Mon, 17 Sep 2012 18:43:55 -0000 Subject: [py-svn] commit/pytest: hpk42: - add request.node which maps to the collection node as specified by the scope. Message-ID: <20120917184355.14126.1637@bitbucket25.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/2bfd9f1d38e9/ changeset: 2bfd9f1d38e9 user: hpk42 date: 2012-09-17 20:43:37 summary: - add request.node which maps to the collection node as specified by the scope. - remove request.markers which is now available via request.node.markers affected #: 3 files diff -r ea10a307d8bcee96d40ad50d67d77e67c111c2c9 -r 2bfd9f1d38e92844576df3e71fee49a76c208637 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,11 @@ Changes between 2.2.4 and 2.3.0.dev ----------------------------------- -- introduce a generic "markers" object on Nodes and request objects - to allow for reading and manipulation of MarkInfo objects previously - set with calls to pytest.mark.* classes. +- introduce a generic "markers" object on Nodes and a request.node + attribute pointing to the scope-specific collection node of a request. + node.markers allows reading and manipulating of MarkInfo objects + previously attached with @pytest.mark.* or request.applymarker or + setattr(node.markers, name, pytest.mark.*) calls. - fix issue185 monkeypatching time.time does not cause pytest to fail - fix issue172 duplicate call of pytest.setup-decoratored setup_module functions diff -r ea10a307d8bcee96d40ad50d67d77e67c111c2c9 -r 2bfd9f1d38e92844576df3e71fee49a76c208637 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -962,8 +962,9 @@ self._factorystack = [] @property - def markers(self): - return self._getscopeitem(self.scope).markers + def node(self): + """ underlying collection node (depends on request scope)""" + return self._getscopeitem(self.scope) def _getfaclist(self, argname): facdeflist = self._name2factory.get(argname, None) @@ -1017,7 +1018,7 @@ @property def keywords(self): - """ dictionary of markers (readonly). """ + """ (deprecated, use node.markers class) dictionary of markers. """ return self._pyfuncitem.keywords @property @@ -1051,8 +1052,7 @@ """ if not isinstance(marker, py.test.mark.XYZ.__class__): raise ValueError("%r is not a py.test.mark.* object") - setattr(self.markers, marker.markname, marker) - #self._pyfuncitem.keywords[marker.markname] = marker + setattr(self.node.markers, marker.markname, marker) def raiseerror(self, msg): """ raise a FuncargLookupError with the given message. """ diff -r ea10a307d8bcee96d40ad50d67d77e67c111c2c9 -r 2bfd9f1d38e92844576df3e71fee49a76c208637 testing/test_python.py --- a/testing/test_python.py +++ b/testing/test_python.py @@ -741,7 +741,7 @@ import pytest @pytest.factory() def markers(request): - return request.markers + return request.node.markers @pytest.mark.XYZ def test_function(markers): assert markers.XYZ is not None @@ -755,7 +755,7 @@ import pytest @pytest.factory() def markers(request): - return request.markers + return request.node.markers @pytest.setup(scope="class") def marking(request): Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue Sep 18 10:54:55 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Tue, 18 Sep 2012 08:54:55 -0000 Subject: [py-svn] commit/pytest: 2 new changesets Message-ID: <20120918085455.16378.78773@bitbucket25.managed.contegix.com> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/changeset/bbd625a935b0/ changeset: bbd625a935b0 user: hpk42 date: 2012-09-18 10:53:42 summary: remove distinction of new versus old funcarg factories affected #: 1 file diff -r 2bfd9f1d38e92844576df3e71fee49a76c208637 -r bbd625a935b068c1e37f2273f79cdc4bdc1b4cee _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1363,12 +1363,10 @@ argname = name scope = marker.scope params = marker.params - new = True elif name.startswith(self._argprefix): argname = name[len(self._argprefix):] scope = None params = None - new = False else: # no funcargs. check if we have a setup function. setup = getattr(obj, "_pytestsetup", None) @@ -1378,8 +1376,7 @@ self.setuplist.append(sf) continue faclist = self.arg2facspec.setdefault(argname, []) - factorydef = FactoryDef(self, nodeid, argname, obj, scope, params, - new) + factorydef = FactoryDef(self, nodeid, argname, obj, scope, params) faclist.append(factorydef) ### check scope/params mismatch? @@ -1489,15 +1486,13 @@ class FactoryDef: """ A container for a factory definition. """ - def __init__(self, funcargmanager, baseid, argname, func, scope, params, - new): + def __init__(self, funcargmanager, baseid, argname, func, scope, params): self.funcargmanager = funcargmanager self.baseid = baseid self.func = func self.argname = argname self.scope = scope self.params = params - self.new = new self.funcargnames = getfuncargnames(func) def getfuncargnames(function, startindex=None): https://bitbucket.org/hpk42/pytest/changeset/93f393567640/ changeset: 93f393567640 user: hpk42 date: 2012-09-18 10:54:12 summary: implement full @pytest.setup function unittest.TestCase interaction affected #: 7 files diff -r bbd625a935b068c1e37f2273f79cdc4bdc1b4cee -r 93f393567640808a5c6166fe618adf38e37890d4 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.3.0.dev12' +__version__ = '2.3.0.dev13' diff -r bbd625a935b068c1e37f2273f79cdc4bdc1b4cee -r 93f393567640808a5c6166fe618adf38e37890d4 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1001,10 +1001,14 @@ if clscol: return clscol.obj - @scopeproperty() + @property def instance(self): """ instance (can be None) on which test function was collected. """ - return py.builtin._getimself(self.function) + # unittest support hack, see _pytest.unittest.TestCaseFunction + try: + return self._pyfuncitem._testcase + except AttributeError: + return py.builtin._getimself(self.function) @scopeproperty() def module(self): @@ -1341,7 +1345,7 @@ for fin in l: fin() - def _parsefactories(self, holderobj, nodeid): + def _parsefactories(self, holderobj, nodeid, unittest=False): if holderobj in self._holderobjseen: return #print "parsefactories", holderobj @@ -1372,7 +1376,7 @@ setup = getattr(obj, "_pytestsetup", None) if setup is not None: scope = setup.scope - sf = SetupCall(self, nodeid, obj, scope) + sf = SetupCall(self, nodeid, obj, scope, unittest) self.setuplist.append(sf) continue faclist = self.arg2facspec.setdefault(argname, []) @@ -1428,7 +1432,6 @@ request._factorystack.append(setupcall) mp = monkeypatch() try: - #mp.setattr(request, "_setupcall", setupcall, raising=False) mp.setattr(request, "scope", setupcall.scope) kwargs = {} for name in setupcall.funcargnames: @@ -1438,7 +1441,12 @@ self.session._setupstate.addfinalizer(setupcall.finish, scol) for argname in setupcall.funcargnames: # XXX all deps? self.addargfinalizer(setupcall.finish, argname) - setupcall.execute(kwargs) + # for unittest-setup methods we need to provide + # the correct instance + posargs = () + if setupcall.unittest: + posargs = (request.instance,) + setupcall.execute(posargs, kwargs) finally: mp.undo() request._factorystack.remove(setupcall) @@ -1457,20 +1465,21 @@ class SetupCall: """ a container/helper for managing calls to setup functions. """ - def __init__(self, funcargmanager, baseid, func, scope): + def __init__(self, funcargmanager, baseid, func, scope, unittest): self.funcargmanager = funcargmanager self.baseid = baseid self.func = func - self.funcargnames = getfuncargnames(func) + self.funcargnames = getfuncargnames(func, startindex=int(unittest)) self.scope = scope self.scopenum = scopes.index(scope) self.active = False + self.unittest= unittest self._finalizer = [] - def execute(self, kwargs): + def execute(self, posargs, kwargs): assert not self.active self.active = True - self.func(**kwargs) + self.func(*posargs, **kwargs) def addfinalizer(self, finalizer): assert self.active diff -r bbd625a935b068c1e37f2273f79cdc4bdc1b4cee -r 93f393567640808a5c6166fe618adf38e37890d4 _pytest/unittest.py --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -21,6 +21,8 @@ class UnitTestCase(pytest.Class): def collect(self): + self.session.funcargmanager._parsefactories(self.obj, self.nodeid, + unittest=True) loader = py.std.unittest.TestLoader() module = self.getparent(pytest.Module).obj cls = self.obj @@ -56,6 +58,8 @@ pytest.skip(self._obj.skip) if hasattr(self._testcase, 'setup_method'): self._testcase.setup_method(self._obj) + if hasattr(self, "_request"): + self._request._callsetup() def teardown(self): if hasattr(self._testcase, 'teardown_method'): diff -r bbd625a935b068c1e37f2273f79cdc4bdc1b4cee -r 93f393567640808a5c6166fe618adf38e37890d4 doc/en/unittest.txt --- a/doc/en/unittest.txt +++ b/doc/en/unittest.txt @@ -24,8 +24,8 @@ $ py.test test_unittest.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 - plugins: xdist, bugzilla, cache, oejskit, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev12 + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 1 items test_unittest.py F @@ -47,3 +47,64 @@ .. _`unittest.py style`: http://docs.python.org/library/unittest.html +Moreover, you can use the new :ref:`@pytest.setup functions <@pytest.setup>` +functions and make use of pytest's unique :ref:`funcarg mechanism` in your +test suite:: + + # content of test_unittest_funcargs.py + import pytest + import unittest + + class MyTest(unittest.TestCase): + @pytest.setup() + def chdir(self, tmpdir): + tmpdir.chdir() # change to pytest-provided temporary directory + tmpdir.join("samplefile.ini").write("# testdata") + + def test_method(self): + s = open("samplefile.ini").read() + assert "testdata" in s + +Running this file should give us one passed test because the setup +function took care to prepare a directory with some test data +which the unittest-testcase method can now use:: + + $ py.test -q test_unittest_funcargs.py + collecting ... collected 1 items + . + 1 passed in 0.28 seconds + +If you want to make a database attribute available on unittest.TestCases +instances, based on a marker, you can do it using :ref:`pytest.mark`` and +:ref:`setup functions`:: + + # content of test_unittest_marked_db.py + import pytest + import unittest + + @pytest.factory() + def db(): + class DummyDB: + x = 1 + return DummyDB() + + @pytest.setup() + def stick_db_to_self(request, db): + if hasattr(request.node.markers, "needsdb"): + request.instance.db = db + + class MyTest(unittest.TestCase): + def test_method(self): + assert not hasattr(self, "db") + + @pytest.mark.needsdb + def test_method2(self): + assert self.db.x == 1 + +Running it passes both tests, one of which will see a ``db`` attribute +because of the according ``needsdb`` marker:: + + $ py.test -q test_unittest_marked_db.py + collecting ... collected 2 items + .. + 2 passed in 0.03 seconds diff -r bbd625a935b068c1e37f2273f79cdc4bdc1b4cee -r 93f393567640808a5c6166fe618adf38e37890d4 setup.py --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.3.0.dev12', + version='2.3.0.dev13', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff -r bbd625a935b068c1e37f2273f79cdc4bdc1b4cee -r 93f393567640808a5c6166fe618adf38e37890d4 testing/test_python.py --- a/testing/test_python.py +++ b/testing/test_python.py @@ -1792,8 +1792,7 @@ reprec.assertoutcome(passed=1) class TestSetupDiscovery: - def pytest_funcarg__testdir(self, request): - testdir = request.getfuncargvalue("testdir") + def pytest_funcarg__testdir(self, testdir): testdir.makeconftest(""" import pytest @pytest.setup() @@ -1832,6 +1831,21 @@ reprec = testdir.inline_run("-s") reprec.assertoutcome(passed=1) + def test_setup_at_classlevel(self, testdir): + testdir.makepyfile(""" + import pytest + class TestClass: + @pytest.setup() + def permethod(self, request): + request.instance.funcname = request.function.__name__ + def test_method1(self): + assert self.funcname == "test_method1" + def test_method2(self): + assert self.funcname == "test_method2" + """) + reprec = testdir.inline_run("-s") + reprec.assertoutcome(passed=2) + def test_callables_nocode(self, testdir): """ a imported mock.call would break setup/factory discovery diff -r bbd625a935b068c1e37f2273f79cdc4bdc1b4cee -r 93f393567640808a5c6166fe618adf38e37890d4 testing/test_unittest.py --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -480,3 +480,28 @@ ]) + +def test_unittest_setup_interaction(testdir): + testdir.makepyfile(""" + import unittest + import pytest + class MyTestCase(unittest.TestCase): + @pytest.setup(scope="class") + def perclass(self, request): + request.cls.hello = "world" + @pytest.setup(scope="function") + def perfunction(self, request): + request.instance.funcname = request.function.__name__ + + def test_method1(self): + assert self.funcname == "test_method1" + assert self.hello == "world" + + def test_method2(self): + assert self.funcname == "test_method2" + + def test_classattr(self): + assert self.__class__.hello == "world" + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines("*3 passed*") Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue Sep 18 14:01:00 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Tue, 18 Sep 2012 12:01:00 -0000 Subject: [py-svn] commit/pytest: 2 new changesets Message-ID: <20120918120100.4492.5971@bitbucket23.managed.contegix.com> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/changeset/b23ae4aae78b/ changeset: b23ae4aae78b user: hpk42 date: 2012-09-18 13:46:24 summary: fix bug introduced with last checkin affected #: 1 file diff -r 93f393567640808a5c6166fe618adf38e37890d4 -r b23ae4aae78bdb1d5ab9fca35e41f7bcb64dd8d6 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1469,7 +1469,8 @@ self.funcargmanager = funcargmanager self.baseid = baseid self.func = func - self.funcargnames = getfuncargnames(func, startindex=int(unittest)) + startindex = unittest and 1 or None + self.funcargnames = getfuncargnames(func, startindex=startindex) self.scope = scope self.scopenum = scopes.index(scope) self.active = False https://bitbucket.org/hpk42/pytest/changeset/32885e1d2189/ changeset: 32885e1d2189 user: hpk42 date: 2012-09-18 14:00:47 summary: allow factory/setup-markers on classes, using their respective __init__ methods which can use the funcarg mechanism affected #: 3 files diff -r b23ae4aae78bdb1d5ab9fca35e41f7bcb64dd8d6 -r 32885e1d2189b3bbedfe1a5951c5e1560590d6fe _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1507,9 +1507,12 @@ def getfuncargnames(function, startindex=None): # XXX merge with main.py's varnames - argnames = py.std.inspect.getargs(py.code.getrawcode(function))[0] - if startindex is None: - startindex = py.std.inspect.ismethod(function) and 1 or 0 + if inspect.isclass(function): + function = function.__init__ + startindex = 1 + elif startindex is None: + startindex = inspect.ismethod(function) and 1 or 0 + argnames = inspect.getargs(py.code.getrawcode(function))[0] defaults = getattr(function, 'func_defaults', getattr(function, '__defaults__', None)) or () numdefaults = len(defaults) diff -r b23ae4aae78bdb1d5ab9fca35e41f7bcb64dd8d6 -r 32885e1d2189b3bbedfe1a5951c5e1560590d6fe doc/en/funcargs.txt --- a/doc/en/funcargs.txt +++ b/doc/en/funcargs.txt @@ -389,19 +389,16 @@ import pytest - class App: + @pytest.factory(scope="module") + class app: def __init__(self, smtp): self.smtp = smtp - @pytest.factory(scope="module") - def app(smtp): - return App(smtp) - def test_exists(app): assert app.smtp -Here we define the factory local to the test module and make it access -the ``smtp`` resource by listing it as an input parameter. Let's run this:: +Here we declare an ``app`` class to be a factory and have its init-method +use the previously defined ``smtp`` resource. Let's run it:: $ py.test -v test_appsetup.py =========================== test session starts ============================ @@ -418,7 +415,7 @@ Due to the parametrization of ``smtp`` the test will run twice with two different ``App`` instances and respective smtp servers. There is no need for the ``app`` factory to be aware of the parametrization. Note -that the ``app`` factory has a scope of ``module`` whereas it uses the +that the ``app`` factory has a scope of ``module`` but uses the session-scoped ``smtp`` object: it is fine for factories to use "broader" scoped resources but not the other way round. A session-scoped resource could not use a module-scoped resource in a @@ -498,8 +495,8 @@ fin mod2 You can see that the parametrized module-scoped ``modarg`` resource caused -a re-ordering of test execution. The finalizer for the ``mod1`` parametrized -resource was executed before the ``mod2`` resource was setup. +an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed +before the ``mod2`` resource was setup. .. currentmodule:: _pytest.python .. _`request`: @@ -520,7 +517,6 @@ * to add finalizers/teardowns to be invoked when the last test of the requesting test context executes - .. autoclass:: _pytest.python.FuncargRequest() :members: diff -r b23ae4aae78bdb1d5ab9fca35e41f7bcb64dd8d6 -r 32885e1d2189b3bbedfe1a5951c5e1560590d6fe testing/test_python.py --- a/testing/test_python.py +++ b/testing/test_python.py @@ -535,6 +535,11 @@ if sys.version_info < (3,0): assert funcargs.getfuncargnames(A.f) == ['arg1'] + class A: + def __init__(self, x): + pass + assert funcargs.getfuncargnames(A) == ["x"] + class TestFillFuncArgs: def test_fillfuncargs_exposed(self): # used by oejskit @@ -1727,6 +1732,26 @@ ]) + def test_factory_setup_as_classes(self, testdir): + testdir.makepyfile(""" + import pytest + class arg1: + def __init__(self, request): + self.x = 1 + arg1 = pytest.factory()(arg1) + + class MySetup: + def __init__(self, request, arg1): + request.instance.arg1 = arg1 + pytest.setup()(MySetup) + + class TestClass: + def test_method(self): + assert self.arg1.x == 1 + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) + class TestResourceIntegrationFunctional: def test_parametrize_with_ids(self, testdir): Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Thu Sep 20 10:56:50 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Thu, 20 Sep 2012 08:56:50 -0000 Subject: [py-svn] commit/py: hpk42: default to utf8 when writing to terminal/file and no encoding can be determined Message-ID: <20120920085650.29677.562@bitbucket25.managed.contegix.com> 1 new commit in py: https://bitbucket.org/hpk42/py/changeset/f60c108f78fd/ changeset: f60c108f78fd user: hpk42 date: 2012-09-20 10:56:42 summary: default to utf8 when writing to terminal/file and no encoding can be determined affected #: 5 files diff -r 0fca612a4bbdb14513a2b014c8459e7859f2fbc7 -r f60c108f78fd973eb08e1933ff2130f339ea0dcf CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +Changes between 1.4.9 and 1.4.10.dev +================================================== + +- terminalwriter: default to encode to UTF8 if no encoding is defined + on the output stream + Changes between 1.4.8 and 1.4.9 ================================================== diff -r 0fca612a4bbdb14513a2b014c8459e7859f2fbc7 -r f60c108f78fd973eb08e1933ff2130f339ea0dcf py/__init__.py --- a/py/__init__.py +++ b/py/__init__.py @@ -8,7 +8,7 @@ (c) Holger Krekel and others, 2004-2010 """ -__version__ = '1.4.10.dev1' +__version__ = '1.4.10.dev2' from py import _apipkg diff -r 0fca612a4bbdb14513a2b014c8459e7859f2fbc7 -r f60c108f78fd973eb08e1933ff2130f339ea0dcf py/_io/terminalwriter.py --- a/py/_io/terminalwriter.py +++ b/py/_io/terminalwriter.py @@ -171,8 +171,8 @@ def _getbytestring(self, s): # XXX review this and the whole logic - if self.encoding and sys.version_info[0] < 3 and isinstance(s, unicode): - return s.encode(self.encoding) + if sys.version_info[0] < 3 and isinstance(s, unicode): + return s.encode(self.encoding or "utf8") elif not isinstance(s, str): try: return str(s) diff -r 0fca612a4bbdb14513a2b014c8459e7859f2fbc7 -r f60c108f78fd973eb08e1933ff2130f339ea0dcf setup.py --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ name='py', description='library with cross-python path, ini-parsing, io, code, log facilities', long_description = open('README.txt').read(), - version='1.4.10.dev1', + version='1.4.10.dev2', url='http://pylib.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff -r 0fca612a4bbdb14513a2b014c8459e7859f2fbc7 -r f60c108f78fd973eb08e1933ff2130f339ea0dcf testing/io_/test_terminalwriter.py --- a/testing/io_/test_terminalwriter.py +++ b/testing/io_/test_terminalwriter.py @@ -1,3 +1,5 @@ +# coding=utf8 + import py import os, sys from py._io import terminalwriter @@ -76,6 +78,21 @@ tw.line(msg) assert l[0].strip() == msg.encode(encoding) +def test_unicode_on_file_with_no_encoding(tmpdir, monkeypatch): + try: + bytes + except NameError: + bytes = str + msg = py.builtin._totext('?l', "utf8") + #pytest.raises(UnicodeEncodeError, lambda: bytes(msg)) + f = py.std.codecs.open(str(tmpdir.join("x")), "w", None) + tw = py.io.TerminalWriter(f, encoding=None) + tw.encoding = None + tw.line(msg) + f.close() + s = tmpdir.join("x").open("rb").read().strip() + assert s == msg.encode("utf8") + class TestTerminalWriter: def pytest_generate_tests(self, metafunc): if "tw" in metafunc.funcargnames: Repository URL: https://bitbucket.org/hpk42/py/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Thu Sep 20 10:57:30 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Thu, 20 Sep 2012 08:57:30 -0000 Subject: [py-svn] commit/pytest: hpk42: refine internal test support for unicode-related bits (used by a test in pytest-pep8) Message-ID: <20120920085730.399.16902@bitbucket15.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/e27845f4a38e/ changeset: e27845f4a38e user: hpk42 date: 2012-09-20 10:57:23 summary: refine internal test support for unicode-related bits (used by a test in pytest-pep8) affected #: 3 files diff -r 32885e1d2189b3bbedfe1a5951c5e1560590d6fe -r e27845f4a38eec31eda20e1a9a20b0900403e0a7 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.3.0.dev13' +__version__ = '2.3.0.dev14' diff -r 32885e1d2189b3bbedfe1a5951c5e1560590d6fe -r e27845f4a38eec31eda20e1a9a20b0900403e0a7 _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -2,6 +2,7 @@ import py, pytest import sys, os +import codecs import re import inspect import time @@ -244,8 +245,10 @@ ret = None for name, value in items: p = self.tmpdir.join(name).new(ext=ext) - source = py.builtin._totext(py.code.Source(value)).lstrip() - p.write(source.encode("utf-8"), "wb") + source = py.builtin._totext(py.code.Source(value)).strip() + content = source.encode("utf-8") # + "\n" + #content = content.rstrip() + "\n" + p.write(content, "wb") if ret is None: ret = p return ret @@ -440,28 +443,35 @@ p1 = self.tmpdir.join("stdout") p2 = self.tmpdir.join("stderr") print_("running", cmdargs, "curdir=", py.path.local()) - f1 = p1.open("wb") - f2 = p2.open("wb") - now = time.time() - popen = self.popen(cmdargs, stdout=f1, stderr=f2, - close_fds=(sys.platform != "win32")) - ret = popen.wait() - f1.close() - f2.close() - out = p1.read("rb") - out = getdecoded(out).splitlines() - err = p2.read("rb") - err = getdecoded(err).splitlines() - def dump_lines(lines, fp): - try: - for line in lines: - py.builtin.print_(line, file=fp) - except UnicodeEncodeError: - print("couldn't print to %s because of encoding" % (fp,)) - dump_lines(out, sys.stdout) - dump_lines(err, sys.stderr) + f1 = codecs.open(str(p1), "w", encoding="utf8") + f2 = codecs.open(str(p2), "w", encoding="utf8") + try: + now = time.time() + popen = self.popen(cmdargs, stdout=f1, stderr=f2, + close_fds=(sys.platform != "win32")) + ret = popen.wait() + finally: + f1.close() + f2.close() + f1 = codecs.open(str(p1), "r", encoding="utf8") + f2 = codecs.open(str(p2), "r", encoding="utf8") + try: + out = f1.read().splitlines() + err = f2.read().splitlines() + finally: + f1.close() + f2.close() + self._dump_lines(out, sys.stdout) + self._dump_lines(err, sys.stderr) return RunResult(ret, out, err, time.time()-now) + def _dump_lines(self, lines, fp): + try: + for line in lines: + py.builtin.print_(line, file=fp) + except UnicodeEncodeError: + print("couldn't print to %s because of encoding" % (fp,)) + def runpybin(self, scriptname, *args): fullargs = self._getpybinargs(scriptname) + args return self.run(*fullargs) diff -r 32885e1d2189b3bbedfe1a5951c5e1560590d6fe -r e27845f4a38eec31eda20e1a9a20b0900403e0a7 setup.py --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.3.0.dev13', + version='2.3.0.dev14', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Thu Sep 20 10:58:19 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Thu, 20 Sep 2012 08:58:19 -0000 Subject: [py-svn] commit/pytest-pep8: hpk42: fix unicode issue, reported by thomas waldmann Message-ID: <20120920085819.29195.93559@bitbucket03.managed.contegix.com> 1 new commit in pytest-pep8: https://bitbucket.org/hpk42/pytest-pep8/changeset/b367f45b6eb8/ changeset: b367f45b6eb8 user: hpk42 date: 2012-09-20 10:58:08 summary: fix unicode issue, reported by thomas waldmann affected #: 4 files diff -r 6ea1b7ce7fde9a4a89df643844f4db04a6ba22a0 -r b367f45b6eb84f02f4d8843869495b15dd9dc0bd pytest_pep8.py --- a/pytest_pep8.py +++ b/pytest_pep8.py @@ -3,7 +3,7 @@ import pytest import pep8 -__version__ = '1.0.2' +__version__ = '1.0.3.dev1' HISTKEY = "pep8/mtimes" @@ -47,7 +47,10 @@ def __init__(self, path, parent, pep8ignore): super(Pep8Item, self).__init__(path, parent) - self.keywords['pep8'] = True + if hasattr(self, "markers"): + self.markers.pep8 = pytest.mark.pep8 + else: + self.keywords["pep8"] = pytest.mark.pep8 self.pep8ignore = pep8ignore def setup(self): diff -r 6ea1b7ce7fde9a4a89df643844f4db04a6ba22a0 -r b367f45b6eb84f02f4d8843869495b15dd9dc0bd setup.py --- a/setup.py +++ b/setup.py @@ -5,11 +5,11 @@ name='pytest-pep8', description='pytest plugin to check PEP8 requirements', long_description=open("README.txt").read(), - version='1.0.2', + version='1.0.3.dev1', author='Holger Krekel and Ronny Pfannschmidt', author_email='holger.krekel at gmail.com', url='http://bitbucket.org/hpk42/pytest-pep8/', py_modules=['pytest_pep8'], entry_points={'pytest11': ['pep8 = pytest_pep8']}, - install_requires=['pytest-cache', 'pytest>=2.0', 'pep8>=1.3', ], + install_requires=['pytest-cache', 'pytest>=2.3.dev14', 'pep8>=1.3', ], ) diff -r 6ea1b7ce7fde9a4a89df643844f4db04a6ba22a0 -r b367f45b6eb84f02f4d8843869495b15dd9dc0bd test_pep8.py --- a/test_pep8.py +++ b/test_pep8.py @@ -1,4 +1,6 @@ +# coding=utf8 +import py pytest_plugins = "pytester", @@ -100,3 +102,19 @@ "*1 failed*", ]) assert 'passed' not in result.stdout.str() + + +def test_unicode_error(testdir): + x = testdir.tmpdir.join("x.py") + import codecs + f = codecs.open(str(x), "w", encoding="utf8") + f.write(py.builtin._totext(""" +# coding=utf8 + +accent_map = { + u'\\xc0': 'a', # ? -> a non-ascii comment crashes it +} +""", "utf8")) + f.close() + result = testdir.runpytest("--pep8", x, "-s") + result.stdout.fnmatch_lines("*non-ascii comment*") diff -r 6ea1b7ce7fde9a4a89df643844f4db04a6ba22a0 -r b367f45b6eb84f02f4d8843869495b15dd9dc0bd tox.ini --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py25,py27,py32,py-xdist +envlist=py25,py27,py27-pytesttrunk,py32,py-xdist indexserver = default = http://pypi.python.org/simple testrun = http://pypi.testrun.org @@ -7,10 +7,14 @@ [testenv] deps= - :pypi:pytest + :testrun:pytest commands= py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} +[testenv:py27-pytesttrunk] +deps = + :testrun:pytest + [testenv:py-xdist] basepython=python deps={[testenv]deps} Repository URL: https://bitbucket.org/hpk42/pytest-pep8/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 21 08:34:42 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Fri, 21 Sep 2012 06:34:42 -0000 Subject: [py-svn] commit/pytest-pep8: 2 new changesets Message-ID: <20120921063442.14685.29694@bitbucket25.managed.contegix.com> 2 new commits in pytest-pep8: https://bitbucket.org/hpk42/pytest-pep8/changeset/1a118b287fe7/ changeset: 1a118b287fe7 user: Thomas Waldmann date: 2012-09-20 22:36:34 summary: added pep8maxlinelength ini setting to configure the maximum allowed line length. updated CHANGELOG, README.txt, tox.ini added a unit test (testing whether a non-standard maxlinelength works) affected #: 5 files diff -r b367f45b6eb84f02f4d8843869495b15dd9dc0bd -r 1a118b287fe71c9a2d0e8ad4d08680777343f935 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +1.0.3 +---------------------------------------------- + +- added pep8maxlinelength ini setting to configure the maximum allowed + line length. + 1.0.2 ---------------------------------------------- diff -r b367f45b6eb84f02f4d8843869495b15dd9dc0bd -r 1a118b287fe71c9a2d0e8ad4d08680777343f935 README.txt --- a/README.txt +++ b/README.txt @@ -166,6 +166,13 @@ Note that doc/conf.py was not considered or imported. +If you'ld like to have longer lines than 79 chars (which is the default for the +pep8 checker), you can configure it like this:: + + # content of setup.cfg + [pytest] + max_line_length = 99 + Running PEP8 checks and no other tests --------------------------------------------- diff -r b367f45b6eb84f02f4d8843869495b15dd9dc0bd -r 1a118b287fe71c9a2d0e8ad4d08680777343f935 pytest_pep8.py --- a/pytest_pep8.py +++ b/pytest_pep8.py @@ -16,6 +16,8 @@ help="each line specifies a glob pattern and whitespace " "separated PEP8 errors or warnings which will be ignored, " "example: *.py W293") + parser.addini("pep8maxlinelength", + help="max. line length (default: %d)" % pep8.MAX_LINE_LENGTH) def pytest_sessionstart(session): @@ -23,6 +25,8 @@ if config.option.pep8: config._pep8ignore = Ignorer(config.getini("pep8ignore")) config._pep8mtimes = config.cache.get(HISTKEY, {}) + config._max_line_length = int(config.getini("pep8maxlinelength") + or pep8.MAX_LINE_LENGTH) def pytest_collect_file(path, parent): @@ -30,7 +34,7 @@ if config.option.pep8 and path.ext == '.py': pep8ignore = config._pep8ignore(path) if pep8ignore is not None: - return Pep8Item(path, parent, pep8ignore) + return Pep8Item(path, parent, pep8ignore, config._max_line_length) def pytest_sessionfinish(session): @@ -45,13 +49,14 @@ class Pep8Item(pytest.Item, pytest.File): - def __init__(self, path, parent, pep8ignore): + def __init__(self, path, parent, pep8ignore, max_line_length): super(Pep8Item, self).__init__(path, parent) if hasattr(self, "markers"): self.markers.pep8 = pytest.mark.pep8 else: self.keywords["pep8"] = pytest.mark.pep8 self.pep8ignore = pep8ignore + self.max_line_length = max_line_length def setup(self): pep8mtimes = self.config._pep8mtimes @@ -62,7 +67,8 @@ def runtest(self): call = py.io.StdCapture.call - found_errors, out, err = call(check_file, self.fspath, self.pep8ignore) + found_errors, out, err = call(check_file, self.fspath, self.pep8ignore, + self.max_line_length) if found_errors: raise Pep8Error(out, err) # update mtime only if test passed @@ -111,6 +117,7 @@ return l -def check_file(path, pep8ignore): - checker = pep8.Checker(str(path), ignore=pep8ignore, show_source=1) +def check_file(path, pep8ignore, max_line_length): + checker = pep8.Checker(str(path), ignore=pep8ignore, show_source=1, + max_line_length=max_line_length) return checker.check_all() diff -r b367f45b6eb84f02f4d8843869495b15dd9dc0bd -r 1a118b287fe71c9a2d0e8ad4d08680777343f935 test_pep8.py --- a/test_pep8.py +++ b/test_pep8.py @@ -104,6 +104,22 @@ assert 'passed' not in result.stdout.str() +def test_maxlinelength(testdir): + testdir.makeini(""" + [pytest] + pep8maxlinelength = 50 + """) + testdir.makepyfile(""" +# this line is longer than the configured max. line length +""") + result = testdir.runpytest("--pep8", "-k pep8") + result.stdout.fnmatch_lines([ + "*E501*", + "*1 failed*", + ]) + assert 'passed' not in result.stdout.str() + + def test_unicode_error(testdir): x = testdir.tmpdir.join("x.py") import codecs diff -r b367f45b6eb84f02f4d8843869495b15dd9dc0bd -r 1a118b287fe71c9a2d0e8ad4d08680777343f935 tox.ini --- a/tox.ini +++ b/tox.ini @@ -25,3 +25,4 @@ [pytest] addopts=--pep8 pep8ignore = E128 +#pep8maxlinelength = 66 https://bitbucket.org/hpk42/pytest-pep8/changeset/e75a84750400/ changeset: e75a84750400 user: Thomas Waldmann date: 2012-09-20 22:44:18 summary: fixed pep8maxlinelength ini setting name in README affected #: 1 file diff -r 1a118b287fe71c9a2d0e8ad4d08680777343f935 -r e75a84750400eff3732ae3008eeefc6d42c151b9 README.txt --- a/README.txt +++ b/README.txt @@ -171,7 +171,7 @@ # content of setup.cfg [pytest] - max_line_length = 99 + pep8maxlinelength = 99 Running PEP8 checks and no other tests --------------------------------------------- Repository URL: https://bitbucket.org/hpk42/pytest-pep8/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 21 09:40:44 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Fri, 21 Sep 2012 07:40:44 -0000 Subject: [py-svn] commit/pytest: hpk42: improve the parametrization scenario example to sort by id, rather than by file-order, see also: http://stackoverflow.com/questions/12521924/pytest-running-scenarios-in-the-correct-order-in-the-class Message-ID: <20120921074044.29697.97916@bitbucket21.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/fb7a0c24f690/ changeset: fb7a0c24f690 user: hpk42 date: 2012-09-21 09:39:54 summary: improve the parametrization scenario example to sort by id, rather than by file-order, see also: http://stackoverflow.com/questions/12521924/pytest-running-scenarios-in-the-correct-order-in-the-class affected #: 6 files diff -r e27845f4a38eec31eda20e1a9a20b0900403e0a7 -r fb7a0c24f6908799dc0bde25b41326ebc56e1f36 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,8 @@ node.markers allows reading and manipulating of MarkInfo objects previously attached with @pytest.mark.* or request.applymarker or setattr(node.markers, name, pytest.mark.*) calls. +- introduce re-ordering of tests by resource and parametrization setup + which takes precedence to the usual file-ordering - fix issue185 monkeypatching time.time does not cause pytest to fail - fix issue172 duplicate call of pytest.setup-decoratored setup_module functions diff -r e27845f4a38eec31eda20e1a9a20b0900403e0a7 -r fb7a0c24f6908799dc0bde25b41326ebc56e1f36 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.3.0.dev14' +__version__ = '2.3.0.dev15' diff -r e27845f4a38eec31eda20e1a9a20b0900403e0a7 -r fb7a0c24f6908799dc0bde25b41326ebc56e1f36 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -574,6 +574,10 @@ for arg,val in zip(argnames, valset): self._checkargnotcontained(arg) getattr(self, valtype)[arg] = val + # we want self.params to be always set because of + # parametrize_sorted() which groups tests by params/scope + if valtype == "funcargs": + self.params[arg] = id self._arg2scopenum[arg] = scopenum self._idlist.append(id) diff -r e27845f4a38eec31eda20e1a9a20b0900403e0a7 -r fb7a0c24f6908799dc0bde25b41326ebc56e1f36 doc/en/example/parametrize.txt --- a/doc/en/example/parametrize.txt +++ b/doc/en/example/parametrize.txt @@ -96,7 +96,7 @@ $ py.test -q test_compute.py collecting ... collected 2 items .. - 2 passed in 0.02 seconds + 2 passed in 0.01 seconds We run only two computations, so we see two dots. let's run the full monty:: @@ -139,7 +139,7 @@ items = scenario[1].items() argnames = [x[0] for x in items] argvalues.append(([x[1] for x in items])) - metafunc.parametrize(argnames, argvalues, ids=idlist) + metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class") scenario1 = ('basic', {'attribute': 'value'}) scenario2 = ('advanced', {'attribute': 'value2'}) @@ -147,36 +147,45 @@ class TestSampleWithScenarios: scenarios = [scenario1, scenario2] - def test_demo(self, attribute): + def test_demo1(self, attribute): + assert isinstance(attribute, str) + + def test_demo2(self, attribute): assert isinstance(attribute, str) this is a fully self-contained example which you can run with:: $ py.test test_scenarios.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev3 - plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov - collecting ... collected 2 items + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev14 + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov + collecting ... collected 4 items - test_scenarios.py .. + test_scenarios.py .... - ========================= 2 passed in 0.02 seconds ========================= + ========================= 4 passed in 0.02 seconds ========================= If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function:: $ py.test --collectonly test_scenarios.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev3 - plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov - collecting ... collected 2 items + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev14 + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov + collecting ... collected 4 items - - + + + + - ============================= in 0.02 seconds ============================= + ============================= in 0.01 seconds ============================= + +Note that we told ``metafunc.parametrize()`` that your scenario values +should be considered class-scoped. With pytest-2.3 this leads to a +resource-based ordering. Deferring the setup of parametrized resources --------------------------------------------------- @@ -224,14 +233,14 @@ $ py.test test_backends.py --collectonly =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev3 - plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov + platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev14 + plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov collecting ... collected 2 items - ============================= in 0.02 seconds ============================= + ============================= in 0.01 seconds ============================= And then when we run the test:: @@ -241,7 +250,7 @@ ================================= FAILURES ================================= _________________________ test_db_initialized[d2] __________________________ - db = + db = def test_db_initialized(db): # a dummy test @@ -250,7 +259,7 @@ E Failed: deliberately failing for demo purposes test_backends.py:6: Failed - 1 failed, 1 passed in 0.02 seconds + 1 failed, 1 passed in 0.01 seconds The first invocation with ``db == "DB1"`` passed while the second with ``db == "DB2"`` failed. Our ``pytest_funcarg__db`` factory has instantiated each of the DB values during the setup phase while the ``pytest_generate_tests`` generated two according calls to the ``test_db_initialized`` during the collection phase. @@ -298,7 +307,7 @@ ================================= FAILURES ================================= ________________________ TestClass.test_equals[1-2] ________________________ - self = , a = 1, b = 2 + self = , a = 1, b = 2 def test_equals(self, a, b): > assert a == b @@ -327,5 +336,5 @@ collecting ... collected 75 items ............sss............sss............sss............ssssssssssssssssss ========================= short test summary info ========================== - SKIP [27] /home/hpk/p/pytest/doc/en/example/multipython.py:36: 'python2.8' not found - 48 passed, 27 skipped in 1.70 seconds + SKIP [27] /home/hpk/p/pytest/doc/en/example/multipython.py:21: 'python2.8' not found + 48 passed, 27 skipped in 3.11 seconds diff -r e27845f4a38eec31eda20e1a9a20b0900403e0a7 -r fb7a0c24f6908799dc0bde25b41326ebc56e1f36 setup.py --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.3.0.dev14', + version='2.3.0.dev15', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff -r e27845f4a38eec31eda20e1a9a20b0900403e0a7 -r fb7a0c24f6908799dc0bde25b41326ebc56e1f36 testing/test_python.py --- a/testing/test_python.py +++ b/testing/test_python.py @@ -1095,6 +1095,44 @@ "*6 fail*", ]) + def test_parametrize_class_scenarios(self, testdir): + testdir.makepyfile(""" + # same as doc/en/example/parametrize scenario example + def pytest_generate_tests(metafunc): + idlist = [] + argvalues = [] + for scenario in metafunc.cls.scenarios: + idlist.append(scenario[0]) + items = scenario[1].items() + argnames = [x[0] for x in items] + argvalues.append(([x[1] for x in items])) + metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class") + + class Test(object): + scenarios = [['1', {'arg': {1: 2}, "arg2": "value2"}], + ['2', {'arg':'value2', "arg2": "value2"}]] + + def test_1(self, arg, arg2): + pass + + def test_2(self, arg2, arg): + pass + + def test_3(self, arg, arg2): + pass + """) + result = testdir.runpytest("-v") + assert result.ret == 0 + result.stdout.fnmatch_lines(""" + *test_1*1* + *test_2*1* + *test_3*1* + *test_1*2* + *test_2*2* + *test_3*2* + *6 passed* + """) + class TestMetafuncFunctional: def test_attributes(self, testdir): p = testdir.makepyfile(""" Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Sep 22 00:23:44 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Fri, 21 Sep 2012 22:23:44 -0000 Subject: [py-svn] commit/pytest: hpk42: don't call nose' setup methods if they are marked with pytest.setup Message-ID: <20120921222344.26688.96686@bitbucket01.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/5083259b34c8/ changeset: 5083259b34c8 user: hpk42 date: 2012-09-22 00:23:36 summary: don't call nose' setup methods if they are marked with pytest.setup affected #: 4 files diff -r fb7a0c24f6908799dc0bde25b41326ebc56e1f36 -r 5083259b34c8ac69d12243d73451d2e2f3a720ab _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.3.0.dev15' +__version__ = '2.3.0.dev16' diff -r fb7a0c24f6908799dc0bde25b41326ebc56e1f36 -r 5083259b34c8ac69d12243d73451d2e2f3a720ab _pytest/nose.py --- a/_pytest/nose.py +++ b/_pytest/nose.py @@ -41,7 +41,7 @@ def call_optional(obj, name): method = getattr(obj, name, None) - if method: + if method is not None and not hasattr(method, "_pytestsetup"): # If there's any problems allow the exception to raise rather than # silently ignoring them method() diff -r fb7a0c24f6908799dc0bde25b41326ebc56e1f36 -r 5083259b34c8ac69d12243d73451d2e2f3a720ab setup.py --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.3.0.dev15', + version='2.3.0.dev16', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff -r fb7a0c24f6908799dc0bde25b41326ebc56e1f36 -r 5083259b34c8ac69d12243d73451d2e2f3a720ab testing/test_nose.py --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -24,6 +24,17 @@ ]) +def test_setup_func_with_setup_decorator(): + from _pytest.nose import call_optional + l = [] + class A: + @pytest.setup() + def f(self): + l.append(1) + call_optional(A(), "f") + assert not l + + def test_nose_setup_func(testdir): p = testdir.makepyfile(""" from nose.tools import with_setup Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Sep 22 18:25:04 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Sat, 22 Sep 2012 16:25:04 -0000 Subject: [py-svn] commit/pytest: RonnyPfannschmidt: fix issue 191 - add support for runTest method of unittest.TestCase subclasses Message-ID: <20120922162504.19592.88874@bitbucket21.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/3a876497815d/ changeset: 3a876497815d user: RonnyPfannschmidt date: 2012-09-22 18:24:53 summary: fix issue 191 - add support for runTest method of unittest.TestCase subclasses affected #: 3 files diff -r 5083259b34c8ac69d12243d73451d2e2f3a720ab -r 3a876497815d887f3c13589aa81b238e8bee3b1e CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -56,6 +56,8 @@ - fix issue 188: ensure sys.exc_info is clear on python2 before calling into a test +- fix issue 191: addd unittest TestCase runTest method support + - reporting refinements: - pytest_report_header now receives a "startdir" so that diff -r 5083259b34c8ac69d12243d73451d2e2f3a720ab -r 3a876497815d887f3c13589aa81b238e8bee3b1e _pytest/unittest.py --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -34,6 +34,9 @@ pytest.mark.xfail(reason=str(funcobj.todo))(funcobj) yield TestCaseFunction(name, parent=self) + if getattr(self.obj, 'runTest', None) is not None: + yield TestCaseFunction('runTest', parent=self) + def setup(self): meth = getattr(self.obj, 'setUpClass', None) if meth is not None: diff -r 5083259b34c8ac69d12243d73451d2e2f3a720ab -r 3a876497815d887f3c13589aa81b238e8bee3b1e testing/test_unittest.py --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -14,6 +14,17 @@ assert reprec.matchreport("testpassing").passed assert reprec.matchreport("test_failing").failed +def test_runTest_method(testdir): + testpath=testdir.makepyfile(""" + import unittest + pytest_plugins = "pytest_unittest" + class MyTestCase(unittest.TestCase): + def runTest(self): + self.assertEquals('foo', 'foo') + """) + reprec = testdir.inline_run(testpath) + assert reprec.matchreport('runTest').passed + def test_isclasscheck_issue53(testdir): testpath = testdir.makepyfile(""" import unittest Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Sep 22 20:27:26 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Sat, 22 Sep 2012 18:27:26 -0000 Subject: [py-svn] commit/pytest: 2 new changesets Message-ID: <20120922182726.19591.67582@bitbucket21.managed.contegix.com> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/changeset/d6bb87d586c1/ changeset: d6bb87d586c1 user: hpk42 date: 2012-09-22 11:44:56 summary: extend --help to tell about --markers and --funcargs affected #: 2 files diff -r 5083259b34c8ac69d12243d73451d2e2f3a720ab -r d6bb87d586c1907fcde2ce15a618b225f2c9e432 _pytest/helpconfig.py --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -79,6 +79,8 @@ tw.line() ; tw.line() #tw.sep("=") + tw.line("to see available markers type: py.test --markers") + tw.line("to see available funcargs type: py.test --funcargs") return tw.line("conftest.py options:") diff -r 5083259b34c8ac69d12243d73451d2e2f3a720ab -r d6bb87d586c1907fcde2ce15a618b225f2c9e432 testing/test_helpconfig.py --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -17,11 +17,13 @@ def test_help(testdir): result = testdir.runpytest("--help") assert result.ret == 0 - result.stdout.fnmatch_lines([ - "*-v*verbose*", - "*setup.cfg*", - "*minversion*", - ]) + result.stdout.fnmatch_lines(""" + *-v*verbose* + *setup.cfg* + *minversion* + *to see*markers*py.test --markers* + *to see*funcargs*py.test --funcargs* + """) def test_collectattr(): class A: https://bitbucket.org/hpk42/pytest/changeset/d9eff229919e/ changeset: d9eff229919e user: hpk42 date: 2012-09-22 20:26:13 summary: merge affected #: 3 files diff -r d6bb87d586c1907fcde2ce15a618b225f2c9e432 -r d9eff229919e8ccf3d32cd66d4828eaeb74e332c CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -56,6 +56,8 @@ - fix issue 188: ensure sys.exc_info is clear on python2 before calling into a test +- fix issue 191: addd unittest TestCase runTest method support + - reporting refinements: - pytest_report_header now receives a "startdir" so that diff -r d6bb87d586c1907fcde2ce15a618b225f2c9e432 -r d9eff229919e8ccf3d32cd66d4828eaeb74e332c _pytest/unittest.py --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -34,6 +34,9 @@ pytest.mark.xfail(reason=str(funcobj.todo))(funcobj) yield TestCaseFunction(name, parent=self) + if getattr(self.obj, 'runTest', None) is not None: + yield TestCaseFunction('runTest', parent=self) + def setup(self): meth = getattr(self.obj, 'setUpClass', None) if meth is not None: diff -r d6bb87d586c1907fcde2ce15a618b225f2c9e432 -r d9eff229919e8ccf3d32cd66d4828eaeb74e332c testing/test_unittest.py --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -14,6 +14,17 @@ assert reprec.matchreport("testpassing").passed assert reprec.matchreport("test_failing").failed +def test_runTest_method(testdir): + testpath=testdir.makepyfile(""" + import unittest + pytest_plugins = "pytest_unittest" + class MyTestCase(unittest.TestCase): + def runTest(self): + self.assertEquals('foo', 'foo') + """) + reprec = testdir.inline_run(testpath) + assert reprec.matchreport('runTest').passed + def test_isclasscheck_issue53(testdir): testpath = testdir.makepyfile(""" import unittest Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 24 10:36:32 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Mon, 24 Sep 2012 08:36:32 -0000 Subject: [py-svn] commit/pytest: hpk42: make sure setups are called ahead of the funcarg factories of the test function Message-ID: <20120924083632.31703.45940@bitbucket05.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/07e5a98aceca/ changeset: 07e5a98aceca user: hpk42 date: 2012-09-24 10:36:22 summary: make sure setups are called ahead of the funcarg factories of the test function affected #: 2 files diff -r d9eff229919e8ccf3d32cd66d4828eaeb74e332c -r 07e5a98aceca50ad07e99b226baf6dd9d84ce380 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -895,9 +895,9 @@ def setup(self): super(Function, self).setup() - fillfuncargs(self) if hasattr(self, "_request"): self._request._callsetup() + fillfuncargs(self) def __eq__(self, other): try: diff -r d9eff229919e8ccf3d32cd66d4828eaeb74e332c -r 07e5a98aceca50ad07e99b226baf6dd9d84ce380 testing/test_python.py --- a/testing/test_python.py +++ b/testing/test_python.py @@ -2633,3 +2633,20 @@ """) reprec = testdir.inline_run() reprec.assertoutcome(passed=1) + +def test_setup_funcarg_order(testdir): + testdir.makepyfile(""" + import pytest + + l = [] + @pytest.setup() + def fix1(): + l.append(1) + @pytest.factory() + def arg1(): + l.append(2) + def test_hello(arg1): + assert l == [1,2] + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 24 11:27:08 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Mon, 24 Sep 2012 09:27:08 -0000 Subject: [py-svn] commit/pytest: RonnyPfannschmidt: get rid of _memoizedcall - we dont really need it anymore Message-ID: <20120924092708.19714.82471@bitbucket15.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/cfe628a3d132/ changeset: cfe628a3d132 user: RonnyPfannschmidt date: 2012-09-24 11:26:38 summary: get rid of _memoizedcall - we dont really need it anymore affected #: 3 files diff -r 07e5a98aceca50ad07e99b226baf6dd9d84ce380 -r cfe628a3d13254a5f6ea8a52656f014f0160116c _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -251,24 +251,6 @@ def teardown(self): pass - def _memoizedcall(self, attrname, function): - exattrname = "_ex_" + attrname - failure = getattr(self, exattrname, None) - if failure is not None: - py.builtin._reraise(failure[0], failure[1], failure[2]) - if hasattr(self, attrname): - return getattr(self, attrname) - try: - res = function() - except py.builtin._sysex: - raise - except: - failure = py.std.sys.exc_info() - setattr(self, exattrname, failure) - raise - setattr(self, attrname, res) - return res - def listchain(self): """ return list of all parent collectors up to self, starting from root of collection tree. """ @@ -345,10 +327,6 @@ return str(exc.args[0]) return self._repr_failure_py(excinfo, style="short") - def _memocollect(self): - """ internal helper method to cache results of calling collect(). """ - return self._memoizedcall('_collected', lambda: list(self.collect())) - def _prunetraceback(self, excinfo): if hasattr(self, 'fspath'): path = self.fspath diff -r 07e5a98aceca50ad07e99b226baf6dd9d84ce380 -r cfe628a3d13254a5f6ea8a52656f014f0160116c _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -332,8 +332,12 @@ class Module(pytest.File, PyCollector): """ Collector for test classes and functions. """ + _obj = None + def _getobj(self): - return self._memoizedcall('_obj', self._importtestmodule) + if self._obj is None: + self._obj = self._importtestmodule() + return _obj def collect(self): self.session.funcargmanager._parsefactories(self.obj, self.nodeid) diff -r 07e5a98aceca50ad07e99b226baf6dd9d84ce380 -r cfe628a3d13254a5f6ea8a52656f014f0160116c _pytest/runner.py --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -244,7 +244,7 @@ self.__dict__.update(extra) def pytest_make_collect_report(collector): - call = CallInfo(collector._memocollect, "memocollect") + call = CallInfo(lambda: list(collector.collect()), "collect") longrepr = None if not call.excinfo: outcome = "passed" Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 24 11:36:48 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Mon, 24 Sep 2012 09:36:48 -0000 Subject: [py-svn] commit/pytest: RonnyPfannschmidt: backout, the _memoizedcall change worked only due to a local effect Message-ID: <20120924093648.4068.49628@bitbucket23.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/da7434cf50bc/ changeset: da7434cf50bc user: RonnyPfannschmidt date: 2012-09-24 11:36:24 summary: backout, the _memoizedcall change worked only due to a local effect affected #: 3 files diff -r cfe628a3d13254a5f6ea8a52656f014f0160116c -r da7434cf50bc85c393c42c1559ced0715f4db5e7 _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -251,6 +251,24 @@ def teardown(self): pass + def _memoizedcall(self, attrname, function): + exattrname = "_ex_" + attrname + failure = getattr(self, exattrname, None) + if failure is not None: + py.builtin._reraise(failure[0], failure[1], failure[2]) + if hasattr(self, attrname): + return getattr(self, attrname) + try: + res = function() + except py.builtin._sysex: + raise + except: + failure = py.std.sys.exc_info() + setattr(self, exattrname, failure) + raise + setattr(self, attrname, res) + return res + def listchain(self): """ return list of all parent collectors up to self, starting from root of collection tree. """ @@ -327,6 +345,10 @@ return str(exc.args[0]) return self._repr_failure_py(excinfo, style="short") + def _memocollect(self): + """ internal helper method to cache results of calling collect(). """ + return self._memoizedcall('_collected', lambda: list(self.collect())) + def _prunetraceback(self, excinfo): if hasattr(self, 'fspath'): path = self.fspath diff -r cfe628a3d13254a5f6ea8a52656f014f0160116c -r da7434cf50bc85c393c42c1559ced0715f4db5e7 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -332,12 +332,8 @@ class Module(pytest.File, PyCollector): """ Collector for test classes and functions. """ - _obj = None - def _getobj(self): - if self._obj is None: - self._obj = self._importtestmodule() - return _obj + return self._memoizedcall('_obj', self._importtestmodule) def collect(self): self.session.funcargmanager._parsefactories(self.obj, self.nodeid) diff -r cfe628a3d13254a5f6ea8a52656f014f0160116c -r da7434cf50bc85c393c42c1559ced0715f4db5e7 _pytest/runner.py --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -244,7 +244,7 @@ self.__dict__.update(extra) def pytest_make_collect_report(collector): - call = CallInfo(lambda: list(collector.collect()), "collect") + call = CallInfo(collector._memocollect, "memocollect") longrepr = None if not call.excinfo: outcome = "passed" Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 24 17:05:44 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Mon, 24 Sep 2012 15:05:44 -0000 Subject: [py-svn] commit/pytest: hpk42: - make request.funcargnames carry the closure of all used funcargs Message-ID: <20120924150544.1464.91820@bitbucket25.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/a25a79906b2e/ changeset: a25a79906b2e user: hpk42 date: 2012-09-24 17:04:34 summary: - make request.funcargnames carry the closure of all used funcargs - make metafunc.funcargnames carry the closure of used funcargs affected #: 2 files diff -r da7434cf50bc85c393c42c1559ced0715f4db5e7 -r a25a79906b2ec440cb9033172bd4b318b875518c _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -296,7 +296,7 @@ clscol = self.getparent(Class) cls = clscol and clscol.obj or None transfer_markers(funcobj, cls, module) - metafunc = Metafunc(funcobj, parentid=self.nodeid, config=self.config, + metafunc = Metafunc(funcobj, parentnode=self, config=self.config, cls=cls, module=module) gentesthook = self.config.hook.pytest_generate_tests extra = [module] @@ -594,13 +594,19 @@ class Metafunc: def __init__(self, function, config=None, cls=None, module=None, - parentid=""): + parentnode=None): self.config = config self.module = module self.function = function - self.parentid = parentid - self.funcargnames = getfuncargnames(function, - startindex=int(cls is not None)) + self.parentnode = parentnode + self.parentid = getattr(parentnode, "nodeid", "") + argnames = getfuncargnames(function, startindex=int(cls is not None)) + if parentnode is not None: + fm = parentnode.session.funcargmanager + self.funcargnames, self._arg2facdeflist = fm.getallfuncargnames( + argnames, parentnode) + else: + self.funcargnames = argnames self.cls = cls self.module = module self._calls = [] @@ -961,8 +967,11 @@ self._name2factory = {} self.funcargmanager = pyfuncitem.session.funcargmanager self._currentarg = None - self.funcargnames = getfuncargnames(self.function) self.parentid = pyfuncitem.parent.nodeid + self.funcargnames, self._arg2facdeflist_ = \ + self.funcargmanager.getallfuncargnames( + getfuncargnames(self.function), # XXX _pyfuncitem... + pyfuncitem.parent) self._factorystack = [] @property @@ -972,19 +981,20 @@ def _getfaclist(self, argname): facdeflist = self._name2factory.get(argname, None) + getfactb = None + function = None if facdeflist is None: if self._factorystack: function = self._factorystack[-1].func getfactb = lambda: self._factorystack[:-1] else: function = self.function - getfactb = None facdeflist = self.funcargmanager.getfactorylist( - argname, self.parentid, function, getfactb) + argname, self.parentid) self._name2factory[argname] = facdeflist - elif not facdeflist: - self.funcargmanager._raiselookupfailed(argname, self.function, - self.parentid) + if not facdeflist: + self.funcargmanager._raiselookupfailed(argname, function, + self.parentid, getfactb) return facdeflist @property @@ -1068,15 +1078,14 @@ def _fillfuncargs(self): - if self.funcargnames: - assert not getattr(self._pyfuncitem, '_args', None), ( + item = self._pyfuncitem + funcargnames = getattr(item, "funcargnames", self.funcargnames) + if funcargnames: + assert not getattr(item, '_args', None), ( "yielded functions cannot have funcargs") - while self.funcargnames: - argname = self.funcargnames.pop(0) - if argname not in self._pyfuncitem.funcargs: - self._pyfuncitem.funcargs[argname] = \ - self.getfuncargvalue(argname) - + for argname in funcargnames: + if argname not in item.funcargs: + item.funcargs[argname] = self.getfuncargvalue(argname) def _callsetup(self): self.funcargmanager.ensure_setupcalls(self) @@ -1305,26 +1314,47 @@ for plugin in plugins: self.pytest_plugin_registered(plugin) + def getallfuncargnames(self, funcargnames, parentnode): + # collect the closure of all funcargs, starting with + # funcargnames as the initial set + # we populate and return a arg2facdeflist mapping + # so that the caller can reuse it and does not have to re-discover + # factories again for each funcargname + parentid = parentnode.nodeid + funcargnames = list(funcargnames) + _, setupargs = self.getsetuplist(parentnode) + def merge(otherlist): + for arg in otherlist: + if arg not in funcargnames: + funcargnames.append(arg) + merge(setupargs) + arg2facdeflist = {} + lastlen = -1 + while lastlen != len(funcargnames): + lastlen = len(funcargnames) + for argname in list(funcargnames): + if argname in arg2facdeflist: + continue + facdeflist = self.getfactorylist(argname, parentid) + arg2facdeflist[argname] = facdeflist + if facdeflist is not None: + for facdef in facdeflist: + merge(facdef.funcargnames) + try: + funcargnames.remove("__request__") + except ValueError: + pass + return funcargnames, arg2facdeflist + def pytest_generate_tests(self, metafunc): - funcargnames = list(metafunc.funcargnames) - _, allargnames = self.getsetuplist(metafunc.parentid) - #print "setuplist, allargnames", setuplist, allargnames - funcargnames.extend(allargnames) - seen = set() - while funcargnames: - argname = funcargnames.pop(0) - if argname in seen or argname == "request": - continue - seen.add(argname) - faclist = self.getfactorylist(argname, metafunc.parentid, - metafunc.function, raising=False) + for argname in metafunc.funcargnames: + faclist = metafunc._arg2facdeflist[argname] if faclist is None: continue # will raise FuncargLookupError at setup time for facdef in faclist: if facdef.params is not None: metafunc.parametrize(argname, facdef.params, indirect=True, - scope=facdef.scope) - funcargnames.extend(facdef.funcargnames) + scope=facdef.scope) def pytest_collection_modifyitems(self, items): # separate parametrized setups @@ -1388,7 +1418,8 @@ faclist.append(factorydef) ### check scope/params mismatch? - def getsetuplist(self, nodeid): + def getsetuplist(self, node): + nodeid = node.nodeid l = [] allargnames = set() for setupcall in self.setuplist: @@ -1399,12 +1430,11 @@ return l, allargnames - def getfactorylist(self, argname, nodeid, function, getfactb=None, raising=True): + def getfactorylist(self, argname, nodeid): try: factorydeflist = self.arg2facspec[argname] except KeyError: - if raising: - self._raiselookupfailed(argname, function, nodeid, getfactb) + return None else: return self._matchfactories(factorydeflist, nodeid) @@ -1429,7 +1459,7 @@ raise FuncargLookupError(function, msg, lines) def ensure_setupcalls(self, request): - setuplist, allnames = self.getsetuplist(request._pyfuncitem.nodeid) + setuplist, allnames = self.getsetuplist(request._pyfuncitem) for setupcall in setuplist: if setupcall.active: continue diff -r da7434cf50bc85c393c42c1559ced0715f4db5e7 -r a25a79906b2ec440cb9033172bd4b318b875518c testing/test_python.py --- a/testing/test_python.py +++ b/testing/test_python.py @@ -1340,6 +1340,27 @@ "*1 passed*" ]) + def test_parametrize_on_setup_arg(self, testdir): + p = testdir.makepyfile(""" + def pytest_generate_tests(metafunc): + assert "arg1" in metafunc.funcargnames + metafunc.parametrize("arg1", [1], indirect=True) + + def pytest_funcarg__arg1(request): + return request.param + + def pytest_funcarg__arg2(request, arg1): + return 10 * arg1 + + def test_func(arg2): + assert arg2 == 10 + """) + result = testdir.runpytest("-v", p) + result.stdout.fnmatch_lines([ + "*test_func*1*PASS*", + "*1 passed*" + ]) + def test_conftest_funcargs_only_available_in_subdir(testdir): sub1 = testdir.mkpydir("sub1") @@ -1648,38 +1669,6 @@ "*ZeroDivisionError*", ]) -class TestRequestAPI: - @pytest.mark.xfail(reason="consider flub feedback") - def test_setup_can_query_funcargs(self, testdir): - testdir.makeconftest(""" - def pytest_runtest_setup(item): - assert not hasattr(item, "_request") - """) - testdir.makepyfile(""" - def pytest_funcarg__a(request): - return 1 - def pytest_funcarg__b(request): - return request.getfuncargvalue("a") + 1 - def test_hello(b): - assert b == 2 - """) - result = testdir.runpytest() - assert result.ret == 0 - result.stdout.fnmatch_lines([ - "*1 passed*", - ]) - - result = testdir.makeconftest(""" - import pytest - @pytest.setup() - def mysetup(request): - request.uses_funcarg("db") - """) - result = testdir.runpytest() - assert result.ret == 0 - result.stdout.fnmatch_lines([ - "*1 passed*", - ]) class TestFuncargFactory: def test_receives_funcargs(self, testdir): @@ -1828,7 +1817,7 @@ testdir.makepyfile(""" def test_hello(item, fm): for name in ("fm", "hello", "item"): - faclist = fm.getfactorylist(name, item.nodeid, item.obj) + faclist = fm.getfactorylist(name, item.nodeid) assert len(faclist) == 1 fac = faclist[0] assert fac.func.__name__ == "pytest_funcarg__" + name @@ -1844,7 +1833,7 @@ def pytest_funcarg__hello(self, request): return "class" def test_hello(self, item, fm): - faclist = fm.getfactorylist("hello", item.nodeid, item.obj) + faclist = fm.getfactorylist("hello", item.nodeid) print (faclist) assert len(faclist) == 3 assert faclist[0].func(item._request) == "conftest" @@ -1880,7 +1869,7 @@ def test_parsefactories_conftest(self, testdir): testdir.makepyfile(""" def test_check_setup(item, fm): - setupcalls, allnames = fm.getsetuplist(item.nodeid) + setupcalls, allnames = fm.getsetuplist(item) assert len(setupcalls) == 2 assert setupcalls[0].func.__name__ == "perfunction" assert "request" in setupcalls[0].funcargnames @@ -2650,3 +2639,23 @@ """) reprec = testdir.inline_run() reprec.assertoutcome(passed=1) + + +def test_request_funcargnames(testdir): + testdir.makepyfile(""" + import pytest + @pytest.factory() + def arg1(): + pass + @pytest.factory() + def farg(arg1): + pass + @pytest.setup() + def sarg(tmpdir): + pass + def test_function(request, farg): + assert set(request.funcargnames) == \ + set(["tmpdir", "arg1", "request", "farg"]) + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue Sep 25 11:58:53 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Tue, 25 Sep 2012 09:58:53 -0000 Subject: [py-svn] commit/pytest: hpk42: avoid double-instantiation of PluginManager in case of the "python pytest.py" or -m pytest invocation Message-ID: <20120925095853.17612.25611@bitbucket25.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/d46b4480e6de/ changeset: d46b4480e6de user: hpk42 date: 2012-09-25 11:58:41 summary: avoid double-instantiation of PluginManager in case of the "python pytest.py" or -m pytest invocation affected #: 1 file diff -r a25a79906b2ec440cb9033172bd4b318b875518c -r d46b4480e6de290d9b965618981f9f186030380f pytest.py --- a/pytest.py +++ b/pytest.py @@ -1,13 +1,15 @@ """ -unit and functional testing with Python. +pytest: unit and functional testing with Python. """ __all__ = ['main'] -from _pytest.core import main, UsageError, _preloadplugins -from _pytest import core as cmdline -from _pytest import __version__ - if __name__ == '__main__': # if run as a script or by 'python -m pytest' - raise SystemExit(main()) + # we trigger the below "else" condition by the following import + import pytest + raise SystemExit(pytest.main()) else: + # we are simply imported + from _pytest.core import main, UsageError, _preloadplugins + from _pytest import core as cmdline + from _pytest import __version__ _preloadplugins() # to populate pytest.* namespace so help(pytest) works Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue Sep 25 15:04:38 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Tue, 25 Sep 2012 13:04:38 -0000 Subject: [py-svn] commit/pytest: hpk42: add an xfail-ed test for a potential future "enabled" parameter to setup functions Message-ID: <20120925130438.22544.67621@bitbucket03.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/9f3ba4c42151/ changeset: 9f3ba4c42151 user: hpk42 date: 2012-09-25 15:04:30 summary: add an xfail-ed test for a potential future "enabled" parameter to setup functions affected #: 2 files diff -r d46b4480e6de290d9b965618981f9f186030380f -r 9f3ba4c42151726a208dd4541288817edfb6cf44 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -19,8 +19,10 @@ return function class SetupMarker: - def __init__(self, scope): + def __init__(self, scope, enabled): self.scope = scope + self.enabled = enabled + def __call__(self, function): function._pytestsetup = self return function @@ -36,14 +38,17 @@ """ return FactoryMarker(scope, params) -def setup(scope="function"): +def setup(scope="function", enabled=None): """ return a decorator to mark functions as setup functions. :arg scope: the scope for which the setup function will be active, one of "function", "class", "module", "session". Defaults to "function". + :arg enabled: if a callable is specified, enabled(node) will be called + and if it returns a false value this setup function + will not be called and its funcargs will not be setup. """ - return SetupMarker(scope) + return SetupMarker(scope, enabled) def cached_property(f): """returns a cached property that is calculated by function f. diff -r d46b4480e6de290d9b965618981f9f186030380f -r 9f3ba4c42151726a208dd4541288817edfb6cf44 testing/test_python.py --- a/testing/test_python.py +++ b/testing/test_python.py @@ -1898,6 +1898,32 @@ reprec = testdir.inline_run("-s") reprec.assertoutcome(passed=2) + @pytest.mark.xfail(reason="'enabled' feature not implemented") + def test_setup_enabled_functionnode(self, testdir): + testdir.makepyfile(""" + import pytest + + def enabled(parentnode, markers): + return "needsdb" in markers + + @pytest.factory(params=[1,2]) + def db(request): + return request.param + + @pytest.setup(enabled=enabled) + def createdb(db): + pass + + def test_func1(request): + assert "db" not in request.funcargnames + + @pytest.mark.needsdb + def test_func2(request): + assert "db" in request.funcargnames + """) + reprec = testdir.inline_run("-s") + reprec.assertoutcome(passed=2) + def test_callables_nocode(self, testdir): """ a imported mock.call would break setup/factory discovery Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue Sep 25 15:14:04 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Tue, 25 Sep 2012 13:14:04 -0000 Subject: [py-svn] commit/pytest: hpk42: back out accidental changes introduced by last patch Message-ID: <20120925131404.14550.15767@bitbucket21.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/a5f4cbe6a5a5/ changeset: a5f4cbe6a5a5 user: hpk42 date: 2012-09-25 15:13:58 summary: back out accidental changes introduced by last patch affected #: 1 file diff -r 9f3ba4c42151726a208dd4541288817edfb6cf44 -r a5f4cbe6a5a5c8ca70c7409f93ea00463f9855b8 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -19,10 +19,8 @@ return function class SetupMarker: - def __init__(self, scope, enabled): + def __init__(self, scope): self.scope = scope - self.enabled = enabled - def __call__(self, function): function._pytestsetup = self return function @@ -38,17 +36,14 @@ """ return FactoryMarker(scope, params) -def setup(scope="function", enabled=None): +def setup(scope="function"): """ return a decorator to mark functions as setup functions. :arg scope: the scope for which the setup function will be active, one of "function", "class", "module", "session". Defaults to "function". - :arg enabled: if a callable is specified, enabled(node) will be called - and if it returns a false value this setup function - will not be called and its funcargs will not be setup. """ - return SetupMarker(scope, enabled) + return SetupMarker(scope) def cached_property(f): """returns a cached property that is calculated by function f. Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue Sep 25 18:16:35 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Tue, 25 Sep 2012 16:16:35 -0000 Subject: [py-svn] commit/pytest: RonnyPfannschmidt: fixes issue 156: monkeypatch class level descriptors Message-ID: <20120925161635.21300.14514@bitbucket22.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/76972802c033/ changeset: 76972802c033 user: RonnyPfannschmidt date: 2012-09-25 18:15:13 summary: fixes issue 156: monkeypatch class level descriptors affected #: 3 files diff -r a5f4cbe6a5a5c8ca70c7409f93ea00463f9855b8 -r 76972802c0339c9309af31158983d683e01461a4 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -56,7 +56,8 @@ - fix issue 188: ensure sys.exc_info is clear on python2 before calling into a test -- fix issue 191: addd unittest TestCase runTest method support +- fix issue 191: add unittest TestCase runTest method support +- fix issue 156: monkeypatch correctly handles class level descriptors - reporting refinements: diff -r a5f4cbe6a5a5c8ca70c7409f93ea00463f9855b8 -r 76972802c0339c9309af31158983d683e01461a4 _pytest/monkeypatch.py --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -1,6 +1,6 @@ """ monkeypatching and mocking functionality. """ -import os, sys +import os, sys, inspect def pytest_funcarg__monkeypatch(request): """The returned ``monkeypatch`` funcarg provides these @@ -39,6 +39,10 @@ oldval = getattr(obj, name, notset) if raising and oldval is notset: raise AttributeError("%r has no attribute %r" %(obj, name)) + + # avoid class descriptors like staticmethod/classmethod + if inspect.isclass(obj): + oldval = obj.__dict__.get(name, notset) self._setattr.insert(0, (obj, name, oldval)) setattr(obj, name, value) diff -r a5f4cbe6a5a5c8ca70c7409f93ea00463f9855b8 -r 76972802c0339c9309af31158983d683e01461a4 testing/test_monkeypatch.py --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -206,3 +206,38 @@ result.stdout.fnmatch_lines(""" *1 passed* """) + + + +class SampleNew(object): + @staticmethod + def hello(): + return True + + +class SampleNewInherit(SampleNew): + pass + + +class SampleOld: + #oldstyle on python2 + @staticmethod + def hello(): + return True + +class SampleOldInherit(SampleOld): + pass + + + at pytest.mark.parametrize('Sample', [ + SampleNew, SampleNewInherit, + SampleOld, SampleOldInherit, +], ids=['new', 'new-inherit', 'old', 'old-inherit']) +def test_issue156_undo_staticmethod(Sample): + monkeypatch = MonkeyPatch() + + monkeypatch.setattr(Sample, 'hello', None) + assert Sample.hello is None + + monkeypatch.undo() + assert Sample.hello() Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Wed Sep 26 12:46:33 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Wed, 26 Sep 2012 10:46:33 -0000 Subject: [py-svn] commit/pytest: hpk42: ensure proper calling of finalizers in case of parametrization on classes Message-ID: <20120926104633.32302.83893@bitbucket05.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/1c8e15ae7969/ changeset: 1c8e15ae7969 user: hpk42 date: 2012-09-26 12:24:04 summary: ensure proper calling of finalizers in case of parametrization on classes affected #: 4 files diff -r 76972802c0339c9309af31158983d683e01461a4 -r 1c8e15ae79692b3c49204fca94769e0ee7e03429 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.3.0.dev16' +__version__ = '2.3.0.dev17' diff -r 76972802c0339c9309af31158983d683e01461a4 -r 1c8e15ae79692b3c49204fca94769e0ee7e03429 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1475,6 +1475,9 @@ self.session._setupstate.addfinalizer(setupcall.finish, scol) for argname in setupcall.funcargnames: # XXX all deps? self.addargfinalizer(setupcall.finish, argname) + req = kwargs.get("request", None) + if req is not None: + mp.setattr(req, "addfinalizer", setupcall.addfinalizer) # for unittest-setup methods we need to provide # the correct instance posargs = () diff -r 76972802c0339c9309af31158983d683e01461a4 -r 1c8e15ae79692b3c49204fca94769e0ee7e03429 setup.py --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.3.0.dev16', + version='2.3.0.dev17', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff -r 76972802c0339c9309af31158983d683e01461a4 -r 1c8e15ae79692b3c49204fca94769e0ee7e03429 testing/test_python.py --- a/testing/test_python.py +++ b/testing/test_python.py @@ -2076,6 +2076,32 @@ reprec.assertoutcome(passed=1) + def test_parametrization_setup_teardown_ordering(self, testdir): + testdir.makepyfile(""" + import pytest + l = [] + def pytest_generate_tests(metafunc): + if metafunc.cls is not None: + metafunc.parametrize("item", [1,2], scope="class") + class TestClass: + @pytest.setup(scope="class") + def addteardown(self, item, request): + request.addfinalizer(lambda: l.append("teardown-%d" % item)) + l.append("setup-%d" % item) + def test_step1(self, item): + l.append("step1-%d" % item) + def test_step2(self, item): + l.append("step2-%d" % item) + + def test_finish(): + print l + assert l == ["setup-1", "step1-1", "step2-1", "teardown-1", + "setup-2", "step1-2", "step2-2", "teardown-2",] + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=5) + + class TestFuncargMarker: def test_parametrize(self, testdir): testdir.makepyfile(""" Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Thu Sep 27 13:27:30 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Thu, 27 Sep 2012 11:27:30 -0000 Subject: [py-svn] commit/pytest: hpk42: remove print, pass python32 Message-ID: <20120927112730.12569.38843@bitbucket02.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/9605c7f187c7/ changeset: 9605c7f187c7 user: hpk42 date: 2012-09-27 13:27:22 summary: remove print, pass python32 affected #: 1 file diff -r 1c8e15ae79692b3c49204fca94769e0ee7e03429 -r 9605c7f187c700413544b84c3eb65bc6eafa0a4f testing/test_python.py --- a/testing/test_python.py +++ b/testing/test_python.py @@ -2094,7 +2094,6 @@ l.append("step2-%d" % item) def test_finish(): - print l assert l == ["setup-1", "step1-1", "step2-1", "teardown-1", "setup-2", "step1-2", "step2-2", "teardown-2",] """) Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Thu Sep 27 15:10:06 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Thu, 27 Sep 2012 13:10:06 -0000 Subject: [py-svn] commit/pytest-xdist: hpk42: mention that --boxed is not available on windows Message-ID: <20120927131006.9729.5887@bitbucket21.managed.contegix.com> 1 new commit in pytest-xdist: https://bitbucket.org/hpk42/pytest-xdist/changeset/db56e4b41e9b/ changeset: db56e4b41e9b user: hpk42 date: 2012-09-27 15:09:55 summary: mention that --boxed is not available on windows affected #: 1 file diff -r 99549e737f220f58e57d8cb1a107370f15379568 -r db56e4b41e9bff631eec7b1f3db9dd5cf1b3e574 README.txt --- a/README.txt +++ b/README.txt @@ -8,8 +8,8 @@ those for a combined test run. This allows to speed up development or to use special resources of `remote machines`_. -* ``--boxed``: run each test in a boxed_ subprocess to survive ``SEGFAULTS`` or - otherwise dying processes +* ``--boxed``: (not available on Windows) run each test in a boxed_ + subprocess to survive ``SEGFAULTS`` or otherwise dying processes * ``--looponfail``: run your tests repeatedly in a subprocess. After each run py.test waits until a file in your project changes and then re-runs Repository URL: https://bitbucket.org/hpk42/pytest-xdist/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sun Sep 30 22:19:20 2012 From: commits-noreply at bitbucket.org (Bitbucket) Date: Sun, 30 Sep 2012 20:19:20 -0000 Subject: [py-svn] commit/pytest: RonnyPfannschmidt: move Item.applymarker to Node, and defer to it from Funcargrequest.applymarker Message-ID: <20120930201920.25752.20500@bitbucket01.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/changeset/1f79503ce1f8/ changeset: 1f79503ce1f8 user: RonnyPfannschmidt date: 2012-09-30 22:17:33 summary: move Item.applymarker to Node, and defer to it from Funcargrequest.applymarker affected #: 2 files diff -r 9605c7f187c700413544b84c3eb65bc6eafa0a4f -r 1f79503ce1f8a68185c7f8b2900546fb84606adb _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -196,6 +196,18 @@ """ dictionary of Keywords / markers on this node. """ return vars(self.markers) + def applymarker(self, marker): + """ Apply a marker to this item. This method is + useful if you have several parametrized function + and want to mark a single one of them. + + :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object + created by a call to ``py.test.mark.NAME(...)``. + """ + if not isinstance(marker, pytest.mark.XYZ.__class__): + raise ValueError("%r is not a py.test.mark.* object") + setattr(self.markers, marker.markname, marker) + #def extrainit(self): # """"extra initialization after Node is initialized. Implemented # by some subclasses. """ @@ -390,19 +402,6 @@ def reportinfo(self): return self.fspath, None, "" - def applymarker(self, marker): - """ Apply a marker to this item. This method is - useful if you have several parametrized function - and want to mark a single one of them. - - :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object - created by a call to ``py.test.mark.NAME(...)``. - """ - if not isinstance(marker, pytest.mark.XYZ.__class__): - raise ValueError("%r is not a py.test.mark.* object") - self.keywords[marker.markname] = marker - - @property def location(self): try: diff -r 9605c7f187c700413544b84c3eb65bc6eafa0a4f -r 1f79503ce1f8a68185c7f8b2900546fb84606adb _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1068,9 +1068,7 @@ :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object created by a call to ``py.test.mark.NAME(...)``. """ - if not isinstance(marker, py.test.mark.XYZ.__class__): - raise ValueError("%r is not a py.test.mark.* object") - setattr(self.node.markers, marker.markname, marker) + self.node.applymarker(marker) def raiseerror(self, msg): """ raise a FuncargLookupError with the given message. """ Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.