From commits-noreply at bitbucket.org Mon Oct 4 12:10:22 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 4 Oct 2010 05:10:22 -0500 (CDT) Subject: [py-svn] py-trunk commit 866bbb5d9006: small simplification and shuffling of python tests, no content change Message-ID: <20101004101022.7B55C1E0FA3@bitbucket02.managed.contegix.com> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1286183055 -7200 # Node ID 866bbb5d9006c7fb377f092cccbc962b9d801df3 # Parent 969f17b64242d78a2697a9ec030f111901f150a1 small simplification and shuffling of python tests, no content change --- a/testing/plugin/test_pytest_python.py +++ b/testing/plugin/test_pytest_python.py @@ -55,11 +55,6 @@ class TestClass: l = modcol.collect() assert len(l) == 0 -if py.std.sys.version_info > (3, 0): - _func_name_attr = "__name__" -else: - _func_name_attr = "func_name" - class TestGenerator: def test_generative_functions(self, testdir): modcol = testdir.getmodulecol(""" @@ -79,7 +74,7 @@ class TestGenerator: assert isinstance(gencolitems[0], py.test.collect.Function) assert isinstance(gencolitems[1], py.test.collect.Function) assert gencolitems[0].name == '[0]' - assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1' + assert gencolitems[0].obj.__name__ == 'func1' def test_generative_methods(self, testdir): modcol = testdir.getmodulecol(""" @@ -97,7 +92,7 @@ class TestGenerator: assert isinstance(gencolitems[0], py.test.collect.Function) assert isinstance(gencolitems[1], py.test.collect.Function) assert gencolitems[0].name == '[0]' - assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1' + assert gencolitems[0].obj.__name__ == 'func1' def test_generative_functions_with_explicit_names(self, testdir): modcol = testdir.getmodulecol(""" @@ -117,9 +112,9 @@ class TestGenerator: assert isinstance(gencolitems[0], py.test.collect.Function) assert isinstance(gencolitems[1], py.test.collect.Function) assert gencolitems[0].name == "['seventeen']" - assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1' + assert gencolitems[0].obj.__name__ == 'func1' assert gencolitems[1].name == "['fortytwo']" - assert getattr(gencolitems[1].obj, _func_name_attr) == 'func1' + assert gencolitems[1].obj.__name__ == 'func1' def test_generative_functions_unique_explicit_names(self, testdir): # generative @@ -151,9 +146,9 @@ class TestGenerator: assert isinstance(gencolitems[0], py.test.collect.Function) assert isinstance(gencolitems[1], py.test.collect.Function) assert gencolitems[0].name == "['m1']" - assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1' + assert gencolitems[0].obj.__name__ == 'func1' assert gencolitems[1].name == "['m2']" - assert getattr(gencolitems[1].obj, _func_name_attr) == 'func1' + assert gencolitems[1].obj.__name__ == 'func1' def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir): o = testdir.makepyfile(""" @@ -360,59 +355,6 @@ class TestConftestCustomization: l = modcol.collect() assert '_hello' not in l - -class TestReportinfo: - - def test_func_reportinfo(self, testdir): - item = testdir.getitem("def test_func(): pass") - fspath, lineno, modpath = item.reportinfo() - assert fspath == item.fspath - assert lineno == 0 - assert modpath == "test_func" - - def test_class_reportinfo(self, testdir): - modcol = testdir.getmodulecol(""" - # lineno 0 - class TestClass: - def test_hello(self): pass - """) - classcol = modcol.collect_by_name("TestClass") - fspath, lineno, msg = classcol.reportinfo() - assert fspath == modcol.fspath - assert lineno == 1 - assert msg == "TestClass" - - def test_generator_reportinfo(self, testdir): - modcol = testdir.getmodulecol(""" - # lineno 0 - def test_gen(): - def check(x): - assert x - yield check, 3 - """) - gencol = modcol.collect_by_name("test_gen") - fspath, lineno, modpath = gencol.reportinfo() - assert fspath == modcol.fspath - assert lineno == 1 - assert modpath == "test_gen" - - genitem = gencol.collect()[0] - fspath, lineno, modpath = genitem.reportinfo() - assert fspath == modcol.fspath - assert lineno == 2 - assert modpath == "test_gen[0]" - """ - def test_func(): - pass - def test_genfunc(): - def check(x): - pass - yield check, 3 - class TestClass: - def test_method(self): - pass - """ - def test_setup_only_available_in_subdir(testdir): sub1 = testdir.mkpydir("sub1") sub2 = testdir.mkpydir("sub2") @@ -1134,3 +1076,54 @@ class TestReportInfo: nodeinfo = runner.getitemnodeinfo(item) location = nodeinfo.location assert location == tup + + def test_func_reportinfo(self, testdir): + item = testdir.getitem("def test_func(): pass") + fspath, lineno, modpath = item.reportinfo() + assert fspath == item.fspath + assert lineno == 0 + assert modpath == "test_func" + + def test_class_reportinfo(self, testdir): + modcol = testdir.getmodulecol(""" + # lineno 0 + class TestClass: + def test_hello(self): pass + """) + classcol = modcol.collect_by_name("TestClass") + fspath, lineno, msg = classcol.reportinfo() + assert fspath == modcol.fspath + assert lineno == 1 + assert msg == "TestClass" + + def test_generator_reportinfo(self, testdir): + modcol = testdir.getmodulecol(""" + # lineno 0 + def test_gen(): + def check(x): + assert x + yield check, 3 + """) + gencol = modcol.collect_by_name("test_gen") + fspath, lineno, modpath = gencol.reportinfo() + assert fspath == modcol.fspath + assert lineno == 1 + assert modpath == "test_gen" + + genitem = gencol.collect()[0] + fspath, lineno, modpath = genitem.reportinfo() + assert fspath == modcol.fspath + assert lineno == 2 + assert modpath == "test_gen[0]" + """ + def test_func(): + pass + def test_genfunc(): + def check(x): + pass + yield check, 3 + class TestClass: + def test_method(self): + pass + """ + From commits-noreply at bitbucket.org Tue Oct 5 17:56:43 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 5 Oct 2010 10:56:43 -0500 (CDT) Subject: [py-svn] py-trunk commit 0fb6ef392e34: remove unused args Message-ID: <20101005155643.AA2B51E1289@bitbucket02.managed.contegix.com> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1286294197 -7200 # Node ID 0fb6ef392e344014ff7a52e41559ae3b6a9072f5 # Parent b3f1ef016b2dab683b5b248132263d099f79a400 remove unused args --- a/py/_plugin/pytest_capture.py +++ b/py/_plugin/pytest_capture.py @@ -253,7 +253,7 @@ def pytest_funcarg__capsys(request): them available successively via a ``capsys.readouterr()`` method which returns a ``(out, err)`` tuple of captured snapshot strings. """ - return CaptureFuncarg(request, py.io.StdCapture) + return CaptureFuncarg(py.io.StdCapture) def pytest_funcarg__capfd(request): """captures writes to file descriptors 1 and 2 and makes @@ -264,14 +264,11 @@ def pytest_funcarg__capfd(request): """ if not hasattr(os, 'dup'): py.test.skip("capfd funcarg needs os.dup") - return CaptureFuncarg(request, py.io.StdCaptureFD) - + return CaptureFuncarg(py.io.StdCaptureFD) class CaptureFuncarg: - def __init__(self, request, captureclass): - self._cclass = captureclass - self.capture = self._cclass(now=False) - #request.addfinalizer(self._finalize) + def __init__(self, captureclass): + self.capture = captureclass(now=False) def _start(self): self.capture.startall() From commits-noreply at bitbucket.org Wed Oct 13 23:52:23 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 13 Oct 2010 16:52:23 -0500 (CDT) Subject: [py-svn] apipkg commit 56f2070fcd8e: allow aliasing of whole modules Message-ID: <20101013215223.0B9A3241099@bitbucket01.managed.contegix.com> # HG changeset patch -- Bitbucket.org # Project apipkg # URL http://bitbucket.org/hpk42/apipkg/overview # User holger krekel # Date 1287006719 -7200 # Node ID 56f2070fcd8e0192bad2eb35c66e6cd0ad858209 # Parent b3d10fd5ae0b3669eed0f7eb0701dc19ac12858c allow aliasing of whole modules --- a/test_apipkg.py +++ b/test_apipkg.py @@ -124,6 +124,18 @@ class TestScenarios: assert isinstance(recmodule, apipkg.ApiModule) assert recmodule.some.__name__ == "someclass" + def test_module_alias_import(self, monkeypatch, tmpdir): + pkgdir = tmpdir.mkdir("aliasimport") + pkgdir.join('__init__.py').write(py.code.Source(""" + import apipkg + apipkg.initpkg(__name__, exportdefs={ + 'some': 'os.path', + }) + """)) + monkeypatch.syspath_prepend(tmpdir) + import aliasimport + assert aliasimport.some is py.std.os.path + def xtest_nested_absolute_imports(): import email api_email = apipkg.ApiModule('email',{ --- a/apipkg.py +++ b/apipkg.py @@ -30,7 +30,9 @@ def initpkg(pkgname, exportdefs): def importobj(modpath, attrname): module = __import__(modpath, None, None, ['__doc__']) - return getattr(module, attrname) + if attrname: + return getattr(module, attrname) + return module class ApiModule(ModuleType): def __init__(self, name, importspec, implprefix=None, attr=None): @@ -49,7 +51,9 @@ class ApiModule(ModuleType): sys.modules[subname] = apimod setattr(self, name, apimod) else: - modpath, attrname = importspec.split(':') + parts = importspec.split(':') + modpath = parts.pop(0) + attrname = parts and parts[0] or "" if modpath[0] == '.': modpath = implprefix + modpath if name == '__doc__': --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,8 @@ 1.0.0b7 - make apipkg memorize the absolute path where a package starts importing so that subsequent chdir + imports won't break. +- allow to alias modules + 1.0.0b6 ---------------------------------------- From commits-noreply at bitbucket.org Thu Oct 14 00:53:22 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 13 Oct 2010 17:53:22 -0500 (CDT) Subject: [py-svn] apipkg commit a27884316e15: fix test for python3.2 Message-ID: <20101013225322.3CD021E128B@bitbucket02.managed.contegix.com> # HG changeset patch -- Bitbucket.org # Project apipkg # URL http://bitbucket.org/hpk42/apipkg/overview # User holger krekel # Date 1287010395 -7200 # Node ID a27884316e15d9f27b41866aec73a7c2227d1a87 # Parent 144664a4ea184197e981873b961130751cc1e3b5 fix test for python3.2 --- a/test_apipkg.py +++ b/test_apipkg.py @@ -351,10 +351,8 @@ def test_dotted_name_lookup(tmpdir, monk pkgdir = tmpdir.mkdir("dotted_name_lookup") pkgdir.join('__init__.py').write(py.code.Source(""" import apipkg - apipkg.initpkg(__name__, dict(fromkeys='UserDict:UserDict.fromkeys')) + apipkg.initpkg(__name__, dict(abs='os:path.abspath')) """)) monkeypatch.syspath_prepend(tmpdir) import dotted_name_lookup - d = dotted_name_lookup.fromkeys(["a", "b"]) - print d - assert d.keys() == ["a", "b"] + assert dotted_name_lookup.abs == py.std.os.path.abspath From commits-noreply at bitbucket.org Thu Oct 14 00:53:22 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 13 Oct 2010 17:53:22 -0500 (CDT) Subject: [py-svn] apipkg commit 144664a4ea18: allow dotted name lookups, e.g. "fromkeys = 'UserDict:UserDict.fromkeys'. Message-ID: <20101013225322.199D41E117D@bitbucket02.managed.contegix.com> # HG changeset patch -- Bitbucket.org # Project apipkg # URL http://bitbucket.org/hpk42/apipkg/overview # User Ralf Schmitt # Date 1287009631 -7200 # Node ID 144664a4ea184197e981873b961130751cc1e3b5 # Parent 56f2070fcd8e0192bad2eb35c66e6cd0ad858209 allow dotted name lookups, e.g. "fromkeys = 'UserDict:UserDict.fromkeys'. --- a/test_apipkg.py +++ b/test_apipkg.py @@ -346,3 +346,15 @@ def test_chdir_with_relative_imports_sho ) assert res == 0 + +def test_dotted_name_lookup(tmpdir, monkeypatch): + pkgdir = tmpdir.mkdir("dotted_name_lookup") + pkgdir.join('__init__.py').write(py.code.Source(""" + import apipkg + apipkg.initpkg(__name__, dict(fromkeys='UserDict:UserDict.fromkeys')) + """)) + monkeypatch.syspath_prepend(tmpdir) + import dotted_name_lookup + d = dotted_name_lookup.fromkeys(["a", "b"]) + print d + assert d.keys() == ["a", "b"] --- a/apipkg.py +++ b/apipkg.py @@ -30,9 +30,14 @@ def initpkg(pkgname, exportdefs): def importobj(modpath, attrname): module = __import__(modpath, None, None, ['__doc__']) - if attrname: - return getattr(module, attrname) - return module + if not attrname: + return module + + retval = module + names = attrname.split(".") + for x in names: + retval = getattr(retval, x) + return retval class ApiModule(ModuleType): def __init__(self, name, importspec, implprefix=None, attr=None): From commits-noreply at bitbucket.org Sat Oct 16 02:59:21 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 15 Oct 2010 19:59:21 -0500 (CDT) Subject: [py-svn] pytest-codecheckers commit 722d41f66471: update to pytest Message-ID: <20101016005921.6DD2024112B@bitbucket01.managed.contegix.com> # HG changeset patch -- Bitbucket.org # Project pytest-codecheckers # URL http://bitbucket.org/RonnyPfannschmidt/pytest-codecheckers/overview # User Ronny Pfannschmidt # Date 1287190752 -7200 # Node ID 722d41f664715a4631025fec3406a244ce8bfde6 # Parent e405fd961f231dfc10d029d49ac531c48e1083ac update to pytest --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ setup( ], }, install_requires=[ - 'py>=1.2.0', + 'pytest', 'pyflakes>=0.4', 'pep8', ], --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [testenv] deps= - py + pytest pyflakes pep8 commands = --- a/tests/test_pyflakes.py +++ b/tests/test_pyflakes.py @@ -21,5 +21,5 @@ def test_reportinfo_verbose(testdir): f.write(f.read() + '\n') out = testdir.runpytest('-v', '--codecheck=pyflakes') out.stdout.fnmatch_lines([ - '*test_reportinfo_verbose.py: codecheck pyflakes PASS', + '*test_reportinfo_verbose.py: codecheck pyflakes PASSED', ]) From py-svn at codespeak.net Tue Oct 19 19:00:31 2010 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Tue, 19 Oct 2010 19:00:31 +0200 (CEST) Subject: py-svn@codespeak.net V|AGRA ® Official Site ID820 Message-ID: <20101019170031.E2851282BDA@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Thu Oct 28 03:48:00 2010 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Thu, 28 Oct 2010 03:48:00 +0200 (CEST) Subject: py-svn@codespeak.net VIAGRA ® Official Site ID0999716 Message-ID: <20101028014800.778E7282C23@codespeak.net> An HTML attachment was scrubbed... URL: From commits-noreply at bitbucket.org Sat Oct 30 19:23:13 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 30 Oct 2010 12:23:13 -0500 (CDT) Subject: [py-svn] pytest commit fb45da2c7547: add and document new parser.addini(name, description) method to describe Message-ID: <20101030172313.22C611E105A@bitbucket02.managed.contegix.com> # HG changeset patch -- Bitbucket.org # Project pytest # URL http://bitbucket.org/hpk42/pytest/overview # User holger krekel # Date 1288459430 -7200 # Node ID fb45da2c7547595731ca4aa816fdf6ee668da45f # Parent 58c688b225baa5ddbbb71bf08879091e0f16ac42 add and document new parser.addini(name, description) method to describe ini-values. Also document the parser object with its public methods. --- a/pytest/plugin/config.py +++ b/pytest/plugin/config.py @@ -10,6 +10,10 @@ def pytest_cmdline_parse(pluginmanager, config.parse(args) return config +def pytest_addoption(parser): + parser.addini('addargs', 'extra command line arguments') + parser.addini('minversion', 'minimally required pytest version') + class Parser: """ Parser for command line arguments. """ @@ -18,6 +22,7 @@ class Parser: self._groups = [] self._processopt = processopt self._usage = usage + self._inidict = {} self.hints = [] def processoption(self, option): @@ -29,6 +34,12 @@ class Parser: self._notes.append(note) def getgroup(self, name, description="", after=None): + """ get (or create) a named option Group. + + :name: unique name of the option group. + :description: long description for --help output. + :after: name of other group, used for ordering --help output. + """ for group in self._groups: if group.name == name: return group @@ -61,6 +72,14 @@ class Parser: setattr(option, name, value) return args + def addini(self, name, description): + """ add an ini-file option with the given name and description. """ + self._inidict[name] = description + + def setfromini(self, inisection, option): + for name, value in inisection.items(): + assert name in self._inidict + return setattr(option, name, value) class OptionGroup: def __init__(self, name, description="", parser=None): @@ -254,7 +273,6 @@ class Config(object): if not hasattr(self.option, opt.dest): setattr(self.option, opt.dest, opt.default) - def _getmatchingplugins(self, fspath): allconftests = self._conftest._conftestpath2mod.values() plugins = [x for x in self.pluginmanager.getplugins() @@ -305,13 +323,14 @@ class Config(object): minver, pytest.__version__)) def parse(self, args): - # cmdline arguments into this config object. + # parse given cmdline arguments into this config object. # Note that this can only be called once per testing process. assert not hasattr(self, 'args'), ( "can only parse cmdline args at most once per Config object") self._preparse(args) self._parser.hints.extend(self.pluginmanager._hints) args = self._parser.parse_setoption(args, self.option) + self._parser.setfromini(self.inicfg, self.option) if not args: args.append(py.std.os.getcwd()) self.args = args --- a/doc/customize.txt +++ b/doc/customize.txt @@ -354,6 +354,9 @@ Reference of important objects involved .. autoclass:: pytest.plugin.config.Config :members: +.. autoclass:: pytest.plugin.config.Parser + :members: + .. autoclass:: pytest.plugin.session.Item :inherited-members: @@ -367,6 +370,7 @@ Reference of important objects involved :members: + conftest.py configuration files ================================================= --- a/doc/talks.txt +++ b/doc/talks.txt @@ -18,7 +18,6 @@ function arguments: - :ref:`mysetup` - `application setup in test functions with funcargs`_ -- `making funcargs dependendent on command line options`_ - `monkey patching done right`_ (blog post, consult `monkeypatch plugin`_ for actual 1.0 API) @@ -40,7 +39,6 @@ plugin specific examples: - `many examples in the docs for plugins`_ .. _`skipping slow tests by default in py.test`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html -.. _`making funcargs dependendent on command line options`: funcargs.html#tut-cmdlineoption .. _`many examples in the docs for plugins`: plugin/index.html .. _`monkeypatch plugin`: plugin/monkeypatch.html .. _`application setup in test functions with funcargs`: funcargs.html#appsetup --- a/pytest/plugin/helpconfig.py +++ b/pytest/plugin/helpconfig.py @@ -7,7 +7,7 @@ import inspect, sys def pytest_addoption(parser): group = parser.getgroup('debugconfig') group.addoption('--version', action="store_true", - help="display py lib version and import information.") + help="display pytest lib version and import information.") group._addoption("-h", "--help", action="store_true", dest="help", help="show help message and configuration info") group._addoption('-p', action="append", dest="plugins", default = [], @@ -40,12 +40,12 @@ def showhelp(config): tw.write(config._parser.optparser.format_help()) tw.line() tw.line() - tw.sep( "=", "ini-settings") + tw.sep( "=", "config file settings") tw.line("the following values can be defined in [pytest] sections of") tw.line("setup.cfg or tox.ini files:") tw.line() - for name, help in ini_settings: + for name, help in sorted(config._parser._inidict.items()): line = " %-15s %s" %(name, help) tw.line(line[:tw.fullwidth]) @@ -59,10 +59,6 @@ def showhelp(config): tw.line() tw.sep( "=") -ini_settings = ( - ('addargs', 'extra command line arguments'), - ('minversion', 'minimally required pytest version'), -) conftest_options = ( ('pytest_plugins', 'list of plugin names to load'), --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -1,5 +1,6 @@ import py from pytest.plugin import config as parseopt +from textwrap import dedent class TestParser: def test_no_help_by_default(self, capsys): @@ -100,6 +101,18 @@ class TestParser: assert option.hello == "world" assert option.this == 42 + def test_parser_addini(self, tmpdir): + parser = parseopt.Parser() + parser.addini("myname", "my new ini value") + cfg = py.iniconfig.IniConfig("tox.ini", dedent(""" + [pytest] + myname=hello + """))['pytest'] + class option: + pass + parser.setfromini(cfg, option) + assert option.myname == "hello" + @py.test.mark.skipif("sys.version_info < (2,5)") def test_addoption_parser_epilog(testdir): testdir.makeconftest(""" --- a/testing/plugin/test_helpconfig.py +++ b/testing/plugin/test_helpconfig.py @@ -15,7 +15,7 @@ def test_help(testdir): assert result.ret == 0 result.stdout.fnmatch_lines([ "*-v*verbose*", - "*ini-settings*", + "*settings*", "*conftest.py*", ]) From commits-noreply at bitbucket.org Sun Oct 31 19:04:34 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 31 Oct 2010 13:04:34 -0500 (CDT) Subject: [py-svn] pytest commit f0ca0f882f80: allow modules/conftest files specify dotted import paths for loading plugins Message-ID: <20101031180434.B38491E103D@bitbucket02.managed.contegix.com> # HG changeset patch -- Bitbucket.org # Project pytest # URL http://bitbucket.org/hpk42/pytest/overview # User holger krekel # Date 1288548106 -3600 # Node ID f0ca0f882f802bb0bbfb3a9dd49d224b5f88cf55 # Parent c628ad6a63cd736186b7724e9ac580f05316fb3e allow modules/conftest files specify dotted import paths for loading plugins --- a/doc/customize.txt +++ b/doc/customize.txt @@ -128,15 +128,19 @@ py.test loads plugin modules at tool sta * by recursively loading all plugins specified by the ``pytest_plugins`` variable in ``conftest.py`` files -Requiring/Loading plugins in a test module or plugin +Requiring/Loading plugins in a test module or conftest file ------------------------------------------------------------- -You can require plugins in a test module or a plugin like this:: +You can require plugins in a test module or a conftest file like this:: pytest_plugins = "name1", "name2", -When the test module or plugin is loaded the specified plugins -will be loaded. +When the test module or conftest plugin is loaded the specified plugins +will be loaded as well. You can also use dotted path like this:: + + pytest_plugins = "myapp.testsupport.myplugin" + +which will import the specified module as a py.test plugin. .. _`setuptools entry points`: .. _registered: --- a/pytest/_core.py +++ b/pytest/_core.py @@ -218,15 +218,16 @@ class PluginManager(object): kwargs=kwargs, firstresult=True).execute() def canonical_importname(name): + if '.' in name: + return name name = name.lower() if not name.startswith(IMPORTPREFIX): name = IMPORTPREFIX + name return name def importplugin(importspec): - #print "importing", importspec try: - return __import__(importspec) + return __import__(importspec, None, None, '__doc__') except ImportError: e = py.std.sys.exc_info()[1] if str(e).find(importspec) == -1: @@ -241,7 +242,7 @@ def importplugin(importspec): if str(e).find(name) == -1: raise # show the original exception, not the failing internal one - return __import__(importspec) + return __import__(importspec, None, None, '__doc__') class MultiCall: --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -101,6 +101,18 @@ class TestBootstrapping: plugin2 = pluginmanager.getplugin("hello") assert plugin2 is plugin1 + def test_import_plugin_dotted_name(self, testdir): + pluginmanager = PluginManager() + py.test.raises(ImportError, 'pluginmanager.import_plugin("x.y")') + py.test.raises(ImportError, 'pluginmanager.import_plugin("pytest_x.y")') + + reset = testdir.syspathinsert() + testdir.mkpydir("pkg").join("plug.py").write("x=3") + pluginname = "pkg.plug" + pluginmanager.import_plugin(pluginname) + mod = pluginmanager.getplugin("pkg.plug") + assert mod.x == 3 + def test_consider_module(self, testdir): pluginmanager = PluginManager() testdir.syspathinsert() From commits-noreply at bitbucket.org Sun Oct 31 19:04:34 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 31 Oct 2010 13:04:34 -0500 (CDT) Subject: [py-svn] pytest commit 4400a75da165: fix --help output for ini-options Message-ID: <20101031180434.C38C51E12AA@bitbucket02.managed.contegix.com> # HG changeset patch -- Bitbucket.org # Project pytest # URL http://bitbucket.org/hpk42/pytest/overview # User holger krekel # Date 1288548158 -3600 # Node ID 4400a75da1653fbf4a932f58e720e33543148858 # Parent f0ca0f882f802bb0bbfb3a9dd49d224b5f88cf55 fix --help output for ini-options --- a/pytest/plugin/helpconfig.py +++ b/pytest/plugin/helpconfig.py @@ -44,7 +44,7 @@ def showhelp(config): tw.line("setup.cfg or tox.ini options to be put into [pytest] section:") tw.line() - for name, help in sorted(config._parser._inidict.items()): + for name, (help, type) in sorted(config._parser._inidict.items()): line = " %-15s %s" %(name, help) tw.line(line[:tw.fullwidth]) From commits-noreply at bitbucket.org Sun Oct 31 19:04:34 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 31 Oct 2010 13:04:34 -0500 (CDT) Subject: [py-svn] pytest commit b7651df53029: remove restdoc plugin which now lives as pytest-restdoc on bitbucket, Message-ID: <20101031180434.D52EF1E12B6@bitbucket02.managed.contegix.com> # HG changeset patch -- Bitbucket.org # Project pytest # URL http://bitbucket.org/hpk42/pytest/overview # User holger krekel # Date 1288548262 -3600 # Node ID b7651df530291c647314b300add17a24b72ac350 # Parent 4400a75da1653fbf4a932f58e720e33543148858 remove restdoc plugin which now lives as pytest-restdoc on bitbucket, and be easily included in a project now (like PyPy which still needs it) --- a/testing/plugin/test_restdoc.py +++ /dev/null @@ -1,96 +0,0 @@ -import py -from pytest.plugin.restdoc import deindent - -def test_deindent(): - assert deindent('foo') == 'foo' - assert deindent('foo\n bar') == 'foo\n bar' - assert deindent(' foo\n bar\n') == 'foo\nbar\n' - assert deindent(' foo\n\n bar\n') == 'foo\n\nbar\n' - assert deindent(' foo\n bar\n') == 'foo\n bar\n' - assert deindent(' foo\n bar\n') == ' foo\nbar\n' - -class TestDoctest: - def setup_class(cls): - py.test.importorskip("docutils") - - def pytest_funcarg__testdir(self, request): - testdir = request.getfuncargvalue("testdir") - testdir.plugins.append("restdoc") - assert request.module.__name__ == __name__ - testdir.makepyfile(confrest= - "from pytest.plugin.restdoc import Project") - # we scope our confrest file so that it doesn't - # conflict with another global confrest.py - testdir.makepyfile(__init__="") - return testdir - - def test_doctest_extra_exec(self, testdir): - xtxt = testdir.maketxtfile(x=""" - hello:: - .. >>> raise ValueError - >>> None - """) - result = testdir.runpytest(xtxt) - result.stdout.fnmatch_lines(['*1 fail*']) - - def test_doctest_basic(self, testdir): - xtxt = testdir.maketxtfile(x=""" - .. - >>> from os.path import abspath - - hello world - - >>> assert abspath - >>> i=3 - >>> print (i) - 3 - - yes yes - - >>> i - 3 - - end - """) - result = testdir.runpytest(xtxt) - result.stdout.fnmatch_lines([ - "*2 passed*" - ]) - - def test_doctest_eol(self, testdir): - ytxt = testdir.maketxtfile(y=".. >>> 1 + 1\r\n 2\r\n\r\n") - result = testdir.runpytest(ytxt) - result.stdout.fnmatch_lines(["*2 passed*"]) - - def test_doctest_indentation(self, testdir): - footxt = testdir.maketxtfile(foo= - '..\n >>> print ("foo\\n bar")\n foo\n bar\n') - result = testdir.runpytest(footxt) - result.stdout.fnmatch_lines(["*2 passed*"]) - - def test_js_ignore(self, testdir): - xtxt = testdir.maketxtfile(xtxt=""" - `blah`_ - - .. _`blah`: javascript:some_function() - """) - result = testdir.runpytest(xtxt) - result.stdout.fnmatch_lines(["*3 passed*"]) - - def test_pytest_doctest_prepare_content(self, testdir): - testdir.makeconftest(""" - def pytest_doctest_prepare_content(content): - return content.replace("False", "True") - """) - xtxt = testdir.maketxtfile(x=""" - hello: - - >>> 2 == 2 - False - - """) - result = testdir.runpytest(xtxt) - outcomes = result.parseoutcomes() - assert outcomes['passed'] >= 1 - assert 'failed' not in outcomes - assert 'skipped' not in outcomes --- a/pytest/plugin/restdoc.py +++ /dev/null @@ -1,429 +0,0 @@ -""" -perform ReST syntax, local and remote reference tests on .rst/.txt files. -""" -import py -import sys, os, re - -def pytest_addoption(parser): - group = parser.getgroup("ReST", "ReST documentation check options") - group.addoption('-R', '--urlcheck', - action="store_true", dest="urlcheck", default=False, - help="urlopen() remote links found in ReST text files.") - group.addoption('--urltimeout', action="store", metavar="secs", - type="int", dest="urlcheck_timeout", default=5, - help="timeout in seconds for remote urlchecks") - group.addoption('--forcegen', - action="store_true", dest="forcegen", default=False, - help="force generation of html files.") - -def pytest_collect_file(path, parent): - if path.ext in (".txt", ".rst"): - project = getproject(path) - if project is not None: - return ReSTFile(path, parent=parent, project=project) - -def getproject(path): - for parent in path.parts(reverse=True): - confrest = parent.join("confrest.py") - if confrest.check(): - Project = confrest.pyimport().Project - return Project(parent) - -class ReSTFile(py.test.collect.File): - def __init__(self, fspath, parent, project): - super(ReSTFile, self).__init__(fspath=fspath, parent=parent) - self.project = project - - def collect(self): - return [ - ReSTSyntaxTest("ReSTSyntax", parent=self, project=self.project), - LinkCheckerMaker("checklinks", parent=self), - DoctestText("doctest", parent=self), - ] - -def deindent(s, sep='\n'): - leastspaces = -1 - lines = s.split(sep) - for line in lines: - if not line.strip(): - continue - spaces = len(line) - len(line.lstrip()) - if leastspaces == -1 or spaces < leastspaces: - leastspaces = spaces - if leastspaces == -1: - return s - for i, line in enumerate(lines): - if not line.strip(): - lines[i] = '' - else: - lines[i] = line[leastspaces:] - return sep.join(lines) - -class ReSTSyntaxTest(py.test.collect.Item): - def __init__(self, name, parent, project): - super(ReSTSyntaxTest, self).__init__(name=name, parent=parent) - self.project = project - - def reportinfo(self): - return self.fspath, None, "syntax check" - - def runtest(self): - self.restcheck(py.path.svnwc(self.fspath)) - - def restcheck(self, path): - py.test.importorskip("docutils") - self.register_linkrole() - from docutils.utils import SystemMessage - try: - self._checkskip(path, self.project.get_htmloutputpath(path)) - self.project.process(path) - except KeyboardInterrupt: - raise - except SystemMessage: - # we assume docutils printed info on stdout - py.test.fail("docutils processing failed, see captured stderr") - - def register_linkrole(self): - #directive.register_linkrole('api', self.resolve_linkrole) - #directive.register_linkrole('source', self.resolve_linkrole) -# -# # XXX fake sphinx' "toctree" and refs -# directive.register_linkrole('ref', self.resolve_linkrole) - - from docutils.parsers.rst import directives - def toctree_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - return [] - toctree_directive.content = 1 - toctree_directive.options = {'maxdepth': int, 'glob': directives.flag, - 'hidden': directives.flag} - directives.register_directive('toctree', toctree_directive) - self.register_pygments() - - def register_pygments(self): - # taken from pygments-main/external/rst-directive.py - from docutils.parsers.rst import directives - try: - from pygments.formatters import HtmlFormatter - except ImportError: - def pygments_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - return [] - pygments_directive.options = {} - else: - # The default formatter - DEFAULT = HtmlFormatter(noclasses=True) - # Add name -> formatter pairs for every variant you want to use - VARIANTS = { - # 'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=True), - } - - from docutils import nodes - - from pygments import highlight - from pygments.lexers import get_lexer_by_name, TextLexer - - def pygments_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - try: - lexer = get_lexer_by_name(arguments[0]) - except ValueError: - # no lexer found - use the text one instead of an exception - lexer = TextLexer() - # take an arbitrary option if more than one is given - formatter = options and VARIANTS[options.keys()[0]] or DEFAULT - parsed = highlight('\n'.join(content), lexer, formatter) - return [nodes.raw('', parsed, format='html')] - - pygments_directive.options = dict([(key, directives.flag) for key in VARIANTS]) - - pygments_directive.arguments = (1, 0, 1) - pygments_directive.content = 1 - directives.register_directive('sourcecode', pygments_directive) - - def resolve_linkrole(self, name, text, check=True): - apigen_relpath = self.project.apigen_relpath - - if name == 'api': - if text == 'py': - return ('py', apigen_relpath + 'api/index.html') - else: - assert text.startswith('py.'), ( - 'api link "%s" does not point to the py package') % (text,) - dotted_name = text - if dotted_name.find('(') > -1: - dotted_name = dotted_name[:text.find('(')] - # remove pkg root - path = dotted_name.split('.')[1:] - dotted_name = '.'.join(path) - obj = py - if check: - for chunk in path: - try: - obj = getattr(obj, chunk) - except AttributeError: - raise AssertionError( - 'problem with linkrole :api:`%s`: can not resolve ' - 'dotted name %s' % (text, dotted_name,)) - return (text, apigen_relpath + 'api/%s.html' % (dotted_name,)) - elif name == 'source': - assert text.startswith('py/'), ('source link "%s" does not point ' - 'to the py package') % (text,) - relpath = '/'.join(text.split('/')[1:]) - if check: - pkgroot = py._pydir - abspath = pkgroot.join(relpath) - assert pkgroot.join(relpath).check(), ( - 'problem with linkrole :source:`%s`: ' - 'path %s does not exist' % (text, relpath)) - if relpath.endswith('/') or not relpath: - relpath += 'index.html' - else: - relpath += '.html' - return (text, apigen_relpath + 'source/%s' % (relpath,)) - elif name == 'ref': - return ("", "") - - def _checkskip(self, lpath, htmlpath=None): - if not self.config.getvalue("forcegen"): - lpath = py.path.local(lpath) - if htmlpath is not None: - htmlpath = py.path.local(htmlpath) - if lpath.ext == '.txt': - htmlpath = htmlpath or lpath.new(ext='.html') - if htmlpath.check(file=1) and htmlpath.mtime() >= lpath.mtime(): - py.test.skip("html file is up to date, use --forcegen to regenerate") - #return [] # no need to rebuild - -class DoctestText(py.test.collect.Item): - def reportinfo(self): - return self.fspath, None, "doctest" - - def runtest(self): - content = self._normalize_linesep() - newcontent = self.config.hook.pytest_doctest_prepare_content(content=content) - if newcontent is not None: - content = newcontent - s = content - l = [] - prefix = '.. >>> ' - mod = py.std.types.ModuleType(self.fspath.purebasename) - skipchunk = False - for line in deindent(s).split('\n'): - stripped = line.strip() - if skipchunk and line.startswith(skipchunk): - py.builtin.print_("skipping", line) - continue - skipchunk = False - if stripped.startswith(prefix): - try: - py.builtin.exec_(py.code.Source( - stripped[len(prefix):]).compile(), mod.__dict__) - except ValueError: - e = sys.exc_info()[1] - if e.args and e.args[0] == "skipchunk": - skipchunk = " " * (len(line) - len(line.lstrip())) - else: - raise - else: - l.append(line) - docstring = "\n".join(l) - mod.__doc__ = docstring - failed, tot = py.std.doctest.testmod(mod, verbose=1) - if failed: - py.test.fail("doctest %s: %s failed out of %s" %( - self.fspath, failed, tot)) - - def _normalize_linesep(self): - # XXX quite nasty... but it works (fixes win32 issues) - s = self.fspath.read() - linesep = '\n' - if '\r' in s: - if '\n' not in s: - linesep = '\r' - else: - linesep = '\r\n' - s = s.replace(linesep, '\n') - return s - -class LinkCheckerMaker(py.test.collect.Collector): - def collect(self): - return list(self.genlinkchecks()) - - def genlinkchecks(self): - path = self.fspath - # generating functions + args as single tests - timeout = self.config.getvalue("urlcheck_timeout") - for lineno, line in enumerate(path.readlines()): - line = line.strip() - if line.startswith('.. _'): - if line.startswith('.. _`'): - delim = '`:' - else: - delim = ':' - l = line.split(delim, 1) - if len(l) != 2: - continue - tryfn = l[1].strip() - name = "%s:%d" %(tryfn, lineno) - if tryfn.startswith('http:') or tryfn.startswith('https'): - if self.config.getvalue("urlcheck"): - yield CheckLink(name, parent=self, - args=(tryfn, path, lineno, timeout), checkfunc=urlcheck) - elif tryfn.startswith('webcal:'): - continue - else: - i = tryfn.find('#') - if i != -1: - checkfn = tryfn[:i] - else: - checkfn = tryfn - if checkfn.strip() and (1 or checkfn.endswith('.html')): - yield CheckLink(name, parent=self, - args=(tryfn, path, lineno), checkfunc=localrefcheck) - -class CheckLink(py.test.collect.Item): - def __init__(self, name, parent, args, checkfunc): - super(CheckLink, self).__init__(name, parent) - self.args = args - self.checkfunc = checkfunc - - def runtest(self): - return self.checkfunc(*self.args) - - def reportinfo(self, basedir=None): - return (self.fspath, self.args[2], "checklink: %s" % self.args[0]) - -def urlcheck(tryfn, path, lineno, TIMEOUT_URLOPEN): - old = py.std.socket.getdefaulttimeout() - py.std.socket.setdefaulttimeout(TIMEOUT_URLOPEN) - try: - try: - py.builtin.print_("trying remote", tryfn) - py.std.urllib2.urlopen(tryfn) - finally: - py.std.socket.setdefaulttimeout(old) - except (py.std.urllib2.URLError, py.std.urllib2.HTTPError): - e = sys.exc_info()[1] - if getattr(e, 'code', None) in (401, 403): # authorization required, forbidden - py.test.skip("%s: %s" %(tryfn, str(e))) - else: - py.test.fail("remote reference error %r in %s:%d\n%s" %( - tryfn, path.basename, lineno+1, e)) - -def localrefcheck(tryfn, path, lineno): - # assume it should be a file - i = tryfn.find('#') - if tryfn.startswith('javascript:'): - return # don't check JS refs - if i != -1: - anchor = tryfn[i+1:] - tryfn = tryfn[:i] - else: - anchor = '' - fn = path.dirpath(tryfn) - ishtml = fn.ext == '.html' - fn = ishtml and fn.new(ext='.txt') or fn - py.builtin.print_("filename is", fn) - if not fn.check(): # not ishtml or not fn.check(): - if not py.path.local(tryfn).check(): # the html could be there - py.test.fail("reference error %r in %s:%d" %( - tryfn, path.basename, lineno+1)) - if anchor: - source = unicode(fn.read(), 'latin1') - source = source.lower().replace('-', ' ') # aehem - - anchor = anchor.replace('-', ' ') - match2 = ".. _`%s`:" % anchor - match3 = ".. _%s:" % anchor - candidates = (anchor, match2, match3) - py.builtin.print_("candidates", repr(candidates)) - for line in source.split('\n'): - line = line.strip() - if line in candidates: - break - else: - py.test.fail("anchor reference error %s#%s in %s:%d" %( - tryfn, anchor, path.basename, lineno+1)) - -if hasattr(sys.stdout, 'fileno') and os.isatty(sys.stdout.fileno()): - def log(msg): - print(msg) -else: - def log(msg): - pass - -def convert_rest_html(source, source_path, stylesheet=None, encoding='latin1'): - """ return html latin1-encoded document for the given input. - source a ReST-string - sourcepath where to look for includes (basically) - stylesheet path (to be used if any) - """ - from docutils.core import publish_string - kwargs = { - 'stylesheet' : stylesheet, - 'stylesheet_path': None, - 'traceback' : 1, - 'embed_stylesheet': 0, - 'output_encoding' : encoding, - #'halt' : 0, # 'info', - 'halt_level' : 2, - } - # docutils uses os.getcwd() :-( - source_path = os.path.abspath(str(source_path)) - prevdir = os.getcwd() - try: - #os.chdir(os.path.dirname(source_path)) - return publish_string(source, source_path, writer_name='html', - settings_overrides=kwargs) - finally: - os.chdir(prevdir) - -def process(txtpath, encoding='latin1'): - """ process a textfile """ - log("processing %s" % txtpath) - assert txtpath.check(ext='.txt') - if isinstance(txtpath, py.path.svnwc): - txtpath = txtpath.localpath - htmlpath = txtpath.new(ext='.html') - #svninfopath = txtpath.localpath.new(ext='.svninfo') - - style = txtpath.dirpath('style.css') - if style.check(): - stylesheet = style.basename - else: - stylesheet = None - content = unicode(txtpath.read(), encoding) - doc = convert_rest_html(content, txtpath, stylesheet=stylesheet, encoding=encoding) - htmlpath.open('wb').write(doc) - #log("wrote %r" % htmlpath) - #if txtpath.check(svnwc=1, versioned=1): - # info = txtpath.info() - # svninfopath.dump(info) - -if sys.version_info > (3, 0): - def _uni(s): return s -else: - def _uni(s): - return unicode(s) - -rex1 = re.compile(r'.*(.*).*', re.MULTILINE | re.DOTALL) -rex2 = re.compile(r'.*
(.*)
.*', re.MULTILINE | re.DOTALL) - -def strip_html_header(string, encoding='utf8'): - """ return the content of the body-tag """ - uni = unicode(string, encoding) - for rex in rex1,rex2: - match = rex.search(uni) - if not match: - break - uni = match.group(1) - return uni - -class Project: # used for confrest.py files - def __init__(self, sourcepath): - self.sourcepath = sourcepath - def process(self, path): - return process(path) - def get_htmloutputpath(self, path): - return path.new(ext='html')