From hpk at codespeak.net Tue Feb 1 14:06:59 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 1 Feb 2005 14:06:59 +0100 (MET) Subject: [py-svn] r8784 - in py/branch/py-collect: . execnet test2 test2/testing test2/testing/test/data Message-ID: <20050201130659.1BFBD27B9A@code1.codespeak.net> Author: hpk Date: Tue Feb 1 14:06:58 2005 New Revision: 8784 Added: py/branch/py-collect/ - copied from r8742, py/dist/py/ py/branch/py-collect/test2/ - copied from r8742, py/dist/py/test/ py/branch/py-collect/test2/testing/test/data/filetest.py (contents, props changed) Modified: py/branch/py-collect/__init__.py py/branch/py-collect/conftest.py py/branch/py-collect/execnet/gateway.py py/branch/py-collect/test2/collect.py py/branch/py-collect/test2/defaultconfig.py py/branch/py-collect/test2/item.py py/branch/py-collect/test2/testing/test/data/disabled.py py/branch/py-collect/test2/testing/test_collect.py Log: open the collect refactoring branch. The goal of this refactoring is to cleanup the collecting process and making it saner/actually work in session or other distributed modes. Also doctests should be integrated at the collection level (this will be experimental). Some of the ideas motivating this branch have been discussed on the "py.test broken" thread on py-dev. Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/branch/py-collect/__init__.py Tue Feb 1 14:06:58 2005 @@ -21,6 +21,25 @@ 'test.GenItem' : ('./test/item.py', 'GenItem'), 'test.Driver' : ('./test/drive.py', 'Driver'), + 'test2.skip' : ('./test2/item.py', 'skip'), + 'test2.skip_on_error' : ('./test2/item.py', 'skip_on_error'), + 'test2.raises' : ('./test2/raises.py', 'raises'), + 'test2.fail' : ('./test2/item.py', 'fail'), + 'test2.exit' : ('./test2/drive.py', 'exit'), + 'test2.config' : ('./test2/config.py', 'config'), + 'test2.compat.TestCase' : ('./test2/compat.py', 'TestCase'), + 'test2.collect.Directory' : ('./test2/collect.py', 'Directory'), + 'test2.collect.Module' : ('./test2/collect.py', 'Module'), + 'test2.collect.Class' : ('./test2/collect.py', 'Class'), + 'test2.collect.Generator' : ('./test2/collect.py', 'Generator'), + 'test2.TextReporter' : ('./test2/report/text/reporter.py', + 'TextReporter'), + 'test2.Option' : ('./test2/tool/optparse.py', 'Option'), + 'test2.MemoReporter' : ('./test2/report/memo.py', 'MemoReporter'), + 'test2.Item' : ('./test2/item.py', 'Item'), + 'test2.GenItem' : ('./test2/item.py', 'GenItem'), + 'test2.Driver' : ('./test2/drive.py', 'Driver'), + 'std' : ('./misc/std.py', 'std'), 'process.cmdexec' : ('./process/cmdexec.py', 'cmdexec'), Modified: py/branch/py-collect/conftest.py ============================================================================== --- py/dist/py/conftest.py (original) +++ py/branch/py-collect/conftest.py Tue Feb 1 14:06:58 2005 @@ -7,9 +7,6 @@ # mod.module = 23 # directory = pypath.root.dirpath() -import py -rootdir = py.magic.autopath().dirpath().dirpath() - # default values for options (modified from cmdline) verbose = 0 nocapture = False Modified: py/branch/py-collect/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/branch/py-collect/execnet/gateway.py Tue Feb 1 14:06:58 2005 @@ -190,7 +190,7 @@ loc = { 'channel' : channel } self.trace("execution starts:", repr(source)[:50]) try: - co = compile(source+'\n', '', 'exec') + co = compile(source+'\n', '', 'exec', 4096) exec co in loc finally: self.trace("execution finished:", repr(source)[:50]) Modified: py/branch/py-collect/test2/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/branch/py-collect/test2/collect.py Tue Feb 1 14:06:58 2005 @@ -1,206 +1,242 @@ -from __future__ import generators - -import sys, inspect, types -from py import path, test import py +isclass = py.std.inspect.isclass -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 py.error.ENOENT(fspath) - return col(fspath) +""" +The is the tree of collectors. Items are the leaves +that really get executed as tests. + + Directory + Module + Class + Instance # empty setup/teardown + Function + Item + Generator + GenItem + Function + Item + Generator + GenItem +""" def configproperty(name): def fget(self): - return py.test.config.getconfigvalue(self.fspath, name) + print "retrieving %r property from %s" %(name, self.fspath) + return py.test2.config.getconfigvalue(self.fspath, name) return property(fget) -class Error(object): - """ represents a non-fatal exception while collecting. """ - def __init__(self, excinfo): - self.excinfo = py.code.ExceptionInfo(excinfo) - - def __repr__(self): - tb = list(self.excinfo)[-1] - return '' % (tb.frame.code.path, - tb.lineno+1) - -class Collector(object): - """ instances are *restartable iterators*. They will yield Units or - Collector instances during iteration. - """ - Item = test.Item - GenItem = test.GenItem - Error = Error +def getfscollector(fspath): + return Directory(fspath.dirpath()).join(fspath.basename) + +class Collector(object): + def __init__(self, name, parent=None): + self.name = name + self.parent = parent + + Item = py.test2.Item + GenItem = py.test2.GenItem Module = configproperty('Module') Directory = configproperty('Directory') Class = configproperty('Class') + Generator = configproperty('Generator') + + def setup(self, item): + pass + + def teardown(self, item): + pass - def iterunits(self): - """ yield all units of the Collector instance. """ - for x in self: + def iteritems(self): + """ yield all Items of a flattened Collector instance. """ + for x in self.listdir(): if isinstance(x, Collector): - for y in x.iterunits(): + for y in x.iteritems(): yield y else: yield x - def _except(self): - excinfo = sys.exc_info() - if isinstance(excinfo[1], (KeyboardInterrupt, SystemExit)): - raise excinfo # KeyboardInterrupt - return Error(excinfo) - -# ---------------------------------------------- -# Collectors starting from a file system path -# ---------------------------------------------- - -class FSCollector(Collector): - def __init__(self, fspath): - self.fspath = py.path.local(fspath) - def __repr__(self): - return '%s(%r)' %(self.__class__.__name__, - self.fspath.relto(py.path.local())) - -class Directory(FSCollector): - def fil(self, fspath): - 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): - # hack to get the correct Directory collector class - yield self.Directory(fspath).Directory(fspath) - elif self.fil(fspath): - yield self.Module(fspath) - except: - yield self._except() - -# ---------------------------------------------- -# Collectors starting from Python Paths/Objects -# ---------------------------------------------- - -class PyCollector(Collector): - def __init__(self, extpy): - if not hasattr(extpy, 'resolve'): - extpy = py.path.extpy(extpy) - self.extpy = extpy - self.yielders = [getattr(self, x) - 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)) - - def __iter__(self): - try: - # we want to sort according to lineno, so here goes - # the generator lazyness - l = [] - for extpy in self.extpy.listdir(): - for meth in self.yielders: - #print "looking at", extpy - for x in meth(extpy): - #print "found", x - sortvalue = self.getsortvalue(x) - l.append((sortvalue, x)) - l.sort() - for x,y in l: - yield y - except: - yield self._except() - - def getsortvalue(self, obj): - """ sorting function to bring test methods in - the same order as int he file. + def fspath(): + def fget(self): + try: + return self._fspath + except AttributeError: + parent = self.parent + while parent: + try: + self._fspath = parent.fspath + except AttributeError: + parent = parent.parent + else: + return self._fspath + def fset(self, value): + self._fspath = value + return property(fget, fset, None, "containing filesystem path") + fspath = fspath() + +class FSCollector(Collector): + def __init__(self, fspath, parent=None): + if isinstance(fspath, str): + fspath = py.path.local(fspath) + self.fspath = fspath + super(FSCollector, self).__init__(fspath.basename, parent) + +class Directory(FSCollector): + def filefilter(self, path): + pb = path.purebasename + return (path.check(fnmatch="*.py") and + (pb.startswith('test_') or pb.endswith('_test'))) + + def recfilter(self, path): + return path.check(dotfile=0) + + def listdir(self): + l = [] + dirlist = self.fspath.listdir() + for x in dirlist: + if x.check(file=1) and self.filefilter(x): + pass + elif x.check(dir=1) and self.recfilter(x): + pass + else: + continue + l.append(self.join(x.basename)) + return filter(None, l) + + def join(self, name): + x = self.fspath.join(name) + if x.check(file=1): + return self.Module(x, parent=self) + elif x.check(dir=1): + return self.Directory(x, parent=self) + + def __repr__(self): + return "" %(self.__class__.__name__, self.obj.__name__) + + def obj(self): + return getattr(self.parent.obj, self.name) + obj = property(obj) + + def listdir(self): + l = [] + if getattr(self.obj, 'disabled', 0): + return l + for name in dir(self.obj): + if self.funcnamefilter(name) or self.classnamefilter(name): + x = self.join(name) + if x: + l.append((x.sortvalue(), x)) + l.sort() + return [x[1] for x in l] + + def join(self, name): + obj = getattr(self.obj, name) + if isclass(obj): + return self.Class(name, parent=self) + elif callable(obj): + if obj.func_code.co_flags & 32: # generator function + return self.Generator(name, parent=self) + else: + return self.Function(name, parent=self) + + def sortvalue(self): + """ sorting function helper to bring test methods in + the same order as in their file. """ - if isinstance(obj, self.Item): - obj = obj.extpy.resolve() - elif isinstance(obj, PyCollector): - for x in obj: - return self.getsortvalue(x) - if hasattr(obj, 'im_func'): - obj = obj.im_func - if hasattr(obj, 'func_code'): - obj = obj.func_code - # enough is enough - return (getattr(obj, 'co_filename', None), - getattr(obj, 'co_firstlineno', sys.maxint)) - -class Module(PyCollector): - def __iter__(self): - try: - iter = self.extpy.join('Collector') - if iter.check(exists=True): - iter = iter.resolve()(self.extpy) - else: - iter = super(Module, self).__iter__() - for x in iter: - yield x - except: - yield self._except() - - def collect_function(self, extpy): - if extpy.check(func=1, basestarts='test_'): - if extpy.check(genfunc=1): - yield Generator(extpy) - else: - #if self.extpy.samefile(extpy): not nfs/different mountpoint safe ? - yield self.Item(extpy) + for x in self.listdir(): + return x.sortvalue() - def collect_class(self, extpy): - #print "checking %r (extpy: %r)" % (extpy.resolve(), extpy) - if extpy.check(basestarts='Test') and self.extpy.samefile(extpy): - obj = extpy.resolve() - if inspect.isclass(obj) and not getattr(obj, 'disabled', 0): - yield self.Class(extpy) - -class Class(PyCollector): - def collect_method(self, extpy): - # note that we want to allow inheritance and thus - # we don't check for "samemodule"-ness of test - # methods like in the Module Collector - if extpy.check(basestarts='test_', func=1): - if extpy.check(genfunc=1): - yield Generator(extpy) - else: - func = extpy.resolve() - try: - yield getattr(func.im_class, 'Item')(extpy) - except AttributeError: - yield self.Item(extpy) +class Module(PyCollectorMixin, FSCollector): + def obj(self): + try: + return self._obj + except AttributeError: + self._obj = obj = self.fspath.getpymodule() + return obj + obj = property(obj, None, None, "module object") + + def setup(self, item): + item.setup_module(self.obj) + def teardown(self, item): + item.teardown_module(self.obj) + +class Class(PyCollectorMixin, Collector): + + def listdir(self): + return self.join("()") + + def join(self, name): + assert name == '()' + return self.Instance(name, self) + + def setup(self, item): + item.setup_class(self.obj) + + def teardown(self, item): + item.teardown_class(self.obj) + +class Instance(PyCollectorMixin, Collector): + def Item(self): + return getattr(self.obj, 'Item', super(Class, self).Item) + Item = property(Item) + +def makebound(x): + assert callable(x) + if hasattr(x, 'im_self') and x.im_self is None: + return x.__get__(x.im_class(), x.im_class) + return x + +class Generator(Collector): + def obj(self): + return makebound(getattr(self.parent.obj, self.name)) + obj = property(obj, None, None, "test generator function") + + def listdir(self): + l = [] + for i, x in py.builtin.enumerate(self.obj()): + name = "%s:%d" % (self.name, i) + l.append(self.join(name)) + return l + + def join(self, name): + myname, num = name.split(':') + if myname != self.name: + raise NameError("name %r is foreign to %r" %(name,self)) + num = int(num) + for i, x in py.builtin.enumerate(self.obj()): + if i == num: + if isinstance(x, (Collector, self.Item)): + return x + call, args = self.getcallargs(x) + return self.GenItem(name, self, call, args) -class Generator(PyCollector): - def builditem(self, obj): + def getcallargs(self, obj): if isinstance(obj, (tuple, list)): - call = obj[0] - args = obj[1:] + call, args = obj[0], obj[1:] else: - call = obj - args = () - #if hasattr(call, 'gi_running'): - # return ... Generator(py.path.obj(call), *args) - return self.GenItem(self.extpy, call, *args) - - def __iter__(self): - try: - # XXX we need to setup/teardown state here - #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)" - gen = self.extpy.resolve() - if hasattr(gen, 'im_self') and not gen.im_self: - gen = gen.__get__(gen.im_class(), gen.im_class) - for call in gen(): - yield self.builditem(call) - except: - yield self._except() + call, args = obj, () + assert callable(call) + return call, args + + def sortvalue(self): + obj = self.obj + obj = getattr(obj, 'im_func', obj) + obj = obj.func_code + return (getattr(obj, 'co_filename', None), + getattr(obj, 'co_firstlineno', py.std.sys.maxint)) + Modified: py/branch/py-collect/test2/defaultconfig.py ============================================================================== --- py/dist/py/test/defaultconfig.py (original) +++ py/branch/py-collect/test2/defaultconfig.py Tue Feb 1 14:06:58 2005 @@ -1,9 +1,10 @@ import py -Option = py.test.Option +Option = py.test2.Option -Module = py.test.collect.Module -Directory = py.test.collect.Directory -Class = py.test.collect.Class +Module = py.test2.collect.Module +Directory = py.test2.collect.Directory +Class = py.test2.collect.Class +Generator = py.test2.collect.Generator def getreporter(): return py.test.TextReporter() Modified: py/branch/py-collect/test2/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/branch/py-collect/test2/item.py Tue Feb 1 14:06:58 2005 @@ -11,44 +11,29 @@ class SetupItem(object): state = SetupState() - def teardownall(cls): - while cls.state._setupstack: - item, extpy, obj = cls.state._setupstack.pop() - item._dispatch_teardown(obj) - teardownall = classmethod(teardownall) - - def _dispatch_teardown(self, obj): - if ismodule(obj): - self.teardown_module(obj) - elif isclass(obj): - self.teardown_class(obj) - - def _dispatch_setup(self, obj): - if ismodule(obj): - self.setup_module(obj) - elif isclass(obj): - self.setup_class(obj) - else: - return False - return True + def teardown_all(cls): + stack = self.state._setuptack + while stack: + col, item = stack.pop() + col.teardown(item) + teardown_all = classmethod(teardown_all) - def setup_path(self, extpy): + def prepare(self): """ setup objects along the path to the test-method - (pointed to by extpy). Tear down any previously - setup objects which are not directly needed. + Teardown any unneccessary previously setup objects. """ - # the setupstack contains (item, 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.state._setupstack - while stack and not extpy.relto(stack[-1][1]): - item, addr, obj = stack.pop() - item._dispatch_teardown(obj) - rest = extpy.parts()[len(stack):-1] - for x in rest: - obj = x.resolve() - if self._dispatch_setup(obj): - stack.append((self, x, obj)) + needed_collectors = self.listcols() + while stack: + col, item = stack.pop() + col.teardown(item) + try: + i = needed_collectors.index(col) + except ValueError: + continue + else: + for col in needed_collectors[i+1:]: + col.setup(item) + stack.append((col, item)) def setup_module(self, mod): if hasattr(mod, 'setup_module'): @@ -65,54 +50,52 @@ def teardown_class(self, cls): if hasattr(cls, 'teardown_class'): cls.teardown_class.im_func(cls) - - def setup_method(self, method): - if hasattr(getattr(method, 'im_self', None), 'setup_method'): - method.im_self.setup_method(method) - - def teardown_method(self, method): - if hasattr(getattr(method, 'im_self', None), 'teardown_method'): - method.im_self.teardown_method(method) - - def setup_function(self, function): - x = function.func_globals.get('setup_function', None) - if x is not None: - x(function) - - def teardown_function(self, function): - x = function.func_globals.get('teardown_function', None) - if x is not None: - x(function) - - def make_callable(self, method): - assert callable(method) - if not hasattr(method, 'im_class'): - return method - if self.state._instance.__class__ != method.im_class: - self.state._instance = method.im_class() - return method.__get__(self.state._instance, method.im_class) + class Item(SetupItem): """ an Item is responsible for locating and executing a Python callable test object. """ + class Outcome: + def __init__(self, **kwargs): + assert 'msg' not in kwargs or isinstance(kwargs['msg'], str), ( + "given 'msg' argument is not a string" ) + self.__dict__.update(kwargs) + def __repr__(self): + return getattr(self, 'msg', object.__repr__(self)) + class Passed(Outcome): pass + class Failed(Outcome): pass + class ExceptionFailure(Failed): msg = 'DID NOT RAISE' + class Skipped(Outcome): pass - def __init__(self, extpy, *args): - self.extpy = extpy - self.name = extpy.basename + def __init__(self, name, parent, args=()): + self.name = name + self.parent = parent self.args = args - def run(self, driver): - self.setup_path(self.extpy) - method = self.make_callable(self.extpy.resolve()) - if hasattr(method, 'im_self'): - self.setup_method(method) - else: - self.setup_function(method) - try: - self.execute(method, *self.args) - finally: - self.teardown_method(method) + def __repr__(self): + return "<%s %r>" %(self.__class__.__name__, self.name) + + def obj(self): + try: + return self._obj + except AttributeError: + self._obj = obj = getattr(self.parent.obj, self.name) + return obj + obj = property(obj, None, None, "underlying callable") + + def sortvalue(self): + obj = self.obj + if hasattr(obj, 'im_func'): + obj = obj.im_func + if hasattr(obj, 'func_code'): + obj = obj.func_code + return (getattr(obj, 'co_filename', None), + getattr(obj, 'co_firstlineno', py.std.sys.maxint)) + + def run(self): + self.prepare() + self.execute(self.obj, *self.args) def execute(self, target, *args): """ default implementation for calling a test target is to @@ -126,34 +109,25 @@ """ return "%s%r" % (self.extpy.modpath, self.args) - class Outcome: - def __init__(self, **kwargs): - assert 'msg' not in kwargs or isinstance(kwargs['msg'], str), ( - "given 'msg' argument is not a string" ) - self.__dict__.update(kwargs) - def __repr__(self): - return getattr(self, 'msg', object.__repr__(self)) - class Passed(Outcome): pass - class Failed(Outcome): pass - class ExceptionFailure(Failed): msg = 'DID NOT RAISE' - class Skipped(Outcome): pass +class Function(Item): + def setup(self, item): + item.setup_function(self.obj) -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 + def teardown(self, item): + item.teardown_function(self.obj) - def execute(self, target, *args): - #print "calling %r%r" %(self.call, args) - return self.call(*args) + def setup_function(self, function): + if hasattr(self.obj, 'setup_function'): + self.obj.setup_function(self.obj) - 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) + def teardown_function(self, function): + if hasattr(self.obj, 'teardown_function'): + self.obj.teardown_function(self.obj) + +class GenItem(Item): + def __init__(self, name, parent, call, args): + super(GenItem, self).__init__(name, parent, args) + self._obj = call # # triggering specific outcomes while executing Items Modified: py/branch/py-collect/test2/testing/test/data/disabled.py ============================================================================== --- py/dist/py/test/testing/test/data/disabled.py (original) +++ py/branch/py-collect/test2/testing/test/data/disabled.py Tue Feb 1 14:06:58 2005 @@ -1,4 +1,4 @@ -class x: - class y: - disabled = True - def test_method(self): pass +class TestDisabled: + disabled = True + def test_method(self): + pass Added: py/branch/py-collect/test2/testing/test/data/filetest.py ============================================================================== --- (empty file) +++ py/branch/py-collect/test2/testing/test/data/filetest.py Tue Feb 1 14:06:58 2005 @@ -0,0 +1,7 @@ + +def test_one(): + assert 42 == 43 + +class TestClass: + def test_method_one(self): + assert 42 == 43 Modified: py/branch/py-collect/test2/testing/test_collect.py ============================================================================== --- py/dist/py/test/testing/test_collect.py (original) +++ py/branch/py-collect/test2/testing/test_collect.py Tue Feb 1 14:06:58 2005 @@ -1,54 +1,63 @@ from __future__ import generators import py -from py.__impl__.test import collect -autopath = py.magic.autopath() -testdir = autopath.dirpath('test') -assert testdir.check(dir=1) -datadir = testdir / 'data' - +datadir = py.magic.autopath().dirpath('test', 'data') def test_failing_import_execfile(): fn = datadir / 'failingimport.py' - col = collect.Module(py.path.extpy(fn)) + col = py.test2.collect.Module(fn) def _import(): - l = list(col) - assert l - ex, = l - assert issubclass(ex.excinfo.type, ImportError) + py.test2.raises(ImportError, col.listdir) _import() _import() +def XXXtest_finds_root(): + fn = datadir / 'filetest.py' + col = py.test2.collect.Module(fn) + root, namelist = col.fromroot() + assert isinstance(root, py.test2.collect.Directory) + cur = root + for x in namelist: + cur = cur.join(x) + assert cur.name == col.name + assert cur.parent == col.parent + assert cur.fspath == cur.fspath + +def test_finds_tests(): + fn = datadir / 'filetest.py' + col = py.test2.collect.Module(fn) + l = col.listdir() + assert len(l) == 2 + items = list(col.iteritems()) + assert len(items) == 2 + + assert l[0].name == 'test_one' + assert l[1].name == 'TestClass' + assert l[1].fspath == fn + def test_failing_import_directory(): - class MyDirectory(collect.Directory): - fil = py.path.checker(basestarts="testspecial_", ext='.py') - l = list(MyDirectory(datadir)) + class MyDirectory(py.test2.collect.Directory): + filefilter = py.path.checker(fnmatch='testspecial*.py') + l = MyDirectory(datadir).listdir() assert len(l) == 1 - assert isinstance(l[0], collect.Module) - l2 = list(l[0]) - assert l2 - exc = l2[0] - assert isinstance(exc, collect.Error) - assert issubclass(exc.excinfo.type, ImportError) + assert isinstance(l[0], py.test2.collect.Module) + py.test.raises(ImportError, l[0].listdir) 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.value, py.error.ENOENT) + fn = datadir.join('nada','no') + col = py.test2.collect.Module(fn) + py.test2.raises(py.error.ENOENT, col.listdir) def test_syntax_error_in_module(): modpath = py.path.extpy(datadir.join('syntax_error.py')) - col = collect.Module(modpath) - l2 = list(col) - assert len(l2) == 1 - assert isinstance(l2[0], collect.Error) - assert issubclass(l2[0].excinfo.type, SyntaxError) + col = py.test2.collect.Module(modpath) + py.test.raises(SyntaxError, col.listdir) def test_disabled_class(): - extpy = py.path.extpy(datadir.join('disabled.py')) - l = list(collect.Class(extpy)) - assert len(l) == 0 + col = py.test2.collect.Module(datadir.join('disabled.py')) + l = col.listdir() + assert len(l) == 1 + assert isinstance(l[0], py.test2.collect.Class) + assert l[0].listdir() == [] class Testsomeclass: disabled = True @@ -56,16 +65,57 @@ raise ValueError -class TestWithCustomItem: - class Item(py.test.Item): - flag = [] - def execute(self, target, *args): - self.flag.append(42) - target(*args) - - def test_hello(self): - assert self.Item.flag == [42] +#class TestWithCustomItem: +# class Item(py.test2.Item): +# flag = [] +# def execute(self, target, *args): +# self.flag.append(42) +# target(*args) +# +# def test_hello(self): +# assert self.Item.flag == [42] +# +def test_generative_simple(): + o = py.test2.config.tmpdir.ensure('generativetest', dir=1) + tfile = o.join('test_generative.py') + tfile.write(py.code.Source(""" + def func1(arg, arg2): + assert arg == arg2 + + def test_gen(): + yield func1, 17, 3*5 + yield func1, 42, 6*7 + + class TestGenMethods: + def test_gen(self): + yield func1, 17, 3*5 + yield func1, 42, 6*7 + """)) + col = py.test2.collect.Module(tfile) + l = col.listdir() + assert len(l) == 2 + + generator = l[0] + assert isinstance(generator, py.test2.collect.Generator) + l2 = generator.listdir() + assert len(l2) == 2 + assert isinstance(l2[0], py.test2.GenItem) + assert isinstance(l2[1], py.test2.GenItem) + assert l2[0].name == 'test_gen:0' + assert l2[1].name == 'test_gen:1' + + classlist = l[1].listdir() + assert len(classlist) == 1 + generator = classlist[0] + assert isinstance(generator, py.test2.collect.Generator) + l2 = generator.listdir() + assert len(l2) == 2 + assert isinstance(l2[0], py.test2.GenItem) + assert isinstance(l2[1], py.test2.GenItem) + assert l2[0].name == 'test_gen:0' + assert l2[1].name == 'test_gen:1' + l = [] def test_1(): l.append(1) @@ -84,66 +134,34 @@ 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] - def test_custom_collection_from_conftest(): - o = py.test.config.tmpdir.ensure('customconfigtest', dir=1) + o = py.test2.config.tmpdir.ensure('customconfigtest', dir=1) o.ensure('conftest.py').write("""if 1: import py - class MyItem(py.test.Item): + class MyItem(py.test2.Item): pass - class Directory(py.test.collect.Directory): - def fil(self, fspath): + class Directory(py.test2.collect.Directory): + def filefilter(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) + class Module(py.test2.collect.Module): + Item = MyItem + def funcnamefilter(self, name): + return name.startswith('check_') + def classnamefilter(self, name): + return name.startswith('CustomTestClass') + class Class(py.test2.collect.Class): + Item = MyItem + def funcnamefilter(self, name): + return name.startswith('check_') """) o.ensure('somedir', 'check_something').write("""if 1: def check_func(): assert 42 == 42 - class TestClass: + class CustomTestClass: 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 py.__impl__.test2.collect import getfscollector + items = list(getfscollector(o).iteritems()) + assert len(items) == 2 + assert items[1].__class__.__name__ == 'MyItem' From arigo at codespeak.net Tue Feb 1 14:37:46 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Tue, 1 Feb 2005 14:37:46 +0100 (MET) Subject: [py-svn] r8786 - py/dist/py/documentation Message-ID: <20050201133746.6E8A927B9A@code1.codespeak.net> Author: arigo Date: Tue Feb 1 14:37:46 2005 New Revision: 8786 Modified: py/dist/py/documentation/getting_started.txt Log: Path correction pointed out by senra on #pypy. Modified: py/dist/py/documentation/getting_started.txt ============================================================================== --- py/dist/py/documentation/getting_started.txt (original) +++ py/dist/py/documentation/getting_started.txt Tue Feb 1 14:37:46 2005 @@ -42,7 +42,7 @@ There already is a convenient way for Bash/Shell based systems to setup the ``PYTHONPATH`` as well as the shell ``PATH``, insert:: - eval `python ~/path/to/dist-py/dist/py/env.py` + eval `python ~/path/to/dist-py/py/env.py` into your ``.bash_profile``. Of course, you need to specify your own checkout-directory. From hpk at codespeak.net Wed Feb 2 23:58:01 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 2 Feb 2005 23:58:01 +0100 (MET) Subject: [py-svn] r8812 - in py/branch/py-collect/code: . testing Message-ID: <20050202225801.6BC3B27BC9@code1.codespeak.net> Author: hpk Date: Wed Feb 2 23:58:01 2005 New Revision: 8812 Modified: py/branch/py-collect/code/frame.py py/branch/py-collect/code/testing/test_frame.py Log: py.code.Code accepts functions (bound, unbound or plain) Modified: py/branch/py-collect/code/frame.py ============================================================================== --- py/branch/py-collect/code/frame.py (original) +++ py/branch/py-collect/code/frame.py Wed Feb 2 23:58:01 2005 @@ -2,6 +2,8 @@ class Code(object): def __init__(self, f_code): + f_code = getattr(f_code, 'im_func', f_code) + f_code = getattr(f_code, 'func_code', f_code) self.raw = f_code self.firstlineno = f_code.co_firstlineno - 1 self.name = f_code.co_name Modified: py/branch/py-collect/code/testing/test_frame.py ============================================================================== --- py/branch/py-collect/code/testing/test_frame.py (original) +++ py/branch/py-collect/code/testing/test_frame.py Wed Feb 2 23:58:01 2005 @@ -1,10 +1,15 @@ import sys -from py.code import Frame +import py def test_frame_getsourcelineno_myself(): def func(): return sys._getframe(0) f = func() - f = Frame(f) + f = py.code.Frame(f) source, lineno = f.code.fullsource, f.lineno assert source[lineno].startswith(" return sys._getframe(0)") + +def test_code_from_func(): + co = py.code.Code(test_frame_getsourcelineno_myself) + assert co.firstlineno + assert co.path From hpk at codespeak.net Wed Feb 2 23:58:42 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 2 Feb 2005 23:58:42 +0100 (MET) Subject: [py-svn] r8813 - py/branch/py-collect/test2/tool Message-ID: <20050202225842.AE63027BC9@code1.codespeak.net> Author: hpk Date: Wed Feb 2 23:58:42 2005 New Revision: 8813 Modified: py/branch/py-collect/test2/tool/outerrcapture.py Log: provide the same interface as that other outerrcapture hack (in py/misc/) Modified: py/branch/py-collect/test2/tool/outerrcapture.py ============================================================================== --- py/branch/py-collect/test2/tool/outerrcapture.py (original) +++ py/branch/py-collect/test2/tool/outerrcapture.py Wed Feb 2 23:58:42 2005 @@ -21,9 +21,13 @@ def reset(self): """ return captured output and restore sys.stdout/err.""" + x, y = self.done() + return x.read(), y.read() + + def done(self): o,e = sys.stdout, sys.stderr sys.stdout, sys.stderr = self.oldout, self.olderr del self.oldout, self.olderr - out = self.newout.getvalue() - err = self.newerr.getvalue() - return out, err + o.seek(0) + e.seek(0) + return o,e From hpk at codespeak.net Thu Feb 3 00:03:04 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 3 Feb 2005 00:03:04 +0100 (MET) Subject: [py-svn] r8814 - in py/branch/py-collect/test2: . report/text testing testing/data testing/test testing/test/data Message-ID: <20050202230304.511E327BC9@code1.codespeak.net> Author: hpk Date: Thu Feb 3 00:03:04 2005 New Revision: 8814 Added: py/branch/py-collect/test2/terminal.py (contents, props changed) py/branch/py-collect/test2/testing/data/ - copied from r8784, py/branch/py-collect/test2/testing/test/data/ py/branch/py-collect/test2/testing/test_drive.py (contents, props changed) py/branch/py-collect/test2/testing/test_setup_nested.py - copied, changed from r8784, py/branch/py-collect/test2/testing/test/test_setup_nested.py Removed: py/branch/py-collect/test2/compat.py py/branch/py-collect/test2/testing/test/data/ py/branch/py-collect/test2/testing/test/test_setup_nested.py py/branch/py-collect/test2/testing/test_compat.py Modified: py/branch/py-collect/test2/collect.py py/branch/py-collect/test2/config.py py/branch/py-collect/test2/defaultconfig.py py/branch/py-collect/test2/drive.py py/branch/py-collect/test2/item.py py/branch/py-collect/test2/report/text/reporter.py py/branch/py-collect/test2/report/text/summary.py py/branch/py-collect/test2/testing/test_collect.py Log: another train ride refactoring. "Extpy" is not used anymore. Collector and Items are now much the same (issubclass(Item, Collector)). This should allow more uniform error handling. It should now be easier to integrate Doctests at collection level. more importantly, collectors now follow listdir()/join() semantics (see the py.test broken thread on py-dev). Once the dust settles i'll try to remerge py.test2 to py.test and fix the documentation. The refactoring is still not complete, though. Modified: py/branch/py-collect/test2/collect.py ============================================================================== --- py/branch/py-collect/test2/collect.py (original) +++ py/branch/py-collect/test2/collect.py Thu Feb 3 00:03:04 2005 @@ -10,22 +10,24 @@ Class Instance # empty setup/teardown Function - Item Generator - GenItem + ANY Function - Item Generator - GenItem + Function + Directory + ... """ def configproperty(name): def fget(self): - print "retrieving %r property from %s" %(name, self.fspath) + #print "retrieving %r property from %s" %(name, self.fspath) return py.test2.config.getconfigvalue(self.fspath, name) return property(fget) def getfscollector(fspath): + if isinstance(fspath, str): + fspath = py.path.local(fspath) return Directory(fspath.dirpath()).join(fspath.basename) class Collector(object): @@ -33,27 +35,102 @@ self.name = name self.parent = parent - Item = py.test2.Item - GenItem = py.test2.GenItem Module = configproperty('Module') Directory = configproperty('Directory') Class = configproperty('Class') + Instance = configproperty('Instance') + Function = configproperty('Function') Generator = configproperty('Generator') - def setup(self, item): + class Outcome(object): + def __init__(self, **kwargs): + assert 'msg' not in kwargs or isinstance(kwargs['msg'], str), ( + "given 'msg' argument is not a string" ) + self.__dict__.update(kwargs) + def __repr__(self): + return getattr(self, 'msg', object.__repr__(self)) + class Passed(Outcome): pass + class Failed(Outcome): pass + class ExceptionFailure(Failed): msg = 'DID NOT RAISE' + class Skipped(Outcome): pass + + + def __repr__(self): + return "<%s %r>" %(self.__class__.__name__, self.name) + + def __eq__(self, other): + try: + return self.name == other.name and self.parent == other.parent + except AttributeError: + return False + + def obj(): + def fget(self): + try: + return self._obj + except AttributeError: + self._obj = obj = self._getobj() + return obj + def fset(self, value): + self._obj = value + return property(fget, fset, None, "underlying object") + obj = obj() + + def _getobj(self): + return getattr(self.parent.obj, self.name) + + def sortvalue(self): + """ sorting function helper to bring test methods in + the same order as in their file. + """ + for x in self.run(): + return x.sortvalue() + + def getpathlineno(self): + print "getpathlineno", self + return self.fspath, py.std.sys.maxint + + def setup(self): pass - def teardown(self, item): + def teardown(self): pass + def listchain(self): + """ return list of all parent collectors up to ourself. """ + l = [self] + while 1: + x = l[-1] + if x.parent is not None: + l.append(x.parent) + else: + l.reverse() + return l + + def getmodpath(self): + """ return path from the last module.""" + inmodule = False + newl = [] + for x in self.listchain(): + if not inmodule and not isinstance(x, Module): + continue + if not inmodule: + inmodule = True + continue + if newl and x.name[:1] in '([': + newl[-1] += x.name + else: + newl.append(x.name) + return ".".join(newl) + def iteritems(self): - """ yield all Items of a flattened Collector instance. """ - for x in self.listdir(): - if isinstance(x, Collector): - for y in x.iteritems(): - yield y - else: - yield x + """ yield all Items from a flattened Collector instance.""" + if isinstance(self, py.test2.Item): + yield self + else: + for x in self.run(): + for y in x.iteritems(): + yield y def fspath(): def fget(self): @@ -89,17 +166,11 @@ def recfilter(self, path): return path.check(dotfile=0) - def listdir(self): - l = [] - dirlist = self.fspath.listdir() - for x in dirlist: - if x.check(file=1) and self.filefilter(x): - pass - elif x.check(dir=1) and self.recfilter(x): - pass - else: - continue - l.append(self.join(x.basename)) + def run(self): + l = [self.join(x.basename) for x in self.fspath.listdir() + if (x.check(file=1) and self.filefilter(x) or + x.check(dir=1) and self.recfilter(x))] + l.sort() return filter(None, l) def join(self, name): @@ -109,33 +180,14 @@ elif x.check(dir=1): return self.Directory(x, parent=self) - def __repr__(self): - return "" %(self.__class__.__name__, self.obj.__name__) - - def obj(self): - return getattr(self.parent.obj, self.name) - obj = property(obj) - - def listdir(self): + def run(self): l = [] - if getattr(self.obj, 'disabled', 0): - return l for name in dir(self.obj): if self.funcnamefilter(name) or self.classnamefilter(name): x = self.join(name) @@ -154,13 +206,6 @@ else: return self.Function(name, parent=self) - def sortvalue(self): - """ sorting function helper to bring test methods in - the same order as in their file. - """ - for x in self.listdir(): - return x.sortvalue() - class Module(PyCollectorMixin, FSCollector): def obj(self): try: @@ -170,60 +215,56 @@ return obj obj = property(obj, None, None, "module object") - def setup(self, item): - item.setup_module(self.obj) - def teardown(self, item): - item.teardown_module(self.obj) + def setup(self): + if hasattr(self, 'setup_module'): + self.setup_module(self.obj) + + def teardown(self): + if hasattr(self, 'teardown_module'): + self.teardown_module(self.obj) class Class(PyCollectorMixin, Collector): - - def listdir(self): - return self.join("()") + def run(self): + if getattr(self.obj, 'disabled', 0): + return [] + return [self.join("()")] def join(self, name): assert name == '()' return self.Instance(name, self) - def setup(self, item): - item.setup_class(self.obj) - - def teardown(self, item): - item.teardown_class(self.obj) + def setup(self): + setup_class = self.obj.__dict__.get('setup_class', None) + if setup_class is not None: + setup_class(self.obj) + + def teardown(self): + teardown_class = self.obj.__dict__.get('teardown_class', None) + if teardown_class is not None: + teardown_class(self.obj) class Instance(PyCollectorMixin, Collector): - def Item(self): - return getattr(self.obj, 'Item', super(Class, self).Item) - Item = property(Item) - -def makebound(x): - assert callable(x) - if hasattr(x, 'im_self') and x.im_self is None: - return x.__get__(x.im_class(), x.im_class) - return x + def _getobj(self): + return self.parent.obj() + def Function(self): + return getattr(self.obj, 'Function', super(Instance, self).Function) + Function = property(Function) class Generator(Collector): - def obj(self): - return makebound(getattr(self.parent.obj, self.name)) - obj = property(obj, None, None, "test generator function") - - def listdir(self): - l = [] - for i, x in py.builtin.enumerate(self.obj()): - name = "%s:%d" % (self.name, i) - l.append(self.join(name)) - return l + def run(self): + return [self.join("[%d]" % i) + for i in range(len(list(self.obj())))] def join(self, name): - myname, num = name.split(':') - if myname != self.name: - raise NameError("name %r is foreign to %r" %(name,self)) - num = int(num) + if name[:1] != '[' or name[-1:] != ']': + raise NameError("%r is not an index" %(name,)) + num = int(name[1:-1]) for i, x in py.builtin.enumerate(self.obj()): if i == num: - if isinstance(x, (Collector, self.Item)): + if isinstance(x, (Collector, )): return x call, args = self.getcallargs(x) - return self.GenItem(name, self, call, args) + return self.Function(name, self, args, call) def getcallargs(self, obj): if isinstance(obj, (tuple, list)): @@ -233,10 +274,9 @@ assert callable(call) return call, args - def sortvalue(self): - obj = self.obj - obj = getattr(obj, 'im_func', obj) - obj = obj.func_code - return (getattr(obj, 'co_filename', None), - getattr(obj, 'co_firstlineno', py.std.sys.maxint)) + def getpathlineno(self): + code = py.code.Code(self.obj) + return code.path, code.firstlineno + def sortvalue(self): + return self.getpathlineno() Deleted: /py/branch/py-collect/test2/compat.py ============================================================================== --- /py/branch/py-collect/test2/compat.py Thu Feb 3 00:03:04 2005 +++ (empty file) @@ -1,58 +0,0 @@ -from __future__ import generators -import py - -class TestCaseUnit(py.test.Item): - """ compatibility Unit executor for TestCase methods - honouring setUp and tearDown semantics. - """ - def execute(self, driver): - unboundmethod = self.extpy.resolve() - cls = unboundmethod.im_class - instance = cls() - instance.setUp() - try: - unboundmethod(instance) - finally: - instance.tearDown() - return py.test.Item.Passed() - -class TestCase: - """compatibility class of unittest's TestCase. """ - Item = TestCaseUnit - - def setUp(self): - pass - - def tearDown(self): - pass - - def fail(self, msg=None): - """ fail immediate with given message. """ - raise py.test.Item.Failed(msg=msg) - - def assertRaises(self, excclass, func, *args, **kwargs): - py.test.raises(excclass, func, *args, **kwargs) - failUnlessRaises = assertRaises - - # dynamically construct (redundant) methods - aliasmap = [ - ('x', 'not x', 'assert_, failUnless'), - ('x', 'x', 'failIf'), - ('x,y', 'x!=y', 'failUnlessEqual,assertEqual, assertEquals'), - ('x,y', 'x==y', 'failIfEqual,assertNotEqual, assertNotEquals'), - ] - items = [] - for sig, expr, names in aliasmap: - names = map(str.strip, names.split(',')) - sigsubst = expr.replace('y', '%s').replace('x', '%s') - for name in names: - items.append(""" - def %(name)s(self, %(sig)s): - if %(expr)s: - raise py.test.Item.Failed(tbindex=-2, msg=%(sigsubst)r %% (%(sig)s)) - """ % locals() ) - - source = "".join(items) - exec py.code.Source(source).compile() - -__all__ = ['TestCase'] Modified: py/branch/py-collect/test2/config.py ============================================================================== --- py/branch/py-collect/test2/config.py (original) +++ py/branch/py-collect/test2/config.py Thu Feb 3 00:03:04 2005 @@ -14,8 +14,10 @@ class Config: def __init__(self): - self.configpaths = [] + self.configpaths = [defaultconfig] self.option = optparse.Values() + self.parseargs([]) + self.configpaths.pop() def _gettmpdir(self, sessiondir=[]): try: @@ -113,7 +115,6 @@ assert exe.check() self.option.executable = exe -config = Config() # helpers @@ -123,3 +124,6 @@ yield y for x in parser.option_list: yield x + +# the default that is visible globally (XXX?) +config = Config() Modified: py/branch/py-collect/test2/defaultconfig.py ============================================================================== --- py/branch/py-collect/test2/defaultconfig.py (original) +++ py/branch/py-collect/test2/defaultconfig.py Thu Feb 3 00:03:04 2005 @@ -1,10 +1,12 @@ import py -Option = py.test2.Option +Option = py.test.Option -Module = py.test2.collect.Module -Directory = py.test2.collect.Directory -Class = py.test2.collect.Class -Generator = py.test2.collect.Generator +Module = py.test2.Module +Directory = py.test2.Directory +Class = py.test2.Class +Generator = py.test2.Generator +Function = py.test2.Function +Instance = py.test2.Instance def getreporter(): return py.test.TextReporter() Modified: py/branch/py-collect/test2/drive.py ============================================================================== --- py/branch/py-collect/test2/drive.py (original) +++ py/branch/py-collect/test2/drive.py Thu Feb 3 00:03:04 2005 @@ -1,110 +1,113 @@ -from __future__ import generators import py -collect = py.test.collect +from py.__impl__.test.tool.outerrcapture import SimpleOutErrCapture -class Exit(Exception): - """ for immediate program exits without tracebacks and reporter/summary. """ - def __init__(self, item=None): - self.item = None - Exception.__init__(self) - -def exit(*args): - """ exit immediately without tracebacks and reporter/summary. """ - raise Exit(*args) +class ShadowOptions(object): + def __init__(self, obj): + self._obj = obj + def __getattr__(self, name): + return getattr(self._obj, name) -class Driver: +class Driver(object): """ 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): + self._memo = [] + self.option = ShadowOptions(py.test2.config.option) - def __init__(self, channel): - self.reporter = py.test.config.getfirst('getreporter') () - self._channel = channel - self._failed = [] + def shouldclose(self): + return False - def setup(self): + def header(self): """ setup any neccessary resources. """ + if not self.option.nomagic: + py.magic.invoke(assertion=1) - def teardown(self): + def footer(self): """ teardown any resources we know about. """ - # XXX a cleaner way to teardown setupmanagers? - py.test.Item.teardownall() + py.test2.Item.state.teardown_all() + if not self.option.nomagic: + py.magic.revoke(assertion=1) + + def start(self, colitem): + if not self.option.nocapture and isinstance(colitem, py.test2.Item): + assert not hasattr(colitem, 'iocapture') + colitem.iocapture = SimpleOutErrCapture() + + def finish(self, colitem, res): + try: + res.out, res.err = colitem.iocapture.reset() + except AttributeError: + pass + self._memo.append((colitem, res)) + if isinstance(res, (colitem.Failed,)): + if self.option.exitfirstproblem: + exit("exit on first problem configured.", item=colitem) + + def getresults(self, cls): + return [x for x in self._memo if isinstance(x[1], cls)] - def run(self, collectors): + def warning(self, msg): + raise Warning(msg) + + def runpath(self, path): + from py.__impl__.test2.collect import getfscollector + col = getfscollector(path) + return self.run(col) + + def run(self, *colitems): """ main loop for running tests. """ - self.setup() try: + self.header() try: - self.reporter.start() - for x in collectors: - self.run_collector_or_item(x) + for colitem in colitems: + self.runone(colitem) finally: - self.teardown() - except self.Exit, ex: + self.footer() + except Exit, ex: pass - self.reporter.end() - l = [] - for x in self._failed: - l.append((str(x.extpy.root), str(x.extpy.modpath))) - return l - - def run_collector_or_item(self, obj): - """ run (possibly many) testitems and/or collectors. """ - if self._channel and self._channel.isclosed(): - raise SystemExit, "Channel is closed" - if isinstance(obj, py.test.Item): - self.runitem(obj) - elif isinstance(obj, py.test.collect.Collector): - self.runcollector(obj) - elif isinstance(obj, py.test.collect.Collector.Error): - try: - self.reporter.report_collect_error(obj) - except (KeyboardInterrupt, self.Exit): - raise - except: - print "*" * 80 - print "internal Reporter Error" - print "*" * 80 - import traceback - traceback.print_exc() - raise self.Exit - if self.option.exitfirstproblem: - raise self.Exit() - elif obj is None: - pass # ok, we allow None to mean skip this item/collector - else: - raise TypeError("%r is not a Item or Collector instance" % obj) - def runcollector(self, collector): - close = self.reporter.open(collector) - try: - for obj in collector: - self.run_collector_or_item(obj) + def runone(self, colitem): + if self.shouldclose(): + raise SystemExit, "received external close signal" + self.start(colitem) + try: + try: + res = self.runinner(colitem) + except (KeyboardInterrupt, Exit): + res = None + raise + except colitem.Outcome, res: + pass + except: + excinfo = py.code.ExceptionInfo() + res = colitem.Failed(excinfo=excinfo) finally: - if close: - close() + self.finish(colitem, res) - def runitem(self, item): - if not self.option.collectonly: - self.reporter.startitem(item) - try: - item.run(self) - except item.Outcome, res: - if not hasattr(res, 'excinfo'): - res.excinfo = py.code.ExceptionInfo() - except (KeyboardInterrupt, self.Exit): - 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,)): - self._failed.append(item) - if py.test.config.option.exitfirstproblem: - raise self.Exit(res.item) + def runinner(self, colitem): + if self.option.collectonly and isinstance(colitem, py.test2.Item): + return + res = colitem.run() + try: + seq = iter(res) + except TypeError: + if res is not None: + self.warning("%r.run() returned neither " + "sequence nor None: %r" % (colitem, res)) + return colitem.Passed() + for obj in seq: + #print "diving into", obj + self.runone(obj) + return res + +class Exit(Exception): + """ for immediate program exits without tracebacks and reporter/summary. """ + def __init__(self, msg="unknown reason", item=None): + self.msg = msg + Exception.__init__(self, msg) + +def exit(msg, item=None): + raise Exit(msg=msg, item=item) Modified: py/branch/py-collect/test2/item.py ============================================================================== --- py/branch/py-collect/test2/item.py (original) +++ py/branch/py-collect/test2/item.py Thu Feb 3 00:03:04 2005 @@ -2,99 +2,59 @@ from inspect import isclass, ismodule -class SetupState: +class SetupState(object): """ shared state for setting up/tearing down tests. """ def __init__(self): - self._setupstack = [] - self._instance = None + self.stack = [] -class SetupItem(object): - state = SetupState() - - def teardown_all(cls): - stack = self.state._setuptack - while stack: - col, item = stack.pop() - col.teardown(item) - teardown_all = classmethod(teardown_all) - - def prepare(self): - """ setup objects along the path to the test-method + def teardown_all(self): + while self.stack: + col, item = self.stack.pop() + col.teardown() + + def prepare(self, colitem): + """ setup objects along the collector chain to the test-method Teardown any unneccessary previously setup objects. """ - needed_collectors = self.listcols() - while stack: - col, item = stack.pop() - col.teardown(item) + needed_collectors = colitem.listchain() + while self.stack: + col = self.stack.pop() try: i = needed_collectors.index(col) except ValueError: + col.teardown() continue else: for col in needed_collectors[i+1:]: - col.setup(item) - stack.append((col, item)) + col.setup() + self.stack.append(col) - def setup_module(self, mod): - if hasattr(mod, 'setup_module'): - mod.setup_module(mod) - - def teardown_module(self, mod): - if hasattr(mod, 'teardown_module'): - mod.teardown_module(mod) - - def setup_class(self, cls): - if hasattr(cls, 'setup_class'): - cls.setup_class.im_func(cls) - - def teardown_class(self, cls): - if hasattr(cls, 'teardown_class'): - cls.teardown_class.im_func(cls) - +class Item(py.test2.Collector): + state = SetupState() -class Item(SetupItem): +class Function(Item): """ an Item is responsible for locating and executing a Python callable test object. """ - class Outcome: - def __init__(self, **kwargs): - assert 'msg' not in kwargs or isinstance(kwargs['msg'], str), ( - "given 'msg' argument is not a string" ) - self.__dict__.update(kwargs) - def __repr__(self): - return getattr(self, 'msg', object.__repr__(self)) - class Passed(Outcome): pass - class Failed(Outcome): pass - class ExceptionFailure(Failed): msg = 'DID NOT RAISE' - class Skipped(Outcome): pass - - def __init__(self, name, parent, args=()): + def __init__(self, name, parent, args=(), obj=None): self.name = name self.parent = parent self.args = args + if obj is not None: + self._obj = obj def __repr__(self): return "<%s %r>" %(self.__class__.__name__, self.name) - def obj(self): - try: - return self._obj - except AttributeError: - self._obj = obj = getattr(self.parent.obj, self.name) - return obj - obj = property(obj, None, None, "underlying callable") - + def getpathlineno(self): + code = py.code.Code(self.obj) + return code.path, code.firstlineno + def sortvalue(self): - obj = self.obj - if hasattr(obj, 'im_func'): - obj = obj.im_func - if hasattr(obj, 'func_code'): - obj = obj.func_code - return (getattr(obj, 'co_filename', None), - getattr(obj, 'co_firstlineno', py.std.sys.maxint)) + return self.getpathlineno() def run(self): - self.prepare() + self.state.prepare(self) self.execute(self.obj, *self.args) def execute(self, target, *args): @@ -109,25 +69,13 @@ """ return "%s%r" % (self.extpy.modpath, self.args) -class Function(Item): - def setup(self, item): - item.setup_function(self.obj) - - def teardown(self, item): - item.teardown_function(self.obj) - - def setup_function(self, function): + def setup(self): if hasattr(self.obj, 'setup_function'): - self.obj.setup_function(self.obj) + return self.parent.setup_function(self.obj) - def teardown_function(self, function): + def teardown(self): if hasattr(self.obj, 'teardown_function'): - self.obj.teardown_function(self.obj) - -class GenItem(Item): - def __init__(self, name, parent, call, args): - super(GenItem, self).__init__(name, parent, args) - self._obj = call + return self.parent.teardown_function(self.obj) # # triggering specific outcomes while executing Items Modified: py/branch/py-collect/test2/report/text/reporter.py ============================================================================== --- py/branch/py-collect/test2/report/text/reporter.py (original) +++ py/branch/py-collect/test2/report/text/reporter.py Thu Feb 3 00:03:04 2005 @@ -8,7 +8,6 @@ from summary import Summary from out import getout - class TextReporter(object): Summary = Summary typemap = { @@ -69,9 +68,7 @@ def open(self, collector): if self.option.collectonly: cols = self.__dict__.setdefault('_opencollectors', []) - if len(cols): - print ' ' * len(cols), - print repr(collector) + print ' ' * len(cols), repr(collector) cols.append(collector) return cols.pop Modified: py/branch/py-collect/test2/report/text/summary.py ============================================================================== --- py/branch/py-collect/test2/report/text/summary.py (original) +++ py/branch/py-collect/test2/report/text/summary.py Thu Feb 3 00:03:04 2005 @@ -51,152 +51,6 @@ self.out.sep('=', 'tests finished: %s in %4.2f seconds' % (status, elapsed)) - def skippedreasons(self): - texts = {} - for res in self.getlist(Item.Skipped): - tbindex = getattr(res, 'tbindex', -1) - raisingtb = res.excinfo.traceback[tbindex] - fn = raisingtb.frame.code.path - lineno = raisingtb.lineno - d = texts.setdefault(res.excinfo.exconly(), {}) - d[(fn,lineno)] = res - - if texts: - self.out.line() - self.out.sep('_', 'reasons for skipped tests') - for text, dict in texts.items(): - for (fn, lineno), res in dict.items(): - self.out.line('Skipped in %s:%d' %(fn,lineno)) - self.out.line("reason: %s" % text) - self.out.line() - - def failures(self): - l = self.getlist(Item.Failed) - if l: - self.out.sep('_') - for res in self.getlist(Item.Failed): - self.repr_failure(res.excinfo, res) - - def repr_failure(self, excinfo, res=None): - traceback = excinfo.traceback - if res: - self.cut_traceback(traceback, res.item) - last = traceback[-1] - for entry in traceback: - self.out.line("") - if entry == last: - indent = self.repr_source(entry, 'E') - self.repr_failure_explanation(excinfo, indent) - else: - self.repr_source(entry, '>') - self.out.line("") - self.out.line("[%s:%d]" %(entry.frame.code.path, entry.lineno+1)) - self.repr_locals(entry) - if entry == last: - if res: - root = res.item.extpy.root - modpath = res.item.extpy.modpath - fn, lineno = res.item.extpy.getfilelineno() - if fn != entry.frame.code.path or \ - entry.frame.code.firstlineno != lineno: - self.out.line("[testcode : %s:%d]" % (fn, lineno+1)) - if root == fn: - self.out.line("[modulepath: %s]" %(modpath)) - else: - self.out.line("[modulepath: %s %s]" %(root.basename, modpath)) - if res: - self.repr_out_err(res) - self.out.sep("_") - else: - self.out.sep("_ ") - - - def repr_source(self, entry, marker=">"): - try: - source = entry.getsource() - except py.error.ENOENT: - self.out.line("[failure to get at sourcelines from %r]\n" % entry) - else: - source = source.deindent() - for line in source[:-1]: - self.out.line(" " + line) - lastline = source[-1] - self.out.line(marker + " " + lastline) - try: - s = str(source.getstatement(len(source)-1)) - except KeyboardInterrupt: - raise - except: - #self.out.line("[failed to get last statement]\n%s" %(source,)) - s = str(source[-1]) - #print "XXX %r" % s - return 4 + (len(s) - len(s.lstrip())) - return 0 - - def cut_traceback(self, traceback, item=None, tbindex=None): - if self.option.fulltrace or item is None: - return - path, lineno = item.extpy.getfilelineno() - for i, entry in py.builtin.enumerate(traceback): - if entry.frame.code.path == path and \ - entry.frame.code.firstlineno == lineno: - del traceback[:i] - break - # get rid of all frames marked with __tracebackhide__ - l = [] - for entry in traceback: - try: - x = entry.frame.eval("__tracebackhide__") - except: - x = None - if not x: - l.append(entry) - traceback[:] = l - #if tbindex is not None and tbindex < -1: - # del traceback[tbindex:] - - def repr_failure_explanation(self, excinfo, indent): - info = None - info = getattr(getattr(excinfo, 'value', ''), 'msg', '') - if not info and not self.option.nomagic: - try: - info = excinfo.traceback[-1].reinterpret() # very detailed info - except KeyboardInterrupt: - raise - except: - if self.option.verbose > 1: - self.out.line("[reinterpretation traceback]") - py.std.traceback.print_exc(file=py.std.sys.stdout) - else: - self.out.line("[reinterpretation failed, increase " - "verbosity to see details]") - indent = " " * indent - if not info: - info = str(excinfo) - - lines = info.split('\n') - self.out.line('~' + indent[:-1] + lines.pop(0)) - for x in lines: - self.out.line(indent + x) - - def repr_out_err(self, res): - 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_locals(self, entry): - if self.option.showlocals: - self.out.sep('- ', 'locals') - for name, value in entry.frame.f_locals.items(): - if len(repr(value)) < 70 or not isinstance(value, - (list, tuple, dict)): - self.out.line("%-10s = %r" %(name, value)) - else: - self.out.line("%-10s =\\" % (name,)) - py.std.pprint.pprint(value, stream=self.out) def Xrepr_traceback_raw(self, traceback): recursioncache = {} Added: py/branch/py-collect/test2/terminal.py ============================================================================== --- (empty file) +++ py/branch/py-collect/test2/terminal.py Thu Feb 3 00:03:04 2005 @@ -0,0 +1,245 @@ +import py + +from py.__impl__.test2.report.text.out import getout +from py.__impl__.test2.report.text.summary import Summary +from time import time as now +Item = py.test2.Item + +class TerminalDriver(py.test2.Driver): + def __init__(self, file=None): + super(TerminalDriver, self).__init__() + if file is None: + file = py.std.sys.stdout + self.out = getout(file) + self._started = {} + self.summary = Summary(self.out) + self.summary.option = self.option + self._opencollectors = [] + + # --------------------- + # PROGRESS information + # --------------------- + + def start(self, colitem): + super(TerminalDriver, self).start(colitem) + if self.option.collectonly: + cols = self._opencollectors + self.out.line(' ' * len(cols) + repr(colitem)) + cols.append(colitem) + self._started[colitem] = now() + + def finish(self, colitem, res): + end = now() + super(TerminalDriver, self).finish(colitem, res) + if self.option.collectonly: + cols = self._opencollectors + last = cols.pop() + #assert last == colitem, "expected %r, got %r" %(last, colitem) + + + # ------------------- + # HEADER information + # ------------------- + def header(self): + super(TerminalDriver, self).header() + self.out.sep("=", "test process starts") + option = py.test.config.option + if option.session: + mode = 'session/child process' + elif option.executable: + mode = 'child process' + else: + mode = 'inprocess' + self.out.line("testing-mode: %s" % mode) + self.out.line("executable : %s (%s)" % + (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') + if additional: + for key, descr in additional(): + self.out.line("%s: %s" %(key, descr)) + self.out.sep("=") + self.starttime = now() + + # ------------------- + # FOOTER information + # ------------------- + + def footer(self): + super(TerminalDriver, self).footer() + self.endtime = now() + self.out.line() + self.skippedreasons() + self.failures() + self.summaryline() + + def summaryline(self): + outlist = [] + sum = 0 + for typ in Item.Passed, Item.Failed, Item.Skipped: + l = self.getresults(typ) + if l: + outlist.append('%d %s' % (len(l), typ.__name__.lower())) + sum += len(l) + elapsed = self.endtime-self.starttime + status = "%s" % ", ".join(outlist) + self.out.sep('=', 'tests finished: %s in %4.2f seconds' % + (status, elapsed)) + + def skippedreasons(self): + texts = {} + for colitem, res in self.getresults(Item.Skipped): + tbindex = getattr(res, 'tbindex', -1) + raisingtb = res.excinfo.traceback[tbindex] + fn = raisingtb.frame.code.path + lineno = raisingtb.lineno + d = texts.setdefault(res.excinfo.exconly(), {}) + d[(fn,lineno)] = res + + if texts: + self.out.line() + self.out.sep('_', 'reasons for skipped tests') + for text, dict in texts.items(): + for (fn, lineno), res in dict.items(): + self.out.line('Skipped in %s:%d' %(fn,lineno)) + self.out.line("reason: %s" % text) + self.out.line() + + def failures(self): + l = self.getresults(Item.Failed) + if l: + self.out.sep('_') + for colitem, res in l: + self.repr_failure(colitem, res) + + def repr_failure(self, item, res): + excinfo = res.excinfo + traceback = excinfo.traceback + if item: + self.cut_traceback(traceback, item) + last = traceback[-1] + for entry in traceback: + self.out.line("") + if entry == last: + indent = self.repr_source(entry, 'E') + self.repr_failure_explanation(excinfo, indent) + else: + self.repr_source(entry, '>') + self.out.line("") + self.out.line("[%s:%d]" %(entry.frame.code.path, entry.lineno+1)) + self.repr_locals(entry) + if entry == last: + if item: + root = item.fspath + modpath = item.getmodpath() + fn, lineno = item.getpathlineno() + if fn != entry.frame.code.path or \ + entry.frame.code.firstlineno != lineno: + self.out.line("[testcode : %s:%d]" % (fn, lineno+1)) + if root == fn: + self.out.line("[modulepath: %s]" %(modpath)) + else: + self.out.line("[modulepath: %s %s]" %(root.basename, modpath)) + self.repr_out_err(res) + self.out.sep("_") + else: + self.out.sep("_ ") + + + def repr_source(self, entry, marker=">"): + try: + source = entry.getsource() + except py.error.ENOENT: + self.out.line("[failure to get at sourcelines from %r]\n" % entry) + else: + source = source.deindent() + for line in source[:-1]: + self.out.line(" " + line) + lastline = source[-1] + self.out.line(marker + " " + lastline) + try: + s = str(source.getstatement(len(source)-1)) + except KeyboardInterrupt: + raise + except: + #self.out.line("[failed to get last statement]\n%s" %(source,)) + s = str(source[-1]) + #print "XXX %r" % s + return 4 + (len(s) - len(s.lstrip())) + return 0 + + def cut_traceback(self, traceback, item=None): + if self.option.fulltrace or item is None: + return + path, lineno = item.getpathlineno() + for i, entry in py.builtin.enumerate(traceback): + if entry.frame.code.path == path and \ + entry.frame.code.firstlineno == lineno: + del traceback[:i] + break + # get rid of all frames marked with __tracebackhide__ + l = [] + for entry in traceback: + try: + x = entry.frame.eval("__tracebackhide__") + except: + x = None + if not x: + l.append(entry) + traceback[:] = l + + def repr_failure_explanation(self, excinfo, indent): + info = None + info = getattr(getattr(excinfo, 'value', ''), 'msg', '') + if not info and not self.option.nomagic: + try: + info = excinfo.traceback[-1].reinterpret() # very detailed info + except KeyboardInterrupt: + raise + except: + if self.option.verbose > 1: + self.out.line("[reinterpretation traceback]") + py.std.traceback.print_exc(file=py.std.sys.stdout) + else: + self.out.line("[reinterpretation failed, increase " + "verbosity to see details]") + indent = " " * indent + if not info: + info = str(excinfo) + + lines = info.split('\n') + self.out.line('~' + indent[:-1] + lines.pop(0)) + for x in lines: + self.out.line(indent + x) + + def repr_out_err(self, res): + 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_locals(self, entry): + if self.option.showlocals: + self.out.sep('- ', 'locals') + for name, value in entry.frame.f_locals.items(): + if len(repr(value)) < 70 or not isinstance(value, + (list, tuple, dict)): + self.out.line("%-10s = %r" %(name, value)) + else: + self.out.line("%-10s =\\" % (name,)) + py.std.pprint.pprint(value, stream=self.out) + +def repr_pythonversion(): + v = py.std.sys.version_info + try: + return "%s.%s.%s-%s-%s" % v + except ValueError: + return str(v) Deleted: /py/branch/py-collect/test2/testing/test/test_setup_nested.py ============================================================================== --- /py/branch/py-collect/test2/testing/test/test_setup_nested.py Thu Feb 3 00:03:04 2005 +++ (empty file) @@ -1,52 +0,0 @@ - -# -# test correct setup/teardowns at -# module, class, and instance level - -modlevel = [] -def setup_module(module): - module.modlevel.append(42) - -def teardown_module(module): - modlevel.pop() - -def setup_function(function): - function.answer = 17 - -def teardown_function(function): - del function.answer - -def test_modlevel(): - assert modlevel[0] == 42 - assert test_modlevel.answer == 17 - - -class TestSimpleClassSetup: - clslevel = [] - def setup_class(cls): - cls.clslevel.append(23) - - def teardown_class(cls): - cls.clslevel.pop() - - def test_classlevel(self): - assert self.clslevel[0] == 23 - - def test_modulelevel(self): - assert modlevel == [42] - -class TestInheritedClassSetupStillWorks(TestSimpleClassSetup): - def test_classlevel_anothertime(self): - assert self.clslevel == [23] - -class SetupTeardownOnInstance(TestSimpleClassSetup): - def setup_method(self, method): - self.clslevel.append(17) - - def teardown_method(self, method): - x = self.clslevel.pop() - assert x == 17 - - def test_setup(self): - assert self.clslevel[-1] == 17 - Modified: py/branch/py-collect/test2/testing/test_collect.py ============================================================================== --- py/branch/py-collect/test2/testing/test_collect.py (original) +++ py/branch/py-collect/test2/testing/test_collect.py Thu Feb 3 00:03:04 2005 @@ -1,20 +1,20 @@ from __future__ import generators import py -datadir = py.magic.autopath().dirpath('test', 'data') +datadir = py.magic.autopath().dirpath('data') def test_failing_import_execfile(): fn = datadir / 'failingimport.py' - col = py.test2.collect.Module(fn) + col = py.test2.Module(fn) def _import(): - py.test2.raises(ImportError, col.listdir) + py.test2.raises(ImportError, col.run) _import() _import() def XXXtest_finds_root(): fn = datadir / 'filetest.py' - col = py.test2.collect.Module(fn) + col = py.test2.Module(fn) root, namelist = col.fromroot() - assert isinstance(root, py.test2.collect.Directory) + assert isinstance(root, py.test2.Directory) cur = root for x in namelist: cur = cur.join(x) @@ -24,8 +24,8 @@ def test_finds_tests(): fn = datadir / 'filetest.py' - col = py.test2.collect.Module(fn) - l = col.listdir() + col = py.test2.Module(fn) + l = col.run() assert len(l) == 2 items = list(col.iteritems()) assert len(items) == 2 @@ -35,29 +35,29 @@ assert l[1].fspath == fn def test_failing_import_directory(): - class MyDirectory(py.test2.collect.Directory): + class MyDirectory(py.test2.Directory): filefilter = py.path.checker(fnmatch='testspecial*.py') - l = MyDirectory(datadir).listdir() + l = MyDirectory(datadir).run() assert len(l) == 1 - assert isinstance(l[0], py.test2.collect.Module) - py.test.raises(ImportError, l[0].listdir) + assert isinstance(l[0], py.test2.Module) + py.test.raises(ImportError, l[0].run) def test_module_file_not_found(): fn = datadir.join('nada','no') - col = py.test2.collect.Module(fn) - py.test2.raises(py.error.ENOENT, col.listdir) + col = py.test2.Module(fn) + py.test2.raises(py.error.ENOENT, col.run) def test_syntax_error_in_module(): modpath = py.path.extpy(datadir.join('syntax_error.py')) - col = py.test2.collect.Module(modpath) - py.test.raises(SyntaxError, col.listdir) + col = py.test2.Module(modpath) + py.test.raises(SyntaxError, col.run) def test_disabled_class(): - col = py.test2.collect.Module(datadir.join('disabled.py')) - l = col.listdir() + col = py.test2.Module(datadir.join('disabled.py')) + l = col.run() assert len(l) == 1 - assert isinstance(l[0], py.test2.collect.Class) - assert l[0].listdir() == [] + assert isinstance(l[0], py.test2.Class) + assert not l[0].run() class Testsomeclass: disabled = True @@ -92,29 +92,30 @@ yield func1, 17, 3*5 yield func1, 42, 6*7 """)) - col = py.test2.collect.Module(tfile) - l = col.listdir() + col = py.test2.Module(tfile) + l = col.run() assert len(l) == 2 generator = l[0] - assert isinstance(generator, py.test2.collect.Generator) - l2 = generator.listdir() + print l + assert isinstance(generator, py.test2.Generator) + l2 = generator.run() assert len(l2) == 2 - assert isinstance(l2[0], py.test2.GenItem) - assert isinstance(l2[1], py.test2.GenItem) - assert l2[0].name == 'test_gen:0' - assert l2[1].name == 'test_gen:1' + assert isinstance(l2[0], py.test2.Function) + assert isinstance(l2[1], py.test2.Function) + assert l2[0].name == '[0]' + assert l2[1].name == '[1]' - classlist = l[1].listdir() + classlist = l[1].run() assert len(classlist) == 1 - generator = classlist[0] - assert isinstance(generator, py.test2.collect.Generator) - l2 = generator.listdir() + generator = classlist[0].run()[0] + assert isinstance(generator, py.test2.Generator) + l2 = generator.run() assert len(l2) == 2 - assert isinstance(l2[0], py.test2.GenItem) - assert isinstance(l2[1], py.test2.GenItem) - assert l2[0].name == 'test_gen:0' - assert l2[1].name == 'test_gen:1' + assert isinstance(l2[0], py.test2.Function) + assert isinstance(l2[1], py.test2.Function) + assert l2[0].name == '[0]' + assert l2[1].name == '[1]' l = [] def test_1(): @@ -138,21 +139,21 @@ o = py.test2.config.tmpdir.ensure('customconfigtest', dir=1) o.ensure('conftest.py').write("""if 1: import py - class MyItem(py.test2.Item): + class MyFunction(py.test2.Function): pass - class Directory(py.test2.collect.Directory): + class Directory(py.test2.Directory): def filefilter(self, fspath): return fspath.check(basestarts='check_') - class Module(py.test2.collect.Module): - Item = MyItem + class myfuncmixin: + Function = MyFunction def funcnamefilter(self, name): return name.startswith('check_') + + class Module(myfuncmixin, py.test2.Module): def classnamefilter(self, name): return name.startswith('CustomTestClass') - class Class(py.test2.collect.Class): - Item = MyItem - def funcnamefilter(self, name): - return name.startswith('check_') + class Instance(myfuncmixin, py.test2.Instance): + pass """) o.ensure('somedir', 'check_something').write("""if 1: def check_func(): @@ -164,4 +165,4 @@ from py.__impl__.test2.collect import getfscollector items = list(getfscollector(o).iteritems()) assert len(items) == 2 - assert items[1].__class__.__name__ == 'MyItem' + assert items[1].__class__.__name__ == 'MyFunction' Deleted: /py/branch/py-collect/test2/testing/test_compat.py ============================================================================== --- /py/branch/py-collect/test2/testing/test_compat.py Thu Feb 3 00:03:04 2005 +++ (empty file) @@ -1,51 +0,0 @@ -from __future__ import generators -import py - -class TestCompatTestCaseSetupSemantics(py.test.compat.TestCase): - globlist = [] - - def setUp(self): - self.__dict__.setdefault('l', []).append(42) - self.globlist.append(self) - - def tearDown(self): - self.l.pop() - - def test_issetup(self): - l = self.l - assert len(l) == 1 - assert l[-1] == 42 - self.checkmultipleinstances() - - def test_issetup2(self): - l = self.l - assert len(l) == 1 - assert l[-1] == 42 - self.checkmultipleinstances() - - def checkmultipleinstances(self): - for x,y in zip(self.globlist, self.globlist[1:]): - assert x is not y - -class TestCompatAssertions(py.test.compat.TestCase): - nameparamdef = { - 'failUnlessEqual,assertEqual,assertEquals': ('1, 1', '1, 0'), - 'assertNotEquals,failIfEqual': ('0, 1', '0,0'), - 'failUnless,assert_': ('1', 'None'), - 'failIf': ('0', '1'), - } - - sourcelist = [] - for names, (paramok, paramfail) in nameparamdef.items(): - for name in names.split(','): - source = """ - def test_%(name)s(self): - self.%(name)s(%(paramok)s) - #self.%(name)s(%(paramfail)s) - - def test_%(name)s_failing(self): - self.assertRaises(py.test.Item.Failed, - self.%(name)s, %(paramfail)s) - """ % locals() - co = py.code.Source(source).compile() - exec co Added: py/branch/py-collect/test2/testing/test_drive.py ============================================================================== --- (empty file) +++ py/branch/py-collect/test2/testing/test_drive.py Thu Feb 3 00:03:04 2005 @@ -0,0 +1,53 @@ +import py +datadir = py.magic.autopath().dirpath('data') +from cStringIO import StringIO + +class TestDefaultDriver: + def test_simple(self): + drive = py.test2.Driver() + drive.option.nocapture = True + drive.runpath(datadir / 'filetest.py') + l = drive.getresults(py.test2.Item.Failed) + assert len(l) == 2 + l = drive.getresults(py.test2.Item.Passed) + assert not l + + def test_exit_first_problem(self): + drive = py.test2.Driver() + drive.option.exitfirstproblem = True + drive.runpath(str(datadir / 'filetest.py')) + l = drive.getresults(py.test2.Item.Failed) + assert len(l) == 1 + l = drive.getresults(py.test2.Item.Passed) + assert not l + +class TestTerminalDriver: + def test_terminal(self): + c = StringIO() + drive = py.test2.TerminalDriver(file=c) + drive.runpath(datadir / 'filetest.py') + out = c.getvalue() + print "memo" + print drive._memo + print "out" + print out + l = drive.getresults(py.test2.Item.Failed) + assert len(l) == 2 + assert out.find('2 failed') != -1 + + def test_collectonly(self): + c = StringIO() + drive = py.test2.TerminalDriver(file=c) + drive.option.collectonly = True + drive.runpath(str(datadir / 'filetest.py')) + out = c.getvalue() + print out + l = drive.getresults(py.test2.Item.Failed) + #if l: + # x = l[0][1].excinfo + # print x.exconly() + # print x.traceback + assert len(l) == 0 + for line in ('filetest.py', 'test_one', + 'TestClass', 'test_method_one'): + assert out.find(line) Copied: py/branch/py-collect/test2/testing/test_setup_nested.py (from r8784, py/branch/py-collect/test2/testing/test/test_setup_nested.py) ============================================================================== --- py/branch/py-collect/test2/testing/test/test_setup_nested.py (original) +++ py/branch/py-collect/test2/testing/test_setup_nested.py Thu Feb 3 00:03:04 2005 @@ -20,7 +20,6 @@ assert modlevel[0] == 42 assert test_modlevel.answer == 17 - class TestSimpleClassSetup: clslevel = [] def setup_class(cls): From hpk at codespeak.net Fri Feb 4 13:43:48 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 4 Feb 2005 13:43:48 +0100 (MET) Subject: [py-svn] r8848 - py/branch/py-collect Message-ID: <20050204124348.6262E27C54@code1.codespeak.net> Author: hpk Date: Fri Feb 4 00:01:54 2005 New Revision: 8848 Modified: py/branch/py-collect/__init__.py Log: ups, that was missing from the branch Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/branch/py-collect/__init__.py (original) +++ py/branch/py-collect/__init__.py Fri Feb 4 00:01:54 2005 @@ -27,18 +27,20 @@ 'test2.fail' : ('./test2/item.py', 'fail'), 'test2.exit' : ('./test2/drive.py', 'exit'), 'test2.config' : ('./test2/config.py', 'config'), - 'test2.compat.TestCase' : ('./test2/compat.py', 'TestCase'), - 'test2.collect.Directory' : ('./test2/collect.py', 'Directory'), - 'test2.collect.Module' : ('./test2/collect.py', 'Module'), - 'test2.collect.Class' : ('./test2/collect.py', 'Class'), - 'test2.collect.Generator' : ('./test2/collect.py', 'Generator'), - 'test2.TextReporter' : ('./test2/report/text/reporter.py', - 'TextReporter'), 'test2.Option' : ('./test2/tool/optparse.py', 'Option'), - 'test2.MemoReporter' : ('./test2/report/memo.py', 'MemoReporter'), - 'test2.Item' : ('./test2/item.py', 'Item'), - 'test2.GenItem' : ('./test2/item.py', 'GenItem'), 'test2.Driver' : ('./test2/drive.py', 'Driver'), + 'test2.TerminalDriver' : ('./test2/terminal.py', 'TerminalDriver'), + #'test2.TextReporter' : ('./test2/report/text/reporter.py', + # 'TextReporter'), + + 'test2.Collector' : ('./test2/collect.py', 'Collector'), + 'test2.Directory' : ('./test2/collect.py', 'Directory'), + 'test2.Module' : ('./test2/collect.py', 'Module'), + 'test2.Class' : ('./test2/collect.py', 'Class'), + 'test2.Instance' : ('./test2/collect.py', 'Instance'), + 'test2.Generator' : ('./test2/collect.py', 'Generator'), + 'test2.Item' : ('./test2/item.py', 'Item'), + 'test2.Function' : ('./test2/item.py', 'Function'), 'std' : ('./misc/std.py', 'std'), From arigo at codespeak.net Sat Feb 5 00:07:53 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sat, 5 Feb 2005 00:07:53 +0100 (MET) Subject: [py-svn] r8887 - py/dist/py/documentation Message-ID: <20050204230753.0E72127C48@code1.codespeak.net> Author: arigo Date: Sat Feb 5 00:07:52 2005 New Revision: 8887 Added: py/dist/py/documentation/greenlet.txt Log: First version -- probably obscure and confusing -- of the greenlet documentation. (To be linked!) Added: py/dist/py/documentation/greenlet.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/greenlet.txt Sat Feb 5 00:07:52 2005 @@ -0,0 +1,205 @@ +===================================================== +py.magic.greenlet: Lightweight concurrent programming +===================================================== + +.. contents:: +.. sectnum:: + +Motivation +========== + +The "greenlet" package is a spin-off of `Stackless`_, a version of CPython +that supports micro-threads called "tasklets". Tasklets run +pseudo-concurrently (typically in a single or a few OS-level threads) and +are synchronized with data exchanges on "channels". + +A "greenlet", on the other hand, is a still more primitive notion of +micro-thread with no implicit scheduling. This is useful when you want to +control exactly when your code runs. You can build custom scheduled +micro-threads on top of greenlet; however, it seems that greenlets are +useful on their own as a way to make advanced control flow structures. +For example, we can recreate generators; the difference with Python's own +generators is that our generators can call nested functions and the nested +functions can yield values too. (Additionally, you don't need a "yield" +keyword. See the example in `test_generator.py`_). + +Greenlets are provided as a C extension module for the regular unmodified +interpreter. + +.. _`Stackless`: http://www.stackless.com +.. _`test_generator.py`: http://codespeak.net/svn/user/arigo/greenlet/test_generator.py + +Usage +===== + +Introduction +------------ + +A "greenlet" is a small independent pseudo-thread. Think about it as a +small stack of frames; the outermost (bottom) frame is the initial +function you called, and the innermost frame is the one in which the +greenlet is currently paused. You work with greenlets by creating a +number of such stacks and jumping execution between them. Jumps are never +implicit: a greenlet must choose to jump to another greenlet, which will +cause the former to suspend and the latter to resume where it was +suspended. Jumping between greenlets is called "switching". + +When you create a greenlet, it gets an initially empty stack; when you +first switch to it, it starts the run a specified function, which may call +other functions, switch out of the greenlet, etc. When eventually the +outermost function finishes its execution, the greenlet's stack becomes +empty again and the greenlet is "dead". Greenlets can also die of an +uncaught exception. + +For example:: + + from py.magic import greenlet + + def test1(): + print 12 + gr2.switch() + print 34 + + def test2(): + print 56 + gr1.switch() + print 78 + + gr1 = greenlet(test1) + gr2 = greenlet(test2) + gr1.switch() + +The last line jumps to test1, which prints 12, jumps to test2, prints 56, +jumps back into test1, prints 34; and then test1 finishes and gr1 dies. +At this point, the execution comes back to the original ``gr1.switch()`` +call. Note that 78 is never printed. + +Parents +------- + +Let's see where execution goes when a greenlet dies. Every greenlet has a +"parent" greenlet. The parent greenlet is initially the one in which the +greenlet was created (this can be changed at any time). The parent is +where execution continues when a greenlet dies. This way, greenlets are +organized in a tree. Top-level code that doesn't run in a user-created +greenlet runs in the implicit "main" greenlet, which is the root of the +tree. + +In the above example, both gr1 and gr2 have the main greenlet as a parent. +Whenever one of them dies, the execution comes back to "main". + +Uncaught exceptions are propagated into the parent, too. For example, if +the above test2() contained a typo, it would generate a NameError that +would kill gr2, and the exception would go back directly into "main". +The traceback would show test2, but not test1. Remember, switches are not +calls, but transfer of execution between parallel "stack containers", and +the "parent" defines which stack logically comes "below" the current one. + +Instantiation +------------- + +``py.magic.greenlet`` is the greenlet type, which supports the following +operations: + +``greenlet(run=None, parent=None)`` + Create a new greenlet object (without running it). ``run`` is the + callable to invoke, and ``parent`` is the parent greenlet, which + defaults to the current greenlet. + +``greenlet.getcurrent()`` + Returns the current greenlet (i.e. the one which called this + function). + +``greenlet.GreenletExit`` + This special exception does not propagate to the parent greenlet; it + can be used to kill a single greenlet. + +The ``greenlet`` type can be subclassed, too. A greenlet runs by calling +its ``run`` attribute, which is normally set when the greenlet is +created; but for subclasses it also makes sense to define a ``run`` method +instead of giving a ``run`` argument to the constructor. + +Switching +--------- + +Switches between greenlets occur when the method switch() of a greenlet is +called, in which case execution jumps to the greenlet whose switch() is +called, or when a greenlet dies, in which case execution jumps to the +parent greenlet. During a switch, an object or an exception is "sent" to +the target greenlet; this can be used as a convenient way to pass +information between greenlets. For example:: + + def test1(x, y): + z = gr2.switch(x+y) + print z + + def test2(u): + print u + gr1.switch(42) + + gr1 = greenlet(test1) + gr2 = greenlet(test2) + gr1.switch("hello", " world") + +This prints "hello world" and 42, with the same order of execution as the +previous example. Note that the arguments of test1() and test2() are not +provided when the greenlet is created, but only the first time someone +switches to it. + +Here are the precise rules for sending objects around: + +``g.switch(obj=None or *args)`` + Switches execution to the greenlet ``g``, sending it the given + ``obj``. As a special case, if ``g`` did not start yet, then it will + start to run now; in this case, any number of arguments can be + provided, and ``g.run(*args)`` is called. + +Dying greenlet + If a greenlet's ``run()`` finishes, its return value is the object + sent to its parent. If ``run()`` terminates with an exception, the + exception is propagated to its parent (unless it is a + ``greenlet.GreenletExit`` exception, in which case the exception + object itself is sent to the parent). + +Apart from the cases described above, the target greenlet normally +receives the object as the return value of the call to ``switch()`` in +which it was previously suspended. Indeed, although a call to +``switch()`` does not return immediately, it will still return at some +point in the future, when some other greenlet switches back. When this +occurs, then execution resumes just after the ``switch()`` where it was +suspended, and the ``switch()`` itself appears to return the object that +was just sent. This means that ``x = g.switch(y)`` will send the object +``y`` to ``g``, and will later put the (unrelated) object that some +(unrelated) greenlet pass us back into ``x``. + +Note that any attempt to switch to a dead greenlet actually goes to the +dead greenlet's parent, or its parent's parent, and so on. (The final +parent is the "main" greenlet, which is never dead.) + +Methods and attributes of greenlets +----------------------------------- + +``g.switch(obj=None or *args)`` + Switches execution to the greenlet ``g``. See above. + +``g.run`` + The callable that ``g`` will run when it starts. After ``g`` started, + this attribute no longer exists. + +``g.parent`` + The parent greenlet. This is writeable, but it is not allowed to + create cycles of parents. + +``g.gr_frame`` + The current top frame, or None. + +``bool(g)`` + True if ``g`` is active, False if it is dead or not yet started. + +Greenlets and Python threads +---------------------------- + +Greenlets can be combined with Python threads; in this case, each thread +contains an independent "main" greenlet with a tree of sub-greenlets. It +is not possible to mix or switch between greenlets belonging to different +threads. From arigo at codespeak.net Sat Feb 5 00:38:17 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sat, 5 Feb 2005 00:38:17 +0100 (MET) Subject: [py-svn] r8889 - py/dist/py/documentation Message-ID: <20050204233817.8DCAB27B60@code1.codespeak.net> Author: arigo Date: Sat Feb 5 00:38:17 2005 New Revision: 8889 Modified: py/dist/py/documentation/greenlet.txt Log: Document garbage-collection of greenlets. Modified: py/dist/py/documentation/greenlet.txt ============================================================================== --- py/dist/py/documentation/greenlet.txt (original) +++ py/dist/py/documentation/greenlet.txt Sat Feb 5 00:38:17 2005 @@ -203,3 +203,25 @@ contains an independent "main" greenlet with a tree of sub-greenlets. It is not possible to mix or switch between greenlets belonging to different threads. + +Garbage-collecting live greenlets +--------------------------------- + +If all the references to a greenlet object go away (including the +references from the parent attribute of other greenlets), then there is no +way to ever switch back to this greenlet. In this case, a GreenletExit +exception is generated into the greenlet. This is the only case where a +greenlet receives the execution asynchronously. This gives +``try:finally:`` blocks a chance to clean up resources held by the +greenlet. This feature also enables a programming style in which +greenlets are infinite loops waiting for data and processing it. Such +loops are automatically interrupted when the last reference to the +greenlet goes away. + +The greenlet is expected to either die or be resurrected by having a new +reference to it stored somewhere; just catching and ignoring the +GreenletExit is likely to lead to an infinite loop. + +Greenlets do not participate in garbage collection; cycles involving data +that is present in a greenlet's frames will not be detected. Storing +references to other greenlets cyclically may lead to leaks. From hpk at codespeak.net Sat Feb 5 11:02:54 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 5 Feb 2005 11:02:54 +0100 (MET) Subject: [py-svn] r8894 - py/dist/py/documentation Message-ID: <20050205100254.2386027BF4@code1.codespeak.net> Author: hpk Date: Sat Feb 5 11:02:53 2005 New Revision: 8894 Modified: py/dist/py/documentation/index.txt Log: reference greenlet from the index page of the py lib Modified: py/dist/py/documentation/index.txt ============================================================================== --- py/dist/py/documentation/index.txt (original) +++ py/dist/py/documentation/index.txt Sat Feb 5 11:02:53 2005 @@ -15,6 +15,8 @@ `py.execnet`_ a (probably uniquely) innovative way to distribute programs across the net + `py.magic.greenlet`_: Lightweight in-process concurrent programming (aka Stackless) + `py.xml`_ a fast'n'easy way to generate xml/html documents (including CSS-styling) `miscellaneous features`_ describes some more py lib features @@ -30,6 +32,7 @@ .. _`py.execnet`: execnet.html +.. _`py.magic.greenlet`: greenlet.html .. _`py.test`: test.html .. _`py.xml`: xml.html .. _`Why What how py?`: why_py.html From arigo at codespeak.net Sat Feb 5 12:54:16 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sat, 5 Feb 2005 12:54:16 +0100 (MET) Subject: [py-svn] r8895 - py/dist/py/documentation Message-ID: <20050205115416.E524B27C19@code1.codespeak.net> Author: arigo Date: Sat Feb 5 12:54:16 2005 New Revision: 8895 Modified: py/dist/py/documentation/greenlet.txt Log: Mention coroutines. Modified: py/dist/py/documentation/greenlet.txt ============================================================================== --- py/dist/py/documentation/greenlet.txt (original) +++ py/dist/py/documentation/greenlet.txt Sat Feb 5 12:54:16 2005 @@ -14,7 +14,8 @@ are synchronized with data exchanges on "channels". A "greenlet", on the other hand, is a still more primitive notion of -micro-thread with no implicit scheduling. This is useful when you want to +micro-thread with no implicit scheduling; coroutines, in other words. +This is useful when you want to control exactly when your code runs. You can build custom scheduled micro-threads on top of greenlet; however, it seems that greenlets are useful on their own as a way to make advanced control flow structures. From arigo at codespeak.net Sat Feb 5 14:58:15 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sat, 5 Feb 2005 14:58:15 +0100 (MET) Subject: [py-svn] r8898 - py/dist/py/documentation Message-ID: <20050205135815.9B72F27C1E@code1.codespeak.net> Author: arigo Date: Sat Feb 5 14:58:15 2005 New Revision: 8898 Modified: py/dist/py/documentation/greenlet.txt Log: english. Modified: py/dist/py/documentation/greenlet.txt ============================================================================== --- py/dist/py/documentation/greenlet.txt (original) +++ py/dist/py/documentation/greenlet.txt Sat Feb 5 14:58:15 2005 @@ -171,7 +171,7 @@ suspended, and the ``switch()`` itself appears to return the object that was just sent. This means that ``x = g.switch(y)`` will send the object ``y`` to ``g``, and will later put the (unrelated) object that some -(unrelated) greenlet pass us back into ``x``. +(unrelated) greenlet passes back to us into ``x``. Note that any attempt to switch to a dead greenlet actually goes to the dead greenlet's parent, or its parent's parent, and so on. (The final From arigo at codespeak.net Sat Feb 5 15:11:48 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sat, 5 Feb 2005 15:11:48 +0100 (MET) Subject: [py-svn] r8899 - in py/dist/py/documentation: . example Message-ID: <20050205141148.0AE8D27C1E@code1.codespeak.net> Author: arigo Date: Sat Feb 5 15:11:47 2005 New Revision: 8899 Modified: py/dist/py/documentation/example/ (props changed) py/dist/py/documentation/example/genhtml.py (props changed) py/dist/py/documentation/example/genhtmlcss.py (props changed) py/dist/py/documentation/example/genxml.py (props changed) py/dist/py/documentation/greenlet.txt (props changed) py/dist/py/documentation/xml.txt (props changed) Log: fixeol From hpk at codespeak.net Sat Feb 5 21:25:58 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 5 Feb 2005 21:25:58 +0100 (MET) Subject: [py-svn] r8908 - in py/dist/py/path/local: . testing Message-ID: <20050205202558.E28C627C1E@code1.codespeak.net> Author: hpk Date: Sat Feb 5 21:25:58 2005 New Revision: 8908 Modified: py/dist/py/path/local/local.py py/dist/py/path/local/testing/test_local.py Log: evil bug Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sat Feb 5 21:25:58 2005 @@ -274,7 +274,8 @@ return p._ensuredirs() else: p.dirpath()._ensuredirs() - p.write("") + if not p.check(file=1): + p.write("") return p def stat(self): Modified: py/dist/py/path/local/testing/test_local.py ============================================================================== --- py/dist/py/path/local/testing/test_local.py (original) +++ py/dist/py/path/local/testing/test_local.py Sat Feb 5 21:25:58 2005 @@ -111,6 +111,9 @@ newfile = tmpdir.join('test1','test2') newfile.ensure() assert newfile.check(file=1) + newfile.write("42") + newfile.ensure() + assert newfile.read() == "42" def test_ensure_filepath_withoutdir(self): tmpdir = self.tmpdir From sanxiyn at codespeak.net Mon Feb 7 06:39:31 2005 From: sanxiyn at codespeak.net (sanxiyn at codespeak.net) Date: Mon, 7 Feb 2005 06:39:31 +0100 (MET) Subject: [py-svn] r8943 - py/dist/py/execnet Message-ID: <20050207053931.078FD27C42@code1.codespeak.net> Author: sanxiyn Date: Mon Feb 7 06:39:30 2005 New Revision: 8943 Modified: py/dist/py/execnet/channel.py Log: 22compat Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Mon Feb 7 06:39:30 2005 @@ -1,3 +1,4 @@ +from __future__ import generators import threading import Queue if 'Message' not in globals(): From hpk at codespeak.net Mon Feb 7 08:30:16 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 7 Feb 2005 08:30:16 +0100 (MET) Subject: [py-svn] r8944 - py/dist/py/execnet Message-ID: <20050207073016.587A027C42@code1.codespeak.net> Author: hpk Date: Mon Feb 7 08:30:16 2005 New Revision: 8944 Modified: py/dist/py/execnet/channel.py Log: looks innocent to have from __future__ here, eh? it isn't because channel.py along with some other files is concantenated and send as a bootstrap to the other side. Then it will appear in the middle of a file, causing errors. Yes, execnet should be cleaned up in this regard but ... Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Mon Feb 7 08:30:16 2005 @@ -1,4 +1,3 @@ -from __future__ import generators import threading import Queue if 'Message' not in globals(): From hpk at codespeak.net Mon Feb 7 08:42:50 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 7 Feb 2005 08:42:50 +0100 (MET) Subject: [py-svn] r8945 - py/dist/py/process Message-ID: <20050207074250.82E0A27C48@code1.codespeak.net> Author: hpk Date: Mon Feb 7 08:42:50 2005 New Revision: 8945 Modified: py/dist/py/process/cmdexec.py Log: hide traceback of posix_exec_cmd (usually it's nicer to see the invocation) Modified: py/dist/py/process/cmdexec.py ============================================================================== --- py/dist/py/process/cmdexec.py (original) +++ py/dist/py/process/cmdexec.py Mon Feb 7 08:42:50 2005 @@ -23,6 +23,7 @@ the exception will provide an 'err' attribute containing the error-output from the command. """ + __tracebackhide__ = True import popen2 #print "execing", cmd From hpk at codespeak.net Tue Feb 8 16:47:52 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 8 Feb 2005 16:47:52 +0100 (MET) Subject: [py-svn] r8994 - in py/branch/py-collect: documentation documentation/example path/local path/local/testing process Message-ID: <20050208154752.2F37E27B8D@code1.codespeak.net> Author: hpk Date: Tue Feb 8 16:47:51 2005 New Revision: 8994 Added: py/branch/py-collect/documentation/greenlet.txt (props changed) - copied unchanged from r8993, py/dist/py/documentation/greenlet.txt Modified: py/branch/py-collect/documentation/example/ (props changed) py/branch/py-collect/documentation/example/genhtml.py (props changed) py/branch/py-collect/documentation/example/genhtmlcss.py (props changed) py/branch/py-collect/documentation/example/genxml.py (props changed) py/branch/py-collect/documentation/getting_started.txt py/branch/py-collect/documentation/index.txt py/branch/py-collect/documentation/xml.txt (props changed) py/branch/py-collect/path/local/local.py py/branch/py-collect/path/local/testing/test_local.py py/branch/py-collect/process/cmdexec.py Log: merged the latest trunk changes back into the collect branch Modified: py/branch/py-collect/documentation/getting_started.txt ============================================================================== --- py/branch/py-collect/documentation/getting_started.txt (original) +++ py/branch/py-collect/documentation/getting_started.txt Tue Feb 8 16:47:51 2005 @@ -42,7 +42,7 @@ There already is a convenient way for Bash/Shell based systems to setup the ``PYTHONPATH`` as well as the shell ``PATH``, insert:: - eval `python ~/path/to/dist-py/dist/py/env.py` + eval `python ~/path/to/dist-py/py/env.py` into your ``.bash_profile``. Of course, you need to specify your own checkout-directory. Modified: py/branch/py-collect/documentation/index.txt ============================================================================== --- py/branch/py-collect/documentation/index.txt (original) +++ py/branch/py-collect/documentation/index.txt Tue Feb 8 16:47:51 2005 @@ -15,6 +15,8 @@ `py.execnet`_ a (probably uniquely) innovative way to distribute programs across the net + `py.magic.greenlet`_: Lightweight in-process concurrent programming (aka Stackless) + `py.xml`_ a fast'n'easy way to generate xml/html documents (including CSS-styling) `miscellaneous features`_ describes some more py lib features @@ -30,6 +32,7 @@ .. _`py.execnet`: execnet.html +.. _`py.magic.greenlet`: greenlet.html .. _`py.test`: test.html .. _`py.xml`: xml.html .. _`Why What how py?`: why_py.html Modified: py/branch/py-collect/path/local/local.py ============================================================================== --- py/branch/py-collect/path/local/local.py (original) +++ py/branch/py-collect/path/local/local.py Tue Feb 8 16:47:51 2005 @@ -274,7 +274,8 @@ return p._ensuredirs() else: p.dirpath()._ensuredirs() - p.write("") + if not p.check(file=1): + p.write("") return p def stat(self): Modified: py/branch/py-collect/path/local/testing/test_local.py ============================================================================== --- py/branch/py-collect/path/local/testing/test_local.py (original) +++ py/branch/py-collect/path/local/testing/test_local.py Tue Feb 8 16:47:51 2005 @@ -111,6 +111,9 @@ newfile = tmpdir.join('test1','test2') newfile.ensure() assert newfile.check(file=1) + newfile.write("42") + newfile.ensure() + assert newfile.read() == "42" def test_ensure_filepath_withoutdir(self): tmpdir = self.tmpdir Modified: py/branch/py-collect/process/cmdexec.py ============================================================================== --- py/branch/py-collect/process/cmdexec.py (original) +++ py/branch/py-collect/process/cmdexec.py Tue Feb 8 16:47:51 2005 @@ -23,6 +23,7 @@ the exception will provide an 'err' attribute containing the error-output from the command. """ + __tracebackhide__ = True import popen2 #print "execing", cmd From hpk at codespeak.net Tue Feb 8 23:44:34 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 8 Feb 2005 23:44:34 +0100 (MET) Subject: [py-svn] r9012 - in py/dist/py: . c-extension c-extension/greenlet magic misc/testing Message-ID: <20050208224434.596AF27BDA@code1.codespeak.net> Author: hpk Date: Tue Feb 8 23:44:34 2005 New Revision: 9012 Added: py/dist/py/c-extension/ py/dist/py/c-extension/greenlet/ - copied from r9011, user/arigo/greenlet/ py/dist/py/magic/greenlet.py Modified: py/dist/py/__init__.py py/dist/py/magic/ (props changed) py/dist/py/misc/testing/test_initpkg.py Log: move greenlets into a central "c-extension" directory. remove externals from py/magic etc.pp. tests pass. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Tue Feb 8 23:44:34 2005 @@ -31,11 +31,11 @@ 'path.extpy' : ('./path/extpy/extpy.py', 'Extpy'), 'path.checker' : ('./path/common.py', 'checker'), + 'magic.greenlet' : ('./magic/greenlet.py', 'greenlet'), 'magic.invoke' : ('./magic/invoke.py', 'invoke'), 'magic.revoke' : ('./magic/invoke.py', 'revoke'), 'magic.patch' : ('./magic/patch.py', 'patch'), 'magic.revert' : ('./magic/patch.py', 'revert'), - 'magic.greenlet' : ('./magic/greenlet/greenlet.c', 'greenlet'), 'magic.autopath' : ('./magic/autopath.py', 'autopath'), 'magic.View' : ('./magic/viewtype.py', 'View'), 'magic.AssertionError' : ('./magic/assertion.py', 'AssertionError'), Added: py/dist/py/magic/greenlet.py ============================================================================== --- (empty file) +++ py/dist/py/magic/greenlet.py Tue Feb 8 23:44:34 2005 @@ -0,0 +1,5 @@ + +import py +gdir = py.path.local(py.__file__).dirpath() +path = gdir.join('c-extension', 'greenlet', 'greenlet.c') +greenlet = path.getpymodule().greenlet Modified: py/dist/py/misc/testing/test_initpkg.py ============================================================================== --- py/dist/py/misc/testing/test_initpkg.py (original) +++ py/dist/py/misc/testing/test_initpkg.py Tue Feb 8 23:44:34 2005 @@ -34,7 +34,7 @@ base.join('path', 'gateway',), base.join('documentation',), base.join('test', 'testing', 'import_test'), - base.join('magic', 'greenlet'), + base.join('c-extension',), base.join('bin'), base.join('execnet', 'bin'), ) From hpk at codespeak.net Wed Feb 9 00:05:31 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 9 Feb 2005 00:05:31 +0100 (MET) Subject: [py-svn] r9013 - in py/dist/py: c-extension magic Message-ID: <20050208230531.9301A27BDA@code1.codespeak.net> Author: hpk Date: Wed Feb 9 00:05:31 2005 New Revision: 9013 Added: py/dist/py/c-extension/conftest.py - copied, changed from r9012, py/dist/py/magic/conftest.py Removed: py/dist/py/magic/conftest.py Log: conditionally don't run greenlets on amd64 Copied: py/dist/py/c-extension/conftest.py (from r9012, py/dist/py/magic/conftest.py) ============================================================================== --- py/dist/py/magic/conftest.py (original) +++ py/dist/py/c-extension/conftest.py Wed Feb 9 00:05:31 2005 @@ -2,6 +2,7 @@ class Directory(py.test.collect.Directory): def rec(self, path): - if path.basename != 'testing': - return False + if py.std.sys.platform == 'linux2': + if path.basename == 'greenlet': + return False return super(Directory, self).rec(path) Deleted: /py/dist/py/magic/conftest.py ============================================================================== --- /py/dist/py/magic/conftest.py Wed Feb 9 00:05:31 2005 +++ (empty file) @@ -1,7 +0,0 @@ -import py - -class Directory(py.test.collect.Directory): - def rec(self, path): - if path.basename != 'testing': - return False - return super(Directory, self).rec(path) From hpk at codespeak.net Wed Feb 9 09:21:36 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 9 Feb 2005 09:21:36 +0100 (MET) Subject: [py-svn] r9017 - in py/dist/py/xmlobj: . testing Message-ID: <20050209082136.AF46127BE9@code1.codespeak.net> Author: hpk Date: Wed Feb 9 09:21:36 2005 New Revision: 9017 Modified: py/dist/py/xmlobj/testing/test_xml.py py/dist/py/xmlobj/visit.py py/dist/py/xmlobj/xml.py Log: only provide "parent" attributes when serializing. Providing it at construction time is a lot more complex (and depending on the amount of tricks you try - almost impossible). Modified: py/dist/py/xmlobj/testing/test_xml.py ============================================================================== --- py/dist/py/xmlobj/testing/test_xml.py (original) +++ py/dist/py/xmlobj/testing/test_xml.py Wed Feb 9 09:21:36 2005 @@ -29,6 +29,7 @@ def test_tag_nested(): x = ns.hello(ns.world()) + unicode(x) # triggers parentifying assert x[0].parent is x u = unicode(x) assert u == '' Modified: py/dist/py/xmlobj/visit.py ============================================================================== --- py/dist/py/xmlobj/visit.py (original) +++ py/dist/py/xmlobj/visit.py Wed Feb 9 09:21:36 2005 @@ -12,6 +12,7 @@ self.visited = {} # for detection of recursion self.indent = indent self.curindent = curindent + self.parents = [] def visit(self, node): """ dispatcher on node's class/bases name. """ @@ -39,6 +40,10 @@ def Tag(self, tag): assert id(tag) not in self.visited + try: + tag.parent = self.parents[-1] + except IndexError: + tag.parent = None self.visited[id(tag)] = 1 tagname = getattr(tag, 'xmlname', tag.__class__.__name__) if self.curindent: @@ -46,7 +51,9 @@ if tag: self.curindent += self.indent self.write(u'<%s%s>' % (tagname, self.attributes(tag))) + self.parents.append(tag) map(self.visit, tag) + self.parents.pop() self.write(u'' % tagname) self.curindent -= self.indent else: Modified: py/dist/py/xmlobj/xml.py ============================================================================== --- py/dist/py/xmlobj/xml.py (original) +++ py/dist/py/xmlobj/xml.py Wed Feb 9 09:21:36 2005 @@ -10,11 +10,6 @@ def __init__(self, *args, **kwargs): super(Tag, self).__init__(args) self.attr = self.Attr(**kwargs) - for x in args: - try: - setattr(x, 'parent', self) - except AttributeError: - pass def __unicode__(self): return self.unicode(indent=0) From hpk at codespeak.net Wed Feb 9 09:23:41 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 9 Feb 2005 09:23:41 +0100 (MET) Subject: [py-svn] r9018 - in py/branch/py-collect: . c-extension c-extension/greenlet magic misc/testing xmlobj xmlobj/testing Message-ID: <20050209082341.14F1E27BE6@code1.codespeak.net> Author: hpk Date: Wed Feb 9 09:23:40 2005 New Revision: 9018 Added: py/branch/py-collect/c-extension/ - copied from r9017, py/dist/py/c-extension/ py/branch/py-collect/c-extension/conftest.py (props changed) - copied unchanged from r9017, py/dist/py/c-extension/conftest.py py/branch/py-collect/c-extension/greenlet/ (props changed) - copied from r9017, py/dist/py/c-extension/greenlet/ py/branch/py-collect/c-extension/greenlet/README.txt (props changed) - copied unchanged from r9017, py/dist/py/c-extension/greenlet/README.txt py/branch/py-collect/c-extension/greenlet/dummy_greenlet.py (props changed) - copied unchanged from r9017, py/dist/py/c-extension/greenlet/dummy_greenlet.py py/branch/py-collect/c-extension/greenlet/greenlet.c - copied unchanged from r9017, py/dist/py/c-extension/greenlet/greenlet.c py/branch/py-collect/c-extension/greenlet/greenlet.h - copied unchanged from r9017, py/dist/py/c-extension/greenlet/greenlet.h py/branch/py-collect/c-extension/greenlet/setup.py (props changed) - copied unchanged from r9017, py/dist/py/c-extension/greenlet/setup.py py/branch/py-collect/c-extension/greenlet/slp_platformselect.h - copied unchanged from r9017, py/dist/py/c-extension/greenlet/slp_platformselect.h py/branch/py-collect/c-extension/greenlet/switch_ppc_macosx.h - copied unchanged from r9017, py/dist/py/c-extension/greenlet/switch_ppc_macosx.h py/branch/py-collect/c-extension/greenlet/switch_ppc_unix.h - copied unchanged from r9017, py/dist/py/c-extension/greenlet/switch_ppc_unix.h py/branch/py-collect/c-extension/greenlet/switch_s390_unix.h - copied unchanged from r9017, py/dist/py/c-extension/greenlet/switch_s390_unix.h py/branch/py-collect/c-extension/greenlet/switch_sparc_sun_gcc.h - copied unchanged from r9017, py/dist/py/c-extension/greenlet/switch_sparc_sun_gcc.h py/branch/py-collect/c-extension/greenlet/switch_x86_msvc.h - copied unchanged from r9017, py/dist/py/c-extension/greenlet/switch_x86_msvc.h py/branch/py-collect/c-extension/greenlet/switch_x86_unix.h - copied unchanged from r9017, py/dist/py/c-extension/greenlet/switch_x86_unix.h py/branch/py-collect/c-extension/greenlet/test_generator.py (props changed) - copied unchanged from r9017, py/dist/py/c-extension/greenlet/test_generator.py py/branch/py-collect/c-extension/greenlet/test_greenlet.py (props changed) - copied unchanged from r9017, py/dist/py/c-extension/greenlet/test_greenlet.py py/branch/py-collect/c-extension/greenlet/test_remote.py - copied unchanged from r9017, py/dist/py/c-extension/greenlet/test_remote.py py/branch/py-collect/magic/greenlet.py - copied unchanged from r9017, py/dist/py/magic/greenlet.py Removed: py/branch/py-collect/magic/conftest.py Modified: py/branch/py-collect/__init__.py py/branch/py-collect/magic/ (props changed) py/branch/py-collect/misc/testing/test_initpkg.py py/branch/py-collect/xmlobj/testing/test_xml.py py/branch/py-collect/xmlobj/visit.py py/branch/py-collect/xmlobj/xml.py Log: remerged the latest trunk into the branch Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/branch/py-collect/__init__.py (original) +++ py/branch/py-collect/__init__.py Wed Feb 9 09:23:40 2005 @@ -52,11 +52,11 @@ 'path.extpy' : ('./path/extpy/extpy.py', 'Extpy'), 'path.checker' : ('./path/common.py', 'checker'), + 'magic.greenlet' : ('./magic/greenlet.py', 'greenlet'), 'magic.invoke' : ('./magic/invoke.py', 'invoke'), 'magic.revoke' : ('./magic/invoke.py', 'revoke'), 'magic.patch' : ('./magic/patch.py', 'patch'), 'magic.revert' : ('./magic/patch.py', 'revert'), - 'magic.greenlet' : ('./magic/greenlet/greenlet.c', 'greenlet'), 'magic.autopath' : ('./magic/autopath.py', 'autopath'), 'magic.View' : ('./magic/viewtype.py', 'View'), 'magic.AssertionError' : ('./magic/assertion.py', 'AssertionError'), Deleted: /py/branch/py-collect/magic/conftest.py ============================================================================== --- /py/branch/py-collect/magic/conftest.py Wed Feb 9 09:23:40 2005 +++ (empty file) @@ -1,7 +0,0 @@ -import py - -class Directory(py.test.collect.Directory): - def rec(self, path): - if path.basename != 'testing': - return False - return super(Directory, self).rec(path) Modified: py/branch/py-collect/misc/testing/test_initpkg.py ============================================================================== --- py/branch/py-collect/misc/testing/test_initpkg.py (original) +++ py/branch/py-collect/misc/testing/test_initpkg.py Wed Feb 9 09:23:40 2005 @@ -34,7 +34,7 @@ base.join('path', 'gateway',), base.join('documentation',), base.join('test', 'testing', 'import_test'), - base.join('magic', 'greenlet'), + base.join('c-extension',), base.join('bin'), base.join('execnet', 'bin'), ) Modified: py/branch/py-collect/xmlobj/testing/test_xml.py ============================================================================== --- py/branch/py-collect/xmlobj/testing/test_xml.py (original) +++ py/branch/py-collect/xmlobj/testing/test_xml.py Wed Feb 9 09:23:40 2005 @@ -29,6 +29,7 @@ def test_tag_nested(): x = ns.hello(ns.world()) + unicode(x) # triggers parentifying assert x[0].parent is x u = unicode(x) assert u == '' Modified: py/branch/py-collect/xmlobj/visit.py ============================================================================== --- py/branch/py-collect/xmlobj/visit.py (original) +++ py/branch/py-collect/xmlobj/visit.py Wed Feb 9 09:23:40 2005 @@ -12,6 +12,7 @@ self.visited = {} # for detection of recursion self.indent = indent self.curindent = curindent + self.parents = [] def visit(self, node): """ dispatcher on node's class/bases name. """ @@ -39,6 +40,10 @@ def Tag(self, tag): assert id(tag) not in self.visited + try: + tag.parent = self.parents[-1] + except IndexError: + tag.parent = None self.visited[id(tag)] = 1 tagname = getattr(tag, 'xmlname', tag.__class__.__name__) if self.curindent: @@ -46,7 +51,9 @@ if tag: self.curindent += self.indent self.write(u'<%s%s>' % (tagname, self.attributes(tag))) + self.parents.append(tag) map(self.visit, tag) + self.parents.pop() self.write(u'' % tagname) self.curindent -= self.indent else: Modified: py/branch/py-collect/xmlobj/xml.py ============================================================================== --- py/branch/py-collect/xmlobj/xml.py (original) +++ py/branch/py-collect/xmlobj/xml.py Wed Feb 9 09:23:40 2005 @@ -10,11 +10,6 @@ def __init__(self, *args, **kwargs): super(Tag, self).__init__(args) self.attr = self.Attr(**kwargs) - for x in args: - try: - setattr(x, 'parent', self) - except AttributeError: - pass def __unicode__(self): return self.unicode(indent=0) From hpk at codespeak.net Wed Feb 9 11:00:45 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 9 Feb 2005 11:00:45 +0100 (MET) Subject: [py-svn] r9021 - py/dist/py/misc/testing Message-ID: <20050209100045.7386F27B95@code1.codespeak.net> Author: hpk Date: Wed Feb 9 11:00:45 2005 New Revision: 9021 Modified: py/dist/py/misc/testing/test_initpkg.py Log: avoid default import of greenlets Modified: py/dist/py/misc/testing/test_initpkg.py ============================================================================== --- py/dist/py/misc/testing/test_initpkg.py (original) +++ py/dist/py/misc/testing/test_initpkg.py Wed Feb 9 11:00:45 2005 @@ -35,6 +35,7 @@ base.join('documentation',), base.join('test', 'testing', 'import_test'), base.join('c-extension',), + base.join('magic', 'greenlet.py'), base.join('bin'), base.join('execnet', 'bin'), ) @@ -42,7 +43,7 @@ relpath = p.new(ext='').relto(base) if base.sep in relpath: # not std/*.py itself for x in nodirs: - if p.relto(x): + if p.relto(x) or p == x: break else: relpath = relpath.replace(base.sep, '.') From hpk at codespeak.net Wed Feb 9 11:04:19 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 9 Feb 2005 11:04:19 +0100 (MET) Subject: [py-svn] r9022 - py/dist/py/path/svn Message-ID: <20050209100419.097DE27B95@code1.codespeak.net> Author: hpk Date: Wed Feb 9 11:04:18 2005 New Revision: 9022 Modified: py/dist/py/path/svn/wccommand.py Log: just fix test failure (and don't think too hard :-) Modified: py/dist/py/path/svn/wccommand.py ============================================================================== --- py/dist/py/path/svn/wccommand.py (original) +++ py/dist/py/path/svn/wccommand.py Wed Feb 9 11:04:18 2005 @@ -364,7 +364,7 @@ def versioned(self): try: s = self.svnwcpath.info() - except py.error.ENOENT: + except (py.error.ENOENT, py.error.EEXIST): return False except py.process.cmdexec.Error, e: if e.err.find('is not a working copy')!=-1: From hpk at codespeak.net Thu Feb 10 23:59:43 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 10 Feb 2005 23:59:43 +0100 (MET) Subject: [py-svn] r9094 - in py/dist/py/code: . testing Message-ID: <20050210225943.CAD9927B88@code1.codespeak.net> Author: hpk Date: Thu Feb 10 23:59:43 2005 New Revision: 9094 Modified: py/dist/py/code/source.py py/dist/py/code/testing/test_source.py Log: small improvements, more tests Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Thu Feb 10 23:59:43 2005 @@ -16,15 +16,26 @@ if isinstance(part, Source): partlines = part.lines elif isinstance(part, str): - if rstrip: - part = part.rstrip() partlines = part.split('\n') + if rstrip: + while partlines: + if partlines[-1].strip(): + break + partlines.pop() else: partlines = getsource(part, deindent=de).lines if de: partlines = deindent(partlines) lines.extend(partlines) + def __eq__(self, other): + try: + return self.lines == other.lines + except AttributeError: + if isinstance(other, str): + return str(self) == other + return False + def __getitem__(self, key): if isinstance(key, int): return self.lines[key] @@ -64,6 +75,14 @@ newsource.lines = before.lines + lines + after.lines return newsource + def indent(self, indent=' ' * 4): + """ return a copy of the source object with + 'before' and 'after' wrapped around it. + """ + newsource = Source() + newsource.lines = [(indent+line) for line in self.lines] + return newsource + def getstatement(self, lineno): """ return Source statement which contains the given linenumber (counted from 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 Thu Feb 10 23:59:43 2005 @@ -47,6 +47,19 @@ else: x = 23""" +def test_source_putaround(): + source = Source() + source = source.putaround(""" + if 1: + x=1 + """) + assert str(source).strip() == "if 1:\n x=1" + +def test_source_strips(): + source = Source("") + assert source == Source() + assert str(source) == '' + def test_syntaxerror_rerepresentation(): ex = py.test.raises(SyntaxError, py.code.compile, 'x x') assert ex.value.lineno == 1 From hpk at codespeak.net Fri Feb 11 17:27:08 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 11 Feb 2005 17:27:08 +0100 (MET) Subject: [py-svn] r9130 - py/dist/py/code Message-ID: <20050211162708.21F6927BB0@code1.codespeak.net> Author: hpk Date: Fri Feb 11 17:27:07 2005 New Revision: 9130 Modified: py/dist/py/code/source.py Log: slight fix to avoid empty lines Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Fri Feb 11 17:27:07 2005 @@ -13,6 +13,8 @@ de = kwargs.get('deindent', True) rstrip = kwargs.get('rstrip', True) for part in parts: + if not part: + partlines = [] if isinstance(part, Source): partlines = part.lines elif isinstance(part, str): From hpk at codespeak.net Fri Feb 11 17:42:36 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 11 Feb 2005 17:42:36 +0100 (MET) Subject: [py-svn] r9131 - in py/dist/py/code: . testing Message-ID: <20050211164236.9E33C27BB2@code1.codespeak.net> Author: hpk Date: Fri Feb 11 17:42:36 2005 New Revision: 9131 Modified: py/dist/py/code/source.py py/dist/py/code/testing/test_source.py Log: some more careful line stripping ... Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Fri Feb 11 17:42:36 2005 @@ -57,10 +57,10 @@ and leading blank lines removed. """ for start in range(0, len(self)): - if not self.lines[start].isspace(): + if self.lines[start].strip(): break for end in range(len(self)-1, -1, -1): - if not self.lines[end].isspace(): + if self.lines[end].strip(): break source = Source() source.lines[:] = self.lines[start:end+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 Fri Feb 11 17:42:36 2005 @@ -60,6 +60,12 @@ assert source == Source() assert str(source) == '' +def test_source_strip_multiline(): + source = Source() + source.lines = ["", " hello", " "] + source2 = source.strip() + assert source2.lines == [" hello"] + def test_syntaxerror_rerepresentation(): ex = py.test.raises(SyntaxError, py.code.compile, 'x x') assert ex.value.lineno == 1 From hpk at codespeak.net Sat Feb 12 23:26:47 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 12 Feb 2005 23:26:47 +0100 (MET) Subject: [py-svn] r9174 - py/dist/py/execnet Message-ID: <20050212222647.BEFDA27C02@code1.codespeak.net> Author: hpk Date: Sat Feb 12 23:26:47 2005 New Revision: 9174 Modified: py/dist/py/execnet/channel.py Log: get rid of 'yield' usage in sources that are sent over to different versions of python. Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Sat Feb 12 23:26:47 2005 @@ -93,11 +93,13 @@ return x def __iter__(self): - while 1: - try: - yield self.receive() - except EOFError: - raise StopIteration + return self + + def next(self): + try: + return self.receive() + except EOFError: + raise StopIteration # # helpers From hpk at codespeak.net Sun Feb 13 11:14:46 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 13 Feb 2005 11:14:46 +0100 (MET) Subject: [py-svn] r9180 - py/dist/py Message-ID: <20050213101446.573B827B53@code1.codespeak.net> Author: hpk Date: Sun Feb 13 11:14:46 2005 New Revision: 9180 Modified: py/dist/py/env.py Log: scrap leftover code (tooldir is long not there) Modified: py/dist/py/env.py ============================================================================== --- py/dist/py/env.py (original) +++ py/dist/py/env.py Sun Feb 13 11:14:46 2005 @@ -7,7 +7,6 @@ packagename = os.path.basename(packagedir) bindir = os.path.join(packagedir, 'bin') rootdir = os.path.dirname(packagedir) -tooldir = os.path.join(rootdir, 'tool') def prepend_unixpath(VAR, strpath): value = "%r:$%s" % (strpath, VAR) @@ -19,5 +18,4 @@ if sys.platform != 'win32': print prepend_unixpath('PATH', bindir) - print prepend_unixpath('PATH', tooldir) print prepend_unixpath('PYTHONPATH', rootdir) From hpk at codespeak.net Mon Feb 14 23:17:12 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 14 Feb 2005 23:17:12 +0100 (MET) Subject: [py-svn] r9223 - in py/dist/py/code: . testing Message-ID: <20050214221712.56D6A27B5A@code1.codespeak.net> Author: hpk Date: Mon Feb 14 23:17:12 2005 New Revision: 9223 Modified: py/dist/py/code/source.py py/dist/py/code/testing/test_source.py Log: fixed a bug (uninitialized locals) discovered by translating via PyPy! added a test. Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Mon Feb 14 23:17:12 2005 @@ -56,6 +56,7 @@ """ return new source object with trailing and leading blank lines removed. """ + start, end = 0,-1 for start in range(0, len(self)): if self.lines[start].strip(): break 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 Feb 14 23:17:12 2005 @@ -59,6 +59,7 @@ source = Source("") assert source == Source() assert str(source) == '' + assert source.strip() == source def test_source_strip_multiline(): source = Source() From arigo at codespeak.net Mon Feb 14 23:38:09 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Mon, 14 Feb 2005 23:38:09 +0100 (MET) Subject: [py-svn] r9224 - py/dist/py/code Message-ID: <20050214223809.3F33927B5A@code1.codespeak.net> Author: arigo Date: Mon Feb 14 23:38:09 2005 New Revision: 9224 Modified: py/dist/py/code/source.py Log: Minor rewrite. Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Mon Feb 14 23:38:09 2005 @@ -56,15 +56,13 @@ """ return new source object with trailing and leading blank lines removed. """ - start, end = 0,-1 - for start in range(0, len(self)): - if self.lines[start].strip(): - break - for end in range(len(self)-1, -1, -1): - if self.lines[end].strip(): - break + start, end = 0, len(self) + while start < end and not self.lines[start].strip(): + start += 1 + while end > start and not self.lines[end-1].strip(): + end -= 1 source = Source() - source.lines[:] = self.lines[start:end+1] + source.lines[:] = self.lines[start:end] return source def putaround(self, before='', after='', indent=' ' * 4): From hpk at codespeak.net Tue Feb 15 11:11:03 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 15 Feb 2005 11:11:03 +0100 (MET) Subject: [py-svn] r9229 - py/dist/py/code Message-ID: <20050215101103.62F8127B5E@code1.codespeak.net> Author: hpk Date: Tue Feb 15 11:11:03 2005 New Revision: 9229 Modified: py/dist/py/code/source.py Log: removed superflous code changed by pychecker Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Tue Feb 15 11:11:03 2005 @@ -145,9 +145,6 @@ 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. @@ -220,7 +217,6 @@ try: fullsource = obj.co_filename.__source__ except AttributeError: - import inspect strsrc = inspect.getsource(obj) assert isinstance(strsrc, str) return Source(strsrc, **kwargs) From hpk at codespeak.net Tue Feb 15 11:27:24 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 15 Feb 2005 11:27:24 +0100 (MET) Subject: [py-svn] r9230 - in py/dist/py/path: local svn Message-ID: <20050215102724.622E627B5E@code1.codespeak.net> Author: hpk Date: Tue Feb 15 11:27:24 2005 New Revision: 9230 Modified: py/dist/py/path/local/local.py py/dist/py/path/svn/svncommon.py py/dist/py/path/svn/wccommand.py Log: some more slight cleanups found through pychecker Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Tue Feb 15 11:27:24 2005 @@ -380,7 +380,7 @@ stdout, stderr,) return stdout - def sysfind(self, name, checker=None): + def sysfind(cls, name, checker=None): """ 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. If a binary @@ -425,7 +425,7 @@ dpath = cls(tempfile.mktemp()) try: dpath.mkdir() - except path.FileExists: + except py.path.FileExists: continue return dpath raise py.error.ENOENT(dpath, "could not create tempdir, %d tries" % tries) Modified: py/dist/py/path/svn/svncommon.py ============================================================================== --- py/dist/py/path/svn/svncommon.py (original) +++ py/dist/py/path/svn/svncommon.py Tue Feb 15 11:27:24 2005 @@ -3,7 +3,6 @@ """ import os, sys, time, re import py -from py import path, process from py.__impl__.path import common #_______________________________________________________________ Modified: py/dist/py/path/svn/wccommand.py ============================================================================== --- py/dist/py/path/svn/wccommand.py (original) +++ py/dist/py/path/svn/wccommand.py Tue Feb 15 11:27:24 2005 @@ -131,6 +131,7 @@ ignored and considered always true (because of underlying svn semantics. """ + assert rec, "svn cannot remove non-recursively" flags = [] if force: flags.append('--force') From hpk at codespeak.net Tue Feb 15 11:32:28 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 15 Feb 2005 11:32:28 +0100 (MET) Subject: [py-svn] r9231 - py/dist/py/path/local Message-ID: <20050215103228.9117427B5E@code1.codespeak.net> Author: hpk Date: Tue Feb 15 11:32:28 2005 New Revision: 9231 Modified: py/dist/py/path/local/local.py Log: holger: when you fix it, why not fix it correctly? Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Tue Feb 15 11:32:28 2005 @@ -425,7 +425,7 @@ dpath = cls(tempfile.mktemp()) try: dpath.mkdir() - except py.path.FileExists: + except (py.error.EEXIST, py.error.EPERM, py.error.EACCES): continue return dpath raise py.error.ENOENT(dpath, "could not create tempdir, %d tries" % tries) From hpk at codespeak.net Thu Feb 17 08:17:58 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 17 Feb 2005 08:17:58 +0100 (MET) Subject: [py-svn] r9261 - py/dist/py/test Message-ID: <20050217071758.AA65A27B58@code1.codespeak.net> Author: hpk Date: Thu Feb 17 08:17:58 2005 New Revision: 9261 Modified: py/dist/py/test/collect.py Log: test methods and functions that begin with 'test' are now automatically collected. Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Thu Feb 17 08:17:58 2005 @@ -149,7 +149,7 @@ yield self._except() def collect_function(self, extpy): - if extpy.check(func=1, basestarts='test_'): + if extpy.check(func=1, basestarts='test'): if extpy.check(genfunc=1): yield Generator(extpy) else: @@ -168,7 +168,7 @@ # note that we want to allow inheritance and thus # we don't check for "samemodule"-ness of test # methods like in the Module Collector - if extpy.check(basestarts='test_', func=1): + if extpy.check(basestarts='test', func=1): if extpy.check(genfunc=1): yield Generator(extpy) else: From hpk at codespeak.net Thu Feb 17 08:19:11 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 17 Feb 2005 08:19:11 +0100 (MET) Subject: [py-svn] r9262 - py/branch/py-collect/test2 Message-ID: <20050217071911.DA85827B58@code1.codespeak.net> Author: hpk Date: Thu Feb 17 08:19:11 2005 New Revision: 9262 Modified: py/branch/py-collect/test2/collect.py Log: let 'test' methods and functions be found (no underscore needed anymore) Modified: py/branch/py-collect/test2/collect.py ============================================================================== --- py/branch/py-collect/test2/collect.py (original) +++ py/branch/py-collect/test2/collect.py Thu Feb 17 08:19:11 2005 @@ -182,7 +182,7 @@ class PyCollectorMixin(object): def funcnamefilter(self, name): - return name.startswith('test_') + return name.startswith('test') def classnamefilter(self, name): return name.startswith('Test') From tismer at codespeak.net Thu Feb 17 18:34:40 2005 From: tismer at codespeak.net (tismer at codespeak.net) Date: Thu, 17 Feb 2005 18:34:40 +0100 (MET) Subject: [py-svn] r9281 - py/dist/py/process Message-ID: <20050217173440.E7B8027B88@code1.codespeak.net> Author: tismer Date: Thu Feb 17 18:34:40 2005 New Revision: 9281 Modified: py/dist/py/process/cmdexec.py Log: small hack that makes exec work under windows. windows hassome very special rules about quotes... Modified: py/dist/py/process/cmdexec.py ============================================================================== --- py/dist/py/process/cmdexec.py (original) +++ py/dist/py/process/cmdexec.py Thu Feb 17 18:34:40 2005 @@ -79,7 +79,14 @@ Note that this method can currently deadlock because we don't have WaitForMultipleObjects in the std-python api. + + Further note that the rules for quoting are very special + under Windows. Do a HELP CMD in a shell, and tell me if + you understand this. For now, I try to do a fix. """ + print "*****", cmd + if '"' in cmd and not cmd.startswith('""'): + cmd = '"%s"' % cmd stdin, stdout, stderr = os.popen3(cmd) out = stdout.read() err = stderr.read() From hpk at codespeak.net Sat Feb 19 13:38:43 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 19 Feb 2005 13:38:43 +0100 (MET) Subject: [py-svn] r9328 - py/dist/py/code Message-ID: <20050219123843.121D627B84@code1.codespeak.net> Author: hpk Date: Sat Feb 19 13:38:42 2005 New Revision: 9328 Modified: py/dist/py/code/source.py Log: accept one more argument to be compatible with CPython Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Sat Feb 19 13:38:42 2005 @@ -1,6 +1,7 @@ from __future__ import generators import sys import inspect +cpy_compile = compile # DON'T IMPORT PY HERE @@ -165,7 +166,7 @@ return "\n".join(self.lines) def compile(self, filename=None, mode='exec', - flag=generators.compiler_flag): + flag=generators.compiler_flag, dont_inherit=0): """ return compiled code object. if filename is None invent an artificial filename which displays the source/line position of the caller frame. @@ -176,7 +177,7 @@ frame.f_lineno) source = str(self)+'\n' try: - co = compile(source, filename, mode, flag) + co = cpy_compile(source, filename, mode, flag) except SyntaxError, ex: # re-represent syntax errors from parsing python strings newex = SyntaxError('\n'.join([ @@ -197,7 +198,7 @@ # def compile_(source, filename=None, mode='exec', flags= - generators.compiler_flag): + generators.compiler_flag, dont_inherit=0): """ compile the given source to a raw code object, which points back to the source code through "co_filename.__source__". All code objects From hpk at codespeak.net Sat Feb 19 13:55:07 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 19 Feb 2005 13:55:07 +0100 (MET) Subject: [py-svn] r9329 - in py/dist/py/magic: . testing Message-ID: <20050219125507.30EA427B84@code1.codespeak.net> Author: hpk Date: Sat Feb 19 13:55:06 2005 New Revision: 9329 Modified: py/dist/py/magic/invoke.py py/dist/py/magic/testing/test_invoke.py Log: ease override of builtin compile with py.code's compile builtin (via py.magic.invoke/revoke(compile=True)) if you use this, then py.test will show tracebacks for all your generated code (as long as you are executing compile()ed code objects and not source code directly). Modified: py/dist/py/magic/invoke.py ============================================================================== --- py/dist/py/magic/invoke.py (original) +++ py/dist/py/magic/invoke.py Sat Feb 19 13:55:06 2005 @@ -1,5 +1,7 @@ +import py +import __builtin__ as cpy_builtin -def invoke(assertion=False): +def invoke(assertion=False, compile=False): """ invoke magic, currently you can specify: assertion patches the builtin AssertionError to try to give @@ -10,10 +12,13 @@ if assertion: from py.__impl__.magic import assertion assertion.invoke() + if compile: + py.magic.patch(cpy_builtin, 'compile', py.code.compile ) -def revoke(assertion=False): +def revoke(assertion=False, compile=False): """ revoke previously invoked magic (see invoke()).""" if assertion: from py.__impl__.magic import assertion assertion.revoke() - + if compile: + py.magic.revert(cpy_builtin, 'compile') Modified: py/dist/py/magic/testing/test_invoke.py ============================================================================== --- py/dist/py/magic/testing/test_invoke.py (original) +++ py/dist/py/magic/testing/test_invoke.py Sat Feb 19 13:55:06 2005 @@ -13,3 +13,17 @@ finally: py.magic.revoke(assertion=True) +def test_invoke_compile(): + py.magic.invoke(compile=True) + try: + co = compile("""if 1: + def f(): + return 1 + \n""", '', 'exec') + d = {} + exec co in d + assert py.code.Source(d['f']) + finally: + py.magic.revoke(compile=True) + + From hpk at codespeak.net Sat Feb 19 15:41:45 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 19 Feb 2005 15:41:45 +0100 (MET) Subject: [py-svn] r9339 - py/dist/py/path/local Message-ID: <20050219144145.0749327B84@code1.codespeak.net> Author: hpk Date: Sat Feb 19 15:41:44 2005 New Revision: 9339 Modified: py/dist/py/path/local/local.py Log: allow empty strings as well (and clean up the init-checks) Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sat Feb 19 15:41:44 2005 @@ -59,14 +59,14 @@ path = path.strpath # initialize the path self = object.__new__(cls) - if path is None: + if not path: self.strpath = os.getcwd() - elif not path: + elif isinstance(path, str): + self.strpath = os.path.abspath(os.path.normpath(str(path))) + else: raise ValueError( "can only pass None, Path instances " "or non-empty strings to LocalPath") - else: - self.strpath = os.path.abspath(os.path.normpath(str(path))) assert isinstance(self.strpath, str) return self From hpk at codespeak.net Sat Feb 19 16:23:08 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 19 Feb 2005 16:23:08 +0100 (MET) Subject: [py-svn] r9341 - py/dist/py/code Message-ID: <20050219152308.D3C6C27B5D@code1.codespeak.net> Author: hpk Date: Sat Feb 19 16:23:08 2005 New Revision: 9341 Modified: py/dist/py/code/source.py Log: if the filename arg doesn't exist or is not True then append the generated-at information always. Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Sat Feb 19 16:23:08 2005 @@ -1,6 +1,7 @@ from __future__ import generators import sys import inspect +import py cpy_compile = compile # DON'T IMPORT PY HERE @@ -171,10 +172,10 @@ invent an artificial filename which displays the source/line position of the caller frame. """ - if filename is None: + if not filename or py.path.local(filename).check(file=0): frame = sys._getframe(1) # the caller - filename = '<%s:%d>' % (frame.f_code.co_filename, - frame.f_lineno) + filename = '%s<%s:%d>' % (filename, frame.f_code.co_filename, + frame.f_lineno) source = str(self)+'\n' try: co = cpy_compile(source, filename, mode, flag) From hpk at codespeak.net Sat Feb 19 16:44:27 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 19 Feb 2005 16:44:27 +0100 (MET) Subject: [py-svn] r9343 - in py/dist/py: path/svn test/report/text Message-ID: <20050219154427.B1B3927B5D@code1.codespeak.net> Author: hpk Date: Sat Feb 19 16:44:27 2005 New Revision: 9343 Modified: py/dist/py/path/svn/wccommand.py py/dist/py/test/report/text/summary.py Log: fixed svnwc constructor now that the local constructor is less forgiving and be a bit more tolerant when trying to show sourcecode in tracebacks ... Modified: py/dist/py/path/svn/wccommand.py ============================================================================== --- py/dist/py/path/svn/wccommand.py (original) +++ py/dist/py/path/svn/wccommand.py Sat Feb 19 16:44:27 2005 @@ -9,7 +9,6 @@ """ import os, sys, time, re -from py import path import py from py.__impl__.path import common from py.__impl__.path.svn import cache @@ -22,7 +21,11 @@ def __new__(cls, wcpath=None): self = object.__new__(cls) - self.localpath = path.local(wcpath) + if isinstance(wcpath, cls): + if wcpath.__class__ == cls: + return wcpath + wcpath = wcpath.localpath + self.localpath = py.path.local(wcpath) return self strpath = property(lambda x: str(x.localpath), None, None, "string path") @@ -358,7 +361,7 @@ def get(self, spec): return self.localpath.get(spec) - class Checkers(path.local.Checkers): + class Checkers(py.path.local.Checkers): def __init__(self, path): self.svnwcpath = path self.path = path.localpath 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 Feb 19 16:44:27 2005 @@ -114,7 +114,7 @@ def repr_source(self, entry, marker=">"): try: source = entry.getsource() - except py.error.ENOENT: + except (py.error.ENOENT, py.error.EISDIR): self.out.line("[failure to get at sourcelines from %r]\n" % entry) else: source = source.deindent() From hpk at codespeak.net Sat Feb 19 17:11:16 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 19 Feb 2005 17:11:16 +0100 (MET) Subject: [py-svn] r9347 - in py/dist/py/code: . testing Message-ID: <20050219161116.DD3B527B60@code1.codespeak.net> Author: hpk Date: Sat Feb 19 17:11:16 2005 New Revision: 9347 Modified: py/dist/py/code/source.py py/dist/py/code/testing/test_source.py Log: allow unicode to be passed into Source Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Sat Feb 19 17:11:16 2005 @@ -19,7 +19,7 @@ partlines = [] if isinstance(part, Source): partlines = part.lines - elif isinstance(part, str): + elif isinstance(part, (unicode, str)): partlines = part.split('\n') if rstrip: while partlines: 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 Sat Feb 19 17:11:16 2005 @@ -19,6 +19,11 @@ """, rstrip=True) assert str(x) == "\n3" +def test_unicode(): + x = Source(unicode("4")) + assert str(x) == "4" + + def test_source_from_function(): source = py.code.Source(test_source_str_function) assert str(source).startswith('def test_source_str_function():') From hpk at codespeak.net Sat Feb 19 17:18:30 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 19 Feb 2005 17:18:30 +0100 (MET) Subject: [py-svn] r9348 - in py/dist/py/code: . testing Message-ID: <20050219161830.BE64C27B60@code1.codespeak.net> Author: hpk Date: Sat Feb 19 17:18:30 2005 New Revision: 9348 Modified: py/dist/py/code/source.py py/dist/py/code/testing/test_source.py Log: unicode fixes for compiling Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Sat Feb 19 17:18:30 2005 @@ -176,7 +176,7 @@ frame = sys._getframe(1) # the caller filename = '%s<%s:%d>' % (filename, frame.f_code.co_filename, frame.f_lineno) - source = str(self)+'\n' + source = "\n".join(self.lines) + '\n' try: co = cpy_compile(source, filename, mode, flag) except SyntaxError, ex: 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 Sat Feb 19 17:18:30 2005 @@ -157,6 +157,11 @@ exec co assert x == 3 + def test_compile_unicode(self): + co = py.code.compile(unicode('u"\xc3\xa5"', 'utf8'), mode='eval') + val = eval(co) + assert isinstance(val, unicode) + def test_compile_and_getsource_simple(self): co = py.code.compile("x=3") exec co From pedronis at codespeak.net Mon Feb 21 15:31:15 2005 From: pedronis at codespeak.net (pedronis at codespeak.net) Date: Mon, 21 Feb 2005 15:31:15 +0100 (MET) Subject: [py-svn] r9382 - in py/dist/py/code: . testing Message-ID: <20050221143115.367BC27B5E@code1.codespeak.net> Author: pedronis Date: Mon Feb 21 15:31:15 2005 New Revision: 9382 Modified: py/dist/py/code/source.py py/dist/py/code/testing/test_source.py Log: some SyntaxErrors have offset = None Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Mon Feb 21 15:31:15 2005 @@ -181,10 +181,11 @@ co = cpy_compile(source, filename, mode, flag) except SyntaxError, ex: # re-represent syntax errors from parsing python strings - newex = SyntaxError('\n'.join([ - "".join(self.lines[:ex.lineno]), - " " * ex.offset + '^', - "syntax error probably generated here: %s" % filename])) + msglines = self.lines[:ex.lineno] + if ex.offset: + msglines.append(" "*ex.offset + '^') + msglines.append("syntax error probably generated here: %s" % filename) + newex = SyntaxError('\n'.join(msglines)) 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 Mon Feb 21 15:31:15 2005 @@ -209,6 +209,9 @@ #print "block", str(block) assert str(stmt).strip().startswith('assert') + def test_offsetless_synerr(self): + py.test.raises(SyntaxError, py.code.compile, "lambda a,a: 0", mode='eval') + def test_getstartingblock_singleline(): class A: def __init__(self, *args): From hpk at codespeak.net Thu Feb 24 12:53:10 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 24 Feb 2005 12:53:10 +0100 (MET) Subject: [py-svn] r9480 - py/dist/py/code Message-ID: <20050224115310.081E027B4C@code1.codespeak.net> Author: hpk Date: Thu Feb 24 12:53:09 2005 New Revision: 9480 Modified: py/dist/py/code/source.py Log: fix docstring, shift things around Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Thu Feb 24 12:53:09 2005 @@ -80,7 +80,7 @@ def indent(self, indent=' ' * 4): """ return a copy of the source object with - 'before' and 'after' wrapped around it. + all lines indented by the given indent-string. """ newsource = Source() newsource.lines = [(indent+line) for line in self.lines] @@ -212,6 +212,13 @@ co = s.compile(filename, mode, flags) return co + +# +# various helper functions +# +class MyStr(str): + """ custom string which allows to add attributes. """ + def getsource(obj, **kwargs): if hasattr(obj, 'func_code'): obj = obj.func_code @@ -228,12 +235,6 @@ end = fullsource.getblockend(lineno) return fullsource[lineno:end+1] -# -# various helper functions -# -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?