From arigo at tunes.org Mon Nov 8 19:26:41 2004 From: arigo at tunes.org (Armin Rigo) Date: Mon, 8 Nov 2004 18:26:41 +0000 Subject: [py-dev] py.path import hook for getpymodule() Message-ID: <20041108182641.GA9227@vicky.ecs.soton.ac.uk> Hi all, I tried to implement the idea Holger and me discussed some time ago on IRC: the method getpymodule() of py.path objects, formerly known as getmodule(), should be smarter about importing modules that contain relative imports, i.e. that import other modules from the same directory. This requires a custom import hook, because we might be talking about a py.path which is not a local path. For example, in an Subversion repository, I have two files: x.py: def x1(): return 5 test_x.py: import x def test_x1(): assert x.x1() == 5 The goal is to make this work: mod = py.path.svnurl('svn+ssh://.../test_x.py').getpymodule() mod.test_x1() This is done by putting in the __file__ attribute of the 'test_x' module a subclass of str, as suggested by Holger, which remembers the py.path. A custom import hook detects the "import" statements coming from modules with such a __file__, and tries to resolve the import locally, relative to the py.path attached to the __file__. If it succeeds, it calls getpymodule() again to import the new module. Otherwise, it falls back to the standard import hook. The patch is attached for review (not checked in right now; depends if we want this kind of hack or not). Note that it doesn't work if the modules being imported hack around sys.path themselves. It works, though, if the hack is being performed by py.magic.autopath(): if called from a module with a special __file__, autopath() will not actually hack sys.path but just record the information it deduced on the path object. Then the custom import hook notices this information... Not too clean. It does allow this kind of stuff to work, though: stuff/__init__.py: # empty stuff/x.py: def x1(): return 5 stuff/test_x.py: import py; py.magic.autopath() import stuff.x # or "from stuff import x", which is harder def test_x1(): assert stuff.x.x1() == 5 Finally, note that (I'm not sure but I believe that) this custom import hook would not be needed with the new hooks of Python 2.3, which would allow a similar result in a cleaner way. Of course that would be 2.3-specific. A bient?t, Armin -------------- next part -------------- Index: magic/autopath.py =================================================================== --- magic/autopath.py (revision 7192) +++ magic/autopath.py (working copy) @@ -1,5 +1,6 @@ import os, sys -from py.path import local +from py.path import local +from py.__impl__.path.common import PathStr def autopath(globs=None, basefile='__init__.py'): """ return the (local) path of the "current" file pointed to by globals @@ -19,9 +20,13 @@ raise ValueError, "cannot compute autopath in interactive mode" __file__ = os.path.abspath(sys.argv[0]) - ret = local(__file__) - if ret.ext in ('.pyc', '.pyo'): - ret = ret.new(ext='.py') + custom__file__ = isinstance(__file__, PathStr) + if custom__file__: + ret = __file__.__path__ + else: + ret = local(__file__) + if ret.ext in ('.pyc', '.pyo'): + ret = ret.new(ext='.py') current = pkgdir = ret.dirpath() while 1: if basefile in current: @@ -29,7 +34,7 @@ current = current.dirpath() if pkgdir != current: continue - elif str(current) not in sys.path: + elif not custom__file__ and str(current) not in sys.path: sys.path.insert(0, str(current)) break ret.pkgdir = pkgdir Index: path/common.py =================================================================== --- path/common.py (revision 7193) +++ path/common.py (working copy) @@ -292,8 +292,7 @@ except KeyError: co = self.getpycodeobj() mod = py.std.new.module(modname) - mod.__file__ = str(self) - #mod.__path__ = [str(self.get('dirname'))] + mod.__file__ = PathStr(self) sys.modules[modname] = mod exec co in mod.__dict__ return mod @@ -303,3 +302,64 @@ # XXX str(self) should show up somewhere in the code's filename return py.magic.dyncode.compile2(s) + +class PathStr(str): + def __init__(self, path): + global old_import_hook + self.__path__ = path + if old_import_hook is None: + import __builtin__ + old_import_hook = __builtin__.__import__ + __builtin__.__import__ = custom_import_hook + +def relativeimport(p, name, parent=None): + names = name.split('.') + last_list = [False] * (len(names)-1) + [True] + modules = [] + for name, is_last in zip(names, last_list): + if hasattr(parent, name): + # shortcut if there is already the correct name + # in the parent package + submodule = getattr(parent, name) + else: + if is_last and p.new(basename=name+'.py').check(): + p = p.new(basename=name+'.py') + else: + p = p.new(basename=name).join('__init__.py') + if not p.check(): + return None # not found + submodule = p.getpymodule() + if parent is not None: + setattr(parent, name, submodule) + modules.append(submodule) + parent = submodule + return modules # success + + +old_import_hook = None + +def custom_import_hook(name, glob=None, loc=None, fromlist=None): + __file__ = glob and glob.get('__file__') + if isinstance(__file__, PathStr): + # try to perform a relative import + # for cooperation with py.magic.autopath, first look in the pkgdir + modules = None + if hasattr(__file__.__path__, 'pkgdir'): + modules = relativeimport(__file__.__path__.pkgdir, name) + if not modules: + modules = relativeimport(__file__.__path__, name) + if modules: + if fromlist: + submodule = modules[-1] # innermost submodule + # try to import submodules named in the 'fromlist' if the + # 'submodule' is a package + p = submodule.__file__.__path__ + if p.check(basename='__init__.py'): + for name in fromlist: + relativeimport(p, name, parent=submodule) + # failures are fine + return submodule + else: + return modules[0] # outermost package + # fall-back + return old_import_hook(name, glob, loc, fromlist) From hpk at trillke.net Tue Nov 9 00:17:43 2004 From: hpk at trillke.net (holger krekel) Date: Tue, 9 Nov 2004 00:17:43 +0100 Subject: [py-dev] py.path import hook for getpymodule() In-Reply-To: <20041108182641.GA9227@vicky.ecs.soton.ac.uk> References: <20041108182641.GA9227@vicky.ecs.soton.ac.uk> Message-ID: <20041108231742.GC11467@solar.trillke.net> Hi Armin! first of all, thanks for having the courage to tackle improvents on the import system. It's a complicated thing with a large "no-fun" risk. [Armin Rigo Mon, Nov 08, 2004 at 06:26:41PM +0000] > > ... description of remote imports from e.g. subversion ... > > This is done by putting in the __file__ attribute of the 'test_x' module a > subclass of str, as suggested by Holger, which remembers the py.path. A > custom import hook detects the "import" statements coming from modules with > such a __file__, and tries to resolve the import locally, relative to the > py.path attached to the __file__. If it succeeds, it calls getpymodule() > again to import the new module. Otherwise, it falls back to the standard > import hook. My worry is that "mixing" the standard and the custom import hook may lead to duplicate imports. The underlying reason seems is that the standard hook uses "dotted module paths" as keys for the sys.modules cache whereas we use py.path's as keys. This may result in importing a certain file twice and hilarity ensues. So how could we avoid double imports? I am not quite sure. In the end, and IIRC you suggested something like this, we might be able to reimplement enough of Python's import logic which we need to do anyway for PyPy (and have done to a certain degree already). At the extreme end we would not invoke the original import hook at all. But this may be too disruptive as we would break compatibility with other custom import hooks. In the alternative, we may want to build a rather complete mapping of __file__->modules for all successfully imported modules, no matter if they were imported by our or by the standard or even some other import hook. We intercept all import statements anyway and could add successfully imported modules to the mapping. Of course __import__('a.b.c', ...) could implicitely import a chain of modules but after the standard import hook succeeds our custom one could look at 'a', 'a.b', 'a.b.c' and put their __file__s into the __file__->module mapping so that we don't import the thing again when called with its direct filesystem location. Like i said, i am not completly sure about all this but i think a general __file__->module mapping might work well. At least it does from a theoretical viewpoint. Btw, note how "inspect.getmodule(obj)" resorts to building such a mapping, too. And it doesn't even consider that there may be multiple modules pointing to the same file! > The patch is attached for review (not checked in right now; depends if we want > this kind of hack or not). Note that it doesn't work if the modules being > imported hack around sys.path themselves. It works, though, if the hack is > being performed by py.magic.autopath(): Yes, and manipulating sys.path directly often leads to incompatibility with e.g. zip-imports, anyway. So apart from sys.argv[0] - hacks i only see interesting use cases when you want to insert paths relative to the package directory or your current location. For this, we might want to allow something like extrapath = __file__.pkgdir.join('..', '..', 'something') __file__.searchpaths.append(extrapath) Btw, i think it's fine to just put our extra attributes like 'pkgdir' and 'searchpaths' directly on the string sub-instance instead of using magic __names__. Overall the patch looks good and it's nicely small but of course a couple of tests are missing :-) I'd love to have the double-import problem addressed somehow, though. > if called from a module with a special > __file__, autopath() will not actually hack sys.path but just record the > information it deduced on the path object. Then the custom import hook > notices this information... Not too clean. It does allow this kind of stuff > to work, though: > > stuff/__init__.py: > # empty > > stuff/x.py: > def x1(): > return 5 > > stuff/test_x.py: > import py; py.magic.autopath() > import stuff.x # or "from stuff import x", which is harder > def test_x1(): > assert stuff.x.x1() == 5 This makes sense to me. It is indeed not completly clean but i don't see how you easily can get much cleaner than that. > Finally, note that (I'm not sure but I believe that) this custom import hook > would not be needed with the new hooks of Python 2.3, which would allow a > similar result in a cleaner way. Of course that would be 2.3-specific. And i only believe it when i see it :-) ciao, holger From arigo at tunes.org Tue Nov 9 10:33:42 2004 From: arigo at tunes.org (Armin Rigo) Date: Tue, 9 Nov 2004 09:33:42 +0000 Subject: [py-dev] py.path import hook for getpymodule() In-Reply-To: <20041108231742.GC11467@solar.trillke.net> References: <20041108182641.GA9227@vicky.ecs.soton.ac.uk> <20041108231742.GC11467@solar.trillke.net> Message-ID: <20041109093341.GA7088@vicky.ecs.soton.ac.uk> Hi Holger, On Tue, Nov 09, 2004 at 12:17:43AM +0100, holger krekel wrote: > So how could we avoid double imports? (...) > > Like i said, i am not completly sure about all this but i think > a general __file__->module mapping might work well. At least it > does from a theoretical viewpoint. I'm afraid it works in one direction only: it would prevent getpymodule() from re-importing a module already imported by CPython. We'll probably need some additional magic to prevent CPython from importing a module already seen by getpymodule(). It looks quite difficult, given that Python only cares about dotted names, and not whether, say, two dotted names (like 'x' and 'package.x') refer to the same file relative to two different entries of sys.path. For this reason I was thinking that it might be enough to document getpymodule() as completely separate from Python's sys.modules (and not use sys.modules as a cache but a custom dict). The user would have to be aware of that. For example, for py.test, it's fine (and probably better, actually): all the test mecanisms and standard library modules are "on the sys.modules side", and all the tests themselves are "on the getpymodule() side". It might be enough to add a few sanity checks and warn if the same __file__ appears to be imported on both "sides". Does it look reasonable? Armin From hpk at trillke.net Tue Nov 9 12:14:20 2004 From: hpk at trillke.net (holger krekel) Date: Tue, 9 Nov 2004 12:14:20 +0100 Subject: [py-dev] py.path import hook for getpymodule() In-Reply-To: <20041109093341.GA7088@vicky.ecs.soton.ac.uk> References: <20041108182641.GA9227@vicky.ecs.soton.ac.uk> <20041108231742.GC11467@solar.trillke.net> <20041109093341.GA7088@vicky.ecs.soton.ac.uk> Message-ID: <20041109111420.GF11467@solar.trillke.net> Hi Armin, [Armin Rigo Tue, Nov 09, 2004 at 09:33:42AM +0000] > On Tue, Nov 09, 2004 at 12:17:43AM +0100, holger krekel wrote: > > So how could we avoid double imports? (...) > > > > Like i said, i am not completly sure about all this but i think > > a general __file__->module mapping might work well. At least it > > does from a theoretical viewpoint. > > I'm afraid it works in one direction only: it would prevent getpymodule() from > re-importing a module already imported by CPython. Yes. > We'll probably need some additional magic to prevent CPython > from importing a module already seen by getpymodule(). It > looks quite difficult, given that Python only cares about > dotted names, and not whether, say, two dotted names (like > 'x' and 'package.x') refer to the same file relative to two > different entries of sys.path. yes, unfortunately. > For this reason I was thinking that it might be enough to document > getpymodule() as completely separate from Python's sys.modules (and not use > sys.modules as a cache but a custom dict). The user would have to be aware of > that. For example, for py.test, it's fine (and probably better, actually): > all the test mecanisms and standard library modules are "on the sys.modules > side", and all the tests themselves are "on the getpymodule() side". Hum. But the py lib implementation files could be imported over getpymodule(), right? Right now, py/initpkg.py performs this task for bootstrapping reasons but it could at least cooperate with getpymodule(). IOW, I'd actually like to apply getpymodule() semantics to a whole package, e.g. the py lib itself. For such cases we might even refuse to defer to the standard import mechanism. i.e. when one does mypkg/__init__.py from py.initpkg import initpkg initpkg.initpkg(__name__, {...}) then we would eventually refuse to fall back to the cpython standard import hook for any import below 'mypkg.*'. Also, we could sanity-check that there is no CPython-imported module below the according dirpath of the init's __file__. This way we can make sure that a package using the initpkg() mechanism avoids duplicate imports alltogether. Of course, if you don't apply initpkg() then your above view of separation between tests and the rest of your package should still be true. > It might be enough to add a few sanity checks and warn if the same __file__ > appears to be imported on both "sides". Btw, even if such checks would be somewhat expensive (like iterating through the whole of sys.modules) we might want to enable them for the next months to detect problems early. > Does it look reasonable? Yes, although the above unification of initpkg() and getpymodule() remains for the future if you can agree to that. I do think that it can give us the best of both worlds in a separatable way. We always wanted to have py.path based imports and this gives us the possibility to care about that without interfering with the rest of the imports. Does that make sense to you? holger From arigo at tunes.org Tue Nov 9 14:47:29 2004 From: arigo at tunes.org (Armin Rigo) Date: Tue, 9 Nov 2004 13:47:29 +0000 Subject: [py-dev] py.path import hook for getpymodule() In-Reply-To: <20041109111420.GF11467@solar.trillke.net> References: <20041108182641.GA9227@vicky.ecs.soton.ac.uk> <20041108231742.GC11467@solar.trillke.net> <20041109093341.GA7088@vicky.ecs.soton.ac.uk> <20041109111420.GF11467@solar.trillke.net> Message-ID: <20041109134729.GA4272@vicky.ecs.soton.ac.uk> Hi Holger, On Tue, Nov 09, 2004 at 12:14:20PM +0100, holger krekel wrote: > IOW, I'd actually like to apply getpymodule() semantics to a whole > package, e.g. the py lib itself. For such cases we might even > refuse to defer to the standard import mechanism. i.e. when one does > > mypkg/__init__.py > from py.initpkg import initpkg > initpkg.initpkg(__name__, {...}) > > then we would eventually refuse to fall back to the cpython standard import hook > for any import below 'mypkg.*'. Sounds pretty much reasonable. > Of course, if you don't apply initpkg() then your above view of separation > between tests and the rest of your package should still be true. Actually, in that view, the test files would contain import statements to import the package they test; such import statements would then be getpymodule()ed as well, so that the current result with py.test is that the py lib and stdlib are loaded using CPython's import and all the tested user code is loaded with getpymodule(), which is nice too. Allowing the py lib itself to be loaded with getpymodule() too is a separate issue, though it would be nice as well. Armin From hpk at trillke.net Tue Nov 9 15:19:00 2004 From: hpk at trillke.net (holger krekel) Date: Tue, 9 Nov 2004 15:19:00 +0100 Subject: [py-dev] py.path import hook for getpymodule() In-Reply-To: <20041109134729.GA4272@vicky.ecs.soton.ac.uk> References: <20041108182641.GA9227@vicky.ecs.soton.ac.uk> <20041108231742.GC11467@solar.trillke.net> <20041109093341.GA7088@vicky.ecs.soton.ac.uk> <20041109111420.GF11467@solar.trillke.net> <20041109134729.GA4272@vicky.ecs.soton.ac.uk> Message-ID: <20041109141900.GJ11467@solar.trillke.net> Hi Armin, [Armin Rigo Tue, Nov 09, 2004 at 01:47:29PM +0000] > On Tue, Nov 09, 2004 at 12:14:20PM +0100, holger krekel wrote: > > Of course, if you don't apply initpkg() then your above view of separation > > between tests and the rest of your package should still be true. > > Actually, in that view, the test files would contain import statements to > import the package they test; such import statements would then be > getpymodule()ed as well, ... Hehe, that's a funny but maybe also a dangerous or at least fragile side effect. I realize that you are already trying to handle absolute imports if they relate to the pkgdir. Btw, i think your patch needs to look into pkgdir.dirpath() because pkgdir itself really is the directory of the package and not the directory containing the package. But you would have catched that with an appropriate test :-) holger From arigo at tunes.org Tue Nov 9 15:33:38 2004 From: arigo at tunes.org (Armin Rigo) Date: Tue, 9 Nov 2004 14:33:38 +0000 Subject: [py-dev] py.path import hook for getpymodule() In-Reply-To: <20041109141900.GJ11467@solar.trillke.net> References: <20041108182641.GA9227@vicky.ecs.soton.ac.uk> <20041108231742.GC11467@solar.trillke.net> <20041109093341.GA7088@vicky.ecs.soton.ac.uk> <20041109111420.GF11467@solar.trillke.net> <20041109134729.GA4272@vicky.ecs.soton.ac.uk> <20041109141900.GJ11467@solar.trillke.net> Message-ID: <20041109143338.GA8284@vicky.ecs.soton.ac.uk> Hi Holger, On Tue, Nov 09, 2004 at 03:19:00PM +0100, holger krekel wrote: > think your patch needs to look into pkgdir.dirpath() because > pkgdir itself really is the directory of the package and not > the directory containing the package. But you would have > catched that with an appropriate test :-) No, it's correct as it is. I did a test manually because I don't have svnadmin installed on my machine so the corresponding tests are skipped anyway -- but it works. It does, because relativeimport() expects the path to point to a file *inside* the directory -- usually it is __init__.py. Should be documented I guess :-) Armin From hpk at trillke.net Wed Nov 10 16:56:20 2004 From: hpk at trillke.net (holger krekel) Date: Wed, 10 Nov 2004 16:56:20 +0100 Subject: [py-dev] py.path import hook for getpymodule() In-Reply-To: <20041109111420.GF11467@solar.trillke.net> References: <20041108182641.GA9227@vicky.ecs.soton.ac.uk> <20041108231742.GC11467@solar.trillke.net> <20041109093341.GA7088@vicky.ecs.soton.ac.uk> <20041109111420.GF11467@solar.trillke.net> Message-ID: <20041110155620.GW12400@solar.trillke.net> Hi Armin, [To py-dev at codespeak.net Tue, Nov 09, 2004 at 12:14:20PM +0100] > [Armin Rigo Tue, Nov 09, 2004 at 09:33:42AM +0000] > > On Tue, Nov 09, 2004 at 12:17:43AM +0100, holger krekel wrote: > Hum. But the py lib implementation files could be imported > over getpymodule(), right? Right now, py/initpkg.py performs > this task for bootstrapping reasons but it could at least > cooperate with getpymodule(). Hum, it seems after your recent commits they actually have to cooperate. For me 'py.test' doesn't currently work because your new custom import tries to import 'py/__init__.py' but this relies on a __path__ for its export definition to work. Which is something that we don't provide. If initpkg.py would basically use the same mechanism than getpymodule() then this wouldn't be a problem. cheers, holger From ianb at colorstudy.com Wed Nov 17 00:25:53 2004 From: ianb at colorstudy.com (Ian Bicking) Date: Tue, 16 Nov 2004 17:25:53 -0600 Subject: [py-dev] Import problems under py.test Message-ID: <419A8C81.6030207@colorstudy.com> I'm having a problem with imports when using py.test. The file db.py gets imported twice. It actually comes from two different file locations, but it's the same file, and there should only be one way to get to it. In this case, importing the module twice immediately throws an exception. /home/ianb/repository/Landscaper/external is in sys.path, but /home is a symlink to /usr/home. This is the class that is initially imported: This is the normal class, which is created second: I guess the problem is that my test script imports db (relatively), then imports a module that imports db (also relatively). I can probably turn this into a simpler test case, but I thought I'd see if the problem was obvious. -- Ian Bicking / ianb at colorstudy.com / http://blog.ianbicking.org From hpk at trillke.net Wed Nov 17 07:45:04 2004 From: hpk at trillke.net (holger krekel) Date: Wed, 17 Nov 2004 07:45:04 +0100 Subject: [py-dev] Import problems under py.test In-Reply-To: <419A8C81.6030207@colorstudy.com> References: <419A8C81.6030207@colorstudy.com> Message-ID: <20041117064504.GI12400@solar.trillke.net> Hi Ian, [Ian Bicking Tue, Nov 16, 2004 at 05:25:53PM -0600] > I'm having a problem with imports when using py.test. The file db.py > gets imported twice. It actually comes from two different file > locations, but it's the same file, and there should only be one way to > get to it. In this case, importing the module twice immediately throws > an exception. Actually, i noticed similar problems i think. > /home/ianb/repository/Landscaper/external is in sys.path, but /home is a > symlink to /usr/home. This is the class that is initially imported: > > '/usr/home/ianb/repository/Landscaper/external/lib/db/py.SiteSQLObject'> OK, the according import of this class started in a test. Tests are imported using .getpymodule() and this method maintains a separate cache. It also supports relative imports. Therefore you see the above "path-based" import. (Path based imports also work remotely etc.pp.) > This is the normal class, which is created second: > > > > I guess the problem is that my test script imports db (relatively), then > imports a module that imports db (also relatively). Yes, IOW this is a mix of path-based and normal imports. > I can probably turn this into a simpler test case, but I thought I'd see > if the problem was obvious. Well, i guess we need to avoid such suprising situations some way or another. cheers, holger From ianb at colorstudy.com Wed Nov 17 19:06:57 2004 From: ianb at colorstudy.com (Ian Bicking) Date: Wed, 17 Nov 2004 12:06:57 -0600 Subject: [py-dev] Import problems under py.test In-Reply-To: <20041117064504.GI12400@solar.trillke.net> References: <419A8C81.6030207@colorstudy.com> <20041117064504.GI12400@solar.trillke.net> Message-ID: <419B9341.9060904@colorstudy.com> I added a test to py.test (py/test/test/import_test) for this import problem. Well, maybe the test belongs in py.path; anyway, it's there. -- Ian Bicking / ianb at colorstudy.com / http://blog.ianbicking.org From hpk at trillke.net Wed Nov 17 19:25:47 2004 From: hpk at trillke.net (holger krekel) Date: Wed, 17 Nov 2004 19:25:47 +0100 Subject: [py-dev] Import problems under py.test In-Reply-To: <419B9341.9060904@colorstudy.com> References: <419A8C81.6030207@colorstudy.com> <20041117064504.GI12400@solar.trillke.net> <419B9341.9060904@colorstudy.com> Message-ID: <20041117182547.GQ12400@solar.trillke.net> Hi Ian, [Ian Bicking Wed, Nov 17, 2004 at 12:06:57PM -0600] > I added a test to py.test (py/test/test/import_test) for this import > problem. Well, maybe the test belongs in py.path; anyway, it's there. yes, i saw it, thanks. Note that (i think) the problem would also occur if you did the traditional "python test_myfile.py" which used unittest.py. If you import relative from this file and then import (absolutely) from the package then you would also get a different instance of the module. However, i discussed the issue shortly with Armin this morning and we may try to "guess" a more normal module path than a mangled str(Path object). I.e. when we do: py.test test_import.py we would traverse upwards from test_import.py as long as there is an __init__.py. The basename()s it so gathers would comprise the absolute module path (which is used as a key for sys.modules). However, i am not completly sure about all this yet (my brain is exploding from the current pypy sprint in Vilnius :-). cheers, holger From ianb at colorstudy.com Wed Nov 17 19:37:27 2004 From: ianb at colorstudy.com (Ian Bicking) Date: Wed, 17 Nov 2004 12:37:27 -0600 Subject: [py-dev] Import problems under py.test In-Reply-To: <20041117182547.GQ12400@solar.trillke.net> References: <419A8C81.6030207@colorstudy.com> <20041117064504.GI12400@solar.trillke.net> <419B9341.9060904@colorstudy.com> <20041117182547.GQ12400@solar.trillke.net> Message-ID: <419B9A67.3040704@colorstudy.com> holger krekel wrote: > [Ian Bicking Wed, Nov 17, 2004 at 12:06:57PM -0600] > >>I added a test to py.test (py/test/test/import_test) for this import >>problem. Well, maybe the test belongs in py.path; anyway, it's there. > > > yes, i saw it, thanks. Note that (i think) the problem would also occur > if you did the traditional "python test_myfile.py" which used unittest.py. > If you import relative from this file and then import (absolutely) from > the package then you would also get a different instance of the module. I just extended the test so you can run it as a script, and it works fine. Since I mix relative and absolute imports frequently in my code (for better or worse), this is what I would expect. You will get a problem with normal inputs when you are in a situation like: /a/b/c.py sys.path includes /a and /a/b You "import c" somewhere, and "import b.c" somewhere else; this creates two modules that come from the same file. But relative imports alone should not create another module. Of course, I'm not sure if the relative import in this case will match the c module or the b.c... this is one of those places where "refuse to guess" would have been a better idea that the current behavior. > However, i discussed the issue shortly with Armin this morning and > we may try to "guess" a more normal module path than a mangled str(Path object). > I.e. when we do: > > py.test test_import.py > > we would traverse upwards from test_import.py as long as there is an > __init__.py. The basename()s it so gathers would comprise the absolute > module path (which is used as a key for sys.modules). > > However, i am not completly sure about all this yet (my brain > is exploding from the current pypy sprint in Vilnius :-). Importing is a pain in the ass, but that sounds like it should work. Alternately, could you try the normal import mechanism, see if it finds the same filename you are looking for, and if so procede with that? I think imp has the functions to find a module without actually loading it. Hrm... but no, you don't actually have a name to try. Or, you could just look for common prefixes in sys.path, and if you find one traverse up making sure __init__.py's exist; most of the time they will. This seems more optimistic. Of course, I don't know if that will fix this test, because I modify sys.path after the module is loaded, so it would be loaded as an absolute path, and relative imports might still be funky. Which makes me wonder how Python normally accomplishes this at all. -- Ian Bicking / ianb at colorstudy.com / http://blog.ianbicking.org From ianb at colorstudy.com Sun Nov 21 09:26:02 2004 From: ianb at colorstudy.com (Ian Bicking) Date: Sun, 21 Nov 2004 02:26:02 -0600 Subject: [py-dev] py.test: customizing collector Message-ID: <41A0511A.9000506@colorstudy.com> I got a custom collector working okay; this checks for a function attribute "params", which is a list of tuples, each of which is an argument. I did it like this: from py.test.collect import PyCollector class ParamCollector(PyCollector): def collect_function(self, extpy): if not extpy.check(func=1, basestarts='test_'): return func = extpy.resolve() if hasattr(func, 'params'): params = func.params for param in params: yield self.Item(extpy, *param) else: yield self.Item(extpy) The problem is that all the tests have the same name, at least as displayed. I notice that item.extpy is used as the displayed name for the test; at least some other function should be used (py.test.report.text.summary:192) so that it's easier to override. But I guess it should also fit into --session, and I don't know what that uses for its test ID...? It might be nice if this parameterization was built into the standard collector in some way, as it's a very common case. I'm not sure if it's best as an attribute, a similarly-named object (e.g., test_func and test_func_params), or if there's other ideas. -- Ian Bicking / ianb at colorstudy.com / http://blog.ianbicking.org From ianb at colorstudy.com Sun Nov 21 20:55:04 2004 From: ianb at colorstudy.com (Ian Bicking) Date: Sun, 21 Nov 2004 13:55:04 -0600 Subject: [py-dev] py.test: customizing collector In-Reply-To: <41A0511A.9000506@colorstudy.com> References: <41A0511A.9000506@colorstudy.com> Message-ID: <41A0F298.70604@colorstudy.com> Ian Bicking wrote: > It might be nice if this parameterization was built into the standard > collector in some way, as it's a very common case. I'm not sure if it's > best as an attribute, a similarly-named object (e.g., test_func and > test_func_params), or if there's other ideas. Another thought occurred to me here -- one way to do this would be like, say: def add_params(params): """ A decorator, e.g.:: @add_params([(1, 'i'), (2, 'ii'),]) def test_roman(input, output): assert toRoman(input) == output """ def decorator(func): return ParamItemizer(func, params) return decorator class ParamItemizer(Collector): def __init__(self, func, params): self.func = func self.params = params def __iter__(self): for param in params: yield Item(self.func, *param) Of course, it's not nearly that simple because of the extpy stuff when collecting and making items -- Item doesn't take a function, but takes an extpy object, which is introduces a whole new concept into the system. Nor do the collectors seem to look for collectors inside the module, except for a collector that completely overrides the normal collector. -- Ian Bicking / ianb at colorstudy.com / http://blog.ianbicking.org From ianb at colorstudy.com Sun Nov 21 21:02:29 2004 From: ianb at colorstudy.com (Ian Bicking) Date: Sun, 21 Nov 2004 14:02:29 -0600 Subject: [py-dev] py.test: customizing collector In-Reply-To: <41A0F298.70604@colorstudy.com> References: <41A0511A.9000506@colorstudy.com> <41A0F298.70604@colorstudy.com> Message-ID: <41A0F455.3060602@colorstudy.com> Ian Bicking wrote: > def add_params(params): > """ > A decorator, e.g.:: > > @add_params([(1, 'i'), (2, 'ii'),]) > def test_roman(input, output): > assert toRoman(input) == output > """ > def decorator(func): > return ParamItemizer(func, params) > return decorator Extending this idea, other similar decorators could read the parameters from other data sources, like a spreadsheet or HTML document, similar to Fitnesse (http://www.fitnesse.org/), e.g., @add_html_params('test_document.html', id='test_table1') -- Ian Bicking / ianb at colorstudy.com / http://blog.ianbicking.org From hpk at trillke.net Sun Nov 21 23:22:15 2004 From: hpk at trillke.net (holger krekel) Date: Sun, 21 Nov 2004 23:22:15 +0100 Subject: [py-dev] py.test: customizing collector In-Reply-To: <41A0511A.9000506@colorstudy.com> References: <41A0511A.9000506@colorstudy.com> Message-ID: <20041121222215.GY12400@solar.trillke.net> Hi Ian, [Ian Bicking Sun, Nov 21, 2004 at 02:26:02AM -0600] > I got a custom collector working okay; this checks for a function > attribute "params", which is a list of tuples, each of which is an > argument. I did it like this: > > from py.test.collect import PyCollector > > class ParamCollector(PyCollector): > > def collect_function(self, extpy): > if not extpy.check(func=1, basestarts='test_'): > return > func = extpy.resolve() > if hasattr(func, 'params'): > params = func.params > for param in params: > yield self.Item(extpy, *param) > else: > yield self.Item(extpy) > > The problem is that all the tests have the same name, at least as > displayed. Yes, giving a good name to parametrized tests is a problem. I suppose some more thinking is needed here. I am still at a PyPy sprint and sorting out some business stuff at the same time. So i can't immediately dive deeper here. Note that i have code sitting in a branch that takes a different angle at customizing the collecting process, inspired by your earlier comment that "extpy" introduces a new concept where one just wants to have parametrized tests or have some custom logic to get at or produce test functions. I agree that this should possibly integrate more naturally. Btw, in the branch you can do a simple yield function, arg1, arg2, ... in a test function. However, this doesn't solve the naming (and addressing) problem. more later, holger From ianb at colorstudy.com Sun Nov 21 23:51:44 2004 From: ianb at colorstudy.com (Ian Bicking) Date: Sun, 21 Nov 2004 16:51:44 -0600 Subject: [py-dev] py.test: customizing collector In-Reply-To: <20041121222215.GY12400@solar.trillke.net> References: <41A0511A.9000506@colorstudy.com> <20041121222215.GY12400@solar.trillke.net> Message-ID: <41A11C00.9000404@colorstudy.com> holger krekel wrote: > Note that i have code sitting in a branch that takes a different angle > at customizing the collecting process, inspired by your earlier > comment that "extpy" introduces a new concept where one just > wants to have parametrized tests or have some custom logic to > get at or produce test functions. I agree that this > should possibly integrate more naturally. > Btw, in the branch you can do a simple > > yield function, arg1, arg2, ... > > in a test function. However, this doesn't solve the naming > (and addressing) problem. In DataTest I used the first argument as the ID. There were a couple simple transformations -- get rid of non-alphanumeric characters, truncate the length, then make sure it is unique by adding a number if necessary. When I wanted specific names in my tests, I'd just use the first argument as the name then ignore it in the function. It seems a little hackish, but in practice I don't really see many problems; it's fairly predictable and simple. -- Ian Bicking / ianb at colorstudy.com / http://blog.ianbicking.org From ianb at colorstudy.com Mon Nov 22 03:45:03 2004 From: ianb at colorstudy.com (Ian Bicking) Date: Sun, 21 Nov 2004 20:45:03 -0600 Subject: [py-dev] py.test: doctests Message-ID: <41A152AF.1070006@colorstudy.com> I played around with doctests today, here's the collector I have so far (requires the 2.4 version of doctest): class DoctestCollector(PyCollector): def __iter__(self): finder = doctest.DocTestFinder() tests = finder.find(self.extpy.getpymodule()) for t in tests: yield DoctestItem(self.extpy, t) class DoctestItem(DoctestCollector.Item): def __init__(self, extpy, doctestitem, *args): self.extpy = extpy self.doctestitem = doctestitem self.name = extpy.basename self.args = args def execute(self, driver): runner = doctest.DocTestRunner() driver.setup_path(self.extpy) target, teardown = driver.setup_method(self.extpy) try: (tried, failed), output = capture_stdout( runner.run, self.doctestitem) if failed: raise self.Failed(msg=output, tbindex=-2) finally: if teardown: teardown(target) def capture_stdout(func, *args, **kw): newstdout = StringIO() oldstdout = sys.stdout sys.stdout = newstdout try: result = func(*args, **kw) finally: sys.stdout = oldstdout return result, newstdout.getvalue() Some problems: * Still the problem where overriding one collector (in this case, using DoctestCollector) cancels the normal collection, instead of adding to it. I guess it's possible that we could do collect_doctests, then use the finder on each object (since the finder can work on individual functions and methods, not just module objects). * Still the naming problem. DocTest instances (doctestitem) have a name attribute, I suppose that should be involved. * The exception reports in py.test aren't really meaningful. tbindex=-2 got rid of some of them, but I wasn't sure how to get rid of all of them. * Generally the results of running the doctest should be folded into the py.test output better. I find item.Item.Outcome a little confusing, as there's some possible attributes, but they aren't documented in that class (due to self.__dict__.update(kw)). I'd rather those attributes be class attributes, to indicate defaults and provide a place for documentation. Though generally I don't understand Outcome. Also, it seems to be raised, but it's not a subclass of Exception? Anyway, it's probably not too hard, if I understood the reporter better; maybe subclassing DocTestRunner. -- Ian Bicking / ianb at colorstudy.com / http://blog.ianbicking.org From dripton at ripton.net Fri Nov 26 13:26:54 2004 From: dripton at ripton.net (David Ripton) Date: Fri, 26 Nov 2004 04:26:54 -0800 Subject: [py-dev] test.py bug report -- string exceptions Message-ID: <20041126122654.GA4006@vidar.dreamhost.com> [This is a resend. Apologies if the original gets unstuck and you see this twice.] I just started playing with test.py, and so far it looks very nice. But I found one little bug so far. (Is there a bug tracker?) Using test.py to test a method that raises a string exception (deprecated, I know) results in a fatal exception that ends the test. Reproduction recipe: ---blah.py-------------- def blah(): raise "TODO" def test_blah(): blah() def test_blah_more(): not_reached() ------------------------ Then $ py.test blah.py The exception is: File "/home/dripton/dist-py/py/test/report/text/summary.py", line 111, in repr_failure_result if issubclass(cls, Item.ExceptionFailure): TypeError: issubclass() arg 1 must be a class -- David Ripton dripton at ripton.net From hpk at trillke.net Fri Nov 26 15:50:43 2004 From: hpk at trillke.net (holger krekel) Date: Fri, 26 Nov 2004 15:50:43 +0100 Subject: [py-dev] test.py bug report -- string exceptions In-Reply-To: <20041126122654.GA4006@vidar.dreamhost.com> References: <20041126122654.GA4006@vidar.dreamhost.com> Message-ID: <20041126145043.GD4550@solar.trillke.net> Hi David, [David Ripton Fri, Nov 26, 2004 at 04:26:54AM -0800] > I just started playing with test.py, and so far it looks very nice. > But I found one little bug so far. > > (Is there a bug tracker?) Not yet. There isn't even a release yet :-) Actually when we have the new codespeak machine + setup i hope to use trac for that. > Using test.py to test a method that raises a string exception > (deprecated, I know) results in a fatal exception that ends > the test. > ... Thanks for the example and bug report. It should be fixed now. cheers, holger From faassen at infrae.com Fri Nov 26 21:38:27 2004 From: faassen at infrae.com (Martijn Faassen) Date: Fri, 26 Nov 2004 21:38:27 +0100 Subject: [py-dev] failing unit tests Message-ID: <41A79443.5090006@infrae.com> Hi there, I get a number of failing tests when I check out Py and run py.test on it. If I read the output right, the failing tests are: /home/faassen/working/dist-py2/py/misc/test_initpkg.test_importing_all_implementations, line 21 /home/faassen/working/dist-py2/py/test/test/import_test/package/test_import.test_import, line 18 Note that the test.py output is rather intimidating, especially if you do not know the codebase you're testing. This is a realistic scenario for any newbie to the codebase like myself in this case, or possibly for deployment scenarios where someone is asked to run the test suite just to answer the question "which tests are failing?". Perhaps a 'non-verbose' mode would be nice, or a 'sysadmin mode' where only a bit of output is seen directly by the sysadmin, and the rest is dumped into a file that can be emailed to the developers. Anyay, I also seem to have a lot of recorded stdout like this: - - - - - - - - - - - - - - - recorded stdout - - - - - - - - - - - - - - - These modules should be identical: shared_lib: shared_lib2: This is all rather mysterious to me as well. Regards, Martijn From hpk at trillke.net Fri Nov 26 22:10:42 2004 From: hpk at trillke.net (holger krekel) Date: Fri, 26 Nov 2004 22:10:42 +0100 Subject: [py-dev] failing unit tests In-Reply-To: <41A79443.5090006@infrae.com> References: <41A79443.5090006@infrae.com> Message-ID: <20041126211042.GO4550@solar.trillke.net> Hi Martijn! [Martijn Faassen Fri, Nov 26, 2004 at 09:38:27PM +0100] > Hi there, > > I get a number of failing tests when I check out Py and run py.test on > it. If I read the output right, the failing tests are: > > /home/faassen/working/dist-py2/py/misc/test_initpkg.test_importing_all_implementations, > line 21 > > /home/faassen/working/dist-py2/py/test/test/import_test/package/test_import.test_import, > line 18 Yes, Ian Bicking checked these tests in to point to a certain import problem. I just renamed it to "bugtest_import.py" so it doesn't get automatically collected any more. Of course, you can still run the test by directly specifying the file with py.test FILENAME. > Note that the test.py output is rather intimidating, especially if you > do not know the codebase you're testing. What exactly do you find intimidating? Note that there is a "failure" demo file in src/example/test/failure_demo.py which if run by py.test gives failure output examples for some 40 tests. > This is a realistic scenario for any newbie to the codebase > like myself in this case, or possibly for deployment > scenarios where someone is asked to run the test suite just > to answer the question "which tests are failing?". Yeah, well, probably at least partly because it relates to imports :-) More seriously, though, i tried to produce good output to show what test is failing in which line (including an according call trace). It shows function context so that you don't neccessarily need to read the test file in your editor to understand the failure. > Perhaps a 'non-verbose' mode would be nice, or a 'sysadmin mode' where > only a bit of output is seen directly by the sysadmin, and the rest is > dumped into a file that can be emailed to the developers. What would you think about a dense summary at the end with one line for each failing tests aka "filename:lineno XXXError" or so? > Anyay, I also seem to have a lot of recorded stdout like this: Yip, the test produces this via print statements. For successful tests the output is not shown, though. Do you find this questionable? I have used it with nice results because you only get to see isolated print output for the failing tests which allows to use print statements even in central places and still you get meaningful test-related stdoutput. (Otherwise you get swamped by all the print output and don't know which output exactly related to the failure you want to debug). cheers, holger From faassen at infrae.com Fri Nov 26 22:32:33 2004 From: faassen at infrae.com (Martijn Faassen) Date: Fri, 26 Nov 2004 22:32:33 +0100 Subject: [py-dev] failing unit tests In-Reply-To: <20041126211042.GO4550@solar.trillke.net> References: <41A79443.5090006@infrae.com> <20041126211042.GO4550@solar.trillke.net> Message-ID: <41A7A0F1.6030202@infrae.com> Hey Holger, holger krekel wrote: > [Martijn Faassen Fri, Nov 26, 2004 at 09:38:27PM +0100] > >> Hi there, >> >> I get a number of failing tests when I check out Py and run py.test >> on it. If I read the output right, the failing tests are: [snip] > Yes, Ian Bicking checked these tests in to point to a certain import > problem. Ah, I do recall seeing some discussion on the list, but I hadn't paid a lot of attention. > I just renamed it to "bugtest_import.py" so it doesn't get > automatically collected any more. Great, I just saw that when I did an "svn up". > Of course, you can still run the test by directly specifying the file > with py.test FILENAME. Right. This is now a bit more clear in the documentation. :) >> Note that the test.py output is rather intimidating, especially if >> you do not know the codebase you're testing. > > What exactly do you find intimidating? There was a lot of it in case of py, showing unfamiliar code, and then there was the collected output. Just the quantity of unfamiliar complicated looking things is intimidating. :) This is of course usually fine and desirable if you're debugging a familiar codebase, but below I tried to describe usage scenarios where you want to avoid such intimidation. > Note that there is a "failure" demo file in > src/example/test/failure_demo.py which if run by py.test gives > failure output examples for some 40 tests. Right, I went through the documentation and ran into that failure demo. Actually the failure demo output with some extra text would make for good documentation by itself, I suspect. >> This is a realistic scenario for any newbie to the codebase like >> myself in this case, or possibly for deployment scenarios where >> someone is asked to run the test suite just to answer the question >> "which tests are failing?". > > Yeah, well, probably at least partly because it relates to imports > :-) More seriously, though, i tried to produce good output to show > what test is failing in which line (including an according call > trace). It shows function context so that you don't neccessarily need > to read the test file in your editor to understand the failure. Yes, and for a developer this is fine. For a developer less familiar with the codebase, or someone who is running the tests on other purposes, a more minimalistic output option would be nice on occasion. Of course part of the problem is that I modified some documentation text and just to make sure wanted to run the test suite before checking it in, even though to my knowledge py.test doesn't do doctets. I was presented with rather scary detailed test failures, something I did not expect at that point. I was expecting "everything is okay". :) >> Perhaps a 'non-verbose' mode would be nice, or a 'sysadmin mode' >> where only a bit of output is seen directly by the sysadmin, and >> the rest is dumped into a file that can be emailed to the >> developers. > > What would you think about a dense summary at the end with one line > for each failing tests aka "filename:lineno XXXError" or so? I think that would be useful. And then an option to suppress any output but the summary and the nice dots before it. :) >> Anyay, I also seem to have a lot of recorded stdout like this: > > Yip, the test produces this via print statements. For successful > tests the output is not shown, though. Do you find this questionable? > I have used it with nice results because you only get to see > isolated print output for the failing tests which allows to use print > statements even in central places and still you get meaningful > test-related stdoutput. > (Otherwise you get swamped by all the print output and don't know > which output exactly related to the failure you want to debug). I do not find this a questionable idea at all. I thought it was an intriguing idea when I read it in the documentation. I've edited the documentation somewhat to make the idea more clear to beginners. One thing I realized after editing the documentation is that I'm free to use 'print' in whatever test code I like now, and even check it in, without causing this to outputted except when I really need it -- in case of test failure. Of course this still doesn't leave me free to do the same with application code. :) Is that correct? If so, I shall edit the documentation accordingly. I hope you like the edits I made to doc/test.txt. Regards, Martijn From hpk at trillke.net Fri Nov 26 23:06:22 2004 From: hpk at trillke.net (holger krekel) Date: Fri, 26 Nov 2004 23:06:22 +0100 Subject: [py-dev] failing unit tests In-Reply-To: <41A7A0F1.6030202@infrae.com> References: <41A79443.5090006@infrae.com> <20041126211042.GO4550@solar.trillke.net> <41A7A0F1.6030202@infrae.com> Message-ID: <20041126220622.GP4550@solar.trillke.net> Hi Martijn, [Martijn Faassen Fri, Nov 26, 2004 at 10:32:33PM +0100] > holger krekel wrote: > >[Martijn Faassen Fri, Nov 26, 2004 at 09:38:27PM +0100] > ... > Right, I went through the documentation and ran into that failure demo. > Actually the failure demo output with some extra text would make for > good documentation by itself, I suspect. Yes, i agree. > >Yeah, well, probably at least partly because it relates to imports > >:-) More seriously, though, i tried to produce good output to show > >what test is failing in which line (including an according call > >trace). It shows function context so that you don't neccessarily need > >to read the test file in your editor to understand the failure. > > Yes, and for a developer this is fine. For a developer less familiar > with the codebase, or someone who is running the tests on other > purposes, a more minimalistic output option would be nice on occasion. It seems reasonable to add an option. Although this might not help a complete newbie as he wouldn't know about the option. > Of course part of the problem is that I modified some documentation text > and just to make sure wanted to run the test suite before checking it > in, even though to my knowledge py.test doesn't do doctets. I was > presented with rather scary detailed test failures, something I did not > expect at that point. I was expecting "everything is okay". :) Yes, and rightfully so. I should have thought about this earlier. Somehow i didn't think that somebody would get irritated by it in the last days and i also was too consumed by the pypy sprint :-) > >What would you think about a dense summary at the end with one line > >for each failing tests aka "filename:lineno XXXError" or so? > > I think that would be useful. And then an option to suppress any output > but the summary and the nice dots before it. :) something like "--kiss" because it's a good occassion to mention this pythonic principle :-) Or maybe just "--terseoutput" or "-t" or something. This option would be tied to the TextReporter though. For generating html-output (definitely a plan for the near future) the mileage will vary i suppose. > >Yip, the test produces this via print statements. For successful > >tests the output is not shown, though. Do you find this questionable? > > I do not find this a questionable idea at all. I thought it was an > intriguing idea when I read it in the documentation. I've edited the > documentation somewhat to make the idea more clear to beginners. One > thing I realized after editing the documentation is that I'm free to use > 'print' in whatever test code I like now, and even check it in, without > causing this to outputted except when I really need it -- in case of > test failure. Of course this still doesn't leave me free to do the same > with application code. :) > > Is that correct? If so, I shall edit the documentation accordingly. That is correct. There is a small glitch with this otherwise nice feature, though: I sometimes spread print statements over all my code and work with the failing test-output. Then the tests pass and i think "cool, checkin time" but of course i still have to get rid of the print statement in my app code. Currently py.test doesn't help with this task and i am not sure yet how to do it (except from some fragile hacks i would envision to reside in the 'magic' part of the py lib :-). > I hope you like the edits I made to doc/test.txt. They are great! I saw you introduced some references to unittest.py which i usually avoided to not confuse people who don't know anything about testing so far. But by now i guess (and Ian said this earlier already) it's probably better to mention it every now and then for developers coming from unittest.py. cheers, holger From faassen at infrae.com Sat Nov 27 14:21:42 2004 From: faassen at infrae.com (Martijn Faassen) Date: Sat, 27 Nov 2004 14:21:42 +0100 Subject: [py-dev] failing unit tests In-Reply-To: <20041126220622.GP4550@solar.trillke.net> References: <41A79443.5090006@infrae.com> <20041126211042.GO4550@solar.trillke.net> <41A7A0F1.6030202@infrae.com> <20041126220622.GP4550@solar.trillke.net> Message-ID: <41A87F66.6080609@infrae.com> holger krekel wrote: >>I hope you like the edits I made to doc/test.txt. > > > They are great! Thanks! > I saw you introduced some references to unittest.py > which i usually avoided to not confuse people who don't know > anything about testing so far. But by now i guess (and Ian said this > earlier already) it's probably better to mention it every now and then > for developers coming from unittest.py. I know one reference, somewhat obscurely, was already there, and I improved it. Then I probably added one or two more. I think it makes sense to reference unittest.py as naturally many new people will be coming from that. A separate section for people coming from 'unittest' would be good too, actually. I don't think the references to unittest in the main text right now harm readability for newbies either. By the way, I don't know what procedure was used to regenerate the test.html so I didn't do that. I also don't know what your procedure is for updating the website. :) Regards, Martijn From hpk at trillke.net Sat Nov 27 14:41:02 2004 From: hpk at trillke.net (holger krekel) Date: Sat, 27 Nov 2004 14:41:02 +0100 Subject: [py-dev] failing unit tests In-Reply-To: <41A87F66.6080609@infrae.com> References: <41A79443.5090006@infrae.com> <20041126211042.GO4550@solar.trillke.net> <41A7A0F1.6030202@infrae.com> <20041126220622.GP4550@solar.trillke.net> <41A87F66.6080609@infrae.com> Message-ID: <20041127134102.GS4550@solar.trillke.net> Hi Martijn, [Martijn Faassen Sat, Nov 27, 2004 at 02:21:42PM +0100] > holger krekel wrote: > > I saw you introduced some references to unittest.py > >which i usually avoided to not confuse people who don't know > >anything about testing so far. But by now i guess (and Ian said this > >earlier already) it's probably better to mention it every now and then > >for developers coming from unittest.py. > > I know one reference, somewhat obscurely, was already there, and I > improved it. Then I probably added one or two more. I think it makes > sense to reference unittest.py as naturally many new people will be > coming from that. Yes. > By the way, I don't know what procedure was used to regenerate the > test.html so I didn't do that. I also don't know what your procedure is > for updating the website. :) It's done automatically on checkin :-) You can check that you didn't add any problems by running py.test on the doc directory (it contains a test which does the rest-translation and fails if there are warnings or errors). Alternatively you can run .../src/tool/rest.py (or simply 'rest.py' if you "installed" the py lib per a bashrc-source-in of py/env.py) on the doc directory or a specific rest-file and it will process it so can you view it in a browser by pointing to that directory. cheers, holger From ianb at colorstudy.com Sun Nov 28 09:25:39 2004 From: ianb at colorstudy.com (Ian Bicking) Date: Sun, 28 Nov 2004 02:25:39 -0600 Subject: [py-dev] more doctest... Message-ID: <41A98B83.9070208@colorstudy.com> Following up on my other doctest email, here's a slightly better technique I'm using: from test_fixture import DoctestCollector import some_module collect_doctest = DoctestCollector(some_module) Then I added something to py.test.collect.Module: def collect_collectors(self, extpy): if extpy.check(basestarts='collect_'): iter = extpy.resolve()(self.extpy) for item in iter: yield item And this is the new test_fixture.py, with just a small addition to DoctestCollector (__init__ and __call__), though extpy starts to feel pretty extraneous: class DoctestCollector(PyCollector): def __init__(self, extpy_or_module): if isinstance(extpy_or_module, types.ModuleType): self.module = extpy_or_module self.extpy = None else: self.extpy = extpy_or_module self.module = self.extpy.getpymodule() def __call__(self, extpy): # we throw it away, because this has been set up to explicitly # check another module; maybe this isn't clean if self.extpy is None: self.extpy = extpy return self def __iter__(self): finder = doctest.DocTestFinder() tests = finder.find(self.module) for t in tests: yield DoctestItem(self.extpy, t) class DoctestItem(DoctestCollector.Item): def __init__(self, extpy, doctestitem, *args): self.extpy = extpy self.doctestitem = doctestitem self.name = extpy.basename self.args = args def execute(self, driver): runner = doctest.DocTestRunner() driver.setup_path(self.extpy) target, teardown = driver.setup_method(self.extpy) try: (failed, tried), run_output = capture_stdout(runner.run, self.doctestitem) if failed: raise self.Failed(msg=run_output, tbindex=-2) finally: if teardown: teardown(target) def capture_stdout(func, *args, **kw): newstdout = StringIO() oldstdout = sys.stdout sys.stdout = newstdout try: result = func(*args, **kw) finally: sys.stdout = oldstdout return result, newstdout.getvalue() -- Ian Bicking / ianb at colorstudy.com / http://blog.ianbicking.org From ianb at colorstudy.com Sun Nov 28 09:58:56 2004 From: ianb at colorstudy.com (Ian Bicking) Date: Sun, 28 Nov 2004 02:58:56 -0600 Subject: [py-dev] more doctest... In-Reply-To: <41A98B83.9070208@colorstudy.com> References: <41A98B83.9070208@colorstudy.com> Message-ID: <41A99350.4050207@colorstudy.com> Ian Bicking wrote: > Following up on my other doctest email, here's a slightly better > technique I'm using: > > from test_fixture import DoctestCollector > import some_module > > collect_doctest = DoctestCollector(some_module) > > > Then I added something to py.test.collect.Module: > > def collect_collectors(self, extpy): > if extpy.check(basestarts='collect_'): > iter = extpy.resolve()(self.extpy) > for item in iter: > yield item Thinking about this, it might be better if every test was potentially a collection of tests. Then something like the parameter-adding decorator would work. Or did I already mention that? I might be going in circles on this. Anyway, I'm not entirely sure how to test if it's a collection; I suppose you could just try iterating over it. But right now collections need access to the extpy object, since that's used by the reporter or driver or something, so we can't actually iterate over the object we find. -- Ian Bicking / ianb at colorstudy.com / http://blog.ianbicking.org