From hpk at codespeak.net Thu Dec 2 21:15:41 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Dec 2004 21:15:41 +0100 (MET) Subject: [py-svn] r7726 - in py/dist/py/path: . local Message-ID: <20041202201541.815105A1C8@thoth.codespeak.net> Author: hpk Date: Thu Dec 2 21:15:39 2004 New Revision: 7726 Modified: py/dist/py/path/common.py py/dist/py/path/local/local.py Log: added a 'mode' parameter to the general read() method for path objects. It supports passing 'U' even in the python2.2 case. Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Thu Dec 2 21:15:39 2004 @@ -247,9 +247,11 @@ return self.get('purebasename') purebasename = property(purebasename, None, None, 'basename without extension') - def read(self): + def read(self, mode='rb'): """ read and return a bytestring from reading the path. """ - f = self.open('rb') + if 'U' in mode and py.std.sys.version_info < (2,3): + mode = mode.replace('U', 'r' not in mode and 'r' or '') + f = self.open(mode) try: return f.read() finally: Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Thu Dec 2 21:15:39 2004 @@ -356,7 +356,7 @@ f.close() except IOError: pass - s = self.read() + s = self.read(mode='rU') codeobj = compile(s, str(self), 'exec') if dotpy: try: From hpk at codespeak.net Thu Dec 2 21:24:56 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Dec 2004 21:24:56 +0100 (MET) Subject: [py-svn] r7727 - py/dist/py/execnet Message-ID: <20041202202456.EFFA15A1C8@thoth.codespeak.net> Author: hpk Date: Thu Dec 2 21:24:56 2004 New Revision: 7727 Modified: py/dist/py/execnet/inputoutput.py Log: another windows issue with binary streams. execnet needs to communicate 8-bit clean. Modified: py/dist/py/execnet/inputoutput.py ============================================================================== --- py/dist/py/execnet/inputoutput.py (original) +++ py/dist/py/execnet/inputoutput.py Thu Dec 2 21:24:56 2004 @@ -57,6 +57,10 @@ error = (IOError, OSError, EOFError) def __init__(self, infile, outfile): + if sys.platform == 'win32': + import msvcrt + msvcrt.setmode(infile.fileno(), os.O_BINARY) + msvcrt.setmode(outfile.fileno(), os.O_BINARY) self.outfile, self.infile = infile, outfile self.readable = self.writeable = True From hpk at codespeak.net Thu Dec 2 21:52:43 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Dec 2004 21:52:43 +0100 (MET) Subject: [py-svn] r7728 - py/dist/py/path Message-ID: <20041202205243.3E6935A1C8@thoth.codespeak.net> Author: hpk Date: Thu Dec 2 21:52:42 2004 New Revision: 7728 Modified: py/dist/py/path/common.py Log: let's be a bit more thorough with universal newline and accept both 'U' and 'u'. Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Thu Dec 2 21:52:42 2004 @@ -249,8 +249,10 @@ def read(self, mode='rb'): """ read and return a bytestring from reading the path. """ - if 'U' in mode and py.std.sys.version_info < (2,3): - mode = mode.replace('U', 'r' not in mode and 'r' or '') + if py.std.sys.version_info < (2,3): + for x in 'u', 'U': + if x in mode: + mode = mode.replace(x, '') f = self.open(mode) try: return f.read() From hpk at codespeak.net Thu Dec 2 22:19:46 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Dec 2004 22:19:46 +0100 (MET) Subject: [py-svn] r7729 - in py/dist/py/test: . report/text Message-ID: <20041202211946.476E75A1C8@thoth.codespeak.net> Author: hpk Date: Thu Dec 2 22:19:45 2004 New Revision: 7729 Modified: py/dist/py/test/config.py py/dist/py/test/defaultconfig.py py/dist/py/test/report/text/reporter.py py/dist/py/test/run.py Log: reenabled --pdb support and guard against using both --session and --pdb at the same time. --session runs the tests in a separate child process and allowing --pdb in this context requires a bit of terminal hacking, at least on Unix. I hope that Armin can help out at some point as he has been doing some crazy terminal stuff lately ... Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Thu Dec 2 22:19:45 2004 @@ -74,6 +74,8 @@ # parse cmdline args cmdlineoption, remaining = parser.parse_args(args, self.option) + + self.checkoptions() # override previously computed defaults #for name in cmdlineoption.__dict__: # if not name.startswith('_'): @@ -82,6 +84,10 @@ return remaining + def checkoptions(self): + if self.option.session and self.option.usepdb: + raise ValueError, "--session together with --pdb not supported yet." + config = Config() # helpers Modified: py/dist/py/test/defaultconfig.py ============================================================================== --- py/dist/py/test/defaultconfig.py (original) +++ py/dist/py/test/defaultconfig.py Thu Dec 2 22:19:45 2004 @@ -26,9 +26,9 @@ Option('', '--nomagic', action="store_true", dest="nomagic", default=False, help="don't invoke magic to e.g. beautify failing/error statements."), - #Option('', '--pdb', - # action="store_true", dest="usepdb", default=False, - # help="Start pdb (the Python debugger) on errors."), + Option('', '--pdb', + action="store_true", dest="usepdb", default=False, + help="Start pdb (the Python debugger) on errors."), Option('', '--collectonly', action="store_true", dest="collectonly", default=False, help="only collect tests, don't execute them. "), Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Thu Dec 2 22:19:45 2004 @@ -142,15 +142,15 @@ self.out.rewrite("%.3f %-2s %-20s %s%s" % ( elapsed, resultstring, location, str(item.extpy.modpath), writeinfo )) - #if self.option.usepdb: - # if (issubclass(restype, Collector.Error) or - # issubclass(restype, Item.Failed)): - # import pdb - # self.out.rewrite( - # '\n%s: %s\n' - # % (result.excinfo[1].__class__.__name__, - # result.excinfo[1])) - # pdb.post_mortem(result.excinfo[2]) + if self.option.usepdb: + if (issubclass(restype, Collector.Error) or + issubclass(restype, Item.Failed)): + import pdb + self.out.rewrite( + '\n%s: %s\n' + % (result.excinfo[1].__class__.__name__, + result.excinfo[1])) + pdb.post_mortem(result.excinfo[2]) def report_collect_error(self, error): restype, c = self.processresult(error) Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/run.py Thu Dec 2 22:19:45 2004 @@ -182,5 +182,5 @@ failures = master(args, filenames) print "#" * 60 print "# session mode: %d failures remaining" % len(failures) - print "# checking py files below %s" % rootdir + print "# watching py files below %s" % rootdir print "# ", "^" * l From hpk at codespeak.net Sun Dec 5 21:40:11 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 5 Dec 2004 21:40:11 +0100 (MET) Subject: [py-svn] r7758 - py/dist/py/path/extpy Message-ID: <20041205204011.BD9F45AF36@thoth.codespeak.net> Author: hpk Date: Sun Dec 5 21:40:11 2004 New Revision: 7758 Modified: py/dist/py/path/extpy/test_extpy.py Log: added a test for class-checks Modified: py/dist/py/path/extpy/test_extpy.py ============================================================================== --- py/dist/py/path/extpy/test_extpy.py (original) +++ py/dist/py/path/extpy/test_extpy.py Sun Dec 5 21:40:11 2004 @@ -127,6 +127,10 @@ p = self.root.join('A') assert p.check(genfunc=0) + def test_class(self): + p = self.root.join('A') + assert p.check(class_=1) + def test_hashing_equality(self): x = self.root y = self.root.new() From hpk at codespeak.net Wed Dec 8 22:40:29 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 8 Dec 2004 22:40:29 +0100 (MET) Subject: [py-svn] r7780 - py/dist/py/path/svn Message-ID: <20041208214029.17E9D5A95A@thoth.codespeak.net> Author: hpk Date: Wed Dec 8 22:40:27 2004 New Revision: 7780 Modified: py/dist/py/path/svn/svncommon.py Log: i think more modern svn version care for setting a C locale themselves (not sure though) Modified: py/dist/py/path/svn/svncommon.py ============================================================================== --- py/dist/py/path/svn/svncommon.py (original) +++ py/dist/py/path/svn/svncommon.py Wed Dec 8 22:40:27 2004 @@ -268,6 +268,6 @@ return value def fixlocale(): - if sys.platform != 'win32': - return 'LC_ALL=C ' + #if sys.platform != 'win32': + # return 'LC_ALL=C ' return '' From hpk at codespeak.net Wed Dec 8 22:47:02 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 8 Dec 2004 22:47:02 +0100 (MET) Subject: [py-svn] r7781 - py/dist/py/test Message-ID: <20041208214702.85CB15A95A@thoth.codespeak.net> Author: hpk Date: Wed Dec 8 22:47:02 2004 New Revision: 7781 Modified: py/dist/py/test/item.py Log: removed obsolete code Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Wed Dec 8 22:47:02 2004 @@ -3,9 +3,6 @@ # Basic Test Item # ---------------------------------------------- class Item(object): - _setupcache = [] - _lastinstance = None - def __init__(self, extpy, *args): self.extpy = extpy self.name = extpy.basename From hpk at codespeak.net Wed Dec 8 23:58:52 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 8 Dec 2004 23:58:52 +0100 (MET) Subject: [py-svn] r7782 - in py/dist/py: . test Message-ID: <20041208225852.475535A95A@thoth.codespeak.net> Author: hpk Date: Wed Dec 8 23:58:51 2004 New Revision: 7782 Modified: py/dist/py/__init__.py py/dist/py/test/drive.py py/dist/py/test/item.py Log: slight refactoring + minimalistic inline documentation Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Wed Dec 8 23:58:51 2004 @@ -17,13 +17,14 @@ 'test.collect.PyCollector':'./test/collect.PyCollector', 'test.collect.Error': './test/collect.Error', 'test.Item': './test/item.Item', + 'test.GeneratorItem': './test/item.GeneratorItem', 'test.Driver': './test/drive.Driver', 'test.Option': './test/tool/optparse.Option', 'test.TextReporter': './test/report/text/reporter.TextReporter', 'test.MemoReporter': './test/report/memo.MemoReporter', 'test.exit': './test/drive.exit', - 'test.fail': './test/drive.fail', - 'test.skip': './test/drive.skip', + 'test.fail': './test/item.fail', + 'test.skip': './test/item.skip', 'test.raises': './test/raises.raises', 'test.config': './test/config.config', 'test.compat.TestCase': './test/compat.TestCase', Modified: py/dist/py/test/drive.py ============================================================================== --- py/dist/py/test/drive.py (original) +++ py/dist/py/test/drive.py Wed Dec 8 23:58:51 2004 @@ -11,14 +11,10 @@ def exit(*args): raise Exit(*args) -def skip(msg="unknown reason"): - """ skip with the given Message. """ - raise py.test.Item.Skipped(msg=msg, tbindex=-2) - -def fail(msg="unknown failure"): - """ fail with the given Message. """ - raise py.test.Item.Failed(msg=msg, tbindex=-2) - +# +# The Driver gets test Items from Collectors, # executes the +# Items and sends the Outcome to the Reporter. +# class Driver: option = py.test.config.option Exit = Exit Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Wed Dec 8 23:58:51 2004 @@ -29,4 +29,13 @@ class ExceptionFailure(Failed): pass class Skipped(Outcome): pass +# +# triggering specific outcomes from executing Items +# +def skip(msg="unknown reason"): + """ skip with the given Message. """ + raise py.test.Item.Skipped(msg=msg, tbindex=-2) +def fail(msg="unknown failure"): + """ fail with the given Message. """ + raise py.test.Item.Failed(msg=msg, tbindex=-2) From hpk at codespeak.net Thu Dec 9 00:23:37 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Dec 2004 00:23:37 +0100 (MET) Subject: [py-svn] r7783 - in py/dist/py: . test Message-ID: <20041208232337.AD80F5A95A@thoth.codespeak.net> Author: hpk Date: Thu Dec 9 00:23:37 2004 New Revision: 7783 Modified: py/dist/py/__init__.py py/dist/py/test/drive.py py/dist/py/test/item.py Log: factored out setup/teardown semantics into a separte class SetupManager. test Items by default share one 'setupmanager' instance. added some docstrings. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Thu Dec 9 00:23:37 2004 @@ -17,7 +17,6 @@ 'test.collect.PyCollector':'./test/collect.PyCollector', 'test.collect.Error': './test/collect.Error', 'test.Item': './test/item.Item', - 'test.GeneratorItem': './test/item.GeneratorItem', 'test.Driver': './test/drive.Driver', 'test.Option': './test/tool/optparse.Option', 'test.TextReporter': './test/report/text/reporter.TextReporter', Modified: py/dist/py/test/drive.py ============================================================================== --- py/dist/py/test/drive.py (original) +++ py/dist/py/test/drive.py Thu Dec 9 00:23:37 2004 @@ -9,23 +9,30 @@ Exception.__init__(self) def exit(*args): + """ exit immediately without tracebacks and reporter/summary. """ raise Exit(*args) -# -# The Driver gets test Items from Collectors, # executes the -# Items and sends the Outcome to the Reporter. -# class Driver: + """ + A Driver gets test Items from Collectors, # executes the + Items and sends the Outcome to the Reporter. + """ option = py.test.config.option Exit = Exit def __init__(self, channel): self.reporter = py.test.config.getfirst('getreporter') () - self._setupstack = [] - self._instance = None self._channel = channel self._failed = [] + def setup(self): + """ setup any neccessary resources. """ + + def teardown(self): + """ teardown any resources we know about. """ + # XXX + py.test.Item.setupmanager.teardown() + def run(self, collectors): """ main loop for running tests. """ self.setup() @@ -96,57 +103,3 @@ if py.test.config.option.exitfirstproblem: raise self.Exit(res.item) - def setup_path(self, extpy): - """ setup objects along the path to the test-method - (pointed to by extpy). Tear down any previously - setup objects which are not directly needed. - """ - # setupstack contains (extpy, obj)'s of already setup objects - # strict ordering is maintained, i.e. each extpy in - # the stack is "relto" the previous extpy. - stack = self._setupstack - while stack and not extpy.relto(stack[-1][0]): - self._teardownone(stack.pop()[1]) - rest = extpy.parts()[len(stack):-1] - for x in rest: - stack.append((x, self._setupone(x))) - - def setup(self): - """ setup any neccessary resources. """ - self._failed[:] = [] - - def teardown(self): - """ teardown any resources the driver knows about. """ - while self._setupstack: - self._teardownone(self._setupstack.pop()[1]) - - def _setupone(self, extpy): - obj = extpy.resolve() - if py.std.inspect.ismodule(obj): - if hasattr(obj, 'setup_module'): - obj.setup_module(obj) - elif py.std.inspect.isclass(obj): - if hasattr(obj, 'setup_class'): - obj.setup_class.im_func(obj) - return obj - - def _teardownone(self, obj): - if py.std.inspect.ismodule(obj): - if hasattr(obj, 'teardown_module'): - obj.teardown_module(obj) - elif py.std.inspect.isclass(obj): - if hasattr(obj, 'teardown_class'): - obj.teardown_class.im_func(obj) - - def setup_method(self, extpy): - """ return a tuple of (bound method or callable, teardown method). """ - method = extpy.resolve() - if not hasattr(method, 'im_class'): - return method, None - if self._instance.__class__ != method.im_class: - self._instance = method.im_class() - method = method.__get__(self._instance, method.im_class) - if hasattr(self._instance, 'setup_method'): - #print "execting setup", self._instance.setup_method - self._instance.setup_method(method) - return (method, getattr(self._instance, 'teardown_method', None)) Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Thu Dec 9 00:23:37 2004 @@ -1,16 +1,79 @@ +import py + +class SetupManager(object): + """ manage setting up and tearing down objects + along Item paths. Items usually share a single + SetupManager instance in order to reuse + already setup objects. + """ + def __init__(self): + self._setupstack = [] + self._instance = None + + def teardown(self): + while self._setupstack: + self._teardownone(self._setupstack.pop()[1]) + + def setup_path(self, extpy): + """ setup objects along the path to the test-method + (pointed to by extpy). Tear down any previously + setup objects which are not directly needed. + """ + # setupstack contains (extpy, obj)'s of already setup objects + # strict ordering is maintained, i.e. each extpy in + # the stack is "relto" the previous extpy. + stack = self._setupstack + while stack and not extpy.relto(stack[-1][0]): + self._teardownone(stack.pop()[1]) + rest = extpy.parts()[len(stack):-1] + for x in rest: + stack.append((x, self._setupone(x))) + + def _setupone(self, extpy): + obj = extpy.resolve() + if py.std.inspect.ismodule(obj): + if hasattr(obj, 'setup_module'): + obj.setup_module(obj) + elif py.std.inspect.isclass(obj): + if hasattr(obj, 'setup_class'): + obj.setup_class.im_func(obj) + return obj + + def _teardownone(self, obj): + if py.std.inspect.ismodule(obj): + if hasattr(obj, 'teardown_module'): + obj.teardown_module(obj) + elif py.std.inspect.isclass(obj): + if hasattr(obj, 'teardown_class'): + obj.teardown_class.im_func(obj) + + def setup_method(self, extpy): + """ return a tuple of (bound method or callable, teardown method). """ + method = extpy.resolve() + if not hasattr(method, 'im_class'): + return method, None + if self._instance.__class__ != method.im_class: + self._instance = method.im_class() + method = method.__get__(self._instance, method.im_class) + if hasattr(self._instance, 'setup_method'): + #print "execting setup", self._instance.setup_method + self._instance.setup_method(method) + return (method, getattr(self._instance, 'teardown_method', None)) -# ---------------------------------------------- -# Basic Test Item -# ---------------------------------------------- class Item(object): + """ an Item is responsible for locating and executing + a Python callable test object. + """ + setupmanager = SetupManager() + def __init__(self, extpy, *args): self.extpy = extpy self.name = extpy.basename self.args = args def execute(self, driver): - driver.setup_path(self.extpy) - target, teardown = driver.setup_method(self.extpy) + self.setupmanager.setup_path(self.extpy) + target, teardown = self.setupmanager.setup_method(self.extpy) try: target(*self.args) finally: From hpk at codespeak.net Thu Dec 9 00:44:17 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Dec 2004 00:44:17 +0100 (MET) Subject: [py-svn] r7784 - in py/dist/py: . test Message-ID: <20041208234417.C50A45A95A@thoth.codespeak.net> Author: hpk Date: Thu Dec 9 00:44:17 2004 New Revision: 7784 Modified: py/dist/py/__init__.py py/dist/py/test/collect.py py/dist/py/test/drive.py py/dist/py/test/item.py py/dist/py/test/test_collect.py Log: implemented support for Generator (collection) functions and methods. plus a couple of tests. With this new hack you can yield parametrized tests in an easy ("the best API is one that doesn't exist") way: def somefunc(x, y): assert x * y == 42 def test_generator(): yield somefunc, 6, 7 yield somefunc, 7, 6 There are still some semantical issues to be sorted out with respect to setup/teardown semantics. Also reporting for such generated tests has not been considered so far (and may be broken but there are not tests for that and i can't be bothered right now). Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Thu Dec 9 00:44:17 2004 @@ -17,6 +17,7 @@ 'test.collect.PyCollector':'./test/collect.PyCollector', 'test.collect.Error': './test/collect.Error', 'test.Item': './test/item.Item', + 'test.GeneratorItem': './test/item.GeneratorItem', 'test.Driver': './test/drive.Driver', 'test.Option': './test/tool/optparse.Option', 'test.TextReporter': './test/report/text/reporter.TextReporter', Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Thu Dec 9 00:44:17 2004 @@ -23,6 +23,7 @@ Collector instances during iteration. """ Item = test.Item + GeneratorItem = test.GeneratorItem Error = Error def iterunits(self): @@ -133,8 +134,11 @@ def collect_function(self, extpy): if extpy.check(func=1, basestarts='test_'): - #if self.extpy.samefile(extpy): not nfs/different mountpoint safe - yield self.Item(extpy) + if extpy.check(genfunc=1): + yield Generator(extpy) + else: + #if self.extpy.samefile(extpy): not nfs/different mountpoint safe ? + yield self.Item(extpy) def collect_class(self, extpy): #print "checking %r (extpy: %r)" % (extpy.resolve(), extpy) @@ -149,6 +153,31 @@ # we don't check for "samemodule"-ness of test # methods like in the Module Collector if extpy.check(basestarts='test_', func=1): - func = extpy.resolve() - yield getattr(func.im_class, 'Item', self.Item)(extpy) + if extpy.check(genfunc=1): + yield Generator(extpy) + else: + func = extpy.resolve() + yield getattr(func.im_class, 'Item', self.Item)(extpy) + +class Generator(PyCollector): + def dispatch(self, obj): + if isinstance(obj, (tuple, list)): + call = obj[0] + args = obj[1:] + else: + call = obj + args = () + #if hasattr(call, 'gi_running'): + # return ... Generator(py.path.obj(call), *args) + return self.GeneratorItem(self.extpy, call, *args) + def __iter__(self): + try: + sm = self.GeneratorItem.setupmanager + sm.setup_path(self.extpy) + gen, teardown = sm.setup_method(self.extpy) + assert not teardown, "%r not processoable in Generator-Collector (XXX)" + for call in gen(): + yield self.dispatch(call) + except: + yield self._except() Modified: py/dist/py/test/drive.py ============================================================================== --- py/dist/py/test/drive.py (original) +++ py/dist/py/test/drive.py Thu Dec 9 00:44:17 2004 @@ -30,7 +30,7 @@ def teardown(self): """ teardown any resources we know about. """ - # XXX + # XXX a cleaner way to teardown setupmanagers? py.test.Item.setupmanager.teardown() def run(self, collectors): Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Thu Dec 9 00:44:17 2004 @@ -92,6 +92,23 @@ class ExceptionFailure(Failed): pass class Skipped(Outcome): pass +class GeneratorItem(Item): + def __init__(self, extpy, call, *args): + super(GeneratorItem, self).__init__(extpy, *args) + self.call = call + assert callable(call), "generated test %r is not callable" % (call,) + + def execute(self, driver): + self.setupmanager.setup_path(self.extpy) + # XXX target is superflous here + target, teardown = self.setupmanager.setup_method(self.extpy) + try: + print "calling %r%r" %(self.call, self.args) + self.call(*self.args) + finally: + if teardown: + teardown(target) + # # triggering specific outcomes from executing Items # @@ -102,3 +119,4 @@ def fail(msg="unknown failure"): """ fail with the given Message. """ raise py.test.Item.Failed(msg=msg, tbindex=-2) + Modified: py/dist/py/test/test_collect.py ============================================================================== --- py/dist/py/test/test_collect.py (original) +++ py/dist/py/test/test_collect.py Thu Dec 9 00:44:17 2004 @@ -77,3 +77,37 @@ self.reslist.append(3) def test_4(self): assert self.reslist == [1,2,3] + +# +# some tests related to "generating tests" +# +l2=[] + +def func1(): + print "in func1" + l2.append(1) + +def func2(i): + print "in func2 with param %d" % i + l2.append(i) + +def test_generator(): + yield func1 + yield func2, 2 + yield func2, 3 + +def test_stateful_previous(): + x = l2 + assert x == [1,2,3] + +class TestGeneratorMethod: + l3 = [] + def func1(self, i): + self.l3.append(i) + + def test_generator(self): + yield self.func1, 4 + yield self.func1, 5 + + def test_previous_generator_worked(self): + assert self.l3 == [4,5] From hpk at codespeak.net Thu Dec 9 00:51:55 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Dec 2004 00:51:55 +0100 (MET) Subject: [py-svn] r7785 - in py/dist/py: . test Message-ID: <20041208235155.1F4D05A95A@thoth.codespeak.net> Author: hpk Date: Thu Dec 9 00:51:54 2004 New Revision: 7785 Modified: py/dist/py/__init__.py py/dist/py/test/collect.py py/dist/py/test/item.py Log: GeneratorItem -> GenItem It wasn't really a GeneratorItem but a generated Item, anyway. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Thu Dec 9 00:51:54 2004 @@ -17,7 +17,7 @@ 'test.collect.PyCollector':'./test/collect.PyCollector', 'test.collect.Error': './test/collect.Error', 'test.Item': './test/item.Item', - 'test.GeneratorItem': './test/item.GeneratorItem', + 'test.GenItem': './test/item.GenItem', 'test.Driver': './test/drive.Driver', 'test.Option': './test/tool/optparse.Option', 'test.TextReporter': './test/report/text/reporter.TextReporter', Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Thu Dec 9 00:51:54 2004 @@ -23,7 +23,7 @@ Collector instances during iteration. """ Item = test.Item - GeneratorItem = test.GeneratorItem + GenItem = test.GenItem Error = Error def iterunits(self): @@ -169,11 +169,11 @@ args = () #if hasattr(call, 'gi_running'): # return ... Generator(py.path.obj(call), *args) - return self.GeneratorItem(self.extpy, call, *args) + return self.GenItem(self.extpy, call, *args) def __iter__(self): try: - sm = self.GeneratorItem.setupmanager + sm = self.GenItem.setupmanager sm.setup_path(self.extpy) gen, teardown = sm.setup_method(self.extpy) assert not teardown, "%r not processoable in Generator-Collector (XXX)" Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Thu Dec 9 00:51:54 2004 @@ -92,9 +92,9 @@ class ExceptionFailure(Failed): pass class Skipped(Outcome): pass -class GeneratorItem(Item): +class GenItem(Item): def __init__(self, extpy, call, *args): - super(GeneratorItem, self).__init__(extpy, *args) + super(GenItem, self).__init__(extpy, *args) self.call = call assert callable(call), "generated test %r is not callable" % (call,) From hpk at codespeak.net Thu Dec 9 01:10:50 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Dec 2004 01:10:50 +0100 (MET) Subject: [py-svn] r7786 - in py/dist: doc example/test py/test py/test/report/text Message-ID: <20041209001050.258BE5A95A@thoth.codespeak.net> Author: hpk Date: Thu Dec 9 01:10:49 2004 New Revision: 7786 Modified: py/dist/doc/rest_test.py py/dist/example/test/failure_demo.py py/dist/py/test/drive.py py/dist/py/test/item.py py/dist/py/test/report/text/reporter.py Log: M dist/doc/rest_test.py simplified rest-tests considerably (with the new generative tests which i begin to like) M dist/py/test/item.py M dist/py/test/drive.py separate Item.execute into Item.run (caring for setup/teardown) and Item.execute(callable, *args) to execute an actual function. M dist/example/test/failure_demo.py M dist/py/test/report/text/reporter.py add two failures involving generator tests and fix the reporter to not break. Modified: py/dist/doc/rest_test.py ============================================================================== --- py/dist/doc/rest_test.py (original) +++ py/dist/doc/rest_test.py Thu Dec 9 01:10:49 2004 @@ -6,25 +6,11 @@ mydir = mypath.dirpath() rest = mydir.dirpath('tool', 'rest.py') -class RestItem(py.test.Item): - def __init__(self, path): - self.path = path - self.extpy = py.path.extpy(mypath, 'RestItem.execute') +def restcheck(path): + out = py.process.cmdexec("%s %s 2>&1" %(rest, path)) + assert not out - def execute(self, *args): - out = py.process.cmdexec("%s %s 2>&1" %(rest, self.path)) - assert not out - -class Collector(py.test.collect.Module): - def __init__(self, extpy): - self.extpy = extpy - - def __iter__(self): - for x in mydir.listdir('*.txt'): - yield RestItem(x) +def test_rest_files(): + for x in mydir.listdir('*.txt'): + yield restcheck, x -#def test_rest(p): -# out = py.process.cmdexec("%s %s" %(rest, mypath)) -# print out -# assert not out - Modified: py/dist/example/test/failure_demo.py ============================================================================== --- py/dist/example/test/failure_demo.py (original) +++ py/dist/example/test/failure_demo.py Thu Dec 9 01:10:49 2004 @@ -85,5 +85,15 @@ if namenotexi: pass + def test_generator(self): + yield None + + def func1(self): + assert 41 == 42 + + def test_generator2(self): + yield self.func1 + + def globf(x): return x+1 Modified: py/dist/py/test/drive.py ============================================================================== --- py/dist/py/test/drive.py (original) +++ py/dist/py/test/drive.py Thu Dec 9 01:10:49 2004 @@ -89,7 +89,7 @@ self.reporter.startitem(item) if not self.option.collectonly: try: - res = item.execute(self) or item.Passed() + res = item.run(self) or item.Passed() except item.Outcome, res: res.excinfo = py.std.sys.exc_info() except (KeyboardInterrupt, SystemExit): Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Thu Dec 9 01:10:49 2004 @@ -71,14 +71,26 @@ self.name = extpy.basename self.args = args - def execute(self, driver): + def run(self, driver): self.setupmanager.setup_path(self.extpy) target, teardown = self.setupmanager.setup_method(self.extpy) try: - target(*self.args) + self.execute(target, *self.args) finally: if teardown: teardown(target) + + def execute(self, target, *args): + """ default implementation for calling a test target is to + simply call it. + """ + return target(*args) + + def reprcall(self): + """ return a string representing a call to the underlying + test function. + """ + return "%r%r" % (self.extpy.modpath, self.args) class Outcome: def __init__(self, **kwargs): @@ -94,20 +106,20 @@ class GenItem(Item): def __init__(self, extpy, call, *args): + assert callable(call), "generated test %r is not callable" % (call,) super(GenItem, self).__init__(extpy, *args) self.call = call - assert callable(call), "generated test %r is not callable" % (call,) - def execute(self, driver): - self.setupmanager.setup_path(self.extpy) - # XXX target is superflous here - target, teardown = self.setupmanager.setup_method(self.extpy) - try: - print "calling %r%r" %(self.call, self.args) - self.call(*self.args) - finally: - if teardown: - teardown(target) + def execute(self, target, *args): + print "calling %r%r" %(self.call, args) + return self.call(*args) + + def reprcall(self): + """ return a string representing a call to the underlying + generated test function. + """ + name = getattr(self.call, '__name__', self.call) + return "%s -> %s%r" % (self.extpy.modpath, name, self.args) # # triggering specific outcomes from executing Items Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Thu Dec 9 01:10:49 2004 @@ -140,7 +140,7 @@ location = "%s:%d" % (realpath.basename, lineno) resultstring = self.namemap.get(restype, result.__class__.__name__) self.out.rewrite("%.3f %-2s %-20s %s%s" % ( - elapsed, resultstring, location, str(item.extpy.modpath), writeinfo + elapsed, resultstring, location, item.reprcall(), writeinfo )) if self.option.usepdb: if (issubclass(restype, Collector.Error) or From hpk at codespeak.net Thu Dec 9 09:16:46 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Dec 2004 09:16:46 +0100 (MET) Subject: [py-svn] r7789 - py/dist/py/test Message-ID: <20041209081646.E739D5AA09@thoth.codespeak.net> Author: hpk Date: Thu Dec 9 09:16:46 2004 New Revision: 7789 Modified: py/dist/py/test/run.py Log: default 'rootdir' to the current dir ... Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/run.py Thu Dec 9 09:16:46 2004 @@ -171,7 +171,7 @@ def session(args, filenames): statcache = {} failures = [] - rootdir = py.path.local(py.test.config.getfirst('rootdir')) + rootdir = py.path.local(py.test.config.getfirst('rootdir', py.path.local())) l = len(str(rootdir)) while 1: while not checkpyfilechange(rootdir, statcache): From ianb at codespeak.net Thu Dec 9 09:56:14 2004 From: ianb at codespeak.net (ianb at codespeak.net) Date: Thu, 9 Dec 2004 09:56:14 +0100 (MET) Subject: [py-svn] r7790 - in py/dist/py/path: extpy extpy/test_data local Message-ID: <20041209085614.0546A5A6E7@thoth.codespeak.net> Author: ianb Date: Thu Dec 9 09:56:14 2004 New Revision: 7790 Added: py/dist/py/path/extpy/test_data/ py/dist/py/path/extpy/test_data/no_trailing_newline.py Modified: py/dist/py/path/extpy/test_extpy.py py/dist/py/path/local/local.py Log: A test for loading Python files (extpy) that have no trailing newline. Also a fix, which adds newlines to all Python files. It's a strange bug, because it's not *just* when there's no trailing newline; there's something else about the file that also triggers it, though adding the newline fixes it anyway. Added: py/dist/py/path/extpy/test_data/no_trailing_newline.py ============================================================================== --- (empty file) +++ py/dist/py/path/extpy/test_data/no_trailing_newline.py Thu Dec 9 09:56:14 2004 @@ -0,0 +1,7 @@ +test = True +if __name__ == '__main__': + pass + +############################################################################# +# 2004 M.E.Farmer Jr. +# Python license \ No newline at end of file Modified: py/dist/py/path/extpy/test_extpy.py ============================================================================== --- py/dist/py/path/extpy/test_extpy.py (original) +++ py/dist/py/path/extpy/test_extpy.py Thu Dec 9 09:56:14 2004 @@ -185,3 +185,17 @@ class ExampleClass: testattr = 1 + +def test_no_newline(): + filepath = mypath.dirpath() / 'test_data' / 'no_trailing_newline.py' + pyc = filepath.dirpath() / 'no_trailing_newline.pyc' + if pyc.check(exists=1): + pyc.remove() + data = filepath.read() + assert not data.endswith('\n') and not data.endswith('\r'), ( + "The file must not end with a newline (that's the point of " + "this test") + #print repr(data) + mod_extpy = py.path.extpy(filepath) + #print mod_extpy.resolve() + assert mod_extpy.resolve().test Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Thu Dec 9 09:56:14 2004 @@ -356,7 +356,7 @@ f.close() except IOError: pass - s = self.read(mode='rU') + s = self.read(mode='rU') + '\n' codeobj = compile(s, str(self), 'exec') if dotpy: try: From hpk at codespeak.net Sat Dec 11 18:27:41 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 11 Dec 2004 18:27:41 +0100 (MET) Subject: [py-svn] r7817 - py/dist/py/misc Message-ID: <20041211172741.9AA245A9B4@thoth.codespeak.net> Author: hpk Date: Sat Dec 11 18:27:41 2004 New Revision: 7817 Modified: py/dist/py/misc/test_initpkg.py Log: exclude Ian's test_data in extpy from the "import all" check Modified: py/dist/py/misc/test_initpkg.py ============================================================================== --- py/dist/py/misc/test_initpkg.py (original) +++ py/dist/py/misc/test_initpkg.py Sat Dec 11 18:27:41 2004 @@ -22,6 +22,7 @@ base = py.path.local(py.__file__).dirpath() nodirs = ( base.join('test', 'test', 'data'), + base.join('path', 'extpy', 'test_data'), base.join('test', 'test', 'import_test'), base.join('bin'), base.join('execnet', 'bin'), From hpk at codespeak.net Sat Dec 11 18:28:27 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 11 Dec 2004 18:28:27 +0100 (MET) Subject: [py-svn] r7818 - in py/dist/py/test: . report/text Message-ID: <20041211172827.CBD2D5A9B4@thoth.codespeak.net> Author: hpk Date: Sat Dec 11 18:28:27 2004 New Revision: 7818 Modified: py/dist/py/test/drive.py py/dist/py/test/item.py py/dist/py/test/report/text/reporter.py py/dist/py/test/report/text/summary.py Log: small cleanup and slightly improved documentation with --pdb you know get to see the usual failure output first (instead of directly thrown on the pdb-cmdline) Modified: py/dist/py/test/drive.py ============================================================================== --- py/dist/py/test/drive.py (original) +++ py/dist/py/test/drive.py Sat Dec 11 18:28:27 2004 @@ -86,13 +86,14 @@ close() def runitem(self, item): - self.reporter.startitem(item) if not self.option.collectonly: + self.reporter.startitem(item) try: res = item.run(self) or item.Passed() except item.Outcome, res: res.excinfo = py.std.sys.exc_info() except (KeyboardInterrupt, SystemExit): + self.reporter.enditem(res) raise except: res = item.Failed(excinfo=py.std.sys.exc_info()) Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Sat Dec 11 18:28:27 2004 @@ -19,9 +19,9 @@ (pointed to by extpy). Tear down any previously setup objects which are not directly needed. """ - # setupstack contains (extpy, obj)'s of already setup objects - # strict ordering is maintained, i.e. each extpy in - # the stack is "relto" the previous extpy. + # _setupstack contains (extpy, obj) tuples of already setup + # objects. Strict ordering is maintained, i.e. each extpy in + # the stack is "relto" its previous extpy. stack = self._setupstack while stack and not extpy.relto(stack[-1][0]): self._teardownone(stack.pop()[1]) @@ -111,7 +111,7 @@ self.call = call def execute(self, target, *args): - print "calling %r%r" %(self.call, args) + #print "calling %r%r" %(self.call, args) return self.call(*args) def reprcall(self): Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Sat Dec 11 18:28:27 2004 @@ -29,7 +29,7 @@ f = py.std.sys.stdout self.out = getout(f) self._started = {} - self.summary = self.Summary() + self.summary = self.Summary(self.out) self.summary.option = self.option = py.test.config.option def start(self): @@ -47,7 +47,7 @@ if not self.option.nomagic: py.magic.revoke(assertion=1) self.summary.endtime = now() - self.summary.render(self.out) + self.summary.render() def open(self, collector): if self.option.collectonly: @@ -145,6 +145,7 @@ if self.option.usepdb: if (issubclass(restype, Collector.Error) or issubclass(restype, Item.Failed)): + self.summary.repr_failure(result) import pdb self.out.rewrite( '\n%s: %s\n' Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Sat Dec 11 18:28:27 2004 @@ -4,8 +4,8 @@ from py.__impl__.magic import exprinfo, assertion class Summary(object): - def __init__(self): - pass + def __init__(self, out): + self.out = out def append(self, restype, testresult): self.getlist(restype).append(testresult) @@ -37,8 +37,7 @@ for error in self.getlist(py.test.collect.Error): self.repr_collect_error(error) - def render(self, out): - self.out = out + def render(self): self.out.write('\n') self.skippedreasons() self.failures() From hpk at codespeak.net Thu Dec 16 11:10:06 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 16 Dec 2004 11:10:06 +0100 (MET) Subject: [py-svn] r7871 - py/dist/py/test/test/import_test/package Message-ID: <20041216101006.B589D5B187@thoth.codespeak.net> Author: hpk Date: Thu Dec 16 11:10:06 2004 New Revision: 7871 Modified: py/dist/py/test/test/import_test/package/ (props changed) py/dist/py/test/test/import_test/package/__init__.py (props changed) py/dist/py/test/test/import_test/package/absolute_import_shared_lib.py (props changed) py/dist/py/test/test/import_test/package/bugtest_import.py (props changed) py/dist/py/test/test/import_test/package/module_that_imports_shared_lib.py (props changed) py/dist/py/test/test/import_test/package/shared_lib.py (props changed) Log: fixeol From hpk at codespeak.net Thu Dec 16 11:13:40 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 16 Dec 2004 11:13:40 +0100 (MET) Subject: [py-svn] r7872 - in py/dist/py: misc misc/greenlet path/local Message-ID: <20041216101340.84AB25B187@thoth.codespeak.net> Author: hpk Date: Thu Dec 16 11:13:36 2004 New Revision: 7872 Added: py/dist/py/misc/greenlet/ (props changed) - copied from r7229, user/arigo/greenlet/ py/dist/py/misc/greenlet/README.txt - copied unchanged from r7869, user/arigo/greenlet/README.txt py/dist/py/misc/greenlet/dummy_greenlet.py (props changed) - copied unchanged from r7869, user/arigo/greenlet/dummy_greenlet.py py/dist/py/misc/greenlet/greenlet.c - copied unchanged from r7869, user/arigo/greenlet/greenlet.c py/dist/py/misc/greenlet/greenlet.h - copied unchanged from r7869, user/arigo/greenlet/greenlet.h py/dist/py/misc/greenlet/setup.py - copied unchanged from r7869, user/arigo/greenlet/setup.py py/dist/py/misc/greenlet/slp_platformselect.h - copied unchanged from r7869, user/arigo/greenlet/slp_platformselect.h py/dist/py/misc/greenlet/switch_ppc_macosx.h - copied unchanged from r7869, user/arigo/greenlet/switch_ppc_macosx.h py/dist/py/misc/greenlet/switch_ppc_unix.h - copied unchanged from r7869, user/arigo/greenlet/switch_ppc_unix.h py/dist/py/misc/greenlet/switch_s390_unix.h - copied unchanged from r7869, user/arigo/greenlet/switch_s390_unix.h py/dist/py/misc/greenlet/switch_sparc_sun_gcc.h - copied unchanged from r7869, user/arigo/greenlet/switch_sparc_sun_gcc.h py/dist/py/misc/greenlet/switch_x86_msvc.h - copied unchanged from r7869, user/arigo/greenlet/switch_x86_msvc.h py/dist/py/misc/greenlet/switch_x86_unix.h - copied unchanged from r7869, user/arigo/greenlet/switch_x86_unix.h py/dist/py/misc/greenlet/test.py - copied unchanged from r7869, user/arigo/greenlet/test.py py/dist/py/misc/greenlet/test2.py - copied unchanged from r7869, user/arigo/greenlet/test2.py py/dist/py/misc/greenlet/test3.py - copied unchanged from r7869, user/arigo/greenlet/test3.py py/dist/py/path/local/stdoutcapture.py - copied unchanged from r7228, pypy/trunk/src/pypy/translator/tool/stdoutcapture.py Modified: py/dist/py/misc/ (props changed) py/dist/py/misc/__init__.py (props changed) py/dist/py/misc/std.py (props changed) py/dist/py/misc/test_api.py (props changed) py/dist/py/misc/test_std.py (props changed) py/dist/py/path/local/local.py Log: hum, so hum, maybe you should not look too closely at this checkin. It adds support for transparent compilation of C-modules via the py.path.local() object. While it basically works this is not final in any way but i want to get it off my current tree on my laptop :-) Oh, and also from user/arigo/... there is the "greenlet" stuff from Armin in the misc directory and you can do py.path.local('.../misc/greenlet/greenlet.c').getpymodule() to actually play with them ... Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Thu Dec 16 11:13:36 2004 @@ -335,6 +335,12 @@ """ return string representation of the Path. """ return self.strpath + def getpymodule(self): + if self.ext != '.c': + return super(LocalPath, self).getpymodule() + mod = make_module_from_c(self) + return mod + def getpycodeobj(self): dotpy = self.check(ext='.py') if dotpy: @@ -510,3 +516,52 @@ except: error.error_enhance(sys.exc_info() ) +def make_module_from_c(cfile): + from distutils.core import setup + from distutils.extension import Extension + import stdoutcapture + debug = 0 + + #try: + # from distutils.log import set_threshold + # set_threshold(10000) + #except ImportError: + # print "ERROR IMPORTING" + # pass + + dirpath = cfile.dirpath() + lastdir = dirpath.chdir() + try: + modname = cfile.purebasename + c = stdoutcapture.Capture(mixed_out_err = True) + try: + try: + setup( + name = "testmodules", + ext_modules=[ + Extension(modname, [str(cfile)]) + ], + script_name = 'setup.py', + script_args = ['-q', 'build_ext', '--inplace'] + #script_args = ['build_ext', '--inplace'] + ) + finally: + foutput, foutput = c.done() + except (KeyboardInterrupt, SystemExit): + raise + except: + print foutput.read() + raise + # XXX do we need to do some check on fout/ferr? + # XXX not a nice way to import a module + if debug: print "inserting path to sys.path", dirpath + sys.path.insert(0, '.') + if debug: print "import %(modname)s as testmodule" % locals() + exec "import %(modname)s as testmodule" % locals() + sys.path.pop(0) + finally: + lastdir.chdir() + #if not debug: + #dirpath.rmtree() + return testmodule + From hpk at codespeak.net Thu Dec 16 11:19:32 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 16 Dec 2004 11:19:32 +0100 (MET) Subject: [py-svn] r7874 - py/dist/py/magic Message-ID: <20041216101932.91C5E5B1AA@thoth.codespeak.net> Author: hpk Date: Thu Dec 16 11:19:32 2004 New Revision: 7874 Modified: py/dist/py/magic/exprinfo.py Log: use RunnerFrame ... Modified: py/dist/py/magic/exprinfo.py ============================================================================== --- py/dist/py/magic/exprinfo.py (original) +++ py/dist/py/magic/exprinfo.py Thu Dec 16 11:19:32 2004 @@ -12,7 +12,6 @@ #import traceback #traceback.print_exc() - class RunnerFrame: """Wrapper around a Python frame holding f_locals and f_globals in which expressions can be evaluated.""" @@ -450,6 +449,7 @@ tb = magic.dyncode.listtb(tb)[-1] source = dyncode.getparseablestartingblock(tb) frame = tb.tb_frame + frame = RunnerFrame(frame.f_globals, frame.f_locals) x = interpret(source, frame) if not isinstance(x, str): raise TypeError, "interpret returned non-string %r" % (x,) From hpk at codespeak.net Thu Dec 16 13:03:11 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 16 Dec 2004 13:03:11 +0100 (MET) Subject: [py-svn] r7880 - py/dist/py/test/test/data Message-ID: <20041216120311.C95CA5AB2F@thoth.codespeak.net> Author: hpk Date: Thu Dec 16 13:03:10 2004 New Revision: 7880 Modified: py/dist/py/test/test/data/Collector.py Log: hard to say ... Modified: py/dist/py/test/test/data/Collector.py ============================================================================== --- py/dist/py/test/test/data/Collector.py (original) +++ py/dist/py/test/test/data/Collector.py Thu Dec 16 13:03:10 2004 @@ -16,6 +16,6 @@ def test_this_should_not_be_called(): assert 1 != 0, "should not be collected" -class A: +class TestA: def test_this_should_not_be_called(self): assert 1 != 0, "should not be collected" From hpk at codespeak.net Thu Dec 16 13:03:46 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 16 Dec 2004 13:03:46 +0100 (MET) Subject: [py-svn] r7881 - py/dist/py/test Message-ID: <20041216120346.9D6B35AB2F@thoth.codespeak.net> Author: hpk Date: Thu Dec 16 13:03:46 2004 New Revision: 7881 Modified: py/dist/py/test/config.py py/dist/py/test/test_config.py Log: a new cleaner method to get a config values based on a given path and a name. (looks upwards and uses defaults last) Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Thu Dec 16 13:03:46 2004 @@ -38,7 +38,19 @@ self.configpaths.append(extpy) self.configpaths.sort(lambda x,y: cmp(len(str(x)), len(str(y)))) self.configpaths.append(defaultconfig) - + + def getconfigvalue(self, path, name): + for p in path.parts(reverse=True): + x = p.join(configbasename) + if x.check(file=1): + extpy = py.path.extpy(x, name) + if extpy.check(): + return extpy.resolve() + extpy = defaultconfig.join(name) + if extpy.check(): + return extpy.resolve() + raise ValueError("could not find config value %r" % name) + def getfirst(self, param, default=dummy): """ return first found parameter. """ for config in self.configpaths: Modified: py/dist/py/test/test_config.py ============================================================================== --- py/dist/py/test/test_config.py (original) +++ py/dist/py/test/test_config.py Thu Dec 16 13:03:46 2004 @@ -43,3 +43,10 @@ assert cfg.getfirst('x') == 1 assert py._x == [1,2,3] +def test_getconfigvalue(): + from py.__impl__.test import config + cfg = config.Config() + o = py.test.config.tmpdir.ensure('configtest', dir=1) + o.ensure('conftest.py').write('x=1') + assert cfg.getconfigvalue(o, 'x') == 1 + py.test.raises(ValueError, "cfg.getconfigvalue(o, 'y')") From hpk at codespeak.net Thu Dec 16 16:27:42 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 16 Dec 2004 16:27:42 +0100 (MET) Subject: [py-svn] r7885 - py/dist/py/misc Message-ID: <20041216152742.B53D35AB2F@thoth.codespeak.net> Author: hpk Date: Thu Dec 16 16:27:42 2004 New Revision: 7885 Modified: py/dist/py/misc/test_initpkg.py Log: ignore greenlet for "import all test" Modified: py/dist/py/misc/test_initpkg.py ============================================================================== --- py/dist/py/misc/test_initpkg.py (original) +++ py/dist/py/misc/test_initpkg.py Thu Dec 16 16:27:42 2004 @@ -24,6 +24,7 @@ base.join('test', 'test', 'data'), base.join('path', 'extpy', 'test_data'), base.join('test', 'test', 'import_test'), + base.join('misc', 'greenlet'), base.join('bin'), base.join('execnet', 'bin'), ) From hpk at codespeak.net Thu Dec 16 17:08:37 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 16 Dec 2004 17:08:37 +0100 (MET) Subject: [py-svn] r7886 - py/dist/py/path/extpy/test_data Message-ID: <20041216160837.71BDD5AB2F@thoth.codespeak.net> Author: hpk Date: Thu Dec 16 17:08:36 2004 New Revision: 7886 Modified: py/dist/py/path/extpy/test_data/ (props changed) py/dist/py/path/extpy/test_data/no_trailing_newline.py (props changed) Log: fixeol From hpk at codespeak.net Thu Dec 16 17:55:47 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 16 Dec 2004 17:55:47 +0100 (MET) Subject: [py-svn] r7890 - in py/dist/py: . test Message-ID: <20041216165547.BAEF15B1AA@thoth.codespeak.net> Author: hpk Date: Thu Dec 16 17:55:47 2004 New Revision: 7890 Modified: py/dist/py/__init__.py py/dist/py/test/collect.py py/dist/py/test/compat.py py/dist/py/test/config.py py/dist/py/test/defaultconfig.py py/dist/py/test/item.py py/dist/py/test/run.py py/dist/py/test/test_collect.py Log: allow customization of the collection process from configfiles (conftest.py). The idea is that you can can locally take over the collection process for Directories, Modules or Classes. "Locally" means that if you have py.test x/y/z then py.test will import and look into x/y/z/conftest.py x/y/conftest.py ... in order to find Directory/Module/Class collectors. The first match wins. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Thu Dec 16 17:55:47 2004 @@ -14,6 +14,7 @@ 'test.collect.Collector': './test/collect.Collector', 'test.collect.Directory': './test/collect.Directory', 'test.collect.Module': './test/collect.Module', + 'test.collect.Class': './test/collect.Class', 'test.collect.PyCollector':'./test/collect.PyCollector', 'test.collect.Error': './test/collect.Error', 'test.Item': './test/item.Item', Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Thu Dec 16 17:55:47 2004 @@ -3,6 +3,20 @@ import sys, inspect, types from py import path, test import py + +def getfscollector(fspath): + if fspath.check(file=1): + col = py.test.config.getconfigvalue(fspath, 'Module') + elif fspath.check(dir=1): + col = py.test.config.getconfigvalue(fspath, 'Directory') + else: + raise IOError, "%r does not exist" % fspath + return col(fspath) + +def configproperty(name): + def fget(self): + return py.test.config.getconfigvalue(self.fspath, name) + return property(fget) class Error(object): """ represents a non-fatal exception while collecting. """ @@ -25,6 +39,9 @@ Item = test.Item GenItem = test.GenItem Error = Error + Module = configproperty('Module') + Directory = configproperty('Directory') + Class = configproperty('Class') def iterunits(self): """ yield all units of the Collector instance. """ @@ -57,13 +74,14 @@ return (fspath.check(file=1, fnmatch='test_*.py') or fspath.check(file=1, fnmatch='*_test.py')) rec = py.path.checker(dir=1, dotfile=0, link=0) + def __iter__(self): try: for fspath in self.fspath.listdir(sort=True): if self.rec(fspath): - yield self.__class__(fspath) + yield self.Directory(fspath) elif self.fil(fspath): - yield Module(fspath) + yield self.Module(fspath) except: yield self._except() @@ -80,6 +98,8 @@ for x in dir(self.__class__) if x.startswith('collect_')] + fspath = property(lambda self: self.extpy.root) + def __repr__(self): return '%s(%r)' %(self.__class__.__name__, str(self.extpy)) @@ -93,7 +113,6 @@ #print "looking at", extpy for x in meth(extpy): #print "found", x - x.fspath = self.extpy.root sortvalue = self.getsortvalue(x) l.append((sortvalue, x)) l.sort() @@ -145,7 +164,7 @@ if extpy.check(basestarts='Test') and self.extpy.samefile(extpy): obj = extpy.resolve() if inspect.isclass(obj) and not getattr(obj, 'disabled', 0): - yield Class(extpy) + yield self.Class(extpy) class Class(PyCollector): def collect_method(self, extpy): @@ -157,7 +176,10 @@ yield Generator(extpy) else: func = extpy.resolve() - yield getattr(func.im_class, 'Item', self.Item)(extpy) + try: + yield getattr(func.im_class, 'Item')(extpy) + except AttributeError: + yield self.Item(extpy) class Generator(PyCollector): def dispatch(self, obj): Modified: py/dist/py/test/compat.py ============================================================================== --- py/dist/py/test/compat.py (original) +++ py/dist/py/test/compat.py Thu Dec 16 17:55:47 2004 @@ -5,7 +5,7 @@ """ compatibility Unit executor for TestCase methods honouring setUp and tearDown semantics. """ - def execute(self, runner): + def execute(self, driver): unboundmethod = self.extpy.resolve() cls = unboundmethod.im_class instance = cls() Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Thu Dec 16 17:55:47 2004 @@ -21,7 +21,7 @@ try: return sessiondir[0] except IndexError: - d = py.path.local.make_numbered_dir(base='utest-') + d = py.path.local.make_numbered_dir(base='pytest-') sessiondir.append(d) return d tmpdir = property(_gettmpdir, None, None, "Temporary Directory") Modified: py/dist/py/test/defaultconfig.py ============================================================================== --- py/dist/py/test/defaultconfig.py (original) +++ py/dist/py/test/defaultconfig.py Thu Dec 16 17:55:47 2004 @@ -1,6 +1,10 @@ import py Option = py.test.Option +Module = py.test.collect.Module +Directory = py.test.collect.Directory +Class = py.test.collect.Class + def getreporter(): return py.test.TextReporter() Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Thu Dec 16 17:55:47 2004 @@ -90,7 +90,7 @@ """ return a string representing a call to the underlying test function. """ - return "%r%r" % (self.extpy.modpath, self.args) + return "%s%r" % (self.extpy.modpath, self.args) class Outcome: def __init__(self, **kwargs): Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/run.py Thu Dec 16 17:55:47 2004 @@ -2,6 +2,7 @@ import py from py.__impl__.execnet.channel import ChannelFile, receive2file from py.__impl__.test.config import configbasename +from py.__impl__.test.collect import getfscollector import sys def checkpyfilechange(rootdir, statcache): @@ -140,12 +141,7 @@ current = py.path.local() for fn in filenames: fullfn = current.join(fn, abs=1) - if fullfn.check(file=1): - yield py.test.collect.Module(fullfn) - elif fullfn.check(dir=1): - yield py.test.collect.Directory(fullfn) - else: - raise IOError, "%r does not exist" % fn + yield getfscollector(fullfn) def getanchors(args): """ yield "anchors" from skimming the args for existing files/dirs. """ Modified: py/dist/py/test/test_collect.py ============================================================================== --- py/dist/py/test/test_collect.py (original) +++ py/dist/py/test/test_collect.py Thu Dec 16 17:55:47 2004 @@ -111,3 +111,33 @@ def test_previous_generator_worked(self): assert self.l3 == [4,5] + +def test_custom_collection_from_conftest(): + o = py.test.config.tmpdir.ensure('customconfigtest', dir=1) + o.ensure('conftest.py').write("""if 1: + import py + class MyItem(py.test.Item): + pass + class Directory(py.test.collect.Directory): + def fil(self, fspath): + return fspath.check(basestarts='check_') + class Module(py.test.collect.Module): + def collect_function(self, extpy): + if extpy.check(basestarts='check_', func=1): + yield self.Item(extpy) + class Class(py.test.collect.Class): + def collect_method(self, extpy): + if extpy.check(basestarts='check_', func=1): + yield MyItem(extpy) + """) + o.ensure('somedir', 'check_something').write("""if 1: + def check_func(): + assert 42 == 42 + class TestClass: + def check_method(self): + assert 23 == 23 + """) + from py.__impl__.test.collect import getfscollector + units = list(getfscollector(o).iterunits()) + assert len(units) == 2 + assert units[1].__class__.__name__ == 'MyItem' From hpk at codespeak.net Fri Dec 17 02:49:37 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 17 Dec 2004 02:49:37 +0100 (MET) Subject: [py-svn] r7893 - py/dist/py/misc/greenlet Message-ID: <20041217014937.6C2675AB2F@thoth.codespeak.net> Author: hpk Date: Fri Dec 17 02:49:36 2004 New Revision: 7893 Removed: py/dist/py/misc/greenlet/ Log: removing greenlets temporarily ... From hpk at codespeak.net Fri Dec 17 11:16:12 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 17 Dec 2004 11:16:12 +0100 (MET) Subject: [py-svn] r7897 - in py/dist/py: . magic misc path path/local Message-ID: <20041217101612.92B785B06B@thoth.codespeak.net> Author: hpk Date: Fri Dec 17 11:16:11 2004 New Revision: 7897 Modified: py/dist/py/__init__.py py/dist/py/initpkg.py py/dist/py/magic/ (props changed) py/dist/py/misc/test_initpkg.py py/dist/py/path/common.py py/dist/py/path/local/local.py Log: reintroduce greenlets at the position where they belong: py.magic.greenlet also make them an external to more closely tie it to arigo's trunk (and to have svn blame report the right thing yadda yadda) refactored the importing bits so that you can specify the export of names of compiled-on-the-fly C-files in __init__.py Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Fri Dec 17 11:16:11 2004 @@ -1,55 +1,50 @@ -from initpkg import initpkg +from initpkg import initpkg initpkg(__name__, exportdefs = { - 'std': './misc/std.std', - 'path.local': './path/local/local.LocalPath', - 'path.checker': './path/common.checker', - 'path.svnurl': './path/svn/urlcommand.SvnCommandPath', - 'path.svnwc': './path/svn/wccommand.SvnWCCommandPath', - 'path.extpy': './path/extpy/extpy.Extpy', - 'path.NotFound': './path/error.FileNotFound', - 'path.Denied': './path/error.PermissionDenied', - 'path.NoDirectory': './path/error.NoDirectory', - 'path.Invalid': './path/error.Invalid', - - 'test.collect.Collector': './test/collect.Collector', - 'test.collect.Directory': './test/collect.Directory', - 'test.collect.Module': './test/collect.Module', - 'test.collect.Class': './test/collect.Class', - 'test.collect.PyCollector':'./test/collect.PyCollector', - 'test.collect.Error': './test/collect.Error', - 'test.Item': './test/item.Item', - 'test.GenItem': './test/item.GenItem', - 'test.Driver': './test/drive.Driver', - 'test.Option': './test/tool/optparse.Option', - 'test.TextReporter': './test/report/text/reporter.TextReporter', - 'test.MemoReporter': './test/report/memo.MemoReporter', - 'test.exit': './test/drive.exit', - 'test.fail': './test/item.fail', - 'test.skip': './test/item.skip', - 'test.raises': './test/raises.raises', - 'test.config': './test/config.config', - 'test.compat.TestCase': './test/compat.TestCase', - - 'process.cmdexec': './process/cmdexec.cmdexec', - - 'execnet.PopenGateway': './execnet/register.PopenGateway', - 'execnet.SocketGateway': './execnet/register.SocketGateway', - #'execnet.SSHGateway' : './execnet/register.SSHGateway', - - 'magic.View': './magic/viewtype.View', - 'magic.autopath': './magic/autopath.autopath', - 'magic.invoke': './magic/invoke.invoke', - 'magic.revoke': './magic/invoke.revoke', - 'magic.AssertionError': './magic/assertion.AssertionError', - 'magic.patch': './magic/patch.patch', - 'magic.revert': './magic/patch.revert', - 'magic.dyncode.compile': './magic/dyncode.compile', - 'magic.dyncode.compile2': './magic/dyncode.compile2', - 'magic.dyncode.getsource': './magic/dyncode.getsource', - 'magic.dyncode.tbinfo': './magic/dyncode.tbinfo', - 'magic.dyncode.listtb': './magic/dyncode.listtb', - 'magic.dyncode.findsource': './magic/dyncode.findsource', - 'magic.dyncode.getline': './magic/dyncode.getline', - 'magic.dyncode.getlines': './magic/dyncode.getlines', + 'test.skip' : ('./test/item.py', 'skip'), + 'test.raises' : ('./test/raises.py', 'raises'), + 'test.fail' : ('./test/item.py', 'fail'), + 'test.exit' : ('./test/drive.py', 'exit'), + 'test.config' : ('./test/config.py', 'config'), + 'test.compat.TestCase' : ('./test/compat.py', 'TestCase'), + 'test.collect.PyCollector': ('./test/collect.py', 'PyCollector'), + 'test.collect.Module' : ('./test/collect.py', 'Module'), + 'test.collect.Error' : ('./test/collect.py', 'Error'), + 'test.collect.Directory' : ('./test/collect.py', 'Directory'), + 'test.collect.Collector' : ('./test/collect.py', 'Collector'), + 'test.collect.Class' : ('./test/collect.py', 'Class'), + 'test.TextReporter' : ('./test/report/text/reporter.py', 'TextReporter'), + 'test.Option' : ('./test/tool/optparse.py', 'Option'), + 'test.MemoReporter' : ('./test/report/memo.py', 'MemoReporter'), + 'test.Item' : ('./test/item.py', 'Item'), + 'test.GenItem' : ('./test/item.py', 'GenItem'), + 'test.Driver' : ('./test/drive.py', 'Driver'), + 'std' : ('./misc/std.py', 'std'), + 'process.cmdexec' : ('./process/cmdexec.py', 'cmdexec'), + 'path.svnwc' : ('./path/svn/wccommand.py', 'SvnWCCommandPath'), + 'path.svnurl' : ('./path/svn/urlcommand.py', 'SvnCommandPath'), + 'path.local' : ('./path/local/local.py', 'LocalPath'), + 'path.extpy' : ('./path/extpy/extpy.py', 'Extpy'), + 'path.checker' : ('./path/common.py', 'checker'), + 'path.NotFound' : ('./path/error.py', 'FileNotFound'), + 'path.NoDirectory' : ('./path/error.py', 'NoDirectory'), + 'path.Invalid' : ('./path/error.py', 'Invalid'), + 'path.Denied' : ('./path/error.py', 'PermissionDenied'), + 'magic.revoke' : ('./magic/invoke.py', 'revoke'), + 'magic.revert' : ('./magic/patch.py', 'revert'), + 'magic.patch' : ('./magic/patch.py', 'patch'), + 'magic.invoke' : ('./magic/invoke.py', 'invoke'), + 'magic.greenlet' : ('./magic/greenlet/greenlet.c', 'greenlet'), + 'magic.dyncode.tbinfo' : ('./magic/dyncode.py', 'tbinfo'), + 'magic.dyncode.listtb' : ('./magic/dyncode.py', 'listtb'), + 'magic.dyncode.getsource': ('./magic/dyncode.py', 'getsource'), + 'magic.dyncode.getlines' : ('./magic/dyncode.py', 'getlines'), + 'magic.dyncode.getline' : ('./magic/dyncode.py', 'getline'), + 'magic.dyncode.findsource': ('./magic/dyncode.py', 'findsource'), + 'magic.dyncode.compile2' : ('./magic/dyncode.py', 'compile2'), + 'magic.dyncode.compile' : ('./magic/dyncode.py', 'compile'), + 'magic.autopath' : ('./magic/autopath.py', 'autopath'), + 'magic.View' : ('./magic/viewtype.py', 'View'), + 'magic.AssertionError' : ('./magic/assertion.py', 'AssertionError'), + 'execnet.SocketGateway' : ('./execnet/register.py', 'SocketGateway'), + 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), }) - Modified: py/dist/py/initpkg.py ============================================================================== --- py/dist/py/initpkg.py (original) +++ py/dist/py/initpkg.py Fri Dec 17 11:16:11 2004 @@ -32,24 +32,25 @@ # inhibit further direct filesystem imports through the package module del pkgmodule.__path__ - def _resolve(self, extpy): - """ resolve a combined filesystem/python "fspy" path. """ - assert extpy.startswith('./'), \ - "%r is not an implementation path (XXX)" % extpy - - slash = extpy.rfind('/') - dot = extpy.find('.', slash) - if dot == -1: - return self._loadimpl(extpy) - - implmodule = self._loadimpl(extpy[:dot]) - attrname = extpy[dot+1:] - return getattr(implmodule, attrname) + def _resolve(self, extpyish): + """ resolve a combined filesystem/python extpy-ish path. """ + fspath, modpath = extpyish + if not fspath.endswith('.py'): + import py + e = py.path.local(self.implmodule.__file__) + e = e.dirpath(fspath, abs=True) + e = py.path.extpy(e, modpath) + return e.resolve() + assert fspath.startswith('./'), \ + "%r is not an implementation path (XXX)" % extpyish + implmodule = self._loadimpl(fspath[:-3]) + return getattr(implmodule, modpath) def _loadimpl(self, relfile): """ load implementation for the given relfile. """ parts = [x.strip() for x in relfile.split('/') if x and x!= '.'] modpath = ".".join([self.implmodule.__name__] + parts) + #print "trying import", modpath return __import__(modpath, None, None, ['name']) def exportitems(self): @@ -120,7 +121,10 @@ del self.__map__[name] # XXX modify some attrs to make a class appear at virtual module level if hasattr(result, '__module__'): - setattr(result, '__module__', self.__name__) + try: + setattr(result, '__module__', self.__name__) + except TypeError: + pass if hasattr(result, '__bases__'): try: setattr(result, '__name__', name) Modified: py/dist/py/misc/test_initpkg.py ============================================================================== --- py/dist/py/misc/test_initpkg.py (original) +++ py/dist/py/misc/test_initpkg.py Fri Dec 17 11:16:11 2004 @@ -24,7 +24,7 @@ base.join('test', 'test', 'data'), base.join('path', 'extpy', 'test_data'), base.join('test', 'test', 'import_test'), - base.join('misc', 'greenlet'), + base.join('magic', 'greenlet'), base.join('bin'), base.join('execnet', 'bin'), ) Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Fri Dec 17 11:16:11 2004 @@ -233,11 +233,11 @@ def __div__(self, other): return self.join(str(other)) - def dirpath(self, *args): + def dirpath(self, *args, **kwargs): """ return the directory Path of the current Path joined with any given path arguments. """ - return self.new(basename='').join(*args) + return self.new(basename='').join(*args, **kwargs) def ext(self): return self.get('ext') Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Fri Dec 17 11:16:11 2004 @@ -158,14 +158,15 @@ """ if not args: return self - args = (self.strpath,) + args + strargs = [self.strpath] + strargs.extend(map(str, args)) if kwargs.get('abs', 0): - for i in range(len(args)-1, 0, -1): - if os.path.isabs(str(args[i])): - args = map(str, args[i:]) + for i in range(len(strargs)-1, 0, -1): + if os.path.isabs(strargs[i]): + strargs = strargs[i:] break obj = self.new() - obj.strpath = os.path.normpath(self.sep.join(args)) + obj.strpath = os.path.normpath(self.sep.join(strargs)) return obj def __eq__(self, other): From hpk at codespeak.net Fri Dec 17 20:17:38 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 17 Dec 2004 20:17:38 +0100 (MET) Subject: [py-svn] r7909 - py/dist/py/code Message-ID: <20041217191738.F1FAA5A919@thoth.codespeak.net> Author: hpk Date: Fri Dec 17 20:17:38 2004 New Revision: 7909 Added: py/dist/py/code/ py/dist/py/code/__init__.py Log: a new namespace to hold Source, Frame and possibly Traceback abstractions (encapsulating either a CPython object or a PyPy one at some point ...) Added: py/dist/py/code/__init__.py ============================================================================== --- (empty file) +++ py/dist/py/code/__init__.py Fri Dec 17 20:17:38 2004 @@ -0,0 +1 @@ +# From hpk at codespeak.net Fri Dec 17 20:24:32 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 17 Dec 2004 20:24:32 +0100 (MET) Subject: [py-svn] r7910 - in py/dist/py: . code execnet Message-ID: <20041217192432.038A65A919@thoth.codespeak.net> Author: hpk Date: Fri Dec 17 20:24:32 2004 New Revision: 7910 Added: py/dist/py/code/source.py (props changed) - copied unchanged from r7904, py/dist/py/execnet/source.py py/dist/py/code/test_source.py (contents, props changed) - copied, changed from r7904, py/dist/py/execnet/test_source.py Removed: py/dist/py/execnet/source.py py/dist/py/execnet/test_source.py Modified: py/dist/py/__init__.py py/dist/py/code/ (props changed) py/dist/py/code/__init__.py (props changed) py/dist/py/execnet/gateway.py py/dist/py/execnet/test_gateway.py Log: moved the Source class to py.code.Source along with its test Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Fri Dec 17 20:24:32 2004 @@ -45,6 +45,7 @@ 'magic.autopath' : ('./magic/autopath.py', 'autopath'), 'magic.View' : ('./magic/viewtype.py', 'View'), 'magic.AssertionError' : ('./magic/assertion.py', 'AssertionError'), + 'code.Source' : ('./code/source.py', 'Source'), 'execnet.SocketGateway' : ('./execnet/register.py', 'SocketGateway'), 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), }) Copied: py/dist/py/code/test_source.py (from r7904, py/dist/py/execnet/test_source.py) ============================================================================== --- py/dist/py/execnet/test_source.py (original) +++ py/dist/py/code/test_source.py Fri Dec 17 20:24:32 2004 @@ -1,5 +1,5 @@ -from py.__impl__.execnet.source import Source +from py.code import Source def test_source_str_function(): x = Source("3") Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Fri Dec 17 20:24:32 2004 @@ -8,7 +8,7 @@ # XXX the following line should not be here g = globals() if 'Source' not in g: - from py.__impl__.execnet.source import Source + from py.code import Source from py.__impl__.execnet.channel import ChannelFactory, Channel from py.__impl__.execnet.message import Message Deleted: /py/dist/py/execnet/source.py ============================================================================== --- /py/dist/py/execnet/source.py Fri Dec 17 20:24:32 2004 +++ (empty file) @@ -1,57 +0,0 @@ - -class Source(object): - """ a mutable object holding a source code fragment, - automatically deindenting it. - """ - def __init__(self, *parts): - self.lines = lines = [] - for part in parts: - if isinstance(part, Source): - lines.extend(part.lines) - else: - i = part.find('\n') - if i != -1 and part[:i].isspace(): - part = part[i+1:] - part = part.rstrip() - lines.extend(deindent(part)) - - def putaround(self, before='', after=''): - """ return a new source object embedded/indented between before and after. """ - before = Source(before) - after = Source(after) - lines = [' ' + line for line in self.lines] - self.lines = before.lines + lines + after.lines - - def __str__(self): - return "\n".join(self.lines) - -def deindent(pysource): - """ return a list of deindented lines from the given python - source code. The first indentation offset of a non-blank - line determines the deindentation-offset for all the lines. - Subsequent lines which have a lower indentation size will - be copied verbatim as they are assumed to be part of - multilines. - """ - lines = [] - indentsize = 0 - for line in pysource.split('\n'): - if not lines: - if not line.strip(): - continue # skip first empty lines - indentsize = len(line) - len(line.lstrip()) - line = line.expandtabs() - if line.strip(): - if len(line) - len(line.lstrip()) >= indentsize: - line = line[indentsize:] - lines.append(line) - - # treat trailing whitespace-containing lines correctly - # (the python parser at least in python 2.2. is picky about it) - #while lines: - # line = lines.pop() - # if not line.strip(): - # continue - # lines.append(line) - # break - return lines Modified: py/dist/py/execnet/test_gateway.py ============================================================================== --- py/dist/py/execnet/test_gateway.py (original) +++ py/dist/py/execnet/test_gateway.py Fri Dec 17 20:24:32 2004 @@ -1,6 +1,5 @@ import os, sys import py -from py.__impl__.execnet.source import Source from py.__impl__.execnet import gateway mypath = py.magic.autopath() @@ -139,7 +138,7 @@ def setup_class(cls): portrange = (7770, 7800) cls.proxygw = py.execnet.PopenGateway() - socketserverbootstrap = Source( + socketserverbootstrap = py.code.Source( mypath.dirpath('bin', 'startserver.py').read(), """ import socket Deleted: /py/dist/py/execnet/test_source.py ============================================================================== --- /py/dist/py/execnet/test_source.py Fri Dec 17 20:24:32 2004 +++ (empty file) @@ -1,40 +0,0 @@ - -from py.__impl__.execnet.source import Source - -def test_source_str_function(): - x = Source("3") - assert str(x) == "3" - - x = Source(" 3") - assert str(x) == "3" - - x = Source(""" - 3 - """) - assert str(x) == "3" - -def test_source_indent_simple(): - source = Source("raise ValueError") - source.putaround( - "try:", - """ - except ValueError: - x = 42 - else: - x = 23""") - assert str(source)=="""\ -try: - raise ValueError -except ValueError: - x = 42 -else: - x = 23""" - -def XXXtest_source_indent_simple(): - x = StrSource("x=3") - assert not x.isindented() - x.indent("try:", "except: pass") - assert x.read() == "try:\n x=3\nexcept: pass" - - #x.indent("try:", - # """except: From arigo at codespeak.net Fri Dec 17 20:32:43 2004 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 17 Dec 2004 20:32:43 +0100 (MET) Subject: [py-svn] r7911 - in py/dist/py: . code magic Message-ID: <20041217193243.6DA815A919@thoth.codespeak.net> Author: arigo Date: Fri Dec 17 20:32:42 2004 New Revision: 7911 Added: py/dist/py/code/frame.py (contents, props changed) Modified: py/dist/py/__init__.py py/dist/py/magic/exprinfo.py Log: moved RunnerFrame to its own file in the new 'py.code' directory. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Fri Dec 17 20:32:42 2004 @@ -46,6 +46,7 @@ 'magic.View' : ('./magic/viewtype.py', 'View'), 'magic.AssertionError' : ('./magic/assertion.py', 'AssertionError'), 'code.Source' : ('./code/source.py', 'Source'), + 'code.RunnerFrame' : ('./code/frame.py', 'RunnerFrame'), 'execnet.SocketGateway' : ('./execnet/register.py', 'SocketGateway'), 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), }) Added: py/dist/py/code/frame.py ============================================================================== --- (empty file) +++ py/dist/py/code/frame.py Fri Dec 17 20:32:42 2004 @@ -0,0 +1,23 @@ + +class RunnerFrame: + """Wrapper around a Python frame holding f_locals and f_globals + in which expressions can be evaluated.""" + + def __init__(self, frame): + self.f_globals = frame.f_globals + self.f_locals = getattr(frame, 'f_locals', {}) + self.f_code = getattr(frame, 'f_code', None) # for inspection + + def eval(self, code, **vars): + self.f_locals.update(vars) + return eval(code, self.f_globals, self.f_locals) + + def exec_(self, code, **vars): + self.f_locals.update(vars) + exec code in self.f_globals, self.f_locals + + def repr(self, object): + return repr(object) + + def is_true(self, object): + return object Modified: py/dist/py/magic/exprinfo.py ============================================================================== --- py/dist/py/magic/exprinfo.py (original) +++ py/dist/py/magic/exprinfo.py Fri Dec 17 20:32:42 2004 @@ -1,6 +1,7 @@ from compiler import parse, ast, pycodegen from py import magic from py.__impl__.magic import dyncode +from py.code import RunnerFrame import __builtin__, sys passthroughex = (KeyboardInterrupt, SystemExit, MemoryError) @@ -12,29 +13,6 @@ #import traceback #traceback.print_exc() -class RunnerFrame: - """Wrapper around a Python frame holding f_locals and f_globals - in which expressions can be evaluated.""" - - def __init__(self, f_globals, f_locals): - self.f_globals = f_globals - self.f_locals = f_locals - - def eval(self, code, **vars): - self.f_locals.update(vars) - return eval(code, self.f_globals, self.f_locals) - - def exec_(self, code, **vars): - self.f_locals.update(vars) - exec code in self.f_globals, self.f_locals - - def repr(self, object): - return repr(object) - - def is_true(self, object): - return object - - class Interpretable(magic.View): """A parse tree node with a few extra methods.""" explanation = None @@ -390,7 +368,7 @@ if frame is None: import sys frame = sys._getframe(1) - frame = RunnerFrame(frame.f_globals, frame.f_locals) + frame = RunnerFrame(frame) expr = parse(s, 'eval') assert isinstance(expr, ast.Expression) node = Interpretable(expr.node) @@ -425,7 +403,7 @@ def interpret(source, frame): module = Interpretable(parse(source, 'exec').node) #print "got module", module - frame = RunnerFrame(frame.f_globals, frame.f_locals) + frame = RunnerFrame(frame) try: return module.run(frame) # None if no exception generated except Failure, e: @@ -443,13 +421,13 @@ def getmsg((typ, val, tb)): #frame, line = gettbline(tb) - #frame = RunnerFrame(frame.f_globals, frame.f_locals) + #frame = RunnerFrame(frame) #return interpret(line, frame) tb = magic.dyncode.listtb(tb)[-1] source = dyncode.getparseablestartingblock(tb) frame = tb.tb_frame - frame = RunnerFrame(frame.f_globals, frame.f_locals) + frame = RunnerFrame(frame) x = interpret(source, frame) if not isinstance(x, str): raise TypeError, "interpret returned non-string %r" % (x,) @@ -466,7 +444,7 @@ if frame is None: import sys frame = sys._getframe(1) - frame = RunnerFrame(frame.f_globals, frame.f_locals) + frame = RunnerFrame(frame) module = Interpretable(parse(s, 'exec').node) try: module.run(frame) From hpk at codespeak.net Fri Dec 17 20:49:43 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 17 Dec 2004 20:49:43 +0100 (MET) Subject: [py-svn] r7912 - py/dist/py/execnet Message-ID: <20041217194943.0BD2E5A919@thoth.codespeak.net> Author: hpk Date: Fri Dec 17 20:49:43 2004 New Revision: 7912 Modified: py/dist/py/execnet/register.py Log: make PopenGateways a bit more seemless with respect to importing packages across process spaces (basically the other side inherits sys.path from the originating process ) Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Fri Dec 17 20:49:43 2004 @@ -12,7 +12,7 @@ self.remote_bootstrap_gateway(io) gateway.Gateway.__init__(self, io=io, startcount=1) - def remote_bootstrap_gateway(self, io): + def remote_bootstrap_gateway(self, io, extra=''): """ return Gateway with a asynchronously remotely initialized counterpart Gateway (which may or may not succeed). Note that the other sides gateways starts enumerating @@ -21,6 +21,7 @@ uniquely identify channels across both sides. """ bootstrap = [ + extra, inspect.getsource(inputoutput), inspect.getsource(message), inspect.getsource(channel), @@ -38,7 +39,20 @@ infile, outfile = os.popen2(cmd) io = inputoutput.Popen2IO(infile, outfile) InstallableGateway.__init__(self, io=io) - self._pidchannel = self.remote_exec("import os ; channel.send(os.getpid())") + + self._pidchannel = self.remote_exec(""" + import os + channel.send(os.getpid()) + """) + + def remote_bootstrap_gateway(self, io, extra=''): + # XXX the following hack helps us to import the same version + # of the py lib, but only works for PopenGateways + # --> we need proper remote imports working + # across any kind of gateway! + s = "import sys ; sys.path[:] = %r" % (sys.path,) + s = "\n".join([extra, s]) + super(PopenGateway, self).remote_bootstrap_gateway(io, s) def exit(self): if not super(PopenGateway, self).exit(): From hpk at codespeak.net Sat Dec 18 10:41:18 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 18 Dec 2004 10:41:18 +0100 (MET) Subject: [py-svn] r7913 - py/dist/py Message-ID: <20041218094118.93C5E5B344@thoth.codespeak.net> Author: hpk Date: Sat Dec 18 10:41:17 2004 New Revision: 7913 Modified: py/dist/py/initpkg.py Log: added a proper docstring to the small package logic. Modified: py/dist/py/initpkg.py ============================================================================== --- py/dist/py/initpkg.py (original) +++ py/dist/py/initpkg.py Sat Dec 18 10:41:17 2004 @@ -1,5 +1,24 @@ """ -std package initialization +package initialization. + +You use the functionality of this package by putting + + from py.initpkg import initpkg + initpkg(__name__, exportdefs={ + 'name1.name2' : ('./path/to/file.py', 'name') + ... + }) + +into your package's __init__.py file. This will +lead your package to only expose the names of all +your implementation files that you explicitely +specify. In the above example 'name1' will +become a Module instance where 'name2' is +bound in its namespace to the 'name' object +in the relative './path/to/file.py' python module. +Note that you can also use a '.c' file in which +case it will be compiled via distutils-facilities +on the fly. """ from __future__ import generators From arigo at codespeak.net Sat Dec 18 12:00:58 2004 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sat, 18 Dec 2004 12:00:58 +0100 (MET) Subject: [py-svn] r7914 - py/dist/py/magic Message-ID: <20041218110058.92B2B5B344@thoth.codespeak.net> Author: arigo Date: Sat Dec 18 12:00:58 2004 New Revision: 7914 Modified: py/dist/py/magic/exprinfo.py Log: Be more verbose about the result value of built-in functions, unless they return a bool, in which case we assume it's pretty clear which bool they returned from the fact that the assert failed. Modified: py/dist/py/magic/exprinfo.py ============================================================================== --- py/dist/py/magic/exprinfo.py (original) +++ py/dist/py/magic/exprinfo.py Sat Dec 18 12:00:58 2004 @@ -221,6 +221,15 @@ class CallFunc(Interpretable): __view__ = ast.CallFunc + def is_bool(self, frame): + co = compile('isinstance(__exprinfo_value, bool)', '?', 'eval') + try: + return frame.is_true(frame.eval(co, __exprinfo_value=self.result)) + except passthroughex: + raise + except: + return False + def eval(self, frame): node = Interpretable(self.node) node.eval(frame) @@ -260,7 +269,7 @@ raise except: raise Failure(self) - if not node.is_builtin(frame): + if not node.is_builtin(frame) or not self.is_bool(frame): r = frame.repr(self.result) self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation) From hpk at codespeak.net Sat Dec 18 12:24:21 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 18 Dec 2004 12:24:21 +0100 (MET) Subject: [py-svn] r7915 - py/dist/py/code Message-ID: <20041218112421.4A9B75B344@thoth.codespeak.net> Author: hpk Date: Sat Dec 18 12:24:20 2004 New Revision: 7915 Modified: py/dist/py/code/source.py py/dist/py/code/test_source.py Log: some more refactoring of the Source object, holding sourcecode lines. Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Sat Dec 18 12:24:20 2004 @@ -1,4 +1,3 @@ - class Source(object): """ a mutable object holding a source code fragment, automatically deindenting it. @@ -15,12 +14,85 @@ part = part.rstrip() lines.extend(deindent(part)) + def __getitem__(self, key): + if isinstance(key, slice): + newsource = Source() + newsource.lines = self.lines[key] + return newsource + else: + return self.lines[key] + def putaround(self, before='', after=''): - """ return a new source object embedded/indented between before and after. """ + """ modifies a new source object embedded/indented + between before and after. + """ before = Source(before) after = Source(after) + newsource = Source() lines = [' ' + line for line in self.lines] - self.lines = before.lines + lines + after.lines + newsource.lines = before.lines + lines + after.lines + return newsource + + def getsource_tryparsing(self, lineno_hint): + # the famous try-parsing technique is back! :-) + # first we go forward, afterwards we try parsing backwards, i don't + # see how we could do better ... given that a multiline assert + # statement has its frame.f_lineno set to the first line, while + # 'A(x,\n y)' will have its frame.f_lineno set to the second line + for end in range(lineno_hint, len(self)): + trysource = self[:end+1] + if trysource.isparseable(): + break + else: + return None + for start in range(end, -1, -1): + trysource = self[start:end+1] + if trysource.isparseable(): + return trysource + + def deindent(self, offset=None): + """ return new source deindented by offset. If offset is + None then guess an indentation offset from the + first non-blank line. Subsequent lines which have a + lower indentation offset will be copied verbatim as + they are assumed to be part of multilines. + """ + # XXX maybe use the tokenizer to properly handle multiline + # strings etc.pp? + if offset is None: + for line in self.lines: + s = line.lstrip() + if s: + offset = len(line)-len(s) + break + else: + offset = 0 + newlines = [] + for line in self.lines: + #line = line.expandtabs() + s = line.lstrip() + if s and line[:offset].isspace(): + line = line[offset:] + newlines.append(line) + newsource = Source() + newsource.lines[:] = newlines + return newsource + + def __len__(self): + return len(self.lines) + + def isparseable(self, deindent=True): + import parser + if deindent: + source = str(self.deindent()) + else: + source = str(self) + try: + parser.suite(source) + except (parser.ParserError, SyntaxError): + return False + else: + return True def __str__(self): return "\n".join(self.lines) @@ -33,6 +105,8 @@ be copied verbatim as they are assumed to be part of multilines. """ + # XXX maybe use the tokenizer to properly handle multiline + # strings etc.pp? lines = [] indentsize = 0 for line in pysource.split('\n'): @@ -52,6 +126,9 @@ # line = lines.pop() # if not line.strip(): # continue + # if not line.endswith('\n'): + # line = line + '\n' # lines.append(line) # break return lines + Modified: py/dist/py/code/test_source.py ============================================================================== --- py/dist/py/code/test_source.py (original) +++ py/dist/py/code/test_source.py Sat Dec 18 12:24:20 2004 @@ -15,7 +15,7 @@ def test_source_indent_simple(): source = Source("raise ValueError") - source.putaround( + source = source.putaround( "try:", """ except ValueError: @@ -29,7 +29,49 @@ x = 42 else: x = 23""" - + +def checkparseable(source): + assert source.isparseable() + +def test_isparseable(): + assert Source("hello").isparseable() + assert Source("if 1:\n pass").isparseable() + assert Source(" \nif 1:\n pass").isparseable() + assert not Source(" \nif 1:\npass").isparseable() + +class TestSliceAccess: + source = Source(""" + def f(x): + pass + def g(x): + pass + """) + def test_getrange(self): + x = self.source[0:2] + assert x.isparseable() + assert len(x.lines) == 2 + assert str(x) == "def f(x):\n pass" + + def test_getline(self): + x = self.source[0] + assert x == "def f(x):" + +class TestParsing: + source = Source(""" + def f(x): + assert (x == + 3 and + 4) + """) + def test_getsource_tryparsing(self): + print str(self.source) + ass = str(self.source[1:]) + for i in range(1, 4): + print "trying start in line %r" % self.source[i] + s = self.source.getsource_tryparsing(i) + #x = s.deindent() + assert str(s) == ass + def XXXtest_source_indent_simple(): x = StrSource("x=3") assert not x.isindented() From hpk at codespeak.net Sat Dec 18 12:48:43 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 18 Dec 2004 12:48:43 +0100 (MET) Subject: [py-svn] r7916 - py/dist/py/code Message-ID: <20041218114843.DC3DF5B344@thoth.codespeak.net> Author: hpk Date: Sat Dec 18 12:48:43 2004 New Revision: 7916 Added: py/dist/py/code/test_frame.py Modified: py/dist/py/code/frame.py Log: some support code for nice introspection of source code. Modified: py/dist/py/code/frame.py ============================================================================== --- py/dist/py/code/frame.py (original) +++ py/dist/py/code/frame.py Sat Dec 18 12:48:43 2004 @@ -1,12 +1,28 @@ +import py -class RunnerFrame: +class InspectableFrame(object): + def __init__(self, f_code, f_lineno): + self.f_code = f_code + self.f_lineno = f_lineno + + def getsourcelineno(self): + fn = self.f_code.co_filename + if hasattr(fn, '__source__'): + source = fn.__source__ + elif hasattr(fn, '__path__'): + source = fn.__path__.read() + else: + source = py.path.local(fn).read() + return py.code.Source(source), self.f_lineno-1 + +class RunnerFrame(InspectableFrame): """Wrapper around a Python frame holding f_locals and f_globals in which expressions can be evaluated.""" def __init__(self, frame): self.f_globals = frame.f_globals - self.f_locals = getattr(frame, 'f_locals', {}) - self.f_code = getattr(frame, 'f_code', None) # for inspection + self.f_locals = frame.f_locals + super(RunnerFrame, self).__init__(frame.f_code, frame.f_lineno) def eval(self, code, **vars): self.f_locals.update(vars) @@ -21,3 +37,5 @@ def is_true(self, object): return object + + Added: py/dist/py/code/test_frame.py ============================================================================== --- (empty file) +++ py/dist/py/code/test_frame.py Sat Dec 18 12:48:43 2004 @@ -0,0 +1,10 @@ +import sys +from py.code import RunnerFrame + +def test_frame_getsourcelineno_myself(): + def func(): + return sys._getframe(0) + f = func() + r = RunnerFrame(f) + source, lineno = r.getsourcelineno() + assert source[lineno].startswith(" return sys._getframe(0)") From hpk at codespeak.net Sat Dec 18 15:00:16 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 18 Dec 2004 15:00:16 +0100 (MET) Subject: [py-svn] r7917 - py/dist/py/magic Message-ID: <20041218140016.DE1A55B344@thoth.codespeak.net> Author: hpk Date: Sat Dec 18 15:00:16 2004 New Revision: 7917 Modified: py/dist/py/magic/test_exprinfo.py Log: fixed a test to work with the slightly enriched RunnerFrame ... Modified: py/dist/py/magic/test_exprinfo.py ============================================================================== --- py/dist/py/magic/test_exprinfo.py (original) +++ py/dist/py/magic/test_exprinfo.py Sat Dec 18 15:00:16 2004 @@ -62,9 +62,14 @@ # ... but it apparently currently does and it's nice to # exercise the code because the exprinfo-machinery is # not much executed when all tests pass ... - + + class DummyCode: + co_filename = 'dummy' class DummyFrame: f_globals = f_locals = {} + f_code = DummyCode + f_lineno = 0 + for exstr in "SystemExit", "KeyboardInterrupt", "MemoryError": ex = eval(exstr) try: From hpk at codespeak.net Sat Dec 18 15:30:31 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 18 Dec 2004 15:30:31 +0100 (MET) Subject: [py-svn] r7918 - py/dist/py/code Message-ID: <20041218143031.68ABE5B344@thoth.codespeak.net> Author: hpk Date: Sat Dec 18 15:30:30 2004 New Revision: 7918 Added: py/dist/py/code/test_cpython_features.py Log: some features explicitely tested for that might change during python versions ... Added: py/dist/py/code/test_cpython_features.py ============================================================================== --- (empty file) +++ py/dist/py/code/test_cpython_features.py Sat Dec 18 15:30:30 2004 @@ -0,0 +1,16 @@ + +import new + +def test_compile_passes_on_filename(): + class mystr(str): + pass + filename = mystr("dummy") + co = compile("hello\n", filename, 'exec') + assert not isinstance(co.co_filename, mystr) + c2 = new.code(co.co_argcount, co.co_nlocals, co.co_stacksize, + co.co_flags, co.co_code, co.co_consts, + co.co_names, co.co_varnames, + filename, + co.co_name, co.co_firstlineno, co.co_lnotab, + co.co_freevars, co.co_cellvars) + assert c2.co_filename is filename From hpk at codespeak.net Sat Dec 18 15:33:10 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 18 Dec 2004 15:33:10 +0100 (MET) Subject: [py-svn] r7919 - py/dist/py/code Message-ID: <20041218143310.319285B344@thoth.codespeak.net> Author: hpk Date: Sat Dec 18 15:33:09 2004 New Revision: 7919 Modified: py/dist/py/code/test_cpython_features.py Log: better name for the test Modified: py/dist/py/code/test_cpython_features.py ============================================================================== --- py/dist/py/code/test_cpython_features.py (original) +++ py/dist/py/code/test_cpython_features.py Sat Dec 18 15:33:09 2004 @@ -1,7 +1,7 @@ import new -def test_compile_passes_on_filename(): +def test_new_code_object_carries_filename_through(): class mystr(str): pass filename = mystr("dummy") From arigo at codespeak.net Sat Dec 18 18:39:31 2004 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sat, 18 Dec 2004 18:39:31 +0100 (MET) Subject: [py-svn] r7921 - py/dist/py/magic Message-ID: <20041218173931.EC6635AA06@thoth.codespeak.net> Author: arigo Date: Sat Dec 18 18:39:31 2004 New Revision: 7921 Modified: py/dist/py/magic/test_exprinfo.py Log: Added tests for nested scopes in magic.exprinfo, which just happened to work fine already. Modified: py/dist/py/magic/test_exprinfo.py ============================================================================== --- py/dist/py/magic/test_exprinfo.py (original) +++ py/dist/py/magic/test_exprinfo.py Sat Dec 18 18:39:31 2004 @@ -22,6 +22,26 @@ msg = getmsg(excinfo) assert msg == 'AssertionError: assert 1 == 2' +def test_nested_scopes(): + def g(): + a = 1 + def h(): + return a + b = 2 + assert h() == b + excinfo = getexcinfo(AssertionError, g) + msg = getmsg(excinfo) + assert msg.startswith('AssertionError: assert 1 == 2\n + where 1 = ') + +def test_nested_scopes_2(): + a = 1 + def g(): + b = 2 + assert a == b + excinfo = getexcinfo(AssertionError, g) + msg = getmsg(excinfo) + assert msg == 'AssertionError: assert 1 == 2' + def test_assert_func_argument_type_error(): def f (): pass From hpk at codespeak.net Sat Dec 18 18:41:09 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 18 Dec 2004 18:41:09 +0100 (MET) Subject: [py-svn] r7922 - py/dist/py/code Message-ID: <20041218174109.BA0CC5AA06@thoth.codespeak.net> Author: hpk Date: Sat Dec 18 18:41:09 2004 New Revision: 7922 Modified: py/dist/py/code/frame.py py/dist/py/code/source.py py/dist/py/code/test_source.py Log: support for compiling from a given source object, preserving the original Source instance on co_filename.__source__. Getting code objects to carry this new subclass-of-string co_filename requires creating new code objects recursively (Armin's suggestion). We think it's worth it because this technqiue let's us get rid of dyncode's global "magicfilename->source" registry. Modified: py/dist/py/code/frame.py ============================================================================== --- py/dist/py/code/frame.py (original) +++ py/dist/py/code/frame.py Sat Dec 18 18:41:09 2004 @@ -9,8 +9,8 @@ fn = self.f_code.co_filename if hasattr(fn, '__source__'): source = fn.__source__ - elif hasattr(fn, '__path__'): - source = fn.__path__.read() + #elif hasattr(fn, '__path__'): + # source = fn.__path__.read() else: source = py.path.local(fn).read() return py.code.Source(source), self.f_lineno-1 Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Sat Dec 18 18:41:09 2004 @@ -1,8 +1,11 @@ +from __future__ import generators +import sys + class Source(object): """ a mutable object holding a source code fragment, automatically deindenting it. """ - def __init__(self, *parts): + def __init__(self, *parts, **kwargs): self.lines = lines = [] for part in parts: if isinstance(part, Source): @@ -12,7 +15,10 @@ if i != -1 and part[:i].isspace(): part = part[i+1:] part = part.rstrip() - lines.extend(deindent(part)) + partlines = part.split('\n') + if kwargs.get('deindent', True): + partlines = deindent(partlines) + lines.extend(partlines) def __getitem__(self, key): if isinstance(key, slice): @@ -33,55 +39,43 @@ newsource.lines = before.lines + lines + after.lines return newsource - def getsource_tryparsing(self, lineno_hint): - # the famous try-parsing technique is back! :-) - # first we go forward, afterwards we try parsing backwards, i don't - # see how we could do better ... given that a multiline assert - # statement has its frame.f_lineno set to the first line, while - # 'A(x,\n y)' will have its frame.f_lineno set to the second line - for end in range(lineno_hint, len(self)): + def getblock(self, lineno): + """ return Source block which contains the + given linenumber (counted from 0). + """ + # find the end + for end in range(lineno, len(self)): trysource = self[:end+1] if trysource.isparseable(): break else: return None + # find the start for start in range(end, -1, -1): trysource = self[start:end+1] if trysource.isparseable(): return trysource def deindent(self, offset=None): - """ return new source deindented by offset. If offset is - None then guess an indentation offset from the - first non-blank line. Subsequent lines which have a + """ return a new source object deindented by offset. + If offset is None then guess an indentation offset from + the first non-blank line. Subsequent lines which have a lower indentation offset will be copied verbatim as they are assumed to be part of multilines. """ # XXX maybe use the tokenizer to properly handle multiline # strings etc.pp? - if offset is None: - for line in self.lines: - s = line.lstrip() - if s: - offset = len(line)-len(s) - break - else: - offset = 0 - newlines = [] - for line in self.lines: - #line = line.expandtabs() - s = line.lstrip() - if s and line[:offset].isspace(): - line = line[offset:] - newlines.append(line) newsource = Source() - newsource.lines[:] = newlines + newsource.lines[:] = deindent(self.lines, offset) return newsource - + def __len__(self): return len(self.lines) def isparseable(self, deindent=True): + """ return True if source is parseable, heuristically + deindenting it by default. + """ import parser if deindent: source = str(self.deindent()) @@ -97,38 +91,91 @@ def __str__(self): return "\n".join(self.lines) -def deindent(pysource): - """ return a list of deindented lines from the given python - source code. The first indentation offset of a non-blank - line determines the deindentation-offset for all the lines. - Subsequent lines which have a lower indentation size will - be copied verbatim as they are assumed to be part of - multilines. - """ + def compile(self, filename=None, mode='exec', + flag=generators.compiler_flag): + """ return compiled code object. if filename is None + invent an artificial filename which displays + the source/line position of the caller frame. + """ + if filename is None: + frame = sys._getframe(1) # the caller + filename = '<%s:%d>' % (frame.f_code.co_filename, + frame.f_lineno) + source = str(self)+'\n' + try: + co = compile(source, filename, mode, flag) + except SyntaxError, ex: + # re-represent syntax errors from parsing python strings + newex = SyntaxError('\n'.join([ + "".join(lines[:ex.lineno]), + " " * ex.offset + '^', + "syntax error probably generated here: %s" % origin])) + newex.offset = ex.offset + newex.lineno = ex.lineno + newex.text = ex.text + raise newex + else: + co_filename = MyStr(filename) + co_filename.__source__ = self + return newcode_withfilename(co, co_filename) + +class MyStr(str): + """ custom string which allows to add attributes. """ + +def deindent(lines, offset=None): # XXX maybe use the tokenizer to properly handle multiline # strings etc.pp? - lines = [] - indentsize = 0 - for line in pysource.split('\n'): - if not lines: - if not line.strip(): - continue # skip first empty lines - indentsize = len(line) - len(line.lstrip()) - line = line.expandtabs() - if line.strip(): - if len(line) - len(line.lstrip()) >= indentsize: - line = line[indentsize:] - lines.append(line) - - # treat trailing whitespace-containing lines correctly - # (the python parser at least in python 2.2. is picky about it) - #while lines: - # line = lines.pop() - # if not line.strip(): - # continue - # if not line.endswith('\n'): - # line = line + '\n' - # lines.append(line) - # break - return lines - + if offset is None: + for line in lines: + s = line.lstrip() + if s: + offset = len(line)-len(s) + break + else: + offset = 0 + newlines = [] + for line in lines: + #line = line.expandtabs() + s = line.lstrip() + if s: + if line[:offset].isspace(): + line = line[offset:] + else: + if not newlines: # skipt trailing blank lines + continue + newlines.append(line) + return newlines + +def newcode(fromcode, **kwargs): + names = [x for x in dir(fromcode) if x[:3] == 'co_'] + for name in names: + if name not in kwargs: + kwargs[name] = getattr(fromcode, name) + import new + return new.code( + kwargs['co_argcount'], + kwargs['co_nlocals'], + kwargs['co_stacksize'], + kwargs['co_flags'], + kwargs['co_code'], + kwargs['co_consts'], + kwargs['co_names'], + kwargs['co_varnames'], + kwargs['co_filename'], + kwargs['co_name'], + kwargs['co_firstlineno'], + kwargs['co_lnotab'], + kwargs['co_freevars'], + kwargs['co_cellvars'], + ) + +def newcode_withfilename(co, co_filename): + newconstlist = [] + cotype = type(co) + for c in co.co_consts: + if isinstance(c, cotype): + c = newcode_withfilename(c, co_filename) + newconstlist.append(c) + return newcode(co, co_consts = tuple(newconstlist), + co_filename = co_filename) + Modified: py/dist/py/code/test_source.py ============================================================================== --- py/dist/py/code/test_source.py (original) +++ py/dist/py/code/test_source.py Sat Dec 18 18:41:09 2004 @@ -1,5 +1,5 @@ - from py.code import Source +import py def test_source_str_function(): x = Source("3") @@ -56,21 +56,67 @@ x = self.source[0] assert x == "def f(x):" -class TestParsing: +class TestCodeHacks: + def test_newcode(self): + from py.__impl__.code.source import newcode + def f(): + pass + co_filename = 'hello' + c = newcode(f.func_code, co_filename=co_filename) + assert c.co_filename is co_filename + + def test_newcode_withfilename(self): + from py.__impl__.code.source import newcode_withfilename + source = py.code.Source(""" + def f(): + def g(): + pass + """) + co = compile(str(source)+'\n', 'nada', 'exec') + obj = 'hello' + newco = newcode_withfilename(co, obj) + def walkcode(co): + for x in co.co_consts: + if isinstance(x, type(co)): + for y in walkcode(x): + yield y + yield co + + names = [] + for code in walkcode(newco): + assert newco.co_filename == obj + assert newco.co_filename is obj + names.append(code.co_name) + assert 'f' in names + assert 'g' in names + +class TestParsingAndCompiling: source = Source(""" def f(x): assert (x == - 3 and + 3 + 4) """) - def test_getsource_tryparsing(self): - print str(self.source) + def test_getblock(self): + #print str(self.source) ass = str(self.source[1:]) for i in range(1, 4): - print "trying start in line %r" % self.source[i] - s = self.source.getsource_tryparsing(i) + #print "trying start in line %r" % self.source[i] + s = self.source.getblock(i) #x = s.deindent() assert str(s) == ass + + def test_compile_and_getsource(self): + co = self.source.compile() + exec co + f(7) + excinfo = py.test.raises(AssertionError, "f(6)") + frame = excinfo[2].tb_next.tb_next.tb_frame + f = py.code.RunnerFrame(frame) + source, lineno = f.getsourcelineno() + block = source.getblock(lineno) + #print "block", str(block) + assert str(block).strip().startswith('assert') def XXXtest_source_indent_simple(): x = StrSource("x=3") From hpk at codespeak.net Sat Dec 18 22:37:53 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 18 Dec 2004 22:37:53 +0100 (MET) Subject: [py-svn] r7923 - py/dist/py/code Message-ID: <20041218213753.952105AA84@thoth.codespeak.net> Author: hpk Date: Sat Dec 18 22:37:53 2004 New Revision: 7923 Modified: py/dist/py/code/source.py py/dist/py/code/test_source.py Log: public "compile/getsource" API, i.e. i added py.code.compile(sourcestring, mode="exec", flags=generator.compiler_flags) which will compile the sourcestring to an (almost) ordinary code object, which when you apply the new py.code.getsource(codeobject) to it will yield a Source instance holding the original sourcestring. Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Sat Dec 18 22:37:53 2004 @@ -39,8 +39,8 @@ newsource.lines = before.lines + lines + after.lines return newsource - def getblock(self, lineno): - """ return Source block which contains the + def getstatement(self, lineno): + """ return Source statement which contains the given linenumber (counted from 0). """ # find the end @@ -118,7 +118,33 @@ co_filename = MyStr(filename) co_filename.__source__ = self return newcode_withfilename(co, co_filename) - +# +# public API shortcut functions +# + +def compile_(source, filename=None, mode='exec', flags= + generators.compiler_flag): + """ compile the given source to a code object, + which points back to the source code through + "co_filename.__source__". All code objects + contained in the code object will also have + this special subclass-of-string filename. + """ + s = Source(source) + co = s.compile(filename, mode, flags) + return co + +def getsource(obj): + if hasattr(obj, 'f_code'): + obj = obj.f_code + try: + return obj.co_filename.__source__ + except AttributeError: + return Source(py.std.inspect.getsource(obj)) + +# +# various helper functions +# class MyStr(str): """ custom string which allows to add attributes. """ Modified: py/dist/py/code/test_source.py ============================================================================== --- py/dist/py/code/test_source.py (original) +++ py/dist/py/code/test_source.py Sat Dec 18 22:37:53 2004 @@ -90,19 +90,19 @@ assert 'f' in names assert 'g' in names -class TestParsingAndCompiling: +class TestSourceParsingAndCompiling: source = Source(""" def f(x): assert (x == 3 + 4) """) - def test_getblock(self): + def test_getstatement(self): #print str(self.source) ass = str(self.source[1:]) for i in range(1, 4): #print "trying start in line %r" % self.source[i] - s = self.source.getblock(i) + s = self.source.getstatement(i) #x = s.deindent() assert str(s) == ass @@ -114,15 +114,17 @@ frame = excinfo[2].tb_next.tb_next.tb_frame f = py.code.RunnerFrame(frame) source, lineno = f.getsourcelineno() - block = source.getblock(lineno) + block = source.getstatement(lineno) #print "block", str(block) assert str(block).strip().startswith('assert') - -def XXXtest_source_indent_simple(): - x = StrSource("x=3") - assert not x.isindented() - x.indent("try:", "except: pass") - assert x.read() == "try:\n x=3\nexcept: pass" - #x.indent("try:", - # """except: +def test_compile(): + co = py.code.compile("x=3") + exec co + assert x == 3 + +def test_compile_and_getsource(): + co = py.code.compile("x=3") + exec co + source = py.code.getsource(co) + assert str(source) == "x=3" From hpk at codespeak.net Sat Dec 18 22:39:15 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 18 Dec 2004 22:39:15 +0100 (MET) Subject: [py-svn] r7924 - in py/dist/py: . path/local Message-ID: <20041218213915.D0E415AA84@thoth.codespeak.net> Author: hpk Date: Sat Dec 18 22:39:15 2004 New Revision: 7924 Modified: py/dist/py/__init__.py py/dist/py/path/local/local.py Log: umm, made the compile/getsource actually available and improved some docstrings of the local path implemenation. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Sat Dec 18 22:39:15 2004 @@ -47,6 +47,8 @@ 'magic.AssertionError' : ('./magic/assertion.py', 'AssertionError'), 'code.Source' : ('./code/source.py', 'Source'), 'code.RunnerFrame' : ('./code/frame.py', 'RunnerFrame'), + 'code.compile' : ('./code/source.py', 'compile_'), + 'code.getsource' : ('./code/source.py', 'getsource'), 'execnet.SocketGateway' : ('./execnet/register.py', 'SocketGateway'), 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), }) Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sat Dec 18 22:39:15 2004 @@ -16,8 +16,8 @@ from py.__impl__.path.local.posix import PosixMixin as PlatformMixin class LocalPath(common.FSPathBase, PlatformMixin): - """ the fully specialized local path implementation. - (including symbolic links and all kinds of stuff.) + """ Local path implementation offering access/modification + methods similar to os.path. """ sep = os.sep class Checkers(common.FSCheckers): @@ -398,7 +398,7 @@ """ return a path object found by looking at the systems underlying PATH specification. If the checker is not None it will be invoked to filter matching paths. - Note: This is not working on plain win32 systems yet. + Note: This is probably not working on plain win32 systems yet. """ for x in py.std.os.environ['PATH'].split(':'): p = py.path.local(x).join(name) @@ -413,7 +413,9 @@ #special class constructors for local filesystem paths #""" def get_temproot(cls): - """ return the system's temporary directory (where tempfiles are usually created in)""" + """ return the system's temporary directory + (where tempfiles are usually created in) + """ p = cls.mkdtemp() try: return p.dirpath() @@ -423,7 +425,7 @@ def mkdtemp(cls): """ return a Path object pointing to a fresh new temporary directory - (which we created ourself). + (which we created ourself). """ import tempfile tries = 10 From hpk at codespeak.net Sun Dec 19 13:29:54 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 19 Dec 2004 13:29:54 +0100 (MET) Subject: [py-svn] r7925 - py/dist/py/bin Message-ID: <20041219122954.BEE7C5B344@thoth.codespeak.net> Author: hpk Date: Sun Dec 19 13:29:53 2004 New Revision: 7925 Added: py/dist/py/bin/py.lookup (contents, props changed) Log: small hands-on script for looking up strings in .py files recursively (from the current directory) Added: py/dist/py/bin/py.lookup ============================================================================== --- (empty file) +++ py/dist/py/bin/py.lookup Sun Dec 19 13:29:53 2004 @@ -0,0 +1,14 @@ +#!/usr/bin/python + +from _findpy import py +import re + +string = py.std.sys.argv[1] +curdir = py.path.local() +for x in curdir.visit('*.py', py.path.checker(dotfile=0)): + s = x.read() + if s.find(string) != -1: + lines = x.readlines() + for i, line in enumerate(lines): + if line.find(string) != -1: + print "%s:%d %s" %(x.relto(curdir), i+1, line.rstrip()) From hpk at codespeak.net Sun Dec 19 14:01:30 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 19 Dec 2004 14:01:30 +0100 (MET) Subject: [py-svn] r7926 - in py/dist: py/bin tool Message-ID: <20041219130130.F3BA55B344@thoth.codespeak.net> Author: hpk Date: Sun Dec 19 14:01:30 2004 New Revision: 7926 Added: py/dist/py/bin/py.rest - copied, changed from r7910, py/dist/tool/rest.py Removed: py/dist/tool/ Log: moved the rest-little command line tool to the common py.* tools directory (py/bin) with some minor adjustments. Copied: py/dist/py/bin/py.rest (from r7910, py/dist/tool/rest.py) ============================================================================== --- py/dist/tool/rest.py (original) +++ py/dist/py/bin/py.rest Sun Dec 19 14:01:30 2004 @@ -1,5 +1,4 @@ #!/usr/bin/python - """ invoke @@ -11,6 +10,8 @@ """ +from _findpy import py + import sys, os, traceback if os.isatty(sys.stdout.fileno()): @@ -66,9 +67,6 @@ basedir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))) sys.path.insert(0, basedir) - import py - from py import path - if len(sys.argv) == 1: filenames = [py.path.svnwc()] else: From hpk at codespeak.net Sun Dec 19 16:10:54 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 19 Dec 2004 16:10:54 +0100 (MET) Subject: [py-svn] r7927 - in py/dist/py: . code magic Message-ID: <20041219151054.9DC8A5B344@thoth.codespeak.net> Author: hpk Date: Sun Dec 19 16:10:53 2004 New Revision: 7927 Added: py/dist/py/code/excinfo.py py/dist/py/code/test_excinfo.py Modified: py/dist/py/__init__.py py/dist/py/code/frame.py py/dist/py/code/source.py py/dist/py/code/test_frame.py py/dist/py/code/test_source.py py/dist/py/magic/exprinfo.py Log: major py.code.* refactoring towards a higher-level introspection model for python frames, traceback, code and "Source" objects. py.code.Source and py.code.ExceptionInfo() are excellent starting points to do uniform convenient navigation and access of python's internal more lower level introspection model. They offer just enough abstraction to allow working with dynamic code and code being stored at remote sides. The latter is not fully tested and probably there are some small adaptions needed here and there when we get to it. Now i soon need to write some documentation i guess ... Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Sun Dec 19 16:10:53 2004 @@ -12,14 +12,18 @@ 'test.collect.Directory' : ('./test/collect.py', 'Directory'), 'test.collect.Collector' : ('./test/collect.py', 'Collector'), 'test.collect.Class' : ('./test/collect.py', 'Class'), - 'test.TextReporter' : ('./test/report/text/reporter.py', 'TextReporter'), + 'test.TextReporter' : ('./test/report/text/reporter.py', + 'TextReporter'), 'test.Option' : ('./test/tool/optparse.py', 'Option'), 'test.MemoReporter' : ('./test/report/memo.py', 'MemoReporter'), 'test.Item' : ('./test/item.py', 'Item'), 'test.GenItem' : ('./test/item.py', 'GenItem'), 'test.Driver' : ('./test/drive.py', 'Driver'), + 'std' : ('./misc/std.py', 'std'), + 'process.cmdexec' : ('./process/cmdexec.py', 'cmdexec'), + 'path.svnwc' : ('./path/svn/wccommand.py', 'SvnWCCommandPath'), 'path.svnurl' : ('./path/svn/urlcommand.py', 'SvnCommandPath'), 'path.local' : ('./path/local/local.py', 'LocalPath'), @@ -29,6 +33,7 @@ 'path.NoDirectory' : ('./path/error.py', 'NoDirectory'), 'path.Invalid' : ('./path/error.py', 'Invalid'), 'path.Denied' : ('./path/error.py', 'PermissionDenied'), + 'magic.revoke' : ('./magic/invoke.py', 'revoke'), 'magic.revert' : ('./magic/patch.py', 'revert'), 'magic.patch' : ('./magic/patch.py', 'patch'), @@ -45,10 +50,12 @@ 'magic.autopath' : ('./magic/autopath.py', 'autopath'), 'magic.View' : ('./magic/viewtype.py', 'View'), 'magic.AssertionError' : ('./magic/assertion.py', 'AssertionError'), + + 'code.compile' : ('./code/source.py', 'compile_'), 'code.Source' : ('./code/source.py', 'Source'), 'code.RunnerFrame' : ('./code/frame.py', 'RunnerFrame'), - 'code.compile' : ('./code/source.py', 'compile_'), - 'code.getsource' : ('./code/source.py', 'getsource'), + 'code.ExceptionInfo' : ('./code/excinfo.py', 'ExceptionInfo'), + 'execnet.SocketGateway' : ('./execnet/register.py', 'SocketGateway'), 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), }) Added: py/dist/py/code/excinfo.py ============================================================================== --- (empty file) +++ py/dist/py/code/excinfo.py Sun Dec 19 16:10:53 2004 @@ -0,0 +1,35 @@ +import sys +import py + +class ExceptionInfo(object): + """ wraps sys.exc_info() objects and offers + help for navigating the traceback. + """ + def __init__(self, tup = None): + if tup is None: + tup = sys.exc_info() + assert tup, "couldn't get at exception info!" + self.type, self.value, self.tb = tup + + def __iter__(self): + current = self.tb + while current: + yield TracebackEntry(current, self) + current = current.tb_next + +class TracebackEntry(object): + def __init__(self, rawentry, excinfo): + self._rawentry = rawentry + self._excinfo = excinfo + self.frame = py.code.RunnerFrame(rawentry.tb_frame) + self.lineno = rawentry.tb_lineno - 1 + + def statement(self): + source = self.frame.code.fullsource + return source.getstatement(self.lineno) + statement = property(statement, None, None, + "statement of this traceback entry.") + + def path(self): + return self.frame.path + path = property(path, None, None, "path to the full source code") Modified: py/dist/py/code/frame.py ============================================================================== --- py/dist/py/code/frame.py (original) +++ py/dist/py/code/frame.py Sun Dec 19 16:10:53 2004 @@ -1,19 +1,33 @@ import py +class Code(object): + def __init__(self, f_code): + self.raw = f_code + self.firstlineno = f_code.co_firstlineno - 1 + + def path(self): + return getattr(self.raw.co_filename, '__path__', + py.path.local(self.raw.co_filename)) + path = property(path, None, None, "path of this code object") + + def fullsource(self): + fn = self.raw.co_filename + try: + return fn.__source__ + except AttributeError: + return py.code.Source(self.path.read()) + fullsource = property(fullsource, None, None, + "full source containing this code object") + class InspectableFrame(object): def __init__(self, f_code, f_lineno): - self.f_code = f_code - self.f_lineno = f_lineno + self.code = Code(f_code) + self.lineno = f_lineno - 1 - def getsourcelineno(self): - fn = self.f_code.co_filename - if hasattr(fn, '__source__'): - source = fn.__source__ - #elif hasattr(fn, '__path__'): - # source = fn.__path__.read() - else: - source = py.path.local(fn).read() - return py.code.Source(source), self.f_lineno-1 + def statement(self): + return self.code.fullsource.getstatement(self.lineno) + statement = property(statement, None, None, + "statement this frame is at") class RunnerFrame(InspectableFrame): """Wrapper around a Python frame holding f_locals and f_globals Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Sun Dec 19 16:10:53 2004 @@ -10,15 +10,13 @@ for part in parts: if isinstance(part, Source): lines.extend(part.lines) - else: - i = part.find('\n') - if i != -1 and part[:i].isspace(): - part = part[i+1:] - part = part.rstrip() + elif isinstance(part, str): partlines = part.split('\n') if kwargs.get('deindent', True): partlines = deindent(partlines) lines.extend(partlines) + else: + lines.extend(getsource(part).lines) def __getitem__(self, key): if isinstance(key, slice): @@ -28,6 +26,20 @@ else: return self.lines[key] + def strip(self): + """ return new source object with trailing + and leading blank lines removed. + """ + for start in range(0, len(self)): + if not self.lines[start].isspace(): + break + for end in range(len(self)-1, -1, -1): + if not self.lines[end].isspace(): + break + source = Source() + source.lines[:] = self.lines[start:end+1] + return source + def putaround(self, before='', after=''): """ modifies a new source object embedded/indented between before and after. @@ -140,7 +152,9 @@ try: return obj.co_filename.__source__ except AttributeError: - return Source(py.std.inspect.getsource(obj)) + strsrc = py.std.inspect.getsource(obj) + assert isinstance(strsrc, str) + return Source(strsrc) # # various helper functions @@ -163,12 +177,8 @@ for line in lines: #line = line.expandtabs() s = line.lstrip() - if s: - if line[:offset].isspace(): - line = line[offset:] - else: - if not newlines: # skipt trailing blank lines - continue + if s and line[:offset].isspace(): + line = line[offset:] newlines.append(line) return newlines Added: py/dist/py/code/test_excinfo.py ============================================================================== --- (empty file) +++ py/dist/py/code/test_excinfo.py Sun Dec 19 16:10:53 2004 @@ -0,0 +1,29 @@ +import py +mypath = py.magic.autopath() + +def test_excinfo_simple(): + try: + raise ValueError + except ValueError: + info = py.code.ExceptionInfo() + assert info.type == ValueError + +def test_excinfo_getstatement(): + def g(): + raise ValueError + def f(): + g() + try: + f() + except ValueError: + info = py.code.ExceptionInfo() + l = list(info) + linenumbers = [f.func_code.co_firstlineno-1+3, + f.func_code.co_firstlineno-1+1, + g.func_code.co_firstlineno-1+1,] + foundlinenumbers = [x.lineno for x in info] + print l[0].frame.statement + assert foundlinenumbers == linenumbers + #for x in info: + # print "%s:%d %s" %(x.path.relto(root), x.lineno, x.statement) + #xxx Modified: py/dist/py/code/test_frame.py ============================================================================== --- py/dist/py/code/test_frame.py (original) +++ py/dist/py/code/test_frame.py Sun Dec 19 16:10:53 2004 @@ -5,6 +5,6 @@ def func(): return sys._getframe(0) f = func() - r = RunnerFrame(f) - source, lineno = r.getsourcelineno() + f = RunnerFrame(f) + source, lineno = f.code.fullsource, f.lineno assert source[lineno].startswith(" return sys._getframe(0)") Modified: py/dist/py/code/test_source.py ============================================================================== --- py/dist/py/code/test_source.py (original) +++ py/dist/py/code/test_source.py Sun Dec 19 16:10:53 2004 @@ -11,13 +11,12 @@ x = Source(""" 3 """) - assert str(x) == "3" + assert str(x) == "\n3\n " def test_source_indent_simple(): source = Source("raise ValueError") source = source.putaround( - "try:", - """ + "try:", """\ except ValueError: x = 42 else: @@ -40,7 +39,7 @@ assert not Source(" \nif 1:\npass").isparseable() class TestSliceAccess: - source = Source(""" + source = Source("""\ def f(x): pass def g(x): @@ -91,12 +90,12 @@ assert 'g' in names class TestSourceParsingAndCompiling: - source = Source(""" + source = Source("""\ def f(x): assert (x == 3 + 4) - """) + """).strip() def test_getstatement(self): #print str(self.source) ass = str(self.source[1:]) @@ -106,6 +105,31 @@ #x = s.deindent() assert str(s) == ass + def test_getstatement_exact_lineno_sanity(self): + source = Source(""" + try: + raise ValueError + finally: + 42 + """) + co = source.compile() + try: + exec co + except ValueError: + excinfo = py.code.ExceptionInfo(py.std.sys.exc_info()) + l = list(excinfo) + tb = l[0] + inner = l[1] + print "inner frame-statement:", inner.frame.statement + print "inner frame-lineno:", inner.frame.lineno + print "inner tb-statement:", inner.statement + print "inner tb-lineno:", inner.lineno + print "...", repr(inner.frame.code.fullsource[inner.lineno]) + + print "tb-statement:", tb.statement + print "tb-lineno:", tb.lineno + XXX + def test_compile_and_getsource(self): co = self.source.compile() exec co @@ -113,10 +137,9 @@ excinfo = py.test.raises(AssertionError, "f(6)") frame = excinfo[2].tb_next.tb_next.tb_frame f = py.code.RunnerFrame(frame) - source, lineno = f.getsourcelineno() - block = source.getstatement(lineno) + stmt = f.code.fullsource.getstatement(f.lineno) #print "block", str(block) - assert str(block).strip().startswith('assert') + assert str(stmt).strip().startswith('assert') def test_compile(): co = py.code.compile("x=3") @@ -126,5 +149,5 @@ def test_compile_and_getsource(): co = py.code.compile("x=3") exec co - source = py.code.getsource(co) + source = py.code.Source(co) assert str(source) == "x=3" Modified: py/dist/py/magic/exprinfo.py ============================================================================== --- py/dist/py/magic/exprinfo.py (original) +++ py/dist/py/magic/exprinfo.py Sun Dec 19 16:10:53 2004 @@ -398,8 +398,8 @@ def getframeline(f, lineno=None): if lineno is None: - lineno = f.f_lineno - co = f.f_code + lineno = f.lineno + co = f.code filename = co.co_filename line = magic.dyncode.getline(filename, lineno) @@ -412,7 +412,8 @@ def interpret(source, frame): module = Interpretable(parse(source, 'exec').node) #print "got module", module - frame = RunnerFrame(frame) + if not isinstance(frame, RunnerFrame): + frame = RunnerFrame(frame) try: return module.run(frame) # None if no exception generated except Failure, e: From hpk at codespeak.net Sun Dec 19 16:36:41 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 19 Dec 2004 16:36:41 +0100 (MET) Subject: [py-svn] r7928 - py/dist/py/code Message-ID: <20041219153641.574645B344@thoth.codespeak.net> Author: hpk Date: Sun Dec 19 16:36:40 2004 New Revision: 7928 Modified: py/dist/py/code/source.py py/dist/py/code/test_source.py Log: explicitely fail if we can't find the enclosing statement. Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Sun Dec 19 16:36:40 2004 @@ -66,6 +66,7 @@ for start in range(end, -1, -1): trysource = self[start:end+1] if trysource.isparseable(): + assert start <= lineno <= end, "could not locate statement" return trysource def deindent(self, offset=None): Modified: py/dist/py/code/test_source.py ============================================================================== --- py/dist/py/code/test_source.py (original) +++ py/dist/py/code/test_source.py Sun Dec 19 16:36:40 2004 @@ -105,13 +105,16 @@ #x = s.deindent() assert str(s) == ass - def test_getstatement_exact_lineno_sanity(self): - source = Source(""" + def XXXtest_getstatement_within_constructs(self): + source = Source("""\ try: raise ValueError finally: 42 """) + stmt = source.getstatement(1) + assert str(stmt).strip() == 'raise ValueError' + xxx co = source.compile() try: exec co From hpk at codespeak.net Sun Dec 19 16:40:00 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 19 Dec 2004 16:40:00 +0100 (MET) Subject: [py-svn] r7929 - in py/dist/py/code: . testing Message-ID: <20041219154000.1D4325B344@thoth.codespeak.net> Author: hpk Date: Sun Dec 19 16:39:59 2004 New Revision: 7929 Added: py/dist/py/code/testing/ py/dist/py/code/testing/test_cpython_features.py - copied unchanged from r7926, py/dist/py/code/test_cpython_features.py py/dist/py/code/testing/test_excinfo.py - copied unchanged from r7927, py/dist/py/code/test_excinfo.py py/dist/py/code/testing/test_frame.py - copied unchanged from r7927, py/dist/py/code/test_frame.py py/dist/py/code/testing/test_source.py - copied unchanged from r7928, py/dist/py/code/test_source.py Removed: py/dist/py/code/test_cpython_features.py py/dist/py/code/test_excinfo.py py/dist/py/code/test_frame.py py/dist/py/code/test_source.py Log: move tests into their own directory ... (i think it's worth considering doing this always in the py lib to not clutter up the implementation so much, but i am not completly sure yet, comments welcome) Deleted: /py/dist/py/code/test_cpython_features.py ============================================================================== --- /py/dist/py/code/test_cpython_features.py Sun Dec 19 16:39:59 2004 +++ (empty file) @@ -1,16 +0,0 @@ - -import new - -def test_new_code_object_carries_filename_through(): - class mystr(str): - pass - filename = mystr("dummy") - co = compile("hello\n", filename, 'exec') - assert not isinstance(co.co_filename, mystr) - c2 = new.code(co.co_argcount, co.co_nlocals, co.co_stacksize, - co.co_flags, co.co_code, co.co_consts, - co.co_names, co.co_varnames, - filename, - co.co_name, co.co_firstlineno, co.co_lnotab, - co.co_freevars, co.co_cellvars) - assert c2.co_filename is filename Deleted: /py/dist/py/code/test_excinfo.py ============================================================================== --- /py/dist/py/code/test_excinfo.py Sun Dec 19 16:39:59 2004 +++ (empty file) @@ -1,29 +0,0 @@ -import py -mypath = py.magic.autopath() - -def test_excinfo_simple(): - try: - raise ValueError - except ValueError: - info = py.code.ExceptionInfo() - assert info.type == ValueError - -def test_excinfo_getstatement(): - def g(): - raise ValueError - def f(): - g() - try: - f() - except ValueError: - info = py.code.ExceptionInfo() - l = list(info) - linenumbers = [f.func_code.co_firstlineno-1+3, - f.func_code.co_firstlineno-1+1, - g.func_code.co_firstlineno-1+1,] - foundlinenumbers = [x.lineno for x in info] - print l[0].frame.statement - assert foundlinenumbers == linenumbers - #for x in info: - # print "%s:%d %s" %(x.path.relto(root), x.lineno, x.statement) - #xxx Deleted: /py/dist/py/code/test_frame.py ============================================================================== --- /py/dist/py/code/test_frame.py Sun Dec 19 16:39:59 2004 +++ (empty file) @@ -1,10 +0,0 @@ -import sys -from py.code import RunnerFrame - -def test_frame_getsourcelineno_myself(): - def func(): - return sys._getframe(0) - f = func() - f = RunnerFrame(f) - source, lineno = f.code.fullsource, f.lineno - assert source[lineno].startswith(" return sys._getframe(0)") Deleted: /py/dist/py/code/test_source.py ============================================================================== --- /py/dist/py/code/test_source.py Sun Dec 19 16:39:59 2004 +++ (empty file) @@ -1,156 +0,0 @@ -from py.code import Source -import py - -def test_source_str_function(): - x = Source("3") - assert str(x) == "3" - - x = Source(" 3") - assert str(x) == "3" - - x = Source(""" - 3 - """) - assert str(x) == "\n3\n " - -def test_source_indent_simple(): - source = Source("raise ValueError") - source = source.putaround( - "try:", """\ - except ValueError: - x = 42 - else: - x = 23""") - assert str(source)=="""\ -try: - raise ValueError -except ValueError: - x = 42 -else: - x = 23""" - -def checkparseable(source): - assert source.isparseable() - -def test_isparseable(): - assert Source("hello").isparseable() - assert Source("if 1:\n pass").isparseable() - assert Source(" \nif 1:\n pass").isparseable() - assert not Source(" \nif 1:\npass").isparseable() - -class TestSliceAccess: - source = Source("""\ - def f(x): - pass - def g(x): - pass - """) - def test_getrange(self): - x = self.source[0:2] - assert x.isparseable() - assert len(x.lines) == 2 - assert str(x) == "def f(x):\n pass" - - def test_getline(self): - x = self.source[0] - assert x == "def f(x):" - -class TestCodeHacks: - def test_newcode(self): - from py.__impl__.code.source import newcode - def f(): - pass - co_filename = 'hello' - c = newcode(f.func_code, co_filename=co_filename) - assert c.co_filename is co_filename - - def test_newcode_withfilename(self): - from py.__impl__.code.source import newcode_withfilename - source = py.code.Source(""" - def f(): - def g(): - pass - """) - co = compile(str(source)+'\n', 'nada', 'exec') - obj = 'hello' - newco = newcode_withfilename(co, obj) - def walkcode(co): - for x in co.co_consts: - if isinstance(x, type(co)): - for y in walkcode(x): - yield y - yield co - - names = [] - for code in walkcode(newco): - assert newco.co_filename == obj - assert newco.co_filename is obj - names.append(code.co_name) - assert 'f' in names - assert 'g' in names - -class TestSourceParsingAndCompiling: - source = Source("""\ - def f(x): - assert (x == - 3 + - 4) - """).strip() - def test_getstatement(self): - #print str(self.source) - ass = str(self.source[1:]) - for i in range(1, 4): - #print "trying start in line %r" % self.source[i] - s = self.source.getstatement(i) - #x = s.deindent() - assert str(s) == ass - - def XXXtest_getstatement_within_constructs(self): - source = Source("""\ - try: - raise ValueError - finally: - 42 - """) - stmt = source.getstatement(1) - assert str(stmt).strip() == 'raise ValueError' - xxx - co = source.compile() - try: - exec co - except ValueError: - excinfo = py.code.ExceptionInfo(py.std.sys.exc_info()) - l = list(excinfo) - tb = l[0] - inner = l[1] - print "inner frame-statement:", inner.frame.statement - print "inner frame-lineno:", inner.frame.lineno - print "inner tb-statement:", inner.statement - print "inner tb-lineno:", inner.lineno - print "...", repr(inner.frame.code.fullsource[inner.lineno]) - - print "tb-statement:", tb.statement - print "tb-lineno:", tb.lineno - XXX - - def test_compile_and_getsource(self): - co = self.source.compile() - exec co - f(7) - excinfo = py.test.raises(AssertionError, "f(6)") - frame = excinfo[2].tb_next.tb_next.tb_frame - f = py.code.RunnerFrame(frame) - stmt = f.code.fullsource.getstatement(f.lineno) - #print "block", str(block) - assert str(stmt).strip().startswith('assert') - -def test_compile(): - co = py.code.compile("x=3") - exec co - assert x == 3 - -def test_compile_and_getsource(): - co = py.code.compile("x=3") - exec co - source = py.code.Source(co) - assert str(source) == "x=3" From hpk at codespeak.net Sun Dec 19 16:40:35 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 19 Dec 2004 16:40:35 +0100 (MET) Subject: [py-svn] r7930 - in py/dist/py/code: . testing Message-ID: <20041219154035.AF94D5B344@thoth.codespeak.net> Author: hpk Date: Sun Dec 19 16:40:35 2004 New Revision: 7930 Modified: py/dist/py/code/excinfo.py (props changed) py/dist/py/code/testing/ (props changed) py/dist/py/code/testing/test_cpython_features.py (props changed) py/dist/py/code/testing/test_excinfo.py (props changed) py/dist/py/code/testing/test_frame.py (props changed) Log: fixeol From hpk at codespeak.net Sun Dec 19 16:51:50 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 19 Dec 2004 16:51:50 +0100 (MET) Subject: [py-svn] r7931 - in py/dist/py: code code/testing magic Message-ID: <20041219155150.AD9795B344@thoth.codespeak.net> Author: hpk Date: Sun Dec 19 16:51:50 2004 New Revision: 7931 Added: py/dist/py/code/testing/__init__.py (contents, props changed) Modified: py/dist/py/code/source.py py/dist/py/code/testing/test_source.py py/dist/py/magic/test_exprinfo.py Log: small test fixes Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Sun Dec 19 16:51:50 2004 @@ -1,5 +1,6 @@ from __future__ import generators import sys +import py class Source(object): """ a mutable object holding a source code fragment, Added: py/dist/py/code/testing/__init__.py ============================================================================== --- (empty file) +++ py/dist/py/code/testing/__init__.py Sun Dec 19 16:51:50 2004 @@ -0,0 +1 @@ +# Modified: py/dist/py/code/testing/test_source.py ============================================================================== --- py/dist/py/code/testing/test_source.py (original) +++ py/dist/py/code/testing/test_source.py Sun Dec 19 16:51:50 2004 @@ -13,6 +13,10 @@ """) assert str(x) == "\n3\n " +def test_source_from_function(): + source = py.code.Source(test_source_str_function) + assert str(source).startswith('def test_source_str_function():') + def test_source_indent_simple(): source = Source("raise ValueError") source = source.putaround( @@ -29,9 +33,6 @@ else: x = 23""" -def checkparseable(source): - assert source.isparseable() - def test_isparseable(): assert Source("hello").isparseable() assert Source("if 1:\n pass").isparseable() Modified: py/dist/py/magic/test_exprinfo.py ============================================================================== --- py/dist/py/magic/test_exprinfo.py (original) +++ py/dist/py/magic/test_exprinfo.py Sun Dec 19 16:51:50 2004 @@ -85,6 +85,7 @@ class DummyCode: co_filename = 'dummy' + co_firstlineno = 0 class DummyFrame: f_globals = f_locals = {} f_code = DummyCode From hpk at codespeak.net Sun Dec 19 17:07:10 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 19 Dec 2004 17:07:10 +0100 (MET) Subject: [py-svn] r7932 - in py/dist/py/code: . testing Message-ID: <20041219160710.EC8755B3A9@thoth.codespeak.net> Author: hpk Date: Sun Dec 19 17:07:10 2004 New Revision: 7932 Modified: py/dist/py/code/source.py py/dist/py/code/testing/test_source.py Log: more tests, and handle the deindenting-choice for Source() instantiations more carefully. Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Sun Dec 19 17:07:10 2004 @@ -8,16 +8,17 @@ """ def __init__(self, *parts, **kwargs): self.lines = lines = [] + de = kwargs.get('deindent', True) for part in parts: if isinstance(part, Source): - lines.extend(part.lines) + partlines = part.lines elif isinstance(part, str): partlines = part.split('\n') - if kwargs.get('deindent', True): - partlines = deindent(partlines) - lines.extend(partlines) else: - lines.extend(getsource(part).lines) + partlines = getsource(part, deindent=False).lines + if de: + partlines = deindent(partlines) + lines.extend(partlines) def __getitem__(self, key): if isinstance(key, slice): @@ -132,23 +133,25 @@ co_filename = MyStr(filename) co_filename.__source__ = self return newcode_withfilename(co, co_filename) + # # public API shortcut functions # def compile_(source, filename=None, mode='exec', flags= generators.compiler_flag): - """ compile the given source to a code object, + """ compile the given source to a raw code object, which points back to the source code through "co_filename.__source__". All code objects - contained in the code object will also have - this special subclass-of-string filename. + contained in the code object will recursively + also have this special subclass-of-string + filename. """ s = Source(source) co = s.compile(filename, mode, flags) return co -def getsource(obj): +def getsource(obj, **kwargs): if hasattr(obj, 'f_code'): obj = obj.f_code try: @@ -156,7 +159,7 @@ except AttributeError: strsrc = py.std.inspect.getsource(obj) assert isinstance(strsrc, str) - return Source(strsrc) + return Source(strsrc, **kwargs) # # various helper functions Modified: py/dist/py/code/testing/test_source.py ============================================================================== --- py/dist/py/code/testing/test_source.py (original) +++ py/dist/py/code/testing/test_source.py Sun Dec 19 17:07:10 2004 @@ -17,6 +17,15 @@ source = py.code.Source(test_source_str_function) assert str(source).startswith('def test_source_str_function():') +def test_source_from_inner_function(): + def f(): + pass + source = py.code.Source(f, deindent=False) + assert str(source).startswith(' def f():') + source = py.code.Source(f) + assert str(source).startswith('def f():') + + def test_source_indent_simple(): source = Source("raise ValueError") source = source.putaround( From hpk at codespeak.net Sun Dec 19 17:53:33 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 19 Dec 2004 17:53:33 +0100 (MET) Subject: [py-svn] r7933 - in py/dist/py/code: . testing Message-ID: <20041219165333.15A8A5B344@thoth.codespeak.net> Author: hpk Date: Sun Dec 19 17:53:32 2004 New Revision: 7933 Modified: py/dist/py/code/source.py py/dist/py/code/testing/test_source.py Log: ported some tests from py.magic.dyncode (which is soon to become obsolete ...) Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Sun Dec 19 17:53:32 2004 @@ -122,9 +122,9 @@ except SyntaxError, ex: # re-represent syntax errors from parsing python strings newex = SyntaxError('\n'.join([ - "".join(lines[:ex.lineno]), + "".join(self.lines[:ex.lineno]), " " * ex.offset + '^', - "syntax error probably generated here: %s" % origin])) + "syntax error probably generated here: %s" % filename])) newex.offset = ex.offset newex.lineno = ex.lineno newex.text = ex.text Modified: py/dist/py/code/testing/test_source.py ============================================================================== --- py/dist/py/code/testing/test_source.py (original) +++ py/dist/py/code/testing/test_source.py Sun Dec 19 17:53:32 2004 @@ -1,5 +1,6 @@ from py.code import Source import py +import sys def test_source_str_function(): x = Source("3") @@ -25,8 +26,7 @@ source = py.code.Source(f) assert str(source).startswith('def f():') - -def test_source_indent_simple(): +def test_source_putaround_simple(): source = Source("raise ValueError") source = source.putaround( "try:", """\ @@ -42,6 +42,12 @@ else: x = 23""" +def test_syntaxerror_rerepresentation(): + ex = py.test.raises(SyntaxError, py.code.compile, 'x x')[1] + assert ex.lineno == 1 + assert ex.offset == 3 + assert ex.text.strip(), 'x x' + def test_isparseable(): assert Source("hello").isparseable() assert Source("if 1:\n pass").isparseable() @@ -106,6 +112,18 @@ 3 + 4) """).strip() + + def test_compile(self): + co = py.code.compile("x=3") + exec co + assert x == 3 + + def test_compile_and_getsource_simple(self): + co = py.code.compile("x=3") + exec co + source = py.code.Source(co) + assert str(source) == "x=3" + def test_getstatement(self): #print str(self.source) ass = str(self.source[1:]) @@ -154,13 +172,44 @@ #print "block", str(block) assert str(stmt).strip().startswith('assert') -def test_compile(): - co = py.code.compile("x=3") - exec co - assert x == 3 - -def test_compile_and_getsource(): - co = py.code.compile("x=3") - exec co - source = py.code.Source(co) - assert str(source) == "x=3" +def test_getstartingblock_singleline(): + class A: + def __init__(self, *args): + frame = sys._getframe(1) + self.source = py.code.RunnerFrame(frame).statement + + x = A('x', 'y') + + l = [i for i in x.source.lines if i.strip()] + assert len(l) == 1 + +def test_getstartingblock_multiline(): + class A: + def __init__(self, *args): + frame = sys._getframe(1) + self.source = py.code.RunnerFrame(frame).statement + + x = A('x', + 'y' \ + , + 'z') + + l = [i for i in x.source.lines if i.strip()] + assert len(l) == 4 + +def test_getline_finally(): + py.test.skip("inner statements cannot be located yet.") + def c(): pass + excinfo = py.test.raises(TypeError, """ + teardown = None + try: + c(1) + finally: + if teardown: + teardown() + """) + # XXX should py.test.raises return the following directly? + excinfo = py.code.ExceptionInfo(excinfo) + source = list(excinfo)[-1].statement + assert str(source.strip()) == 'c(1)' + From hpk at codespeak.net Sun Dec 19 17:55:16 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 19 Dec 2004 17:55:16 +0100 (MET) Subject: [py-svn] r7934 - py/dist/py/test Message-ID: <20041219165516.4112C5B344@thoth.codespeak.net> Author: hpk Date: Sun Dec 19 17:55:15 2004 New Revision: 7934 Modified: py/dist/py/test/compat.py py/dist/py/test/raises.py py/dist/py/test/test_compat.py Log: the test core code (other than the reporter does not use dyncode anymore yet, now on to the reporter ...) Modified: py/dist/py/test/compat.py ============================================================================== --- py/dist/py/test/compat.py (original) +++ py/dist/py/test/compat.py Sun Dec 19 17:55:15 2004 @@ -53,6 +53,6 @@ """ % locals() ) source = "".join(items) - exec py.magic.dyncode.compile2(source) + exec py.code.Source(source).compile() __all__ = ['TestCase'] Modified: py/dist/py/test/raises.py ============================================================================== --- py/dist/py/test/raises.py (original) +++ py/dist/py/test/raises.py Sun Dec 19 17:55:15 2004 @@ -15,7 +15,7 @@ loc.update(kwargs) #print "raises frame scope: %r" % frame.f_locals try: - eval(py.magic.dyncode.compile2(expr), frame.f_globals, loc) + eval(py.code.Source(expr).compile(), frame.f_globals, loc) # XXX didn'T mean f_globals == f_locals something special? # this is destroyed here ... except ExpectedException: Modified: py/dist/py/test/test_compat.py ============================================================================== --- py/dist/py/test/test_compat.py (original) +++ py/dist/py/test/test_compat.py Sun Dec 19 17:55:15 2004 @@ -47,5 +47,5 @@ self.assertRaises(py.test.Item.Failed, self.%(name)s, %(paramfail)s) """ % locals() - co = py.magic.dyncode.compile2(source) + co = py.code.Source(source).compile() exec co From hpk at codespeak.net Sun Dec 19 18:29:04 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 19 Dec 2004 18:29:04 +0100 (MET) Subject: [py-svn] r7935 - in py/dist/py/code: . testing Message-ID: <20041219172904.0CEBF5B344@thoth.codespeak.net> Author: hpk Date: Sun Dec 19 18:29:03 2004 New Revision: 7935 Modified: py/dist/py/code/source.py py/dist/py/code/testing/test_source.py Log: ok, we just guess harder to find the statement at a given linenumber in a given source code ... still not clean ... maybe someone can come up with a better idea ... Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Sun Dec 19 18:29:03 2004 @@ -57,19 +57,33 @@ """ return Source statement which contains the given linenumber (counted from 0). """ - # find the end + # XXX there must be a better than these heuristic ways ... for end in range(lineno, len(self)): - trysource = self[:end+1] + trysource = self[lineno:end+1] if trysource.isparseable(): + start = lineno break else: - return None - # find the start - for start in range(end, -1, -1): - trysource = self[start:end+1] - if trysource.isparseable(): - assert start <= lineno <= end, "could not locate statement" - return trysource + end = lineno + for start in range(lineno, -1, -1): + trysource = self[start:end+1] + if trysource.isparseable(): + break + else: + # find the end + for end in range(lineno, len(self)): + trysource = self[:end+1] + if trysource.isparseable(): + break + else: + return None + # find the start + for start in range(end, -1, -1): + trysource = self[start:end+1] + if trysource.isparseable(): + break + assert start <= lineno <= end, "could not locate statement" + return trysource def deindent(self, offset=None): """ return a new source object deindented by offset. Modified: py/dist/py/code/testing/test_source.py ============================================================================== --- py/dist/py/code/testing/test_source.py (original) +++ py/dist/py/code/testing/test_source.py Sun Dec 19 18:29:03 2004 @@ -198,7 +198,7 @@ assert len(l) == 4 def test_getline_finally(): - py.test.skip("inner statements cannot be located yet.") + #py.test.skip("inner statements cannot be located yet.") def c(): pass excinfo = py.test.raises(TypeError, """ teardown = None @@ -211,5 +211,5 @@ # XXX should py.test.raises return the following directly? excinfo = py.code.ExceptionInfo(excinfo) source = list(excinfo)[-1].statement - assert str(source.strip()) == 'c(1)' + assert str(source).strip() == 'c(1)' From hpk at codespeak.net Sun Dec 19 18:33:13 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 19 Dec 2004 18:33:13 +0100 (MET) Subject: [py-svn] r7936 - py/dist/py/magic Message-ID: <20041219173313.8D1AF5B344@thoth.codespeak.net> Author: hpk Date: Sun Dec 19 18:33:13 2004 New Revision: 7936 Modified: py/dist/py/magic/assertion.py py/dist/py/magic/test_dyncode.py Log: use dyncode less part 2 Modified: py/dist/py/magic/assertion.py ============================================================================== --- py/dist/py/magic/assertion.py (original) +++ py/dist/py/magic/assertion.py Sun Dec 19 18:33:13 2004 @@ -1,6 +1,6 @@ import __builtin__, sys -from py import magic -from py.__impl__.magic import exprinfo, dyncode +import py +from py.__impl__.magic import exprinfo BuiltinAssertionError = __builtin__.AssertionError @@ -8,7 +8,8 @@ def __init__(self, *args): BuiltinAssertionError.__init__(self, *args) f = sys._getframe(1) - source = dyncode.getparseablestartingblock(f) + source = py.code.RunnerFrame(f).statement + source = str(source).strip() #print "f.f_lineno", f.f_lineno if source: self.msg = exprinfo.interpret(source, f) @@ -21,8 +22,7 @@ else: self.msg = None - def invoke(): - magic.patch(__builtin__, 'AssertionError', AssertionError) + py.magic.patch(__builtin__, 'AssertionError', AssertionError) def revoke(): - magic.revert(__builtin__, 'AssertionError') + py.magic.revert(__builtin__, 'AssertionError') Modified: py/dist/py/magic/test_dyncode.py ============================================================================== --- py/dist/py/magic/test_dyncode.py (original) +++ py/dist/py/magic/test_dyncode.py Sun Dec 19 18:33:13 2004 @@ -56,7 +56,7 @@ fn = dyncode.getpyfile(dyncode) assert os.path.exists(fn) -def test_getstartingblock_singleline(): +def DEPRECATED_test_getstartingblock_singleline(): class A: def __init__(self, *args): frame = sys._getframe(1) @@ -67,7 +67,7 @@ l = [i for i in x.source.split('\n') if i.strip()] assert len(l) == 1 -def test_getstartingblock_multiline(): +def DEPRECATED_test_getstartingblock_multiline(): class A: def __init__(self, *args): frame = sys._getframe(1) @@ -81,7 +81,7 @@ l = [i for i in x.source.split('\n') if i.strip()] assert len(l) == 4 -def test_getline_finally(): +def DEPRECATED_test_getline_finally(): def c(): pass excinfo = py.test.raises(TypeError, """ teardown = None From hpk at codespeak.net Sun Dec 19 18:42:22 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 19 Dec 2004 18:42:22 +0100 (MET) Subject: [py-svn] r7937 - py/dist/py/magic Message-ID: <20041219174222.BA1E65B344@thoth.codespeak.net> Author: hpk Date: Sun Dec 19 18:42:22 2004 New Revision: 7937 Modified: py/dist/py/magic/exprinfo.py Log: getting rid of dyncode part 3 Modified: py/dist/py/magic/exprinfo.py ============================================================================== --- py/dist/py/magic/exprinfo.py (original) +++ py/dist/py/magic/exprinfo.py Sun Dec 19 18:42:22 2004 @@ -1,7 +1,5 @@ from compiler import parse, ast, pycodegen -from py import magic -from py.__impl__.magic import dyncode -from py.code import RunnerFrame +import py import __builtin__, sys passthroughex = (KeyboardInterrupt, SystemExit, MemoryError) @@ -13,7 +11,7 @@ #import traceback #traceback.print_exc() -class Interpretable(magic.View): +class Interpretable(py.magic.View): """A parse tree node with a few extra methods.""" explanation = None @@ -377,7 +375,7 @@ if frame is None: import sys frame = sys._getframe(1) - frame = RunnerFrame(frame) + frame = py.code.RunnerFrame(frame) expr = parse(s, 'eval') assert isinstance(expr, ast.Expression) node = Interpretable(expr.node) @@ -396,24 +394,11 @@ # API / Entry points # ######################################################### -def getframeline(f, lineno=None): - if lineno is None: - lineno = f.lineno - co = f.code - - filename = co.co_filename - line = magic.dyncode.getline(filename, lineno) - if line: - line = line.strip() - else: - line = None - return line - def interpret(source, frame): module = Interpretable(parse(source, 'exec').node) #print "got module", module - if not isinstance(frame, RunnerFrame): - frame = RunnerFrame(frame) + if not isinstance(frame, py.code.RunnerFrame): + frame = py.code.RunnerFrame(frame) try: return module.run(frame) # None if no exception generated except Failure, e: @@ -424,21 +409,15 @@ import traceback traceback.print_exc() -def gettbline(tb, index=-1): - tb = magic.dyncode.listtb(tb)[-1] - f = tb.tb_frame - return f, getframeline(f, tb.tb_lineno) - def getmsg((typ, val, tb)): #frame, line = gettbline(tb) - #frame = RunnerFrame(frame) + #frame = py.code.RunnerFrame(frame) #return interpret(line, frame) - - tb = magic.dyncode.listtb(tb)[-1] - source = dyncode.getparseablestartingblock(tb) - frame = tb.tb_frame - frame = RunnerFrame(frame) - x = interpret(source, frame) + + excinfo = py.code.ExceptionInfo((typ, val, tb)) + tb = list(excinfo)[-1] + source = str(tb.statement).strip() + x = interpret(source, tb.frame) if not isinstance(x, str): raise TypeError, "interpret returned non-string %r" % (x,) return x @@ -454,7 +433,7 @@ if frame is None: import sys frame = sys._getframe(1) - frame = RunnerFrame(frame) + frame = py.code.RunnerFrame(frame) module = Interpretable(parse(s, 'exec').node) try: module.run(frame) From arigo at codespeak.net Sun Dec 19 22:58:07 2004 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 19 Dec 2004 22:58:07 +0100 (MET) Subject: [py-svn] r7939 - in py/dist/py: . magic test/test Message-ID: <20041219215807.B83165B344@thoth.codespeak.net> Author: arigo Date: Sun Dec 19 22:58:07 2004 New Revision: 7939 Modified: py/dist/py/__init__.py py/dist/py/magic/exprinfo.py py/dist/py/test/test/demo.py Log: - export InspectableFrame for exprinfo.py and for PyPy. (the name is bad, it should be changed...) - renamed the test class in demo.py: its name didn't start with "Test". Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Sun Dec 19 22:58:07 2004 @@ -53,6 +53,7 @@ 'code.compile' : ('./code/source.py', 'compile_'), 'code.Source' : ('./code/source.py', 'Source'), + 'code.InspectableFrame' : ('./code/frame.py', 'InspectableFrame'), 'code.RunnerFrame' : ('./code/frame.py', 'RunnerFrame'), 'code.ExceptionInfo' : ('./code/excinfo.py', 'ExceptionInfo'), Modified: py/dist/py/magic/exprinfo.py ============================================================================== --- py/dist/py/magic/exprinfo.py (original) +++ py/dist/py/magic/exprinfo.py Sun Dec 19 22:58:07 2004 @@ -397,7 +397,7 @@ def interpret(source, frame): module = Interpretable(parse(source, 'exec').node) #print "got module", module - if not isinstance(frame, py.code.RunnerFrame): + if not isinstance(frame, py.code.InspectableFrame): frame = py.code.RunnerFrame(frame) try: return module.run(frame) # None if no exception generated Modified: py/dist/py/test/test/demo.py ============================================================================== --- py/dist/py/test/test/demo.py (original) +++ py/dist/py/test/test/demo.py Sun Dec 19 22:58:07 2004 @@ -6,7 +6,7 @@ def somefunc(x,y): otherfunc(x,y) -class FailingTests(object): +class TestFailing(object): def test_simple(self): def f(): return 42 From arigo at codespeak.net Mon Dec 20 12:56:15 2004 From: arigo at codespeak.net (arigo at codespeak.net) Date: Mon, 20 Dec 2004 12:56:15 +0100 (MET) Subject: [py-svn] r7942 - in py/dist/py: . execnet Message-ID: <20041220115615.33EAA5ADA0@thoth.codespeak.net> Author: arigo Date: Mon Dec 20 12:56:14 2004 New Revision: 7942 Modified: py/dist/py/__init__.py py/dist/py/execnet/gateway.py py/dist/py/execnet/message.py py/dist/py/execnet/register.py Log: - SshGateway - gateways now use repr/eval instead of marshal to avoid issues with Python 2.4, whose format changed :-( - the remote side was again importing 'py' locally for Source... Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Mon Dec 20 12:56:14 2004 @@ -59,4 +59,5 @@ 'execnet.SocketGateway' : ('./execnet/register.py', 'SocketGateway'), 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), + 'execnet.SshGateway' : ('./execnet/register.py', 'SshGateway'), }) Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Mon Dec 20 12:56:14 2004 @@ -7,12 +7,12 @@ # XXX the following line should not be here g = globals() -if 'Source' not in g: +if 'Message' not in g: from py.code import Source from py.__impl__.execnet.channel import ChannelFactory, Channel from py.__impl__.execnet.message import Message -assert Message and Source and ChannelFactory, "Import/Configuration Error" +assert Message and ChannelFactory, "Import/Configuration Error" import os debug = 0 # open('/tmp/execnet-debug-%d' % os.getpid() , 'wa') Modified: py/dist/py/execnet/message.py ============================================================================== --- py/dist/py/execnet/message.py (original) +++ py/dist/py/execnet/message.py Mon Dec 20 12:56:14 2004 @@ -1,5 +1,5 @@ import struct -import marshal +#import marshal # ___________________________________________________________________________ # @@ -16,7 +16,11 @@ self.data = data def writeto(self, io): - data = marshal.dumps(self.data) + # big XXX and all this + # data = marshal.dumps(self.data) + # doesn't work for exchanging data across Python version :-((( + data = repr(self.data) # argh + header = struct.pack("!iii", self.msgtype, self.channelid, len(data)) io.write(header) io.write(data) @@ -26,9 +30,11 @@ msgtype, senderid, stringlen = struct.unpack("!iii", header) if stringlen: string = io.read(stringlen) + # same big XXX as in writeto() + # string = marshal.loads(string) + string = eval(string, {}) else: string = '' - string = marshal.loads(string) msg = cls._types[msgtype](senderid, string) return msg readfrom = classmethod(readfrom) Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Mon Dec 20 12:56:14 2004 @@ -33,29 +33,19 @@ self.trace("sending gateway bootstrap code") io.write('%r\n' % source) -class PopenGateway(InstallableGateway): - def __init__(self, python=sys.executable): - cmd = '%s -u -c "exec input()"' % python +class PopenCmdGateway(InstallableGateway): + def __init__(self, cmd): infile, outfile = os.popen2(cmd) io = inputoutput.Popen2IO(infile, outfile) - InstallableGateway.__init__(self, io=io) + super(PopenCmdGateway, self).__init__(io=io) self._pidchannel = self.remote_exec(""" import os channel.send(os.getpid()) """) - def remote_bootstrap_gateway(self, io, extra=''): - # XXX the following hack helps us to import the same version - # of the py lib, but only works for PopenGateways - # --> we need proper remote imports working - # across any kind of gateway! - s = "import sys ; sys.path[:] = %r" % (sys.path,) - s = "\n".join([extra, s]) - super(PopenGateway, self).remote_bootstrap_gateway(io, s) - def exit(self): - if not super(PopenGateway, self).exit(): + if not super(PopenCmdGateway, self).exit(): return try: pid = self._pidchannel.receive() @@ -69,6 +59,20 @@ except OSError: self.trace("child process %s already dead?" %pid) +class PopenGateway(PopenCmdGateway): + def __init__(self, python=sys.executable): + cmd = '%s -u -c "exec input()"' % python + super(PopenGateway, self).__init__(cmd) + + def remote_bootstrap_gateway(self, io, extra=''): + # XXX the following hack helps us to import the same version + # of the py lib, but only works for PopenGateways + # --> we need proper remote imports working + # across any kind of gateway! + s = "import sys ; sys.path[:] = %r" % (sys.path,) + s = "\n".join([extra, s]) + super(PopenGateway, self).remote_bootstrap_gateway(io, s) + class SocketGateway(InstallableGateway): def __init__(self, host, port): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -77,7 +81,21 @@ sock.connect((host, port)) io = inputoutput.SocketIO(sock) InstallableGateway.__init__(self, io=io) - + +class SshGateway(PopenCmdGateway): + def __init__(self, host, port=None, username=None, remotepython='python'): + if port is not None: + host = '%s:%d' % (host, port) + remotecmd = '%s -u -c "exec input()"' % (remotepython,) + cmdline = [host, remotecmd] + if username is not None: + cmdline[:0] = ['-l', username] + # XXX Unix style quoting + for i in range(len(cmdline)): + cmdline[i] = "'" + cmdline[i].replace("'", "'\\''") + "'" + cmdline.insert(0, 'ssh') + super(SshGateway, self).__init__(' '.join(cmdline)) + class ExecGateway(PopenGateway): def remote_exec_sync_stdcapture(self, lines, callback): # hack: turn the content of the cell into From arigo at codespeak.net Mon Dec 20 14:16:36 2004 From: arigo at codespeak.net (arigo at codespeak.net) Date: Mon, 20 Dec 2004 14:16:36 +0100 (MET) Subject: [py-svn] r7944 - py/dist/py/execnet Message-ID: <20041220131636.65B695AF07@thoth.codespeak.net> Author: arigo Date: Mon Dec 20 14:16:35 2004 New Revision: 7944 Added: py/dist/py/execnet/sshtesting.py (contents, props changed) Modified: py/dist/py/execnet/inputoutput.py Log: A test for the new SSH gateway (it doesn't run unless you run it manually). Bug discovered by the test: everything crashes if the debugging output files are not writeable on the remote machine (typically because they already exist and belong to hpk :-) Modified: py/dist/py/execnet/inputoutput.py ============================================================================== --- py/dist/py/execnet/inputoutput.py (original) +++ py/dist/py/execnet/inputoutput.py Mon Dec 20 14:16:35 2004 @@ -6,12 +6,15 @@ import socket, os, sys class SocketIO: - server_stmt = """if 1: - io = SocketIO(clientsock) - import sys - sys.stdout = sys.stderr = open('/tmp/execnet-socket-debug.log', 'a', 0) - print '='*60 - """ + server_stmt = """ +io = SocketIO(clientsock) +import sys +try: + sys.stdout = sys.stderr = open('/tmp/execnet-socket-debug.log', 'a', 0) +except (IOError, OSError): + sys.stdout = sys.stderr = open('/dev/null', 'w') +print '='*60 +""" error = (socket.error, EOFError) def __init__(self, sock): @@ -51,7 +54,10 @@ server_stmt = """ import sys io = Popen2IO(sys.stdout, sys.stdin) -sys.stdout = sys.stderr = open('/tmp/execnet-popen-debug.log', 'a', 0) +try: + sys.stdout = sys.stderr = open('/tmp/execnet-popen-debug.log', 'a', 0) +except (IOError, OSError): + sys.stdout = sys.stderr = open('/dev/null', 'w') print '='*60 """ error = (IOError, OSError, EOFError) Added: py/dist/py/execnet/sshtesting.py ============================================================================== --- (empty file) +++ py/dist/py/execnet/sshtesting.py Mon Dec 20 14:16:35 2004 @@ -0,0 +1,14 @@ +""" +A test file that doesn't run by default to test SshGateway. +""" + +import py + +REMOTE_HOST = 'codespeak.net' # set this to some place where you can ssh to +REMOTE_HOSTNAME = 'thoth.codespeak.net' # the remote's socket.gethostname() + +def test_sshgateway(): + gw = py.execnet.SshGateway(REMOTE_HOST) + c = gw.remote_exec('import socket; channel.send(socket.gethostname())') + msg = c.receive() + assert msg == REMOTE_HOSTNAME From hpk at codespeak.net Mon Dec 20 18:06:33 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 20 Dec 2004 18:06:33 +0100 (MET) Subject: [py-svn] r7946 - py/dist/doc Message-ID: <20041220170633.7C4AA5AF07@thoth.codespeak.net> Author: hpk Date: Mon Dec 20 18:06:32 2004 New Revision: 7946 Modified: py/dist/doc/rest_test.py Log: adapt to moved script Modified: py/dist/doc/rest_test.py ============================================================================== --- py/dist/doc/rest_test.py (original) +++ py/dist/doc/rest_test.py Mon Dec 20 18:06:32 2004 @@ -4,7 +4,7 @@ mypath = py.magic.autopath() mydir = mypath.dirpath() -rest = mydir.dirpath('tool', 'rest.py') +rest = mydir.dirpath('py', 'bin', 'py.rest') def restcheck(path): out = py.process.cmdexec("%s %s 2>&1" %(rest, path)) From hpk at codespeak.net Mon Dec 20 18:10:55 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 20 Dec 2004 18:10:55 +0100 (MET) Subject: [py-svn] r7947 - in py/dist/py: code code/testing magic test test/report/text Message-ID: <20041220171055.8AEA85AF07@thoth.codespeak.net> Author: hpk Date: Mon Dec 20 18:10:54 2004 New Revision: 7947 Modified: py/dist/py/code/excinfo.py py/dist/py/code/frame.py py/dist/py/code/source.py py/dist/py/code/testing/test_source.py py/dist/py/magic/exprinfo.py py/dist/py/magic/test_assertion.py py/dist/py/magic/test_exprinfo.py py/dist/py/magic/test_invoke.py py/dist/py/test/collect.py py/dist/py/test/drive.py py/dist/py/test/raises.py py/dist/py/test/report/text/reporter.py py/dist/py/test/report/text/summary.py py/dist/py/test/test_collect.py Log: hairy refactoring to use the higher level introspection model for the text reporter. lots of little things fixed and added here and there (further clean up needed ...) Modified: py/dist/py/code/excinfo.py ============================================================================== --- py/dist/py/code/excinfo.py (original) +++ py/dist/py/code/excinfo.py Mon Dec 20 18:10:54 2004 @@ -17,6 +17,11 @@ yield TracebackEntry(current, self) current = current.tb_next + def format_exception_only(self): + func = py.std.traceback.format_exception_only + for x in func(self.type, self.value): + yield x + class TracebackEntry(object): def __init__(self, rawentry, excinfo): self._rawentry = rawentry Modified: py/dist/py/code/frame.py ============================================================================== --- py/dist/py/code/frame.py (original) +++ py/dist/py/code/frame.py Mon Dec 20 18:10:54 2004 @@ -4,6 +4,7 @@ def __init__(self, f_code): self.raw = f_code self.firstlineno = f_code.co_firstlineno - 1 + self.name = f_code.co_name def path(self): return getattr(self.raw.co_filename, '__path__', Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Mon Dec 20 18:10:54 2004 @@ -1,6 +1,8 @@ from __future__ import generators import sys -import py +import inspect + +# DON'T IMPORT PY HERE class Source(object): """ a mutable object holding a source code fragment, @@ -57,6 +59,13 @@ """ return Source statement which contains the given linenumber (counted from 0). """ + start, end = self.getstatementrange(lineno) + return self[start:end] + + def getstatementrange(self, lineno): + """ return (start, end) tuple which span the statement + region which contains the given lineno. + """ # XXX there must be a better than these heuristic ways ... for end in range(lineno, len(self)): trysource = self[lineno:end+1] @@ -83,7 +92,14 @@ if trysource.isparseable(): break assert start <= lineno <= end, "could not locate statement" - return trysource + return start, end + 1 + + def getblockend(self, lineno): + # XXX + lines = [x + '\n' for x in self.lines[lineno:]] + blocklines = inspect.getblock(lines) + #print blocklines + return lineno + len(blocklines) - 1 def deindent(self, offset=None): """ return a new source object deindented by offset. @@ -171,7 +187,8 @@ try: return obj.co_filename.__source__ except AttributeError: - strsrc = py.std.inspect.getsource(obj) + import inspect + strsrc = inspect.getsource(obj) assert isinstance(strsrc, str) return Source(strsrc, **kwargs) Modified: py/dist/py/code/testing/test_source.py ============================================================================== --- py/dist/py/code/testing/test_source.py (original) +++ py/dist/py/code/testing/test_source.py Mon Dec 20 18:10:54 2004 @@ -166,9 +166,8 @@ exec co f(7) excinfo = py.test.raises(AssertionError, "f(6)") - frame = excinfo[2].tb_next.tb_next.tb_frame - f = py.code.RunnerFrame(frame) - stmt = f.code.fullsource.getstatement(f.lineno) + frame = list(excinfo)[2].frame + stmt = frame.code.fullsource.getstatement(frame.lineno) #print "block", str(block) assert str(stmt).strip().startswith('assert') @@ -208,8 +207,6 @@ if teardown: teardown() """) - # XXX should py.test.raises return the following directly? - excinfo = py.code.ExceptionInfo(excinfo) source = list(excinfo)[-1].statement assert str(source).strip() == 'c(1)' Modified: py/dist/py/magic/exprinfo.py ============================================================================== --- py/dist/py/magic/exprinfo.py (original) +++ py/dist/py/magic/exprinfo.py Mon Dec 20 18:10:54 2004 @@ -409,12 +409,13 @@ import traceback traceback.print_exc() -def getmsg((typ, val, tb)): +def getmsg(excinfo): + if not isinstance(excinfo, py.code.ExceptionInfo): + excinfo = py.code.ExceptionInfo(excinfo) #frame, line = gettbline(tb) #frame = py.code.RunnerFrame(frame) #return interpret(line, frame) - excinfo = py.code.ExceptionInfo((typ, val, tb)) tb = list(excinfo)[-1] source = str(tb.statement).strip() x = interpret(source, tb.frame) Modified: py/dist/py/magic/test_assertion.py ============================================================================== --- py/dist/py/magic/test_assertion.py (original) +++ py/dist/py/magic/test_assertion.py Mon Dec 20 18:10:54 2004 @@ -26,7 +26,7 @@ finally: i = 42 """) - s = str(excinfo[1]) + s = str(excinfo.value) assert s.find("takes no argument") != -1 #def g(): Modified: py/dist/py/magic/test_exprinfo.py ============================================================================== --- py/dist/py/magic/test_exprinfo.py (original) +++ py/dist/py/magic/test_exprinfo.py Mon Dec 20 18:10:54 2004 @@ -86,6 +86,7 @@ class DummyCode: co_filename = 'dummy' co_firstlineno = 0 + co_name = 'dummy' class DummyFrame: f_globals = f_locals = {} f_code = DummyCode Modified: py/dist/py/magic/test_invoke.py ============================================================================== --- py/dist/py/magic/test_invoke.py (original) +++ py/dist/py/magic/test_invoke.py Mon Dec 20 18:10:54 2004 @@ -11,7 +11,7 @@ def check_assertion(): excinfo = py.test.raises(AssertionError, "assert 1 == 2") - value = excinfo[1] + value = excinfo.value assert str(value) == "assert 1 == 2" def test_invoke_dyncode(): Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Mon Dec 20 18:10:54 2004 @@ -21,16 +21,12 @@ class Error(object): """ represents a non-fatal exception while collecting. """ def __init__(self, excinfo): - self.excinfo = excinfo + self.excinfo = py.code.ExceptionInfo(excinfo) def __repr__(self): - tb = self.excinfo[2] - while tb.tb_next: - tb = tb.tb_next - - return '' % ( - tb.tb_frame.f_code.co_filename, - tb.tb_lineno) + tb = list(self.excinfo)[-1] + return '' % (tb.code.path, + tb.lineno+1) class Collector(object): """ instances are *restartable iterators*. They will yield Units or Modified: py/dist/py/test/drive.py ============================================================================== --- py/dist/py/test/drive.py (original) +++ py/dist/py/test/drive.py Mon Dec 20 18:10:54 2004 @@ -91,12 +91,12 @@ try: res = item.run(self) or item.Passed() except item.Outcome, res: - res.excinfo = py.std.sys.exc_info() + res.excinfo = py.code.ExceptionInfo() except (KeyboardInterrupt, SystemExit): self.reporter.enditem(res) raise except: - res = item.Failed(excinfo=py.std.sys.exc_info()) + res = item.Failed(excinfo=py.code.ExceptionInfo()) res.item = item self.reporter.enditem(res) if isinstance(res, (item.Failed,)): Modified: py/dist/py/test/raises.py ============================================================================== --- py/dist/py/test/raises.py (original) +++ py/dist/py/test/raises.py Mon Dec 20 18:10:54 2004 @@ -19,9 +19,9 @@ # XXX didn'T mean f_globals == f_locals something special? # this is destroyed here ... except ExpectedException: - return sys.exc_info() + return py.code.ExceptionInfo() except Exception, e: - excinfo = sys.exc_info() + excinfo = py.code.ExceptionInfo(sys.exc_info()) else: excinfo = None raise ExceptionFailure(expr=expr, expected=ExpectedException, Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Mon Dec 20 18:10:54 2004 @@ -167,7 +167,10 @@ self.out.write(c) if writeinfo is not None: - exc, frame, filename,lineno = self.summary.getexinfo(error) + #exc, frame, filename,lineno = self.summary.getexinfo(error) + tbentries = list(error.excinfo) + filename = tbentries[-1].frame.code.path + lineno = tbentries[-1].lineno self.out.line('CollectError: %s:%d' % (filename, lineno) ) def processresult(self, testresult): Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Mon Dec 20 18:10:54 2004 @@ -15,18 +15,17 @@ return self.__dict__.setdefault(name, []) def repr_collect_error(self, error): - exc, frame, filename,lineno = self.getexinfo(error) self.out.line() self.out.sep("_") self.out.sep("_", "Collect Error") self.out.line() - self.repr_traceback_raw(filename, error.excinfo[2]) + self.repr_traceback_raw(error.excinfo) #self.repr_failure_result(res) #self.out.line() #if isinstance(res.excinfo[1], AssertionError): # from std.utest.tool import hackexpr # res.msg = "failed: " + hackexpr.getmsg(res.excinfo) - for line in py.std.traceback.format_exception_only(*error.excinfo[:2]): + for line in error.excinfo.format_exception_only(): self.out.line(line.rstrip()) #if self.option.showlocals: @@ -54,26 +53,19 @@ self.out.sep('=', 'tests finished: %s in %4.2f seconds' % (status, elapsed)) - def getexinfo(self, res): - _,exc,tb = res.excinfo - tbindex = getattr(res, 'tbindex', -1) - filename, lineno = py.magic.dyncode.tbinfo(tb, tbindex) - frame = py.magic.dyncode.listtb(tb)[tbindex].tb_frame - return exc, frame, filename, lineno - def skippedreasons(self): d = {} for res in self.getlist(Item.Skipped): tbindex = getattr(res, 'tbindex', -1) - raisingtb = py.magic.dyncode.listtb(res.excinfo[2])[tbindex] - fn = raisingtb.tb_frame.f_code.co_filename - lineno = raisingtb.tb_lineno + raisingtb = list(res.excinfo)[tbindex] + fn = raisingtb.frame.code.path + lineno = raisingtb.lineno d[(fn,lineno)] = res if d: self.out.line() self.out.sep('_', 'reasons for skipped tests') for (fn,lineno), res in d.items(): - self.out.line('Skipped in %s:%d %s' %(fn,lineno,getattr(res, 'msg'))) + self.out.line('Skipped in %s:%d %s' %(fn,lineno+1,getattr(res, 'msg'))) self.out.line() def failures(self): @@ -81,14 +73,10 @@ self.repr_failure(res) def repr_failure(self, res): - exc, frame, filename,lineno = self.getexinfo(res) self.out.line() self.out.sep("_") - self.out.line() - #self.out.sep("_", "Test Failure") # %s" % res.unit.extpy) - #self.out.sep("_") #self.out.line() - self.repr_traceback(res.item, res.excinfo[2], + self.repr_traceback(res.item, res.excinfo, getattr(res, 'tbindex', -1)) #self.out.line() self.repr_failure_result(res) @@ -106,7 +94,7 @@ #self.out.line() def repr_failure_result(self, res): - cls = res.excinfo[0] + cls = res.excinfo.type if isinstance(cls, str): self.out.line("string-exception: %s" % cls) elif issubclass(cls, Item.ExceptionFailure): @@ -119,17 +107,16 @@ #self.out.line("got : %r" % res.innerexcinfo[0]) #self.out.line() self.out.sep("- ", "traceback of unexpected exception") - #self.out.line("reality:") - self.repr_traceback(res.item, res.innerexcinfo[2]) - for line in py.std.traceback.format_exception_only(*res.innerexcinfo[:2]): + self.repr_traceback(res.item, res.innerexcinfo) # [2]) + for line in res.innerexcinfo.format_exception_only(): self.out.line(line) elif issubclass(cls, assertion.AssertionError): - res.msg = str(res.excinfo[1]) + res.msg = str(res.excinfo.value) self.out.line(res) return else: if not self.option.nomagic: - showex = False + showex = False try: res.msg = exprinfo.getmsg(res.excinfo) except KeyboardInterrupt: @@ -139,31 +126,34 @@ self.out.line("reinterpretation traceback") py.std.traceback.print_exc() else: - self.out.line("(reinterpretation failed, you may increase " - "verbosity to see details)") + self.out.line("(reinterpretation failed, " + "you may increase " + "verbosity to see details)") showex = True else: self.out.line(res) else: showex = True if showex: - for line in py.std.traceback.format_exception_only(*res.excinfo[:2]): + for line in res.excinfo.format_exception_only(): self.out.line(line) - def repr_source(self, frame, lineno): + def repr_source(self, tb): # represent the source code self.out.line() - try: - lines, firstlineno = py.magic.dyncode.findsource(frame) # .f_code) - except IOError: - self.out.line("failure to retrieve sourcelines from %r" % frame) + source = tb.frame.code.fullsource + if not source: + self.out.line("failure to get at sourcelines from %r" % tb) #self.out.line("(co_filename = %r)" % (frame.f_code.co_filename)) #self.out.line("(f_lineno = %r)" % (frame.f_lineno)) return - - for line in lines[firstlineno:lineno-1]: + start = tb.frame.code.firstlineno + #end = source.getblockend(start) + end = tb.lineno + + for line in source[start:end]: self.out.line(line.rstrip()) - line = lines[lineno-1] + line = source[end] if line.startswith(" "): line = line[1:] # to avoid the indentation caused by ">" self.out.line(">" + line) @@ -178,32 +168,31 @@ # line = py.magic.dyncode.getline(filename, lineno) # self.out.line("> %s" % line.rstrip()) - def forward_traceback_to_test(self, tb, filename): - old = tb - while tb: - codefn = tb.tb_frame.f_code.co_filename - if filename == codefn or filename.endswith(codefn) or codefn.endswith(filename): - return tb - tb = tb.tb_next - return old + def forward_traceback_to_test(self, tbentries, path): + for i in range(len(tbentries)): + tb = tbentries[i] + #print "eq", tb.frame.code.path, path + if tb.frame.code.path == path: + break + return tbentries[i:] - def repr_traceback(self, item, tb, tbindex=-1): + def repr_traceback(self, item, excinfo, tbindex=-1): t_file, t_lineno = item.extpy.getfilelineno() - self.out.line("%s, line %d" % (item.extpy, t_lineno) ) fspath = item.extpy.root - self.out.sep('_') - self.repr_traceback_raw(fspath, tb, tbindex) + tbentries = list(excinfo) + if fspath and not self.option.fulltrace: + tbentries = self.forward_traceback_to_test(tbentries, fspath) + else: + tbindex = -1 + self.repr_traceback_raw(tbentries, tbindex) #t_file, t_lineno = unit.extpy.getfilelineno() #if t_file != filename or lineno != t_lineno: #self.out.line("%s, line %d" % (unit.extpy, t_lineno) ) + #self.out.line("%s, line %d" % (item.extpy, t_lineno) ) self.out.sep('-') + #self.out.sep('-') - def repr_traceback_raw(self, fspath, tb, tbindex=-1): - if fspath and not self.option.fulltrace: - tb = self.forward_traceback_to_test(tb, str(fspath)) - else: - tbindex = -1 - tbentries = py.magic.dyncode.listtb(tb) + def repr_traceback_raw(self, tbentries, tbindex=-1): if tbindex < -1 and not self.option.fulltrace: tbentries = tbentries[:tbindex+1] @@ -214,18 +203,19 @@ first = False else: self.out.sep('-') - filename = tb.tb_frame.f_code.co_filename - lineno = tb.tb_lineno - name = tb.tb_frame.f_code.co_name - showfn = filename + path = tb.frame.code.path + lineno = tb.lineno + name = tb.frame.code.name + showfn = path #showfn = filename.split(os.sep) #if len(showfn) > 5: # showfn = os.sep.join(['...'] + showfn[-5:]) - self.repr_source(tb.tb_frame, lineno) - self.out.line("> %s, line %d" %(showfn, lineno)) # , name)) + self.repr_source(tb) + self.out.line("") + self.out.line("[%s:%d]" %(showfn, lineno+1)) # , name)) if self.option.showlocals: self.out.sep('- ', 'locals') - for name, value in tb.tb_frame.f_locals.items(): + for name, value in tb.frame.f_locals.items(): if len(repr(value)) < 70 or not isinstance(value, (list, tuple, dict)): self.out.line("%-10s = %r" %(name, value)) @@ -233,9 +223,9 @@ self.out.line("%-10s =\\" % (name,)) py.std.pprint.pprint(value, stream=self.out) #self.out.line("%-10s = %r" %(name, value)) - key = (filename, lineno) + key = (path, lineno) if key not in recursioncache: - recursioncache.setdefault(key, []).append(tb.tb_frame.f_locals) + recursioncache.setdefault(key, []).append(tb.frame.f_locals) else: loc = tb.tb_frame.f_locals for x in recursioncache[key]: Modified: py/dist/py/test/test_collect.py ============================================================================== --- py/dist/py/test/test_collect.py (original) +++ py/dist/py/test/test_collect.py Mon Dec 20 18:10:54 2004 @@ -12,7 +12,7 @@ l = list(collect.Module(py.path.extpy(fn))) assert l ex, = l - assert issubclass(ex.excinfo[0], ImportError) + assert issubclass(ex.excinfo.type, ImportError) def test_failing_import_directory(): class MyDirectory(collect.Directory): @@ -24,21 +24,21 @@ assert l2 exc = l2[0] assert isinstance(exc, collect.Error) - assert issubclass(exc.excinfo[0], ImportError) + assert issubclass(exc.excinfo.type, ImportError) def test_module_file_not_found(): fn = testdir.join('nada','no') l = list(collect.Module(fn)) assert len(l) == 1 assert isinstance(l[0], collect.Error) - assert isinstance(l[0].excinfo[1], py.path.NotFound) + assert isinstance(l[0].excinfo.value, py.path.NotFound) def test_syntax_error_in_module(): modpath = py.path.extpy(datadir.join('syntax_error.py')) l2 = list(collect.Module(modpath)) assert len(l2) == 1 assert isinstance(l2[0], collect.Error) - assert issubclass(l2[0].excinfo[0], SyntaxError) + assert issubclass(l2[0].excinfo.type, SyntaxError) def test_disabled_class(): extpy = py.path.extpy(datadir.join('disabled.py')) From arigo at codespeak.net Mon Dec 20 18:10:57 2004 From: arigo at codespeak.net (arigo at codespeak.net) Date: Mon, 20 Dec 2004 18:10:57 +0100 (MET) Subject: [py-svn] r7948 - py/dist/py/path/gateway Message-ID: <20041220171057.42E765AF07@thoth.codespeak.net> Author: arigo Date: Mon Dec 20 18:10:56 2004 New Revision: 7948 Added: py/dist/py/path/gateway/ (props changed) py/dist/py/path/gateway/channeltest.py (contents, props changed) py/dist/py/path/gateway/channeltest2.py (contents, props changed) py/dist/py/path/gateway/remotepath.py (contents, props changed) Log: A quick hack for RemotePaths working over a gateway. Added: py/dist/py/path/gateway/channeltest.py ============================================================================== --- (empty file) +++ py/dist/py/path/gateway/channeltest.py Mon Dec 20 18:10:56 2004 @@ -0,0 +1,66 @@ +import threading + + +class PathServer: + + def __init__(self, channel): + self.channel = channel + self.C2P = {} + self.next_id = 0 + threading.Thread(target=self.serve).start() + + def p2c(self, path): + n = self.next_id + self.next_id += 1 + self.C2P[n] = path + return n + + def command_LIST(self, n, *args): + path = self.C2P[n] + answer = [(self.p2c(p), p.basename) for p in path.listdir(*args)] + self.channel.send(answer) + + def command_DEL(self, n): + del self.C2P[n] + + def command_BASENAME(self, n): + path = self.C2P[n] + self.channel.send(path.basename) + + def command_READ(self, n): + path = self.C2P[n] + self.channel.send(path.read()) + + def command_JOIN(self, n, n2, *args): + path = self.C2P[n] + assert n2 not in self.C2P + self.C2P[n2] = path.join(*args) + + def command_DIRPATH(self, n, n2): + path = self.C2P[n] + assert n2 not in self.C2P + self.C2P[n2] = path.dirpath() + + def serve(self): + try: + while 1: + msg = self.channel.receive() + meth = getattr(self, 'command_' + msg[0]) + meth(*msg[1:]) + except EOFError: + pass + + +if __name__ == '__main__': + import py + gw = py.execnet.PopenGateway() + channel = gw.channelfactory.new() + srv = PathServer(channel) + c = gw.remote_exec(""" + import remotepath + p = remotepath.RemotePath(channel.receive(), channel.receive()) + channel.send(len(p.listdir())) + """) + c.send(channel) + c.send(srv.p2c(py.path.local('/tmp'))) + print c.receive() Added: py/dist/py/path/gateway/channeltest2.py ============================================================================== --- (empty file) +++ py/dist/py/path/gateway/channeltest2.py Mon Dec 20 18:10:56 2004 @@ -0,0 +1,19 @@ +import py +from remotepath import RemotePath + + +SRC = open('channeltest.py', 'r').read() + +SRC += ''' +import py +srv = PathServer(channel.receive()) +channel.send(srv.p2c(py.path.local("/tmp"))) +''' + + +gw = py.execnet.SshGateway('codespeak.net') +c = gw.remote_exec(SRC) +subchannel = gw.channelfactory.new() +c.send(subchannel) + +p = RemotePath(subchannel, c.receive()) Added: py/dist/py/path/gateway/remotepath.py ============================================================================== --- (empty file) +++ py/dist/py/path/gateway/remotepath.py Mon Dec 20 18:10:56 2004 @@ -0,0 +1,44 @@ +import py, itertools +from py.__impl__.path import common + +COUNTER = itertools.count() + +class RemotePath(common.FSPathBase): + sep = '/' + + def __init__(self, c, n, basename=None): + self._c = c + self._n = n + self._basename = basename + + def __del__(self): + self._c.send(('DEL', self._n)) + + def __repr__(self): + return 'RemotePath(%s)' % self.basename + + def listdir(self, *args): + self._c.send(('LIST', self._n) + args) + return [RemotePath(self._c, n, basename) + for (n, basename) in self._c.receive()] + + def dirpath(self): + n = ~COUNTER.next() + self._c.send(('DIRPATH', self._n, n)) + return RemotePath(self._c, n) + + def join(self, *args): + n = ~COUNTER.next() + self._c.send(('JOIN', self._n, n) + args) + return RemotePath(self._c, n) + + def get(self, spec): + assert spec == 'basename' # XXX! + if self._basename is None: + self._c.send(('BASENAME', self._n)) + self._basename = self._c.receive() + return self._basename + + def read(self): + self._c.send(('READ', self._n)) + return self._c.receive() From hpk at codespeak.net Wed Dec 22 10:16:41 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 22 Dec 2004 10:16:41 +0100 (MET) Subject: [py-svn] r7959 - py/dist/doc Message-ID: <20041222091641.124EA5ACE5@thoth.codespeak.net> Author: hpk Date: Wed Dec 22 10:16:39 2004 New Revision: 7959 Modified: py/dist/doc/getting_started.txt py/dist/doc/test.txt Log: - little documentation improvements and fixes - blurb about the "generative tests" feature Modified: py/dist/doc/getting_started.txt ============================================================================== --- py/dist/doc/getting_started.txt (original) +++ py/dist/doc/getting_started.txt Wed Dec 22 10:16:39 2004 @@ -7,13 +7,15 @@ Obtaining the current py lib ============================ -Due to the nature of its goals the py lib can't be easily -released without a certain API consistency. Nevertheless, +Due to the nature of its innovative goals `the py lib`_ can't be +easily released without a certain API consistency. Nevertheless, the API is pretty stable in many respects and very well tested. So we invite you to participate and use it - especially if you share `the frustrations with current python package development`_. +.. _`the py lib`: index.html + getting it via subversion ------------------------- Modified: py/dist/doc/test.txt ============================================================================== --- py/dist/doc/test.txt (original) +++ py/dist/doc/test.txt Wed Dec 22 10:16:39 2004 @@ -8,7 +8,7 @@ starting point: ``py.test`` command line tool ============================================= -First, see `getting started`_ for how to install the 'py.test' script +First, see `getting started`_ for how to install the 'py.test' tool on your system. ``py.test`` is the command line tool to run tests. You can supply it @@ -88,13 +88,33 @@ can be customized at each level. (see `collection process`_ for some implementation details). +generative tests: yielding more tests +------------------------------------- + +*Generative tests* are test methods that are *generator functions* which +``yield`` callables and their arguments. This is most useful for running a +test function multiple times against different parameters. +Example:: + + def test_generative(): + for x in (42,17,49): + yield check, x + + def check(arg): + assert arg % 7 == 0 # second generated tests fails! + +Note that ``test_generative()`` will cause three tests +to get run, notably ``check(42)``, ``check(17)`` and ``check(49)`` +of which the middle one will obviously fail. + testing starts immediately -------------------------- -Testing starts as soon as the first ``test item``, -usually a test function or method, is collected. -There is no need to wait for a test collection phase to finish -before the actual testing process can start. +Testing starts as soon as the first ``test item`` +is collected. The collection process is iterative +and does not need to complete before your first +test items are executed. + no interference with cmdline utilities -------------------------------------- @@ -116,21 +136,20 @@ Each failing test that produced output during the running of the test will have its output displayed in the ``recorded stdout`` section. -The catching of stdout/stderr output can be disabled using the ``-S`` -option to the ``py.test`` runner. Any output will in this case be -displayed as soon as it is generated. +The catching of stdout/stderr output can be disabled using the +``--nocapture`` option to the ``py.test`` tool. Any output will +in this case be displayed as soon as it is generated. order of execution is guaranteed -------------------------------- -Great care is taken to make sure that all tests run in the order in which -they appear in the files. If you run tests multiple times you -will find that they execute in exactly the same order within -each file. - -This allows multi-stage tests where you can rely on your test to -iteratively build up a test structure. It also makes it easier to find -failing tests. +Tests will run in the order in which they appear in the files. +If you invoke ``py.test`` multiple times you should find that tests +execute in exactly the same order within each file. + +Besides making it easier to compare test output this allows +multi-stage tests where you can rely on your test to iteratively +build up a test structure. useful tracebacks, recursion detection -------------------------------------- @@ -145,11 +164,12 @@ ``py.test`` uses the same order for presenting tracebacks as Python itself: the outer function is shown first, and the most recent call is -shown last. Similarly, a ``py.test`` traceback starts with your +shown last. Similarly, a ``py.test`` reported traceback starts with your failing test function and then works its way downwards. If the maximum recursion depth has been exceeded during the running of a test, for instance because of infinite recursion, ``py.test`` will indicate -where in the code the recursion was taking place. +where in the code the recursion was taking place. You can +inhibit traceback "cutting" magic by supplying ``--fulltrace``. no inheritance requirement -------------------------- @@ -430,4 +450,4 @@ allow plain test functions (without being in a class) and allow classes to simply mean "grouping" of tests. -.. _`getting started`: getting_started +.. _`getting started`: getting_started.html From hpk at codespeak.net Wed Dec 22 10:19:21 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 22 Dec 2004 10:19:21 +0100 (MET) Subject: [py-svn] r7960 - py/dist/py/execnet Message-ID: <20041222091921.55C515ACE5@thoth.codespeak.net> Author: hpk Date: Wed Dec 22 10:19:20 2004 New Revision: 7960 Modified: py/dist/py/execnet/register.py py/dist/py/execnet/sshtesting.py Log: use localhost instead of codespeak.net as the default host for testing SSH-Gateways Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Wed Dec 22 10:19:20 2004 @@ -60,6 +60,7 @@ self.trace("child process %s already dead?" %pid) class PopenGateway(PopenCmdGateway): + # use sysfind/sysexec/subprocess instead of os.popen? def __init__(self, python=sys.executable): cmd = '%s -u -c "exec input()"' % python super(PopenGateway, self).__init__(cmd) Modified: py/dist/py/execnet/sshtesting.py ============================================================================== --- py/dist/py/execnet/sshtesting.py (original) +++ py/dist/py/execnet/sshtesting.py Wed Dec 22 10:19:20 2004 @@ -4,8 +4,10 @@ import py -REMOTE_HOST = 'codespeak.net' # set this to some place where you can ssh to -REMOTE_HOSTNAME = 'thoth.codespeak.net' # the remote's socket.gethostname() +#REMOTE_HOST = 'codespeak.net' +#REMOTE_HOSTNAME = 'thoth.codespeak.net' +REMOTE_HOST = 'localhost' # you need to have a local ssh-daemon running! +REMOTE_HOSTNAME = py.std.socket.gethostname() # the remote's socket.gethostname() def test_sshgateway(): gw = py.execnet.SshGateway(REMOTE_HOST) From hpk at codespeak.net Wed Dec 22 10:23:01 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 22 Dec 2004 10:23:01 +0100 (MET) Subject: [py-svn] r7961 - in py/dist/py/builtin: . testing Message-ID: <20041222092301.C54495ACE5@thoth.codespeak.net> Author: hpk Date: Wed Dec 22 10:23:01 2004 New Revision: 7961 Added: py/dist/py/builtin/ (props changed) py/dist/py/builtin/__init__.py (contents, props changed) py/dist/py/builtin/enumerate.py (contents, props changed) py/dist/py/builtin/testing/ Log: this is the start of cross-python-version support for certain builtins. Currently only enumerate, but basically this is the place where you can "backport" python2.4 and python2.3 builtins to run on python2.2. E.g. just use py.builtin.enumerate(...) and your programm will run on python2.2 as well. or do from py.builtin import enumerate or from py.builtin import * some backporting help welcome ... If there are serious counter arguments i'd like to hear ... Added: py/dist/py/builtin/__init__.py ============================================================================== --- (empty file) +++ py/dist/py/builtin/__init__.py Wed Dec 22 10:23:01 2004 @@ -0,0 +1 @@ +# Added: py/dist/py/builtin/enumerate.py ============================================================================== --- (empty file) +++ py/dist/py/builtin/enumerate.py Wed Dec 22 10:23:01 2004 @@ -0,0 +1,9 @@ +from __future__ import generators +try: + enumerate = enumerate +except NameError: + def enumerate(iterable): + i = 0 + for x in iterable: + yield i, x + i += 1 From hpk at codespeak.net Wed Dec 22 10:23:22 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 22 Dec 2004 10:23:22 +0100 (MET) Subject: [py-svn] r7962 - py/dist/py/misc Message-ID: <20041222092322.1F6695ACE5@thoth.codespeak.net> Author: hpk Date: Wed Dec 22 10:23:21 2004 New Revision: 7962 Modified: py/dist/py/misc/test_initpkg.py Log: inhibit unused tests Modified: py/dist/py/misc/test_initpkg.py ============================================================================== --- py/dist/py/misc/test_initpkg.py (original) +++ py/dist/py/misc/test_initpkg.py Wed Dec 22 10:23:21 2004 @@ -23,6 +23,7 @@ nodirs = ( base.join('test', 'test', 'data'), base.join('path', 'extpy', 'test_data'), + base.join('path', 'gateway',), base.join('test', 'test', 'import_test'), base.join('magic', 'greenlet'), base.join('bin'), From hpk at codespeak.net Wed Dec 22 10:37:33 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 22 Dec 2004 10:37:33 +0100 (MET) Subject: [py-svn] r7963 - in py/dist: . example/test py py/builtin/testing py/code py/code/testing py/execnet py/execnet/bin py/magic py/path py/path/extpy py/path/gateway py/path/local py/path/local/popen5 py/path/svn py/path/test py/test py/test/report/text py/test/test py/test/test/data Message-ID: <20041222093733.0E1465ACE5@thoth.codespeak.net> Author: hpk Date: Wed Dec 22 10:37:32 2004 New Revision: 7963 Added: py/dist/py/conftest.py (contents, props changed) - copied, changed from r7948, py/dist/conftest.py Removed: py/dist/conftest.py py/dist/py/magic/dyncode.py py/dist/py/magic/test_dyncode.py Modified: py/dist/example/test/failure_demo.py py/dist/py/__init__.py py/dist/py/builtin/testing/ (props changed) py/dist/py/code/excinfo.py py/dist/py/code/source.py py/dist/py/code/testing/test_excinfo.py py/dist/py/code/testing/test_source.py py/dist/py/execnet/bin/ (props changed) py/dist/py/execnet/bin/startserver.py (props changed) py/dist/py/execnet/channel.py (props changed) py/dist/py/execnet/message.py (props changed) py/dist/py/execnet/test_gateway.py (props changed) py/dist/py/magic/invoke.py py/dist/py/magic/test_invoke.py py/dist/py/path/common.py py/dist/py/path/extpy/extpy.py py/dist/py/path/extpy/inc_pseudofs.py (props changed) py/dist/py/path/extpy/inc_test_extpy.py (props changed) py/dist/py/path/gateway/channeltest.py py/dist/py/path/gateway/channeltest2.py py/dist/py/path/gateway/remotepath.py py/dist/py/path/local/local.py py/dist/py/path/local/popen5/ (props changed) py/dist/py/path/local/popen5/__init__.py (props changed) py/dist/py/path/local/popen5/subprocess.py (props changed) py/dist/py/path/local/popen5/test_subprocess.py (props changed) py/dist/py/path/local/test_local.py py/dist/py/path/local/test_posix.py (contents, props changed) py/dist/py/path/svn/svncommon.py py/dist/py/path/test/fscommon.py py/dist/py/test/defaultconfig.py (contents, props changed) py/dist/py/test/item.py (props changed) py/dist/py/test/raises.py py/dist/py/test/report/text/reporter.py py/dist/py/test/report/text/summary.py py/dist/py/test/test/data/disabled.py (props changed) py/dist/py/test/test/test_setup_nested.py (props changed) Log: ok, sorry for this *huge* checkin ... but i fixed/changed so many details during my last train ride that i can't be bothered to isolate them. Here is the summary (please read if you were using py.magic.dyncode because this is gone now!): - got rid of py.magic.dyncode in favour of the cleaner few py.code.* methods. py.code is the place to look for if you are doing dynamic code generation, e.g. co = py.code.compile(source) will do the compile that will let you access its source code with print str(py.code.Source(co)) py.code.Source is an object that offers some more nice methods and flexible ways to construct dynamic code like source = py.code.Source(""" def f(x): pass """) If you now do source.compile() the indentation will not be a problem etc.pp. I need to write documetnation for this as the API is stabilizing. - refactored the test-text-reporter to use the higher level py.code.* introspection model. Basically Source, ExceptionInfo and *Frame wrap cpython's according objects and present clean helper methods. NOTE that py.code.* and other py lib introspection code (e.g. py.path.extpy().getfilelino() will always give you linenumbers starting from 0. So if you display linenumbers to the user that you got from py.code.* somewhere be sure to +1 it. - refactored the output of the text reporter to be more clean. Please suggest improvements! - refactored execnet code to use more explicit names (Armin is a mathematician who has no problems keeping the meaning of 'c', 'n' and so on in his mind but for the rest of us it's nice to read 'channel' and 'id' instead :-) - moved the py libs test-configuration files to within the py lib so that if you use 'dist/py' as an external it's still there. - the path.get() method now always returns a list. I plan to make this method an underscore method because it is more of an implementation detail now. The official nice way to access the according information is path.basename # property returning the basename path.ext # property returning the extension part (with the dot) path.purebasename # property returning the basename without extension and so on. - fixeol Deleted: /py/dist/conftest.py ============================================================================== --- /py/dist/conftest.py Wed Dec 22 10:37:32 2004 +++ (empty file) @@ -1,20 +0,0 @@ -#pythonexecutables = ('python2.2', 'python2.3',) -#pythonexecutable = 'python2.2' - -# in the future we want to be able to say here: -#def setup_module(extpy): -# mod = extpy.resolve() -# mod.module = 23 -# directory = pypath.root.dirpath() - -import py -rootdir = py.magic.autopath().dirpath() - -# default values for options (modified from cmdline) -verbose = 0 -nocapture = False -collectonly = False -exitfirstproblem = False -fulltrace = False -showlocals = False -nomagic = False Modified: py/dist/example/test/failure_demo.py ============================================================================== --- py/dist/example/test/failure_demo.py (original) +++ py/dist/example/test/failure_demo.py Wed Dec 22 10:37:32 2004 @@ -77,8 +77,9 @@ def test_tupleerror(self): a,b = [1] - def test_reinterpret_fails(self): + def test_reinterpret_fails_with_print_for_the_fun_of_it(self): l = [1,2,3] + print "l is", l a,b = l.pop() def test_some_error(self): Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Wed Dec 22 10:37:32 2004 @@ -34,19 +34,11 @@ 'path.Invalid' : ('./path/error.py', 'Invalid'), 'path.Denied' : ('./path/error.py', 'PermissionDenied'), + 'magic.invoke' : ('./magic/invoke.py', 'invoke'), 'magic.revoke' : ('./magic/invoke.py', 'revoke'), - 'magic.revert' : ('./magic/patch.py', 'revert'), 'magic.patch' : ('./magic/patch.py', 'patch'), - 'magic.invoke' : ('./magic/invoke.py', 'invoke'), + 'magic.revert' : ('./magic/patch.py', 'revert'), 'magic.greenlet' : ('./magic/greenlet/greenlet.c', 'greenlet'), - 'magic.dyncode.tbinfo' : ('./magic/dyncode.py', 'tbinfo'), - 'magic.dyncode.listtb' : ('./magic/dyncode.py', 'listtb'), - 'magic.dyncode.getsource': ('./magic/dyncode.py', 'getsource'), - 'magic.dyncode.getlines' : ('./magic/dyncode.py', 'getlines'), - 'magic.dyncode.getline' : ('./magic/dyncode.py', 'getline'), - 'magic.dyncode.findsource': ('./magic/dyncode.py', 'findsource'), - 'magic.dyncode.compile2' : ('./magic/dyncode.py', 'compile2'), - 'magic.dyncode.compile' : ('./magic/dyncode.py', 'compile'), 'magic.autopath' : ('./magic/autopath.py', 'autopath'), 'magic.View' : ('./magic/viewtype.py', 'View'), 'magic.AssertionError' : ('./magic/assertion.py', 'AssertionError'), @@ -57,6 +49,8 @@ 'code.RunnerFrame' : ('./code/frame.py', 'RunnerFrame'), 'code.ExceptionInfo' : ('./code/excinfo.py', 'ExceptionInfo'), + 'builtin.enumerate' : ('./builtin/enumerate.py', 'enumerate'), + 'execnet.SocketGateway' : ('./execnet/register.py', 'SocketGateway'), 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), 'execnet.SshGateway' : ('./execnet/register.py', 'SshGateway'), Modified: py/dist/py/code/excinfo.py ============================================================================== --- py/dist/py/code/excinfo.py (original) +++ py/dist/py/code/excinfo.py Wed Dec 22 10:37:32 2004 @@ -22,6 +22,38 @@ for x in func(self.type, self.value): yield x + def getentries(self, startcondition=None, endcondition=None): + if startcondition is not None and not callable(startcondition): + raise TypeError("%r is not callable or None" % startcondition) + if endcondition is not None and not callable(endcondition): + raise TypeError("%r is not callable or None" % endcondition) + result = [] + gen = iter(self) + for x in gen: + if startcondition is None or startcondition(x): + result.append(x) + break + for x in gen: + result.append(x) + if endcondition is not None and endcondition(x): + break + return result + #entries = list(self) + #gen = py.builtin.enumerate(self) + #if start is not None: + # for i, entry in gen: + # p,l = entry.frame.code.path, entry.frame.code.firstlineno + # if start == i or (p,l) == start: + # break + #res = [] + #for i, entry in gen: + # res.append(entry) + # if end is not None: + # p,l = entry.frame.code.path, entry.frame.code.firstlineno + # if i == end or (p,l) == end: + # break + #return res + class TracebackEntry(object): def __init__(self, rawentry, excinfo): self._rawentry = rawentry Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Wed Dec 22 10:37:32 2004 @@ -182,15 +182,21 @@ return co def getsource(obj, **kwargs): - if hasattr(obj, 'f_code'): + if hasattr(obj, 'func_code'): + obj = obj.func_code + elif hasattr(obj, 'f_code'): obj = obj.f_code try: - return obj.co_filename.__source__ + fullsource = obj.co_filename.__source__ except AttributeError: import inspect strsrc = inspect.getsource(obj) assert isinstance(strsrc, str) return Source(strsrc, **kwargs) + else: + lineno = obj.co_firstlineno - 1 + end = fullsource.getblockend(lineno) + return fullsource[lineno:end+1] # # various helper functions Modified: py/dist/py/code/testing/test_excinfo.py ============================================================================== --- py/dist/py/code/testing/test_excinfo.py (original) +++ py/dist/py/code/testing/test_excinfo.py Wed Dec 22 10:37:32 2004 @@ -27,3 +27,39 @@ #for x in info: # print "%s:%d %s" %(x.path.relto(root), x.lineno, x.statement) #xxx + +# testchain for getentries test below +def f(): + raise ValueError +def g(): + f() +def h(): + g() + +def test_excinfo_getentries_nocut(): + excinfo = py.test.raises(ValueError, h) + entries = excinfo.getentries() + assert len(entries) == 4 # fragile + names = ['f','g','h'] + for entry in entries: + try: + names.remove(entry.frame.code.name) + except ValueError: + pass + assert not names + +def test_excinfo_getentries_cut(): + excinfo = py.test.raises(ValueError, h) + entries = excinfo.getentries( + lambda x: x.frame.code.name != 'raises', + lambda x: x.frame.code.name != 'f') + names = [x.frame.code.name for x in entries] + assert names == ['h','g'] + +def test_excinfo_getentries_type_error(): + excinfo = py.test.raises(ValueError, h) + entries = excinfo.getentries( + lambda x: x.frame.code.name != 'raises', + lambda x: x.frame.code.name != 'f') + names = [x.frame.code.name for x in entries] + assert names == ['h','g'] Modified: py/dist/py/code/testing/test_source.py ============================================================================== --- py/dist/py/code/testing/test_source.py (original) +++ py/dist/py/code/testing/test_source.py Wed Dec 22 10:37:32 2004 @@ -43,10 +43,10 @@ x = 23""" def test_syntaxerror_rerepresentation(): - ex = py.test.raises(SyntaxError, py.code.compile, 'x x')[1] - assert ex.lineno == 1 - assert ex.offset == 3 - assert ex.text.strip(), 'x x' + ex = py.test.raises(SyntaxError, py.code.compile, 'x x') + assert ex.value.lineno == 1 + assert ex.value.offset == 3 + assert ex.value.text.strip(), 'x x' def test_isparseable(): assert Source("hello").isparseable() @@ -210,3 +210,15 @@ source = list(excinfo)[-1].statement assert str(source).strip() == 'c(1)' +def test_getfuncsource_dynamic(): + source = """ + def f(): + raise ValueError + + def g(): pass + """ + co = py.code.compile(source) + exec co + assert str(py.code.Source(f)).strip() == 'def f():\n raise ValueError' + assert str(py.code.Source(g)).strip() == 'def g(): pass' + Copied: py/dist/py/conftest.py (from r7948, py/dist/conftest.py) ============================================================================== --- py/dist/conftest.py (original) +++ py/dist/py/conftest.py Wed Dec 22 10:37:32 2004 @@ -8,7 +8,7 @@ # directory = pypath.root.dirpath() import py -rootdir = py.magic.autopath().dirpath() +rootdir = py.magic.autopath().dirpath().dirpath() # default values for options (modified from cmdline) verbose = 0 Deleted: /py/dist/py/magic/dyncode.py ============================================================================== --- /py/dist/py/magic/dyncode.py Wed Dec 22 10:37:32 2004 +++ (empty file) @@ -1,259 +0,0 @@ -""" - creating code objects and keeping track of their source code - with artificial unique filenames. Provides and extends some - 'inspect' functions which usually only work with code coming - from static modules. -""" -from __future__ import generators -import sys -import inspect -import os -import re -from linecache import getline as linecache_getline -from linecache import getlines as linecache_getlines -from inspect import findsource as inspect_findsource -import linecache -import __builtin__ -from __builtin__ import compile as oldcompile -from py import magic - -def gettb(tb, n=-1): - """ return the n'th traceback. """ - return listtb(tb)[n] - -def listtb(tb): - tblist = [] - while tb: - tblist.append(tb) - tb = tb.tb_next - return tblist - -def getparseablestartingblock(obj): - if hasattr(obj, 'tb_lineno'): - lineno = obj.tb_lineno-1 - frame = obj.tb_frame - elif hasattr(obj, 'f_lineno'): - lineno = obj.f_lineno-1 - frame = obj - else: - raise ValueError, "can only grok frame and traceback objects" - #lineno_hint = frame.f_lineno - 1 - #print "getstartingblock: lineno_hint is %d" % lineno_hint - lines = magic.dyncode.getlines(frame.f_code.co_filename) - source = getsource_tryparsing(lines, lineno) - #print "getstartingblock: returning %r" % source - return source - -def getsource_tryparsing(lines, lineno_hint): - # the famous try-parsing technique is back! :-) - # first we go forward, afterwards we try parsing backwards, i don't - # see how we could do better ... given that a multiline assert - # statement has its frame.f_lineno set to the first line, while - # 'A(x,\n y)' will have its frame.f_lineno set to the second line - current = lineno_hint - while current < len(lines)+1: - source = "".join(lines[lineno_hint:current+1]) - source = source.lstrip() - if isparseable(source): - return source - current += 1 - - current = lineno_hint - while current >= 0: - source = "".join(lines[current:lineno_hint+1]) - source = source.lstrip() - if isparseable(source): - return source - current -= 1 - -def isparseable(source): - import parser - try: - parser.suite(source) - except (parser.ParserError, SyntaxError): - return False - else: - return True - -_dynfileregistry = {} - -def deindent(pysource): - """ return a list of deindented lines from the given python - source code. The first indentation offset of a non-blank - line determines the deindentation-offset for all the lines. - Subsequent lines which have a lower indentation size will - be copied verbatim as they are assumed to be part of - multilines. - """ - lines = [] - indentsize = 0 - for line in pysource.split('\n'): - if not lines: - if not line.strip(): - continue # skip first empty lines - indentsize = len(line) - len(line.lstrip()) - line = line.expandtabs() - if line.strip(): - if len(line) - len(line.lstrip()) >= indentsize: - line = line[indentsize:] - lines.append(line + '\n') - - # treat trailing whitespace-containing lines correctly - # (the python parser at least in python 2.2. is picky about it) - while lines: - line = lines.pop() - if not line.strip(): - continue - if not line.endswith('\n'): - line = line + '\n' - lines.append(line) - break - return lines - -def compile2(source, mode='exec', flag=generators.compiler_flag): - frame = inspect.currentframe(1) # the caller - return newcompile(frame, source, mode, flag) - -def newcompile(frame, source, mode='exec', flag=generators.compiler_flag): - lines = deindent(source) - source = "".join(lines) - origin = "%s:%d" % (frame.f_code.co_filename, frame.f_lineno) - filename = _makedynfilename(origin, lines) - #print "frames filename", frame.f_code.co_filename - #print "making dynfilename", filename - try: - #print "compiling source:", repr(source) - co = oldcompile(source+'\n', filename, mode, flag) - except SyntaxError, ex: - # re-represent syntax errors from parsing python strings - newex = SyntaxError('\n'.join([ - "".join(lines[:ex.lineno]), - " " * ex.offset + '^', - "syntax error probably generated here: %s" % origin])) - newex.offset = ex.offset - newex.lineno = ex.lineno - newex.text = ex.text - raise newex - _dynfileregistry[filename] = origin, lines, co - return co - -builtin_compile = compile -def compile(source, filename, mode='exec', flag=generators.compiler_flag): - if os.path.exists(filename): - return builtin_compile(source, filename, mode, flag) - frame = inspect.currentframe(1) # the caller - return newcompile(frame, source, mode, flag) - -def invoke(): - # a hack for convenience of displaying tracebacks - magic.patch(linecache, 'getline', getline) - magic.patch(linecache, 'getlines', getlines) - magic.patch(__builtin__, 'compile', compile) - magic.patch(inspect, 'findsource', findsource) - -def revoke(): - magic.revert(linecache, 'getline') - magic.revert(linecache, 'getlines') - magic.revert(__builtin__, 'compile') - magic.revert(inspect, 'findsource') - -def tbinfo(tb, index = -1): - """ return an (filename:lineno, linecontent) tuple for the given - traceback. Works also with code generated from compile2(). - """ - tb = gettb(tb, index) - filename = tb.tb_frame.f_code.co_filename - lineno = tb.tb_lineno - return filename, lineno - -def getframe(tb, index = -1): - """ return an (filename:lineno, linecontent) tuple for the given - traceback. Works also with code generated from compile2(). - """ - tb = gettb(tb, index) - return tb.tb_frame - -def _makedynfilename(origin, source, originmap={}): - origin = origin - entry = originmap.setdefault(origin, []) - num = len(entry) - entry.append(num) - if num > 0: - return "<%s [%d]>" % (origin, num) - else: - return "<%s>" % origin - -def getline(filename, lineno): - """ return line in filename (possibly dyncode). """ - tup = _dynfileregistry.get(filename, None) - if tup is not None: - origin, lines, co = tup - return lines[lineno-1] - else: - return linecache_getline(filename, lineno) - -def getlines(filename): - """ return line in filename (possibly dyncode). """ - tup = _dynfileregistry.get(filename, None) - if tup is not None: - origin, lines, co = tup - return lines - else: - return linecache_getlines(filename) - - -def getcode(obj): - if inspect.iscode(obj): - return obj - for name in ('func_code', 'f_code'): - if hasattr(obj, name): - return getattr(obj, name) - -def getsourcelines(object): - """Return a list of source lines and starting line number for an object. - - The argument may be a module, class, method, function, traceback, - frame, or (a dynamically created) code object. The source code is - returned as a list of the lines corresponding to the object and the - line number indicates where in the original source file the first - line of code was found. An IOError is raised if the source code - cannot be retrieved. - """ - lines, lnum = findsource(object) - if inspect.ismodule(object): - return lines, 0 - else: - return inspect.getblock(lines[lnum:]), lnum + 1 - -def getsource(object): - """Return the text of the source code for an object. - - The argument may be a module, class, method, function, traceback, frame, - or code object. The source code is returned as a single string. An - IOError is raised if the source code cannot be retrieved.""" - lines, lnum = getsourcelines(object) - return ''.join(lines) - -def findsource(obj): - obj = getcode(obj) - filename = obj.co_filename - if _dynfileregistry.has_key(filename): - origin, lines, gencode = _dynfileregistry[filename] - - lnum = obj.co_firstlineno - 1 - print "firstlineno", lnum - pat = re.compile(r'^(\s*def\s)|(.*\slambda(:|\s))') - while lnum > 0: - if pat.match(lines[lnum]): - break - lnum = lnum - 1 - return lines, lnum - else: - return inspect_findsource(obj) - -def getpyfile(obj): - fn = inspect.getfile(obj) - if fn.endswith('.pyc'): - fn = fn[:-1] - assert fn.endswith('.py') - return fn Modified: py/dist/py/magic/invoke.py ============================================================================== --- py/dist/py/magic/invoke.py (original) +++ py/dist/py/magic/invoke.py Wed Dec 22 10:37:32 2004 @@ -1,29 +1,18 @@ -def invoke(dyncode=False, assertion=False): +def invoke(assertion=False): """ invoke magic, currently you can specify: - dyncode patches some syslibs and the compile builtins - to support better traces for dynamically compiled - code. e.g. inspect.getsource(compile('...', ...)) - should work. - assertion patches the builtin AssertionError to try to give more meaningful AssertionErrors, which by means of deploying a mini-interpreter constructs a useful error message. """ - if dyncode: - from py.__impl__.magic import dyncode - dyncode.invoke() if assertion: from py.__impl__.magic import assertion assertion.invoke() -def revoke(dyncode=False, assertion=False): +def revoke(assertion=False): """ revoke previously invoked magic (see invoke()).""" - if dyncode: - from py.__impl__.magic import dyncode - dyncode.revoke() if assertion: from py.__impl__.magic import assertion assertion.revoke() Deleted: /py/dist/py/magic/test_dyncode.py ============================================================================== --- /py/dist/py/magic/test_dyncode.py Wed Dec 22 10:37:32 2004 +++ (empty file) @@ -1,97 +0,0 @@ -import sys -import os -#print "dyncode_test: __name__ ==", __name__ -from py.__impl__.magic import dyncode, exprinfo -import py - -def setup_module(mod): - py.magic.invoke(dyncode=1) -def teardown_module(mod): - py.magic.revoke(dyncode=1) - -def test_dyncode_trace(): - source = """ - def f(): - raise ValueError - """ - co = dyncode.compile2(source) - exec co - excinfo = py.test.raises(ValueError, f) - filename, lineno = dyncode.tbinfo(excinfo[2]) - line = dyncode.getline(filename, lineno) - assert line.strip() == 'raise ValueError' - -def test_dyncode_trace_multiple(): - test_dyncode_trace() - test_dyncode_trace() - -def test_unique_filenames(): - fn1 = dyncode._makedynfilename('fn','source') - fn2 = dyncode._makedynfilename('fn','source') - assert fn1 != fn2 - -def test_syntaxerror_rerepresentation(): - ex = py.test.raises(SyntaxError, dyncode.compile2, 'x x')[1] - assert ex.lineno == 1 - assert ex.offset == 3 - assert ex.text.strip(), 'x x' - -def test_getfuncsource_dynamic(): - source = """ - def f(): - raise ValueError - - def g(): pass - """ - co = dyncode.compile2(source) - exec co - source = dyncode.getsource(f) - assert dyncode.getsource(f) == 'def f():\n raise ValueError\n' - assert dyncode.getsource(g) == 'def g(): pass\n' - lines, lnum = dyncode.findsource(g) - lines = lines[lnum:] - assert lines and lines[0] == 'def g(): pass\n' - -def test_getpyfile(): - fn = dyncode.getpyfile(dyncode) - assert os.path.exists(fn) - -def DEPRECATED_test_getstartingblock_singleline(): - class A: - def __init__(self, *args): - frame = sys._getframe(1) - self.source = dyncode.getparseablestartingblock(frame) - - x = A('x', 'y') - - l = [i for i in x.source.split('\n') if i.strip()] - assert len(l) == 1 - -def DEPRECATED_test_getstartingblock_multiline(): - class A: - def __init__(self, *args): - frame = sys._getframe(1) - self.source = dyncode.getparseablestartingblock(frame) - - x = A('x', - 'y' \ - , - 'z') - - l = [i for i in x.source.split('\n') if i.strip()] - assert len(l) == 4 - -def DEPRECATED_test_getline_finally(): - def c(): pass - excinfo = py.test.raises(TypeError, """ - teardown = None - try: - c(1) - finally: - if teardown: - teardown() - """) - tb = dyncode.gettb(excinfo[2], -1) - source = dyncode.getparseablestartingblock(tb) - assert source.strip() == 'c(1)' - Modified: py/dist/py/magic/test_invoke.py ============================================================================== --- py/dist/py/magic/test_invoke.py (original) +++ py/dist/py/magic/test_invoke.py Wed Dec 22 10:37:32 2004 @@ -2,27 +2,11 @@ import py import inspect -def check_dyncode(): - co = compile('x=3', 'bogus', 'exec') - s = inspect.getfile(co) - assert s - line = inspect.getsource(co) - assert line.strip() == "x=3" - def check_assertion(): excinfo = py.test.raises(AssertionError, "assert 1 == 2") value = excinfo.value assert str(value) == "assert 1 == 2" -def test_invoke_dyncode(): - old = compile - py.magic.invoke(dyncode=True) - try: - assert compile != old - check_dyncode() - finally: - py.magic.revoke(dyncode=True) - def test_invoke_assertion(): py.magic.invoke(assertion=True) try: Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Wed Dec 22 10:37:32 2004 @@ -107,7 +107,7 @@ return p.check() def basename(self): - return self.get('basename') + return self.get('basename')[0] basename = property(basename, None, None, 'basename part of path') def parts(self, reverse=False): @@ -200,7 +200,7 @@ """ pattern = self.pattern if pattern.find(path.sep) == -1: - name = path.get('basename') + name = path.basename else: name = str(path) # path.strpath # XXX svn? pattern = '*' + path.sep + pattern @@ -223,7 +223,7 @@ def ext(self, arg): if not arg.startswith('.'): arg = '.' + arg - return self.path.get('ext') == arg + return self.path.ext == arg class FSPathBase(PathBase): """ shared implementation for filesystem path objects.""" @@ -240,11 +240,11 @@ return self.new(basename='').join(*args, **kwargs) def ext(self): - return self.get('ext') + return self.get('ext')[0] ext = property(ext, None, None, 'extension part of path') def purebasename(self): - return self.get('purebasename') + return self.get('purebasename')[0] purebasename = property(purebasename, None, None, 'basename without extension') def read(self, mode='rb'): @@ -310,8 +310,7 @@ def getpycodeobj(self): s = self.read() # XXX str(self) should show up somewhere in the code's filename - return py.magic.dyncode.compile2(s) - + return py.code.compile(s) class PathStr(str): def __init__(self, path): Modified: py/dist/py/path/extpy/extpy.py ============================================================================== --- py/dist/py/path/extpy/extpy.py (original) +++ py/dist/py/path/extpy/extpy.py Wed Dec 22 10:37:32 2004 @@ -71,13 +71,7 @@ for name in spec.split(','): if name == 'basename': l.append(modparts[-1]) - - if len(l) == 1: - return l[0] - elif len(l) == 0: - return None - else: - return l + return l def resolve(self): """return the python object, obtained from traversing from @@ -140,10 +134,10 @@ if inspect.isfunction(obj): obj = obj.func_code if inspect.iscode(obj): - return py.path.local(obj.co_filename), obj.co_firstlineno + return py.path.local(obj.co_filename), obj.co_firstlineno - 1 else: source, lineno = inspect.findsource(obj) - return x.getfile(), lineno + return x.getfile(), lineno - 1 def visit(self, fil=None, rec=None, ignore=None, seen=None): def myrec(p, seen={id(self): True}): Modified: py/dist/py/path/gateway/channeltest.py ============================================================================== --- py/dist/py/path/gateway/channeltest.py (original) +++ py/dist/py/path/gateway/channeltest.py Wed Dec 22 10:37:32 2004 @@ -10,36 +10,36 @@ threading.Thread(target=self.serve).start() def p2c(self, path): - n = self.next_id + id = self.next_id self.next_id += 1 - self.C2P[n] = path - return n + self.C2P[id] = path + return id - def command_LIST(self, n, *args): - path = self.C2P[n] + def command_LIST(self, id, *args): + path = self.C2P[id] answer = [(self.p2c(p), p.basename) for p in path.listdir(*args)] self.channel.send(answer) - def command_DEL(self, n): - del self.C2P[n] + def command_DEL(self, id): + del self.C2P[id] - def command_BASENAME(self, n): - path = self.C2P[n] - self.channel.send(path.basename) + def command_GET(self, id, spec): + path = self.C2P[id] + self.channel.send(path.get(spec)) - def command_READ(self, n): - path = self.C2P[n] + def command_READ(self, id): + path = self.C2P[id] self.channel.send(path.read()) - def command_JOIN(self, n, n2, *args): - path = self.C2P[n] - assert n2 not in self.C2P - self.C2P[n2] = path.join(*args) - - def command_DIRPATH(self, n, n2): - path = self.C2P[n] - assert n2 not in self.C2P - self.C2P[n2] = path.dirpath() + def command_JOIN(self, id, resultid, *args): + path = self.C2P[id] + assert resultid not in self.C2P + self.C2P[resultid] = path.join(*args) + + def command_DIRPATH(self, id, resultid): + path = self.C2P[id] + assert resultid not in self.C2P + self.C2P[resultid] = path.dirpath() def serve(self): try: @@ -50,7 +50,6 @@ except EOFError: pass - if __name__ == '__main__': import py gw = py.execnet.PopenGateway() Modified: py/dist/py/path/gateway/channeltest2.py ============================================================================== --- py/dist/py/path/gateway/channeltest2.py (original) +++ py/dist/py/path/gateway/channeltest2.py Wed Dec 22 10:37:32 2004 @@ -11,7 +11,8 @@ ''' -gw = py.execnet.SshGateway('codespeak.net') +#gw = py.execnet.SshGateway('codespeak.net') +gw = py.execnet.PopenGateway() c = gw.remote_exec(SRC) subchannel = gw.channelfactory.new() c.send(subchannel) Modified: py/dist/py/path/gateway/remotepath.py ============================================================================== --- py/dist/py/path/gateway/remotepath.py (original) +++ py/dist/py/path/gateway/remotepath.py Wed Dec 22 10:37:32 2004 @@ -6,39 +6,42 @@ class RemotePath(common.FSPathBase): sep = '/' - def __init__(self, c, n, basename=None): - self._c = c - self._n = n + def __init__(self, channel, id, basename=None): + self._channel = channel + self._id = id self._basename = basename + self._specs = {} def __del__(self): - self._c.send(('DEL', self._n)) + self._channel.send(('DEL', self._id)) def __repr__(self): return 'RemotePath(%s)' % self.basename def listdir(self, *args): - self._c.send(('LIST', self._n) + args) - return [RemotePath(self._c, n, basename) - for (n, basename) in self._c.receive()] + self._channel.send(('LIST', self._id) + args) + return [RemotePath(self._channel, id, basename) + for (id, basename) in self._channel.receive()] def dirpath(self): - n = ~COUNTER.next() - self._c.send(('DIRPATH', self._n, n)) - return RemotePath(self._c, n) + id = ~COUNTER.next() + self._channel.send(('DIRPATH', self._id, id)) + return RemotePath(self._channel, id) def join(self, *args): - n = ~COUNTER.next() - self._c.send(('JOIN', self._n, n) + args) - return RemotePath(self._c, n) + id = ~COUNTER.next() + self._channel.send(('JOIN', self._id, id) + args) + return RemotePath(self._channel, id) def get(self, spec): - assert spec == 'basename' # XXX! - if self._basename is None: - self._c.send(('BASENAME', self._n)) - self._basename = self._c.receive() - return self._basename + parts = spec.split(',') + ask = [x for x in parts if x not in self._specs] + if ask: + self._channel.send(('GET', self._id, ",".join(ask))) + for part, value in zip(ask, self._channel.receive()): + self._specs[part] = value + return [self._specs[x] for x in parts] def read(self): - self._c.send(('READ', self._n)) - return self._c.receive() + self._channel.send(('READ', self._id)) + return self._channel.receive() Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Wed Dec 22 10:37:32 2004 @@ -85,22 +85,24 @@ |--| ext """ obj = object.__new__(self.__class__) + drive, dirname, basename, purebasename,ext = self.get( + "drive,dirname,basename,purebasename,ext") if 'basename' in kw: if 'purebasename' in kw or 'ext' in kw: - raise ValueError("invalid specification") + raise ValueError("invalid specification %r" % kw) else: - pb = kw.setdefault('purebasename', self.get('purebasename')) - if 'ext' in kw: + pb = kw.setdefault('purebasename', purebasename) + try: ext = kw['ext'] + except KeyError: + pass + else: if ext and not ext.startswith('.'): ext = '.' + ext - else: - ext = self.get('ext') kw['basename'] = pb + ext - kw.setdefault('drive', self.get('drive')) - kw.setdefault('dirname', self.get('dirname')) - + kw.setdefault('drive', drive) + kw.setdefault('dirname', dirname) kw.setdefault('sep', self.sep) obj.strpath = os.path.normpath( "%(drive)s%(dirname)s%(sep)s%(basename)s" % kw) @@ -143,13 +145,7 @@ append(ext) else: raise ValueError, "invalid part specification %r" % name - - if len(res) == 1: - return res[0] - elif len(res) == 0: - return None - else: - return res + return res def join(self, *args, **kwargs): """ return a new path by appending all 'args' as path @@ -451,7 +447,7 @@ def parse_num(path): """ parse the number out of a path (if it matches the base) """ - bn = path.get('basename') + bn = path.basename if bn.startswith(base): try: return int(bn[len(base):]) Modified: py/dist/py/path/local/test_local.py ============================================================================== --- py/dist/py/path/local/test_local.py (original) +++ py/dist/py/path/local/test_local.py Wed Dec 22 10:37:32 2004 @@ -183,7 +183,7 @@ for i in range(10): numdir = local.make_numbered_dir(root, 'base.', keep=2) assert numdir.check() - assert numdir.get('basename') == 'base.%d' %i + assert numdir.basename == 'base.%d' %i if i>=1: assert numdir.new(ext=str(i-1)).check() if i>=2: Modified: py/dist/py/path/local/test_posix.py ============================================================================== --- py/dist/py/path/local/test_posix.py (original) +++ py/dist/py/path/local/test_posix.py Wed Dec 22 10:37:32 2004 @@ -104,7 +104,7 @@ filepath.write("") linkpath.mksymlinkto(filepath) realpath = linkpath.realpath() - assert realpath.get('basename') == 'file' + assert realpath.basename == 'file' finally: tmpdir.remove(rec=1) Modified: py/dist/py/path/svn/svncommon.py ============================================================================== --- py/dist/py/path/svn/svncommon.py (original) +++ py/dist/py/path/svn/svncommon.py Wed Dec 22 10:37:32 2004 @@ -36,18 +36,19 @@ """ obj = object.__new__(self.__class__) obj.rev = kw.get('rev', self.rev) - + dirname, basename, purebasename, ext = self.get( + "dirname,basename,purebasename,ext") if 'basename' in kw: if 'purebasename' in kw or 'ext' in kw: - raise ValueError("invalid specification") + raise ValueError("invalid specification %r" % kw) else: - pb = kw.setdefault('purebasename', self.get('purebasename')) - ext = kw.setdefault('ext', self.get('ext')) + pb = kw.setdefault('purebasename', purebasename) + ext = kw.setdefault('ext', ext) if ext and not ext.startswith('.'): ext = '.' + ext kw['basename'] = pb + ext - kw.setdefault('dirname', self.get('dirname')) + kw.setdefault('dirname', dirname) kw.setdefault('sep', self.sep) if kw['basename']: obj.strpath = "%(dirname)s%(sep)s%(basename)s" % kw @@ -89,11 +90,7 @@ res.append(ext) else: raise NameError, "Don't know part %r" % name - if len(res) == 1: - return res[0] - elif len(res) == 0: - return None - return res + return res def __eq__(self, other): """ return true if path and rev attributes each match """ Modified: py/dist/py/path/test/fscommon.py ============================================================================== --- py/dist/py/path/test/fscommon.py (original) +++ py/dist/py/path/test/fscommon.py Wed Dec 22 10:37:32 2004 @@ -66,7 +66,7 @@ def test_dotted_name_ext(self): newpath = self.root.join('a.b.c') - ext = newpath.get('ext') + ext = newpath.ext assert ext == '.c' assert newpath.ext == '.c' Modified: py/dist/py/test/defaultconfig.py ============================================================================== --- py/dist/py/test/defaultconfig.py (original) +++ py/dist/py/test/defaultconfig.py Wed Dec 22 10:37:32 2004 @@ -8,6 +8,8 @@ def getreporter(): return py.test.TextReporter() +additionalinfo = None + options = ('py.test standard options', [ Option('-v', '--verbose', action="count", dest="verbose", default=0, Modified: py/dist/py/test/raises.py ============================================================================== --- py/dist/py/test/raises.py (original) +++ py/dist/py/test/raises.py Wed Dec 22 10:37:32 2004 @@ -32,9 +32,9 @@ try: func(*args[1:], **kwargs) except ExpectedException: - return sys.exc_info() + return py.code.ExceptionInfo() except Exception, e: - excinfo = sys.exc_info() + excinfo = py.code.ExceptionInfo() else: excinfo = None k = ", ".join(["%s=%r" % x for x in kwargs.items()]) Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Wed Dec 22 10:37:32 2004 @@ -34,10 +34,17 @@ def start(self): self.out.sep("=", "test process starts") - mode = py.test.config.option.session and 'session/child process' or 'inprocess' + mode = py.test.config.option.session and \ + 'session/child process' or 'inprocess' self.out.line("testing-mode: %s" % mode) self.out.line("executable : %s (%s)" % (py.std.sys.executable, repr_pythonversion())) + for i, x in py.builtin.enumerate(py.test.config.configpaths): + self.out.line("initial testconfig %d: %s" %(i, x)) + additional = py.test.config.getfirst('additionalinfo') + if additional: + for key, descr in additional(): + self.out.line("%s: %s" %(key, descr)) self.out.sep("=") if not self.option.nomagic: py.magic.invoke(assertion=1) Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Wed Dec 22 10:37:32 2004 @@ -19,7 +19,7 @@ self.out.sep("_") self.out.sep("_", "Collect Error") self.out.line() - self.repr_traceback_raw(error.excinfo) + self.repr_traceback_raw(error.excinfo.getentries()) #self.repr_failure_result(res) #self.out.line() #if isinstance(res.excinfo[1], AssertionError): @@ -80,18 +80,12 @@ getattr(res, 'tbindex', -1)) #self.out.line() self.repr_failure_result(res) - try: - out, err = res.out, res.err - except AttributeError: - pass - else: - if out.strip(): - self.out.sep("- ", "recorded stdout") - self.out.line(out.strip()) - if err.strip(): - self.out.sep("- ", "recorded stderr") - self.out.line(err.strip()) - #self.out.line() + for name in 'out', 'err': + if hasattr(res, name): + out = getattr(res, name) + if out.strip(): + self.out.sep("- ", "recorded std%s" % name) + self.out.line(out.strip()) def repr_failure_result(self, res): cls = res.excinfo.type @@ -139,7 +133,7 @@ self.out.line(line) def repr_source(self, tb): - # represent the source code + # represent source code for a given traceback entry self.out.line() source = tb.frame.code.fullsource if not source: @@ -148,7 +142,6 @@ #self.out.line("(f_lineno = %r)" % (frame.f_lineno)) return start = tb.frame.code.firstlineno - #end = source.getblockend(start) end = tb.lineno for line in source[start:end]: @@ -159,43 +152,26 @@ self.out.line(">" + line) return - #def tracelines(self, filename, lineno): - # prelines = 3 - # if prelines: - # for i in range(lineno-prelines-1, lineno): - # line = py.magic.dyncode.getline(filename, i) - # self.out.line(" %s" % line.rstrip()) - # line = py.magic.dyncode.getline(filename, lineno) - # self.out.line("> %s" % line.rstrip()) - - def forward_traceback_to_test(self, tbentries, path): - for i in range(len(tbentries)): - tb = tbentries[i] - #print "eq", tb.frame.code.path, path - if tb.frame.code.path == path: - break - return tbentries[i:] - - def repr_traceback(self, item, excinfo, tbindex=-1): - t_file, t_lineno = item.extpy.getfilelineno() - fspath = item.extpy.root - tbentries = list(excinfo) - if fspath and not self.option.fulltrace: - tbentries = self.forward_traceback_to_test(tbentries, fspath) - else: - tbindex = -1 - self.repr_traceback_raw(tbentries, tbindex) - #t_file, t_lineno = unit.extpy.getfilelineno() - #if t_file != filename or lineno != t_lineno: - #self.out.line("%s, line %d" % (unit.extpy, t_lineno) ) - #self.out.line("%s, line %d" % (item.extpy, t_lineno) ) + def cut_traceback(self, excinfo, item=None, tbindex=None): + if self.option.fulltrace or item is None: + startcondition = endcondition = None + else: + path,lineno = item.extpy.getfilelineno() + def startcondition(entry): + return entry.frame.code.path == path and \ + entry.frame.code.firstlineno == lineno + endcondition = None + entries = excinfo.getentries(startcondition, endcondition) + if tbindex is not None and tbindex < -1: + entries = entries[:tbindex] + return entries + + def repr_traceback(self, item, excinfo, tbindex=None): + tbentries = self.cut_traceback(excinfo, item, tbindex) + self.repr_traceback_raw(tbentries) self.out.sep('-') - #self.out.sep('-') - - def repr_traceback_raw(self, tbentries, tbindex=-1): - if tbindex < -1 and not self.option.fulltrace: - tbentries = tbentries[:tbindex+1] + def repr_traceback_raw(self, tbentries): recursioncache = {} first = True for tb in tbentries: From hpk at codespeak.net Wed Dec 22 10:38:14 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 22 Dec 2004 10:38:14 +0100 (MET) Subject: [py-svn] r7964 - py/dist/py/builtin/testing Message-ID: <20041222093814.108F05ACE5@thoth.codespeak.net> Author: hpk Date: Wed Dec 22 10:38:13 2004 New Revision: 7964 Added: py/dist/py/builtin/testing/__init__.py py/dist/py/builtin/testing/test_enumerate.py Log: of course the usual "forgot to checkin this" checkin ... Added: py/dist/py/builtin/testing/__init__.py ============================================================================== --- (empty file) +++ py/dist/py/builtin/testing/__init__.py Wed Dec 22 10:38:13 2004 @@ -0,0 +1 @@ +# Added: py/dist/py/builtin/testing/test_enumerate.py ============================================================================== --- (empty file) +++ py/dist/py/builtin/testing/test_enumerate.py Wed Dec 22 10:38:13 2004 @@ -0,0 +1,7 @@ +import sys + +if sys.version_info < (2,3,0): + def test_enumerate(self): + l = [0,1,2] + for i,x in enumerate(l): + assert i == x From hpk at codespeak.net Wed Dec 22 10:42:47 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 22 Dec 2004 10:42:47 +0100 (MET) Subject: [py-svn] r7965 - in py/dist/py: . test Message-ID: <20041222094247.53C245ACE5@thoth.codespeak.net> Author: hpk Date: Wed Dec 22 10:42:46 2004 New Revision: 7965 Modified: py/dist/py/initpkg.py py/dist/py/test/run.py Log: small printing and formatting improvements Modified: py/dist/py/initpkg.py ============================================================================== --- py/dist/py/initpkg.py (original) +++ py/dist/py/initpkg.py Wed Dec 22 10:42:46 2004 @@ -103,8 +103,10 @@ """ return string representing a zipfile containing the package. """ import zipfile import py - try: from cStringIO import StringIO - except ImportError: from StringIO import StringIO + try: + from cStringIO import StringIO + except ImportError: + from StringIO import StringIO base = py.__package__.getpath().dirpath() outf = StringIO() f = zipfile.ZipFile(outf, 'w', compression=zipfile.ZIP_DEFLATED) Modified: py/dist/py/test/run.py ============================================================================== --- py/dist/py/test/run.py (original) +++ py/dist/py/test/run.py Wed Dec 22 10:42:46 2004 @@ -176,7 +176,9 @@ failures = failure_master(args, filenames, failures) else: failures = master(args, filenames) - print "#" * 60 - print "# session mode: %d failures remaining" % len(failures) - print "# watching py files below %s" % rootdir - print "# ", "^" * l + print "#" * 60 + print "# session mode: %d failures remaining" % len(failures) + for x in failures: + print "Failure at: %r" % (x,) + print "# watching py files below %s" % rootdir + print "# ", "^" * l From hpk at codespeak.net Wed Dec 22 11:21:21 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 22 Dec 2004 11:21:21 +0100 (MET) Subject: [py-svn] r7966 - in py/dist/py: . misc path/local test/report/text Message-ID: <20041222102121.981F45ACE5@thoth.codespeak.net> Author: hpk Date: Wed Dec 22 11:21:21 2004 New Revision: 7966 Modified: py/dist/py/initpkg.py py/dist/py/misc/test_initpkg.py py/dist/py/path/local/local.py py/dist/py/test/report/text/reporter.py Log: added py-lib information to the test-process header (if many people think this is unneccessary then i can probably show it only when testing the py lib itself by putting it in py/conftest.py ...) This is useful when you are having multiple checkouts and may otherwise not exactly know which version of the py lib is really used ... added tests appropriately. Modified: py/dist/py/initpkg.py ============================================================================== --- py/dist/py/initpkg.py (original) +++ py/dist/py/initpkg.py Wed Dec 22 11:21:21 2004 @@ -117,6 +117,16 @@ f.close() return outf.getvalue() + def getrev(self): + import py + p = py.path.svnwc(self.module.__file__).dirpath() + try: + return p.info().rev + except (KeyboardError, MemoryError, SystemExit): + raise + except: + return 'unknown' + def setmodule(modpath, module): #print "sys.modules[%r] = %r" % (modpath, module) sys.modules[modpath] = module Modified: py/dist/py/misc/test_initpkg.py ============================================================================== --- py/dist/py/misc/test_initpkg.py (original) +++ py/dist/py/misc/test_initpkg.py Wed Dec 22 11:21:21 2004 @@ -46,7 +46,16 @@ def test_getzipdata(): s = py.__package__.getzipdata() - + +def test_getrev(): + d = py.__package__.getrev() + try: + svnversion = py.path.local.sysfind('svnversion') + except py.path.NotFound: + py.test.skip("cannot test svnversion, 'svnversion' binary not found") + v = svnversion.sysexec(py.path.local(py.__file__).dirpath()) + assert v.startswith(str(d)) + # the following test should abasically work in the future def XXXtest_virtual_on_the_fly(): py.initpkg('my', { Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Wed Dec 22 11:21:21 2004 @@ -381,6 +381,7 @@ invoked and not through a system shell. """ from py.__impl__.path.local.popen5.subprocess import Popen, PIPE + argv = map(str, argv) proc = Popen([str(self)] + list(argv), stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate() ret = proc.wait() Modified: py/dist/py/test/report/text/reporter.py ============================================================================== --- py/dist/py/test/report/text/reporter.py (original) +++ py/dist/py/test/report/text/reporter.py Wed Dec 22 11:21:21 2004 @@ -38,7 +38,12 @@ 'session/child process' or 'inprocess' self.out.line("testing-mode: %s" % mode) self.out.line("executable : %s (%s)" % - (py.std.sys.executable, repr_pythonversion())) + (py.std.sys.executable, repr_pythonversion())) + + rev = py.__package__.getrev() + self.out.line("using py lib: %s " % ( + py.path.local(py.__file__).dirpath(), rev)) + for i, x in py.builtin.enumerate(py.test.config.configpaths): self.out.line("initial testconfig %d: %s" %(i, x)) additional = py.test.config.getfirst('additionalinfo') From hpk at codespeak.net Wed Dec 22 11:24:34 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 22 Dec 2004 11:24:34 +0100 (MET) Subject: [py-svn] r7967 - py/dist/example/test Message-ID: <20041222102434.6DC385AF29@thoth.codespeak.net> Author: hpk Date: Wed Dec 22 11:24:33 2004 New Revision: 7967 Modified: py/dist/example/test/failure_demo.py Log: added Matthew's test fragment posted on py-dev slightly modified to work with the new py.code. stuff Modified: py/dist/example/test/failure_demo.py ============================================================================== --- py/dist/example/test/failure_demo.py (original) +++ py/dist/example/test/failure_demo.py Wed Dec 22 11:24:33 2004 @@ -1,4 +1,5 @@ from py.test import raises +import py def otherfunc(a,b): assert a==b @@ -94,6 +95,16 @@ def test_generator2(self): yield self.func1 + +# thanks to Matthew Scott for this test +def test_dynamic_compile_shows_nicely(): + src = 'def foo():\n assert 1 == 0\n' + name = 'abc-123' + module = py.std.imp.new_module(name) + code = py.code.compile(src, name, 'exec') + exec code in module.__dict__ + py.std.sys.modules[name] = module + module.foo() def globf(x): From hpk at codespeak.net Wed Dec 22 11:27:57 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 22 Dec 2004 11:27:57 +0100 (MET) Subject: [py-svn] r7968 - in py/dist: doc py/documentation Message-ID: <20041222102757.9349B5AFE9@thoth.codespeak.net> Author: hpk Date: Wed Dec 22 11:27:57 2004 New Revision: 7968 Added: py/dist/py/documentation/ - copied from r7965, py/dist/doc/ Removed: py/dist/doc/ Log: moved documentation below the source-tree (examples will follow) From hpk at codespeak.net Wed Dec 22 11:30:39 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 22 Dec 2004 11:30:39 +0100 (MET) Subject: [py-svn] r7969 - in py/dist: example py/documentation py/documentation/example py/documentation/example/test Message-ID: <20041222103039.0FC375B0B0@thoth.codespeak.net> Author: hpk Date: Wed Dec 22 11:30:39 2004 New Revision: 7969 Added: py/dist/py/documentation/example/ - copied from r7965, py/dist/example/ py/dist/py/documentation/example/test/ - copied from r7968, py/dist/example/test/ Removed: py/dist/example/ Modified: py/dist/py/documentation/test.txt Log: moved example directory below the documentation directory lib Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Wed Dec 22 11:30:39 2004 @@ -157,7 +157,7 @@ A lot of care is taken to present nice tracebacks in case of test failure. Try:: - py.test example/test/failure_demo.py + py.test py/documentation/example/test/failure_demo.py to see a variety of 17 tracebacks, each tailored to a different failure situation. @@ -259,7 +259,7 @@ Here is a working example for what goes on when you setup modules, classes and methods:: - # [[from example/test/test_setup_flow_example.py]] + # [[from py/documentation/example/test/test_setup_flow_example.py]] def setup_module(module): module.TestStateFullThing.classcount = 0 From hpk at codespeak.net Wed Dec 22 13:22:52 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 22 Dec 2004 13:22:52 +0100 (MET) Subject: [py-svn] r7970 - in py/dist/py: bin documentation misc test Message-ID: <20041222122252.E3FC75ACE5@thoth.codespeak.net> Author: hpk Date: Wed Dec 22 13:22:52 2004 New Revision: 7970 Modified: py/dist/py/bin/py.rest py/dist/py/documentation/rest_test.py py/dist/py/misc/test_initpkg.py py/dist/py/test/collect.py py/dist/py/test/drive.py Log: some cleanup after the move of the documentation (which also includes some tests that will now fail if docutils generates a warning while processing them) Modified: py/dist/py/bin/py.rest ============================================================================== --- py/dist/py/bin/py.rest (original) +++ py/dist/py/bin/py.rest Wed Dec 22 13:22:52 2004 @@ -32,6 +32,8 @@ 'stylesheet' : stylesheet, 'traceback' : 1, 'output_encoding' : 'latin1', + #'halt' : 0, # 'info', + 'halt_level' : 2, } return publish_string(source, str(source_path), writer_name='html', settings_overrides=kwargs) @@ -48,20 +50,13 @@ stylesheet = style.basename else: stylesheet = None - try: - doc = convert_rest_html(txtpath.read(), txtpath, - stylesheet=stylesheet) - except: - print "failed to parse %r" % txtpath - import traceback - print traceback.print_exc() - raise SystemExit(1) - else: - htmlpath.write(doc) - #log("wrote %r" % htmlpath) - #if txtpath.check(svnwc=1, versioned=1): - # info = txtpath.info() - # svninfopath.dumpobj(info) + doc = convert_rest_html(txtpath.read(), txtpath, + stylesheet=stylesheet) + htmlpath.write(doc) + #log("wrote %r" % htmlpath) + #if txtpath.check(svnwc=1, versioned=1): + # info = txtpath.info() + # svninfopath.dumpobj(info) if __name__=='__main__': basedir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))) Modified: py/dist/py/documentation/rest_test.py ============================================================================== --- py/dist/py/documentation/rest_test.py (original) +++ py/dist/py/documentation/rest_test.py Wed Dec 22 13:22:52 2004 @@ -2,15 +2,14 @@ import py -mypath = py.magic.autopath() -mydir = mypath.dirpath() -rest = mydir.dirpath('py', 'bin', 'py.rest') +pydir = py.magic.autopath(vars(py)).dirpath() +rest = pydir.join('bin', 'py.rest').getpymodule() +docdir = py.path.svnwc(pydir.join('documentation')) def restcheck(path): - out = py.process.cmdexec("%s %s 2>&1" %(rest, path)) - assert not out + rest.process(path) + #assert not out def test_rest_files(): - for x in mydir.listdir('*.txt'): + for x in docdir.listdir('*.txt'): yield restcheck, x - Modified: py/dist/py/misc/test_initpkg.py ============================================================================== --- py/dist/py/misc/test_initpkg.py (original) +++ py/dist/py/misc/test_initpkg.py Wed Dec 22 13:22:52 2004 @@ -24,6 +24,7 @@ base.join('test', 'test', 'data'), base.join('path', 'extpy', 'test_data'), base.join('path', 'gateway',), + base.join('documentation',), base.join('test', 'test', 'import_test'), base.join('magic', 'greenlet'), base.join('bin'), Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Wed Dec 22 13:22:52 2004 @@ -178,7 +178,7 @@ yield self.Item(extpy) class Generator(PyCollector): - def dispatch(self, obj): + def builditem(self, obj): if isinstance(obj, (tuple, list)): call = obj[0] args = obj[1:] @@ -196,6 +196,6 @@ gen, teardown = sm.setup_method(self.extpy) assert not teardown, "%r not processoable in Generator-Collector (XXX)" for call in gen(): - yield self.dispatch(call) + yield self.builditem(call) except: yield self._except() Modified: py/dist/py/test/drive.py ============================================================================== --- py/dist/py/test/drive.py (original) +++ py/dist/py/test/drive.py Wed Dec 22 13:22:52 2004 @@ -93,7 +93,6 @@ except item.Outcome, res: res.excinfo = py.code.ExceptionInfo() except (KeyboardInterrupt, SystemExit): - self.reporter.enditem(res) raise except: res = item.Failed(excinfo=py.code.ExceptionInfo()) From arigo at codespeak.net Wed Dec 22 18:48:24 2004 From: arigo at codespeak.net (arigo at codespeak.net) Date: Wed, 22 Dec 2004 18:48:24 +0100 (MET) Subject: [py-svn] r7971 - py/dist/py/test Message-ID: <20041222174824.A380D5ACE5@thoth.codespeak.net> Author: arigo Date: Wed Dec 22 18:48:24 2004 New Revision: 7971 Modified: py/dist/py/test/drive.py py/dist/py/test/item.py Log: Test items cannot return anything any more; instead, they should always raise instead of return an Outcome if they wish to do so. Also, they can now raise an Outcome with a custom excinfo attribute that won't be overwritten. Modified: py/dist/py/test/drive.py ============================================================================== --- py/dist/py/test/drive.py (original) +++ py/dist/py/test/drive.py Wed Dec 22 18:48:24 2004 @@ -89,13 +89,16 @@ if not self.option.collectonly: self.reporter.startitem(item) try: - res = item.run(self) or item.Passed() + item.run(self) except item.Outcome, res: - res.excinfo = py.code.ExceptionInfo() + if not hasattr(res, 'excinfo'): + res.excinfo = py.code.ExceptionInfo() except (KeyboardInterrupt, SystemExit): raise except: res = item.Failed(excinfo=py.code.ExceptionInfo()) + else: + res = item.Passed() res.item = item self.reporter.enditem(res) if isinstance(res, (item.Failed,)): Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Wed Dec 22 18:48:24 2004 @@ -84,7 +84,7 @@ """ default implementation for calling a test target is to simply call it. """ - return target(*args) + target(*args) def reprcall(self): """ return a string representing a call to the underlying From hpk at codespeak.net Thu Dec 23 11:44:39 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 23 Dec 2004 11:44:39 +0100 (MET) Subject: [py-svn] r7973 - py/dist/py/path/local Message-ID: <20041223104439.0D3045A2EB@thoth.codespeak.net> Author: hpk Date: Thu Dec 23 11:44:39 2004 New Revision: 7973 Modified: py/dist/py/path/local/test_posix.py Log: small fix to minimize a test (and work on osx) Modified: py/dist/py/path/local/test_posix.py ============================================================================== --- py/dist/py/path/local/test_posix.py (original) +++ py/dist/py/path/local/test_posix.py Thu Dec 23 11:44:39 2004 @@ -89,7 +89,6 @@ try: linkpath = tmpdir.join('test') linkpath.mksymlinkto(linkpath) # point to itself - assert linkpath.check(dir=0) assert linkpath.check(link=1) linkpath.remove() assert not linkpath.check() From hpk at codespeak.net Thu Dec 23 12:15:35 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 23 Dec 2004 12:15:35 +0100 (MET) Subject: [py-svn] r7974 - py/dist/py/misc Message-ID: <20041223111535.9C29C5A2EB@thoth.codespeak.net> Author: hpk Date: Thu Dec 23 12:15:35 2004 New Revision: 7974 Modified: py/dist/py/misc/test_api.py Log: enable the tests (aehem) Modified: py/dist/py/misc/test_api.py ============================================================================== --- py/dist/py/misc/test_api.py (original) +++ py/dist/py/misc/test_api.py Thu Dec 23 12:15:35 2004 @@ -4,15 +4,14 @@ import sys import inspect -class API_V0_namespace_consistence: +class TestAPI_V0_namespace_consistence: def test_path_entrypoints(self): assert inspect.ismodule(py.path) assert_class('py.path', 'local') assert_class('py.path', 'svnwc') assert_class('py.path', 'svnurl') - assert_class('py.path', 'py') + assert_class('py.path', 'extpy') assert_class('py.path', 'checker') - assert_class('py.path', 'invchecker') assert_class('py.path', 'NotFound') assert_class('py.path', 'Denied') @@ -23,18 +22,10 @@ assert_function('py.magic', 'patch') assert_function('py.magic', 'revoke') - assert inspect.ismodule(py.magic.dyncode) - assert_function('py.magic.dyncode', 'compile') - assert_function('py.magic.dyncode', 'compile2') - assert_function('py.magic.dyncode', 'findsource') - assert_function('py.magic.dyncode', 'getsource') - assert_function('py.magic.dyncode', 'listtb') - assert_function('py.magic.dyncode', 'findsource') - def test_process_entrypoints(self): assert_function('py.process', 'cmdexec') - def test_utest_entrypoints(self): + def XXXtest_utest_entrypoints(self): # XXX TOBECOMPLETED assert_function('py.test', 'main') #assert_module('std.utest', 'collect') From arigo at codespeak.net Fri Dec 24 16:53:15 2004 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 24 Dec 2004 16:53:15 +0100 (MET) Subject: [py-svn] r7984 - py/dist/py/test Message-ID: <20041224155315.3EE255B552@thoth.codespeak.net> Author: arigo Date: Fri Dec 24 16:53:14 2004 New Revision: 7984 Modified: py/dist/py/test/collect.py Log: Obvious bug. Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Fri Dec 24 16:53:14 2004 @@ -25,7 +25,7 @@ def __repr__(self): tb = list(self.excinfo)[-1] - return '' % (tb.code.path, + return '' % (tb.frame.code.path, tb.lineno+1) class Collector(object): From arigo at codespeak.net Fri Dec 24 17:11:06 2004 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 24 Dec 2004 17:11:06 +0100 (MET) Subject: [py-svn] r7985 - in py/dist/py: . code code/testing magic test test/report/text Message-ID: <20041224161106.967625B552@thoth.codespeak.net> Author: arigo Date: Fri Dec 24 17:11:05 2004 New Revision: 7985 Modified: py/dist/py/__init__.py py/dist/py/code/excinfo.py py/dist/py/code/frame.py py/dist/py/code/testing/test_excinfo.py py/dist/py/code/testing/test_frame.py py/dist/py/code/testing/test_source.py py/dist/py/magic/assertion.py py/dist/py/magic/exprinfo.py py/dist/py/magic/test_assertion.py py/dist/py/magic/test_exprinfo.py py/dist/py/magic/test_invoke.py py/dist/py/test/item.py py/dist/py/test/raises.py py/dist/py/test/report/text/summary.py Log: Another lots-of-small-changes-everywhere kind of refactoring, changing the interface to ExceptionInfo. * a unification: the ExceptionInfo, TracebackEntry, Frame, Code etc. classes now all have a constructor which just expects the corresponding Python object, and wrap it. To provide a different behavior, 3rd-party code should provide another class with a similar interface (but a custom constructor), which could subclass py's classes or not. In particular, InspectableFrame/RunnerFrame are gone and there is just a Frame. * the idea about ExceptionInfo is that it may represent a remote exception, or a PyPy exception; in both cases, it is not possible to provide meaningful 'type' and 'value' attributes. So ExceptionInfo has grown a few new attributes/methods to deal with it in a generic manner. This means lots of small changes in summary.py, raises.py, and other bits of the testing framework: * raises() is simplified and no longer catches unexpected exceptions; they just crash through. A quick (but possibly good) hack hides raises() itself from excinfo's tracebacks. * this allows the special treatment of ExceptionFailure in summary.py to be removed. By providing a default 'msg' of 'DID NOT RAISE', we get the following summary output without a special case: def test_raises_doesnt(self): > raises(IOError, "int('3')") [/home/arigo/svn/pypy/branch/src-pytest/py/test/test/demo.py:72] ------------------------------------------------------------------------------- ExceptionFailure: << DID NOT RAISE * the main trick to simplify summary.py was to put a reinterpret() method on ExceptionInfo, which either does the reinterpretation (for exceptions other than AssertionErrors), or returns a 'exprinfo' explanation precomputed by py.magic.assertion.AssertionError. * the API of magic.exprinfo is still as messy as always. Should be fixed at some point... In this check-in I only tried to minimize code duplication. For example, removing "AssertionError:" in front of exprinfo's explanation or complaining that reinterpretation inconsistently failed then succeeded is now done at a single place. * this is a depressingly big check-in which may hide a couple of genuine bug fixes in addition to the refactoring... Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Fri Dec 24 17:11:05 2004 @@ -45,8 +45,7 @@ 'code.compile' : ('./code/source.py', 'compile_'), 'code.Source' : ('./code/source.py', 'Source'), - 'code.InspectableFrame' : ('./code/frame.py', 'InspectableFrame'), - 'code.RunnerFrame' : ('./code/frame.py', 'RunnerFrame'), + 'code.Frame' : ('./code/frame.py', 'Frame'), 'code.ExceptionInfo' : ('./code/excinfo.py', 'ExceptionInfo'), 'builtin.enumerate' : ('./builtin/enumerate.py', 'enumerate'), Modified: py/dist/py/code/excinfo.py ============================================================================== --- py/dist/py/code/excinfo.py (original) +++ py/dist/py/code/excinfo.py Fri Dec 24 17:11:05 2004 @@ -5,22 +5,48 @@ """ wraps sys.exc_info() objects and offers help for navigating the traceback. """ - def __init__(self, tup = None): - if tup is None: - tup = sys.exc_info() - assert tup, "couldn't get at exception info!" - self.type, self.value, self.tb = tup - - def __iter__(self): - current = self.tb - while current: - yield TracebackEntry(current, self) - current = current.tb_next - - def format_exception_only(self): - func = py.std.traceback.format_exception_only - for x in func(self.type, self.value): - yield x + TRACEBACK_HIDE = object() + + def __init__(self, tup=None, exprinfo=None): + strip = '' + if tup is None: + tup = sys.exc_info() + if exprinfo is None and isinstance(tup[1], py.magic.AssertionError): + exprinfo = tup[1].msg + if exprinfo.startswith('assert '): + strip = 'AssertionError: ' + self._excinfo = tup + self.type, self.value, tb = self._excinfo + # get the text representation of the exception + lines = py.std.traceback.format_exception_only(self.type, self.value) + text = ''.join(lines) + if text.endswith('\n'): + text = text[:-1] + if text.startswith(strip): + text = text[len(strip):] + # NB. only the attributes below are official; subclasses may have + # no .type and .value attributes. + self.exprinfo = exprinfo + self.exception_text = text + + def __str__(self): + return self.exception_text + + def reinterpret(self): + """Reinterpret the failing statement and returns a detailed information + about what operations are performed.""" + if self.exprinfo is None: + from py.__impl__.magic import exprinfo + self.exprinfo = exprinfo.getmsg(self) + return self.exprinfo + + def __iter__(self): + current = self._excinfo[2] + while current is not None: + special = current.tb_frame.f_locals.get('__traceback__') + if special is not self.TRACEBACK_HIDE: + yield TracebackEntry(current) + current = current.tb_next def getentries(self, startcondition=None, endcondition=None): if startcondition is not None and not callable(startcondition): @@ -55,10 +81,9 @@ #return res class TracebackEntry(object): - def __init__(self, rawentry, excinfo): + def __init__(self, rawentry): self._rawentry = rawentry - self._excinfo = excinfo - self.frame = py.code.RunnerFrame(rawentry.tb_frame) + self.frame = py.code.Frame(rawentry.tb_frame) self.lineno = rawentry.tb_lineno - 1 def statement(self): Modified: py/dist/py/code/frame.py ============================================================================== --- py/dist/py/code/frame.py (original) +++ py/dist/py/code/frame.py Fri Dec 24 17:11:05 2004 @@ -20,24 +20,21 @@ fullsource = property(fullsource, None, None, "full source containing this code object") -class InspectableFrame(object): - def __init__(self, f_code, f_lineno): - self.code = Code(f_code) - self.lineno = f_lineno - 1 - def statement(self): - return self.code.fullsource.getstatement(self.lineno) - statement = property(statement, None, None, - "statement this frame is at") - -class RunnerFrame(InspectableFrame): +class Frame(object): """Wrapper around a Python frame holding f_locals and f_globals in which expressions can be evaluated.""" def __init__(self, frame): + self.code = Code(frame.f_code) + self.lineno = frame.f_lineno - 1 self.f_globals = frame.f_globals self.f_locals = frame.f_locals - super(RunnerFrame, self).__init__(frame.f_code, frame.f_lineno) + + def statement(self): + return self.code.fullsource.getstatement(self.lineno) + statement = property(statement, None, None, + "statement this frame is at") def eval(self, code, **vars): self.f_locals.update(vars) Modified: py/dist/py/code/testing/test_excinfo.py ============================================================================== --- py/dist/py/code/testing/test_excinfo.py (original) +++ py/dist/py/code/testing/test_excinfo.py Fri Dec 24 17:11:05 2004 @@ -36,8 +36,12 @@ def h(): g() -def test_excinfo_getentries_nocut(): - excinfo = py.test.raises(ValueError, h) +def test_excinfo_getentries_nocut(): + try: + h() + except ValueError: + pass + excinfo = py.code.ExceptionInfo() entries = excinfo.getentries() assert len(entries) == 4 # fragile names = ['f','g','h'] @@ -63,3 +67,13 @@ lambda x: x.frame.code.name != 'f') names = [x.frame.code.name for x in entries] assert names == ['h','g'] + +def test_excinfo_exception_text(): + excinfo = py.test.raises(ValueError, h) + assert excinfo.exception_text.startswith('ValueError') + +def test_excinfo_reinterpret(): + x = 'foobar' + excinfo = py.test.raises(TypeError, "x+5") + del x + assert excinfo.reinterpret().startswith("TypeError: ('foobar' + 5)") Modified: py/dist/py/code/testing/test_frame.py ============================================================================== --- py/dist/py/code/testing/test_frame.py (original) +++ py/dist/py/code/testing/test_frame.py Fri Dec 24 17:11:05 2004 @@ -1,10 +1,10 @@ import sys -from py.code import RunnerFrame +from py.code import Frame def test_frame_getsourcelineno_myself(): def func(): return sys._getframe(0) f = func() - f = RunnerFrame(f) + f = Frame(f) source, lineno = f.code.fullsource, f.lineno assert source[lineno].startswith(" return sys._getframe(0)") Modified: py/dist/py/code/testing/test_source.py ============================================================================== --- py/dist/py/code/testing/test_source.py (original) +++ py/dist/py/code/testing/test_source.py Fri Dec 24 17:11:05 2004 @@ -166,7 +166,7 @@ exec co f(7) excinfo = py.test.raises(AssertionError, "f(6)") - frame = list(excinfo)[2].frame + frame = list(excinfo)[-1].frame stmt = frame.code.fullsource.getstatement(frame.lineno) #print "block", str(block) assert str(stmt).strip().startswith('assert') @@ -175,7 +175,7 @@ class A: def __init__(self, *args): frame = sys._getframe(1) - self.source = py.code.RunnerFrame(frame).statement + self.source = py.code.Frame(frame).statement x = A('x', 'y') @@ -186,7 +186,7 @@ class A: def __init__(self, *args): frame = sys._getframe(1) - self.source = py.code.RunnerFrame(frame).statement + self.source = py.code.Frame(frame).statement x = A('x', 'y' \ Modified: py/dist/py/magic/assertion.py ============================================================================== --- py/dist/py/magic/assertion.py (original) +++ py/dist/py/magic/assertion.py Fri Dec 24 17:11:05 2004 @@ -8,15 +8,15 @@ def __init__(self, *args): BuiltinAssertionError.__init__(self, *args) f = sys._getframe(1) - source = py.code.RunnerFrame(f).statement - source = str(source).strip() - #print "f.f_lineno", f.f_lineno + try: + source = py.code.Frame(f).statement + source = str(source).strip() + except py.path.NotFound: + source = None + # this can also occur during reinterpretation, when the + # co_filename is set to "". if source: - self.msg = exprinfo.interpret(source, f) - if self.msg is None: - self.msg = "(inconsistenty failed then succeeded)" - elif self.msg.startswith('AssertionError: '): - self.msg = self.msg[16:] + self.msg = exprinfo.interpret(source, f, should_fail=True) if not self.args: self.args = (self.msg,) else: Modified: py/dist/py/magic/exprinfo.py ============================================================================== --- py/dist/py/magic/exprinfo.py (original) +++ py/dist/py/magic/exprinfo.py Fri Dec 24 17:11:05 2004 @@ -375,7 +375,7 @@ if frame is None: import sys frame = sys._getframe(1) - frame = py.code.RunnerFrame(frame) + frame = py.code.Frame(frame) expr = parse(s, 'eval') assert isinstance(expr, ast.Expression) node = Interpretable(expr.node) @@ -394,13 +394,13 @@ # API / Entry points # ######################################################### -def interpret(source, frame): +def interpret(source, frame, should_fail=False): module = Interpretable(parse(source, 'exec').node) #print "got module", module - if not isinstance(frame, py.code.InspectableFrame): - frame = py.code.RunnerFrame(frame) + if isinstance(frame, py.std.types.FrameType): + frame = py.code.Frame(frame) try: - return module.run(frame) # None if no exception generated + module.run(frame) except Failure, e: return getfailure(e) except passthroughex: @@ -408,33 +408,41 @@ except: import traceback traceback.print_exc() + if should_fail: + return "(inconsistenty failed then succeeded)" + else: + return None def getmsg(excinfo): - if not isinstance(excinfo, py.code.ExceptionInfo): + if isinstance(excinfo, tuple): excinfo = py.code.ExceptionInfo(excinfo) #frame, line = gettbline(tb) - #frame = py.code.RunnerFrame(frame) + #frame = py.code.Frame(frame) #return interpret(line, frame) tb = list(excinfo)[-1] source = str(tb.statement).strip() - x = interpret(source, tb.frame) + x = interpret(source, tb.frame, should_fail=True) if not isinstance(x, str): raise TypeError, "interpret returned non-string %r" % (x,) return x def getfailure(e): explanation = e.node.nice_explanation() - if not str(e.value): - return "%s: %s" % (e.exc.__name__, explanation) - else: - return "%s: %s << %s" % (e.exc.__name__, explanation, e.value) + if str(e.value): + lines = explanation.split('\n') + lines[0] += " << %s" % (e.value,) + explanation = '\n'.join(lines) + text = "%s: %s" % (e.exc.__name__, explanation) + if text.startswith('AssertionError: assert '): + text = text[16:] + return text def run(s, frame=None): if frame is None: import sys frame = sys._getframe(1) - frame = py.code.RunnerFrame(frame) + frame = py.code.Frame(frame) module = Interpretable(parse(s, 'exec').node) try: module.run(frame) Modified: py/dist/py/magic/test_assertion.py ============================================================================== --- py/dist/py/magic/test_assertion.py (original) +++ py/dist/py/magic/test_assertion.py Fri Dec 24 17:11:05 2004 @@ -26,7 +26,7 @@ finally: i = 42 """) - s = str(excinfo.value) + s = excinfo.exception_text assert s.find("takes no argument") != -1 #def g(): Modified: py/dist/py/magic/test_exprinfo.py ============================================================================== --- py/dist/py/magic/test_exprinfo.py (original) +++ py/dist/py/magic/test_exprinfo.py Fri Dec 24 17:11:05 2004 @@ -1,5 +1,6 @@ import sys +import py from py.__impl__.magic.exprinfo import getmsg, interpret def getexcinfo(exc, obj, *args, **kwargs): @@ -20,7 +21,7 @@ assert a == b excinfo = getexcinfo(AssertionError, g) msg = getmsg(excinfo) - assert msg == 'AssertionError: assert 1 == 2' + assert msg == 'assert 1 == 2' def test_nested_scopes(): def g(): @@ -31,7 +32,7 @@ assert h() == b excinfo = getexcinfo(AssertionError, g) msg = getmsg(excinfo) - assert msg.startswith('AssertionError: assert 1 == 2\n + where 1 = ') + assert msg.startswith('assert 1 == 2\n + where 1 = ') def test_nested_scopes_2(): a = 1 @@ -40,7 +41,7 @@ assert a == b excinfo = getexcinfo(AssertionError, g) msg = getmsg(excinfo) - assert msg == 'AssertionError: assert 1 == 2' + assert msg == 'assert 1 == 2' def test_assert_func_argument_type_error(): def f (): @@ -74,7 +75,7 @@ assert global_f() == 43 excinfo = getexcinfo(AssertionError, g) msg = getmsg(excinfo) - assert msg == 'AssertionError: assert 42 == 43\n + where 42 = global_f()' + assert msg == 'assert 42 == 43\n + where 42 = global_f()' def test_keyboard_interrupt(): # XXX this test is slightly strange because it is not @@ -95,7 +96,7 @@ for exstr in "SystemExit", "KeyboardInterrupt", "MemoryError": ex = eval(exstr) try: - interpret("raise %s" % exstr, DummyFrame) + interpret("raise %s" % exstr, py.code.Frame(DummyFrame)) except ex: pass else: Modified: py/dist/py/magic/test_invoke.py ============================================================================== --- py/dist/py/magic/test_invoke.py (original) +++ py/dist/py/magic/test_invoke.py Fri Dec 24 17:11:05 2004 @@ -4,8 +4,7 @@ def check_assertion(): excinfo = py.test.raises(AssertionError, "assert 1 == 2") - value = excinfo.value - assert str(value) == "assert 1 == 2" + assert excinfo.exception_text == "assert 1 == 2" def test_invoke_assertion(): py.magic.invoke(assertion=True) Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Fri Dec 24 17:11:05 2004 @@ -101,7 +101,7 @@ return getattr(self, 'msg', object.__repr__(self)) class Passed(Outcome): pass class Failed(Outcome): pass - class ExceptionFailure(Failed): pass + class ExceptionFailure(Failed): msg = 'DID NOT RAISE' class Skipped(Outcome): pass class GenItem(Item): Modified: py/dist/py/test/raises.py ============================================================================== --- py/dist/py/test/raises.py (original) +++ py/dist/py/test/raises.py Fri Dec 24 17:11:05 2004 @@ -14,32 +14,29 @@ loc = frame.f_locals.copy() loc.update(kwargs) #print "raises frame scope: %r" % frame.f_locals + source = py.code.Source(expr) try: - eval(py.code.Source(expr).compile(), frame.f_globals, loc) + __traceback__ = py.code.ExceptionInfo.TRACEBACK_HIDE + eval(source.compile(), frame.f_globals, loc) + del __traceback__ # XXX didn'T mean f_globals == f_locals something special? # this is destroyed here ... except ExpectedException: return py.code.ExceptionInfo() - except Exception, e: - excinfo = py.code.ExceptionInfo(sys.exc_info()) - else: - excinfo = None raise ExceptionFailure(expr=expr, expected=ExpectedException, - innerexcinfo=excinfo, tbindex = -2) + tbindex = -2) else: func = args[0] assert callable try: + __traceback__ = py.code.ExceptionInfo.TRACEBACK_HIDE func(*args[1:], **kwargs) + del __traceback__ except ExpectedException: return py.code.ExceptionInfo() - except Exception, e: - excinfo = py.code.ExceptionInfo() - else: - excinfo = None k = ", ".join(["%s=%r" % x for x in kwargs.items()]) if k: k = ', ' + k expr = '%s(%r%s)' %(func.__name__, args, k) raise ExceptionFailure(expr=args, expected=ExpectedException, - innerexcinfo=excinfo, tbindex = -2) + tbindex = -2) Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Fri Dec 24 17:11:05 2004 @@ -25,8 +25,7 @@ #if isinstance(res.excinfo[1], AssertionError): # from std.utest.tool import hackexpr # res.msg = "failed: " + hackexpr.getmsg(res.excinfo) - for line in error.excinfo.format_exception_only(): - self.out.line(line.rstrip()) + self.out.line(error.excinfo) #if self.option.showlocals: # self.out.sep('- ', 'locals') @@ -88,49 +87,21 @@ self.out.line(out.strip()) def repr_failure_result(self, res): - cls = res.excinfo.type - if isinstance(cls, str): - self.out.line("string-exception: %s" % cls) - elif issubclass(cls, Item.ExceptionFailure): - if not res.innerexcinfo: - self.out.line("%s <<< DID NOT RAISE" % (res.expr,)) - self.out.line("expected: %r" % (res.expected,)) - else: - self.out.write("%r raised wrong Exception, " % (res.expr,)) - self.out.line("expected: %r" % (res.expected.__name__,)) - #self.out.line("got : %r" % res.innerexcinfo[0]) - #self.out.line() - self.out.sep("- ", "traceback of unexpected exception") - self.repr_traceback(res.item, res.innerexcinfo) # [2]) - for line in res.innerexcinfo.format_exception_only(): - self.out.line(line) - elif issubclass(cls, assertion.AssertionError): - res.msg = str(res.excinfo.value) - self.out.line(res) - return - else: - if not self.option.nomagic: - showex = False - try: - res.msg = exprinfo.getmsg(res.excinfo) - except KeyboardInterrupt: - raise - except: - if self.option.verbose > 1: - self.out.line("reinterpretation traceback") - py.std.traceback.print_exc() - else: - self.out.line("(reinterpretation failed, " - "you may increase " - "verbosity to see details)") - showex = True + exprinfo = None + if not self.option.nomagic: + try: + exprinfo = res.excinfo.reinterpret() # very detailed info + except KeyboardInterrupt: + raise + except: + if self.option.verbose > 1: + self.out.line("reinterpretation traceback") + py.std.traceback.print_exc() else: - self.out.line(res) - else: - showex = True - if showex: - for line in res.excinfo.format_exception_only(): - self.out.line(line) + self.out.line("(reinterpretation failed, " + "you may increase " + "verbosity to see details)") + self.out.line(exprinfo or res.excinfo.exception_text) def repr_source(self, tb): # represent source code for a given traceback entry @@ -163,7 +134,7 @@ endcondition = None entries = excinfo.getentries(startcondition, endcondition) if tbindex is not None and tbindex < -1: - entries = entries[:tbindex] + entries = entries[:tbindex+1] return entries def repr_traceback(self, item, excinfo, tbindex=None): From arigo at codespeak.net Fri Dec 24 17:35:08 2004 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 24 Dec 2004 17:35:08 +0100 (MET) Subject: [py-svn] r7986 - in py/dist/py: . code test/report/text Message-ID: <20041224163508.70A535B552@thoth.codespeak.net> Author: arigo Date: Fri Dec 24 17:35:07 2004 New Revision: 7986 Modified: py/dist/py/__init__.py py/dist/py/code/excinfo.py py/dist/py/test/report/text/summary.py Log: Minor fixes. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Fri Dec 24 17:35:07 2004 @@ -45,8 +45,10 @@ 'code.compile' : ('./code/source.py', 'compile_'), 'code.Source' : ('./code/source.py', 'Source'), + 'code.Code' : ('./code/frame.py', 'Code'), 'code.Frame' : ('./code/frame.py', 'Frame'), 'code.ExceptionInfo' : ('./code/excinfo.py', 'ExceptionInfo'), + 'code.TracebackEntry' : ('./code/excinfo.py', 'TracebackEntry'), 'builtin.enumerate' : ('./builtin/enumerate.py', 'enumerate'), Modified: py/dist/py/code/excinfo.py ============================================================================== --- py/dist/py/code/excinfo.py (original) +++ py/dist/py/code/excinfo.py Fri Dec 24 17:35:07 2004 @@ -8,6 +8,8 @@ TRACEBACK_HIDE = object() def __init__(self, tup=None, exprinfo=None): + # NB. all attributes are private! Subclasses or other + # ExceptionInfo-like classes may have different attributes. strip = '' if tup is None: tup = sys.exc_info() @@ -16,6 +18,7 @@ if exprinfo.startswith('assert '): strip = 'AssertionError: ' self._excinfo = tup + self.exprinfo = exprinfo self.type, self.value, tb = self._excinfo # get the text representation of the exception lines = py.std.traceback.format_exception_only(self.type, self.value) @@ -24,9 +27,6 @@ text = text[:-1] if text.startswith(strip): text = text[len(strip):] - # NB. only the attributes below are official; subclasses may have - # no .type and .value attributes. - self.exprinfo = exprinfo self.exception_text = text def __str__(self): Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Fri Dec 24 17:35:07 2004 @@ -101,7 +101,7 @@ self.out.line("(reinterpretation failed, " "you may increase " "verbosity to see details)") - self.out.line(exprinfo or res.excinfo.exception_text) + self.out.line(exprinfo or res.excinfo) def repr_source(self, tb): # represent source code for a given traceback entry @@ -174,9 +174,11 @@ if key not in recursioncache: recursioncache.setdefault(key, []).append(tb.frame.f_locals) else: - loc = tb.tb_frame.f_locals + f = tb.tb_frame + loc = f.f_locals for x in recursioncache[key]: - if x==loc: + if f.is_true(f.eval(co_equal, __recursioncache_locals_1=loc, + __recursioncache_locals_2=x)): self.out.line("Recursion detected (same locals & position)") break else: @@ -184,3 +186,5 @@ continue break +co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2', + '?', 'eval') From hpk at codespeak.net Sun Dec 26 23:47:16 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 26 Dec 2004 23:47:16 +0100 (MET) Subject: [py-svn] r7990 - py/dist/py/test/report/text Message-ID: <20041226224716.995AF5A2DE@thoth.codespeak.net> Author: hpk Date: Sun Dec 26 23:47:15 2004 New Revision: 7990 Modified: py/dist/py/test/report/text/summary.py Log: small hack/fix to actually show tracebacks for failing generated tests (rethinking the cut-traceback logic is probably needed) Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Sun Dec 26 23:47:15 2004 @@ -133,6 +133,12 @@ entry.frame.code.firstlineno == lineno endcondition = None entries = excinfo.getentries(startcondition, endcondition) + # XXX the cutting logic needs refactoring with respect + # to generative tests (which will not have the + # test item in their tb-path) + # + if not entries: + entries = excinfo.getentries() if tbindex is not None and tbindex < -1: entries = entries[:tbindex+1] return entries From arigo at codespeak.net Mon Dec 27 16:27:57 2004 From: arigo at codespeak.net (arigo at codespeak.net) Date: Mon, 27 Dec 2004 16:27:57 +0100 (MET) Subject: [py-svn] r8000 - py/dist/py/test/report/text Message-ID: <20041227152757.200785A1D3@thoth.codespeak.net> Author: arigo Date: Mon Dec 27 16:27:56 2004 New Revision: 8000 Modified: py/dist/py/test/report/text/summary.py Log: typo Modified: py/dist/py/test/report/text/summary.py ============================================================================== --- py/dist/py/test/report/text/summary.py (original) +++ py/dist/py/test/report/text/summary.py Mon Dec 27 16:27:56 2004 @@ -180,7 +180,7 @@ if key not in recursioncache: recursioncache.setdefault(key, []).append(tb.frame.f_locals) else: - f = tb.tb_frame + f = tb.frame loc = f.f_locals for x in recursioncache[key]: if f.is_true(f.eval(co_equal, __recursioncache_locals_1=loc, From hpk at codespeak.net Thu Dec 30 13:03:13 2004 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 30 Dec 2004 13:03:13 +0100 (MET) Subject: [py-svn] r8009 - py/dist/py/code Message-ID: <20041230120313.F2A9F5A6D9@thoth.codespeak.net> Author: hpk Date: Thu Dec 30 13:03:13 2004 New Revision: 8009 Modified: py/dist/py/code/frame.py Log: read source code with universal newline support Modified: py/dist/py/code/frame.py ============================================================================== --- py/dist/py/code/frame.py (original) +++ py/dist/py/code/frame.py Thu Dec 30 13:03:13 2004 @@ -16,7 +16,7 @@ try: return fn.__source__ except AttributeError: - return py.code.Source(self.path.read()) + return py.code.Source(self.path.read(mode="rU")) fullsource = property(fullsource, None, None, "full source containing this code object")