From jan at codespeak.net Thu Nov 10 02:06:39 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Thu, 10 Nov 2005 02:06:39 +0100 (CET) Subject: [py-svn] r19696 - in py/dist/py: code code/testing execnet execnet/testing test/terminal test/tkinter thread thread/testing Message-ID: <20051110010639.6FB5627B3F@code1.codespeak.net> Author: jan Date: Thu Nov 10 02:06:34 2005 New Revision: 19696 Modified: py/dist/py/code/testing/test_excinfo.py py/dist/py/code/traceback2.py py/dist/py/execnet/channel.py py/dist/py/execnet/gateway.py py/dist/py/execnet/testing/test_gateway.py py/dist/py/test/terminal/remote.py py/dist/py/test/terminal/terminal.py py/dist/py/test/tkinter/backend.py py/dist/py/thread/pool.py py/dist/py/thread/testing/test_pool.py Log: Merged monday branch (-r17654:17687) into dist. Important changes: - removed channel argument from gateway.remote_exec - removed receiver arg from newchannel() - introduced new channel.setcallback(callback) that sends all received items to the callback Modified: py/dist/py/code/testing/test_excinfo.py ============================================================================== --- py/dist/py/code/testing/test_excinfo.py (original) +++ py/dist/py/code/testing/test_excinfo.py Thu Nov 10 02:06:34 2005 @@ -30,11 +30,18 @@ # testchain for getentries test below def f(): + # raise ValueError + # def g(): + # + __tracebackhide__ = True f() + # def h(): + # g() + # class TestTraceback_f_g_h: def setup_method(self, method): @@ -80,6 +87,66 @@ assert s.startswith("def xyz():\n try:") assert s.endswith("except somenoname:") + def test_traceback_cut(self): + co = py.code.Code(f) + path, firstlineno = co.path, co.firstlineno + traceback = self.excinfo.traceback + newtraceback = traceback.cut(path=path, firstlineno=firstlineno) + assert len(newtraceback) == 1 + newtraceback = traceback.cut(path=path, lineno=firstlineno+2) + assert len(newtraceback) == 1 + + def test_traceback_filter(self): + traceback = self.excinfo.traceback + ntraceback = traceback.filter() + assert len(ntraceback) == len(traceback) - 1 + + def test_traceback_recursion_index(self): + def f(n): + if n < 10: + n += 1 + f(n) + excinfo = py.test.raises(RuntimeError, f, 8) + traceback = excinfo.traceback + recindex = traceback.recursionindex() + assert recindex == 3 + + def test_traceback_getcrashentry(self): + def i(): + __tracebackhide__ = True + raise ValueError + def h(): + i() + def g(): + __tracebackhide__ = True + h() + def f(): + g() + + excinfo = py.test.raises(ValueError, f) + tb = excinfo.traceback + entry = tb.getcrashentry() + co = py.code.Code(h) + assert entry.frame.code.path == co.path + assert entry.lineno == co.firstlineno + 1 + assert entry.frame.code.name == 'h' + + def test_traceback_getcrashentry_empty(self): + def g(): + __tracebackhide__ = True + raise ValueError + def f(): + __tracebackhide__ = True + g() + + excinfo = py.test.raises(ValueError, f) + tb = excinfo.traceback + entry = tb.getcrashentry() + co = py.code.Code(g) + assert entry.frame.code.path == co.path + assert entry.lineno == co.firstlineno + 2 + assert entry.frame.code.name == 'g' + #def test_traceback_display_func(self): # tb = self.excinfo.traceback # for x in tb: Modified: py/dist/py/code/traceback2.py ============================================================================== --- py/dist/py/code/traceback2.py (original) +++ py/dist/py/code/traceback2.py Thu Nov 10 02:06:34 2005 @@ -52,6 +52,14 @@ break return source[start:end] + def ishidden(self): + try: + return self.frame.eval("__tracebackhide__") + except (SystemExit, KeyboardInterrupt): + raise + except: + return False + def __str__(self): try: fn = str(self.path) @@ -65,12 +73,58 @@ Entry = TracebackEntry def __init__(self, tb): - def f(cur): - while cur is not None: - yield self.Entry(cur) - cur = cur.tb_next - list.__init__(self, f(tb)) + if hasattr(tb, 'tb_next'): + def f(cur): + while cur is not None: + yield self.Entry(cur) + cur = cur.tb_next + list.__init__(self, f(tb)) + else: + list.__init__(self, tb) + + def cut(self, path=None, lineno=None, firstlineno=None): + for x in self: + if ((path is None or x.frame.code.path == path) and + (lineno is None or x.lineno == lineno) and + (firstlineno is None or x.frame.code.firstlineno == firstlineno)): + return Traceback(x._rawentry) + return self + + def __getitem__(self, key): + val = super(Traceback, self).__getitem__(key) + if isinstance(key, slice): + val = self.__class__(val) + return val + def filter(self, fn=lambda x: not x.ishidden()): + return Traceback(filter(fn, self)) + + def getcrashentry(self): + """ return last non-hidden traceback entry that lead + to the exception of a traceback. + """ + tb = self.filter() + if not tb: + tb = self + return tb[-1] + + def recursionindex(self): + cache = {} + for i, entry in py.builtin.enumerate(self): + key = entry.frame.code.path, entry.frame.lineno + #print "checking for recursion at", key + l = cache.setdefault(key, []) + if l: + f = entry.frame + loc = f.f_locals + for otherloc in l: + if f.is_true(f.eval(co_equal, + __recursioncache_locals_1=loc, + __recursioncache_locals_2=otherloc)): + return i + l.append(entry.frame.f_locals) + return None + # def __str__(self): # for x in self # l = [] @@ -78,3 +132,6 @@ # l.append(entry.display()) # return "".join(l) + +co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2', + '?', 'eval') Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Thu Nov 10 02:06:34 2005 @@ -24,18 +24,44 @@ """Communication channel between two possibly remote threads of code. """ RemoteError = RemoteError - def __init__(self, gateway, id, has_callback=False): + def __init__(self, gateway, id): assert isinstance(id, int) self.gateway = gateway self.id = id - if has_callback: - self._items = None - else: - self._items = Queue.Queue() + self._items = Queue.Queue() self._closed = False self._receiveclosed = threading.Event() self._remoteerrors = [] + def setcallback(self, callback): + queue = self._items + lock = self.gateway.channelfactory._receivelock + lock.acquire() + try: + _callbacks = self.gateway.channelfactory._callbacks + if _callbacks.setdefault(self.id, callback) is not callback: + raise IOError("%r has callback already registered" %(self,)) + self._items = None + while 1: + try: + olditem = queue.get(block=False) + except Queue.Empty: + break + else: + if olditem is ENDMARKER: + queue.put(olditem) + break + else: + callback(olditem) + if self._closed or self._receiveclosed.isSet(): + # no need to keep a callback + try: + del _callbacks[self.id] + except KeyError: + pass + finally: + lock.release() + def __repr__(self): flag = self.isclosed() and "closed" or "open" return "" % (self.id, flag) @@ -100,8 +126,9 @@ self._remoteerrors.append(error) self._closed = True # --> "closed" self._receiveclosed.set() - if self._items is not None: - self._items.put(ENDMARKER) + queue = self._items + if queue is not None: + queue.put(ENDMARKER) self.gateway.channelfactory._no_longer_opened(self.id) def waitclose(self, timeout=None): @@ -139,11 +166,12 @@ reraised as channel.RemoteError exceptions containing a textual representation of the remote traceback. """ - if self._items is None: + queue = self._items + if queue is None: raise IOError("calling receive() on channel with receiver callback") - x = self._items.get() + x = queue.get() if x is ENDMARKER: - self._items.put(x) # for other receivers + queue.put(x) # for other receivers raise self._getremoteerror() or EOFError() else: return x @@ -170,20 +198,18 @@ self._channels = weakref.WeakValueDictionary() self._callbacks = {} self._writelock = threading.Lock() + self._receivelock = threading.RLock() self.gateway = gateway self.count = startcount - def new(self, id=None, receiver=None): + def new(self, id=None): """ create a new Channel with 'id' (or create new id if None). """ self._writelock.acquire() try: if id is None: id = self.count self.count += 2 - has_callback = receiver is not None - if has_callback: - self._callbacks[id] = receiver - channel = Channel(self.gateway, id, has_callback) + channel = Channel(self.gateway, id) self._channels[id] = channel return channel finally: @@ -217,8 +243,9 @@ channel._remoteerrors.append(remoteerror) channel._closed = True # --> "closed" channel._receiveclosed.set() - if channel._items is not None: - channel._items.put(ENDMARKER) + queue = channel._items + if queue is not None: + queue.put(ENDMARKER) self._no_longer_opened(id) def _local_last_message(self, id): @@ -229,21 +256,27 @@ else: # state transition: if "opened", change to "sendonly" channel._receiveclosed.set() - if channel._items is not None: - channel._items.put(ENDMARKER) + queue = channel._items + if queue is not None: + queue.put(ENDMARKER) self._no_longer_opened(id) def _local_receive(self, id, data): # executes in receiver thread - callback = self._callbacks.get(id) - if callback is not None: - callback(data) # even if channel may be already closed - else: - channel = self._channels.get(id) - if channel is None or channel._items is None: - pass # drop data + self._receivelock.acquire() + try: + callback = self._callbacks.get(id) + if callback is not None: + callback(data) # even if channel may be already closed else: - channel._items.put(data) + channel = self._channels.get(id) + queue = channel and channel._items + if queue is None: + pass # drop data + else: + queue.put(data) + finally: + self._receivelock.release() def _finished_receiving(self): for id in self._channels.keys(): Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Thu Nov 10 02:06:34 2005 @@ -15,7 +15,7 @@ # to the other side. Yes, it is fragile but we have a # few tests that try to catch when we mess up. -# XXX the following line should not be here +# XXX the following lines should not be here if 'ThreadOut' not in globals(): import py from py.code import Source @@ -50,11 +50,13 @@ sender = self.thread_sender) def __repr__(self): - R = len(self.pool.getstarted('receiver')) and "receiving" or "not receiving" - S = len(self.pool.getstarted('sender')) and "sending" or "not sending" + r = (len(self.pool.getstarted('receiver')) + and "receiving" or "not receiving") + s = (len(self.pool.getstarted('sender')) + and "sending" or "not sending") i = len(self.channelfactory.channels()) - return "<%s %s/%s (%d active channels)>" %(self.__class__.__name__, - R, S, i) + return "<%s %s/%s (%d active channels)>" %( + self.__class__.__name__, r, s, i) ## def _local_trystopexec(self): ## self._execpool.shutdown() @@ -71,6 +73,7 @@ raise except: traceback.print_exc() + def traceex(self, excinfo): try: l = traceback.format_exception(*excinfo) @@ -137,7 +140,7 @@ channel.close() return close - def thread_executor(self, channel, (source, outid, errid, autoclose)): + def thread_executor(self, channel, (source, outid, errid)): """ worker thread to execute source objects from the execution queue. """ from sys import exc_info try: @@ -159,15 +162,7 @@ channel.close(errortext) self.trace(errortext) else: - if autoclose: - channel.close() - else: - # the channel should usually be closed by Channel.__del__. - # Give it a better chance now. - try: - del loc['channel'] - except KeyError: - pass + channel.close() def _local_schedulexec(self, channel, sourcetask): self.trace("dispatching exec") @@ -179,7 +174,8 @@ if hasattr(callback, 'write'): callback = callback.write assert callable(callback) - chan = self.newchannel(receiver=callback) + chan = self.newchannel() + chan.setcallback(callback) return chan.id # _____________________________________________________________________ @@ -187,18 +183,14 @@ # High Level Interface # _____________________________________________________________________ # - def newchannel(self, receiver=None): - """ return new channel object. If a 'receiver' callback is provided - it will be invoked on each received item. You cannot call - receive() anymore on such a channel. - """ - return self.channelfactory.new(receiver=receiver) + def newchannel(self): + """ return new channel object. """ + return self.channelfactory.new() - def remote_exec(self, source, stdout=None, stderr=None, channel=None): + def remote_exec(self, source, stdout=None, stderr=None): """ return channel object for communicating with the asynchronously executing 'source' code which will have a corresponding 'channel' - object in its executing namespace. If a channel object is not - provided a new channel will be created. + object in its executing namespace. """ try: source = str(Source(source)) @@ -208,26 +200,23 @@ source = str(py.code.Source(source)) except ImportError: pass - if channel is None: - channel = self.newchannel() - autoclose = True - else: - autoclose = False + channel = self.newchannel() outid = self._newredirectchannelid(stdout) errid = self._newredirectchannelid(stderr) self._outgoing.put(Message.CHANNEL_OPEN(channel.id, - (source, outid, errid, autoclose))) + (source, outid, errid))) return channel def remote_redirect(self, stdout=None, stderr=None): - """ return a handle representing a redirection of of remote + """ return a handle representing a redirection of a remote end's stdout to a local file object. with handle.close() the redirection will be reverted. """ clist = [] for name, out in ('stdout', stdout), ('stderr', stderr): if out: - outchannel = self.newchannel(receiver=getattr(out, 'write', out)) + outchannel = self.newchannel() + outchannel.setcallback(getattr(out, 'write', out)) channel = self.remote_exec(""" import sys outchannel = channel.receive() Modified: py/dist/py/execnet/testing/test_gateway.py ============================================================================== --- py/dist/py/execnet/testing/test_gateway.py (original) +++ py/dist/py/execnet/testing/test_gateway.py Thu Nov 10 02:06:34 2005 @@ -69,6 +69,9 @@ for x in 'sender', 'receiver': assert self.gw.pool.getstarted(x) + def test_repr_doesnt_crash(self): + assert isinstance(repr(self), str) + def test_correct_setup_no_py(self): channel = self.gw.remote_exec(""" import sys @@ -162,41 +165,76 @@ def test_channel_receiver_callback(self): l = [] - channel = self.gw.newchannel(receiver=l.append) - self.gw.remote_exec(channel=channel, source=''' + #channel = self.gw.newchannel(receiver=l.append) + channel = self.gw.remote_exec(source=''' channel.send(42) channel.send(13) channel.send(channel.gateway.newchannel()) ''') + channel.setcallback(callback=l.append) + py.test.raises(IOError, channel.receive) channel.waitclose(1.0) assert len(l) == 3 assert l[:2] == [42,13] assert isinstance(l[2], channel.__class__) + def test_channel_callback_after_receive(self): + l = [] + channel = self.gw.remote_exec(source=''' + channel.send(42) + channel.send(13) + channel.send(channel.gateway.newchannel()) + ''') + x = channel.receive() + assert x == 42 + channel.setcallback(callback=l.append) + py.test.raises(IOError, channel.receive) + channel.waitclose(1.0) + assert len(l) == 2 + assert l[0] == 13 + assert isinstance(l[1], channel.__class__) + + def test_waiting_for_callbacks(self): + l = [] + def callback(msg): + import time; time.sleep(0.2) + l.append(msg) + channel = self.gw.remote_exec(source=''' + channel.send(42) + ''') + channel.setcallback(callback) + channel.waitclose(1.0) + assert l == [42] + def test_channel_callback_stays_active(self, earlyfree=True): # with 'earlyfree==True', this tests the "sendonly" channel state. l = [] - channel = self.gw.newchannel(receiver=l.append) - self.gw.remote_exec(channel=channel, source=''' + channel = self.gw.remote_exec(source=''' import thread, time - def producer(channel): + def producer(subchannel): for i in range(5): time.sleep(0.15) - channel.send(i*100) - thread.start_new_thread(producer, (channel,)) + subchannel.send(i*100) + channel2 = channel.receive() + thread.start_new_thread(producer, (channel2,)) + del channel2 ''') + subchannel = self.gw.newchannel() + subchannel.setcallback(l.append) + channel.send(subchannel) if earlyfree: - channel = None + subchannel = None counter = 100 while len(l) < 5: - if channel and channel.isclosed(): + if subchannel and subchannel.isclosed(): break counter -= 1 + print counter if not counter: - py.test.fail("timed out waiting for the answer[%d]" % i) + py.test.fail("timed out waiting for the answer[%d]" % len(l)) time.sleep(0.04) # busy-wait assert l == [0, 100, 200, 300, 400] - return channel + return subchannel def test_channel_callback_remote_freed(self): channel = self.test_channel_callback_stays_active(False) Modified: py/dist/py/test/terminal/remote.py ============================================================================== --- py/dist/py/test/terminal/remote.py (original) +++ py/dist/py/test/terminal/remote.py Thu Nov 10 02:06:34 2005 @@ -21,8 +21,8 @@ print "# WARN: race condition on", path else: if oldstat: - if oldstat.st_mtime != curstat.st_mtime or \ - oldstat.st_size != curstat.st_size: + if oldstat.mtime != curstat.mtime or \ + oldstat.size != curstat.size: changed = True print "# MODIFIED", path else: Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Thu Nov 10 02:06:34 2005 @@ -255,26 +255,34 @@ #print "repr_failures sees item", item #print "repr_failures sees traceback" #py.std.pprint.pprint(traceback) - if item: - self.cut_traceback(traceback, item) + if item and not self.config.option.fulltrace: + path, firstlineno = item.getpathlineno() + ntraceback = traceback.cut(path=path, firstlineno=firstlineno) + if ntraceback == traceback: + ntraceback = ntraceback.cut(path=path) + traceback = ntraceback.filter() if not traceback: self.out.line("empty traceback from item %r" % (item,)) return last = traceback[-1] first = traceback[0] - recursioncache = {} - for entry in traceback: + if not self.config.option.nomagic and excinfo.errisinstance(RuntimeError): + recursionindex = traceback.recursionindex() + else: + recursionindex = None + for index, entry in py.builtin.enumerate(traceback): if entry == first: if item: self.repr_failure_info(item, entry) self.out.line() else: self.out.line("") + source = self.getentrysource(entry) if entry == last: - indent = self.repr_source(entry, 'E') - self.repr_failure_explanation(excinfo, indent) + self.repr_source(source, 'E') + self.repr_failure_explanation(excinfo, source) else: - self.repr_source(entry, '>') + self.repr_source(source, '>') self.out.line("") self.out.line("[%s:%d]" %(entry.frame.code.path, entry.lineno+1)) self.repr_locals(entry) @@ -287,27 +295,11 @@ self.out.sep("_") else: self.out.sep("_ ") - if not self.config.option.nomagic and excinfo.errisinstance(RuntimeError) \ - and self.isrecursive(entry, recursioncache): + if index == recursionindex: self.out.line("Recursion detected (same locals & position)") self.out.sep("!") break - def isrecursive(self, entry, recursioncache): - # recursion detection - key = entry.frame.code.path, entry.frame.lineno - #print "checking for recursion at", key - l = recursioncache.setdefault(key, []) - if l: - f = entry.frame - loc = f.f_locals - for otherloc in l: - if f.is_true(f.eval(co_equal, - __recursioncache_locals_1=loc, - __recursioncache_locals_2=otherloc)): - return True - l.append(entry.frame.f_locals) - def repr_failure_info(self, item, entry): root = item.fspath modpath = item.getmodpath() @@ -326,62 +318,27 @@ else: self.out.sep("_", "entrypoint: %s %s" %(root.basename, modpath)) - def repr_source(self, entry, marker=">"): + def getentrysource(self, entry): 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.config.option.fulltrace or item is None: - return - newtraceback = traceback[:] - path, lineno = item.getpathlineno() - for i, entry in py.builtin.enumerate(newtraceback): - if entry.frame.code.path == path: - last = i - while i < len(newtraceback)-1: - entry = newtraceback[i] - next = newtraceback[i+1] - if next.frame.code.path != path: - break - if entry.frame.code.firstlineno == lineno: - break - del newtraceback[:i] - break - if not newtraceback: - newtraceback = traceback[:] - - # get rid of all frames marked with __tracebackhide__ - l = [] - for entry in newtraceback: - try: - x = entry.frame.eval("__tracebackhide__") - except: - x = None - if not x: - l.append(entry) - traceback[:] = l + source = py.code.Source("[failure to get at sourcelines from %r]\n" % entry) + return source.deindent() - def repr_failure_explanation(self, excinfo, indent): + def repr_source(self, source, marker=">"): + for line in source[:-1]: + self.out.line(" " + line) + lastline = source[-1] + self.out.line(marker + " " + lastline) - indent = " " * indent + def repr_failure_explanation(self, excinfo, source): + try: + s = str(source.getstatement(len(source)-1)) + except KeyboardInterrupt: + raise + except: + s = str(source[-1]) + indent = " " * (4 + (len(s) - len(s.lstrip()))) # get the real exception information out lines = excinfo.exconly(tryshort=True).split('\n') self.out.line('>' + indent[:-1] + lines.pop(0)) @@ -435,9 +392,6 @@ self.out.line("%-10s =\\" % (name,)) py.std.pprint.pprint(value, stream=self.out) -co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2', - '?', 'eval') - def repr_pythonversion(): v = py.std.sys.version_info try: Modified: py/dist/py/test/tkinter/backend.py ============================================================================== --- py/dist/py/test/tkinter/backend.py (original) +++ py/dist/py/test/tkinter/backend.py Thu Nov 10 02:06:34 2005 @@ -191,8 +191,8 @@ self.testrepository = TestRepository() self.reportstore = ReportStore() self.gateway = py.execnet.PopenGateway(config.option.executable) - self.channel = self.gateway.newchannel(receiver = self.queue.put) - self.gateway.remote_exec(channel = self.channel, source = ''' + #self.channel = self.gateway.newchannel(receiver = self.queue.put) + self.channel = self.gateway.remote_exec(source = ''' import py from py.__.test.tkinter.backend import remote @@ -201,6 +201,7 @@ # why? channel.close() ''') + self.channel.setcallback(self.queue.put) self.channel.send((args, tests)) self.waitfinish_thread = threading.Thread(target = waitfinish, args = (self.channel,)) self.waitfinish_thread.start() Modified: py/dist/py/thread/pool.py ============================================================================== --- py/dist/py/thread/pool.py (original) +++ py/dist/py/thread/pool.py Thu Nov 10 02:06:34 2005 @@ -3,18 +3,20 @@ import time import sys +ERRORMARKER = object() + class Reply(object): _excinfo = None def __init__(self, task): self.task = task self._queue = Queue.Queue() - def set(self, result): + def _set(self, result): self._queue.put(result) - def setexcinfo(self, excinfo): + def _setexcinfo(self, excinfo): self._excinfo = excinfo - self._queue.put(None) + self._queue.put(ERRORMARKER) def _get_with_timeout(self, timeout): # taken from python2.3's Queue.get() @@ -32,13 +34,15 @@ time.sleep(delay) #reduce CPU usage by using a sleep def get(self, timeout=None): + if self._queue is None: + raise EOFError("reply has already been delivered") if timeout is not None: result = self._get_with_timeout(timeout) else: result = self._queue.get() - excinfo = self._excinfo - if excinfo: - self._excinfo = None + if result is ERRORMARKER: + self._queue = None + excinfo = self._excinfo raise excinfo[0], excinfo[1], excinfo[2] return result @@ -61,9 +65,9 @@ except (SystemExit, KeyboardInterrupt): return False except: - reply.setexcinfo(sys.exc_info()) + reply._setexcinfo(sys.exc_info()) else: - reply.set(result) + reply._set(result) # at this point, reply, task and all other local variables go away return True Modified: py/dist/py/thread/testing/test_pool.py ============================================================================== --- py/dist/py/thread/testing/test_pool.py (original) +++ py/dist/py/thread/testing/test_pool.py Thu Nov 10 02:06:34 2005 @@ -46,6 +46,7 @@ raise ValueError("42") reply = pool.dispatch(f) excinfo = py.test.raises(ValueError, "reply.get(1.0)") + py.test.raises(EOFError, "reply.get(1.0)") def test_maxthreads(): pool = WorkerPool(maxthreads=1) From jan at codespeak.net Thu Nov 10 17:00:24 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Thu, 10 Nov 2005 17:00:24 +0100 (CET) Subject: [py-svn] r19721 - in py/dist/py/test: . testing testing/data Message-ID: <20051110160024.D0C2427B46@code1.codespeak.net> Author: jan Date: Thu Nov 10 17:00:22 2005 New Revision: 19721 Added: py/dist/py/test/testing/data/disabled_module.py Removed: py/dist/py/test/testing/data/disabledclass.py Modified: py/dist/py/test/collect.py py/dist/py/test/testing/test_collect.py Log: remove not used testdata Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Thu Nov 10 17:00:22 2005 @@ -275,6 +275,12 @@ return d class Module(PyCollectorMixin, FSCollector): + + def run(self): + if getattr(self.obj, 'disabled', 0): + return [] + return FSCollector.run(self) + def startcapture(self): if not self.option.nocapture and not self.option.usepdb: assert not hasattr(self, '_capture') Added: py/dist/py/test/testing/data/disabled_module.py ============================================================================== --- (empty file) +++ py/dist/py/test/testing/data/disabled_module.py Thu Nov 10 17:00:22 2005 @@ -0,0 +1,15 @@ +disabled = True + +def setup_module(mod): + raise ValueError + +class TestClassOne: + def test_func(self): + raise ValueError + +class TestClassTwo: + def setup_class(cls): + raise ValueError + def test_func(self): + raise ValueError + Deleted: /py/dist/py/test/testing/data/disabledclass.py ============================================================================== --- /py/dist/py/test/testing/data/disabledclass.py Thu Nov 10 17:00:22 2005 +++ (empty file) @@ -1,5 +0,0 @@ - -class TestClass: - disabled = True - def test_func(): - pass Modified: py/dist/py/test/testing/test_collect.py ============================================================================== --- py/dist/py/test/testing/test_collect.py (original) +++ py/dist/py/test/testing/test_collect.py Thu Nov 10 17:00:22 2005 @@ -76,6 +76,12 @@ assert isinstance(colitem, py.test.collect.Class) assert not colitem.run() +def test_disabled_class(): + col = py.test.collect.Module(datadir.join('disabled_module.py')) + l = col.run() + assert len(l) == 0 + + class Testsomeclass: disabled = True def test_something(): From jan at codespeak.net Thu Nov 10 17:10:04 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Thu, 10 Nov 2005 17:10:04 +0100 (CET) Subject: [py-svn] r19723 - in py/dist/py/test: . testing/data Message-ID: <20051110161004.36D8927B46@code1.codespeak.net> Author: jan Date: Thu Nov 10 17:10:02 2005 New Revision: 19723 Modified: py/dist/py/test/collect.py py/dist/py/test/testing/data/disabled_module.py Log: A test module can be disabled by setting disabled=True at module level. This message should have been commited with rev 19721. Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Thu Nov 10 17:10:02 2005 @@ -275,7 +275,7 @@ return d class Module(PyCollectorMixin, FSCollector): - + def run(self): if getattr(self.obj, 'disabled', 0): return [] @@ -326,6 +326,7 @@ class Class(PyCollectorMixin, Collector): + def run(self): if getattr(self.obj, 'disabled', 0): return [] Modified: py/dist/py/test/testing/data/disabled_module.py ============================================================================== --- py/dist/py/test/testing/data/disabled_module.py (original) +++ py/dist/py/test/testing/data/disabled_module.py Thu Nov 10 17:10:02 2005 @@ -1,3 +1,4 @@ + disabled = True def setup_module(mod): From jan at codespeak.net Fri Nov 11 13:43:54 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 11 Nov 2005 13:43:54 +0100 (CET) Subject: [py-svn] r19748 - py/branch/traits Message-ID: <20051111124354.95BCC27B3F@code1.codespeak.net> Author: jan Date: Fri Nov 11 13:43:53 2005 New Revision: 19748 Added: py/branch/traits/ - copied from r19743, py/dist/py/ Log: support tagging of testcases From jan at codespeak.net Fri Nov 11 14:09:02 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 11 Nov 2005 14:09:02 +0100 (CET) Subject: [py-svn] r19751 - in py/branch/traits: . bin builtin c-extension code compat documentation execnet io log magic misc path process py test thread tool xmlobj Message-ID: <20051111130902.B8B4C27B3D@code1.codespeak.net> Author: jan Date: Fri Nov 11 14:08:59 2005 New Revision: 19751 Added: py/branch/traits/py/ - copied from r19750, py/dist/py/ py/branch/traits/setup.py - copied unchanged from r19750, py/dist/setup.py Removed: py/branch/traits/LICENSE py/branch/traits/__init__.py py/branch/traits/bin/ py/branch/traits/builtin/ py/branch/traits/c-extension/ py/branch/traits/code/ py/branch/traits/compat/ py/branch/traits/conftest.py py/branch/traits/documentation/ py/branch/traits/env.py py/branch/traits/execnet/ py/branch/traits/initpkg.py py/branch/traits/io/ py/branch/traits/log/ py/branch/traits/magic/ py/branch/traits/misc/ py/branch/traits/path/ py/branch/traits/process/ py/branch/traits/test/ py/branch/traits/thread/ py/branch/traits/tool/ py/branch/traits/xmlobj/ Log: If you create a branch, do it right! Use switch not copy :-( Deleted: /py/branch/traits/LICENSE ============================================================================== --- /py/branch/traits/LICENSE Fri Nov 11 14:08:59 2005 +++ (empty file) @@ -1,19 +0,0 @@ -py lib Copyright holders, 2003-2005 -======================================= - -Except when otherwise stated (look for LICENSE files or information at -the beginning of each file) the files in the 'py' directory are -copyrighted by one or more of the following people and organizations: - - Holger Krekel - merlinux GmbH, Germany - Armin Rigo - Jan Balster - -Contributors include:: - - Ian Bicking - Grig Gheorghiu - Bob Ippolito - Christian Tismer - Samuele Pedroni Deleted: /py/branch/traits/__init__.py ============================================================================== --- /py/branch/traits/__init__.py Fri Nov 11 14:08:59 2005 +++ (empty file) @@ -1,124 +0,0 @@ -"""\ -the py lib is a development support library featuring -py.test, an interactive testing tool which supports -unit-testing with practically no boilerplate. -""" -from initpkg import initpkg - -initpkg(__name__, - description = "py.test and the py lib", - revision = int('$LastChangedRevision$'.split(':')[1][:-1]), - lastchangedate = '$LastChangedDate$', - version = "0.8.0-alpha2", - url = "http://codespeak.net/py", - download_url = "http://codespeak.net/download/py/py-0.8.0-alpha2.tar.gz", - license = "MIT license", - platforms = ['unix', 'linux', 'cygwin'], - author = "holger krekel & others", - author_email = "hpk at merlinux.de", - long_description = globals()['__doc__'], - - exportdefs = { - '_dist.setup' : ('./misc/_dist.py', 'setup'), - - # helpers for use from test functions or collectors - 'test.raises' : ('./test/raises.py', 'raises'), - 'test.deprecated_call' : ('./test/deprecate.py', 'deprecated_call'), - 'test.skip' : ('./test/item.py', 'skip'), - 'test.fail' : ('./test/item.py', 'fail'), - 'test.exit' : ('./test/session.py', 'exit'), - 'test.compat.TestCase' : ('./test/compat.py', 'TestCase'), - - # configuration/initialization related test api - 'test.Config' : ('./test/config.py', 'Config'), - 'test.ensuretemp' : ('./test/config.py', 'ensuretemp'), - 'test.cmdline.main' : ('./test/cmdline.py', 'main'), - - # for customization of collecting/running tests - 'test.Session' : ('./test/session.py', 'Session'), - 'test.TerminalSession' : ('./test/terminal/terminal.py', 'TerminalSession'), - 'test.TkinterSession' : ('./test/tkinter/tksession.py', 'TkSession'), - 'test.collect.Collector' : ('./test/collect.py', 'Collector'), - 'test.collect.Directory' : ('./test/collect.py', 'Directory'), - 'test.collect.Module' : ('./test/collect.py', 'Module'), - 'test.collect.Class' : ('./test/collect.py', 'Class'), - 'test.collect.Instance' : ('./test/collect.py', 'Instance'), - 'test.collect.Generator' : ('./test/collect.py', 'Generator'), - 'test.Item' : ('./test/item.py', 'Item'), - 'test.Function' : ('./test/item.py', 'Function'), - - # thread related API (still in early design phase) - '_thread.WorkerPool' : ('./thread/pool.py', 'WorkerPool'), - '_thread.NamedThreadPool' : ('./thread/pool.py', 'NamedThreadPool'), - '_thread.ThreadOut' : ('./thread/io.py', 'ThreadOut'), - - # hook into the top-level standard library - 'std' : ('./misc/std.py', 'std'), - - 'process.cmdexec' : ('./process/cmdexec.py', 'cmdexec'), - - # path implementations - 'path.svnwc' : ('./path/svn/wccommand.py', 'SvnWCCommandPath'), - 'path.svnurl' : ('./path/svn/urlcommand.py', 'SvnCommandPath'), - 'path.local' : ('./path/local/local.py', 'LocalPath'), - 'path.extpy' : ('./path/extpy/extpy.py', 'Extpy'), - 'path.checker' : ('./path/common.py', 'checker'), # deprecated - - # some nice slightly magic APIs - '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.autopath' : ('./magic/autopath.py', 'autopath'), - 'magic.View' : ('./magic/viewtype.py', 'View'), - 'magic.AssertionError' : ('./magic/assertion.py', 'AssertionError'), - - # python inspection/code-generation API - 'code.compile' : ('./code/source.py', 'compile_'), - 'code.Source' : ('./code/source.py', 'Source'), - 'code.Code' : ('./code/code.py', 'Code'), - 'code.Frame' : ('./code/frame.py', 'Frame'), - 'code.ExceptionInfo' : ('./code/excinfo.py', 'ExceptionInfo'), - 'code.Traceback' : ('./code/traceback2.py', 'Traceback'), - - # backports and additions of builtins - 'builtin.enumerate' : ('./builtin/enumerate.py', 'enumerate'), - 'builtin.reversed' : ('./builtin/reversed.py', 'reversed'), - - # gateways into remote contexts - 'execnet.SocketGateway' : ('./execnet/register.py', 'SocketGateway'), - 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), - 'execnet.SshGateway' : ('./execnet/register.py', 'SshGateway'), - - # input-output helping - 'io.dupfile' : ('./io/dupfile.py', 'dupfile'), - 'io.FDCapture' : ('./io/capture.py', 'FDCapture'), - 'io.OutErrCapture' : ('./io/capture.py', 'OutErrCapture'), - 'io.callcapture' : ('./io/capture.py', 'callcapture'), - - # error module, defining all errno's as Classes - 'error' : ('./misc/error.py', 'error'), - - # small and mean xml/html generation - 'xml.html' : ('./xmlobj/html.py', 'html'), - 'xml.Tag' : ('./xmlobj/xml.py', 'Tag'), - 'xml.Namespace' : ('./xmlobj/xml.py', 'Namespace'), - 'xml.escape' : ('./xmlobj/misc.py', 'escape'), - - # logging API ('producers' and 'consumers' connected via keywords) - 'log.Producer' : ('./log/producer.py', 'Producer'), - 'log.default' : ('./log/producer.py', 'default'), - 'log._getstate' : ('./log/producer.py', '_getstate'), - 'log._setstate' : ('./log/producer.py', '_setstate'), - 'log.setconsumer' : ('./log/consumer.py', 'setconsumer'), - 'log.Path' : ('./log/consumer.py', 'Path'), - 'log.STDOUT' : ('./log/consumer.py', 'STDOUT'), - 'log.STDERR' : ('./log/consumer.py', 'STDERR'), - 'log.get' : ('./log/logger.py', 'get'), - - # compatibility modules (taken from 2.4.1) - 'compat.doctest' : ('./compat/doctest.py', '*'), - 'compat.optparse' : ('./compat/optparse.py', '*'), - 'compat.textwrap' : ('./compat/textwrap.py', '*'), -}) Deleted: /py/branch/traits/conftest.py ============================================================================== --- /py/branch/traits/conftest.py Fri Nov 11 14:08:59 2005 +++ (empty file) @@ -1,26 +0,0 @@ -#pythonexecutables = ('python2.2', 'python2.3',) -#pythonexecutable = 'python2.2' - -# in the future we want to be able to say here: -#def setup_module(extpy): -# mod = extpy.resolve() -# mod.module = 23 -# directory = pypath.root.dirpath() - -# default values for options (modified from cmdline) -verbose = 0 -nocapture = False -collectonly = False -exitfirst = False -fulltrace = False -showlocals = False -nomagic = False - -import py -Option = py.test.Config.Option - -option = py.test.Config.addoptions("execnet options", - Option('-S', '', - action="store", dest="sshtarget", default=None, - help="target to run tests requiring ssh, e.g. user at codespeak.net"), - ) Deleted: /py/branch/traits/env.py ============================================================================== --- /py/branch/traits/env.py Fri Nov 11 14:08:59 2005 +++ (empty file) @@ -1,21 +0,0 @@ -#!/usr/bin/env python - -import sys, os - -progpath = sys.argv[0] -packagedir = os.path.abspath(os.path.dirname(progpath)) -packagename = os.path.basename(packagedir) -bindir = os.path.join(packagedir, 'bin') -rootdir = os.path.dirname(packagedir) - -def prepend_unixpath(VAR, strpath): - value = "%r:$%s" % (strpath, VAR) - shell = os.getenv('SHELL') - if shell and shell.endswith('csh'): - return "setenv %s %s" % (VAR, value) - else: - return "%s=%s; export %s" % (VAR, value, VAR) - -if sys.platform != 'win32': - print prepend_unixpath('PATH', bindir) - print prepend_unixpath('PYTHONPATH', rootdir) Deleted: /py/branch/traits/initpkg.py ============================================================================== --- /py/branch/traits/initpkg.py Fri Nov 11 14:08:59 2005 +++ (empty file) @@ -1,258 +0,0 @@ -""" -package initialization. - -You use the functionality of this package by putting - - from py.initpkg import initpkg - initpkg(__name__, exportdefs={ - 'name1.name2' : ('./path/to/file.py', 'name') - ... - }) - -into your package's __init__.py file. This will -lead your package to only expose the names of all -your implementation files that you explicitely -specify. In the above example 'name1' will -become a Module instance where 'name2' is -bound in its namespace to the 'name' object -in the relative './path/to/file.py' python module. -Note that you can also use a '.c' file in which -case it will be compiled via distutils-facilities -on the fly. - -""" -from __future__ import generators -import sys -import os -assert sys.version_info >= (2,2,0), "py lib requires python 2.2 or higher" -from types import ModuleType - -# --------------------------------------------------- -# Package Object -# --------------------------------------------------- - -class Package(object): - def __init__(self, name, exportdefs): - pkgmodule = sys.modules[name] - assert pkgmodule.__name__ == name - self.name = name - self.exportdefs = exportdefs - self.module = pkgmodule - assert not hasattr(pkgmodule, '__package__'), \ - "unsupported reinitialization of %r" % pkgmodule - pkgmodule.__package__ = self - - # make available pkgname.__ - implname = name + '.' + '__' - self.implmodule = ModuleType(implname) - self.implmodule.__name__ = implname - self.implmodule.__file__ = pkgmodule.__file__ - self.implmodule.__path__ = [os.path.abspath(p) - for p in pkgmodule.__path__] - pkgmodule.__ = self.implmodule - setmodule(implname, self.implmodule) - # inhibit further direct filesystem imports through the package module - del pkgmodule.__path__ - - def _resolve(self, extpyish): - """ resolve a combined filesystem/python extpy-ish path. """ - fspath, modpath = extpyish - if not fspath.endswith('.py'): - import py - e = py.path.local(self.implmodule.__file__) - e = e.dirpath(fspath, abs=True) - e = py.path.extpy(e, modpath) - return e.resolve() - assert fspath.startswith('./'), \ - "%r is not an implementation path (XXX)" % (extpyish,) - implmodule = self._loadimpl(fspath[:-3]) - if not isinstance(modpath, str): # export the entire module - return implmodule - - current = implmodule - for x in modpath.split('.'): - try: - current = getattr(current, x) - except AttributeError: - raise AttributeError("resolving %r failed: %s" %( - extpyish, x)) - - return current - - def getimportname(self, path): - if not path.ext.startswith('.py'): - return None - import py - base = py.path.local(self.implmodule.__file__).dirpath() - if not path.relto(base): - return None - names = path.new(ext='').relto(base).split(path.sep) - dottedname = ".".join([self.implmodule.__name__] + names) - return dottedname - - def _loadimpl(self, relfile): - """ load implementation for the given relfile. """ - parts = [x.strip() for x in relfile.split('/') if x and x!= '.'] - modpath = ".".join([self.implmodule.__name__] + parts) - #print "trying import", modpath - return __import__(modpath, None, None, ['__doc__']) - - def exportitems(self): - return self.exportdefs.items() - - def getpath(self): - from py.path import local - base = local(self.implmodule.__file__).dirpath() - assert base.check() - return base - - def _iterfiles(self): - from py.path import checker - base = self.getpath() - for x in base.visit(checker(file=1, notext='.pyc'), - rec=checker(dotfile=0)): - yield x - - def shahexdigest(self, cache=[]): - """ return sha hexdigest for files contained in package. """ - if cache: - return cache[0] - from sha import sha - sum = sha() - for x in self._iterfiles(): - sum.update(x.read()) - cache.append(sum.hexdigest()) - return cache[0] - - def getzipdata(self): - """ return string representing a zipfile containing the package. """ - import zipfile - import py - try: - from cStringIO import StringIO - except ImportError: - from StringIO import StringIO - base = py.__package__.getpath().dirpath() - outf = StringIO() - f = zipfile.ZipFile(outf, 'w', compression=zipfile.ZIP_DEFLATED) - try: - for x in self._iterfiles(): - f.write(str(x), x.relto(base)) - finally: - f.close() - return outf.getvalue() - - def getrev(self): - import py - p = py.path.svnwc(self.module.__file__).dirpath() - try: - return p.info().rev - except (KeyboardInterrupt, MemoryError, SystemExit): - raise - except: - return 'unknown' - -def setmodule(modpath, module): - #print "sys.modules[%r] = %r" % (modpath, module) - sys.modules[modpath] = module - -# --------------------------------------------------- -# Virtual Module Object -# --------------------------------------------------- - -class Module(ModuleType): - def __init__(self, pkg, name): - self.__package__ = pkg - self.__name__ = name - self.__map__ = {} - - def __getattr__(self, name): - if '*' in self.__map__: - extpy = self.__map__['*'][0], name - result = self.__package__._resolve(extpy) - else: - try: - extpy = self.__map__[name] - except KeyError: - __tracebackhide__ = True - raise AttributeError(name) - else: - result = self.__package__._resolve(extpy) - del self.__map__[name] - setattr(self, name, result) - self._fixinspection(result, name) - return result - - def _fixinspection(self, result, name): - # modify some attrs to make a class appear at export level - if hasattr(result, '__module__'): - try: - setattr(result, '__module__', self.__name__) - except (AttributeError, TypeError): - pass - if hasattr(result, '__bases__'): - try: - setattr(result, '__name__', name) - except (AttributeError, TypeError): - pass - - def __repr__(self): - return '' % (self.__name__, ) - - def getdict(self): - # force all the content of the module to be loaded when __dict__ is read - dictdescr = ModuleType.__dict__['__dict__'] - dict = dictdescr.__get__(self) - if '*' not in self.__map__: - for name in self.__map__.keys(): - hasattr(self, name) # force attribute to be loaded, ignore errors - assert not self.__map__, "%r not empty" % self.__map__ - else: - fsname = self.__map__['*'][0] - dict.update(self.__package__._loadimpl(fsname[:-3]).__dict__) - return dict - - __dict__ = property(getdict) - del getdict - -# --------------------------------------------------- -# Bootstrap Virtual Module Hierarchy -# --------------------------------------------------- - -def initpkg(pkgname, exportdefs, **kw): - #print "initializing package", pkgname - # bootstrap Package object - pkg = Package(pkgname, exportdefs) - for name, value in kw.items(): - setattr(pkg, name, value) - seen = { pkgname : pkg.module } - deferred_imports = [] - - for pypath, extpy in pkg.exportitems(): - pyparts = pypath.split('.') - modparts = pyparts[:] - if extpy[1] != '*': - lastmodpart = modparts.pop() - else: - lastmodpart = '*' - current = pkgname - - # ensure modules - for name in modparts: - previous = current - current += '.' + name - if current not in seen: - seen[current] = mod = Module(pkg, current) - setattr(seen[previous], name, mod) - setmodule(current, mod) - - mod = seen[current] - if not hasattr(mod, '__map__'): - assert mod is pkg.module, \ - "only root modules are allowed to be non-lazy. " - deferred_imports.append((mod, pyparts[-1], extpy)) - else: - mod.__map__[lastmodpart] = extpy - - for mod, pypart, extpy in deferred_imports: - setattr(mod, pypart, pkg._resolve(extpy)) From jan at codespeak.net Fri Nov 11 14:16:06 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 11 Nov 2005 14:16:06 +0100 (CET) Subject: [py-svn] r19754 - in py/branch/traits/py/test: . testing testing/data Message-ID: <20051111131606.5939227B3D@code1.codespeak.net> Author: jan Date: Fri Nov 11 14:16:03 2005 New Revision: 19754 Added: py/branch/traits/py/test/testing/data/notraits.py py/branch/traits/py/test/testing/data/traits.py py/branch/traits/py/test/testing/test_traits.py Modified: py/branch/traits/py/test/collect.py Log: every collector has now tags(list) and traits(dict) Modified: py/branch/traits/py/test/collect.py ============================================================================== --- py/branch/traits/py/test/collect.py (original) +++ py/branch/traits/py/test/collect.py Fri Nov 11 14:16:03 2005 @@ -215,7 +215,26 @@ def finishcapture(self): return None # by default collectors don't capture output def getouterr(self): - return self.captured_out, self.captured_err + return self.captured_out, self.captured_err + + def traits(self): + def read_traits(traits_list): + tags = [tag for tag in traits_list + if isinstance(tag, basestring)] + traits = dict([k_v for k_v in traits_list + if isinstance(k_v, tuple)]) + traits['tags'] = tags + return traits + + if getattr(self, '_traits', None) is None: + self._traits = read_traits(getattr(self.obj, 'traits', [])) + return self._traits + traits = property(traits, None, None, "module traits") + + def tags(self): + return self.traits['tags'] + tags = property(tags, None, None, "module tags") + class FSCollector(Collector): def __init__(self, fspath, parent=None): @@ -361,6 +380,7 @@ Function = property(Function) class Generator(Collector): + def buildname2items(self): d = {} for i, x in py.builtin.enumerate(self.obj()): Added: py/branch/traits/py/test/testing/data/notraits.py ============================================================================== --- (empty file) +++ py/branch/traits/py/test/testing/data/notraits.py Fri Nov 11 14:16:03 2005 @@ -0,0 +1,9 @@ +#no traits + +class TestNoTraits: + # no traits + def test_method(self): + pass + +def test_function(self): + pass Added: py/branch/traits/py/test/testing/data/traits.py ============================================================================== --- (empty file) +++ py/branch/traits/py/test/testing/data/traits.py Fri Nov 11 14:16:03 2005 @@ -0,0 +1,17 @@ + +traits = ['bob', 'module', ('key', 'value')] + +class TestTrait: + traits = ['mary', 'class', ('car', 'blue')] + + def test_method(self): + pass + test_method.traits = ['mary', 'method', ('house', 'green')] + +def test_function(): + pass +test_function.traits = ['function', ('speed', 'fast')] + +def test_generartor(self): + yield test_function +test_generartor.traits= ['generator', ('type', 'flower')] Added: py/branch/traits/py/test/testing/test_traits.py ============================================================================== --- (empty file) +++ py/branch/traits/py/test/testing/test_traits.py Fri Nov 11 14:16:03 2005 @@ -0,0 +1,88 @@ +from __future__ import generators +import py +datadir = py.magic.autopath().dirpath('data') + +class TraitFixture: + + def test_type(self): + assert isinstance(self.item, self.type) + + def test_tags(self): + assert len(self.tags) == len(self.item.tags) + #for x in self.tags: + # assert x in self.item.tags + assert self.item.tags == self.tags + + def test_traits(self): + for key,value in self.traits: + #if key == 'tags': continue + assert self.item.traits[key] == value + + def test_modify_traits(self): + # ensure we don't modify a copy + self.item.traits['special'] = 'sauce' + assert self.item.traits['special'] == 'sauce' + self.item.tags.append('dip') + assert 'dip' in self.item.traits['tags'] + assert 'dip' in self.item.tags + assert len(self.tags) +1 == len(self.item.tags) + +class TestClass(TraitFixture): + + def setup_method(self, method): + col = py.test.collect.Module(datadir.join('traits.py')) + l = col.run() + self.item = col.join(l[0]) + self.type = py.test.collect.Class + self.tags = ['mary', 'class'] + self.traits = [('car', 'blue'), ('tags', self.tags)] + + +class TestModule(TraitFixture): + + def setup_method(self, method): + self.item = py.test.collect.Module(datadir.join('traits.py')) + self.type = py.test.collect.Module + self.tags = ['bob', 'module'] + self.traits = [('tags', self.tags), ('key', 'value')] + + +class TestMethod(TraitFixture): + + def setup_method(self, method): + col = py.test.collect.Module(datadir.join('traits.py')) + l = col.run() + colitem = col.join(l[0]) + l = colitem.run() + instance = colitem.join(l[0]) + l = instance.run() + self.item = instance.join(l[0]) + self.type = py.test.Function + self.tags = ['mary', 'method'] + self.traits = [('house', 'green'), ('tags', self.tags)] + +class TestFunction(TraitFixture): + + def setup_method(self, method): + col = py.test.collect.Module(datadir.join('traits.py')) + l = col.run() + self.item = col.join(l[1]) + self.type = py.test.Function + self.tags = ['function'] + self.traits= [('speed','fast'), ('tags', self.tags)] + +class TestGenerator(TraitFixture): + + def setup_method(self, method): + col = py.test.collect.Module(datadir.join('traits.py')) + l = col.run() + self.item = col.join(l[2]) + self.type = py.test.collect.Generator + self.tags = ['generator'] + self.traits= [('type', 'flower'), ('tags', self.tags)] + + +def test_no_traits_module(): + col = py.test.collect.Module(datadir.join('notraits.py')) + assert len(col.traits) == 1 + assert col.traits == {'tags':[]} From hpk at codespeak.net Fri Nov 11 16:26:12 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 11 Nov 2005 16:26:12 +0100 (CET) Subject: [py-svn] r19781 - py/dist/py Message-ID: <20051111152612.C83CF27B3E@code1.codespeak.net> Author: hpk Date: Fri Nov 11 16:26:12 2005 New Revision: 19781 Modified: py/dist/py/LICENSE Log: explicitely add the MIT license verbatim Modified: py/dist/py/LICENSE ============================================================================== --- py/dist/py/LICENSE (original) +++ py/dist/py/LICENSE Fri Nov 11 16:26:12 2005 @@ -17,3 +17,26 @@ Bob Ippolito Christian Tismer Samuele Pedroni + +Except when otherwise stated (look for LICENSE files or information at +the beginning of each file) all files in the 'py' directory are +licensed under the MIT license: + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + From jan at codespeak.net Fri Nov 11 17:51:38 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 11 Nov 2005 17:51:38 +0100 (CET) Subject: [py-svn] r19784 - py/branch/monday Message-ID: <20051111165138.B78FE27B48@code1.codespeak.net> Author: jan Date: Fri Nov 11 17:51:37 2005 New Revision: 19784 Removed: py/branch/monday/ Log: remove monday branch From ale at codespeak.net Sun Nov 13 21:29:27 2005 From: ale at codespeak.net (ale at codespeak.net) Date: Sun, 13 Nov 2005 21:29:27 +0100 (CET) Subject: [py-svn] r19840 - in py/dist/py: bin xmlobj Message-ID: <20051113202927.6E2AE27B5D@code1.codespeak.net> Author: ale Date: Sun Nov 13 21:29:25 2005 New Revision: 19840 Modified: py/dist/py/bin/py.test py/dist/py/xmlobj/html.py Log: implementation of pep302 (import hooks). Currently mostly implemented at applevel - will try to convert to interpreter level later. Modified: py/dist/py/bin/py.test ============================================================================== --- py/dist/py/bin/py.test (original) +++ py/dist/py/bin/py.test Sun Nov 13 21:29:25 2005 @@ -1,4 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python2.4 +import sys from _findpy import py py.test.cmdline.main() Modified: py/dist/py/xmlobj/html.py ============================================================================== --- py/dist/py/xmlobj/html.py (original) +++ py/dist/py/xmlobj/html.py Sun Nov 13 21:29:25 2005 @@ -25,7 +25,7 @@ __stickyname__ = True __tagspec__ = dict([(x,1) for x in ( "h1,h2,h3,h5,h6,p,b,i,a,div,span,code," - "html,head,title,style,table,tr,tt," + "html,head,title,style,table,thead,tr,tt," "td,th,link,img,meta,body,pre,br,ul," "ol,li,em,form,input,select,option," "button,script,colgroup,col,map,area," From hpk at codespeak.net Sun Nov 13 21:50:44 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 13 Nov 2005 21:50:44 +0100 (CET) Subject: [py-svn] r19844 - py/dist/py/bin Message-ID: <20051113205044.06FAF27B58@code1.codespeak.net> Author: hpk Date: Sun Nov 13 21:50:43 2005 New Revision: 19844 Modified: py/dist/py/bin/py.test Log: revert ale's erranenous checkin Modified: py/dist/py/bin/py.test ============================================================================== --- py/dist/py/bin/py.test (original) +++ py/dist/py/bin/py.test Sun Nov 13 21:50:43 2005 @@ -1,5 +1,4 @@ -#!/usr/bin/env python2.4 -import sys +#!/usr/bin/env python from _findpy import py py.test.cmdline.main() From hpk at codespeak.net Tue Nov 15 17:49:51 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 15 Nov 2005 17:49:51 +0100 (CET) Subject: [py-svn] r19908 - py/dist/py/documentation Message-ID: <20051115164951.AFE5327B5B@code1.codespeak.net> Author: hpk Date: Tue Nov 15 17:47:23 2005 New Revision: 19908 Modified: py/dist/py/documentation/TODO.txt Log: the greenlet issue is fixed ASFAIK Modified: py/dist/py/documentation/TODO.txt ============================================================================== --- py/dist/py/documentation/TODO.txt (original) +++ py/dist/py/documentation/TODO.txt Tue Nov 15 17:47:23 2005 @@ -24,10 +24,3 @@ using Armin's collect class * hide py.test.TerminalSession and TkinterSession? (questionable) - -misc ----- - -* get Armin or Christian to fix greenlets on (no clue) - recent gcc's (compile fails at least for - switch_x86_unix.h) From hpk at codespeak.net Tue Nov 15 17:54:14 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 15 Nov 2005 17:54:14 +0100 (CET) Subject: [py-svn] r19909 - in py/dist/py/path: . testing Message-ID: <20051115165414.7F58C27B5B@code1.codespeak.net> Author: hpk Date: Tue Nov 15 17:54:13 2005 New Revision: 19909 Modified: py/dist/py/path/common.py py/dist/py/path/testing/test_api.py Log: fix small bug Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Tue Nov 15 17:54:13 2005 @@ -7,7 +7,7 @@ import py def checktype(pathinstance, kw): - names = ('local', 'svnwc', 'svnurl', 'py', 'fspy') + names = ('local', 'svnwc', 'svnurl', 'py', 'extpy') for name,value in kw.items(): if name in names: cls = getattr(py.path, name) Modified: py/dist/py/path/testing/test_api.py ============================================================================== --- py/dist/py/path/testing/test_api.py (original) +++ py/dist/py/path/testing/test_api.py Tue Nov 15 17:54:13 2005 @@ -24,6 +24,7 @@ assert p.check() assert p.check(local=1) assert p.check(svnwc=0) + assert p.check(extpy=0) assert not p.check(svnwc=1) self.repr_eval_test(p) From jan at codespeak.net Wed Nov 16 12:39:44 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Wed, 16 Nov 2005 12:39:44 +0100 (CET) Subject: [py-svn] r19925 - py/dist/py/test/tkinter/icon Message-ID: <20051116113944.426B027B40@code1.codespeak.net> Author: jan Date: Wed Nov 16 12:39:43 2005 New Revision: 19925 Removed: py/dist/py/test/tkinter/icon/ Log: removed unused image files From cfbolz at codespeak.net Fri Nov 18 11:32:00 2005 From: cfbolz at codespeak.net (cfbolz at codespeak.net) Date: Fri, 18 Nov 2005 11:32:00 +0100 (CET) Subject: [py-svn] r20001 - in py/dist/py/log: . testing Message-ID: <20051118103200.3649427B82@code1.codespeak.net> Author: cfbolz Date: Fri Nov 18 11:31:59 2005 New Revision: 20001 Modified: py/dist/py/log/logger.py py/dist/py/log/testing/test_logger.py Log: make del_override not raise an exception if called twice/without an override set. test Modified: py/dist/py/log/logger.py ============================================================================== --- py/dist/py/log/logger.py (original) +++ py/dist/py/log/logger.py Fri Nov 18 11:31:59 2005 @@ -51,7 +51,10 @@ self._override = lambda msg: consumer(msg) def del_override(self): - del self._override + try: + del self._override + except AttributeError: + pass def _setsub(self, name, dest): assert "_" not in name Modified: py/dist/py/log/testing/test_logger.py ============================================================================== --- py/dist/py/log/testing/test_logger.py (original) +++ py/dist/py/log/testing/test_logger.py Fri Nov 18 11:31:59 2005 @@ -38,6 +38,7 @@ assert r == ["hello", "world", "42"] l[:] = [] log.del_override() + log.del_override() log.x2("hello") assert l2[0].strcontent() == "hello" From cfbolz at codespeak.net Fri Nov 18 12:11:15 2005 From: cfbolz at codespeak.net (cfbolz at codespeak.net) Date: Fri, 18 Nov 2005 12:11:15 +0100 (CET) Subject: [py-svn] r20003 - in py/dist/py/log: . testing Message-ID: <20051118111115.052C927B5E@code1.codespeak.net> Author: cfbolz Date: Fri Nov 18 12:11:05 2005 New Revision: 20003 Modified: py/dist/py/log/consumer.py py/dist/py/log/testing/test_log.py Log: give the py.log.Path consumer a new option delayed_create: the logfile is only created on the first log message that gets send to it. Modified: py/dist/py/log/consumer.py ============================================================================== --- py/dist/py/log/consumer.py (original) +++ py/dist/py/log/consumer.py Fri Nov 18 12:11:05 2005 @@ -10,11 +10,22 @@ def __call__(self, msg): print >>self._file, str(msg) -class Path(File): - def __init__(self, filename, append=False): - mode = append and 'a' or 'w' - f = open(str(filename), mode, buffering=1) - super(Path, self).__init__(f) +class Path(object): + def __init__(self, filename, append=False, delayed_create=False): + self._append = append + self._filename = filename + if not delayed_create: + self._openfile() + + def _openfile(self): + mode = self._append and 'a' or 'w' + f = open(str(self._filename), mode, buffering=1) + self._file = f + + def __call__(self, msg): + if not hasattr(self, "_file"): + self._openfile() + print >> self._file, msg def STDOUT(msg): print >>sys.stdout, str(msg) Modified: py/dist/py/log/testing/test_log.py ============================================================================== --- py/dist/py/log/testing/test_log.py (original) +++ py/dist/py/log/testing/test_log.py Fri Nov 18 12:11:05 2005 @@ -125,6 +125,7 @@ # The append mode is on by default, so we don't need to specify it for File py.log.setconsumer("default", py.log.Path(logfilefn, append=True)) + assert logfilefn.check() py.log.default("hello world #1") lines = logfilefn.readlines() assert lines == ['[default] hello world #1\n'] @@ -133,7 +134,16 @@ lines = logfilefn.readlines() assert lines == ['[default] hello world #1\n', '[default] hello world #1\n'] - + + def test_log_file_delayed_create(self): + logfilefn = tempdir.join('log_create.out') + + py.log.setconsumer("default", py.log.Path(logfilefn, delayed_create=True)) + assert not logfilefn.check() + py.log.default("hello world #1") + lines = logfilefn.readlines() + assert lines == ['[default] hello world #1\n'] + def test_keyword_based_log_files(self): logfiles = [] keywords = 'k1 k2 k3'.split() From arigo at codespeak.net Fri Nov 18 21:30:10 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 18 Nov 2005 21:30:10 +0100 (CET) Subject: [py-svn] r20046 - in py/dist/py: code documentation Message-ID: <20051118203010.D17AD27B8E@code1.codespeak.net> Author: arigo Date: Fri Nov 18 21:30:08 2005 New Revision: 20046 Modified: py/dist/py/code/source.py py/dist/py/documentation/confrest.py Log: * fix the DOCTYPE header for confrest: a space was missing between the two quoted arguments * takes content="..", not value=".." * unrelated: complain on py.code.Source()[slice:with:step] Modified: py/dist/py/code/source.py ============================================================================== --- py/dist/py/code/source.py (original) +++ py/dist/py/code/source.py Fri Nov 18 21:30:08 2005 @@ -44,6 +44,8 @@ if isinstance(key, int): return self.lines[key] else: + if key.step not in (None, 1): + raise IndexError("cannot slice a Source with a step") return self.__getslice__(key.start, key.stop) def __len__(self): Modified: py/dist/py/documentation/confrest.py ============================================================================== --- py/dist/py/documentation/confrest.py (original) +++ py/dist/py/documentation/confrest.py Fri Nov 18 21:30:08 2005 @@ -7,7 +7,7 @@ class Page(object): doctype = ('\n') + ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n') def __init__(self, project, title, stylesheeturl=None, type="text/html", encoding="ISO-8859-1"): self.project = project @@ -24,7 +24,7 @@ def fill(self): content_type = "%s;charset=%s" %(self.type, self.encoding) self.head.append(html.title(self.title)) - self.head.append(html.meta(name="Content-Type", value=content_type)) + self.head.append(html.meta(name="Content-Type", content=content_type)) if self.stylesheeturl: self.head.append( html.link(href=self.stylesheeturl, From arigo at codespeak.net Sun Nov 20 11:10:46 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 20 Nov 2005 11:10:46 +0100 (CET) Subject: [py-svn] r20097 - in py/dist/py: documentation misc Message-ID: <20051120101046.4381427BAE@code1.codespeak.net> Author: arigo Date: Sun Nov 20 11:10:43 2005 New Revision: 20097 Modified: py/dist/py/documentation/confrest.py py/dist/py/misc/rest.py Log: A function missing an 'encoding' argument and just guessing 'utf-8'. I suspect it is the cause for the codespeak documentation errors with latin1-encoded .txt files. Modified: py/dist/py/documentation/confrest.py ============================================================================== --- py/dist/py/documentation/confrest.py (original) +++ py/dist/py/documentation/confrest.py Sun Nov 20 11:10:43 2005 @@ -99,7 +99,7 @@ stylesheet = None content = convert_rest_html(content, txtpath, stylesheet=stylesheet, encoding=encoding) - content = strip_html_header(content) + content = strip_html_header(content, encoding=encoding) page = self.Page(self, "[%s] " % txtpath.purebasename, stylesheeturl=stylesheet) Modified: py/dist/py/misc/rest.py ============================================================================== --- py/dist/py/misc/rest.py (original) +++ py/dist/py/misc/rest.py Sun Nov 20 11:10:43 2005 @@ -57,9 +57,9 @@ rex1 = re.compile(ur'.*(.*).*', re.MULTILINE | re.DOTALL) rex2 = re.compile(ur'.*
(.*)
.*', re.MULTILINE | re.DOTALL) -def strip_html_header(string): +def strip_html_header(string, encoding='utf8'): """ return the content of the body-tag """ - uni = unicode(string, 'utf8') + uni = unicode(string, encoding) for rex in rex1,rex2: match = rex.search(uni) if not match: From cfbolz at codespeak.net Mon Nov 21 10:32:13 2005 From: cfbolz at codespeak.net (cfbolz at codespeak.net) Date: Mon, 21 Nov 2005 10:32:13 +0100 (CET) Subject: [py-svn] r20109 - in py/dist/py/test/testing: . data Message-ID: <20051121093213.1C41627BCE@code1.codespeak.net> Author: cfbolz Date: Mon Nov 21 10:32:11 2005 New Revision: 20109 Removed: py/dist/py/test/testing/data/syntax_error.py Modified: py/dist/py/test/testing/test_session.py Log: issue17 resolved now syntax_error.py is not checked in any more, but rather generated for the test to not break apt-get Deleted: /py/dist/py/test/testing/data/syntax_error.py ============================================================================== --- /py/dist/py/test/testing/data/syntax_error.py Mon Nov 21 10:32:11 2005 +++ (empty file) @@ -1,4 +0,0 @@ - - -this is really not python - Modified: py/dist/py/test/testing/test_session.py ============================================================================== --- py/dist/py/test/testing/test_session.py (original) +++ py/dist/py/test/testing/test_session.py Mon Nov 21 10:32:11 2005 @@ -84,6 +84,12 @@ #f = open('/tmp/logfile', 'wa') class TestTerminalSession: + def setup_class(cls): + (datadir / 'syntax_error.py').write("\nthis is really not python\n") + + def teardown_class(cls): + (datadir / 'syntax_error.py').remove() + def setup_method(self, method): self.file = StringIO() config, args = py.test.Config.parse([]) From mwh at codespeak.net Tue Nov 22 12:35:53 2005 From: mwh at codespeak.net (mwh at codespeak.net) Date: Tue, 22 Nov 2005 12:35:53 +0100 (CET) Subject: [py-svn] r20164 - py/dist/py/bin Message-ID: <20051122113553.C37FD27BA7@code1.codespeak.net> Author: mwh Date: Tue Nov 22 12:35:53 2005 New Revision: 20164 Modified: py/dist/py/bin/py.lookup Log: add a colon after the line number in py.lookup's output -- this means that emacs's grep mode recognizes the output and you can jump between hits with C-x `. Modified: py/dist/py/bin/py.lookup ============================================================================== --- py/dist/py/bin/py.lookup (original) +++ py/dist/py/bin/py.lookup Tue Nov 22 12:35:53 2005 @@ -14,4 +14,4 @@ lines = x.readlines() for i, line in enumerate(lines): if line.find(string) != -1: - print "%s:%d %s" %(x.relto(curdir), i+1, line.rstrip()) + print "%s:%d: %s" %(x.relto(curdir), i+1, line.rstrip()) From jan at codespeak.net Fri Nov 25 15:48:47 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 25 Nov 2005 15:48:47 +0100 (CET) Subject: [py-svn] r20244 - py/dist/py/test/testing Message-ID: <20051125144847.AA9BF27B5D@code1.codespeak.net> Author: jan Date: Fri Nov 25 15:48:46 2005 New Revision: 20244 Modified: py/dist/py/test/testing/test_collect.py Log: fix problem with missing file syntax_error.py, which was deleted in rev r20109 Modified: py/dist/py/test/testing/test_collect.py ============================================================================== --- py/dist/py/test/testing/test_collect.py (original) +++ py/dist/py/test/testing/test_collect.py Fri Nov 25 15:48:46 2005 @@ -64,9 +64,11 @@ py.test.raises(py.error.ENOENT, col.run) def test_syntax_error_in_module(): + (datadir / 'syntax_error.py').write("\nthis is really not python\n") modpath = datadir.join('syntax_error.py') col = py.test.collect.Module(modpath) - py.test.raises(SyntaxError, col.run) + py.test.raises(SyntaxError, col.run) + (datadir / 'syntax_error.py').remove() def test_disabled_class(): col = py.test.collect.Module(datadir.join('disabled.py')) From cfbolz at codespeak.net Sat Nov 26 22:56:50 2005 From: cfbolz at codespeak.net (cfbolz at codespeak.net) Date: Sat, 26 Nov 2005 22:56:50 +0100 (CET) Subject: [py-svn] r20293 - in py/dist/py/rest: . testing testing/data Message-ID: <20051126215650.9401627B51@code1.codespeak.net> Author: cfbolz Date: Sat Nov 26 22:56:45 2005 New Revision: 20293 Added: py/dist/py/rest/ py/dist/py/rest/__init__.py py/dist/py/rest/convert.py py/dist/py/rest/latex.py py/dist/py/rest/rest.sty.template py/dist/py/rest/restgraphviz.py py/dist/py/rest/testing/ py/dist/py/rest/testing/__init__.py py/dist/py/rest/testing/data/ py/dist/py/rest/testing/data/example.rst2pdfconfig py/dist/py/rest/testing/data/example1.dot py/dist/py/rest/testing/data/graphviz.txt py/dist/py/rest/testing/data/part1.txt py/dist/py/rest/testing/data/part2.txt py/dist/py/rest/testing/test_convert.py py/dist/py/rest/testing/test_restgraphviz.py py/dist/py/rest/testing/test_rst2pdf.py Log: add some (still quite in-progress work) to have some better rest features: * added a new py/rest dir (that does not export any names for now, watch out for changes) that will contain rest helpers. * added a convert.py file that contains functions to convert various (image) file formats to each other * added a latex.py file that makes producing pdf files with ReST easier * added a restgraphviz.py file that contains a docutils directive to allow embedding dot files in a way that the result fits the output format: for html output the dot will be converted to png, for tex to pdf * tests for all of that Added: py/dist/py/rest/__init__.py ============================================================================== Added: py/dist/py/rest/convert.py ============================================================================== --- (empty file) +++ py/dist/py/rest/convert.py Sat Nov 26 22:56:45 2005 @@ -0,0 +1,56 @@ +import py + +# utility functions to convert between various formats + +format_to_dotargument = {"png": "png", + "eps": "ps", + "ps": "ps", + "pdf": "ps", + } + +def ps2eps(ps): + # XXX write a pure python version + try: + eps = ps.new(ext=".eps") + py.process.cmdexec("ps2epsi %s %s" % (ps, eps)) + except py.process.ExecutionFailed: + try: + py.process.cmdexec("ps2eps -l -f %s" % ps) + except py.process.ExecutionFailed: + raise OSError("neither ps2eps nor ps2epsi found") + +def ps2pdf(ps, compat_level="1.2"): + pdf = ps.new(ext=".pdf") + options = dict(OPTIONS="-dSAFER -dCompatibilityLevel=%s" % compat_level, + infile=ps, outfile=pdf) + cmd = ('gs %(OPTIONS)s -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite ' + '"-sOutputFile=%(outfile)s" %(OPTIONS)s -c .setpdfwrite ' + '-f "%(infile)s"') % options + py.process.cmdexec(cmd) + return pdf + +def eps2pdf(eps): + # XXX write a pure python version + py.process.cmdexec("epstopdf %s" % eps) + +def convert_dot(fn, new_extension): + result = fn.new(ext=new_extension) + print result + arg = "-T%s" % (format_to_dotargument[new_extension], ) + py.std.os.system("dot %s %s > %s" % (arg, fn, result)) + if new_extension == "eps": + ps = result.new(ext="ps") + result.move(ps) + ps2eps(ps) + ps.remove() + elif new_extension == "pdf": + # convert to eps file first, to get the bounding box right + eps = result.new(ext="eps") + ps = result.new(ext="ps") + result.move(ps) + ps2eps(ps) + eps2pdf(eps) + ps.remove() + eps.remove() + return result + Added: py/dist/py/rest/latex.py ============================================================================== --- (empty file) +++ py/dist/py/rest/latex.py Sat Nov 26 22:56:45 2005 @@ -0,0 +1,122 @@ +import py + +from docutils.core import publish_cmdline + +font_to_package = {"times": "times", "helvetica": "times", + "new century schoolbock": "newcent", "avant garde": "newcent", + "palatino": "palatino", + } +sans_serif_fonts = {"helvetica": True, + "avant garde": True, + } + + +def merge_files(pathlist, pagebreak=False): + pathlist = [py.path.local(path) for path in pathlist] + if len(pathlist) == 1: + return pathlist[0].read() + sectnum = False + toc = False + result = [] + includes = {} + for path in pathlist: + lines = path.readlines() + for line in lines: + # prevent several table of contents + # and especially sectnum several times + if ".. contents::" in line: + if not toc: + toc = True + result.append(line) + elif ".. sectnum::" in line: + if not sectnum: + sectnum = True + result.append(line) + elif line.strip().startswith(".. include:: "): + #XXX slightly unsafe + inc = line.strip()[13:] + if inc not in includes: + includes[inc] = True + result.append(line) + else: + result.append(line) + if pagebreak: + result.append(".. raw:: latex \n\n \\newpage\n\n") + if pagebreak: + result.pop() #remove the last pagebreak again + return "".join(result) + +def create_stylesheet(options, path): + fill_in = {} + if "logo" in options: + fill_in["have_logo"] = "" + fill_in["logo"] = options["logo"] + else: + fill_in["have_logo"] = "%" + fill_in["logo"] = "" + if "font" in options: + font = options["font"].lower() + fill_in["font_package"] = font_to_package[font] + fill_in["specified_font"] = "" + fill_in["sans_serif"] = font not in sans_serif_fonts and "%" or "" + else: + fill_in["specified_font"] = "%" + fill_in["sans_serif"] = "%" + fill_in["font_package"] = "" + fill_in["heading"] = options.get("heading", "") + template_file = path.join("rest.sty.template") + if not template_file.check(): + template_file = py.magic.autopath().dirpath().join("rest.sty.template") + return template_file.read() % fill_in + +def process_configfile(configfile, debug=False): + old = py.path.local() + py.path.local(configfile).dirpath().chdir() + configfile = py.path.local(configfile) + path = configfile.dirpath() + configfile_dic = {} + py.std.sys.path.insert(0, path) + execfile(str(configfile), configfile_dic) + pagebreak = configfile_dic.get("pagebreak", False) + content = merge_files(configfile_dic['rest_sources'], pagebreak) + rest = configfile.new(ext='txt') + rest.write(content) + sty = configfile.new(ext='sty') + content = create_stylesheet(configfile_dic, path) + sty.write(content) + process_rest_file(rest, sty.basename, debug) + #cleanup: + if not debug: + sty.remove() + rest.remove() + old.chdir() + +def process_rest_file(restfile, stylesheet=None, debug=False): + old = py.path.local() + f = py.path.local(restfile) + path = f.dirpath() + path.chdir() + pdf = f.new(ext="pdf") + if pdf.check(): + pdf.remove() + tex = f.new(ext="tex").basename + options = [f, "--graphicx-option=auto", "--traceback"] + if stylesheet is not None: + sty = path.join(stylesheet) + if sty.check(): + options.append("--stylesheet=%s" % (sty, )) + options.append(f.new(basename=tex)) + options = map(str, options) + publish_cmdline(writer_name='latex', argv=options) + for i in range(2): + #XXX sometimes pdflatex has to be run several time + #find a cleaner way to do it + latexoutput = py.process.cmdexec("pdflatex %s" % (tex, )) + if debug: + print latexoutput + old.chdir() + #cleanup: + if not debug: + for ext in "log aux tex out".split(): + p = pdf.new(ext=ext) + p.remove() Added: py/dist/py/rest/rest.sty.template ============================================================================== --- (empty file) +++ py/dist/py/rest/rest.sty.template Sat Nov 26 22:56:45 2005 @@ -0,0 +1,25 @@ +\usepackage{fancyhdr} +\usepackage{lastpage} +\pagestyle{fancy} +\usepackage[pdftex]{graphicx} +\usepackage{epstopdf} + +%(sans_serif)s\renewcommand{\familydefault}{\sfdefault} +%(specified_font)s\usepackage{%(font_package)s} +\lhead{ +\begin{tabular}{l} +\textbf{\Large %(heading)s}\tabularnewline +\thepage\ of \pageref{LastPage}, \today +\tabularnewline +\tabularnewline +\end{tabular} +} +\rhead{ +%(have_logo)s\includegraphics[height=4\baselineskip]{%(logo)s} +} +\cfoot{} +\addtolength{\headheight}{3\baselineskip} +\addtolength{\headheight}{0.61pt} +\setlength\parskip{\medskipamount} +\setlength\parindent{0pt} + Added: py/dist/py/rest/restgraphviz.py ============================================================================== --- (empty file) +++ py/dist/py/rest/restgraphviz.py Sat Nov 26 22:56:45 2005 @@ -0,0 +1,40 @@ +import py + +from py.__.rest.convert import convert_dot + +import sys +from docutils import nodes, utils +from docutils.parsers.rst import directives, states +from docutils.parsers.rst.directives import images +from docutils.nodes import whitespace_normalize_name + +class GraphvizDirective(object): + #XXX this whole class should not be there: + #XXX it is only used to work around the inflexibility of docutils: + # a directive does not know the path of the file it looks at, nor the + # format + def __init__(self, convert_to_format, path=py.path.local()): + self.convert_to_format = convert_to_format + self.path = path + directives.register_directive("graphviz", self.directive_function) + + def convert(self, fn): + dot = self.path.join(fn) + result = convert_dot(dot, self.convert_to_format) + return result.relto(self.path) + + def directive_function(self, name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + newname = self.convert(arguments[0]) + text = block_text.replace("graphviz", "image", 1) + text = text.replace(arguments[0], newname, 1) + return images.image(u'image', [newname], options, content, lineno, + content_offset, text, state, state_machine) + directive_function.arguments = (1, 0, 1) + directive_function.options = {'alt': directives.unchanged, + 'height': directives.nonnegative_int, + 'width': directives.nonnegative_int, + 'scale': directives.nonnegative_int, + 'align': images.align, + 'target': directives.unchanged_required, + 'class': directives.class_option} Added: py/dist/py/rest/testing/__init__.py ============================================================================== Added: py/dist/py/rest/testing/data/example.rst2pdfconfig ============================================================================== --- (empty file) +++ py/dist/py/rest/testing/data/example.rst2pdfconfig Sat Nov 26 22:56:45 2005 @@ -0,0 +1,2 @@ +rest_sources = ['part1.txt', 'part2.txt'] + Added: py/dist/py/rest/testing/data/example1.dot ============================================================================== --- (empty file) +++ py/dist/py/rest/testing/data/example1.dot Sat Nov 26 22:56:45 2005 @@ -0,0 +1,3 @@ +digraph G { + a -> b -> c -> d; +} Added: py/dist/py/rest/testing/data/graphviz.txt ============================================================================== --- (empty file) +++ py/dist/py/rest/testing/data/graphviz.txt Sat Nov 26 22:56:45 2005 @@ -0,0 +1,7 @@ +This tests the graphviz directive +================================= + +let's embed the cool graphviz example: + +.. graphviz:: example1.dot + :scale: 50 Added: py/dist/py/rest/testing/data/part1.txt ============================================================================== --- (empty file) +++ py/dist/py/rest/testing/data/part1.txt Sat Nov 26 22:56:45 2005 @@ -0,0 +1,7 @@ +This is the first part of the example rest file +=============================================== + +.. sectnum:: + +some content. some more. the end. + Added: py/dist/py/rest/testing/data/part2.txt ============================================================================== --- (empty file) +++ py/dist/py/rest/testing/data/part2.txt Sat Nov 26 22:56:45 2005 @@ -0,0 +1,7 @@ +This is the second part of the test file +========================================= + +.. sectnum:: + +the text in it is not much more interesting. + Added: py/dist/py/rest/testing/test_convert.py ============================================================================== --- (empty file) +++ py/dist/py/rest/testing/test_convert.py Sat Nov 26 22:56:45 2005 @@ -0,0 +1,29 @@ +import py +from py.__.rest.convert import convert_dot + +def is_on_path(name): + try: + py.path.local.sysfind(name) + except py.error.ENOENT: + return False + else: + return True + +datadir = py.magic.autopath().dirpath().join("data") + +def setup_module(mod): + if not is_on_path("gs") or not is_on_path("dot"): + py.test.skip("ghostscript and graphviz needed") + +def test_convert_dot(): + # XXX not really clear that the result is valid pdf/eps + dot = datadir.join("example1.dot") + convert_dot(dot, "pdf") + pdf = dot.new(ext="pdf") + assert pdf.check() + pdf.remove() + convert_dot(dot, "eps") + eps = dot.new(ext="eps") + assert eps.check() + eps.remove() + Added: py/dist/py/rest/testing/test_restgraphviz.py ============================================================================== --- (empty file) +++ py/dist/py/rest/testing/test_restgraphviz.py Sat Nov 26 22:56:45 2005 @@ -0,0 +1,32 @@ +import py +from py.__.rest import restgraphviz +from py.__.misc import rest +from py.__.rest.latex import process_rest_file + +datadir = py.magic.autopath().dirpath().join("data") + +def test_graphviz_html(): + graphvizdirective = restgraphviz.GraphvizDirective("png", datadir) + #for reasons that elude me rest.process expects svnwcs??? + txt = py.path.svnwc(datadir.join("graphviz.txt")) + html = txt.new(ext="html") + png = datadir.join("example1.png") + rest.process(txt) + assert html.check() + assert png.check() + html_content = html.read() + assert png.basename in html_content + html.remove() + png.remove() + +def test_graphviz_pdf(): + graphvizdirective = restgraphviz.GraphvizDirective("pdf", datadir) + txt = py.path.local(datadir.join("graphviz.txt")) + pdf = txt.new(ext="pdf") + dotpdf = datadir.join("example1.pdf") + process_rest_file(txt) + assert pdf.check() + assert dotpdf.check() + pdf.remove() + dotpdf.remove() + Added: py/dist/py/rest/testing/test_rst2pdf.py ============================================================================== --- (empty file) +++ py/dist/py/rest/testing/test_rst2pdf.py Sat Nov 26 22:56:45 2005 @@ -0,0 +1,18 @@ +import py +from py.__.rest.latex import process_configfile, process_rest_file + +data = py.magic.autopath().dirpath().join("data") + +def test_process_rest_file(): + part2 = data.join("part1.txt") + pdf = part2.new(ext="pdf") + process_rest_file(part2) + assert pdf.check() + pdf.remove() + +def test_process_configfile(): + config = data.join("example.rst2pdfconfig") + pdf = config.new(ext="pdf") + process_configfile(config) + assert pdf.check() + pdf.remove() From cfbolz at codespeak.net Sat Nov 26 22:57:58 2005 From: cfbolz at codespeak.net (cfbolz at codespeak.net) Date: Sat, 26 Nov 2005 22:57:58 +0100 (CET) Subject: [py-svn] r20294 - py/dist/py/bin Message-ID: <20051126215758.8800027B51@code1.codespeak.net> Author: cfbolz Date: Sat Nov 26 22:57:56 2005 New Revision: 20294 Added: py/dist/py/bin/rst2pdf.py (contents, props changed) Modified: py/dist/py/bin/py.rest Log: exposed the new rest functionality via py.rest and rst2pdf.py (maybe those two should be merged?). now the graphviz directive can be used. Modified: py/dist/py/bin/py.rest ============================================================================== --- py/dist/py/bin/py.rest (original) +++ py/dist/py/bin/py.rest Sat Nov 26 22:57:56 2005 @@ -15,7 +15,9 @@ from py.__.misc import rest if __name__=='__main__': + from py.__.rest import restgraphviz basedir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))) + graphvizdirective = restgraphviz.GraphvizDirective("png") sys.path.insert(0, basedir) if len(sys.argv) == 1: @@ -32,7 +34,9 @@ def rec(p): return p.check(dotfile=0) if p.check(dir=1): + graphvizdirective.path = p for x in p.visit(fil, rec): rest.process(x) elif p.check(file=1): + graphvizdirective.path = p.dirpath() rest.process(p) Added: py/dist/py/bin/rst2pdf.py ============================================================================== --- (empty file) +++ py/dist/py/bin/rst2pdf.py Sat Nov 26 22:57:56 2005 @@ -0,0 +1,42 @@ +#!/usr/bin/env python +docstring = """ +invoke + + %s filename1.txt + +to generate a pdf file from restructered text. + +http://docutils.sourceforge.net/docs/user/rst/quickref.html + +""" % __file__ + +from _findpy import py + +from py.__.rest import restgraphviz +from py.__.rest.latex import process_rest_file, process_configfile + +optparse = py.compat.optparse + +parser = optparse.OptionParser(usage=docstring) +parser.add_option("-c", "--config", dest="configfile", + help="use config file") +parser.add_option("--stylesheet", dest="stylesheet", default="rest.sty", + help="use style sheet") +parser.add_option("--debug", action="store_true", dest="debug", + default=False, + help="print debug output and don't delete files") + +if __name__ == '__main__': + graphvizdirective = restgraphviz.GraphvizDirective("pdf") + (options, args) = parser.parse_args() + if options.configfile is not None: + configfile = py.path.local(options.configfile) + graphvizdirective.path = configfile.dirpath() + process_configfile(options.configfile, options.debug) + elif len(args) != 1: + parser.error("please supply a file name") + else: + f = py.path.local(args[0]) + graphvizdirective.path = f.dirpath() + process_rest_file(args[0], options.debug) + From cfbolz at codespeak.net Sat Nov 26 23:01:09 2005 From: cfbolz at codespeak.net (cfbolz at codespeak.net) Date: Sat, 26 Nov 2005 23:01:09 +0100 (CET) Subject: [py-svn] r20295 - in py/dist/py: bin rest rest/testing rest/testing/data Message-ID: <20051126220109.1537D27B85@code1.codespeak.net> Author: cfbolz Date: Sat Nov 26 23:01:05 2005 New Revision: 20295 Modified: py/dist/py/bin/rst2pdf.py (props changed) py/dist/py/rest/ (props changed) py/dist/py/rest/__init__.py (props changed) py/dist/py/rest/convert.py (props changed) py/dist/py/rest/latex.py (props changed) py/dist/py/rest/restgraphviz.py (props changed) py/dist/py/rest/testing/ (props changed) py/dist/py/rest/testing/__init__.py (props changed) py/dist/py/rest/testing/data/ (props changed) py/dist/py/rest/testing/data/graphviz.txt (props changed) py/dist/py/rest/testing/data/part1.txt (props changed) py/dist/py/rest/testing/data/part2.txt (props changed) py/dist/py/rest/testing/test_convert.py (props changed) py/dist/py/rest/testing/test_restgraphviz.py (props changed) py/dist/py/rest/testing/test_rst2pdf.py (props changed) Log: fixeol From jan at codespeak.net Mon Nov 28 11:00:04 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Mon, 28 Nov 2005 11:00:04 +0100 (CET) Subject: [py-svn] r20333 - in py/dist/py/path/local: . testing Message-ID: <20051128100004.962D027B7A@code1.codespeak.net> Author: jan Date: Mon Nov 28 11:00:02 2005 New Revision: 20333 Modified: py/dist/py/path/local/local.py py/dist/py/path/local/posix.py py/dist/py/path/local/testing/test_local.py Log: py.local.path.sysfind doesn't check if the user can read all dirs in the PATH enviroment variable. Fix: catch the exception Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Mon Nov 28 11:00:02 2005 @@ -501,11 +501,14 @@ for x in paths: for addext in tryadd: p = py.path.local(x).join(name, abs=True) + addext - if p.check(file=1): - if checker: - if not checker(p): - continue - return p + try: + if p.check(file=1): + if checker: + if not checker(p): + continue + return p + except py.error.EACCES: + pass raise py.error.ENOENT(name) sysfind = classmethod(sysfind) #""" Modified: py/dist/py/path/local/posix.py ============================================================================== --- py/dist/py/path/local/posix.py (original) +++ py/dist/py/path/local/posix.py Mon Nov 28 11:00:02 2005 @@ -52,9 +52,10 @@ def chmod(self, mode, rec=0): """ change permissions to the given mode. If mode is an integer it directly encodes the os-specific modes. + if rec is True perform recursively. + (xxx if mode is a string then it specifies access rights in '/bin/chmod' style, e.g. a+r). - if rec is True perform recursively. """ if not isinstance(mode, int): raise TypeError("mode %r must be an integer" % (mode,)) 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 Mon Nov 28 11:00:02 2005 @@ -149,6 +149,21 @@ py.path.local.sysfind('jaksdkasldqwe') """) + def test_sysfind_no_permisson(self): + dir = py.test.ensuretemp('sysfind') + env = py.std.os.environ + oldpath = env['PATH'] + try: + noperm = dir.ensure('noperm', dir=True) + env['PATH'] += ":%s" % (noperm) + noperm.chmod(0) + py.test.raises(py.error.ENOENT, """ py.path.local.sysfind('a') + """) + finally: + env['PATH'] = oldpath + noperm.chmod(0644) + noperm.remove() + def test_sysfind_absolute(self): x = py.path.local.sysfind('test') assert x.check(file=1) From jan at codespeak.net Mon Nov 28 14:57:13 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Mon, 28 Nov 2005 14:57:13 +0100 (CET) Subject: [py-svn] r20355 - py/branch/setup Message-ID: <20051128135713.ED2EB27B76@code1.codespeak.net> Author: jan Date: Mon Nov 28 14:57:12 2005 New Revision: 20355 Added: py/branch/setup/ - copied from r20353, py/dist/ py/branch/setup/finddata.py Modified: py/branch/setup/setup.py Log: Branch for moving py-lib to setuptools Ian Bicking submitted setup.py and finddata.py Added: py/branch/setup/finddata.py ============================================================================== --- (empty file) +++ py/branch/setup/finddata.py Mon Nov 28 14:57:12 2005 @@ -0,0 +1,96 @@ +# Note: you may want to copy this into your setup.py file verbatim, as +# you can't import this from another package, when you don't know if +# that package is installed yet. + +import os +import sys +from fnmatch import fnmatchcase +from distutils.util import convert_path + +# Provided as an attribute, so you can append to these instead +# of replicating them: +standard_exclude = ('*.py', '*.pyc', '*~', '.*', '*.bak') +standard_exclude_directories = ('.*', 'CVS', '_darcs', './build', + './dist', 'EGG-INFO', '*.egg-info') + +def find_package_data( + where='.', package='', + exclude=standard_exclude, + exclude_directories=standard_exclude_directories, + only_in_packages=True, + show_ignored=False): + """ + Return a dictionary suitable for use in ``package_data`` + in a distutils ``setup.py`` file. + + The dictionary looks like:: + + {'package': [files]} + + Where ``files`` is a list of all the files in that package that + don't match anything in ``exclude``. + + If ``only_in_packages`` is true, then top-level directories that + are not packages won't be included (but directories under packages + will). + + Directories matching any pattern in ``exclude_directories`` will + be ignored; by default directories with leading ``.``, ``CVS``, + and ``_darcs`` will be ignored. + + If ``show_ignored`` is true, then all the files that aren't + included in package data are shown on stderr (for debugging + purposes). + + Note patterns use wildcards, or can be exact paths (including + leading ``./``), and all searching is case-insensitive. + """ + + out = {} + stack = [(convert_path(where), '', package, only_in_packages)] + while stack: + where, prefix, package, only_in_packages = stack.pop(0) + for name in os.listdir(where): + fn = os.path.join(where, name) + if os.path.isdir(fn): + bad_name = False + for pattern in exclude_directories: + if (fnmatchcase(name, pattern) + or fn.lower() == pattern.lower()): + bad_name = True + if show_ignored: + print >> sys.stderr, ( + "Directory %s ignored by pattern %s" + % (fn, pattern)) + break + if bad_name: + continue + if os.path.isfile(os.path.join(fn, '__init__.py')): + if not package: + new_package = name + else: + new_package = package + '.' + name + stack.append((fn, '', new_package, False)) + else: + stack.append((fn, prefix + name + '/', package, only_in_packages)) + elif package or not only_in_packages: + # is a file + bad_name = False + for pattern in exclude: + if (fnmatchcase(name, pattern) + or fn.lower() == pattern.lower()): + bad_name = True + if show_ignored: + print >> sys.stderr, ( + "File %s ignored by pattern %s" + % (fn, pattern)) + break + if bad_name: + continue + out.setdefault(package, []).append(prefix+name) + return out + +if __name__ == '__main__': + import pprint + pprint.pprint( + find_package_data(show_ignored=True)) Modified: py/branch/setup/setup.py ============================================================================== --- py/dist/setup.py (original) +++ py/branch/setup/setup.py Mon Nov 28 14:57:12 2005 @@ -1,2 +1,27 @@ -import py -py._dist.setup(py) +from setuptools import setup, find_packages +from finddata import find_package_data + +setup( + name="py", + version="0.8.0-alpha2", + description="py.test and the py lib", + long_description="""\ +The py lib is a development support library featuring +py.test, an interactive testing tool which supports +unit-testing with practically no boilerplate.""", + url="http://codespeak.net/py", + author="Holger Krekel & others", + author_email="hpk at merlinux.de", + license="MIT license", + download_url="http://codespeak.net/download/py/py-0.8.0-alpha2.tar.gz", + packages=find_packages(), + package_data=find_package_data(), + scripts=['py/bin/_findpy.py', + 'py/bin/_makepyrelease.py', + 'py/bin/py.cleanup', + 'py/bin/py.countloc', + 'py/bin/py.lookup', + 'py/bin/py.rest', + 'py/bin/py.test', + 'py/bin/pytest.cmd'], + )