From hpk at codespeak.net Sat Apr 2 03:21:43 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 2 Apr 2005 03:21:43 +0200 (MEST) Subject: [py-svn] r10243 - py/dist/py/documentation Message-ID: <20050402012143.D4BD327C45@code1.codespeak.net> Author: hpk Date: Sat Apr 2 03:21:43 2005 New Revision: 10243 Modified: py/dist/py/documentation/test.txt Log: added a trailing chapter about planned features in py.test Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Sat Apr 2 03:21:43 2005 @@ -475,3 +475,61 @@ .. _`getting started`: getting_started.html .. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev + + +Future/Planned Features of py.test +================================== + +Please note that the following descriptions of future features +sound factual although they aren't implemented yet. This +allows easy migration to real documentation later. +Nevertheless, none of the described planned features is +set in stone, yet. In fact, they are open to discussion on +py-dev at codespeak dot net. + +Hey, if you want to suggest new features or command line options +for py.test it would be great if you could do it by providing +documentation for the feature. Welcome to documentation driven +development :-) + +selecting tests by queries/full text search +------------------------------------------- + +You can selectively run tests by specifiying words +on the command line in a google-like way. Example:: + + py.test simple + +will run all tests that are found from the current directory +and that have the word "simple" somewhere in their `test address`_. +``test_simple1`` and ``TestSomething.test_whatever_simpleton`` would both +qualify. If you want to exclude the latter test method you could say:: + + py.test -- simple -simpleton + +Note that the doubledash "--" signals the end of option parsing so +that "-simpleton" will not be misinterpreted as a command line option. + +Interpreting positional arguments as specifying search queries +means that you can only restrict the set of tests. There is no way to +say "run all 'simple' in addition to all 'complex' tests". If this proves +to be a problem we can probably come up with a command line option +that allows to specify multiple queries which all add to the set of +tests-to-consider. + +.. _`test address`: + +the concept of a test address +----------------------------- + +For specifiying tests it is convenient to define the notion +of a *test address*, representable as a filesystem path and a +list of names leading to a test item. If represented as a single +string the path and names are separated by a `/` character, for example: + + ``somedir/somepath.py/TestClass/test_method`` + +Such representations can be used to memoize failing tests +by writing them out in a file or communicating them across +process and computer boundaries. + From hpk at codespeak.net Mon Apr 4 15:46:56 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 4 Apr 2005 15:46:56 +0200 (MEST) Subject: [py-svn] r10286 - py/branch/py-collect/c-extension Message-ID: <20050404134656.0BBE427BC2@code1.codespeak.net> Author: hpk Date: Mon Apr 4 15:46:55 2005 New Revision: 10286 Modified: py/branch/py-collect/c-extension/conftest.py Log: exclude c-extensions from testing for the time being Modified: py/branch/py-collect/c-extension/conftest.py ============================================================================== --- py/branch/py-collect/c-extension/conftest.py (original) +++ py/branch/py-collect/c-extension/conftest.py Mon Apr 4 15:46:55 2005 @@ -1,8 +1,13 @@ import py class Directory(py.test.Directory): - def recfilter(self, path): - if py.std.sys.platform == 'linux2': - if path.basename == 'greenlet': - return False - return super(Directory, self).recfilter(path) + # XXX see in which situations/platforms we want + # run tests here + #def recfilter(self, path): + # if py.std.sys.platform == 'linux2': + # if path.basename == 'greenlet': + # return False + # return super(Directory, self).recfilter(path) + + def run(self): + py.test.skip("c-extension testing needs platform selection") From jan at codespeak.net Mon Apr 4 17:32:41 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Mon, 4 Apr 2005 17:32:41 +0200 (MEST) Subject: [py-svn] r10293 - in py/branch/py-collect: bin test/tkinter test/tkinter/icons Message-ID: <20050404153241.C117F27BC2@code1.codespeak.net> Author: jan Date: Mon Apr 4 17:32:41 2005 New Revision: 10293 Added: py/branch/py-collect/bin/py.test.tkinter (contents, props changed) py/branch/py-collect/test/tkinter/ py/branch/py-collect/test/tkinter/__init__.py py/branch/py-collect/test/tkinter/gui.py py/branch/py-collect/test/tkinter/guidriver.py py/branch/py-collect/test/tkinter/icons/ py/branch/py-collect/test/tkinter/icons/gray.gif (contents, props changed) py/branch/py-collect/test/tkinter/icons/green.gif (contents, props changed) py/branch/py-collect/test/tkinter/icons/green.png (contents, props changed) py/branch/py-collect/test/tkinter/icons/green_aqua.gif (contents, props changed) py/branch/py-collect/test/tkinter/icons/green_aqua.png (contents, props changed) py/branch/py-collect/test/tkinter/icons/green_s.gif (contents, props changed) py/branch/py-collect/test/tkinter/icons/green_s.png (contents, props changed) py/branch/py-collect/test/tkinter/icons/green_s_aqua.gif (contents, props changed) py/branch/py-collect/test/tkinter/icons/green_s_aqua.png (contents, props changed) py/branch/py-collect/test/tkinter/icons/red.gif (contents, props changed) py/branch/py-collect/test/tkinter/icons/red.png (contents, props changed) py/branch/py-collect/test/tkinter/icons/red_aqua.gif (contents, props changed) py/branch/py-collect/test/tkinter/icons/red_aqua.png (contents, props changed) py/branch/py-collect/test/tkinter/icons/red_c.gif (contents, props changed) py/branch/py-collect/test/tkinter/icons/red_c.png (contents, props changed) py/branch/py-collect/test/tkinter/icons/red_c_aqua.gif (contents, props changed) py/branch/py-collect/test/tkinter/icons/red_c_aqua.png (contents, props changed) py/branch/py-collect/test/tkinter/icons/yellow.gif (contents, props changed) py/branch/py-collect/test/tkinter/icons/yellow.png (contents, props changed) py/branch/py-collect/test/tkinter/icons/yellow_aqua.gif (contents, props changed) py/branch/py-collect/test/tkinter/icons/yellow_aqua.png (contents, props changed) py/branch/py-collect/test/tkinter/icons/yellow_s.gif (contents, props changed) py/branch/py-collect/test/tkinter/icons/yellow_s.png (contents, props changed) py/branch/py-collect/test/tkinter/repository.py py/branch/py-collect/test/tkinter/test_exceptionfailure.py py/branch/py-collect/test/tkinter/test_guidriver.py py/branch/py-collect/test/tkinter/test_repository.py py/branch/py-collect/test/tkinter/test_utils.py py/branch/py-collect/test/tkinter/tkgui.py py/branch/py-collect/test/tkinter/tktree.py py/branch/py-collect/test/tkinter/utils.py Log: First checkin of a Tkinter frontend for py.test. Start with py.test.tkinter. Added: py/branch/py-collect/bin/py.test.tkinter ============================================================================== --- (empty file) +++ py/branch/py-collect/bin/py.test.tkinter Mon Apr 4 17:32:41 2005 @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +from _findpy import py +from py.__impl__.test.tkinter import tkgui +tkgui.main() Added: py/branch/py-collect/test/tkinter/__init__.py ============================================================================== Added: py/branch/py-collect/test/tkinter/gui.py ============================================================================== --- (empty file) +++ py/branch/py-collect/test/tkinter/gui.py Mon Apr 4 17:32:41 2005 @@ -0,0 +1,180 @@ +from tktree import Tree, Node +from utils import TestReport, Status, Null +from Tkinter import PhotoImage +import py +Item = py.test.Item +Collector = py.test.Collector + + +class ResultTree(Tree): + + def __init__(self, *args, **kwargs): + kwdict = {'dist_x': 19, 'dist_y': 19, + 'selection_callback': Null(), + 'double_click_callback': Null()} + kwdict.update(kwargs) + self.set_selection_callback(kwdict['selection_callback']) + self.set_double_click_callback(kwdict['double_click_callback']) + del kwdict['selection_callback'] + del kwdict['double_click_callback'] + self.setup_icons() + + Tree.__init__(self, *args, **kwdict) + self.save_view() + self.save_selection() + self.bind('<4>', self.prev) + self.bind('<5>', self.next) + + def setup_icons(self): + iconpath = py.magic.autopath().dirpath('icons') + self.icon_green = PhotoImage(file = str(iconpath.join('green.gif'))) + self.icon_red = PhotoImage(file = str(iconpath.join('red.gif'))) + self.icon_yellow = PhotoImage(file = str(iconpath.join('yellow.gif'))) + self.icon_gray = PhotoImage(file = str(iconpath.join('gray.gif'))) + + self.icon_map = {str(Status.NotExecuted()): self.icon_gray, + str(Status.Passed()): self.icon_green, + str(Status.Failed()): self.icon_red, + str(Status.Skipped()): self.icon_yellow, + str(Status.ExceptionFailure()): self.icon_red} + + def get_icon(self, status): + status = str(status) + return self.icon_map[status] + + def add_node(self, *args, **kwargs): + report = kwargs.setdefault('report', TestReport()) + icon = self.get_icon(report.status) + kwdict = {'collapsed_icon': icon, 'expanded_icon': icon} + del kwargs['report'] + kwdict.update(kwargs) + Tree.add_node(self, *args, **kwdict) + + def set_selection_callback(self, callback): + self._selection_callback = callback + + def set_double_click_callback(self, callback): + self._double_click_callback = callback + + def double_click_callback(self, node = None): + self._double_click_callback(self.pos) + + def save_view(self): + self.saved_view = self.get_expanded_state() + + def save_selection(self): + self.saved_selection_id = self.cursor_node(None).full_id() + + def restore_view(self): + self.apply_expanded_state(self.saved_view) + + def restore_selection(self): + if self.find_full_id(self.saved_selection_id): + self.move_cursor(self.find_full_id(self.saved_selection_id)) + + def next(self, event = None): + Tree.next(self, event) + self.save_selection() + + def prev(self, event = None): + Tree.prev(self, event) + self.save_selection() + + + def move_cursor(self, node): + Tree.move_cursor(self, node) + self._selection_callback(node) + + def listnodes(self, startnode = None): + if startnode is None: + startnode = self.root + ret = [startnode] + for child in startnode.children(): + ret.append(child) + ret.extend(self.listnodes(child)) + return ret + + + def get_expanded_state(self, key = None): + "returns [node.full_id() for all_nodes if node.expanded() == True" + if key is None: + key = self.root.full_id() + if not self.find_full_id(key) or not self.find_full_id(key).expanded(): + return [] + startnode = self.find_full_id(key) + return [node.full_id() for node in self.listnodes(startnode) if node.expanded()] + + def apply_expanded_state(self, id_list): + ids = [item for item in id_list] + ids.sort() + for id in ids: + node = self.find_full_id(id) + if node is None or not node.expandable(): + continue + node.expand() + pass + + def get_parent(self, node_id): + node = self.find_full_id(node_id) + if node: + return node.parent() + return self.find_full_id(node_id[:-1]) + + def update_root(self, report): + self.root.set_collapsed_icon(self.get_icon(report.status)) + self.root.set_expanded_icon(self.get_icon(report.status)) + self.root.set_label(report.label) + + def update_node(self, node_id): + if node_id == self.root.full_id() or self.get_parent(node_id) is None: + parent = self.root + else: + parent = self.get_parent(node_id) + + if not parent.expandable(): + self.update_node(parent.full_id()) + return + + if parent.expanded(): + parent.toggle_state() + parent.toggle_state() + self.restore_view() + self.restore_selection() + + + +class ResultNode(Node): + + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + Node.__init__(self, *args, **kwargs) + self.widget.tag_bind(self.symbol, '', + self.PVT_click_select, add='+') + self.widget.tag_bind(self.label, '', + self.PVT_click_select, add='+') + self.widget.tag_bind(self.symbol, '', + self.PVT_double_click, add='+') + self.widget.tag_bind(self.label, '', + self.PVT_double_click, add='+') + + + + def get_node_args(self): + return self.args, self.kwargs + + def PVT_click(self, event): + Node.PVT_click(self, event) + self.widget.save_selection() + self.widget.save_view() + + def PVT_click_select(self, event): + # not interested in dnd, so just do a select + self.widget.move_cursor(self) + self.widget.save_selection() + + def PVT_double_click(self, event): + self.widget.double_click_callback(self) + + + Added: py/branch/py-collect/test/tkinter/guidriver.py ============================================================================== --- (empty file) +++ py/branch/py-collect/test/tkinter/guidriver.py Mon Apr 4 17:32:41 2005 @@ -0,0 +1,100 @@ + +import py +from utils import Status, TestReport, Null +from py.__impl__.test.drive import Exit, exit, SimpleOutErrCapture + +import pprint + +class GuiDriver(py.test.Driver): + + def __init__(self, option = None, channel = None): + super(GuiDriver, self).__init__(option) + self.channel = channel + self.reportlist = [] + + def info(self, **kwargs): + return + print '-' * 60 + #print 'reportlist:', self.reportlist + for key, value in kwargs.items(): + print key,':', pprint.pformat(value) + if isinstance(kwargs.get('res'), (py.test.Item.Passed, + py.test.Item.Skipped, + py.test.Item.Failed)): + print '!' *80 + print 'py.test.Collector.Outcome found' + print '!' *80 + print '-' * 60 + + def header(self, colitems): + super(GuiDriver, self).header(colitems) + report = TestReport() + report.settime() + self.reportlist = [report] + self.sendreport(report) + + def footer(self, colitems): + super(GuiDriver, self).footer(colitems) + report = self.reportlist.pop() + report.settime() + self.sendreport(report) + self.channel.send(None) + + def start(self, colitem): + super(GuiDriver, self).start(colitem) + report = TestReport() + report.start(colitem) + self.reportlist.append(report) + self.sendreport(report) + + + def finish(self, colitem, res): + super(GuiDriver, self).finish(colitem, res) + report = self.reportlist.pop() + report.finish(colitem, res, self.option) + self.reportlist[-1].status.update(report.status) + self.sendreport(report) + #py.std.time.sleep(0.5) + + def sendreport(self, report): + self.channel.send(report.toChannel()) + + def warning(self, msg): + pass + + # hack! Driver.runone should be patched + def runone(self, colitem): + if self.shouldclose(): + raise SystemExit, "received external close signal" + + capture = None + if not self.option.nocapture and isinstance(colitem, py.test.Item): + capture = SimpleOutErrCapture() + res = None + try: + self.start(colitem) + try: + try: + res = self.runinner(colitem) + except (KeyboardInterrupt, Exit): + res = None + raise + except colitem.Outcome, res: + res.excinfo = py.code.ExceptionInfo() + except: + excinfo = py.code.ExceptionInfo() + res = colitem.Failed(excinfo=excinfo) + else: + assert (res is None or + isinstance(res, (list, colitem.Outcome))) + finally: + if capture is not None: + out, err = capture.reset() + try: + res.out, res.err = out, err + except AttributeError: + pass + self.finish(colitem, res) + finally: + pass + Added: py/branch/py-collect/test/tkinter/icons/gray.gif ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/green.gif ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/green.png ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/green_aqua.gif ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/green_aqua.png ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/green_s.gif ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/green_s.png ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/green_s_aqua.gif ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/green_s_aqua.png ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/red.gif ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/red.png ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/red_aqua.gif ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/red_aqua.png ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/red_c.gif ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/red_c.png ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/red_c_aqua.gif ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/red_c_aqua.png ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/yellow.gif ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/yellow.png ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/yellow_aqua.gif ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/yellow_aqua.png ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/yellow_s.gif ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/icons/yellow_s.png ============================================================================== Binary file. No diff available. Added: py/branch/py-collect/test/tkinter/repository.py ============================================================================== --- (empty file) +++ py/branch/py-collect/test/tkinter/repository.py Mon Apr 4 17:32:41 2005 @@ -0,0 +1,170 @@ +import py +Item = py.test.Item +Collector = py.test.Collector + + + +import copy +import time + +from utils import Null +from itertools import izip, count + +import UserDict + +## import logging +## log = logging +## def initlog(): +## global log +## remote = logging.FileHandler('./remote.log') +## remote.setLevel(logging.DEBUG) +## formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') +## remote.setFormatter(formatter) +## log = logging.getLogger('remote') +## log.addHandler(remote) + + + +class Repository(object): + nothing = object() + + def __init__(self): + self.root = self.newnode() + + def newnode(self): + return [self.nothing, OrderedDictMemo()] + + def copy(self): + newrepos = Repository() + newrepos.root = copy.deepcopy(self.root) + return newrepos + + def add(self, key, value): + node = self.root + for k in key: + node = node[1].setdefault(k, self.newnode()) + node[0] = value + + def find_tuple(self, key=[]): + node = self.root + for k in key: + node = node[1][k] + return node + + def find(self, key=[]): + return self.find_tuple(key)[0] + + def haskey(self, key): + try: + value = self.find(key) + except KeyError: + return False + return True + + def haskeyandvalue(self, key): + if self.haskey(key): + value = self.find(key) + return value is not self.nothing + return False + + def find_children(self, key=[]): + if self.haskey(key): + node = self.find_tuple(key) + return [list(key) + [childname] for childname in node[1].keys()] + return [] + + def keys(self, startkey=[]): + ret = [] + for key in self.find_children(startkey): + ret.append(key) + ret.extend(self.keys(key)) + return ret + + def removestalekeys(self, key): + if self.find_children(key) == [] and not self.haskeyandvalue(key): + if len(key) > 0: + parent = self.find_tuple(key[:-1]) + del parent[1][key[-1]] + self.removestalekeys(key[:-1]) + + + def delete(self, key): + if self.haskeyandvalue(key): + node = self.find_tuple(key) + node[0] = self.newnode()[0] + self.removestalekeys(key) + + def delete_all(self, key): + if self.haskeyandvalue(key): + node = self.find_tuple(key) + node[0], node[1] = self.newnode()[0], self.newnode()[1] + self.removestalekeys(key) + + def values(self, startkey=[]): + return [self.find(key) for key in self.keys(startkey)] + + def items(self, startkey=[]): + return [(key, self.find(key)) for key in self.keys(startkey)] + + +class OrderedDict(UserDict.DictMixin): + + def __init__(self, *args, **kwargs): + self._dict = dict(*args, **kwargs) + self._keys = self._dict.keys() + + def __getitem__(self, key): + return self._dict.__getitem__(key) + + def __setitem__(self, key, value): + self._dict.__setitem__(key, value) + try: + self._keys.remove(key) + except ValueError: + pass + self._keys.append(key) + + def __delitem__(self, key): + self._dict.__delitem__(key) + self._keys.remove(key) + + def keys(self): + return self._keys[:] + + def copy(self): + new = OrderedDict() + for key, value in self.iteritems(): + new[key] = value + return new + +class OrderedDictMemo(UserDict.DictMixin): + + def __init__(self, *args, **kwargs): + self._dict = dict(*args, **kwargs) + self._keys = self._dict.keys() + + def __getitem__(self, key): + return self._dict.__getitem__(key) + + def __setitem__(self, key, value): + self._dict.__setitem__(key, value) + if key not in self._keys: + self._keys.append(key) + + def __delitem__(self, key): + self._dict.__delitem__(key) + + def keys(self): + return [key for key in self._keys if key in self._dict] + + def copy(self): + new = OrderedDict() + for key, value in self.iteritems(): + new[key] = value + return new + + + + + + Added: py/branch/py-collect/test/tkinter/test_exceptionfailure.py ============================================================================== --- (empty file) +++ py/branch/py-collect/test/tkinter/test_exceptionfailure.py Mon Apr 4 17:32:41 2005 @@ -0,0 +1,7 @@ + +import time + +import py + +def test_exceptionfailure(): + py.test.raises(TypeError, 'time.time()') Added: py/branch/py-collect/test/tkinter/test_guidriver.py ============================================================================== --- (empty file) +++ py/branch/py-collect/test/tkinter/test_guidriver.py Mon Apr 4 17:32:41 2005 @@ -0,0 +1,63 @@ + +import py +import guidriver +GuiDriver = guidriver.GuiDriver + +from utils import Status, TestReport, Null + +class TestGuiDriver: + + class ChannelMock: + + def __init__(self, receinvelist = []): + self.reset(receinvelist) + + def reset(self, receivelist = []): + self.receivelist = receivelist + self.sendlist = [] + + def send(self, obj): + self.sendlist.append(obj) + + def receive(self): + return self.receivelist.pop(0) + + def setup_method(self, method): + self.channel = self.ChannelMock() + self.driver = GuiDriver(Null(), self.channel) + self.collitems = [Null(), Null()] + + def test_header_sends_report_with_id_root(self): + self.driver.header(self.collitems) + + assert self.channel.sendlist != [] + report = TestReport.fromChannel(self.channel.sendlist[0]) + assert report.status == Status.NotExecuted() + assert report.id == 'Root' + assert report.label == 'Root' + + def test_footer_sends_report_and_None(self): + self.driver.header(self.collitems) + self.driver.footer(self.collitems) + + assert self.channel.sendlist != [] + assert self.channel.sendlist[-1] is None + report = TestReport.fromChannel(self.channel.sendlist[-2]) + assert report.status == Status.NotExecuted() + assert report.id == 'Root' + +## def test_status_is_passed_to_root(self): +## self.driver.header(self.collitems) +## self.driver.start(self.collitems[0]) +## self.driver.finish(self.collitems[0], py.test.Collector.Failed()) +## self.driver.footer(self.collitems) + +## assert self.channel.sendlist[-1] is None +## assert self.channel.sendlist.pop() is None + +## report = TestReport.fromChannel(self.channel.sendlist[-1]) +## assert report.name == 'Root' +## assert report.status == Status.Failed() + + + Added: py/branch/py-collect/test/tkinter/test_repository.py ============================================================================== --- (empty file) +++ py/branch/py-collect/test/tkinter/test_repository.py Mon Apr 4 17:32:41 2005 @@ -0,0 +1,170 @@ + +from repository import Repository, OrderedDict, OrderedDictMemo +import py +Item = py.test.Item + + +import itertools + + +class TestRepository: + + def setup_method(self, method): + self.rep = Repository() + + def test_add_find_single_value(self): + key = ['key'] + value = 'value' + self.rep.add(key, value) + assert self.rep.find(key) == value + + def test_add_works_like_update(self): + key = 'k e y'.split() + value = 'value' + value2 = 'value2' + self.rep.add(key, value) + self.rep.add(key, value2) + assert self.rep.find(key) == value2 + + def test_haskeyandvalue(self): + key = 'first_middle_last' + value = 'value' + self.rep.add(key, value) + assert self.rep.haskeyandvalue(key) + assert not self.rep.haskeyandvalue('first') + for index in range(1, len(key[0])): + assert not self.rep.haskeyandvalue(key[0:index]) + + def test_add_find_subkey(self): + key = ('key', 'subkey') + value = 'subvalue' + self.rep.add(key, value) + self.rep.add((key[0],), 'value') + assert self.rep.find(key) == value + + def test_find_raises_KeyError(self): + py.test.raises(KeyError, self.rep.find, 'nothing') + + def test_haskey(self): + self.rep.add('key', 'value') + assert self.rep.haskey('key') == True + assert self.rep.haskey('katja') == False + assert self.rep.haskey('ke') == True + + def test_find_children_empyt_repository(self): + assert self.rep.find_children() == [] + + def test_find_children(self): + self.rep.add(['c'], 'childvalue') + self.rep.add('c a'.split(), 'a') + self.rep.add('c b'.split(), 'b') + assert self.rep.find_children(['c']) == [ ['c','a'], ['c','b']] + assert self.rep.find_children() == [['c']] + + def test_find_children_with_tuple_key(self): + key = tuple('k e y'.split()) + value = 'value' + self.rep.add(key, value) + assert self.rep.find_children([]) == [['k']] + assert self.rep.find_children(('k', 'e')) == [['k', 'e', 'y']] + + def test_keys(self): + keys = [ 'a b c'.split(), 'a b'.split(), ['a']] + for key in keys: + self.rep.add(key, 'value') + assert len(keys) == len(self.rep.keys()) + for key in self.rep.keys(): + assert key in keys + for key in keys: + assert key in self.rep.keys() + + def test_delete_simple(self): + key = 'k' + value = 'value' + self.rep.add(key, value) + self.rep.delete(key) + assert self.rep.haskeyandvalue(key) == False + + + def test_removestallkeys_remove_all(self): + key = 'k e y'.split() + value = 'value' + self.rep.add(key, value) + node = self.rep.find_tuple(key) + node[0] = self.rep.newnode()[0] + self.rep.removestalekeys(key) + assert self.rep.keys() == [] + + def test_removestallkeys_dont_remove_parent(self): + key = 'k e y'.split() + key2 = 'k e y 2'.split() + value = 'value' + self.rep.add(key, value) + self.rep.add(key2, self.rep.newnode()[0]) + self.rep.removestalekeys(key2) + assert self.rep.haskey(key2) == False + assert self.rep.haskeyandvalue(key) + + def test_removestallkeys_works_with_parameter_root(self): + self.rep.removestalekeys([]) + + def test_copy(self): + key = 'k e y'.split() + key2 = 'k e y 2'.split() + value = 'value' + self.rep.add(key, value) + self.rep.add(key2, value) + newrep = self.rep.copy() + assert newrep.root is not self.rep.root + assert newrep.find(key) == self.rep.find(key) + + + +class TestOrderedDict: + + def setup_method(self, method): + self.dict = OrderedDict() + + def test_add(self): + self.dict['key'] = 'value' + assert 'key' in self.dict + + def test_order(self): + keys = range(3) + for k in keys: + self.dict[k] = str(k) + assert keys == self.dict.keys() + +class TestOrderedDictMemo(TestOrderedDict): + + def setup_method(self, method): + self.dict = OrderedDictMemo() + + def test_insert(self): + self.dict['key1'] = 1 + self.dict['key2'] = 2 + del self.dict['key1'] + self.dict['key1'] = 1 + assert self.dict.keys() == ['key1', 'key2'] + + +class Test_Failing: + """ will fail!!""" + + def test_fail(self): + print 'recorded stdout' + m = dict(((1,2),(3,4))) + l = (1,m) + assert False == l + + def test_calling_fail(self): + py.test.fail('Try to fail') + + def test_skip(self): + print 'recorded stdout' + py.test.skip('Try to skip this test') + + def test_skip_on_error(self): + py.test.skip_on_error(''.index, 'hello') + + Added: py/branch/py-collect/test/tkinter/test_utils.py ============================================================================== --- (empty file) +++ py/branch/py-collect/test/tkinter/test_utils.py Mon Apr 4 17:32:41 2005 @@ -0,0 +1,118 @@ + +import utils +from utils import Status, TestReport, OutBuffer +import py +Item = py.test.Item + +class TestStatus: + + def test_init_with_None(self): + status = Status(None) + assert status == status.NotExecuted() + + def test_str(self): + status = Status(Item.Passed()) + assert status == status.Passed() + + status = Status(Item.Failed()) + assert status == status.Failed() + + status = Status(Item.Skipped()) + assert status == status.Skipped() + + status = Status(Item.ExceptionFailure()) + assert status == status.ExceptionFailure() + + + def test_init_with_bad_name(self): + status = Status('nothing') + assert status == Status.NotExecuted() + + def test_init_with_good_name(self): + def check_str(obj, expected): + assert str(obj) == expected + + for name in Status.ordered_list: + yield check_str, Status(name), name + + def test_update(self): + failed = Status.Failed() + passed = Status.Passed() + failed.update(passed) + assert failed == Status.Failed() + + passed.update(failed) + assert passed == Status.Failed() + assert passed == failed + + def test_eq_(self): + passed = Status.Passed() + assert passed == passed + assert passed == Status.Passed() + + failed = Status.Failed() + assert failed != passed + + +class TestTestReport: + + def setup_method(self, method): + self.path = py.path.local() + self.collector = py.test.Directory(self.path) + self.testresult = TestReport() + + def test_start(self): + self.testresult.start(self.collector) + + assert self.testresult.full_id == self.collector.listnames() + assert self.testresult.time != 0 + assert self.testresult.status == Status.NotExecuted() + + def test_finish(self): + self.testresult.start(self.collector) + + py.std.time.sleep(1.1) + + self.testresult.finish(self.collector, None) + assert self.testresult.time > 1 + assert self.testresult.status == Status.NotExecuted() + +## def test_finish_failed(self): +## self.testresult.start(self.collector) + +## self.testresult.finish(self.collector, py.test.Collector.Failed()) +## assert self.testresult.status == Status.Failed() + + + + def test_toChannel_fromChannel(self): + assert isinstance(self.testresult.toChannel()['status'], str) + result = TestReport.fromChannel(self.testresult.toChannel()) + assert isinstance(result.status, Status) + + def test_copy(self): + result2 = self.testresult.copy() + assert self.testresult.status == Status.NotExecuted() + for key in TestReport.template.keys(): + assert getattr(result2, key) == getattr(self.testresult, key) + + self.testresult.status = Status.Failed() + assert result2.status != self.testresult.status + + +class Test_OutBuffer: + + def setup_method(self, method): + self.out = OutBuffer() + + def test_line(self): + oneline = 'oneline' + self.out.line(oneline) + assert self.out.getoutput() == oneline + '\n' + + def test_write(self): + item = 'item' + self.out.write(item) + assert self.out.getoutput() == item + + Added: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- (empty file) +++ py/branch/py-collect/test/tkinter/tkgui.py Mon Apr 4 17:32:41 2005 @@ -0,0 +1,366 @@ +import Tkinter +tk = Tkinter +import ScrolledText +import time +import threading +import Queue +import sys +import os +import re +import sha + +import py +from py.test import Driver +Item = py.test.Item +Collector = py.test.Collector + +import repository +import utils +from utils import TestReport, Status, Null +import gui + + +class TkMain: + def __init__(self, parent, queue, endcommand, startcommand, repositorycommand, manager = Null()): + self.parent = parent + self.queue = queue + self.endcommand = endcommand + self.startcommand = startcommand + self.repositorycommand = repositorycommand + self.manager = manager + # GUI + self.text = Null() + self.createwidgets() + start = Tkinter.Button(parent, text='Start', command=startcommand) + start.pack(side = tk.TOP) + done = Tkinter.Button(parent, text='Done', command=endcommand) + done.pack(side = tk.TOP) + + + def createwidgets(self): + self.mainframe = tk.Frame(self.parent) + self.treeframe = tk.Frame(self.mainframe) + self.tree = gui.ResultTree(master=self.treeframe, root_id='Root', + get_contents_callback=self.create_new_nodes, + width=400, node_class = gui.ResultNode, + selection_callback=self.selection_callback, + double_click_callback = self.double_click_callback) + sb=tk.Scrollbar(self.treeframe) + sb.pack(side=tk.RIGHT, fill = tk.Y) + self.tree.configure(yscrollcommand=sb.set) + sb.configure(command=self.tree.yview) + + sb=tk.Scrollbar(self.treeframe, orient=tk.HORIZONTAL) + sb.pack(side=tk.BOTTOM, fill = tk.X) + self.tree.configure(xscrollcommand=sb.set) + sb.configure(command=self.tree.xview) + + self.tree.pack(side = tk.LEFT, expand=tk.YES, fill = tk.BOTH) + self.treeframe.pack(side = tk.TOP, expand = tk.YES, fill = tk.BOTH) + + + self.text = ScrolledText.ScrolledText(self.mainframe) + self.text.pack(side = tk.TOP, expand=tk.YES, fill = tk.BOTH) + self.mainframe.pack(side = tk.LEFT, expand = tk.YES, fill = tk.BOTH) + + def double_click_callback(self, node): + self.startcommand(rerun_ids=[node.full_id()]) + + def selection_callback(self, node): + # check if node exists + if not self.repositorycommand().haskey(node.full_id()): + return + report = self.repositorycommand().find(node.full_id()) + self.text['state'] = tk.NORMAL + self.text.delete(1.0, tk.END) + self.text.tag_config('sel', relief=tk.FLAT) + self.text.insert(tk.END, report.error_report) + #if len(report.error_report.splitlines()) < 20: + # self.text.config(height=len(report.error_report.splitlines()) + 5) + #self.text.yview_pickplace(tk.END) + self.attacheditorhotspots(self.text) + self.text['state'] = tk.DISABLED + pass + + def attacheditorhotspots(self, text): + # Attach clickable regions to a Text widget. + filelink = re.compile(r"""\[(?:testcode\s*:)?\s*(.+):(\d+)\]""") + skippedlink = re.compile(r"""in\s+(/.*):(\d+)\s+""") + lines = text.get('1.0', tk.END).splitlines(1) + if not lines: + return + tagname = '' + start, end = 0,0 + for index, line in enumerate(lines): + match = filelink.search(line) + if match is None: + match = skippedlink.search(line) + if match is None: + continue + file, line = match.group(1, 2) + start, end = match.span() + tagname = "ref%d" % index + text.tag_add(tagname, + "%d.%d" % (index + 1, start), + "%d.%d" % (index + 1, end)) + text.tag_bind(tagname, "", + lambda e, n=tagname: + e.widget.tag_config(n, underline=1)) + text.tag_bind(tagname, "", + lambda e, n=tagname: + e.widget.tag_config(n, underline=0)) + text.tag_bind(tagname, "", + lambda e, self=self, f=file, l=line: + self.launch_editor(f, l)) + + def launch_editor(self, file, line): + editor = (py.std.os.environ.get('PYUNIT_EDITOR', None) or + py.std.os.environ.get('EDITOR_REMOTE', None) or + os.environ.get('EDITOR', None) or "emacsclient --no-wait ") + #"emacsclient --no-wait ") + if editor: + print "%s +%s %s" % (editor, line, file) + py.process.cmdexec('%s +%s %s' % (editor, line, file)) + + + def create_new_nodes(self, node): + repos = self.repositorycommand() + id = node.full_id() + for child in repos.find_children(id): + if not repos.haskeyandvalue(child): + continue + report = repos.find(child) + name = report.label + folder = 0 + if repos.find_children(child): + folder = 1 + self.tree.add_node(name=name, id=report.id, flag=folder, report = report) + + def display_action(self, action): + self.parent.title(str(action)) + + def process_messages(self, manager): + """ + Handle all the messages currently in the queue (if any). + """ + while self.queue.qsize(): + try: + full_id = self.queue.get(0) + if full_id is None: + root_status = self.repositorycommand().find(self.tree.root.full_id()).status + self.display_action(root_status) + if root_status != Status.Passed(): + self.parent.bell() + py.std.time.sleep(0.2) + self.parent.bell() + else: + self.tree.update_node(full_id) + self.display_action('running: ' + str(full_id[-1]) ) + except Queue.Empty: + pass + self.tree.update_root(self.repositorycommand().find(self.tree.root.full_id())) + + +class Manager: + """ + Launch the main part of the GUI and the worker thread. periodicCall and + endApplication could reside in the GUI part, but putting them here + means that you have all the thread controls in a single place. + """ + def __init__(self, parent): + """ + Start the GUI and the asynchronous threads. We are in the main + (original) thread of the application, which will later be used by + the GUI. + """ + self.parent = parent + + self.reset_repository() + self.args = py.std.sys.argv[1:] + self.paths = py.test.config.init(self.args)[1] + self.testfilewatcher = utils.TestFileWatcher(*self.paths) + # Create the queue + self.guiqueue = Queue.Queue() + self.reporterqueue = Queue.Queue() + + self.should_stop = False + + # Set up the GUI part + self.gui = TkMain(parent, self.guiqueue, self.endApplication, self.start_tests, self.getrepository, manager = self) + + # Set up the thread to do asynchronous I/O + self.thread = Null() + # Start the periodic call in the GUI to check if the queue contains + # anything + self.check_messages() + + def reset_repository(self, ids = []): + if not ids: + self.repository = self.get_new_repository() + return + for id in ids: + self.repository.delete_all(id) + for key, value in self.get_new_repository().items(): + if not self.repository.haskey(key): + self.repository.add(key, value) + + + def get_new_repository(self): + new_repository = repository.Repository() + template = TestReport() + new_repository.add(['Root'], template.copy()) + for name in [str(x) for x in (Status.Failed(), + Status.Skipped(), + Status.ExceptionFailure())]: + new_repository.add(['Root', name], template.copy(id = name, label= name)) + return new_repository + + + def thread_is_alive(self): + return self.thread.isAlive() == True + running = property(thread_is_alive) + + + def getrepository(self): + return self.repository + + + def check_messages(self): + """ + Check every 200 ms if there is something new in the queue. + """ + while self.reporterqueue.qsize(): + try: + report_fromChannel = self.reporterqueue.get(0) + if report_fromChannel is None: + self.guiqueue.put(None) + else: + report = TestReport.fromChannel(report_fromChannel) + root_id = TestReport().full_id + # keep root up-to-date + self.repository.find(root_id).status.update(report.status) + if report.full_id == root_id: + # don't overwrite overall status on partial run + report.status = Status(self.repository.find(root_id).status) + else: + # hack: prefix Root for all Tests, except Root + report.full_id = ['Root'] + report.full_id + id = report.full_id + self.repository.add(id, report) + added_name = self.add_status(report) + if added_name is not None: + self.guiqueue.put(added_name) + self.guiqueue.put(id) + except Queue.Empty: + pass + + self.gui.process_messages(self) + if self.should_stop: + # This is the brutal stop of the system. You may want to do + # some cleanup before actually shutting it down. + import sys + sys.exit(1) + if not self.running and self.testfilewatcher.changed(): + self.start_tests() + self.parent.after(200, self.check_messages) + + def add_status(self, newreport): + report = newreport.copy() + status = report.status + if report.error_report: + folder = ('Root', str(report.status)) + # unique id, for failing tests with the same name + report.id = report.id + str(report.full_id) + id = folder + (report.id,) + self.repository.find(folder).status.update(report.status) + self.repository.add(id, report) + return id + + def endApplication(self): + self.should_stop = True + #print 'System is shutting down now!' + #print 'join thread %s' % self.thread + self.thread.join() + #print 'Thread is down' + + + def get_restart_params_list(self, id): + repos = self.getrepository() + if repos.find(id).restart_params is not None: + return [repos.find(id).restart_params] + params = [] + for child_id in repos.find_children(id): + params.extend(self.get_restart_params_list(child_id)) + return params + + def start_tests(self, filenames = [], rerun_ids = []): + if self.running: + return + rerun_cols = [] + for id in rerun_ids: + rerun_cols.extend(self.get_restart_params_list(id)) + + self.reset_repository(rerun_ids) + if filenames == []: + filenames = [str(p) for p in self.paths] + kwargs = {'rerun_cols': rerun_cols} + if rerun_cols == []: + kwargs['rerun_cols'] = [(filename,[]) for filename in filenames] + + self.thread = threading.Thread(target=self.run_tests_threaded, + kwargs = kwargs) + self.thread.start() + + + def run_tests_threaded(self, rerun_cols = None): + gw = py.execnet.PopenGateway(sys.executable) + try: + channel = gw.remote_exec(""" + import py + from py.__impl__.test.tkinter.guidriver import GuiDriver + from py.__impl__.test.tkinter import repository + import os + import time + from py.__impl__.test.run import FailureCollector + + rerun_cols, args = channel.receive() + col = FailureCollector(rerun_cols) + # hack!! + frontenddriver, paths = py.test.config.init(args) + driver = GuiDriver(option = frontenddriver.option, channel = channel) + driver.run(col) + """) + channel.send((rerun_cols, self.args)) + self.receive_all_data(channel) + self.channel_waitclose(channel) + finally: + channel.close() + channel.gateway.exit() + + def receive_all_data(self, channel): + while True: + data = channel.receive() + self.reporterqueue.put(data) + if data is None: + break + + + def channel_waitclose(self, channel): + while 1: + try: + channel.waitclose(0.1) + except (IOError, py.error.Error): + continue + break + +def main(): + root = Tkinter.Tk() + + client = Manager(root) + root.protocol('WM_DELETE_WINDOW', client.endApplication) + root.mainloop() + + +if __name__ == '__main__': + main() + Added: py/branch/py-collect/test/tkinter/tktree.py ============================================================================== --- (empty file) +++ py/branch/py-collect/test/tkinter/tktree.py Mon Apr 4 17:32:41 2005 @@ -0,0 +1,911 @@ +# Highly optimized Tkinter tree control +# by Charles E. "Gene" Cash +# +# This is documented more fully on my homepage at +# http://home.cfl.rr.com/genecash/ and if it's not there, look in the Vaults +# of Parnassus at http://www.vex.net/parnassus/ which I promise to keep +# updated. +# +# Thanks to Laurent Claustre for sending lots of helpful +# bug reports. +# +# This copyright license is intended to be similar to the FreeBSD license. +# +# Copyright 1998 Gene Cash All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the +# distribution. +# +# THIS SOFTWARE IS PROVIDED BY GENE CASH ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# This means you may do anything you want with this code, except claim you +# wrote it. Also, if it breaks you get to keep both pieces. +# +# 02-DEC-98 Started writing code. +# 22-NOV-99 Changed garbage collection to a better algorithm. +# 28-AUG-01 Added logic to deal with exceptions in user callbacks. +# 02-SEP-01 Fixed hang when closing last node. +# 07-SEP-01 Added binding tracking so nodes got garbage-collected. +# Also fixed subclass call to initialize Canvas to properly deal +# with variable arguments and keyword arguments. +# 11-SEP-01 Bugfix for unbinding code. +# 13-OCT-01 Added delete & insert methods for nodes (by email request). +# LOTS of code cleanup. +# Changed leading double underscores to PVT nomenclature. +# Added ability to pass Node subclass to Tree constructor. +# Removed after_callback since subclassing Node is better idea. +# 15-OCT-01 Finally added drag'n'drop support. It consisted of a simple +# change to the Node PVT_click method, and addition of logic like +# the example in Tkdnd.py. It took 3 days to grok the Tkdnd +# example and 2 hours to make the code changes. Plus another 1/2 +# day to get a working where() function. +# 16-OCT-01 Incorporated fixes to delete() and dnd_commit() bugs by +# Laurent Claustre . +# 17-OCT-01 Added find_full_id() and cursor_node() methods. +# 18-OCT-01 Fixes to delete() on root during collapse and with +# drag-in-progress flag by Laurent Claustre . +# 10-FEB-02 Fix to prev_visible() by Nicolas Pascal . +# Fixes which made insert_before()/insert_after() actually work. +# Also added expand/collapse indicators like Internet Explorer +# as requested by Nicolas. +# 11-FEB-02 Another fix to prev_visible(). It works this time. Honest. +# 31-MAY-02 Added documentation strings so the new PYthon 2.2 help function +# is a little more useful. +# 19-AUG-02 Minor fix to eliminate crash in "treedemo-icons.py" caused by +# referencing expand/collapse indicators when lines are turned off. +# 15-OCT-02 Used new idiom for calling Canvas superclass. +# 18-NOV-02 Fixed bug discovered by Amanjit Gill , where +# I didn't pass "master" properly to the Canvas superclass. Sigh. +# One step forward, one step back. + +import Tkdnd +from Tkinter import * + +#------------------------------------------------------------------------------ +def report_callback_exception(): + """report exception on sys.stderr.""" + import traceback + import sys + + sys.stderr.write("Exception in Tree control callback\n") + traceback.print_exc() + +#------------------------------------------------------------------------------ +class Struct: + """Helper object for add_node() method""" + def __init__(self): + pass + +#------------------------------------------------------------------------------ +class Node: + """Tree helper class that's instantiated for each element in the tree. It + has several useful attributes: + parent_node - immediate parent node + id - id assigned at creation + expanded_icon - image displayed when folder is expanded to display + children + collapsed_icon - image displayed when node is not a folder or folder is + collapsed. + parent_widget - reference to tree widget that contains node. + expandable_flag - is true when node is a folder that may be expanded or + collapsed. + expanded_flag - true to indicate node is currently expanded. + h_line - canvas line to left of node image. + v_line - canvas line below node image that connects children. + indic - expand/collapse canvas image. + label - canvas text label + symbol - current canvas image + + Please note that methods prefixed PVT_* are not meant to be used by + client programs.""" + + def __init__(self, parent_node, id, collapsed_icon, x, y, + parent_widget=None, expanded_icon=None, label=None, + expandable_flag=0): + """Create node and initialize it. This also displays the node at the + given position on the canvas, and binds mouseclicks.""" + # immediate parent node + self.parent_node=parent_node + # internal name used to manipulate things + self.id=id + # bitmaps to be displayed + self.expanded_icon=expanded_icon + self.collapsed_icon=collapsed_icon + # tree widget we belong to + if parent_widget: + self.widget=parent_widget + else: + self.widget=parent_node.widget + # for speed + sw=self.widget + # our list of child nodes + self.child_nodes=[] + # flag that node can be expanded + self.expandable_flag=expandable_flag + self.expanded_flag=0 + # add line + if parent_node and sw.line_flag: + self.h_line=sw.create_line(x, y, x-sw.dist_x, y) + else: + self.h_line=None + self.v_line=None + # draw approprate image + self.symbol=sw.create_image(x, y, image=self.collapsed_icon) + # add expand/collapse indicator + self.indic=None + if expandable_flag and sw.line_flag and sw.plus_icon and sw.minus_icon: + self.indic=sw.create_image(x-sw.dist_x, y, image=sw.plus_icon) + # add label + self.label=sw.create_text(x+sw.text_offset, y, text=label, anchor='w') + # single-click to expand/collapse + if self.indic: + sw.tag_bind(self.indic, '<1>', self.PVT_click) + else: + sw.tag_bind(self.symbol, '<1>', self.PVT_click) + ## Modification: symbol always clickable + #sw.tag_bind(self.symbol, '<1>', self.PVT_click) + + # for drag'n'drop target detection + sw.tag_bind(self.symbol, '', self.PVT_enter) + sw.tag_bind(self.label, '', self.PVT_enter) + + # for testing (gotta make sure nodes get properly GC'ed) + #def __del__(self): + # print self.full_id(), 'deleted' + + # ----- PUBLIC METHODS ----- + def set_collapsed_icon(self, icon): + """Set node's collapsed image""" + self.collapsed_icon=icon + if not self.expanded_flag: + self.widget.itemconfig(self.symbol, image=icon) + + def set_expanded_icon(self, icon): + """Set node's expanded image""" + self.expanded_icon=icon + if self.expanded_flag: + self.widget.itemconfig(self.symbol, image=icon) + + def parent(self): + """Return node's parent node""" + return self.parent_node + + def prev_sib(self): + """Return node's previous sibling (the child immediately above it)""" + i=self.parent_node.child_nodes.index(self)-1 + if i >= 0: + return self.parent_node.child_nodes[i] + else: + return None + + def next_sib(self): + """Return node's next sibling (the child immediately below it)""" + i=self.parent_node.child_nodes.index(self)+1 + if i < len(self.parent_node.child_nodes): + return self.parent_node.child_nodes[i] + else: + return None + + def next_visible(self): + """Return next lower visible node""" + n=self + if n.child_nodes: + # if you can go right, do so + return n.child_nodes[0] + while n.parent_node: + # move to next sibling + i=n.parent_node.child_nodes.index(n)+1 + if i < len(n.parent_node.child_nodes): + return n.parent_node.child_nodes[i] + # if no siblings, move to parent's sibling + n=n.parent_node + # we're at bottom + return self + + def prev_visible(self): + """Return next higher visible node""" + n=self + if n.parent_node: + i=n.parent_node.child_nodes.index(n)-1 + if i < 0: + return n.parent_node + else: + j=n.parent_node.child_nodes[i] + return j.PVT_last() + else: + return n + + def children(self): + """Return list of node's children""" + return self.child_nodes[:] + + def get_label(self): + """Return string containing text of current label""" + return self.widget.itemcget(self.label, 'text') + + def set_label(self, label): + """Set current text label""" + self.widget.itemconfig(self.label, text=label) + + def expanded(self): + """Returns true if node is currently expanded, false otherwise""" + return self.expanded_flag + + def expandable(self): + """Returns true if node can be expanded (i.e. if it's a folder)""" + return self.expandable_flag + + def full_id(self): + """Return list of IDs of all parents and node ID""" + if self.parent_node: + return self.parent_node.full_id()+(self.id,) + else: + return (self.id,) + + def expand(self): + """Expand node if possible""" + if not self.expanded_flag: + self.PVT_set_state(1) + + def collapse(self): + """Collapse node if possible""" + if self.expanded_flag: + self.PVT_set_state(0) + + def delete(self, me_too=1): + """Delete node from tree. ("me_too" is a hack not to be used by + external code, please!)""" + sw=self.widget + if not self.parent_node and me_too: + # can't delete the root node + raise ValueError, "can't delete root node" + self.PVT_delete_subtree() + # move everything up so that distance to next subnode is correct + n=self.next_visible() + x1, y1=sw.coords(self.symbol) + x2, y2=sw.coords(n.symbol) + if me_too: + dist=y2-y1 + else: + dist=y2-y1-sw.dist_y + self.PVT_tag_move(-dist) + n=self + if me_too: + if sw.pos == self: + # move cursor if it points to current node + sw.move_cursor(self.parent_node) + self.PVT_unbind_all() + sw.delete(self.symbol) + sw.delete(self.label) + sw.delete(self.h_line) + sw.delete(self.v_line) + sw.delete(self.indic) + self.parent_node.child_nodes.remove(self) + # break circular ref now, so parent may be GC'ed later + n=self.parent_node + self.parent_node=None + n.PVT_cleanup_lines() + n.PVT_update_scrollregion() + + def insert_before(self, nodes): + """Insert list of nodes as siblings before this node. Call parent + node's add_node() function to generate the list of nodes.""" + i=self.parent_node.child_nodes.index(self) + self.parent_node.PVT_insert(nodes, i, self.prev_visible()) + + def insert_after(self, nodes): + """Insert list of nodes as siblings after this node. Call parent + node's add_node() function to generate the list of nodes.""" + i=self.parent_node.child_nodes.index(self)+1 + self.parent_node.PVT_insert(nodes, i, self.PVT_last()) + + def insert_children(self, nodes): + """Insert list of nodes as children of this node. Call node's + add_node() function to generate the list of nodes.""" + self.PVT_insert(nodes, 0, self) + + def toggle_state(self): + """Toggle node's state between expanded and collapsed, if possible""" + if self.expandable_flag: + if self.expanded_flag: + self.PVT_set_state(0) + else: + self.PVT_set_state(1) + + # ----- functions for drag'n'drop support ----- + def PVT_enter(self, event): + """detect mouse hover for drag'n'drop""" + self.widget.target=self + + def dnd_end(self, target, event): + """Notification that dnd processing has been ended. It DOES NOT imply + that we've been dropped somewhere useful, we could have just been + dropped into deep space and nothing happened to any data structures, + or it could have been just a plain mouse-click w/o any dragging.""" + if not self.widget.drag: + # if there's been no dragging, it was just a mouse click + self.widget.move_cursor(self) + self.toggle_state() + self.widget.drag=0 + + # ----- PRIVATE METHODS (prefixed with "PVT_") ----- + # these methods are subject to change, so please try not to use them + def PVT_last(self): + """Return bottom-most node in subtree""" + n=self + while n.child_nodes: + n=n.child_nodes[-1] + return n + + def PVT_find(self, search): + """Used by searching functions""" + if self.id != search[0]: + # this actually only goes tilt if root doesn't match + return None + if len(search) == 1: + return self + # get list of children IDs + i=map(lambda x: x.id, self.child_nodes) + # if there is a child that matches, search it + try: + return self.child_nodes[i.index(search[1])].PVT_find(search[1:]) + except: + return None + + def PVT_insert(self, nodes, pos, below): + """Create and insert new children. "nodes" is list previously created + via calls to add_list(). "pos" is index in the list of children where + the new nodes are inserted. "below" is node which new children should + appear immediately below.""" + if not self.expandable_flag: + raise TypeError, 'not an expandable node' + # for speed + sw=self.widget + # expand and insert children + children=[] + self.expanded_flag=1 + sw.itemconfig(self.symbol, image=self.expanded_icon) + if sw.minus_icon and sw.line_flag: + sw.itemconfig(self.indic, image=sw.minus_icon) + if len(nodes): + # move stuff to make room + below.PVT_tag_move(sw.dist_y*len(nodes)) + # get position of first new child + xp, dummy=sw.coords(self.symbol) + dummy, yp=sw.coords(below.symbol) + xp=xp+sw.dist_x + yp=yp+sw.dist_y + # create vertical line + if sw.line_flag and not self.v_line: + self.v_line=sw.create_line( + xp, yp, + xp, yp+sw.dist_y*len(nodes)) + sw.tag_lower(self.v_line, self.symbol) + n=sw.node_class + for i in nodes: + # add new subnodes, they'll draw themselves + # this is a very expensive call + children.append( + n(parent_node=self, expandable_flag=i.flag, label=i.name, + id=i.id, collapsed_icon=i.collapsed_icon, + expanded_icon=i.expanded_icon, x=xp, y=yp)) + yp=yp+sw.dist_y + self.child_nodes[pos:pos]=children + self.PVT_cleanup_lines() + self.PVT_update_scrollregion() + sw.move_cursor(sw.pos) + + def PVT_set_state(self, state): + """Common code forexpanding/collapsing folders. It's not re-entrant, + and there are certain cases in which we can be called again before + we're done, so we use a mutex.""" + while self.widget.spinlock: + pass + self.widget.spinlock=1 + # expand & draw our subtrees + if state: + self.child_nodes=[] + self.widget.new_nodes=[] + if self.widget.get_contents_callback: + # this callback needs to make multiple calls to add_node() + try: + self.widget.get_contents_callback(self) + except: + report_callback_exception() + self.PVT_insert(self.widget.new_nodes, 0, self) + # collapse and delete subtrees + else: + self.expanded_flag=0 + self.widget.itemconfig(self.symbol, image=self.collapsed_icon) + if self.indic: + self.widget.itemconfig(self.indic, image=self.widget.plus_icon) + self.delete(0) + # release mutex + self.widget.spinlock=0 + + def PVT_cleanup_lines(self): + """Resize connecting lines""" + if self.widget.line_flag: + n=self + while n: + if n.child_nodes: + x1, y1=self.widget.coords(n.symbol) + x2, y2=self.widget.coords(n.child_nodes[-1].symbol) + self.widget.coords(n.v_line, x1, y1, x1, y2) + n=n.parent_node + + def PVT_update_scrollregion(self): + """Update scroll region for new size""" + x1, y1, x2, y2=self.widget.bbox('all') + self.widget.configure(scrollregion=(x1, y1, x2+5, y2+5)) + + def PVT_delete_subtree(self): + """Recursively delete subtree & clean up cyclic references to make + garbage collection happy""" + sw=self.widget + sw.delete(self.v_line) + self.v_line=None + for i in self.child_nodes: + # delete node's subtree, if any + i.PVT_delete_subtree() + i.PVT_unbind_all() + # delete widgets from canvas + sw.delete(i.symbol) + sw.delete(i.label) + sw.delete(i.h_line) + sw.delete(i.v_line) + sw.delete(i.indic) + # break circular reference + i.parent_node=None + # move cursor if it's in deleted subtree + if sw.pos in self.child_nodes: + sw.move_cursor(self) + # now subnodes will be properly garbage collected + self.child_nodes=[] + + def PVT_unbind_all(self): + """Unbind callbacks so node gets garbage-collected. This wasn't easy + to figure out the proper way to do this. See also tag_bind() for the + Tree widget itself.""" + for j in (self.symbol, self.label, self.indic, self.h_line, + self.v_line): + for k in self.widget.bindings.get(j, ()): + self.widget.tag_unbind(j, k[0], k[1]) + + def PVT_tag_move(self, dist): + """Move everything below current icon, to make room for subtree using + the Disney magic of item tags. This is the secret of making + everything as fast as it is.""" + # mark everything below current node as movable + bbox1=self.widget.bbox(self.widget.root.symbol, self.label) + bbox2=self.widget.bbox('all') + self.widget.dtag('move') + self.widget.addtag('move', 'overlapping', + bbox2[0], bbox1[3], bbox2[2], bbox2[3]) + # untag cursor & node so they don't get moved too + self.widget.dtag(self.widget.cursor_box, 'move') + self.widget.dtag(self.symbol, 'move') + self.widget.dtag(self.label, 'move') + # now do the move of all the tagged objects + self.widget.move('move', 0, dist) + + def PVT_click(self, event): + """Handle mouse clicks by kicking off possible drag'n'drop + processing""" + if self.widget.drop_callback: + if Tkdnd.dnd_start(self, event): + x1, y1, x2, y2=self.widget.bbox(self.symbol) + self.x_off=(x1-x2)/2 + self.y_off=(y1-y2)/2 + else: + # no callback, don't bother with drag'n'drop + self.widget.drag=0 + self.dnd_end(None, None) + +#------------------------------------------------------------------------------ +class Tree(Canvas): + # do we have enough possible arguments?!?!?! + def __init__(self, master, root_id, root_label='', + get_contents_callback=None, dist_x=15, dist_y=15, + text_offset=10, line_flag=1, expanded_icon=None, + collapsed_icon=None, regular_icon=None, plus_icon=None, + minus_icon=None, node_class=Node, drop_callback=None, + *args, **kw_args): + # pass args to superclass (new idiom from Python 2.2) + Canvas.__init__(self, master, *args, **kw_args) + + # this allows to subclass Node and pass our class in + self.node_class=node_class + # keep track of node bindings + self.bindings={} + # cheap mutex spinlock + self.spinlock=0 + # flag to see if there's been any d&d dragging + self.drag=0 + # default images (BASE64-encoded GIF files) + if expanded_icon == None: + self.expanded_icon=PhotoImage( + data='R0lGODlhEAANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \ + 'ACH5BAEAAAEALAAAAAAQAA0AAAM6GCrM+jCIQamIbw6ybXNSx3GVB' \ + 'YRiygnA534Eq5UlO8jUqLYsquuy0+SXap1CxBHr+HoBjoGndDpNAAA7') + else: + self.expanded_icon=expanded_icon + if collapsed_icon == None: + self.collapsed_icon=PhotoImage( + data='R0lGODlhDwANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \ + 'ACH5BAEAAAEALAAAAAAPAA0AAAMyGCHM+lAMMoeAT9Jtm5NDKI4Wo' \ + 'FXcJphhipanq7Kvu8b1dLc5tcuom2foAQQAyKRSmQAAOw==') + else: + self.collapsed_icon=collapsed_icon + if regular_icon == None: + self.regular_icon=PhotoImage( + data='R0lGODlhCwAOAJEAAAAAAICAgP///8DAwCH5BAEAAAMALAAA' \ + 'AAALAA4AAAIphA+jA+JuVgtUtMQePJlWCgSN9oSTV5lkKQpo2q5W+' \ + 'wbzuJrIHgw1WgAAOw==') + else: + self.regular_icon=regular_icon + if plus_icon == None: + self.plus_icon=PhotoImage( + data='R0lGODdhCQAJAPEAAAAAAH9/f////wAAACwAAAAACQAJAAAC' \ + 'FIyPoiu2sJyCyoF7W3hxz850CFIA\nADs=') + else: + self.plus_icon=plus_icon + if minus_icon == None: + self.minus_icon=PhotoImage( + data='R0lGODdhCQAJAPEAAAAAAH9/f////wAAACwAAAAACQAJAAAC' \ + 'EYyPoivG614LAlg7ZZbxoR8UADs=') + else: + self.minus_icon=minus_icon + # horizontal distance that subtrees are indented + self.dist_x=dist_x + # vertical distance between rows + self.dist_y=dist_y + # how far to offset text label + self.text_offset=text_offset + # flag controlling connecting line display + self.line_flag=line_flag + # called just before subtree expand/collapse + self.get_contents_callback=get_contents_callback + # called after drag'n'drop + self.drop_callback=drop_callback + # create root node to get the ball rolling + self.root=node_class(parent_node=None, label=root_label, + id=root_id, expandable_flag=1, + collapsed_icon=self.collapsed_icon, + expanded_icon=self.expanded_icon, + x=dist_x, y=dist_y, parent_widget=self) + # configure for scrollbar(s) + x1, y1, x2, y2=self.bbox('all') + self.configure(scrollregion=(x1, y1, x2+5, y2+5)) + # add a cursor + self.cursor_box=self.create_rectangle(0, 0, 0, 0) + self.move_cursor(self.root) + # make it easy to point to control + self.bind('', self.PVT_mousefocus) + # totally arbitrary yet hopefully intuitive default keybindings + # stole 'em from ones used by microsoft tree control + # page-up/page-down + self.bind('', self.pagedown) + self.bind('', self.pageup) + # arrow-up/arrow-down + self.bind('', self.next) + self.bind('', self.prev) + # arrow-left/arrow-right + self.bind('', self.ascend) + # (hold this down and you expand the entire tree) + self.bind('', self.descend) + # home/end + self.bind('', self.first) + self.bind('', self.last) + # space bar + self.bind('', self.toggle) + + # ----- PRIVATE METHODS (prefixed with "PVT_") ----- + # these methods are subject to change, so please try not to use them + def PVT_mousefocus(self, event): + """Soak up event argument when moused-over""" + self.focus_set() + + # ----- PUBLIC METHODS ----- + def tag_bind(self, tag, seq, *args, **kw_args): + """Keep track of callback bindings so we can delete them later. I + shouldn't have to do this!!!!""" + # pass args to superclass + func_id=apply(Canvas.tag_bind, (self, tag, seq)+args, kw_args) + # save references + self.bindings[tag]=self.bindings.get(tag, [])+[(seq, func_id)] + + def add_list(self, list=None, name=None, id=None, flag=0, + expanded_icon=None, collapsed_icon=None): + """Add node construction info to list""" + n=Struct() + n.name=name + n.id=id + n.flag=flag + if collapsed_icon: + n.collapsed_icon=collapsed_icon + else: + if flag: + # it's expandable, use closed folder icon + n.collapsed_icon=self.collapsed_icon + else: + # it's not expandable, use regular file icon + n.collapsed_icon=self.regular_icon + if flag: + if expanded_icon: + n.expanded_icon=expanded_icon + else: + n.expanded_icon=self.expanded_icon + else: + # not expandable, don't need an icon + n.expanded_icon=None + if list == None: + list=[] + list.append(n) + return list + + def add_node(self, name=None, id=None, flag=0, expanded_icon=None, + collapsed_icon=None): + """Add a node during get_contents_callback()""" + self.add_list(self.new_nodes, name, id, flag, expanded_icon, + collapsed_icon) + + def find_full_id(self, search): + """Search for a node""" + return self.root.PVT_find(search) + + def cursor_node(self, search): + """Return node under cursor""" + return self.pos + + def see(self, *items): + """Scroll (in a series of nudges) so items are visible""" + x1, y1, x2, y2=apply(self.bbox, items) + while x2 > self.canvasx(0)+self.winfo_width(): + old=self.canvasx(0) + self.xview('scroll', 1, 'units') + # avoid endless loop if we can't scroll + if old == self.canvasx(0): + break + while y2 > self.canvasy(0)+self.winfo_height(): + old=self.canvasy(0) + self.yview('scroll', 1, 'units') + if old == self.canvasy(0): + break + # done in this order to ensure upper-left of object is visible + while x1 < self.canvasx(0): + old=self.canvasx(0) + self.xview('scroll', -1, 'units') + if old == self.canvasx(0): + break + while y1 < self.canvasy(0): + old=self.canvasy(0) + self.yview('scroll', -1, 'units') + if old == self.canvasy(0): + break + + def move_cursor(self, node): + """Move cursor to node""" + self.pos=node + x1, y1, x2, y2=self.bbox(node.symbol, node.label) + self.coords(self.cursor_box, x1-1, y1-1, x2+1, y2+1) + self.see(node.symbol, node.label) + + def toggle(self, event=None): + """Expand/collapse subtree""" + self.pos.toggle_state() + + def next(self, event=None): + """Move to next lower visible node""" + self.move_cursor(self.pos.next_visible()) + + def prev(self, event=None): + """Move to next higher visible node""" + self.move_cursor(self.pos.prev_visible()) + + def ascend(self, event=None): + """Move to immediate parent""" + if self.pos.parent_node: + # move to parent + self.move_cursor(self.pos.parent_node) + + def descend(self, event=None): + """Move right, expanding as we go""" + if self.pos.expandable_flag: + self.pos.expand() + if self.pos.child_nodes: + # move to first subnode + self.move_cursor(self.pos.child_nodes[0]) + return + # if no subnodes, move to next sibling + self.next() + + def first(self, event=None): + """Go to root node""" + # move to root node + self.move_cursor(self.root) + + def last(self, event=None): + """Go to last visible node""" + # move to bottom-most node + self.move_cursor(self.root.PVT_last()) + + def pageup(self, event=None): + """Previous page""" + n=self.pos + j=self.winfo_height()/self.dist_y + for i in range(j-3): + n=n.prev_visible() + self.yview('scroll', -1, 'pages') + self.move_cursor(n) + + def pagedown(self, event=None): + """Next page""" + n=self.pos + j=self.winfo_height()/self.dist_y + for i in range(j-3): + n=n.next_visible() + self.yview('scroll', 1, 'pages') + self.move_cursor(n) + + # ----- functions for drag'n'drop support ----- + def where(self, event): + """Determine drag location in canvas coordinates. event.x & event.y + don't seem to be what we want.""" + # where the corner of the canvas is relative to the screen: + x_org=self.winfo_rootx() + y_org=self.winfo_rooty() + # where the pointer is relative to the canvas widget, + # including scrolling + x=self.canvasx(event.x_root-x_org) + y=self.canvasy(event.y_root-y_org) + return x, y + + def dnd_accept(self, source, event): + """Accept dnd messages, i.e. we're a legit drop target, and we do + implement d&d functions.""" + self.target=None + return self + + def dnd_enter(self, source, event): + """Get ready to drag or drag has entered widget (create drag + object)""" + # this flag lets us know there's been drag motion + self.drag=1 + x, y=self.where(event) + x1, y1, x2, y2=source.widget.bbox(source.symbol, source.label) + dx, dy=x2-x1, y2-y1 + # create dragging icon + if source.expanded_flag: + self.dnd_symbol=self.create_image(x, y, + image=source.expanded_icon) + else: + self.dnd_symbol=self.create_image(x, y, + image=source.collapsed_icon) + self.dnd_label=self.create_text(x+self.text_offset, y, + text=source.get_label(), + justify='left', + anchor='w') + + def dnd_motion(self, source, event): + """Move drag icon""" + self.drag=1 + x, y=self.where(event) + x1, y1, x2, y2=self.bbox(self.dnd_symbol, self.dnd_label) + self.move(self.dnd_symbol, x-x1+source.x_off, y-y1+source.y_off) + self.move(self.dnd_label, x-x1+source.x_off, y-y1+source.y_off) + + def dnd_leave(self, source, event): + """Finish dragging or drag has left widget (destroy drag object)""" + self.delete(self.dnd_symbol) + self.delete(self.dnd_label) + + def dnd_commit(self, source, event): + """Object has been dropped here""" + # call our own dnd_leave() to clean up + self.dnd_leave(source, event) + # process pending events to detect target node + # update_idletasks() doesn't do the trick if source & target are + # on different widgets + self.update() + if not self.target: + # no target node + return + # we must update data structures based on the drop + if self.drop_callback: + try: + # called with dragged node and target node + # this is where a file manager would move the actual file + # it must also move the nodes around as it wishes + self.drop_callback(source, self.target) + except: + report_callback_exception() + +#------------------------------------------------------------------------------ +# the good 'ol test/demo code +if __name__ == '__main__': + import os + import sys + + # default routine to get contents of subtree + # supply this for a different type of app + # argument is the node object being expanded + # should call add_node() + def get_contents(node): + path=apply(os.path.join, node.full_id()) + for filename in os.listdir(path): + full=os.path.join(path, filename) + name=filename + folder=0 + if os.path.isdir(full): + # it's a directory + folder=1 + elif not os.path.isfile(full): + # but it's not a file + name=name+' (special)' + if os.path.islink(full): + # it's a link + name=name+' (link to '+os.readlink(full)+')' + node.widget.add_node(name=name, id=filename, flag=folder) + + root=Tk() + root.title(os.path.basename(sys.argv[0])) + tree=os.sep + if sys.platform == 'win32': + # we could call the root "My Computer" and mess with get_contents() + # to return "A:", "B:", "C:", ... etc. as it's children, but that + # would just be terminally cute and I'd have to shoot myself + tree='C:'+os.sep + + # create the control + t=Tree(master=root, + root_id=tree, + root_label=tree, + get_contents_callback=get_contents, + width=300) + t.grid(row=0, column=0, sticky='nsew') + + # make expandable + root.grid_rowconfigure(0, weight=1) + root.grid_columnconfigure(0, weight=1) + + # add scrollbars + sb=Scrollbar(root) + sb.grid(row=0, column=1, sticky='ns') + t.configure(yscrollcommand=sb.set) + sb.configure(command=t.yview) + + sb=Scrollbar(root, orient=HORIZONTAL) + sb.grid(row=1, column=0, sticky='ew') + t.configure(xscrollcommand=sb.set) + sb.configure(command=t.xview) + + # must get focus so keys work for demo + t.focus_set() + + # we could do without this, but it's nice and friendly to have + Button(root, text='Quit', command=root.quit).grid(row=2, column=0, + columnspan=2) + + # expand out the root + t.root.expand() + + root.mainloop() Added: py/branch/py-collect/test/tkinter/utils.py ============================================================================== --- (empty file) +++ py/branch/py-collect/test/tkinter/utils.py Mon Apr 4 17:32:41 2005 @@ -0,0 +1,247 @@ +import types, string, sys +import py +from py.__impl__.test.report.text import out +from py.__impl__.test.terminal import TerminalDriver + +class Null: + """ Null objects always and reliably "do nothing." """ + + def __init__(self, *args, **kwargs): pass + def __call__(self, *args, **kwargs): return self + def __repr__(self): return "Null()" + def __str__(self): return repr(self) + ' with id:' + str(id(self)) + def __nonzero__(self): return 0 + + def __getattr__(self, name): return self + def __setattr__(self, name, value): return self + def __delattr__(self, name): return self + + +_NotExecuted = 'NotExecuted' +_Passed = 'Passed' +_Failed = 'Failed' +_Skipped = 'Skipped' +_ExceptionFailure = 'ExceptionFailure' + +class Status(object): + + @classmethod + def NotExecuted(cls): + return cls('NotExecuted') + @classmethod + def Passed(cls): + return cls('Passed') + @classmethod + def Failed(cls): + return cls('Failed') + @classmethod + def Skipped(cls): + return cls('Skipped') + @classmethod + def ExceptionFailure(cls): + return cls(_ExceptionFailure) + + ordered_list = [_NotExecuted, + _Passed, + _Skipped, + _Failed, + _ExceptionFailure] + + namemap = { + py.test.Item.Passed: _Passed, + py.test.Item.Skipped: _Skipped, + py.test.Item.Failed: _Failed, + py.test.Item.ExceptionFailure: _ExceptionFailure } + + def __init__(self, outcome_or_name = ''): + self.str = _NotExecuted + if isinstance(outcome_or_name, py.test.Collector.Outcome): + # hack + if isinstance(outcome_or_name, py.test.Item.ExceptionFailure): + self.str = self.namemap[py.test.Item.ExceptionFailure] + else: + for restype, name in self.namemap.items(): + if isinstance(outcome_or_name, restype): + self.str = name + else: + if str(outcome_or_name) in self.ordered_list: + self.str = str(outcome_or_name) + + def __repr__(self): + return 'Status("%s")' % self.str + + def __str__(self): + return self.str + + def update(self, status): + name_int_map = dict( + py.std.itertools.izip(self.ordered_list, + py.std.itertools.count())) + self.str = self.ordered_list[max([name_int_map[i] for i in (str(status), self.str)])] + + def __eq__(self, other): + return self.str == other.str + + def __ne__(self, other): + return not self.__eq__(other) + +class OutBuffer(out.Out): + + def __init__(self, fullwidth = 80 -1): + self.output = [] + self.fullwidth = fullwidth + + def line(self, s= ''): + self.output.append(str(s) + '\n') + + def write(self, s): + self.output.append(str(s)) + + def getoutput(self): + return ''.join(self.output) + + def rewrite(self, s=''): + self.write(s) + + + + +class TestReport(object): + + template = {'time' : 0, + 'label': 'Root', + 'id': 'Root', + 'full_id': ['Root'], + 'status': Status.NotExecuted(), + 'report': 'NoReport', + 'error_report': '', + 'finished': False, + 'restart_params': None, # ('',('',)) + } + @classmethod + def fromChannel(cls, kwdict): + if 'status' in kwdict: + kwdict['status'] = Status(kwdict['status']) + return cls(**kwdict) + + + def __init__(self, **kwargs): + # copy status -> deepcopy + kwdict = py.std.copy.deepcopy(self.template) + kwdict.update(kwargs) + for key, value in kwdict.iteritems(): + setattr(self, key, value) + + def start(self, collector): + self.full_id = collector.listnames() + self.id = collector.name + if collector.getpathlineno(): # save for Null() in test_utils.py + fspath, lineno = collector.getpathlineno() + if lineno != sys.maxint: + str_append = ' [%s:%s]' % (fspath.basename, lineno) + else: + str_append = ' [%s]' % fspath.basename + self.label = collector.name + str_append + + self.settime() + self.restart_params = (str(collector.listchain()[0].fspath), + collector.listnames()) + self.status = Status.NotExecuted() + + def finish(self, collector, res, option = Null()): + self.settime() + if collector.getpathlineno(): # save for Null() in test_utils.py + fspath, lineno = collector.getpathlineno() + if lineno != sys.maxint: + str_append = ' [%s:%s] %0.2fsecs' % (fspath.basename, lineno, self.time) + else: + str_append = ' [%s] %0.2fsecs' % (fspath.basename, self.time) + self.label = collector.name + str_append + if res: + if Status(res) in (Status.Failed(), Status.ExceptionFailure()): + self.error_report = self.report_failed(option, collector, res) + elif Status(res) == Status.Skipped(): + self.error_report = self.report_skipped(option, collector, res) + self.status.update(Status(res)) + self.finished = True + + def report_failed(self, option, item, res): + #XXX hack abuse of TerminalDriver + terminal = TerminalDriver(option) + out = OutBuffer() + terminal.out = out + terminal.repr_failure(item, res) + return out.getoutput() + + def report_skipped(self,option, item, res): + texts = {} + tbindex = getattr(res, 'tbindex', -1) + raisingtb = res.excinfo.traceback[tbindex] + fn = raisingtb.frame.code.path + lineno = raisingtb.lineno + d = texts.setdefault(res.excinfo.exconly(), {}) + d[(fn,lineno)] = res + out = OutBuffer() + out.sep('_', 'reasons for skipped tests') + for text, dict in texts.items(): + for (fn, lineno), res in dict.items(): + out.line('Skipped in %s:%d' %(fn,lineno)) + out.line("reason: %s" % text) + + return out.getoutput() + + def settime(self): + self.time = py.std.time.time() - self.time + + def toChannel(self): + ret = self.template.copy() + for key in ret.keys(): + ret[key] = getattr(self, key, self.template[key]) + ret['status'] = str(ret['status']) + return ret + + def __str__(self): + return str(self.toChannel()) + + def __repr__(self): + return str(self) + + def copy(self, **kwargs): + channel_dict = self.toChannel() + channel_dict.update(kwargs) + return TestReport.fromChannel(channel_dict) + +class TestFileWatcher: + + def __init__(self, *paths): + self.paths = [py.path.local(path) for path in paths] + self.watchdict = dict() + + def file_information(self, path): + try: + return path.stat().st_ctime + except: + return None + + def check_files(self): + fil = py.path.checker(fnmatch='*.py') + rec = py.path.checker(dotfile=0) + + files = [] + for path in self.paths: + if path.check(file=1): + files.append(path) + else: + files.extend(path.visit(fil, rec)) + newdict = dict(zip(files, [self.file_information(p) for p in files])) + files_deleted = [f for f in self.watchdict.keys() if not newdict.has_key(f)] + files_new = [f for f in newdict.keys() if not self.watchdict.has_key(f)] + files_changed = [f for f in newdict.keys() if self.watchdict.has_key(f) and newdict[f]!= self.watchdict[f]] + files_changed = files_new + files_changed + + self.watchdict = newdict + return files_changed, files_deleted + + def changed(self): + changed, deleted = self.check_files() + return changed != [] or deleted != [] From jan at codespeak.net Mon Apr 4 18:59:17 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Mon, 4 Apr 2005 18:59:17 +0200 (MEST) Subject: [py-svn] r10300 - py/branch/py-collect/test/tkinter Message-ID: <20050404165917.8B4F427BC1@code1.codespeak.net> Author: jan Date: Mon Apr 4 18:59:17 2005 New Revision: 10300 Added: py/branch/py-collect/test/tkinter/util.py - copied unchanged from r10295, py/branch/py-collect/test/tkinter/utils.py Removed: py/branch/py-collect/test/tkinter/utils.py Modified: py/branch/py-collect/test/tkinter/gui.py py/branch/py-collect/test/tkinter/guidriver.py py/branch/py-collect/test/tkinter/repository.py py/branch/py-collect/test/tkinter/tkgui.py Log: py/test/tkinter moved utils.py to util.py Modified: py/branch/py-collect/test/tkinter/gui.py ============================================================================== --- py/branch/py-collect/test/tkinter/gui.py (original) +++ py/branch/py-collect/test/tkinter/gui.py Mon Apr 4 18:59:17 2005 @@ -1,5 +1,5 @@ from tktree import Tree, Node -from utils import TestReport, Status, Null +from util import TestReport, Status, Null from Tkinter import PhotoImage import py Item = py.test.Item Modified: py/branch/py-collect/test/tkinter/guidriver.py ============================================================================== --- py/branch/py-collect/test/tkinter/guidriver.py (original) +++ py/branch/py-collect/test/tkinter/guidriver.py Mon Apr 4 18:59:17 2005 @@ -1,6 +1,6 @@ import py -from utils import Status, TestReport, Null +from util import Status, TestReport, Null from py.__impl__.test.drive import Exit, exit, SimpleOutErrCapture import pprint Modified: py/branch/py-collect/test/tkinter/repository.py ============================================================================== --- py/branch/py-collect/test/tkinter/repository.py (original) +++ py/branch/py-collect/test/tkinter/repository.py Mon Apr 4 18:59:17 2005 @@ -7,7 +7,7 @@ import copy import time -from utils import Null +from util import Null from itertools import izip, count import UserDict Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Mon Apr 4 18:59:17 2005 @@ -16,7 +16,7 @@ import repository import utils -from utils import TestReport, Status, Null +from util import TestReport, Status, Null import gui Deleted: /py/branch/py-collect/test/tkinter/utils.py ============================================================================== --- /py/branch/py-collect/test/tkinter/utils.py Mon Apr 4 18:59:17 2005 +++ (empty file) @@ -1,247 +0,0 @@ -import types, string, sys -import py -from py.__impl__.test.report.text import out -from py.__impl__.test.terminal import TerminalDriver - -class Null: - """ Null objects always and reliably "do nothing." """ - - def __init__(self, *args, **kwargs): pass - def __call__(self, *args, **kwargs): return self - def __repr__(self): return "Null()" - def __str__(self): return repr(self) + ' with id:' + str(id(self)) - def __nonzero__(self): return 0 - - def __getattr__(self, name): return self - def __setattr__(self, name, value): return self - def __delattr__(self, name): return self - - -_NotExecuted = 'NotExecuted' -_Passed = 'Passed' -_Failed = 'Failed' -_Skipped = 'Skipped' -_ExceptionFailure = 'ExceptionFailure' - -class Status(object): - - @classmethod - def NotExecuted(cls): - return cls('NotExecuted') - @classmethod - def Passed(cls): - return cls('Passed') - @classmethod - def Failed(cls): - return cls('Failed') - @classmethod - def Skipped(cls): - return cls('Skipped') - @classmethod - def ExceptionFailure(cls): - return cls(_ExceptionFailure) - - ordered_list = [_NotExecuted, - _Passed, - _Skipped, - _Failed, - _ExceptionFailure] - - namemap = { - py.test.Item.Passed: _Passed, - py.test.Item.Skipped: _Skipped, - py.test.Item.Failed: _Failed, - py.test.Item.ExceptionFailure: _ExceptionFailure } - - def __init__(self, outcome_or_name = ''): - self.str = _NotExecuted - if isinstance(outcome_or_name, py.test.Collector.Outcome): - # hack - if isinstance(outcome_or_name, py.test.Item.ExceptionFailure): - self.str = self.namemap[py.test.Item.ExceptionFailure] - else: - for restype, name in self.namemap.items(): - if isinstance(outcome_or_name, restype): - self.str = name - else: - if str(outcome_or_name) in self.ordered_list: - self.str = str(outcome_or_name) - - def __repr__(self): - return 'Status("%s")' % self.str - - def __str__(self): - return self.str - - def update(self, status): - name_int_map = dict( - py.std.itertools.izip(self.ordered_list, - py.std.itertools.count())) - self.str = self.ordered_list[max([name_int_map[i] for i in (str(status), self.str)])] - - def __eq__(self, other): - return self.str == other.str - - def __ne__(self, other): - return not self.__eq__(other) - -class OutBuffer(out.Out): - - def __init__(self, fullwidth = 80 -1): - self.output = [] - self.fullwidth = fullwidth - - def line(self, s= ''): - self.output.append(str(s) + '\n') - - def write(self, s): - self.output.append(str(s)) - - def getoutput(self): - return ''.join(self.output) - - def rewrite(self, s=''): - self.write(s) - - - - -class TestReport(object): - - template = {'time' : 0, - 'label': 'Root', - 'id': 'Root', - 'full_id': ['Root'], - 'status': Status.NotExecuted(), - 'report': 'NoReport', - 'error_report': '', - 'finished': False, - 'restart_params': None, # ('',('',)) - } - @classmethod - def fromChannel(cls, kwdict): - if 'status' in kwdict: - kwdict['status'] = Status(kwdict['status']) - return cls(**kwdict) - - - def __init__(self, **kwargs): - # copy status -> deepcopy - kwdict = py.std.copy.deepcopy(self.template) - kwdict.update(kwargs) - for key, value in kwdict.iteritems(): - setattr(self, key, value) - - def start(self, collector): - self.full_id = collector.listnames() - self.id = collector.name - if collector.getpathlineno(): # save for Null() in test_utils.py - fspath, lineno = collector.getpathlineno() - if lineno != sys.maxint: - str_append = ' [%s:%s]' % (fspath.basename, lineno) - else: - str_append = ' [%s]' % fspath.basename - self.label = collector.name + str_append - - self.settime() - self.restart_params = (str(collector.listchain()[0].fspath), - collector.listnames()) - self.status = Status.NotExecuted() - - def finish(self, collector, res, option = Null()): - self.settime() - if collector.getpathlineno(): # save for Null() in test_utils.py - fspath, lineno = collector.getpathlineno() - if lineno != sys.maxint: - str_append = ' [%s:%s] %0.2fsecs' % (fspath.basename, lineno, self.time) - else: - str_append = ' [%s] %0.2fsecs' % (fspath.basename, self.time) - self.label = collector.name + str_append - if res: - if Status(res) in (Status.Failed(), Status.ExceptionFailure()): - self.error_report = self.report_failed(option, collector, res) - elif Status(res) == Status.Skipped(): - self.error_report = self.report_skipped(option, collector, res) - self.status.update(Status(res)) - self.finished = True - - def report_failed(self, option, item, res): - #XXX hack abuse of TerminalDriver - terminal = TerminalDriver(option) - out = OutBuffer() - terminal.out = out - terminal.repr_failure(item, res) - return out.getoutput() - - def report_skipped(self,option, item, res): - texts = {} - tbindex = getattr(res, 'tbindex', -1) - raisingtb = res.excinfo.traceback[tbindex] - fn = raisingtb.frame.code.path - lineno = raisingtb.lineno - d = texts.setdefault(res.excinfo.exconly(), {}) - d[(fn,lineno)] = res - out = OutBuffer() - out.sep('_', 'reasons for skipped tests') - for text, dict in texts.items(): - for (fn, lineno), res in dict.items(): - out.line('Skipped in %s:%d' %(fn,lineno)) - out.line("reason: %s" % text) - - return out.getoutput() - - def settime(self): - self.time = py.std.time.time() - self.time - - def toChannel(self): - ret = self.template.copy() - for key in ret.keys(): - ret[key] = getattr(self, key, self.template[key]) - ret['status'] = str(ret['status']) - return ret - - def __str__(self): - return str(self.toChannel()) - - def __repr__(self): - return str(self) - - def copy(self, **kwargs): - channel_dict = self.toChannel() - channel_dict.update(kwargs) - return TestReport.fromChannel(channel_dict) - -class TestFileWatcher: - - def __init__(self, *paths): - self.paths = [py.path.local(path) for path in paths] - self.watchdict = dict() - - def file_information(self, path): - try: - return path.stat().st_ctime - except: - return None - - def check_files(self): - fil = py.path.checker(fnmatch='*.py') - rec = py.path.checker(dotfile=0) - - files = [] - for path in self.paths: - if path.check(file=1): - files.append(path) - else: - files.extend(path.visit(fil, rec)) - newdict = dict(zip(files, [self.file_information(p) for p in files])) - files_deleted = [f for f in self.watchdict.keys() if not newdict.has_key(f)] - files_new = [f for f in newdict.keys() if not self.watchdict.has_key(f)] - files_changed = [f for f in newdict.keys() if self.watchdict.has_key(f) and newdict[f]!= self.watchdict[f]] - files_changed = files_new + files_changed - - self.watchdict = newdict - return files_changed, files_deleted - - def changed(self): - changed, deleted = self.check_files() - return changed != [] or deleted != [] From jan at codespeak.net Mon Apr 4 19:59:01 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Mon, 4 Apr 2005 19:59:01 +0200 (MEST) Subject: [py-svn] r10304 - py/branch/py-collect/test/tkinter Message-ID: <20050404175901.4000227BC2@code1.codespeak.net> Author: jan Date: Mon Apr 4 19:59:01 2005 New Revision: 10304 Added: py/branch/py-collect/test/tkinter/test_util.py - copied, changed from r10300, py/branch/py-collect/test/tkinter/test_utils.py Removed: py/branch/py-collect/test/tkinter/test_utils.py Modified: py/branch/py-collect/test/tkinter/test_guidriver.py py/branch/py-collect/test/tkinter/tkgui.py py/branch/py-collect/test/tkinter/util.py Log: missed some references to utils.py Modified: py/branch/py-collect/test/tkinter/test_guidriver.py ============================================================================== --- py/branch/py-collect/test/tkinter/test_guidriver.py (original) +++ py/branch/py-collect/test/tkinter/test_guidriver.py Mon Apr 4 19:59:01 2005 @@ -3,7 +3,7 @@ import guidriver GuiDriver = guidriver.GuiDriver -from utils import Status, TestReport, Null +from util import Status, TestReport, Null class TestGuiDriver: Copied: py/branch/py-collect/test/tkinter/test_util.py (from r10300, py/branch/py-collect/test/tkinter/test_utils.py) ============================================================================== --- py/branch/py-collect/test/tkinter/test_utils.py (original) +++ py/branch/py-collect/test/tkinter/test_util.py Mon Apr 4 19:59:01 2005 @@ -1,6 +1,6 @@ -import utils -from utils import Status, TestReport, OutBuffer +import util +from util import Status, TestReport, OutBuffer import py Item = py.test.Item Deleted: /py/branch/py-collect/test/tkinter/test_utils.py ============================================================================== --- /py/branch/py-collect/test/tkinter/test_utils.py Mon Apr 4 19:59:01 2005 +++ (empty file) @@ -1,118 +0,0 @@ - -import utils -from utils import Status, TestReport, OutBuffer -import py -Item = py.test.Item - -class TestStatus: - - def test_init_with_None(self): - status = Status(None) - assert status == status.NotExecuted() - - def test_str(self): - status = Status(Item.Passed()) - assert status == status.Passed() - - status = Status(Item.Failed()) - assert status == status.Failed() - - status = Status(Item.Skipped()) - assert status == status.Skipped() - - status = Status(Item.ExceptionFailure()) - assert status == status.ExceptionFailure() - - - def test_init_with_bad_name(self): - status = Status('nothing') - assert status == Status.NotExecuted() - - def test_init_with_good_name(self): - def check_str(obj, expected): - assert str(obj) == expected - - for name in Status.ordered_list: - yield check_str, Status(name), name - - def test_update(self): - failed = Status.Failed() - passed = Status.Passed() - failed.update(passed) - assert failed == Status.Failed() - - passed.update(failed) - assert passed == Status.Failed() - assert passed == failed - - def test_eq_(self): - passed = Status.Passed() - assert passed == passed - assert passed == Status.Passed() - - failed = Status.Failed() - assert failed != passed - - -class TestTestReport: - - def setup_method(self, method): - self.path = py.path.local() - self.collector = py.test.Directory(self.path) - self.testresult = TestReport() - - def test_start(self): - self.testresult.start(self.collector) - - assert self.testresult.full_id == self.collector.listnames() - assert self.testresult.time != 0 - assert self.testresult.status == Status.NotExecuted() - - def test_finish(self): - self.testresult.start(self.collector) - - py.std.time.sleep(1.1) - - self.testresult.finish(self.collector, None) - assert self.testresult.time > 1 - assert self.testresult.status == Status.NotExecuted() - -## def test_finish_failed(self): -## self.testresult.start(self.collector) - -## self.testresult.finish(self.collector, py.test.Collector.Failed()) -## assert self.testresult.status == Status.Failed() - - - - def test_toChannel_fromChannel(self): - assert isinstance(self.testresult.toChannel()['status'], str) - result = TestReport.fromChannel(self.testresult.toChannel()) - assert isinstance(result.status, Status) - - def test_copy(self): - result2 = self.testresult.copy() - assert self.testresult.status == Status.NotExecuted() - for key in TestReport.template.keys(): - assert getattr(result2, key) == getattr(self.testresult, key) - - self.testresult.status = Status.Failed() - assert result2.status != self.testresult.status - - -class Test_OutBuffer: - - def setup_method(self, method): - self.out = OutBuffer() - - def test_line(self): - oneline = 'oneline' - self.out.line(oneline) - assert self.out.getoutput() == oneline + '\n' - - def test_write(self): - item = 'item' - self.out.write(item) - assert self.out.getoutput() == item - - Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Mon Apr 4 19:59:01 2005 @@ -15,8 +15,8 @@ Collector = py.test.Collector import repository -import utils -from util import TestReport, Status, Null +import util +from util import TestReport, Status, Null, TestFileWatcher import gui @@ -178,7 +178,7 @@ self.reset_repository() self.args = py.std.sys.argv[1:] self.paths = py.test.config.init(self.args)[1] - self.testfilewatcher = utils.TestFileWatcher(*self.paths) + self.testfilewatcher = TestFileWatcher(*self.paths) # Create the queue self.guiqueue = Queue.Queue() self.reporterqueue = Queue.Queue() Modified: py/branch/py-collect/test/tkinter/util.py ============================================================================== --- py/branch/py-collect/test/tkinter/util.py (original) +++ py/branch/py-collect/test/tkinter/util.py Mon Apr 4 19:59:01 2005 @@ -135,7 +135,7 @@ def start(self, collector): self.full_id = collector.listnames() self.id = collector.name - if collector.getpathlineno(): # save for Null() in test_utils.py + if collector.getpathlineno(): # save for Null() in test_util.py fspath, lineno = collector.getpathlineno() if lineno != sys.maxint: str_append = ' [%s:%s]' % (fspath.basename, lineno) @@ -150,7 +150,7 @@ def finish(self, collector, res, option = Null()): self.settime() - if collector.getpathlineno(): # save for Null() in test_utils.py + if collector.getpathlineno(): # save for Null() in test_util.py fspath, lineno = collector.getpathlineno() if lineno != sys.maxint: str_append = ' [%s:%s] %0.2fsecs' % (fspath.basename, lineno, self.time) From hpk at codespeak.net Mon Apr 4 22:31:34 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 4 Apr 2005 22:31:34 +0200 (MEST) Subject: [py-svn] r10307 - in py/branch/py-collect: . c-extension test test/testing test/tkinter Message-ID: <20050404203134.0651827BB9@code1.codespeak.net> Author: hpk Date: Mon Apr 4 22:31:33 2005 New Revision: 10307 Modified: py/branch/py-collect/__init__.py py/branch/py-collect/c-extension/conftest.py py/branch/py-collect/test/defaultconfig.py py/branch/py-collect/test/item.py py/branch/py-collect/test/run.py py/branch/py-collect/test/terminal.py py/branch/py-collect/test/testing/test_collect.py py/branch/py-collect/test/tkinter/ (props changed) py/branch/py-collect/test/tkinter/__init__.py (props changed) py/branch/py-collect/test/tkinter/gui.py (contents, props changed) py/branch/py-collect/test/tkinter/guidriver.py (contents, props changed) py/branch/py-collect/test/tkinter/repository.py (contents, props changed) py/branch/py-collect/test/tkinter/test_exceptionfailure.py (props changed) py/branch/py-collect/test/tkinter/test_guidriver.py (contents, props changed) py/branch/py-collect/test/tkinter/test_repository.py (props changed) py/branch/py-collect/test/tkinter/test_util.py (contents, props changed) py/branch/py-collect/test/tkinter/tkgui.py (contents, props changed) py/branch/py-collect/test/tkinter/tktree.py (props changed) py/branch/py-collect/test/tkinter/util.py (contents, props changed) Log: - moved primary Collector Classes into the py.test.collect namespace (which is the same location as they have with the trunk) - fixed tkinter gui to run on python2.3 (don't use decorator syntax in the py lib, please :-) Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/branch/py-collect/__init__.py (original) +++ py/branch/py-collect/__init__.py Mon Apr 4 22:31:33 2005 @@ -16,12 +16,12 @@ 'test.compat.TestCase' : ('./test/compat.py', 'TestCase'), - 'test.Collector' : ('./test/collect.py', 'Collector'), - 'test.Directory' : ('./test/collect.py', 'Directory'), - 'test.Module' : ('./test/collect.py', 'Module'), - 'test.Class' : ('./test/collect.py', 'Class'), - 'test.Instance' : ('./test/collect.py', 'Instance'), - 'test.Generator' : ('./test/collect.py', 'Generator'), + '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'), Modified: py/branch/py-collect/c-extension/conftest.py ============================================================================== --- py/branch/py-collect/c-extension/conftest.py (original) +++ py/branch/py-collect/c-extension/conftest.py Mon Apr 4 22:31:33 2005 @@ -1,6 +1,6 @@ import py -class Directory(py.test.Directory): +class Directory(py.test.collect.Directory): # XXX see in which situations/platforms we want # run tests here #def recfilter(self, path): Modified: py/branch/py-collect/test/defaultconfig.py ============================================================================== --- py/branch/py-collect/test/defaultconfig.py (original) +++ py/branch/py-collect/test/defaultconfig.py Mon Apr 4 22:31:33 2005 @@ -3,12 +3,12 @@ Driver = py.test.TerminalDriver -Module = py.test.Module -Directory = py.test.Directory -Class = py.test.Class -Generator = py.test.Generator +Module = py.test.collect.Module +Directory = py.test.collect.Directory +Class = py.test.collect.Class +Generator = py.test.collect.Generator Function = py.test.Function -Instance = py.test.Instance +Instance = py.test.collect.Instance additionalinfo = None Modified: py/branch/py-collect/test/item.py ============================================================================== --- py/branch/py-collect/test/item.py (original) +++ py/branch/py-collect/test/item.py Mon Apr 4 22:31:33 2005 @@ -29,7 +29,7 @@ col.setup() self.stack.append(col) -class Item(py.test.Collector): +class Item(py.test.collect.Collector): state = SetupState() class Function(Item): Modified: py/branch/py-collect/test/run.py ============================================================================== --- py/branch/py-collect/test/run.py (original) +++ py/branch/py-collect/test/run.py Mon Apr 4 22:31:33 2005 @@ -28,7 +28,7 @@ changed = True return changed -class FailingCollector(py.test.Collector): +class FailingCollector(py.test.collect.Collector): def __init__(self, faileditems): self._faileditems = faileditems @@ -101,16 +101,16 @@ failures = driver.run(cols) channel.send(failures) -class FailureCollector(py.test.Collector): +class FailureCollector(py.test.collect.Collector): def __init__(self, failures): self.failures = failures def __iter__(self): for rootpath, names in self.failures: root = py.path.local(rootpath) if root.check(dir=1): - current = py.test.Directory(root).Directory(root) + current = py.test.collect.Directory(root).Directory(root) elif root.check(file=1): - current = py.test.Module(root).Module(root) + current = py.test.collect.Module(root).Module(root) # root is fspath of names[0] -> pop names[0] # slicing works with empty lists names = names[1:] Modified: py/branch/py-collect/test/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal.py (original) +++ py/branch/py-collect/test/terminal.py Mon Apr 4 22:31:33 2005 @@ -90,7 +90,7 @@ if self.option.exitfirstproblem: py.test.exit("exit on first problem configured.", item=colitem) if result is None or not isinstance(colitem, py.test.Item): - if isinstance(colitem, py.test.Module) and self.option.verbose == 0: + if isinstance(colitem, py.test.collect.Module) and self.option.verbose == 0: self.out.line() return else: @@ -264,7 +264,7 @@ try: fn, lineno = item.getpathlineno() except TypeError: - assert isinstance(item.parent, py.test.Generator) + assert isinstance(item.parent, py.test.collect.Generator) # a generative test yielded a non-callable fn, lineno = item.parent.getpathlineno() if fn != entry.frame.code.path or \ Modified: py/branch/py-collect/test/testing/test_collect.py ============================================================================== --- py/branch/py-collect/test/testing/test_collect.py (original) +++ py/branch/py-collect/test/testing/test_collect.py Mon Apr 4 22:31:33 2005 @@ -6,7 +6,7 @@ def test_failing_import_execfile(): fn = datadir / 'failingimport.py' - col = py.test.Module(fn) + col = py.test.collect.Module(fn) def _import(): py.test.raises(ImportError, col.run) _import() @@ -14,9 +14,9 @@ def XXXtest_finds_root(): fn = datadir / 'filetest.py' - col = py.test.Module(fn) + col = py.test.collect.Module(fn) root, namelist = col.fromroot() - assert isinstance(root, py.test.Directory) + assert isinstance(root, py.test.collect.Directory) cur = root for x in namelist: cur = cur.join(x) @@ -26,7 +26,7 @@ def test_finds_tests(): fn = datadir / 'filetest.py' - col = py.test.Module(fn) + col = py.test.collect.Module(fn) l = col.run() assert len(l) == 2 items = list(col.iteritems()) @@ -37,28 +37,28 @@ assert l[1].fspath == fn def test_failing_import_directory(): - class MyDirectory(py.test.Directory): + class MyDirectory(py.test.collect.Directory): filefilter = py.path.checker(fnmatch='testspecial*.py') l = MyDirectory(datadir).run() assert len(l) == 1 - assert isinstance(l[0], py.test.Module) + assert isinstance(l[0], py.test.collect.Module) py.test.raises(ImportError, l[0].run) def test_module_file_not_found(): fn = datadir.join('nada','no') - col = py.test.Module(fn) + col = py.test.collect.Module(fn) py.test.raises(py.error.ENOENT, col.run) def test_syntax_error_in_module(): modpath = py.path.extpy(datadir.join('syntax_error.py')) - col = py.test.Module(modpath) + col = py.test.collect.Module(modpath) py.test.raises(SyntaxError, col.run) def test_disabled_class(): - col = py.test.Module(datadir.join('disabled.py')) + col = py.test.collect.Module(datadir.join('disabled.py')) l = col.run() assert len(l) == 1 - assert isinstance(l[0], py.test.Class) + assert isinstance(l[0], py.test.collect.Class) assert not l[0].run() class Testsomeclass: @@ -94,13 +94,13 @@ yield func1, 17, 3*5 yield func1, 42, 6*7 """)) - col = py.test.Module(tfile) + col = py.test.collect.Module(tfile) l = col.run() assert len(l) == 2 generator = l[0] print l - assert isinstance(generator, py.test.Generator) + assert isinstance(generator, py.test.collect.Generator) l2 = generator.run() assert len(l2) == 2 assert isinstance(l2[0], py.test.Function) @@ -113,7 +113,7 @@ classlist = l[1].run() assert len(classlist) == 1 generator = classlist[0].run()[0] - assert isinstance(generator, py.test.Generator) + assert isinstance(generator, py.test.collect.Generator) l2 = generator.run() assert len(l2) == 2 assert isinstance(l2[0], py.test.Function) @@ -127,7 +127,7 @@ import py class MyFunction(py.test.Function): pass - class Directory(py.test.Directory): + class Directory(py.test.collect.Directory): def filefilter(self, fspath): return fspath.check(basestarts='check_') class myfuncmixin: @@ -135,10 +135,10 @@ def funcnamefilter(self, name): return name.startswith('check_') - class Module(myfuncmixin, py.test.Module): + class Module(myfuncmixin, py.test.collect.Module): def classnamefilter(self, name): return name.startswith('CustomTestClass') - class Instance(myfuncmixin, py.test.Instance): + class Instance(myfuncmixin, py.test.collect.Instance): pass """) o.ensure('somedir', 'check_something').write("""if 1: Modified: py/branch/py-collect/test/tkinter/gui.py ============================================================================== --- py/branch/py-collect/test/tkinter/gui.py (original) +++ py/branch/py-collect/test/tkinter/gui.py Mon Apr 4 22:31:33 2005 @@ -3,7 +3,7 @@ from Tkinter import PhotoImage import py Item = py.test.Item -Collector = py.test.Collector +Collector = py.test.collect.Collector class ResultTree(Tree): Modified: py/branch/py-collect/test/tkinter/guidriver.py ============================================================================== --- py/branch/py-collect/test/tkinter/guidriver.py (original) +++ py/branch/py-collect/test/tkinter/guidriver.py Mon Apr 4 22:31:33 2005 @@ -22,7 +22,7 @@ py.test.Item.Skipped, py.test.Item.Failed)): print '!' *80 - print 'py.test.Collector.Outcome found' + print 'py.test.item.Outcome found' print '!' *80 print '-' * 60 Modified: py/branch/py-collect/test/tkinter/repository.py ============================================================================== --- py/branch/py-collect/test/tkinter/repository.py (original) +++ py/branch/py-collect/test/tkinter/repository.py Mon Apr 4 22:31:33 2005 @@ -1,8 +1,6 @@ import py Item = py.test.Item -Collector = py.test.Collector - - +Collector = py.test.collect.Collector import copy import time Modified: py/branch/py-collect/test/tkinter/test_guidriver.py ============================================================================== --- py/branch/py-collect/test/tkinter/test_guidriver.py (original) +++ py/branch/py-collect/test/tkinter/test_guidriver.py Mon Apr 4 22:31:33 2005 @@ -49,7 +49,7 @@ ## def test_status_is_passed_to_root(self): ## self.driver.header(self.collitems) ## self.driver.start(self.collitems[0]) -## self.driver.finish(self.collitems[0], py.test.Collector.Failed()) +## self.driver.finish(self.collitems[0], py.test.collect.Collector.Failed()) ## self.driver.footer(self.collitems) ## assert self.channel.sendlist[-1] is None Modified: py/branch/py-collect/test/tkinter/test_util.py ============================================================================== --- py/branch/py-collect/test/tkinter/test_util.py (original) +++ py/branch/py-collect/test/tkinter/test_util.py Mon Apr 4 22:31:33 2005 @@ -58,7 +58,7 @@ def setup_method(self, method): self.path = py.path.local() - self.collector = py.test.Directory(self.path) + self.collector = py.test.collect.Directory(self.path) self.testresult = TestReport() def test_start(self): @@ -80,7 +80,7 @@ ## def test_finish_failed(self): ## self.testresult.start(self.collector) -## self.testresult.finish(self.collector, py.test.Collector.Failed()) +## self.testresult.finish(self.collector, py.test.collect.Collector.Failed()) ## assert self.testresult.status == Status.Failed() Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Mon Apr 4 22:31:33 2005 @@ -12,7 +12,7 @@ import py from py.test import Driver Item = py.test.Item -Collector = py.test.Collector +Collector = py.test.collect.Collector import repository import util Modified: py/branch/py-collect/test/tkinter/util.py ============================================================================== --- py/branch/py-collect/test/tkinter/util.py (original) +++ py/branch/py-collect/test/tkinter/util.py Mon Apr 4 22:31:33 2005 @@ -25,21 +25,25 @@ class Status(object): - @classmethod def NotExecuted(cls): return cls('NotExecuted') - @classmethod + NotExecuted = classmethod(NotExecuted) + def Passed(cls): return cls('Passed') - @classmethod + Passed = classmethod(Passed) + def Failed(cls): return cls('Failed') - @classmethod + Failed = classmethod(Failed) + def Skipped(cls): return cls('Skipped') - @classmethod + Skipped = classmethod(Skipped) + def ExceptionFailure(cls): return cls(_ExceptionFailure) + ExceptionFailure = classmethod(ExceptionFailure) ordered_list = [_NotExecuted, _Passed, @@ -55,7 +59,7 @@ def __init__(self, outcome_or_name = ''): self.str = _NotExecuted - if isinstance(outcome_or_name, py.test.Collector.Outcome): + if isinstance(outcome_or_name, py.test.Item.Outcome): # hack if isinstance(outcome_or_name, py.test.Item.ExceptionFailure): self.str = self.namemap[py.test.Item.ExceptionFailure] @@ -118,12 +122,11 @@ 'finished': False, 'restart_params': None, # ('',('',)) } - @classmethod def fromChannel(cls, kwdict): if 'status' in kwdict: kwdict['status'] = Status(kwdict['status']) return cls(**kwdict) - + fromChannel = classmethod(fromChannel) def __init__(self, **kwargs): # copy status -> deepcopy From hpk at codespeak.net Mon Apr 4 22:37:46 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 4 Apr 2005 22:37:46 +0200 (MEST) Subject: [py-svn] r10308 - in py/branch/py-collect/test/tkinter: . icon icons testing Message-ID: <20050404203746.CC47027BB9@code1.codespeak.net> Author: hpk Date: Mon Apr 4 22:37:46 2005 New Revision: 10308 Added: py/branch/py-collect/test/tkinter/icon/ - copied from r10306, py/branch/py-collect/test/tkinter/icons/ py/branch/py-collect/test/tkinter/testing/ (props changed) py/branch/py-collect/test/tkinter/testing/__init__.py - copied unchanged from r10307, py/branch/py-collect/test/tkinter/__init__.py py/branch/py-collect/test/tkinter/testing/test_exceptionfailure.py - copied unchanged from r10307, py/branch/py-collect/test/tkinter/test_exceptionfailure.py py/branch/py-collect/test/tkinter/testing/test_guidriver.py - copied unchanged from r10307, py/branch/py-collect/test/tkinter/test_guidriver.py py/branch/py-collect/test/tkinter/testing/test_repository.py - copied unchanged from r10307, py/branch/py-collect/test/tkinter/test_repository.py py/branch/py-collect/test/tkinter/testing/test_util.py - copied unchanged from r10307, py/branch/py-collect/test/tkinter/test_util.py Removed: py/branch/py-collect/test/tkinter/icons/ py/branch/py-collect/test/tkinter/test_exceptionfailure.py py/branch/py-collect/test/tkinter/test_guidriver.py py/branch/py-collect/test/tkinter/test_repository.py py/branch/py-collect/test/tkinter/test_util.py Modified: py/branch/py-collect/test/tkinter/gui.py Log: more changes towards coding-style compliance :-) Modified: py/branch/py-collect/test/tkinter/gui.py ============================================================================== --- py/branch/py-collect/test/tkinter/gui.py (original) +++ py/branch/py-collect/test/tkinter/gui.py Mon Apr 4 22:37:46 2005 @@ -26,7 +26,7 @@ self.bind('<5>', self.next) def setup_icons(self): - iconpath = py.magic.autopath().dirpath('icons') + iconpath = py.magic.autopath().dirpath('icon') self.icon_green = PhotoImage(file = str(iconpath.join('green.gif'))) self.icon_red = PhotoImage(file = str(iconpath.join('red.gif'))) self.icon_yellow = PhotoImage(file = str(iconpath.join('yellow.gif'))) Deleted: /py/branch/py-collect/test/tkinter/test_exceptionfailure.py ============================================================================== --- /py/branch/py-collect/test/tkinter/test_exceptionfailure.py Mon Apr 4 22:37:46 2005 +++ (empty file) @@ -1,7 +0,0 @@ - -import time - -import py - -def test_exceptionfailure(): - py.test.raises(TypeError, 'time.time()') Deleted: /py/branch/py-collect/test/tkinter/test_guidriver.py ============================================================================== --- /py/branch/py-collect/test/tkinter/test_guidriver.py Mon Apr 4 22:37:46 2005 +++ (empty file) @@ -1,63 +0,0 @@ - -import py -import guidriver -GuiDriver = guidriver.GuiDriver - -from util import Status, TestReport, Null - -class TestGuiDriver: - - class ChannelMock: - - def __init__(self, receinvelist = []): - self.reset(receinvelist) - - def reset(self, receivelist = []): - self.receivelist = receivelist - self.sendlist = [] - - def send(self, obj): - self.sendlist.append(obj) - - def receive(self): - return self.receivelist.pop(0) - - def setup_method(self, method): - self.channel = self.ChannelMock() - self.driver = GuiDriver(Null(), self.channel) - self.collitems = [Null(), Null()] - - def test_header_sends_report_with_id_root(self): - self.driver.header(self.collitems) - - assert self.channel.sendlist != [] - report = TestReport.fromChannel(self.channel.sendlist[0]) - assert report.status == Status.NotExecuted() - assert report.id == 'Root' - assert report.label == 'Root' - - def test_footer_sends_report_and_None(self): - self.driver.header(self.collitems) - self.driver.footer(self.collitems) - - assert self.channel.sendlist != [] - assert self.channel.sendlist[-1] is None - report = TestReport.fromChannel(self.channel.sendlist[-2]) - assert report.status == Status.NotExecuted() - assert report.id == 'Root' - -## def test_status_is_passed_to_root(self): -## self.driver.header(self.collitems) -## self.driver.start(self.collitems[0]) -## self.driver.finish(self.collitems[0], py.test.collect.Collector.Failed()) -## self.driver.footer(self.collitems) - -## assert self.channel.sendlist[-1] is None -## assert self.channel.sendlist.pop() is None - -## report = TestReport.fromChannel(self.channel.sendlist[-1]) -## assert report.name == 'Root' -## assert report.status == Status.Failed() - - - Deleted: /py/branch/py-collect/test/tkinter/test_repository.py ============================================================================== --- /py/branch/py-collect/test/tkinter/test_repository.py Mon Apr 4 22:37:46 2005 +++ (empty file) @@ -1,170 +0,0 @@ - -from repository import Repository, OrderedDict, OrderedDictMemo -import py -Item = py.test.Item - - -import itertools - - -class TestRepository: - - def setup_method(self, method): - self.rep = Repository() - - def test_add_find_single_value(self): - key = ['key'] - value = 'value' - self.rep.add(key, value) - assert self.rep.find(key) == value - - def test_add_works_like_update(self): - key = 'k e y'.split() - value = 'value' - value2 = 'value2' - self.rep.add(key, value) - self.rep.add(key, value2) - assert self.rep.find(key) == value2 - - def test_haskeyandvalue(self): - key = 'first_middle_last' - value = 'value' - self.rep.add(key, value) - assert self.rep.haskeyandvalue(key) - assert not self.rep.haskeyandvalue('first') - for index in range(1, len(key[0])): - assert not self.rep.haskeyandvalue(key[0:index]) - - def test_add_find_subkey(self): - key = ('key', 'subkey') - value = 'subvalue' - self.rep.add(key, value) - self.rep.add((key[0],), 'value') - assert self.rep.find(key) == value - - def test_find_raises_KeyError(self): - py.test.raises(KeyError, self.rep.find, 'nothing') - - def test_haskey(self): - self.rep.add('key', 'value') - assert self.rep.haskey('key') == True - assert self.rep.haskey('katja') == False - assert self.rep.haskey('ke') == True - - def test_find_children_empyt_repository(self): - assert self.rep.find_children() == [] - - def test_find_children(self): - self.rep.add(['c'], 'childvalue') - self.rep.add('c a'.split(), 'a') - self.rep.add('c b'.split(), 'b') - assert self.rep.find_children(['c']) == [ ['c','a'], ['c','b']] - assert self.rep.find_children() == [['c']] - - def test_find_children_with_tuple_key(self): - key = tuple('k e y'.split()) - value = 'value' - self.rep.add(key, value) - assert self.rep.find_children([]) == [['k']] - assert self.rep.find_children(('k', 'e')) == [['k', 'e', 'y']] - - def test_keys(self): - keys = [ 'a b c'.split(), 'a b'.split(), ['a']] - for key in keys: - self.rep.add(key, 'value') - assert len(keys) == len(self.rep.keys()) - for key in self.rep.keys(): - assert key in keys - for key in keys: - assert key in self.rep.keys() - - def test_delete_simple(self): - key = 'k' - value = 'value' - self.rep.add(key, value) - self.rep.delete(key) - assert self.rep.haskeyandvalue(key) == False - - - def test_removestallkeys_remove_all(self): - key = 'k e y'.split() - value = 'value' - self.rep.add(key, value) - node = self.rep.find_tuple(key) - node[0] = self.rep.newnode()[0] - self.rep.removestalekeys(key) - assert self.rep.keys() == [] - - def test_removestallkeys_dont_remove_parent(self): - key = 'k e y'.split() - key2 = 'k e y 2'.split() - value = 'value' - self.rep.add(key, value) - self.rep.add(key2, self.rep.newnode()[0]) - self.rep.removestalekeys(key2) - assert self.rep.haskey(key2) == False - assert self.rep.haskeyandvalue(key) - - def test_removestallkeys_works_with_parameter_root(self): - self.rep.removestalekeys([]) - - def test_copy(self): - key = 'k e y'.split() - key2 = 'k e y 2'.split() - value = 'value' - self.rep.add(key, value) - self.rep.add(key2, value) - newrep = self.rep.copy() - assert newrep.root is not self.rep.root - assert newrep.find(key) == self.rep.find(key) - - - -class TestOrderedDict: - - def setup_method(self, method): - self.dict = OrderedDict() - - def test_add(self): - self.dict['key'] = 'value' - assert 'key' in self.dict - - def test_order(self): - keys = range(3) - for k in keys: - self.dict[k] = str(k) - assert keys == self.dict.keys() - -class TestOrderedDictMemo(TestOrderedDict): - - def setup_method(self, method): - self.dict = OrderedDictMemo() - - def test_insert(self): - self.dict['key1'] = 1 - self.dict['key2'] = 2 - del self.dict['key1'] - self.dict['key1'] = 1 - assert self.dict.keys() == ['key1', 'key2'] - - -class Test_Failing: - """ will fail!!""" - - def test_fail(self): - print 'recorded stdout' - m = dict(((1,2),(3,4))) - l = (1,m) - assert False == l - - def test_calling_fail(self): - py.test.fail('Try to fail') - - def test_skip(self): - print 'recorded stdout' - py.test.skip('Try to skip this test') - - def test_skip_on_error(self): - py.test.skip_on_error(''.index, 'hello') - - Deleted: /py/branch/py-collect/test/tkinter/test_util.py ============================================================================== --- /py/branch/py-collect/test/tkinter/test_util.py Mon Apr 4 22:37:46 2005 +++ (empty file) @@ -1,118 +0,0 @@ - -import util -from util import Status, TestReport, OutBuffer -import py -Item = py.test.Item - -class TestStatus: - - def test_init_with_None(self): - status = Status(None) - assert status == status.NotExecuted() - - def test_str(self): - status = Status(Item.Passed()) - assert status == status.Passed() - - status = Status(Item.Failed()) - assert status == status.Failed() - - status = Status(Item.Skipped()) - assert status == status.Skipped() - - status = Status(Item.ExceptionFailure()) - assert status == status.ExceptionFailure() - - - def test_init_with_bad_name(self): - status = Status('nothing') - assert status == Status.NotExecuted() - - def test_init_with_good_name(self): - def check_str(obj, expected): - assert str(obj) == expected - - for name in Status.ordered_list: - yield check_str, Status(name), name - - def test_update(self): - failed = Status.Failed() - passed = Status.Passed() - failed.update(passed) - assert failed == Status.Failed() - - passed.update(failed) - assert passed == Status.Failed() - assert passed == failed - - def test_eq_(self): - passed = Status.Passed() - assert passed == passed - assert passed == Status.Passed() - - failed = Status.Failed() - assert failed != passed - - -class TestTestReport: - - def setup_method(self, method): - self.path = py.path.local() - self.collector = py.test.collect.Directory(self.path) - self.testresult = TestReport() - - def test_start(self): - self.testresult.start(self.collector) - - assert self.testresult.full_id == self.collector.listnames() - assert self.testresult.time != 0 - assert self.testresult.status == Status.NotExecuted() - - def test_finish(self): - self.testresult.start(self.collector) - - py.std.time.sleep(1.1) - - self.testresult.finish(self.collector, None) - assert self.testresult.time > 1 - assert self.testresult.status == Status.NotExecuted() - -## def test_finish_failed(self): -## self.testresult.start(self.collector) - -## self.testresult.finish(self.collector, py.test.collect.Collector.Failed()) -## assert self.testresult.status == Status.Failed() - - - - def test_toChannel_fromChannel(self): - assert isinstance(self.testresult.toChannel()['status'], str) - result = TestReport.fromChannel(self.testresult.toChannel()) - assert isinstance(result.status, Status) - - def test_copy(self): - result2 = self.testresult.copy() - assert self.testresult.status == Status.NotExecuted() - for key in TestReport.template.keys(): - assert getattr(result2, key) == getattr(self.testresult, key) - - self.testresult.status = Status.Failed() - assert result2.status != self.testresult.status - - -class Test_OutBuffer: - - def setup_method(self, method): - self.out = OutBuffer() - - def test_line(self): - oneline = 'oneline' - self.out.line(oneline) - assert self.out.getoutput() == oneline + '\n' - - def test_write(self): - item = 'item' - self.out.write(item) - assert self.out.getoutput() == item - - From hpk at codespeak.net Mon Apr 4 23:53:00 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 4 Apr 2005 23:53:00 +0200 (MEST) Subject: [py-svn] r10309 - py/branch/py-collect/test/tkinter/testing Message-ID: <20050404215300.7DE5F27BB9@code1.codespeak.net> Author: hpk Date: Mon Apr 4 23:53:00 2005 New Revision: 10309 Modified: py/branch/py-collect/test/tkinter/testing/test_guidriver.py py/branch/py-collect/test/tkinter/testing/test_repository.py py/branch/py-collect/test/tkinter/testing/test_util.py Log: fixed imports Modified: py/branch/py-collect/test/tkinter/testing/test_guidriver.py ============================================================================== --- py/branch/py-collect/test/tkinter/testing/test_guidriver.py (original) +++ py/branch/py-collect/test/tkinter/testing/test_guidriver.py Mon Apr 4 23:53:00 2005 @@ -1,10 +1,9 @@ import py -import guidriver +from py.__impl__.test.tkinter import guidriver +from py.__impl__.test.tkinter.util import Status, TestReport, Null GuiDriver = guidriver.GuiDriver -from util import Status, TestReport, Null - class TestGuiDriver: class ChannelMock: Modified: py/branch/py-collect/test/tkinter/testing/test_repository.py ============================================================================== --- py/branch/py-collect/test/tkinter/testing/test_repository.py (original) +++ py/branch/py-collect/test/tkinter/testing/test_repository.py Mon Apr 4 23:53:00 2005 @@ -1,5 +1,5 @@ -from repository import Repository, OrderedDict, OrderedDictMemo +from py.__impl__.test.tkinter.repository import Repository, OrderedDict, OrderedDictMemo import py Item = py.test.Item Modified: py/branch/py-collect/test/tkinter/testing/test_util.py ============================================================================== --- py/branch/py-collect/test/tkinter/testing/test_util.py (original) +++ py/branch/py-collect/test/tkinter/testing/test_util.py Mon Apr 4 23:53:00 2005 @@ -1,6 +1,6 @@ -import util -from util import Status, TestReport, OutBuffer +from py.__impl__.test.tkinter import util +from py.__impl__.test.tkinter.util import Status, TestReport, OutBuffer import py Item = py.test.Item From hpk at codespeak.net Tue Apr 5 16:09:34 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 5 Apr 2005 16:09:34 +0200 (CEST) Subject: [py-svn] r10319 - py/dist/py/documentation Message-ID: <20050405140934.C960F27B40@code1.codespeak.net> Author: hpk Date: Tue Apr 5 16:09:34 2005 New Revision: 10319 Modified: py/dist/py/documentation/test.txt Log: clarify "automatic collecting process" Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Tue Apr 5 16:09:34 2005 @@ -35,9 +35,10 @@ py.test -This will automatically collect any Python module that starts with -``test_`` in the whole directory hierarchy, starting with the current -directory, and run them. +This will automatically collect and run any Python module whose filenames +start with ``test_`` from the directory and any subdirectories, starting +with the current directory, and run them. Each Python test module is +inspect for test methods starting with ``test_``. Basic Features of ``py.test`` ============================= @@ -81,14 +82,15 @@ automatic collection of tests on all levels ------------------------------------------- -The automated test collection process collects files -from all subdirectories with a leading ``test_`` in their -filename and then every function with a leading ``test_`` -or ``Test`` in the case of classes. The collecting process -can be customized at each level. (see `collection process`_ +The automated test collection process walks the current +directory (or the directory given as a command line argument) +and all its subdirectories and collects python modules with a +leading ``test_`` filename. From each test module every function +with a leading ``test_`` or class with a leading ``Test`` name +is collected. The collecting process can be customized at +directory, module or class level. (see `collection process`_ for some implementation details). - .. _`generative tests`: generative tests: yielding more tests From hpk at codespeak.net Tue Apr 5 17:15:51 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 5 Apr 2005 17:15:51 +0200 (CEST) Subject: [py-svn] r10329 - in py/branch/py-collect/test/testing: import_test test Message-ID: <20050405151551.A6FBE27B57@code1.codespeak.net> Author: hpk Date: Tue Apr 5 17:15:51 2005 New Revision: 10329 Added: py/branch/py-collect/test/testing/import_test/ - copied from r10309, py/branch/py-collect/test/testing/test/import_test/ Removed: py/branch/py-collect/test/testing/test/ Log: (re)moved old code From hpk at codespeak.net Tue Apr 5 19:37:16 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 5 Apr 2005 19:37:16 +0200 (CEST) Subject: [py-svn] r10344 - in py/branch/py-collect: . test test/testing Message-ID: <20050405173716.CA39D27B60@code1.codespeak.net> Author: hpk Date: Tue Apr 5 19:37:16 2005 New Revision: 10344 Modified: py/branch/py-collect/__init__.py py/branch/py-collect/test/config.py py/branch/py-collect/test/defaultconfig.py py/branch/py-collect/test/testing/test_config.py Log: (somewhat questionable) refactoring of the cmdline parsing code. The underlying problem is messy: we want an easy "no-api" extensibility of the py.test session, especially adding command line options. But if a project provides custom command line options then it wants to access its values (usually from the conftest file itself), of course. But conftest.py modules are really loaded at global level (cached at sys.modules level) so nesting testing sessions becomes difficult due to the global-importedness of conftest.py files. (Nesting testing sessions is neccessary for testing py.test, btw :-) OTOH, we can't just execfile() the conftest.py files because we want to allow users to have e.g. "import myproject.conftest" work as expected. The latter is crucial if you want to share test configuration options across various directories in your project. Or am i missing something? Anyway, this commit works ok enough by introducing a new API "py.test.addoptions()" which you usually execute at conftest.py import time and it will add the given options _and_ return an option object (providing any custom cmdline option values) which you can then peruse from functions and custom test classes. Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/branch/py-collect/__init__.py (original) +++ py/branch/py-collect/__init__.py Tue Apr 5 19:37:16 2005 @@ -1,6 +1,7 @@ from initpkg import initpkg initpkg(__name__, exportdefs = { 'test.skip' : ('./test/item.py', 'skip'), + 'test.addoptions' : ('./test/config.py', 'addoptions'), 'test.skip_on_error' : ('./test/item.py', 'skip_on_error'), 'test.raises' : ('./test/raises.py', 'raises'), 'test.fail' : ('./test/item.py', 'fail'), Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Tue Apr 5 19:37:16 2005 @@ -67,8 +67,12 @@ configpaths = findconfigpaths(anchorpaths) parser = makeparser(configpaths) cmdlineoption, remaining = parser.parse_args(args) - fixoptions(cmdlineoption) - return cmdlineoption, remaining + uservalues = parser._pytestvalues + for name, value in vars(cmdlineoption).items(): + assert not hasattr(uservalues, name) + setattr(uservalues, name, value) + fixoptions(uservalues) + return uservalues, remaining # # helpers @@ -112,18 +116,42 @@ option.executable = exe def makeparser(configpaths): - # first a small fight with optparse to merge the - # conftest.py file options correctly + global _lastoptions, _option + # trigger loading config options + for p in configpaths: + p.resolve() + defaultconfig.join('adddefaultoptions').resolve()() parser = optparse.OptionParser() - for config in configpaths: - meth = config.join('options') - if meth.check(): - groupname, groupoptions = meth.resolve() - optgroup = optparse.OptionGroup(parser, groupname) - optgroup.add_options(groupoptions) - parser.add_option_group(optgroup) + for groupname, groupoptions in _lastoptions: + optgroup = optparse.OptionGroup(parser, groupname) + optgroup.add_options(groupoptions) + parser.add_option_group(optgroup) + # each time we make a parser which incorporates + # all the cmdline options that users may have provided + # we want to start over + parser._pytestvalues = _option + _lastoptions = [] + _option = optparse.Values() return parser +# XXX _lastoptions holds all options that are added from conftest +# py.test.addoptions() invocations. This "global state" +# manipulation is not completely nice but how else could we allow +# test projects to add cmdline options and provide them access to +# the resulting values? (apart from exposing driver initialisation +# some more which i really don't want to do) + +_lastoptions = [] +_option = optparse.Values() + +def addoptions(groupname, *specs): + """ add a named group of options to the current testing session. + This function gets invoked during testing session initialization. + """ + global _lastoptions + _lastoptions.append((groupname, specs)) + return _option + def findconfigpaths(anchors): """ return test configuration paths. """ configpaths = [] @@ -133,12 +161,10 @@ x = p.join(configbasename) if x.check(file=1): extpy = py.path.extpy(x) - extpy.resolve() # trigger loading it if extpy not in d: configpaths.append(extpy) d[extpy] = True configpaths.sort(lambda x,y: cmp(len(str(x)), len(str(y)))) - configpaths.append(defaultconfig) return configpaths def getcollectors(filenames): Modified: py/branch/py-collect/test/defaultconfig.py ============================================================================== --- py/branch/py-collect/test/defaultconfig.py (original) +++ py/branch/py-collect/test/defaultconfig.py Tue Apr 5 19:37:16 2005 @@ -12,7 +12,8 @@ additionalinfo = None -options = ('py.test standard options', [ +def adddefaultoptions(): + py.test.addoptions('py.test standard options', Option('-v', '--verbose', action="count", dest="verbose", default=0, help="increase verbosity"), @@ -43,4 +44,4 @@ Option('', '--collectonly', action="store_true", dest="collectonly", default=False, help="only collect tests, don't execute them. "), -]) + ) Modified: py/branch/py-collect/test/testing/test_config.py ============================================================================== --- py/branch/py-collect/test/testing/test_config.py (original) +++ py/branch/py-collect/test/testing/test_config.py Tue Apr 5 19:37:16 2005 @@ -28,6 +28,23 @@ assert d1 == d2 assert d1.check(dir=1) +def test_config_options(): + o = py.test.ensuretemp('configoptions') + o.ensure("conftest.py").write(py.code.Source(""" + import py + Option = py.test.Option + option = py.test.addoptions("testing group", + Option('-g', '--glong', action="store", default=42, + type="int", dest="gdest", help="g value."), + ) + """)) + old = o.chdir() + try: + driver, paths = py.test.config.init(['-g', '17']) + assert driver.option.gdest == 17 + finally: + old.chdir() + #def test_config_order(): # from py.__impl__.test import config # o = py.test.ensuretemp('configorder') From hpk at codespeak.net Tue Apr 5 20:42:01 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 5 Apr 2005 20:42:01 +0200 (CEST) Subject: [py-svn] r10347 - py/branch/py-collect/test Message-ID: <20050405184201.5505027B49@code1.codespeak.net> Author: hpk Date: Tue Apr 5 20:42:00 2005 New Revision: 10347 Modified: py/branch/py-collect/test/collect.py Log: sort listdir() results to make testfiles execute in the same canonical order. Modified: py/branch/py-collect/test/collect.py ============================================================================== --- py/branch/py-collect/test/collect.py (original) +++ py/branch/py-collect/test/collect.py Tue Apr 5 20:42:00 2005 @@ -184,7 +184,10 @@ return path.check(dotfile=0) def run(self): - l = [self.join(x.basename) for x in self.fspath.listdir() + l = self.fspath.listdir() + l.sort() + l.reverse() + l = [self.join(x.basename) for x in l if (x.check(file=1) and self.filefilter(x) or x.check(dir=1) and self.recfilter(x))] l.sort() From hpk at codespeak.net Wed Apr 6 23:45:55 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 6 Apr 2005 23:45:55 +0200 (CEST) Subject: [py-svn] r10379 - py/branch/py-collect/documentation Message-ID: <20050406214555.BB90627B49@code1.codespeak.net> Author: hpk Date: Wed Apr 6 23:45:55 2005 New Revision: 10379 Modified: py/branch/py-collect/documentation/future.txt Log: new chapters in the "future book"! Modified: py/branch/py-collect/documentation/future.txt ============================================================================== --- py/branch/py-collect/documentation/future.txt (original) +++ py/branch/py-collect/documentation/future.txt Wed Apr 6 23:45:55 2005 @@ -351,3 +351,62 @@ .. _`PEP-324 subprocess module`: http://www.python.org/peps/pep-0324.html .. _`subprocess implementation`: http://www.lysator.liu.se/~astrand/popen5/ .. _`py.test`: test.html + +Refactor path implementations to use a Filesystem Abstraction +============================================================= + +It seems a good idea to refactor all python implementations to +use an internal Filesystem abstraction. There would be +Filesystem implementations for e.g. local, subversion +and subversion working copy filesystems. Today the +according code is scattered through the actual path-handling +code. + +On a related note, Armin has hacked `pylufs`_ which allows to +write linux filesystems at pure python level. The idea is that +the new filesystems objects would be implemented in a way that +make them directly usable for the linux-filesystem glue code. +Implementing a `memoryfs`_ or a `dictfs`_ should then give +you for free: a filesystem mountable at linux kernel level +as well as a uniform "path" object allowing you to traverse +and manipulate the filesystem tree. At some point it might +become interesting to interface to some `reiserfs v4 stuff`_ +at the Filesystem level. + + +.. _`memoryfs`: http://codespeak.net/svn/user/arigo/hack/pylufs/memoryfs.py +.. _`dictfs`: http://codespeak.net/pipermail/py-dev/2005-January/000191.html +.. _`pylufs`: http://codespeak.net/svn/user/arigo/hack/pylufs/ +.. _`reiserfs v4 stuff`: http://www.namesys.com/v4/v4.html + + +Improvde and unify Path API +=========================== + +visit() grows depth control +--------------------------- + +Add a ``maxdepth`` argument to the path.visit() method, +which will limit traversal to subdirectories. Example:: + + x = py.path.local.get_tmproot() + for x in x.visit('bin', maxdepth=N): + ... + +This will with find all files or directories named 'bin', +depending on the values of ``maxdepth``:: + + x # maxdepth == 0 (and x.basename == 'bin') + x / bin # maxdepth == 1 + x / ... / bin # maxdepth == 2 + x / ... / ... / bin # maxdepth == 3 + +What if `x < 0`? We could let that indicate just going upwards. + for x in x.visit('py/bin', maxdepth=-255): + # will yield all parent direcotires which have a + # py/bin subpath + # + +Is `maxdepth` a good name? Should this functionality live +on a different method alltogether? + From hpk at codespeak.net Wed Apr 6 23:53:29 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 6 Apr 2005 23:53:29 +0200 (CEST) Subject: [py-svn] r10380 - py/branch/py-collect/test Message-ID: <20050406215329.D5BDA27B49@code1.codespeak.net> Author: hpk Date: Wed Apr 6 23:53:29 2005 New Revision: 10380 Modified: py/branch/py-collect/test/drive.py Log: give custom Outcomes more freedom Modified: py/branch/py-collect/test/drive.py ============================================================================== --- py/branch/py-collect/test/drive.py (original) +++ py/branch/py-collect/test/drive.py Wed Apr 6 23:53:29 2005 @@ -75,7 +75,8 @@ res = None raise except colitem.Outcome, res: - res.excinfo = py.code.ExceptionInfo() + if not hasattr(res, 'excinfo'): + res.excinfo = py.code.ExceptionInfo() except: excinfo = py.code.ExceptionInfo() res = colitem.Failed(excinfo=excinfo) From hpk at codespeak.net Wed Apr 6 23:54:43 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 6 Apr 2005 23:54:43 +0200 (CEST) Subject: [py-svn] r10381 - py/branch/py-collect/code Message-ID: <20050406215443.B9B7927B49@code1.codespeak.net> Author: hpk Date: Wed Apr 6 23:54:43 2005 New Revision: 10381 Modified: py/branch/py-collect/code/frame.py Log: just return None if no filename/path can be determined for a frame object (or rather it's code object). Modified: py/branch/py-collect/code/frame.py ============================================================================== --- py/branch/py-collect/code/frame.py (original) +++ py/branch/py-collect/code/frame.py Wed Apr 6 23:54:43 2005 @@ -15,7 +15,10 @@ try: return self.raw.co_filename.__path__ except AttributeError: - return py.path.local(self.raw.co_filename) + try: + return py.path.local(self.raw.co_filename) + except ValueError: + return None path = property(path, None, None, "path of this code object") def fullsource(self): From hpk at codespeak.net Thu Apr 7 00:18:47 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 7 Apr 2005 00:18:47 +0200 (CEST) Subject: [py-svn] r10382 - py/branch/py-collect/code Message-ID: <20050406221847.53A6427B44@code1.codespeak.net> Author: hpk Date: Thu Apr 7 00:18:47 2005 New Revision: 10382 Modified: py/branch/py-collect/code/frame.py Log: the underlying code object is an implementation detail ... (consider IronPython or Jython which have a different code object model) Modified: py/branch/py-collect/code/frame.py ============================================================================== --- py/branch/py-collect/code/frame.py (original) +++ py/branch/py-collect/code/frame.py Thu Apr 7 00:18:47 2005 @@ -4,7 +4,7 @@ def __init__(self, f_code): f_code = getattr(f_code, 'im_func', f_code) f_code = getattr(f_code, 'func_code', f_code) - self.raw = f_code + self._raw = f_code try: self.firstlineno = f_code.co_firstlineno - 1 except AttributeError: @@ -13,22 +13,22 @@ def path(self): try: - return self.raw.co_filename.__path__ + return self._raw.co_filename.__path__ except AttributeError: try: - return py.path.local(self.raw.co_filename) + return py.path.local(self._raw.co_filename) except ValueError: return None - path = property(path, None, None, "path of this code object") + path = property(path, None, None, "path to source of this code object") def fullsource(self): - fn = self.raw.co_filename + fn = self._raw.co_filename try: return fn.__source__ except AttributeError: return py.code.Source(self.path.read(mode="rU")) fullsource = property(fullsource, None, None, - "full source containing this code object") + "full source representing this code object") class Frame(object): From hpk at codespeak.net Thu Apr 7 00:19:11 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 7 Apr 2005 00:19:11 +0200 (CEST) Subject: [py-svn] r10383 - py/branch/py-collect/test Message-ID: <20050406221911.D2F6D27B44@code1.codespeak.net> Author: hpk Date: Thu Apr 7 00:19:11 2005 New Revision: 10383 Modified: py/branch/py-collect/test/drive.py Log: fix a few code path Modified: py/branch/py-collect/test/drive.py ============================================================================== --- py/branch/py-collect/test/drive.py (original) +++ py/branch/py-collect/test/drive.py Thu Apr 7 00:19:11 2005 @@ -68,11 +68,11 @@ capture = SimpleOutErrCapture() try: self.start(colitem) + res = None try: try: res = self.runinner(colitem) except (KeyboardInterrupt, Exit): - res = None raise except colitem.Outcome, res: if not hasattr(res, 'excinfo'): From hpk at codespeak.net Thu Apr 7 00:20:08 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 7 Apr 2005 00:20:08 +0200 (CEST) Subject: [py-svn] r10384 - in py/branch/py-collect: . code Message-ID: <20050406222008.07D3427B44@code1.codespeak.net> Author: hpk Date: Thu Apr 7 00:20:07 2005 New Revision: 10384 Added: py/branch/py-collect/code/traceback2.py - copied unchanged from r10344, py/branch/py-collect/code/traceback.py Removed: py/branch/py-collect/code/traceback.py Modified: py/branch/py-collect/__init__.py Log: rename traceback to a different name than the standard library 'traceback' module Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/branch/py-collect/__init__.py (original) +++ py/branch/py-collect/__init__.py Thu Apr 7 00:20:07 2005 @@ -50,7 +50,7 @@ 'code.Code' : ('./code/frame.py', 'Code'), 'code.Frame' : ('./code/frame.py', 'Frame'), 'code.ExceptionInfo' : ('./code/excinfo.py', 'ExceptionInfo'), - 'code.Traceback' : ('./code/traceback.py', 'Traceback'), + 'code.Traceback' : ('./code/traceback2.py', 'Traceback'), 'builtin.enumerate' : ('./builtin/enumerate.py', 'enumerate'), Deleted: /py/branch/py-collect/code/traceback.py ============================================================================== --- /py/branch/py-collect/code/traceback.py Thu Apr 7 00:20:07 2005 +++ (empty file) @@ -1,80 +0,0 @@ -from __future__ import generators -import py - -class TracebackEntry(object): - exprinfo = None - - def __init__(self, rawentry): - self._rawentry = rawentry - self.frame = py.code.Frame(rawentry.tb_frame) - self.lineno = rawentry.tb_lineno - 1 - - def __repr__(self): - return "" %(self.frame.code.path, self.lineno+1) - - def statement(self): - source = self.frame.code.fullsource - return source.getstatement(self.lineno) - statement = property(statement, None, None, - "statement of this traceback entry.") - - def path(self): - return self.frame.code.path - path = property(path, None, None, "path to the full source code") - - def reinterpret(self): - """Reinterpret the failing statement and returns a detailed information - about what operations are performed.""" - if self.exprinfo is None: - from py.__impl__.magic import exprinfo - source = str(self.statement).strip() - x = exprinfo.interpret(source, self.frame, should_fail=True) - if not isinstance(x, str): - raise TypeError, "interpret returned non-string %r" % (x,) - self.exprinfo = x - return self.exprinfo - - def getsource(self): - """ return failing source code. """ - source = self.frame.code.fullsource - start, end = self.frame.code.firstlineno, self.lineno - try: - _, end = source.getstatementrange(end) - except IndexError: - end = self.lineno + 1 - # heuristic to stop displaying source on e.g. - # if something: # assume this causes a NameError - # # _this_ lines and the one - # below we don't want from entry.getsource() - for i in range(self.lineno, end): - if source[i].rstrip().endswith(':'): - end = i + 1 - break - return source[start:end] - - def __str__(self): - try: - fn = str(self.path) - except py.error.Error: - fn = '???' - name = self.frame.code.name - line = str(self.statement).lstrip() - return " File %r:%d in %s\n %s\n" %(fn, self.lineno+1, name, line) - -class Traceback(list): - 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)) - -# def __str__(self): -# for x in self -# l = [] -## for func, entry in self._tblist: -# l.append(entry.display()) -# return "".join(l) - From hpk at codespeak.net Thu Apr 7 01:02:04 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 7 Apr 2005 01:02:04 +0200 (CEST) Subject: [py-svn] r10386 - py/branch/py-collect/documentation Message-ID: <20050406230204.0FD5627B47@code1.codespeak.net> Author: hpk Date: Thu Apr 7 01:02:03 2005 New Revision: 10386 Modified: py/branch/py-collect/documentation/future.txt Log: typo Modified: py/branch/py-collect/documentation/future.txt ============================================================================== --- py/branch/py-collect/documentation/future.txt (original) +++ py/branch/py-collect/documentation/future.txt Thu Apr 7 01:02:03 2005 @@ -380,8 +380,8 @@ .. _`reiserfs v4 stuff`: http://www.namesys.com/v4/v4.html -Improvde and unify Path API -=========================== +Improve and unify Path API +========================== visit() grows depth control --------------------------- From jan at codespeak.net Thu Apr 7 10:04:23 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Thu, 7 Apr 2005 10:04:23 +0200 (CEST) Subject: [py-svn] r10395 - in py/branch/py-collect/test/tkinter: . testing Message-ID: <20050407080423.4D66827B48@code1.codespeak.net> Author: jan Date: Thu Apr 7 10:04:23 2005 New Revision: 10395 Removed: py/branch/py-collect/test/tkinter/testing/test_exceptionfailure.py Modified: py/branch/py-collect/test/tkinter/guidriver.py py/branch/py-collect/test/tkinter/repository.py py/branch/py-collect/test/tkinter/testing/test_repository.py py/branch/py-collect/test/tkinter/testing/test_util.py py/branch/py-collect/test/tkinter/tkgui.py py/branch/py-collect/test/tkinter/util.py Log: cleaned up code added label to GUI with information about selected tests Modified: py/branch/py-collect/test/tkinter/guidriver.py ============================================================================== --- py/branch/py-collect/test/tkinter/guidriver.py (original) +++ py/branch/py-collect/test/tkinter/guidriver.py Thu Apr 7 10:04:23 2005 @@ -1,9 +1,7 @@ - +'''GuiDriver builds TestReport instances and sends them to tkgui.Manager''' import py -from util import Status, TestReport, Null -from py.__impl__.test.drive import Exit, exit, SimpleOutErrCapture - -import pprint +from util import TestReport +from py.__impl__.test.drive import Exit, SimpleOutErrCapture class GuiDriver(py.test.Driver): @@ -12,19 +10,6 @@ self.channel = channel self.reportlist = [] - def info(self, **kwargs): - return - print '-' * 60 - #print 'reportlist:', self.reportlist - for key, value in kwargs.items(): - print key,':', pprint.pformat(value) - if isinstance(kwargs.get('res'), (py.test.Item.Passed, - py.test.Item.Skipped, - py.test.Item.Failed)): - print '!' *80 - print 'py.test.item.Outcome found' - print '!' *80 - print '-' * 60 def header(self, colitems): super(GuiDriver, self).header(colitems) @@ -57,7 +42,7 @@ #py.std.time.sleep(0.5) def sendreport(self, report): - self.channel.send(report.toChannel()) + self.channel.send(report.to_channel()) def warning(self, msg): pass Modified: py/branch/py-collect/test/tkinter/repository.py ============================================================================== --- py/branch/py-collect/test/tkinter/repository.py (original) +++ py/branch/py-collect/test/tkinter/repository.py Thu Apr 7 10:04:23 2005 @@ -5,25 +5,11 @@ import copy import time -from util import Null -from itertools import izip, count - import UserDict -## import logging -## log = logging -## def initlog(): -## global log -## remote = logging.FileHandler('./remote.log') -## remote.setLevel(logging.DEBUG) -## formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') -## remote.setFormatter(formatter) -## log = logging.getLogger('remote') -## log.addHandler(remote) - - class Repository(object): + '''like a trie''' nothing = object() def __init__(self): @@ -106,7 +92,7 @@ class OrderedDict(UserDict.DictMixin): - + '''like a normal dict, but keys are ordered by time of setting''' def __init__(self, *args, **kwargs): self._dict = dict(*args, **kwargs) self._keys = self._dict.keys() @@ -136,7 +122,7 @@ return new class OrderedDictMemo(UserDict.DictMixin): - + '''memorize all keys and how they were ordered''' def __init__(self, *args, **kwargs): self._dict = dict(*args, **kwargs) self._keys = self._dict.keys() Deleted: /py/branch/py-collect/test/tkinter/testing/test_exceptionfailure.py ============================================================================== --- /py/branch/py-collect/test/tkinter/testing/test_exceptionfailure.py Thu Apr 7 10:04:23 2005 +++ (empty file) @@ -1,7 +0,0 @@ - -import time - -import py - -def test_exceptionfailure(): - py.test.raises(TypeError, 'time.time()') Modified: py/branch/py-collect/test/tkinter/testing/test_repository.py ============================================================================== --- py/branch/py-collect/test/tkinter/testing/test_repository.py (original) +++ py/branch/py-collect/test/tkinter/testing/test_repository.py Thu Apr 7 10:04:23 2005 @@ -148,23 +148,5 @@ assert self.dict.keys() == ['key1', 'key2'] -class Test_Failing: - """ will fail!!""" - - def test_fail(self): - print 'recorded stdout' - m = dict(((1,2),(3,4))) - l = (1,m) - assert False == l - - def test_calling_fail(self): - py.test.fail('Try to fail') - - def test_skip(self): - print 'recorded stdout' - py.test.skip('Try to skip this test') - - def test_skip_on_error(self): - py.test.skip_on_error(''.index, 'hello') Modified: py/branch/py-collect/test/tkinter/testing/test_util.py ============================================================================== --- py/branch/py-collect/test/tkinter/testing/test_util.py (original) +++ py/branch/py-collect/test/tkinter/testing/test_util.py Thu Apr 7 10:04:23 2005 @@ -86,8 +86,8 @@ def test_toChannel_fromChannel(self): - assert isinstance(self.testresult.toChannel()['status'], str) - result = TestReport.fromChannel(self.testresult.toChannel()) + assert isinstance(self.testresult.to_channel()['status'], str) + result = TestReport.fromChannel(self.testresult.to_channel()) assert isinstance(result.status, Status) def test_copy(self): Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Thu Apr 7 10:04:23 2005 @@ -39,6 +39,8 @@ def createwidgets(self): self.mainframe = tk.Frame(self.parent) + self.reportlabel = tk.Label(self.mainframe, text = ' \n ', anchor=tk.W) + self.reportlabel.pack() self.treeframe = tk.Frame(self.mainframe) self.tree = gui.ResultTree(master=self.treeframe, root_id='Root', get_contents_callback=self.create_new_nodes, @@ -80,7 +82,8 @@ #self.text.yview_pickplace(tk.END) self.attacheditorhotspots(self.text) self.text['state'] = tk.DISABLED - pass + + self.reportlabel['text'] = '%s\n%s' % (report.fspath, report.modpath) def attacheditorhotspots(self, text): # Attach clickable regions to a Text widget. @@ -136,8 +139,12 @@ folder = 1 self.tree.add_node(name=name, id=report.id, flag=folder, report = report) - def display_action(self, action): + def display_action(self, action, node_id=None): self.parent.title(str(action)) + if node_id is not None: + report = self.repositorycommand().find(node_id) + self.reportlabel['text'] = '%s\n%s' % (report.fspath, + report.modpath) def process_messages(self, manager): """ @@ -155,7 +162,7 @@ self.parent.bell() else: self.tree.update_node(full_id) - self.display_action('running: ' + str(full_id[-1]) ) + self.display_action('running: ' + str(full_id[-1]), full_id ) except Queue.Empty: pass self.tree.update_root(self.repositorycommand().find(self.tree.root.full_id())) Modified: py/branch/py-collect/test/tkinter/util.py ============================================================================== --- py/branch/py-collect/test/tkinter/util.py (original) +++ py/branch/py-collect/test/tkinter/util.py Thu Apr 7 10:04:23 2005 @@ -1,4 +1,6 @@ -import types, string, sys +'''some classes to handle text reports''' + +import sys import py from py.__impl__.test.report.text import out from py.__impl__.test.terminal import TerminalDriver @@ -24,6 +26,8 @@ _ExceptionFailure = 'ExceptionFailure' class Status(object): + '''Represents py.test.Collector.Outcome as a string. + Possible values: NotExecuted, Passed, Skipped, Failed, ExceptionFailure''' def NotExecuted(cls): return cls('NotExecuted') @@ -78,10 +82,13 @@ return self.str def update(self, status): + '''merge self and status, self will be set to the "higher" status + in ordered_list''' name_int_map = dict( py.std.itertools.izip(self.ordered_list, py.std.itertools.count())) - self.str = self.ordered_list[max([name_int_map[i] for i in (str(status), self.str)])] + self.str = self.ordered_list[max([name_int_map[i] + for i in (str(status), self.str)])] def __eq__(self, other): return self.str == other.str @@ -90,7 +97,8 @@ return not self.__eq__(other) class OutBuffer(out.Out): - + '''Simple MockObject for py.__impl__.test.report.text.out.Out. + Used to get the output of TerminalDriver.''' def __init__(self, fullwidth = 80 -1): self.output = [] self.fullwidth = fullwidth @@ -111,6 +119,7 @@ class TestReport(object): + '''"Channel-save" report of a py.test.Collector.Outcome instance''' template = {'time' : 0, 'label': 'Root', @@ -121,8 +130,12 @@ 'error_report': '', 'finished': False, 'restart_params': None, # ('',('',)) + 'fspath' : '', + 'modpath': '', } + def fromChannel(cls, kwdict): + ''' TestReport.fromChannel(report.toChannel()) == report ''' if 'status' in kwdict: kwdict['status'] = Status(kwdict['status']) return cls(**kwdict) @@ -136,6 +149,7 @@ setattr(self, key, value) def start(self, collector): + '''Driver.start should call this to init the report''' self.full_id = collector.listnames() self.id = collector.name if collector.getpathlineno(): # save for Null() in test_util.py @@ -145,18 +159,24 @@ else: str_append = ' [%s]' % fspath.basename self.label = collector.name + str_append - + + self.fspath = self.abbrev_path(collector.fspath) + self.modpath = collector.getmodpath() self.settime() self.restart_params = (str(collector.listchain()[0].fspath), collector.listnames()) self.status = Status.NotExecuted() def finish(self, collector, res, option = Null()): + '''Driver.finish should call this to set the + value of error_report + option is passed to Driver at initialization''' self.settime() if collector.getpathlineno(): # save for Null() in test_util.py fspath, lineno = collector.getpathlineno() if lineno != sys.maxint: - str_append = ' [%s:%s] %0.2fsecs' % (fspath.basename, lineno, self.time) + str_append = ' [%s:%s] %0.2fsecs' % (fspath.basename, + lineno, self.time) else: str_append = ' [%s] %0.2fsecs' % (fspath.basename, self.time) self.label = collector.name + str_append @@ -168,6 +188,16 @@ self.status.update(Status(res)) self.finished = True + def abbrev_path(self, fspath): + parts = fspath.parts() + basename = parts.pop().basename + while parts and parts[-1].basename in ('testing', 'test'): + parts.pop() + base = parts[-1].basename + if len(base) < 13: + base = base + "_" * (13-len(base)) + return base + "_" + basename + def report_failed(self, option, item, res): #XXX hack abuse of TerminalDriver terminal = TerminalDriver(option) @@ -176,27 +206,29 @@ terminal.repr_failure(item, res) return out.getoutput() - def report_skipped(self,option, item, res): + def report_skipped(self, option, item, res): texts = {} tbindex = getattr(res, 'tbindex', -1) raisingtb = res.excinfo.traceback[tbindex] fn = raisingtb.frame.code.path lineno = raisingtb.lineno d = texts.setdefault(res.excinfo.exconly(), {}) - d[(fn,lineno)] = res + d[(fn, lineno)] = res out = OutBuffer() out.sep('_', 'reasons for skipped tests') for text, dict in texts.items(): for (fn, lineno), res in dict.items(): - out.line('Skipped in %s:%d' %(fn,lineno)) + out.line('Skipped in %s:%d' %(fn, lineno)) out.line("reason: %s" % text) return out.getoutput() def settime(self): + '''update self.time ''' self.time = py.std.time.time() - self.time - def toChannel(self): + def to_channel(self): + '''counterpart of classmethod fromChannel''' ret = self.template.copy() for key in ret.keys(): ret[key] = getattr(self, key, self.template[key]) @@ -204,18 +236,18 @@ return ret def __str__(self): - return str(self.toChannel()) + return str(self.to_channel()) def __repr__(self): return str(self) def copy(self, **kwargs): - channel_dict = self.toChannel() + channel_dict = self.to_channel() channel_dict.update(kwargs) return TestReport.fromChannel(channel_dict) class TestFileWatcher: - + '''watches files or paths''' def __init__(self, *paths): self.paths = [py.path.local(path) for path in paths] self.watchdict = dict() @@ -227,6 +259,7 @@ return None def check_files(self): + '''returns (changed files, deleted files)''' fil = py.path.checker(fnmatch='*.py') rec = py.path.checker(dotfile=0) @@ -237,14 +270,17 @@ else: files.extend(path.visit(fil, rec)) newdict = dict(zip(files, [self.file_information(p) for p in files])) - files_deleted = [f for f in self.watchdict.keys() if not newdict.has_key(f)] + files_deleted = [f for f in self.watchdict.keys() + if not newdict.has_key(f)] files_new = [f for f in newdict.keys() if not self.watchdict.has_key(f)] - files_changed = [f for f in newdict.keys() if self.watchdict.has_key(f) and newdict[f]!= self.watchdict[f]] + files_changed = [f for f in newdict.keys() if self.watchdict.has_key(f) + and newdict[f]!= self.watchdict[f]] files_changed = files_new + files_changed self.watchdict = newdict return files_changed, files_deleted def changed(self): + '''returns False if nothing changed''' changed, deleted = self.check_files() return changed != [] or deleted != [] From hpk at codespeak.net Fri Apr 8 03:23:35 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 8 Apr 2005 03:23:35 +0200 (CEST) Subject: [py-svn] r10418 - in py/branch/py-collect/test: . testing tkinter Message-ID: <20050408012335.733B127B5F@code1.codespeak.net> Author: hpk Date: Fri Apr 8 03:23:34 2005 New Revision: 10418 Modified: py/branch/py-collect/test/cmdline.py py/branch/py-collect/test/drive.py py/branch/py-collect/test/run.py py/branch/py-collect/test/testing/test_drive.py py/branch/py-collect/test/tkinter/guidriver.py py/branch/py-collect/test/tkinter/tkgui.py Log: first round of renaming some driver methods (i hope i catched all tkinter references, can't test it on my OSX yet). But i even intend to do some more renamings, mainly: driver -> session because while reading Jan Balster's new tkinter frontend code i realized that 'TerminalSession' and 'GUISession' make more intuitive sense than "...Driver" ... Modified: py/branch/py-collect/test/cmdline.py ============================================================================== --- py/branch/py-collect/test/cmdline.py (original) +++ py/branch/py-collect/test/cmdline.py Fri Apr 8 03:23:34 2005 @@ -8,7 +8,7 @@ args = py.std.sys.argv[1:] driver, paths = py.test.config.init(args) try: - driver.runpaths(*paths) + driver.runsession(paths) except KeyboardInterrupt: print print "Keybordinterrupt" Modified: py/branch/py-collect/test/drive.py ============================================================================== --- py/branch/py-collect/test/drive.py (original) +++ py/branch/py-collect/test/drive.py Fri Apr 8 03:23:34 2005 @@ -36,19 +36,14 @@ def warning(self, msg): raise Warning(msg) - def runpaths(self, *paths): - #print "runpaths", paths - from py.__impl__.test.collect import getfscollector - cols = [getfscollector(p) for p in paths] - return self.run(cols) - - def run(self, colitems): + def runsession(self, *objs): """ main loop for running tests. """ + colitems = map2colitems(objs) try: self.header(colitems) try: for colitem in colitems: - self.runone(colitem) + self.runtraced(colitem) finally: self.footer(colitems) except Exit, ex: @@ -59,19 +54,20 @@ for item, res in self.getresults(py.test.Item.Failed)] - def runone(self, colitem): + def runtraced(self, colitem): if self.shouldclose(): raise SystemExit, "received external close signal" - capture = None if not self.option.nocapture and isinstance(colitem, py.test.Item): capture = SimpleOutErrCapture() + else: + capture = None try: self.start(colitem) res = None try: try: - res = self.runinner(colitem) + res = self.run(colitem) except (KeyboardInterrupt, Exit): raise except colitem.Outcome, res: @@ -93,7 +89,7 @@ except AttributeError: pass - def runinner(self, colitem): + def run(self, colitem): if self.option.collectonly and isinstance(colitem, py.test.Item): return res = colitem.run() @@ -105,10 +101,20 @@ "sequence nor None: %r" % (colitem, res)) return colitem.Passed() for obj in seq: - #print "diving into", obj - self.runone(obj) + self.runtraced(obj) return res +def map2colitems(items): + # first convert all path objects into collectors + from py.__impl__.test.collect import getfscollector + colitems = [] + for item in items: + if isinstance(item, (list, tuple)): + colitems.extend(map2colitems(item)) + elif not isinstance(item, py.test.collect.Collector): + colitems.append(getfscollector(item)) + return colitems + class Exit(Exception): """ for immediate program exits without tracebacks and reporter/summary. """ def __init__(self, msg="unknown reason", item=None): Modified: py/branch/py-collect/test/run.py ============================================================================== --- py/branch/py-collect/test/run.py (original) +++ py/branch/py-collect/test/run.py Fri Apr 8 03:23:34 2005 @@ -98,7 +98,7 @@ else: cols = list(getcollectors(paths)) driver.shouldclose = channel.isclosed - failures = driver.run(cols) + failures = driver.process(cols) channel.send(failures) class FailureCollector(py.test.collect.Collector): Modified: py/branch/py-collect/test/testing/test_drive.py ============================================================================== --- py/branch/py-collect/test/testing/test_drive.py (original) +++ py/branch/py-collect/test/testing/test_drive.py Fri Apr 8 03:23:34 2005 @@ -7,7 +7,7 @@ class TestDefaultDriver: def test_simple(self): driver, paths = py.test.config.init([]) - driver.runpaths(datadir / 'filetest.py') + driver.runsession(datadir / 'filetest.py') l = driver.getresults(py.test.Item.Failed) assert len(l) == 2 l = driver.getresults(py.test.Item.Passed) @@ -29,7 +29,7 @@ #f.flush() def test_terminal(self): - self.driver.runpaths(datadir / 'filetest.py') + self.driver.runsession(datadir / 'filetest.py') out = self.file.getvalue() #print "memo" #print self.driver._memo @@ -42,7 +42,7 @@ def test_exit_first_problem(self): driver = self.driver driver.option.exitfirstproblem = True - driver.runpaths(str(datadir / 'filetest.py')) + driver.runsession(str(datadir / 'filetest.py')) l = driver.getresults(py.test.Item.Failed) assert len(l) == 1 l = driver.getresults(py.test.Item.Passed) @@ -51,7 +51,7 @@ def test_collectonly(self): driver = self.driver driver.option.collectonly = True - driver.runpaths(str(datadir / 'filetest.py')) + driver.runsession(str(datadir / 'filetest.py')) out = self.file.getvalue() #print out l = driver.getresults(py.test.Item.Failed) @@ -78,9 +78,9 @@ #print "hello" driver = self.driver #driver.option.nocapture = True - print "calling runpaths", o - driver.runpaths(str(o)) - print "back from runpaths", o + print "calling runsession", o + driver.runsession(str(o)) + print "back from runsession", o out = self.file.getvalue() #print out i = out.find('Recursion detected') @@ -94,7 +94,7 @@ yield None """)) driver = self.driver - driver.runpaths(o) + driver.runsession(o) out = self.file.getvalue() #print out i = out.find('TypeError') @@ -124,7 +124,7 @@ """)) driver = self.driver - driver.runpaths(o) + driver.runsession([o]) l = driver.getresults(py.test.Item.Failed) assert len(l) == 0 l = driver.getresults(py.test.Item.Passed) Modified: py/branch/py-collect/test/tkinter/guidriver.py ============================================================================== --- py/branch/py-collect/test/tkinter/guidriver.py (original) +++ py/branch/py-collect/test/tkinter/guidriver.py Fri Apr 8 03:23:34 2005 @@ -47,39 +47,3 @@ def warning(self, msg): pass - # hack! Driver.runone should be patched - def runone(self, colitem): - if self.shouldclose(): - raise SystemExit, "received external close signal" - - capture = None - if not self.option.nocapture and isinstance(colitem, py.test.Item): - capture = SimpleOutErrCapture() - res = None - try: - self.start(colitem) - try: - try: - res = self.runinner(colitem) - except (KeyboardInterrupt, Exit): - res = None - raise - except colitem.Outcome, res: - res.excinfo = py.code.ExceptionInfo() - except: - excinfo = py.code.ExceptionInfo() - res = colitem.Failed(excinfo=excinfo) - else: - assert (res is None or - isinstance(res, (list, colitem.Outcome))) - finally: - if capture is not None: - out, err = capture.reset() - try: - res.out, res.err = out, err - except AttributeError: - pass - self.finish(colitem, res) - finally: - pass - Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Fri Apr 8 03:23:34 2005 @@ -335,7 +335,7 @@ # hack!! frontenddriver, paths = py.test.config.init(args) driver = GuiDriver(option = frontenddriver.option, channel = channel) - driver.run(col) + driver.process(col) """) channel.send((rerun_cols, self.args)) self.receive_all_data(channel) From hpk at codespeak.net Fri Apr 8 03:24:18 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 8 Apr 2005 03:24:18 +0200 (CEST) Subject: [py-svn] r10419 - py/branch/py-collect/documentation Message-ID: <20050408012418.03DC027B5F@code1.codespeak.net> Author: hpk Date: Fri Apr 8 03:24:18 2005 New Revision: 10419 Modified: py/branch/py-collect/documentation/future.txt Log: refactor and extend the future chapter with some real fun stuff Modified: py/branch/py-collect/documentation/future.txt ============================================================================== --- py/branch/py-collect/documentation/future.txt (original) +++ py/branch/py-collect/documentation/future.txt Fri Apr 8 03:24:18 2005 @@ -10,7 +10,8 @@ statements within this document - even if they sound factual - mostly just express thoughts and ideas. They not always refer to real code so read with some caution. This is not a reference guide -(tm).* +(tm). Moreover, the order in which appear here in the file does +not reflect the order in which they may be implemented.* .. _`general-path`: .. _`a more general view on path objects`: @@ -355,29 +356,29 @@ Refactor path implementations to use a Filesystem Abstraction ============================================================= -It seems a good idea to refactor all python implementations to -use an internal Filesystem abstraction. There would be -Filesystem implementations for e.g. local, subversion -and subversion working copy filesystems. Today the -according code is scattered through the actual path-handling -code. - -On a related note, Armin has hacked `pylufs`_ which allows to -write linux filesystems at pure python level. The idea is that -the new filesystems objects would be implemented in a way that -make them directly usable for the linux-filesystem glue code. -Implementing a `memoryfs`_ or a `dictfs`_ should then give -you for free: a filesystem mountable at linux kernel level -as well as a uniform "path" object allowing you to traverse -and manipulate the filesystem tree. At some point it might -become interesting to interface to some `reiserfs v4 stuff`_ -at the Filesystem level. - +It seems like a good idea to refactor all python implementations to +use an internal Filesystem abstraction. The current code base +would be transformed to have Filesystem implementations for e.g. +local, subversion and subversion "working copy" filesystems. Today +the according code is scattered through path-handling code. + +On a related note, Armin Rigo has hacked `pylufs`_ which allows to +implement kernel-level linux filesystems with pure python. Now +the idea is that the mentioned filesystem implementations would +be directly usable for such linux-filesystem glue code. + +In other words, implementing a `memoryfs`_ or a `dictfs`_ would +give you two things for free: a filesystem mountable at kernel level +as well as a uniform "path" object allowing you to access your +filesystem in convenient ways. (At some point it might +even become interesting to think about interfacing to +`reiserfs v4 features`_ at the Filesystem level but that +is a can of subsequent worms). .. _`memoryfs`: http://codespeak.net/svn/user/arigo/hack/pylufs/memoryfs.py .. _`dictfs`: http://codespeak.net/pipermail/py-dev/2005-January/000191.html .. _`pylufs`: http://codespeak.net/svn/user/arigo/hack/pylufs/ -.. _`reiserfs v4 stuff`: http://www.namesys.com/v4/v4.html +.. _`reiserfs v4 features`: http://www.namesys.com/v4/v4.html Improve and unify Path API @@ -390,23 +391,49 @@ which will limit traversal to subdirectories. Example:: x = py.path.local.get_tmproot() - for x in x.visit('bin', maxdepth=N): + for x in p.visit('bin', stop=N): ... -This will with find all files or directories named 'bin', -depending on the values of ``maxdepth``:: +This will yield all file or directory paths whose basename +is 'bin', depending on the values of ``stop``:: + + p # stop == 0 or higher (and p.basename == 'bin') + p / bin # stop == 1 or higher + p / ... / bin # stop == 2 or higher + p / ... / ... / bin # stop == 3 or higher + +The default for stop would be `255`. - x # maxdepth == 0 (and x.basename == 'bin') - x / bin # maxdepth == 1 - x / ... / bin # maxdepth == 2 - x / ... / ... / bin # maxdepth == 3 +But what if `stop < 0`? We could let that mean to go upwards:: -What if `x < 0`? We could let that indicate just going upwards. - for x in x.visit('py/bin', maxdepth=-255): + for x in x.visit('py/bin', stop=-255): # will yield all parent direcotires which have a # py/bin subpath - # -Is `maxdepth` a good name? Should this functionality live -on a different method alltogether? +visit() returning a lazy list? +------------------------------ + +There is a very nice "no-API" `lazy list`_ implementation from +Armin Rigo which presents a complete list interface, given some +iterable. The iterable is consumed only on demand and retains +memory efficiency as much as possible. The lazy list +provides a number of advantages in addition to the fact that +a list interface is nicer to deal with than an iterator. +For example it lets you do:: + + for x in p1.visit('*.cfg') + p2.visit('*.cfg'): + # will iterate through all results + +Here the for-iter expression will retain all lazyness (with +the result of adding lazy lists being another another lazy +list) by internally concatenating the underlying +lazylists/iterators. Moreover, the lazylist implementation +will know that there are no references left to the lazy list +and throw away iterated elements. This makes the iteration +over the sum of the two visit()s as efficient as if we had +used iterables to begin with! + +For this, we would like to move the lazy list into the +py lib's namespace, most probably at `py.builtin.lazylist`. +.. _`lazy list`: http://codespeak.net/svn/user/arigo/hack/collect From hpk at codespeak.net Fri Apr 8 03:24:47 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 8 Apr 2005 03:24:47 +0200 (CEST) Subject: [py-svn] r10420 - py/branch/py-collect/documentation Message-ID: <20050408012447.366BA27B5F@code1.codespeak.net> Author: hpk Date: Fri Apr 8 03:24:47 2005 New Revision: 10420 Modified: py/branch/py-collect/documentation/rest_test.py Log: comments += 1 Modified: py/branch/py-collect/documentation/rest_test.py ============================================================================== --- py/branch/py-collect/documentation/rest_test.py (original) +++ py/branch/py-collect/documentation/rest_test.py Fri Apr 8 03:24:47 2005 @@ -11,7 +11,8 @@ import docutils except ImportError: py.test.skip("docutils not importable") - rest.process(path) + # this helper will raise errors instead of warnings + rest.process(path) #assert not out def test_rest_files(): From hpk at codespeak.net Fri Apr 8 03:26:56 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 8 Apr 2005 03:26:56 +0200 (CEST) Subject: [py-svn] r10421 - py/branch/py-collect/documentation Message-ID: <20050408012656.AC7AB27B5F@code1.codespeak.net> Author: hpk Date: Fri Apr 8 03:26:56 2005 New Revision: 10421 Added: py/branch/py-collect/documentation/releasescheme.txt (contents, props changed) Modified: py/branch/py-collect/documentation/getting_started.txt Log: moving towards a nice release scheme describing - the structure of upcoming py releases - how you can install it via subversion or tarballs - how you can integrate into your project via svn-externals - outlook on how to integrate it with distutils Modified: py/branch/py-collect/documentation/getting_started.txt ============================================================================== --- py/branch/py-collect/documentation/getting_started.txt (original) +++ py/branch/py-collect/documentation/getting_started.txt Fri Apr 8 03:26:56 2005 @@ -25,12 +25,13 @@ to checkout the code, documentation, tool and example tree into a ``dist-py`` checkout directory. Your naming desire may vary -for your local checkout directory, of course. +for your local checkout directory. If you experience problems with the subversion checkout e.g. because you have a http-proxy in between that doesn't proxy DAV requests you can try to use "codespeak.net:8080" instead -of just "codespeak.net". +of just "codespeak.net". Alternatively, you may tweak +your local subversion installation. setting it up ------------- @@ -50,10 +51,10 @@ If you know of a good developer-style way of doing the equivalent on win32 (non-cygwin) environments, tell us_. -And no, we don't provide a distutils-install currently, -because we really want have the py lib installable -on multiple python versions, want to have automated -upgrades ... +And no, we don't provide a distutils-install until +we have settled on a convenient way to upgrade seamlessly +via an `svn up` while at the same time allowing +installs/upgrades via the distutils `setup.py` way. upgrading it ------------ @@ -77,7 +78,7 @@ the near coding feature to give a means for preliminary feedback before code hits the ground. -With our `coding style` we are mostly following +With our `coding style`_ we are mostly following cpython guidance with some additional restrictions some of which projects like twisted_ or zope3_ have adopted in similar ways. Added: py/branch/py-collect/documentation/releasescheme.txt ============================================================================== --- (empty file) +++ py/branch/py-collect/documentation/releasescheme.txt Fri Apr 8 03:26:56 2005 @@ -0,0 +1,117 @@ +The directory release layout of the repository:: + + svn/py/dist # latest released code + svn/py/tag/py-X.Y.Z # tagged releases + svn/py/branch/py-X.Y # contains release branch development + svn/py/trunk # head development + +Scenario "no svn and just let me play, please" +============================================== + +If you don't have a subversion client you can download +specific versions by going to + + http://codespeak.net/download/py + +and pick a suitable archive file. You need to +unpack it and run `setup.py` to install it for +your platform. + +Installation Scenario "svn + stay close to released versions" +------------------------------------------------------------- + +If you have a subversion client (you can easily install +one :-) it is recommended that you choose the following +method to install, irrespective if you want to install +things via `distutils`_. Start by issueing the following +shell command (or a graphical equivalent):: + + svn co http://codespeak.net/svn/py/dist dist-py + +You will then want to make sure that the `dist-py/py/bin` directory +is on your shell search path. This allows you to use "py.test" from +the command line. + +If you not only want to use `py.test` but want to import +the py lib within your application you have two choices: + +1) you make sure that `dist-py` is on your python search + path, e.g. via setting the `PYTHONPATH`. (you may + also consider the `svn-external scenario`_ if you + need to include the py lib in your application). + +2) go to the `dist-py` directory and run `python setup.py install`. + +If you now want to upgrade your version of the py lib +you can simply issue:: + + svn up + +or to switch to the development trunk via:: + + svn switch http://codespeak.net/svn/py/trunk + +or to a specific version via:: + + svn switch http://codespeak.net/svn/py/tag/py-X.Y.Z + +or to a specific release branch:: + + svn switch http://codespeak.net/svn/py/branch/py-X.Y + +If you choose the option No. 2) above you have to repeat +the distutils install after a checkout, aka +`python setup.py install`. + +.. _`svn-external scenario`: + +Installation Scenario "svn + include py lib as an external" +=========================================================== + +OK, so you want to have the py lib supporting your +application and are using subversion? Great because +things are quite easy and flexible for this scenario. + +Tying the py lib into your subversion controled project +------------------------------------------------------- + +On the `DIRECTORY` which contains your root package issue:: + + svn pe 'svn:externals' DIRECTORY + +and add the following line to your (possibly empty) list +of svn-externals:: + + py http://codespeak.net/svn/py/dist + +This will make your projcet follow the most recent +release of the py lib. (please substitute `dist` for `trunk` +if you want to follow py lib development, this will let +you catch interaction problems early on ...). +If you now issue an `svn up` on your `DIRECTORY` you +will retrieve the external into your application. + +If you want to follow a minor release branch seamlessly +then use the following line:: + + py http://codespeak.net/svn/py/branch/py-X.Y + +where `X.Y` indicate the branch you want to follow. + +If you want to use a very fixed version of the py lib +you can tie to a specific release:: + + py http://codespeak.net/svn/py/tag/py-X.Y.Z + + +Integrating the py lib into your distutils `setup.py` +----------------------------------------------------- + +XXX, maybe something like:: + + import py # XXX make sure this is the accomopanying py lib + + def setup( + ... + .... XXX + ) From hpk at codespeak.net Fri Apr 8 03:35:28 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 8 Apr 2005 03:35:28 +0200 (CEST) Subject: [py-svn] r10422 - in py/branch/py-collect: . path/local path/local/testing path/testing test test/testing Message-ID: <20050408013528.A0B7927B5F@code1.codespeak.net> Author: hpk Date: Fri Apr 8 03:35:28 2005 New Revision: 10422 Modified: py/branch/py-collect/initpkg.py py/branch/py-collect/path/local/local.py py/branch/py-collect/path/local/testing/test_local.py py/branch/py-collect/path/testing/common.py py/branch/py-collect/path/testing/fscommon.py py/branch/py-collect/test/collect.py py/branch/py-collect/test/testing/test_collect.py Log: hopefully a serious blow to the import dragons ... basically py.test now doesn't use or imply a custom import hook anymore! I wonder if getpymodule() could just call the new pyimport(), should be phased out or be replaced by an even more powerful interfacing mechanism. Currently, it allows to import from C-coded extension modules on-the-fly (invoking distutils in the background). While this import-rework should make py.test interact more nicely with import-magic-using applications, it also derives us from a way to import things remotely, but that was limited anyway. Modified: py/branch/py-collect/initpkg.py ============================================================================== --- py/branch/py-collect/initpkg.py (original) +++ py/branch/py-collect/initpkg.py Fri Apr 8 03:35:28 2005 @@ -70,6 +70,21 @@ current = getattr(current, x) return current + def importfrompath(self, path): + import py + base = py.path.local(self.implmodule.__file__).dirpath() + if not path.relto(base): + raise ImportError("cannot import '%s' from '%s'" %( + path, self.module.__name__)) + names = path.new(ext='').relto(base).split(path.sep) + dottedname = ".".join([self.implmodule.__name__] + names) + try: + return __import__(dottedname, None, None, ['name']) + except ImportError: + if dottedname in sys.modules: + del sys.modules[dottedname] + raise + def _loadimpl(self, relfile): """ load implementation for the given relfile. """ parts = [x.strip() for x in relfile.split('/') if x and x!= '.'] Modified: py/branch/py-collect/path/local/local.py ============================================================================== --- py/branch/py-collect/path/local/local.py (original) +++ py/branch/py-collect/path/local/local.py Fri Apr 8 03:35:28 2005 @@ -321,6 +321,40 @@ """ return string representation of the Path. """ return self.strpath + def pyimport(self, name=None, ensuresyspath=True): + """ return the path as an imported python module. + if name is None, look for the containing package + and construct an according module name. + The module will be put/looked up in sys.modules. + insertpkgroot determines if the package's root + will be put + """ + if not self.check(): + raise py.error.ENOENT(self) + #assert self.check() + pkgpath = None + #print "trying to import", self + for p in self.parts(reverse=True)[1:]: + if p.join('__init__.py').check(): + pkgpath = p + continue + if ensuresyspath: + #print "inserting into sys.path", p + py.std.sys.path.insert(0, str(p)) + if pkgpath is not None: + pkg = __import__(pkgpath.basename, None, None, []) + assert py.path.local(pkg.__file__).relto(pkgpath) + if hasattr(pkg, '__package__'): + return pkg.__package__.importfrompath(self) + names = self.new(ext='').relto(pkgpath.dirpath()) + names = names.split(self.sep) + dottedname = ".".join(names) + return __import__(dottedname, None, None, ['something']) + else: + #print "sys.path[0] is", sys.path[0] + return __import__(self.purebasename) + raise ImportError("cannot import from path %s" %(self,)) + def getpymodule(self): if self.ext != '.c': return super(LocalPath, self).getpymodule() Modified: py/branch/py-collect/path/local/testing/test_local.py ============================================================================== --- py/branch/py-collect/path/local/testing/test_local.py (original) +++ py/branch/py-collect/path/local/testing/test_local.py Fri Apr 8 03:35:28 2005 @@ -201,6 +201,35 @@ #def test_parentdirmatch(self): # local.parentdirmatch('std', startmodule=__name__) + # + + # importing tests + def test_pyimport(self): + obj = self.root.join('execfile.py').pyimport() + assert obj.x == 42 + assert obj.__name__ == 'execfile' + + def test_pyimport_a(self): + otherdir = self.root.join('otherdir') + mod = otherdir.join('a.py').pyimport() + assert mod.result == "got it" + assert mod.__name__ == 'otherdir.a' + + def test_pyimport_b(self): + otherdir = self.root.join('otherdir') + mod = otherdir.join('b.py').pyimport() + assert mod.stuff == "got it" + assert mod.__name__ == 'otherdir.b' + + def test_pyimport_c(self): + otherdir = self.root.join('otherdir') + mod = otherdir.join('c.py').pyimport() + assert mod.value == "got it" + + def test_pyimport_d(self): + otherdir = self.root.join('otherdir') + mod = otherdir.join('d.py').pyimport() + assert mod.value2 == "got it" #class XTestLocalPath(TestLocalPath): # def __init__(self): Modified: py/branch/py-collect/path/testing/common.py ============================================================================== --- py/branch/py-collect/path/testing/common.py (original) +++ py/branch/py-collect/path/testing/common.py Fri Apr 8 03:35:28 2005 @@ -16,6 +16,9 @@ def test_new_identical(self): assert self.root == self.root.new() + def test_type_check_for_path(self): + assert self.root.check(path=1) + def test_join(self): p = self.root.join('sampledir') strp = str(p) Modified: py/branch/py-collect/path/testing/fscommon.py ============================================================================== --- py/branch/py-collect/path/testing/fscommon.py (original) +++ py/branch/py-collect/path/testing/fscommon.py Fri Apr 8 03:35:28 2005 @@ -13,6 +13,9 @@ execfile = path.ensure('execfile') execfile.write('x=42') + execfilepy = path.ensure('execfile.py') + execfilepy.write('x=42') + d = {1:2, 'hello': 'world', 'answer': 42} path.ensure('samplepickle').dumpobj(d) Modified: py/branch/py-collect/test/collect.py ============================================================================== --- py/branch/py-collect/test/collect.py (original) +++ py/branch/py-collect/test/collect.py Fri Apr 8 03:35:28 2005 @@ -231,7 +231,7 @@ try: return self._obj except AttributeError: - self._obj = obj = self.fspath.getpymodule() + self._obj = obj = self.fspath.pyimport() return obj obj = property(obj, None, None, "module object") Modified: py/branch/py-collect/test/testing/test_collect.py ============================================================================== --- py/branch/py-collect/test/testing/test_collect.py (original) +++ py/branch/py-collect/test/testing/test_collect.py Fri Apr 8 03:35:28 2005 @@ -50,7 +50,7 @@ py.test.raises(py.error.ENOENT, col.run) def test_syntax_error_in_module(): - modpath = py.path.extpy(datadir.join('syntax_error.py')) + modpath = datadir.join('syntax_error.py') col = py.test.collect.Module(modpath) py.test.raises(SyntaxError, col.run) @@ -141,7 +141,7 @@ class Instance(myfuncmixin, py.test.collect.Instance): pass """) - o.ensure('somedir', 'check_something').write("""if 1: + o.ensure('somedir', 'check_something.py').write("""if 1: def check_func(): assert 42 == 42 class CustomTestClass: From hpk at codespeak.net Fri Apr 8 03:39:55 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 8 Apr 2005 03:39:55 +0200 (CEST) Subject: [py-svn] r10423 - py/branch/py-collect/path/testing Message-ID: <20050408013955.644EA27B5F@code1.codespeak.net> Author: hpk Date: Fri Apr 8 03:39:55 2005 New Revision: 10423 Modified: py/branch/py-collect/path/testing/common.py Log: accidentally slipped in Modified: py/branch/py-collect/path/testing/common.py ============================================================================== --- py/branch/py-collect/path/testing/common.py (original) +++ py/branch/py-collect/path/testing/common.py Fri Apr 8 03:39:55 2005 @@ -16,9 +16,6 @@ def test_new_identical(self): assert self.root == self.root.new() - def test_type_check_for_path(self): - assert self.root.check(path=1) - def test_join(self): p = self.root.join('sampledir') strp = str(p) From hpk at codespeak.net Fri Apr 8 05:11:52 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 8 Apr 2005 05:11:52 +0200 (CEST) Subject: [py-svn] r10427 - py/branch/py-collect/documentation Message-ID: <20050408031152.F182C27B5F@code1.codespeak.net> Author: hpk Date: Fri Apr 8 05:11:52 2005 New Revision: 10427 Modified: py/branch/py-collect/documentation/releasescheme.txt Log: fix a bug Modified: py/branch/py-collect/documentation/releasescheme.txt ============================================================================== --- py/branch/py-collect/documentation/releasescheme.txt (original) +++ py/branch/py-collect/documentation/releasescheme.txt Fri Apr 8 05:11:52 2005 @@ -23,7 +23,7 @@ If you have a subversion client (you can easily install one :-) it is recommended that you choose the following method to install, irrespective if you want to install -things via `distutils`_. Start by issueing the following +things via distutils. Start by issueing the following shell command (or a graphical equivalent):: svn co http://codespeak.net/svn/py/dist dist-py From hpk at codespeak.net Fri Apr 8 05:30:39 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 8 Apr 2005 05:30:39 +0200 (CEST) Subject: [py-svn] r10428 - in py/branch/py-collect: . test test/testing test/tkinter test/tkinter/testing Message-ID: <20050408033039.C710D27B5F@code1.codespeak.net> Author: hpk Date: Fri Apr 8 05:30:39 2005 New Revision: 10428 Added: py/branch/py-collect/test/session.py - copied, changed from r10422, py/branch/py-collect/test/drive.py py/branch/py-collect/test/testing/test_session.py - copied, changed from r10422, py/branch/py-collect/test/testing/test_drive.py py/branch/py-collect/test/tkinter/guisession.py - copied, changed from r10422, py/branch/py-collect/test/tkinter/guidriver.py Removed: py/branch/py-collect/test/drive.py py/branch/py-collect/test/testing/test_drive.py py/branch/py-collect/test/tkinter/guidriver.py Modified: py/branch/py-collect/__init__.py py/branch/py-collect/test/cmdline.py py/branch/py-collect/test/collect.py py/branch/py-collect/test/compat.py py/branch/py-collect/test/config.py py/branch/py-collect/test/defaultconfig.py py/branch/py-collect/test/run.py py/branch/py-collect/test/terminal.py py/branch/py-collect/test/testing/test_config.py py/branch/py-collect/test/tkinter/testing/test_guidriver.py py/branch/py-collect/test/tkinter/tkgui.py py/branch/py-collect/test/tkinter/util.py Log: some more large scale renaming together with slight refactorings. We now speak of sessions instead of drivers. We have a TerminalSessiona and a tkinter GuiSession so far. However, i managed to break the tkinter one. It must be something stupid but i can't spot it at the moment. Jan, if you want to have a look, that would be great as i still haven't fully groked how everything plays together with tkinter ... Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/branch/py-collect/__init__.py (original) +++ py/branch/py-collect/__init__.py Fri Apr 8 05:30:39 2005 @@ -5,10 +5,10 @@ 'test.skip_on_error' : ('./test/item.py', 'skip_on_error'), 'test.raises' : ('./test/raises.py', 'raises'), 'test.fail' : ('./test/item.py', 'fail'), - 'test.exit' : ('./test/drive.py', 'exit'), + 'test.exit' : ('./test/session.py', 'exit'), 'test.Option' : ('./test/tool/optparse.py', 'Option'), - 'test.Driver' : ('./test/drive.py', 'Driver'), - 'test.TerminalDriver' : ('./test/terminal.py', 'TerminalDriver'), + 'test.Session' : ('./test/session.py', 'Session'), + 'test.TerminalSession' : ('./test/terminal.py', 'TerminalSession'), 'test.config.init' : ('./test/config.py', 'init'), 'test.config.parse' : ('./test/config.py', 'parse'), Modified: py/branch/py-collect/test/cmdline.py ============================================================================== --- py/branch/py-collect/test/cmdline.py (original) +++ py/branch/py-collect/test/cmdline.py Fri Apr 8 05:30:39 2005 @@ -6,9 +6,9 @@ def main(): args = py.std.sys.argv[1:] - driver, paths = py.test.config.init(args) + session, paths = py.test.config.init(args) try: - driver.runsession(paths) + session.main(paths) except KeyboardInterrupt: print print "Keybordinterrupt" Modified: py/branch/py-collect/test/collect.py ============================================================================== --- py/branch/py-collect/test/collect.py (original) +++ py/branch/py-collect/test/collect.py Fri Apr 8 05:30:39 2005 @@ -3,7 +3,7 @@ Collect test items at filesystem and python module levels. Collectors and test items form a tree. The difference -between a collector and a test item as seen from the driver +between a collector and a test item as seen from the session is conceptually non existent. However, Collectors usually return a list of child collectors/items whereas items usually return None indicating a successful test run. @@ -186,11 +186,9 @@ def run(self): l = self.fspath.listdir() l.sort() - l.reverse() l = [self.join(x.basename) for x in l if (x.check(file=1) and self.filefilter(x) or x.check(dir=1) and self.recfilter(x))] - l.sort() return filter(None, l) def join(self, name): Modified: py/branch/py-collect/test/compat.py ============================================================================== --- py/branch/py-collect/test/compat.py (original) +++ py/branch/py-collect/test/compat.py Fri Apr 8 05:30:39 2005 @@ -5,7 +5,7 @@ """ compatibility Unit executor for TestCase methods honouring setUp and tearDown semantics. """ - def execute(self, driver): + def execute(self, session): boundmethod = self.obj instance = boundmethod.im_self instance.setUp() Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Fri Apr 8 05:30:39 2005 @@ -41,21 +41,21 @@ return default def init(args, ignoreremote=False): - """ return driver object and test paths, determined from + """ return session object and test paths, determined from commandline arguments and config files. """ cmdlineoption, remaining = parse(args) if not ignoreremote and (cmdlineoption.session or cmdlineoption.executable): # execute in child process, i.e. setup a frontend - # pseudo-driver and do the real thing on the other side - from py.__impl__.test.run import FrontendDriver - return FrontendDriver(cmdlineoption, args), remaining - Driver = getdriverclass(args) + # pseudo-session and do the real thing on the other side + from py.__impl__.test.run import FrontendSession + return FrontendSession(cmdlineoption, args), remaining + Session = getsessionclass(args) remaining = [py.path.local(x) for x in remaining] if not remaining: remaining.append(py.path.local()) - driver = Driver(cmdlineoption) - return driver, remaining + session = Session(cmdlineoption) + return session, remaining def parse(args=None): """ return (Options, remaining args) by parsing @@ -77,27 +77,27 @@ # # helpers # -def getdriverclass(args): - """ return Driver class determined from cmdlines +def getsessionclass(args): + """ return Session class determined from cmdlines arguments (in a somewhat heuristic way). """ - # find driver - Driver = None + # find session + Session = None options = [] anchorpaths = getanchorpaths(args) for anchor in anchorpaths: - tmp = getconfigvalue(anchor, 'Driver', + tmp = getconfigvalue(anchor, 'Session', default=None, trydefaultconfig=False) if tmp is not None: - if Driver is not None and Driver[1] != tmp: - raise RuntimeError("conflicting Drivers from %s and %s" %( - Driver[0], anchor)) - Driver = (anchor, tmp) - if Driver is None: - Driver = getconfigvalue(anchor, "Driver", trydefaultconfig=True) + if Session is not None and Session[1] != tmp: + raise RuntimeError("conflicting Sessions from %s and %s" %( + Session[0], anchor)) + Session = (anchor, tmp) + if Session is None: + Session = getconfigvalue(anchor, "Session", trydefaultconfig=True) else: - Driver = Driver[1] - return Driver + Session = Session[1] + return Session def fixoptions(option): """ sanity checks and postprocessing of options. """ @@ -138,7 +138,7 @@ # py.test.addoptions() invocations. This "global state" # manipulation is not completely nice but how else could we allow # test projects to add cmdline options and provide them access to -# the resulting values? (apart from exposing driver initialisation +# the resulting values? (apart from exposing session initialisation # some more which i really don't want to do) _lastoptions = [] Modified: py/branch/py-collect/test/defaultconfig.py ============================================================================== --- py/branch/py-collect/test/defaultconfig.py (original) +++ py/branch/py-collect/test/defaultconfig.py Fri Apr 8 05:30:39 2005 @@ -1,7 +1,7 @@ import py Option = py.test.Option -Driver = py.test.TerminalDriver +Session = py.test.TerminalSession Module = py.test.collect.Module Directory = py.test.collect.Directory Deleted: /py/branch/py-collect/test/drive.py ============================================================================== --- /py/branch/py-collect/test/drive.py Fri Apr 8 05:30:39 2005 +++ (empty file) @@ -1,126 +0,0 @@ -import py -from py.__impl__.test.tool.outerrcapture import SimpleOutErrCapture - -class Driver(object): - """ - A Driver gets test Items from Collectors, # executes the - Items and sends the Outcome to the Reporter. - """ - def __init__(self, option): - self._memo = [] - self.option = option - - def shouldclose(self): - return False - - def header(self, colitems): - """ setup any neccessary resources. """ - if not self.option.nomagic: - py.magic.invoke(assertion=1) - - def footer(self, colitems): - """ teardown any resources we know about. """ - py.test.Item.state.teardown_all() - if not self.option.nomagic: - py.magic.revoke(assertion=1) - - def start(self, colitem): - pass - - def finish(self, colitem, res): - self._memo.append((colitem, res)) - - def getresults(self, cls): - return [x for x in self._memo if isinstance(x[1], cls)] - - def warning(self, msg): - raise Warning(msg) - - def runsession(self, *objs): - """ main loop for running tests. """ - colitems = map2colitems(objs) - try: - self.header(colitems) - try: - for colitem in colitems: - self.runtraced(colitem) - finally: - self.footer(colitems) - except Exit, ex: - pass - - # return [(fspath as string, [names as string])] - return [(str(item.listchain()[0].fspath), item.listnames()) - for item, res in self.getresults(py.test.Item.Failed)] - - - def runtraced(self, colitem): - if self.shouldclose(): - raise SystemExit, "received external close signal" - - if not self.option.nocapture and isinstance(colitem, py.test.Item): - capture = SimpleOutErrCapture() - else: - capture = None - try: - self.start(colitem) - res = None - try: - try: - res = self.run(colitem) - except (KeyboardInterrupt, Exit): - raise - except colitem.Outcome, res: - if not hasattr(res, 'excinfo'): - res.excinfo = py.code.ExceptionInfo() - except: - excinfo = py.code.ExceptionInfo() - res = colitem.Failed(excinfo=excinfo) - else: - assert (res is None or - isinstance(res, (list, colitem.Outcome))) - finally: - self.finish(colitem, res) - finally: - if capture is not None: - out, err = capture.reset() - try: - res.out, res.err = out, err - except AttributeError: - pass - - def run(self, colitem): - if self.option.collectonly and isinstance(colitem, py.test.Item): - return - res = colitem.run() - try: - seq = iter(res) - except TypeError: - if res is not None: - self.warning("%r.run() returned neither " - "sequence nor None: %r" % (colitem, res)) - return colitem.Passed() - for obj in seq: - self.runtraced(obj) - return res - -def map2colitems(items): - # first convert all path objects into collectors - from py.__impl__.test.collect import getfscollector - colitems = [] - for item in items: - if isinstance(item, (list, tuple)): - colitems.extend(map2colitems(item)) - elif not isinstance(item, py.test.collect.Collector): - colitems.append(getfscollector(item)) - return colitems - -class Exit(Exception): - """ for immediate program exits without tracebacks and reporter/summary. """ - def __init__(self, msg="unknown reason", item=None): - self.msg = msg - Exception.__init__(self, msg) - -def exit(msg, item=None): - raise Exit(msg=msg, item=item) - Modified: py/branch/py-collect/test/run.py ============================================================================== --- py/branch/py-collect/test/run.py (original) +++ py/branch/py-collect/test/run.py Fri Apr 8 05:30:39 2005 @@ -3,6 +3,7 @@ from py.__impl__.execnet.channel import ChannelFile, receive2file from py.__impl__.test.config import configbasename from py.__impl__.test.collect import getfscollector +from py.__impl__.test.session import map2colitems import sys def checkpyfilechange(rootdir, statcache): @@ -81,8 +82,8 @@ outproxy.setup() try: channel = gw.remote_exec(""" - from py.__impl__.test.run import failure_slave - failure_slave(channel) + from py.__impl__.test.run import failure_slave + failure_slave(channel) """) channel.send((args, failures)) return waitfinish(channel) @@ -92,37 +93,36 @@ def failure_slave(channel): """ we run this on the other side. """ args, failures = channel.receive() - driver, paths = py.test.config.init(args, ignoreremote=True) + session, paths = py.test.config.init(args, ignoreremote=True) if failures: - cols = FailureCollector(failures) + cols = getfailureitems(failures) else: - cols = list(getcollectors(paths)) - driver.shouldclose = channel.isclosed - failures = driver.process(cols) + cols = paths + session.shouldclose = channel.isclosed + failures = session.main(cols) channel.send(failures) -class FailureCollector(py.test.collect.Collector): - def __init__(self, failures): - self.failures = failures - def __iter__(self): - for rootpath, names in self.failures: - root = py.path.local(rootpath) - if root.check(dir=1): - current = py.test.collect.Directory(root).Directory(root) - elif root.check(file=1): - current = py.test.collect.Module(root).Module(root) - # root is fspath of names[0] -> pop names[0] - # slicing works with empty lists - names = names[1:] - while names: - name = names.pop(0) - try: - current = current.join(name) - except NameError: - print "WARNING: could not find %s on %r" %(name, current) - break - else: - yield current +def getfailureitems(failures): + l = [] + for rootpath, names in failures: + root = py.path.local(rootpath) + if root.check(dir=1): + current = py.test.collect.Directory(root).Directory(root) + elif root.check(file=1): + current = py.test.collect.Module(root).Module(root) + # root is fspath of names[0] -> pop names[0] + # slicing works with empty lists + names = names[1:] + while names: + name = names.pop(0) + try: + current = current.join(name) + except NameError: + print "WARNING: could not find %s on %r" %(name, current) + break + else: + l.append(current) + return l def getcollectors(filenames): current = py.path.local() @@ -130,12 +130,12 @@ fullfn = current.join(str(fn), abs=1) yield getfscollector(fullfn) -class FrontendDriver: +class FrontendSession: def __init__(self, option, args): self.option = option self.args = args - def runpaths(self, *paths): + def main(self, *paths): statcache = {} # XXX figure out a better rootdir? rootdir = py.path.local() @@ -151,7 +151,7 @@ print "#" * 60 print "# session mode: %d failures remaining" % len(failures) for root, names in failures: - name = ":".join(names) # XXX + name = "/".join(names) # XXX print "Failure at: %r" % (name,) print "# watching py files below %s" % rootdir print "# ", "^" * len(str(rootdir)) Copied: py/branch/py-collect/test/session.py (from r10422, py/branch/py-collect/test/drive.py) ============================================================================== --- py/branch/py-collect/test/drive.py (original) +++ py/branch/py-collect/test/session.py Fri Apr 8 05:30:39 2005 @@ -1,9 +1,9 @@ import py from py.__impl__.test.tool.outerrcapture import SimpleOutErrCapture -class Driver(object): +class Session(object): """ - A Driver gets test Items from Collectors, # executes the + A Session gets test Items from Collectors, # executes the Items and sends the Outcome to the Reporter. """ def __init__(self, option): @@ -36,7 +36,7 @@ def warning(self, msg): raise Warning(msg) - def runsession(self, *objs): + def main(self, *objs): """ main loop for running tests. """ colitems = map2colitems(objs) try: @@ -48,7 +48,6 @@ self.footer(colitems) except Exit, ex: pass - # return [(fspath as string, [names as string])] return [(str(item.listchain()[0].fspath), item.listnames()) for item, res in self.getresults(py.test.Item.Failed)] @@ -93,15 +92,17 @@ if self.option.collectonly and isinstance(colitem, py.test.Item): return res = colitem.run() - try: - seq = iter(res) - except TypeError: - if res is not None: - self.warning("%r.run() returned neither " - "sequence nor None: %r" % (colitem, res)) - return colitem.Passed() - for obj in seq: - self.runtraced(obj) + if res is None: + return py.test.Item.Passed() + else: + try: + seq = iter(res) + except TypeError: + raise TypeError("%r.run() returned neither " + "sequence nor None: %r" % (colitem, res)) + else: + for obj in seq: + self.runtraced(obj) return res def map2colitems(items): Modified: py/branch/py-collect/test/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal.py (original) +++ py/branch/py-collect/test/terminal.py Fri Apr 8 05:30:39 2005 @@ -4,9 +4,9 @@ from time import time as now Item = py.test.Item -class TerminalDriver(py.test.Driver): +class TerminalSession(py.test.Session): def __init__(self, option, file=None): - super(TerminalDriver, self).__init__(option) + super(TerminalSession, self).__init__(option) if file is None: file = py.std.sys.stdout self.out = getout(file) @@ -18,7 +18,7 @@ # --------------------- def start(self, colitem): - super(TerminalDriver, self).start(colitem) + super(TerminalSession, self).start(colitem) if self.option.collectonly: cols = self._opencollectors self.out.line(' ' * len(cols) + repr(colitem)) @@ -69,7 +69,7 @@ def finish(self, colitem, result): end = now() - super(TerminalDriver, self).finish(colitem, result) + super(TerminalSession, self).finish(colitem, result) if self.option.collectonly: cols = self._opencollectors last = cols.pop() @@ -106,7 +106,7 @@ # HEADER information # ------------------- def header(self, colitems): - super(TerminalDriver, self).header(colitems) + super(TerminalSession, self).header(colitems) self.out.sep("=", "test process starts") option = self.option if option.session: @@ -140,7 +140,7 @@ # ------------------- def footer(self, colitems): - super(TerminalDriver, self).footer(colitems) + super(TerminalSession, self).footer(colitems) self.endtime = now() self.out.line() self.skippedreasons() Modified: py/branch/py-collect/test/testing/test_config.py ============================================================================== --- py/branch/py-collect/test/testing/test_config.py (original) +++ py/branch/py-collect/test/testing/test_config.py Fri Apr 8 05:30:39 2005 @@ -40,8 +40,8 @@ """)) old = o.chdir() try: - driver, paths = py.test.config.init(['-g', '17']) - assert driver.option.gdest == 17 + session, paths = py.test.config.init(['-g', '17']) + assert session.option.gdest == 17 finally: old.chdir() Deleted: /py/branch/py-collect/test/testing/test_drive.py ============================================================================== --- /py/branch/py-collect/test/testing/test_drive.py Fri Apr 8 05:30:39 2005 +++ (empty file) @@ -1,136 +0,0 @@ -import py -datadir = py.magic.autopath().dirpath('data') -from cStringIO import StringIO - -tmpdir = py.test.ensuretemp('test_drive') - -class TestDefaultDriver: - def test_simple(self): - driver, paths = py.test.config.init([]) - driver.runsession(datadir / 'filetest.py') - l = driver.getresults(py.test.Item.Failed) - assert len(l) == 2 - l = driver.getresults(py.test.Item.Passed) - assert not l - -#f = open('/tmp/logfile', 'wa') -class TestTerminalDriver: - - def setup_method(self, method): - self.file = StringIO() - option, _ = py.test.config.parse() - self.driver = py.test.TerminalDriver(option, file=self.file) - #print >>f, "driver %r is setup for %r" %(self.driver, method) - #f.flush() - #print "driver is setup", self.driver - - #def teardown_method(self, method): - #print >>f, "driver %r finished method %r" % (self.driver, method) - #f.flush() - - def test_terminal(self): - self.driver.runsession(datadir / 'filetest.py') - out = self.file.getvalue() - #print "memo" - #print self.driver._memo - #print "out" - #print out - l = self.driver.getresults(py.test.Item.Failed) - assert len(l) == 2 - assert out.find('2 failed') != -1 - - def test_exit_first_problem(self): - driver = self.driver - driver.option.exitfirstproblem = True - driver.runsession(str(datadir / 'filetest.py')) - l = driver.getresults(py.test.Item.Failed) - assert len(l) == 1 - l = driver.getresults(py.test.Item.Passed) - assert not l - - def test_collectonly(self): - driver = self.driver - driver.option.collectonly = True - driver.runsession(str(datadir / 'filetest.py')) - out = self.file.getvalue() - #print out - l = driver.getresults(py.test.Item.Failed) - #if l: - # x = l[0][1].excinfo - # print x.exconly() - # print x.traceback - assert len(l) == 0 - for line in ('filetest.py', 'test_one', - 'TestClass', 'test_method_one'): - assert out.find(line) - - def test_recursion_detection(self): - o = tmpdir.ensure('recursiontest', dir=1) - tfile = o.join('test_recursion.py') - tfile.write(py.code.Source(""" - def test_1(): - def f(): - g() - def g(): - f() - f() - """)) - #print "hello" - driver = self.driver - #driver.option.nocapture = True - print "calling runsession", o - driver.runsession(str(o)) - print "back from runsession", o - out = self.file.getvalue() - #print out - i = out.find('Recursion detected') - assert i != -1 - - def test_generator_yields_None(self): - o = tmpdir.ensure('generatornonetest', dir=1) - tfile = o.join('test_generatornone.py') - tfile.write(py.code.Source(""" - def test_1(): - yield None - """)) - driver = self.driver - driver.runsession(o) - out = self.file.getvalue() - #print out - i = out.find('TypeError') - assert i != -1 - - def test_order_of_execution(self): - o = tmpdir.ensure('ordertest', dir=1) - tfile = o.join('test_orderofexecution.py') - tfile.write(py.code.Source(""" - l = [] - def test_1(): - l.append(1) - def test_2(): - l.append(2) - def test_3(): - assert l == [1,2] - class Testmygroup: - reslist = l - def test_1(self): - self.reslist.append(1) - def test_2(self): - self.reslist.append(2) - def test_3(self): - self.reslist.append(3) - def test_4(self): - assert self.reslist == [1,2,1,2,3] - """)) - - driver = self.driver - driver.runsession([o]) - l = driver.getresults(py.test.Item.Failed) - assert len(l) == 0 - l = driver.getresults(py.test.Item.Passed) - assert len(l) == 7 - # also test listnames() here ... - item, result = l[-1] - assert item.name == 'test_4' - names = item.listnames() - assert names == ['ordertest', 'test_orderofexecution.py', 'Testmygroup', '()', 'test_4'] Copied: py/branch/py-collect/test/testing/test_session.py (from r10422, py/branch/py-collect/test/testing/test_drive.py) ============================================================================== --- py/branch/py-collect/test/testing/test_drive.py (original) +++ py/branch/py-collect/test/testing/test_session.py Fri Apr 8 05:30:39 2005 @@ -4,57 +4,57 @@ tmpdir = py.test.ensuretemp('test_drive') -class TestDefaultDriver: +class TestDefaultSession: def test_simple(self): - driver, paths = py.test.config.init([]) - driver.runsession(datadir / 'filetest.py') - l = driver.getresults(py.test.Item.Failed) + session, paths = py.test.config.init([]) + session.main(datadir / 'filetest.py') + l = session.getresults(py.test.Item.Failed) assert len(l) == 2 - l = driver.getresults(py.test.Item.Passed) + l = session.getresults(py.test.Item.Passed) assert not l #f = open('/tmp/logfile', 'wa') -class TestTerminalDriver: +class TestTerminalSession: def setup_method(self, method): self.file = StringIO() option, _ = py.test.config.parse() - self.driver = py.test.TerminalDriver(option, file=self.file) - #print >>f, "driver %r is setup for %r" %(self.driver, method) + self.session = py.test.TerminalSession(option, file=self.file) + #print >>f, "session %r is setup for %r" %(self.session, method) #f.flush() - #print "driver is setup", self.driver + #print "session is setup", self.session #def teardown_method(self, method): - #print >>f, "driver %r finished method %r" % (self.driver, method) + #print >>f, "session %r finished method %r" % (self.session, method) #f.flush() def test_terminal(self): - self.driver.runsession(datadir / 'filetest.py') + self.session.main(datadir / 'filetest.py') out = self.file.getvalue() #print "memo" - #print self.driver._memo + #print self.session._memo #print "out" #print out - l = self.driver.getresults(py.test.Item.Failed) + l = self.session.getresults(py.test.Item.Failed) assert len(l) == 2 assert out.find('2 failed') != -1 def test_exit_first_problem(self): - driver = self.driver - driver.option.exitfirstproblem = True - driver.runsession(str(datadir / 'filetest.py')) - l = driver.getresults(py.test.Item.Failed) + session = self.session + session.option.exitfirstproblem = True + session.main(str(datadir / 'filetest.py')) + l = session.getresults(py.test.Item.Failed) assert len(l) == 1 - l = driver.getresults(py.test.Item.Passed) + l = session.getresults(py.test.Item.Passed) assert not l def test_collectonly(self): - driver = self.driver - driver.option.collectonly = True - driver.runsession(str(datadir / 'filetest.py')) + session = self.session + session.option.collectonly = True + session.main(str(datadir / 'filetest.py')) out = self.file.getvalue() #print out - l = driver.getresults(py.test.Item.Failed) + l = session.getresults(py.test.Item.Failed) #if l: # x = l[0][1].excinfo # print x.exconly() @@ -76,11 +76,11 @@ f() """)) #print "hello" - driver = self.driver - #driver.option.nocapture = True - print "calling runsession", o - driver.runsession(str(o)) - print "back from runsession", o + session = self.session + #session.option.nocapture = True + print "calling main", o + session.main(str(o)) + print "back from main", o out = self.file.getvalue() #print out i = out.find('Recursion detected') @@ -93,8 +93,8 @@ def test_1(): yield None """)) - driver = self.driver - driver.runsession(o) + session = self.session + session.main(o) out = self.file.getvalue() #print out i = out.find('TypeError') @@ -123,11 +123,11 @@ assert self.reslist == [1,2,1,2,3] """)) - driver = self.driver - driver.runsession([o]) - l = driver.getresults(py.test.Item.Failed) + session = self.session + session.main([o]) + l = session.getresults(py.test.Item.Failed) assert len(l) == 0 - l = driver.getresults(py.test.Item.Passed) + l = session.getresults(py.test.Item.Passed) assert len(l) == 7 # also test listnames() here ... item, result = l[-1] Deleted: /py/branch/py-collect/test/tkinter/guidriver.py ============================================================================== --- /py/branch/py-collect/test/tkinter/guidriver.py Fri Apr 8 05:30:39 2005 +++ (empty file) @@ -1,49 +0,0 @@ -'''GuiDriver builds TestReport instances and sends them to tkgui.Manager''' -import py -from util import TestReport -from py.__impl__.test.drive import Exit, SimpleOutErrCapture - -class GuiDriver(py.test.Driver): - - def __init__(self, option = None, channel = None): - super(GuiDriver, self).__init__(option) - self.channel = channel - self.reportlist = [] - - - def header(self, colitems): - super(GuiDriver, self).header(colitems) - report = TestReport() - report.settime() - self.reportlist = [report] - self.sendreport(report) - - def footer(self, colitems): - super(GuiDriver, self).footer(colitems) - report = self.reportlist.pop() - report.settime() - self.sendreport(report) - self.channel.send(None) - - def start(self, colitem): - super(GuiDriver, self).start(colitem) - report = TestReport() - report.start(colitem) - self.reportlist.append(report) - self.sendreport(report) - - - def finish(self, colitem, res): - super(GuiDriver, self).finish(colitem, res) - report = self.reportlist.pop() - report.finish(colitem, res, self.option) - self.reportlist[-1].status.update(report.status) - self.sendreport(report) - #py.std.time.sleep(0.5) - - def sendreport(self, report): - self.channel.send(report.to_channel()) - - def warning(self, msg): - pass - Copied: py/branch/py-collect/test/tkinter/guisession.py (from r10422, py/branch/py-collect/test/tkinter/guidriver.py) ============================================================================== --- py/branch/py-collect/test/tkinter/guidriver.py (original) +++ py/branch/py-collect/test/tkinter/guisession.py Fri Apr 8 05:30:39 2005 @@ -1,40 +1,38 @@ -'''GuiDriver builds TestReport instances and sends them to tkgui.Manager''' +'''GuiSession builds TestReport instances and sends them to tkgui.Manager''' import py from util import TestReport -from py.__impl__.test.drive import Exit, SimpleOutErrCapture +from py.__impl__.test.session import Exit, SimpleOutErrCapture -class GuiDriver(py.test.Driver): +class GuiSession(py.test.Session): def __init__(self, option = None, channel = None): - super(GuiDriver, self).__init__(option) + super(GuiSession, self).__init__(option) self.channel = channel self.reportlist = [] - def header(self, colitems): - super(GuiDriver, self).header(colitems) + super(GuiSession, self).header(colitems) report = TestReport() report.settime() self.reportlist = [report] self.sendreport(report) def footer(self, colitems): - super(GuiDriver, self).footer(colitems) + super(GuiSession, self).footer(colitems) report = self.reportlist.pop() report.settime() self.sendreport(report) self.channel.send(None) def start(self, colitem): - super(GuiDriver, self).start(colitem) + super(GuiSession, self).start(colitem) report = TestReport() report.start(colitem) self.reportlist.append(report) self.sendreport(report) - def finish(self, colitem, res): - super(GuiDriver, self).finish(colitem, res) + super(GuiSession, self).finish(colitem, res) report = self.reportlist.pop() report.finish(colitem, res, self.option) self.reportlist[-1].status.update(report.status) Modified: py/branch/py-collect/test/tkinter/testing/test_guidriver.py ============================================================================== --- py/branch/py-collect/test/tkinter/testing/test_guidriver.py (original) +++ py/branch/py-collect/test/tkinter/testing/test_guidriver.py Fri Apr 8 05:30:39 2005 @@ -1,10 +1,10 @@ import py -from py.__impl__.test.tkinter import guidriver +from py.__impl__.test.tkinter import guisession from py.__impl__.test.tkinter.util import Status, TestReport, Null -GuiDriver = guidriver.GuiDriver +GuiSession = guisession.GuiSession -class TestGuiDriver: +class TestGuiSession: class ChannelMock: @@ -23,11 +23,11 @@ def setup_method(self, method): self.channel = self.ChannelMock() - self.driver = GuiDriver(Null(), self.channel) + self.session = GuiSession(Null(), self.channel) self.collitems = [Null(), Null()] def test_header_sends_report_with_id_root(self): - self.driver.header(self.collitems) + self.session.header(self.collitems) assert self.channel.sendlist != [] report = TestReport.fromChannel(self.channel.sendlist[0]) @@ -36,8 +36,8 @@ assert report.label == 'Root' def test_footer_sends_report_and_None(self): - self.driver.header(self.collitems) - self.driver.footer(self.collitems) + self.session.header(self.collitems) + self.session.footer(self.collitems) assert self.channel.sendlist != [] assert self.channel.sendlist[-1] is None @@ -46,10 +46,10 @@ assert report.id == 'Root' ## def test_status_is_passed_to_root(self): -## self.driver.header(self.collitems) -## self.driver.start(self.collitems[0]) -## self.driver.finish(self.collitems[0], py.test.collect.Collector.Failed()) -## self.driver.footer(self.collitems) +## self.session.header(self.collitems) +## self.session.start(self.collitems[0]) +## self.session.finish(self.collitems[0], py.test.collect.Collector.Failed()) +## self.session.footer(self.collitems) ## assert self.channel.sendlist[-1] is None ## assert self.channel.sendlist.pop() is None Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Fri Apr 8 05:30:39 2005 @@ -10,7 +10,7 @@ import sha import py -from py.test import Driver +from py.test import Session Item = py.test.Item Collector = py.test.collect.Collector @@ -193,7 +193,8 @@ self.should_stop = False # Set up the GUI part - self.gui = TkMain(parent, self.guiqueue, self.endApplication, self.start_tests, self.getrepository, manager = self) + self.gui = TkMain(parent, self.guiqueue, self.endApplication, + self.start_tests, self.getrepository, manager = self) # Set up the thread to do asynchronous I/O self.thread = Null() @@ -324,18 +325,18 @@ try: channel = gw.remote_exec(""" import py - from py.__impl__.test.tkinter.guidriver import GuiDriver + from py.__impl__.test.tkinter.guisession import GuiSession from py.__impl__.test.tkinter import repository import os import time - from py.__impl__.test.run import FailureCollector + from py.__impl__.test.run import getfailureitems rerun_cols, args = channel.receive() - col = FailureCollector(rerun_cols) + col = getfailureitems(rerun_cols) # hack!! - frontenddriver, paths = py.test.config.init(args) - driver = GuiDriver(option = frontenddriver.option, channel = channel) - driver.process(col) + frontendsession, paths = py.test.config.init(args) + session = GuiSession(option = frontendsession.option, channel = channel) + session.main(col) """) channel.send((rerun_cols, self.args)) self.receive_all_data(channel) @@ -362,7 +363,6 @@ def main(): root = Tkinter.Tk() - client = Manager(root) root.protocol('WM_DELETE_WINDOW', client.endApplication) root.mainloop() Modified: py/branch/py-collect/test/tkinter/util.py ============================================================================== --- py/branch/py-collect/test/tkinter/util.py (original) +++ py/branch/py-collect/test/tkinter/util.py Fri Apr 8 05:30:39 2005 @@ -3,7 +3,7 @@ import sys import py from py.__impl__.test.report.text import out -from py.__impl__.test.terminal import TerminalDriver +from py.__impl__.test.terminal import TerminalSession class Null: """ Null objects always and reliably "do nothing." """ @@ -98,7 +98,7 @@ class OutBuffer(out.Out): '''Simple MockObject for py.__impl__.test.report.text.out.Out. - Used to get the output of TerminalDriver.''' + Used to get the output of TerminalSession.''' def __init__(self, fullwidth = 80 -1): self.output = [] self.fullwidth = fullwidth @@ -149,7 +149,7 @@ setattr(self, key, value) def start(self, collector): - '''Driver.start should call this to init the report''' + '''Session.start should call this to init the report''' self.full_id = collector.listnames() self.id = collector.name if collector.getpathlineno(): # save for Null() in test_util.py @@ -168,9 +168,9 @@ self.status = Status.NotExecuted() def finish(self, collector, res, option = Null()): - '''Driver.finish should call this to set the + '''Session.finish should call this to set the value of error_report - option is passed to Driver at initialization''' + option is passed to Session at initialization''' self.settime() if collector.getpathlineno(): # save for Null() in test_util.py fspath, lineno = collector.getpathlineno() @@ -199,8 +199,8 @@ return base + "_" + basename def report_failed(self, option, item, res): - #XXX hack abuse of TerminalDriver - terminal = TerminalDriver(option) + #XXX hack abuse of TerminalSession + terminal = TerminalSession(option) out = OutBuffer() terminal.out = out terminal.repr_failure(item, res) From jan at codespeak.net Fri Apr 8 14:33:54 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 8 Apr 2005 14:33:54 +0200 (CEST) Subject: [py-svn] r10431 - py/branch/py-collect/test Message-ID: <20050408123354.54BEF27B49@code1.codespeak.net> Author: jan Date: Fri Apr 8 14:33:54 2005 New Revision: 10431 Modified: py/branch/py-collect/test/session.py Log: fixed session.map2colitems tkinter frontend works again Modified: py/branch/py-collect/test/session.py ============================================================================== --- py/branch/py-collect/test/session.py (original) +++ py/branch/py-collect/test/session.py Fri Apr 8 14:33:54 2005 @@ -113,7 +113,9 @@ if isinstance(item, (list, tuple)): colitems.extend(map2colitems(item)) elif not isinstance(item, py.test.collect.Collector): - colitems.append(getfscollector(item)) + colitems.append(getfscollector(item)) + else: + colitems.append(item) return colitems class Exit(Exception): From hpk at codespeak.net Fri Apr 8 18:07:45 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 8 Apr 2005 18:07:45 +0200 (CEST) Subject: [py-svn] r10439 - in py/branch/py-collect: documentation misc/testing path/local path/local/testing test Message-ID: <20050408160745.8A1AC27B75@code1.codespeak.net> Author: hpk Date: Fri Apr 8 18:07:45 2005 New Revision: 10439 Modified: py/branch/py-collect/documentation/rest_test.py py/branch/py-collect/misc/testing/test_initpkg.py py/branch/py-collect/path/local/local.py py/branch/py-collect/path/local/testing/test_local.py py/branch/py-collect/test/config.py Log: - extpy() usage is gone from py.test - transformed a few places to use pyimport() instead of getpymodule() - extended pyimport() to accept custom module names i which case a pseudo-import via execfile is performed. Modified: py/branch/py-collect/documentation/rest_test.py ============================================================================== --- py/branch/py-collect/documentation/rest_test.py (original) +++ py/branch/py-collect/documentation/rest_test.py Fri Apr 8 18:07:45 2005 @@ -3,7 +3,7 @@ import py pydir = py.magic.autopath(vars(py)).dirpath() -rest = pydir.join('bin', 'py.rest').getpymodule() +rest = pydir.join('bin', 'py.rest').pyimport('py_rest') docdir = py.path.svnwc(pydir.join('documentation')) def restcheck(path): Modified: py/branch/py-collect/misc/testing/test_initpkg.py ============================================================================== --- py/branch/py-collect/misc/testing/test_initpkg.py (original) +++ py/branch/py-collect/misc/testing/test_initpkg.py Fri Apr 8 18:07:45 2005 @@ -52,7 +52,7 @@ yield check_import, modpath def check_import(modpath): - #print "checking import", modpath + print "checking import", modpath assert __import__(modpath) def test_shahexdigest(): Modified: py/branch/py-collect/path/local/local.py ============================================================================== --- py/branch/py-collect/path/local/local.py (original) +++ py/branch/py-collect/path/local/local.py Fri Apr 8 18:07:45 2005 @@ -321,7 +321,7 @@ """ return string representation of the Path. """ return self.strpath - def pyimport(self, name=None, ensuresyspath=True): + def pyimport(self, modname=None, ensuresyspath=True): """ return the path as an imported python module. if name is None, look for the containing package and construct an according module name. @@ -331,29 +331,47 @@ """ if not self.check(): raise py.error.ENOENT(self) - #assert self.check() - pkgpath = None #print "trying to import", self - for p in self.parts(reverse=True)[1:]: - if p.join('__init__.py').check(): - pkgpath = p - continue - if ensuresyspath: - #print "inserting into sys.path", p - py.std.sys.path.insert(0, str(p)) - if pkgpath is not None: - pkg = __import__(pkgpath.basename, None, None, []) - assert py.path.local(pkg.__file__).relto(pkgpath) - if hasattr(pkg, '__package__'): - return pkg.__package__.importfrompath(self) - names = self.new(ext='').relto(pkgpath.dirpath()) - names = names.split(self.sep) - dottedname = ".".join(names) - return __import__(dottedname, None, None, ['something']) - else: - #print "sys.path[0] is", sys.path[0] - return __import__(self.purebasename) - raise ImportError("cannot import from path %s" %(self,)) + pkgpath = None + if modname is None: + for parent in self.parts(reverse=True)[1:]: + if parent.join('__init__.py').check(): + pkgpath = parent + continue + if ensuresyspath: + #print "inserting into sys.path", parent + py.std.sys.path.insert(0, str(parent)) + if pkgpath is not None: + pkg = __import__(pkgpath.basename, None, None, []) + assert py.path.local(pkg.__file__).relto(pkgpath) + if hasattr(pkg, '__package__'): + return pkg.__package__.importfrompath(self) + names = self.new(ext='').relto(pkgpath.dirpath()) + names = names.split(self.sep) + modname = ".".join(names) + else: + #print "sys.path[0] is", sys.path[0] + modname = self.purebasename + try: + return __import__(modname, None, None, ['something']) + except ImportError: + if modname in sys.modules: + del sys.modules[modname] + raise ImportError("cannot import from path %s" %(self,)) + else: + try: + return sys.modules[modname] + except KeyError: + # we have a custom modname, do a pseudo-import + mod = py.std.new.module(modname) + mod.__file__ = str(self) + sys.modules[modname] = mod + try: + execfile(str(self), mod.__dict__) + except: + del sys.modules[modname] + raise + return mod def getpymodule(self): if self.ext != '.c': Modified: py/branch/py-collect/path/local/testing/test_local.py ============================================================================== --- py/branch/py-collect/path/local/testing/test_local.py (original) +++ py/branch/py-collect/path/local/testing/test_local.py Fri Apr 8 18:07:45 2005 @@ -209,6 +209,11 @@ assert obj.x == 42 assert obj.__name__ == 'execfile' + def test_pyimport_execfile_different_name(self): + obj = self.root.join('execfile.py').pyimport(modname="0x.y.z") + assert obj.x == 42 + assert obj.__name__ == '0x.y.z' + def test_pyimport_a(self): otherdir = self.root.join('otherdir') mod = otherdir.join('a.py').pyimport() Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Fri Apr 8 18:07:45 2005 @@ -4,7 +4,6 @@ from py.__impl__.test.tool import optparse defaultconfig = py.magic.autopath().dirpath('defaultconfig.py') -defaultconfig = py.path.extpy(defaultconfig) dummy = object() # @@ -26,16 +25,14 @@ """ return config value 'name' from the first conftest file found from traversing up from the given 'path' """ - for p in path.parts(reverse=True): - x = p.join(configbasename) - if x.check(file=1): - extpy = py.path.extpy(x, name) - if extpy.check(): - return extpy.resolve() + configpaths = [p.join(configbasename) for p in path.parts(reverse=True)] + configpaths = [p for p in configpaths if p.check(file=1)] if trydefaultconfig: - extpy = defaultconfig.join(name) - if extpy.check(): - return extpy.resolve() + configpaths.append(defaultconfig) + for p in configpaths: + mod = p.pyimport() + if hasattr(mod, name): + return getattr(mod, name) if default is dummy: raise ValueError("could not find config value %r" % name) return default @@ -116,54 +113,60 @@ option.executable = exe def makeparser(configpaths): - global _lastoptions, _option - # trigger loading config options + # trigger loading conftest files which might add options! for p in configpaths: - p.resolve() - defaultconfig.join('adddefaultoptions').resolve()() + if not p.dirpath('__init__.py').check(file=1): + # hack: we don't want a "globally" imported + # conftest, prone to errors ... + modname = str(p).replace('.', p.sep) + p.pyimport(modname=modname) + else: + p.pyimport() + defaultconfig.pyimport().adddefaultoptions() parser = optparse.OptionParser() - for groupname, groupoptions in _lastoptions: + for groupname, groupoptions in optionstate.lastoptions: optgroup = optparse.OptionGroup(parser, groupname) optgroup.add_options(groupoptions) parser.add_option_group(optgroup) # each time we make a parser which incorporates # all the cmdline options that users may have provided # we want to start over - parser._pytestvalues = _option - _lastoptions = [] - _option = optparse.Values() + parser._pytestvalues = optionstate.option + optionstate.reset() return parser -# XXX _lastoptions holds all options that are added from conftest -# py.test.addoptions() invocations. This "global state" +class optionstate: + lastoptions = [] + option = optparse.Values() + + def reset(cls): + cls.lastoptions[:] = [] + cls.option = optparse.Values() + reset = classmethod(reset) + +# XXX optionstate.lastoptions holds all options that are added from +# conftest's py.test.addoptions() invocations. This "global state" # manipulation is not completely nice but how else could we allow # test projects to add cmdline options and provide them access to # the resulting values? (apart from exposing session initialisation # some more which i really don't want to do) -_lastoptions = [] -_option = optparse.Values() - def addoptions(groupname, *specs): """ add a named group of options to the current testing session. This function gets invoked during testing session initialization. """ - global _lastoptions - _lastoptions.append((groupname, specs)) - return _option + optionstate.lastoptions.append((groupname, specs)) + return optionstate.option def findconfigpaths(anchors): """ return test configuration paths. """ - configpaths = [] d = {} for anchor in anchors: for p in anchor.parts(): x = p.join(configbasename) if x.check(file=1): - extpy = py.path.extpy(x) - if extpy not in d: - configpaths.append(extpy) - d[extpy] = True + d[x] = True + configpaths = d.keys() configpaths.sort(lambda x,y: cmp(len(str(x)), len(str(y)))) return configpaths From hpk at codespeak.net Fri Apr 8 18:28:27 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 8 Apr 2005 18:28:27 +0200 (CEST) Subject: [py-svn] r10440 - py/branch/py-collect/test/testing/import_test/package Message-ID: <20050408162827.5034D27B75@code1.codespeak.net> Author: hpk Date: Fri Apr 8 18:28:27 2005 New Revision: 10440 Added: py/branch/py-collect/test/testing/import_test/package/test_import.py - copied unchanged from r10431, py/branch/py-collect/test/testing/import_test/package/bugtest_import.py Removed: py/branch/py-collect/test/testing/import_test/package/bugtest_import.py Log: finally, after long last, Ian's old import related bugtest can be promoted to become a passing test! Deleted: /py/branch/py-collect/test/testing/import_test/package/bugtest_import.py ============================================================================== --- /py/branch/py-collect/test/testing/import_test/package/bugtest_import.py Fri Apr 8 18:28:27 2005 +++ (empty file) @@ -1,52 +0,0 @@ -import sys -import os - -def setup_module(mod=None): - if mod is None: - f = __file__ - else: - f = mod.__file__ - sys.path.append(os.path.dirname(os.path.dirname(f))) - -def teardown_module(mod=None): - if mod is None: - f = __file__ - else: - f = mod.__file__ - sys.path.remove(os.path.dirname(os.path.dirname(f))) - -def test_import(): - global shared_lib, module_that_imports_shared_lib - import shared_lib - from package import shared_lib as shared_lib2 - import module_that_imports_shared_lib - import absolute_import_shared_lib - all_modules = [ - ('shared_lib', shared_lib), - ('shared_lib2', shared_lib2), - ('module_that_imports_shared_lib', - module_that_imports_shared_lib.shared_lib), - ('absolute_import_shared_lib', - absolute_import_shared_lib.shared_lib), - ] - bad_matches = [] - while all_modules: - name1, mod1 = all_modules[0] - all_modules = all_modules[1:] - for name2, mod2 in all_modules: - if mod1 is not mod2: - bad_matches.append((name1, mod1, name2, mod2)) - for name1, mod1, name2, mod2 in bad_matches: - print "These modules should be identical:" - print " %s:" % name1 - print " ", mod1 - print " %s:" % name2 - print " ", mod2 - print - if bad_matches: - assert False - -if __name__ == "__main__": - setup_module() - test_import - teardown_module() From hpk at codespeak.net Fri Apr 8 18:37:09 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 8 Apr 2005 18:37:09 +0200 (CEST) Subject: [py-svn] r10441 - in py/branch/py-collect: bin documentation misc Message-ID: <20050408163709.5591E27B7B@code1.codespeak.net> Author: hpk Date: Fri Apr 8 18:37:09 2005 New Revision: 10441 Added: py/branch/py-collect/misc/rest.py Modified: py/branch/py-collect/bin/py.rest py/branch/py-collect/documentation/rest_test.py Log: move some rest functionality around at implementation level Modified: py/branch/py-collect/bin/py.rest ============================================================================== --- py/branch/py-collect/bin/py.rest (original) +++ py/branch/py-collect/bin/py.rest Fri Apr 8 18:37:09 2005 @@ -10,53 +10,9 @@ """ +import os, sys from _findpy import py - -import sys, os, traceback - -if hasattr(sys.stdout, 'fileno') and os.isatty(sys.stdout.fileno()): - def log(msg): - print msg -else: - def log(msg): - pass - -def convert_rest_html(source, source_path, stylesheet=None): - """ return html latin1-encoded document for the given input. - source a ReST-string - sourcepath where to look for includes (basically) - stylesheet path (to be used if any) - """ - from docutils.core import publish_string - kwargs = { - 'stylesheet' : stylesheet, - 'traceback' : 1, - 'output_encoding' : 'latin1', - #'halt' : 0, # 'info', - 'halt_level' : 2, - } - return publish_string(source, str(source_path), writer_name='html', - settings_overrides=kwargs) - -def process(txtpath): - """ process a textfile """ - log("processing %s" % txtpath) - assert txtpath.check(ext='.txt') - htmlpath = txtpath.localpath.new(ext='.html') - #svninfopath = txtpath.localpath.new(ext='.svninfo') - - style = txtpath.dirpath('style.css') - if style.check(): - stylesheet = style.basename - else: - stylesheet = None - doc = convert_rest_html(txtpath.read(), txtpath, - stylesheet=stylesheet) - htmlpath.write(doc) - #log("wrote %r" % htmlpath) - #if txtpath.check(svnwc=1, versioned=1): - # info = txtpath.info() - # svninfopath.dumpobj(info) +from py.__impl__.misc import rest if __name__=='__main__': basedir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))) @@ -74,6 +30,6 @@ if p.check(dir=1): for x in p.visit(fil=py.path.checker(fnmatch='*.txt', versioned=True), rec=py.path.checker(dotfile=0)): - process(x) + rest.process(x) elif p.check(file=1): - process(p) + rest.process(p) Modified: py/branch/py-collect/documentation/rest_test.py ============================================================================== --- py/branch/py-collect/documentation/rest_test.py (original) +++ py/branch/py-collect/documentation/rest_test.py Fri Apr 8 18:37:09 2005 @@ -1,9 +1,9 @@ from __future__ import generators import py +from py.__impl__.misc import rest pydir = py.magic.autopath(vars(py)).dirpath() -rest = pydir.join('bin', 'py.rest').pyimport('py_rest') docdir = py.path.svnwc(pydir.join('documentation')) def restcheck(path): Added: py/branch/py-collect/misc/rest.py ============================================================================== --- (empty file) +++ py/branch/py-collect/misc/rest.py Fri Apr 8 18:37:09 2005 @@ -0,0 +1,47 @@ + +import sys, os, traceback + +if hasattr(sys.stdout, 'fileno') and os.isatty(sys.stdout.fileno()): + def log(msg): + print msg +else: + def log(msg): + pass + +def convert_rest_html(source, source_path, stylesheet=None): + """ return html latin1-encoded document for the given input. + source a ReST-string + sourcepath where to look for includes (basically) + stylesheet path (to be used if any) + """ + from docutils.core import publish_string + kwargs = { + 'stylesheet' : stylesheet, + 'traceback' : 1, + 'output_encoding' : 'latin1', + #'halt' : 0, # 'info', + 'halt_level' : 2, + } + return publish_string(source, str(source_path), writer_name='html', + settings_overrides=kwargs) + +def process(txtpath): + """ process a textfile """ + log("processing %s" % txtpath) + assert txtpath.check(ext='.txt') + htmlpath = txtpath.localpath.new(ext='.html') + #svninfopath = txtpath.localpath.new(ext='.svninfo') + + style = txtpath.dirpath('style.css') + if style.check(): + stylesheet = style.basename + else: + stylesheet = None + doc = convert_rest_html(txtpath.read(), txtpath, + stylesheet=stylesheet) + htmlpath.write(doc) + #log("wrote %r" % htmlpath) + #if txtpath.check(svnwc=1, versioned=1): + # info = txtpath.info() + # svninfopath.dumpobj(info) + From hpk at codespeak.net Fri Apr 8 18:37:44 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 8 Apr 2005 18:37:44 +0200 (CEST) Subject: [py-svn] r10442 - in py/branch/py-collect: . path/local Message-ID: <20050408163744.559FF27B7B@code1.codespeak.net> Author: hpk Date: Fri Apr 8 18:37:44 2005 New Revision: 10442 Modified: py/branch/py-collect/initpkg.py py/branch/py-collect/path/local/local.py Log: refactor the package object to just provide a getimportname(path) method, returning None if the package doesn't know a canonical import name. Modified: py/branch/py-collect/initpkg.py ============================================================================== --- py/branch/py-collect/initpkg.py (original) +++ py/branch/py-collect/initpkg.py Fri Apr 8 18:37:44 2005 @@ -70,20 +70,16 @@ current = getattr(current, x) return current - def importfrompath(self, path): + 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): - raise ImportError("cannot import '%s' from '%s'" %( - path, self.module.__name__)) + return None names = path.new(ext='').relto(base).split(path.sep) dottedname = ".".join([self.implmodule.__name__] + names) - try: - return __import__(dottedname, None, None, ['name']) - except ImportError: - if dottedname in sys.modules: - del sys.modules[dottedname] - raise + return dottedname def _loadimpl(self, relfile): """ load implementation for the given relfile. """ Modified: py/branch/py-collect/path/local/local.py ============================================================================== --- py/branch/py-collect/path/local/local.py (original) +++ py/branch/py-collect/path/local/local.py Fri Apr 8 18:37:44 2005 @@ -323,11 +323,9 @@ def pyimport(self, modname=None, ensuresyspath=True): """ return the path as an imported python module. - if name is None, look for the containing package + if modname is None, look for the containing package and construct an according module name. The module will be put/looked up in sys.modules. - insertpkgroot determines if the package's root - will be put """ if not self.check(): raise py.error.ENOENT(self) @@ -345,10 +343,13 @@ pkg = __import__(pkgpath.basename, None, None, []) assert py.path.local(pkg.__file__).relto(pkgpath) if hasattr(pkg, '__package__'): - return pkg.__package__.importfrompath(self) - names = self.new(ext='').relto(pkgpath.dirpath()) - names = names.split(self.sep) - modname = ".".join(names) + modname = pkg.__package__.getimportname(self) + assert modname is not None, "package %s doesn't know %s" % ( + pkg.__name__, self) + else: + names = self.new(ext='').relto(pkgpath.dirpath()) + names = names.split(self.sep) + modname = ".".join(names) else: #print "sys.path[0] is", sys.path[0] modname = self.purebasename @@ -357,7 +358,8 @@ except ImportError: if modname in sys.modules: del sys.modules[modname] - raise ImportError("cannot import from path %s" %(self,)) + raise + raise ImportError("cannot import fspath %s" %(self,)) else: try: return sys.modules[modname] From hpk at codespeak.net Sat Apr 9 11:53:35 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 9 Apr 2005 11:53:35 +0200 (CEST) Subject: [py-svn] r10482 - py/branch/py-collect/path/local/testing Message-ID: <20050409095335.13ED527B71@code1.codespeak.net> Author: hpk Date: Sat Apr 9 11:53:34 2005 New Revision: 10482 Modified: py/branch/py-collect/path/local/testing/test_local.py Log: test that "import" and "pyimport" interoperate Modified: py/branch/py-collect/path/local/testing/test_local.py ============================================================================== --- py/branch/py-collect/path/local/testing/test_local.py (original) +++ py/branch/py-collect/path/local/testing/test_local.py Sat Apr 9 11:53:34 2005 @@ -236,6 +236,16 @@ mod = otherdir.join('d.py').pyimport() assert mod.value2 == "got it" + def test_pyimport_and_import(self): + # XXX maybe a bit of a fragile test ... + p = py.test.ensuretemp("pyimport") + p.ensure('xxxpackage', '__init__.py') + mod1path = p.ensure('xxxpackage', 'module1.py') + mod1 = mod1path.pyimport() + assert mod1.__name__ == 'xxxpackage.module1' + from xxxpackage import module1 + assert module1 is mod1 + #class XTestLocalPath(TestLocalPath): # def __init__(self): # TestLocalPath.__init__(self) From hpk at codespeak.net Sat Apr 9 13:09:31 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 9 Apr 2005 13:09:31 +0200 (CEST) Subject: [py-svn] r10484 - py/branch/py-collect Message-ID: <20050409110931.9AF6627B72@code1.codespeak.net> Author: hpk Date: Sat Apr 9 13:09:31 2005 New Revision: 10484 Modified: py/branch/py-collect/env.py Log: no tooldir in sight anymore Modified: py/branch/py-collect/env.py ============================================================================== --- py/branch/py-collect/env.py (original) +++ py/branch/py-collect/env.py Sat Apr 9 13:09:31 2005 @@ -7,7 +7,6 @@ packagename = os.path.basename(packagedir) bindir = os.path.join(packagedir, 'bin') rootdir = os.path.dirname(packagedir) -tooldir = os.path.join(rootdir, 'tool') def prepend_unixpath(VAR, strpath): value = "%r:$%s" % (strpath, VAR) @@ -19,5 +18,4 @@ if sys.platform != 'win32': print prepend_unixpath('PATH', bindir) - print prepend_unixpath('PATH', tooldir) print prepend_unixpath('PYTHONPATH', rootdir) From hpk at codespeak.net Sat Apr 9 21:42:39 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 9 Apr 2005 21:42:39 +0200 (CEST) Subject: [py-svn] r10498 - in py/branch/py-collect: path/local test test/testing Message-ID: <20050409194239.500D827B72@code1.codespeak.net> Author: hpk Date: Sat Apr 9 21:42:39 2005 New Revision: 10498 Modified: py/branch/py-collect/path/local/local.py py/branch/py-collect/test/collect.py py/branch/py-collect/test/run.py py/branch/py-collect/test/session.py py/branch/py-collect/test/terminal.py py/branch/py-collect/test/testing/test_session.py Log: - factored the "package finding" algo out into py.path.local().pypkgpath() - made the test collection more uniform by effectively unifying direct invocations of test modules and invocations from directories above. - changed the default output of verbose==0 for "module summaries" - small related fixes Modified: py/branch/py-collect/path/local/local.py ============================================================================== --- py/branch/py-collect/path/local/local.py (original) +++ py/branch/py-collect/path/local/local.py Sat Apr 9 21:42:39 2005 @@ -321,8 +321,34 @@ """ return string representation of the Path. """ return self.strpath + def pypkgpath(self, pkgname=None): + """ return the path's package path by looking for the given + pkgname. If pkgname is None then look for the last + directory upwards which still contains an __init__.py. + Return None if a pkgpath can not be determined. + """ + pkgpath = None + for parent in self.parts(reverse=True): + if pkgname is None: + if parent.check(file=1): + continue + if parent.join('__init__.py').check(): + pkgpath = parent + continue + return pkgpath + else: + if parent.basename == pkgname: + return parent + return pkgpath + + def _prependsyspath(self, path): + s = str(path) + if s != sys.path[0]: + #print "prepending to sys.path", s + sys.path.insert(0, s) + def pyimport(self, modname=None, ensuresyspath=True): - """ return the path as an imported python module. + """ return path as an imported python module. if modname is None, look for the containing package and construct an according module name. The module will be put/looked up in sys.modules. @@ -332,33 +358,31 @@ #print "trying to import", self pkgpath = None if modname is None: - for parent in self.parts(reverse=True)[1:]: - if parent.join('__init__.py').check(): - pkgpath = parent - continue + pkgpath = self.pypkgpath() + if pkgpath is not None: if ensuresyspath: - #print "inserting into sys.path", parent - py.std.sys.path.insert(0, str(parent)) - if pkgpath is not None: - pkg = __import__(pkgpath.basename, None, None, []) - assert py.path.local(pkg.__file__).relto(pkgpath) - if hasattr(pkg, '__package__'): - modname = pkg.__package__.getimportname(self) - assert modname is not None, "package %s doesn't know %s" % ( - pkg.__name__, self) - else: - names = self.new(ext='').relto(pkgpath.dirpath()) - names = names.split(self.sep) - modname = ".".join(names) + self._prependsyspath(pkgpath.dirpath()) + pkg = __import__(pkgpath.basename, None, None, []) + assert py.path.local(pkg.__file__).relto(pkgpath) + if hasattr(pkg, '__package__'): + modname = pkg.__package__.getimportname(self) + assert modname is not None, "package %s doesn't know %s" % ( + pkg.__name__, self) else: - #print "sys.path[0] is", sys.path[0] - modname = self.purebasename - try: - return __import__(modname, None, None, ['something']) - except ImportError: - if modname in sys.modules: - del sys.modules[modname] - raise + names = self.new(ext='').relto(pkgpath.dirpath()) + names = names.split(self.sep) + modname = ".".join(names) + else: + # no package scope, still make it possible + if ensuresyspath: + self._prependsyspath(self.dirpath()) + modname = self.purebasename + try: + return __import__(modname, None, None, ['something']) + except ImportError: + if modname in sys.modules: + del sys.modules[modname] + raise raise ImportError("cannot import fspath %s" %(self,)) else: try: Modified: py/branch/py-collect/test/collect.py ============================================================================== --- py/branch/py-collect/test/collect.py (original) +++ py/branch/py-collect/test/collect.py Sat Apr 9 21:42:39 2005 @@ -41,9 +41,26 @@ def getfscollector(fspath): if isinstance(fspath, str): fspath = py.path.local(fspath) - col = Directory(fspath.dirpath()).join(fspath.basename) - col.parent = None - return col + if not fspath.check(): + raise py.error.ENOENT(fspath) + pkgpath = fspath.pypkgpath() + if pkgpath is None: + x = Directory(fspath.dirpath()).join(fspath.basename) + x.parent = None + return x + # XXX alternatively: + #if fspath.check(file=1): + # colname = 'Module' + #else: + # colname = 'Directory' + #col = py.test.config.getconfigvalue(fspath, colname) + #return col(fspath) + current = Directory(pkgpath) + #print "pkgpath", pkgpath + for name in filter(None, fspath.relto(pkgpath).split(fspath.sep)): + #print "joining", name + current = current.join(name) + return current class Collector(object): def __init__(self, name, parent=None): Modified: py/branch/py-collect/test/run.py ============================================================================== --- py/branch/py-collect/test/run.py (original) +++ py/branch/py-collect/test/run.py Sat Apr 9 21:42:39 2005 @@ -124,12 +124,6 @@ l.append(current) return l -def getcollectors(filenames): - current = py.path.local() - for fn in filenames: - fullfn = current.join(str(fn), abs=1) - yield getfscollector(fullfn) - class FrontendSession: def __init__(self, option, args): self.option = option Modified: py/branch/py-collect/test/session.py ============================================================================== --- py/branch/py-collect/test/session.py (original) +++ py/branch/py-collect/test/session.py Sat Apr 9 21:42:39 2005 @@ -57,13 +57,14 @@ if self.shouldclose(): raise SystemExit, "received external close signal" - if not self.option.nocapture and isinstance(colitem, py.test.Item): + if not self.option.nocapture: # and isinstance(colitem, py.test.Item): capture = SimpleOutErrCapture() else: capture = None + + res = None try: self.start(colitem) - res = None try: try: res = self.run(colitem) Modified: py/branch/py-collect/test/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal.py (original) +++ py/branch/py-collect/test/terminal.py Sat Apr 9 21:42:39 2005 @@ -38,19 +38,22 @@ try: numunits = len(list(colitem.iteritems())) except TypeError: - numunits = 99999 - + numunits = -1 + + colitem.numunits = numunits if numunits > 0: fspath = colitem.fspath if self.option.verbose == 0: - parts = fspath.parts() - basename = parts.pop().basename - while parts and parts[-1].basename in ('testing', 'test'): - parts.pop() - base = parts[-1].basename - if len(base) < 13: - base = base + "_" * (13-len(base)) - abbrev_fn = base + "_" + basename + # old representation + #parts = fspath.parts() + #basename = parts.pop().basename + #while parts and parts[-1].basename in ('testing', 'test'): + # parts.pop() + #base = parts[-1].basename + #if len(base) < 13: + # base = base + "_" * (13-len(base)) + #abbrev_fn = base + "_" + basename + abbrev_fn = "/".join(colitem.listnames()) self.out.write('%s[%d] ' % (abbrev_fn, numunits)) elif self.option.verbose > 0: #curdir = py.path.local() @@ -90,7 +93,9 @@ if self.option.exitfirstproblem: py.test.exit("exit on first problem configured.", item=colitem) if result is None or not isinstance(colitem, py.test.Item): - if isinstance(colitem, py.test.collect.Module) and self.option.verbose == 0: + if isinstance(colitem, py.test.collect.Module) \ + and self.option.verbose == 0 \ + and colitem.numunits > 0: self.out.line() return else: @@ -215,6 +220,9 @@ traceback = excinfo.traceback if item: self.cut_traceback(traceback, item) + if not traceback: + self.out.line("empty traceback from item %r" % (item,)) + return last = traceback[-1] recursioncache = {} for entry in traceback: Modified: py/branch/py-collect/test/testing/test_session.py ============================================================================== --- py/branch/py-collect/test/testing/test_session.py (original) +++ py/branch/py-collect/test/testing/test_session.py Sat Apr 9 21:42:39 2005 @@ -133,4 +133,4 @@ item, result = l[-1] assert item.name == 'test_4' names = item.listnames() - assert names == ['ordertest', 'test_orderofexecution.py', 'Testmygroup', '()', 'test_4'] + assert names == ['test_drive', 'ordertest', 'test_orderofexecution.py', 'Testmygroup', '()', 'test_4'] From hpk at codespeak.net Sat Apr 9 23:41:00 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 9 Apr 2005 23:41:00 +0200 (CEST) Subject: [py-svn] r10499 - py/branch/py-collect/test/tkinter Message-ID: <20050409214100.6FA8627B43@code1.codespeak.net> Author: hpk Date: Sat Apr 9 23:41:00 2005 New Revision: 10499 Modified: py/branch/py-collect/test/tkinter/tkgui.py Log: making launching of an editor work ... (for me) Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Sat Apr 9 23:41:00 2005 @@ -123,8 +123,8 @@ #"emacsclient --no-wait ") if editor: print "%s +%s %s" % (editor, line, file) - py.process.cmdexec('%s +%s %s' % (editor, line, file)) - + #py.process.cmdexec('%s +%s %s' % (editor, line, file)) + os.system('%s +%s %s' % (editor, line, file)) def create_new_nodes(self, node): repos = self.repositorycommand() From hpk at codespeak.net Sat Apr 9 23:42:53 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 9 Apr 2005 23:42:53 +0200 (CEST) Subject: [py-svn] r10500 - in py/branch/py-collect/test: . testing Message-ID: <20050409214253.2850727B43@code1.codespeak.net> Author: hpk Date: Sat Apr 9 23:42:52 2005 New Revision: 10500 Modified: py/branch/py-collect/test/collect.py py/branch/py-collect/test/config.py py/branch/py-collect/test/session.py py/branch/py-collect/test/testing/test_collect.py py/branch/py-collect/test/testing/test_session.py Log: - refactored Collector.run() to return a list of names instead of colitems. This makes it more natural for custom Collectors. - fixed output capturing to work with --pdb Modified: py/branch/py-collect/test/collect.py ============================================================================== --- py/branch/py-collect/test/collect.py (original) +++ py/branch/py-collect/test/collect.py Sat Apr 9 23:42:52 2005 @@ -116,7 +116,11 @@ the same order as in their file. """ for x in self.run(): - return x.sortvalue() + return self.join(x).sortvalue() + + def multijoin(self, namelist): + """ return a list of colitems for the given namelist. """ + return [self.join(name) for name in namelist] def getpathlineno(self): return self.fspath, py.std.sys.maxint @@ -163,7 +167,7 @@ yield self else: for x in self.run(): - for y in x.iteritems(): + for y in self.join(x).iteritems(): yield y def fspath(): @@ -203,10 +207,9 @@ def run(self): l = self.fspath.listdir() l.sort() - l = [self.join(x.basename) for x in l + return [x.basename for x in l if (x.check(file=1) and self.filefilter(x) or x.check(dir=1) and self.recfilter(x))] - return filter(None, l) def join(self, name): x = self.fspath.join(name) @@ -226,8 +229,8 @@ for name in dir(self.obj): if self.funcnamefilter(name) or self.classnamefilter(name): x = self.join(name) - if x: - l.append((x.sortvalue(), x)) + if x is not None: + l.append((x.sortvalue(), name)) l.sort() return [x[1] for x in l] @@ -262,7 +265,7 @@ def run(self): if getattr(self.obj, 'disabled', 0): return [] - return [self.join("()")] + return ["()"] def join(self, name): assert name == '()' @@ -300,7 +303,7 @@ if not callable(call): raise TypeError("yielded test %r not callable" %(call,)) l.append(x) - namelist.append(self.join("[%d]" % i)) + namelist.append("[%d]" % i) return namelist def join(self, name): Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Sat Apr 9 23:42:52 2005 @@ -189,9 +189,10 @@ l = [current] return l -def flattenoptions(parser): - for group in parser.option_groups: - for y in group.option_list: - yield y - for x in parser.option_list: - yield x +#XXX was needed for extracting defaults, not needed anymore? +#def flattenoptions(parser): +# for group in parser.option_groups: +# for y in group.option_list: +# yield y +# for x in parser.option_list: +# yield x Modified: py/branch/py-collect/test/session.py ============================================================================== --- py/branch/py-collect/test/session.py (original) +++ py/branch/py-collect/test/session.py Sat Apr 9 23:42:52 2005 @@ -57,30 +57,26 @@ if self.shouldclose(): raise SystemExit, "received external close signal" + res = capture = None if not self.option.nocapture: # and isinstance(colitem, py.test.Item): capture = SimpleOutErrCapture() - else: - capture = None - - res = None + needfinish = False try: self.start(colitem) - try: - try: - res = self.run(colitem) - except (KeyboardInterrupt, Exit): - raise - except colitem.Outcome, res: - if not hasattr(res, 'excinfo'): - res.excinfo = py.code.ExceptionInfo() - except: - excinfo = py.code.ExceptionInfo() - res = colitem.Failed(excinfo=excinfo) - else: - assert (res is None or - isinstance(res, (list, colitem.Outcome))) - finally: - self.finish(colitem, res) + needfinish = True + try: + res = self.run(colitem) + except (KeyboardInterrupt, Exit): + raise + except colitem.Outcome, res: + if not hasattr(res, 'excinfo'): + res.excinfo = py.code.ExceptionInfo() + except: + excinfo = py.code.ExceptionInfo() + res = colitem.Failed(excinfo=excinfo) + else: + assert (res is None or + isinstance(res, (list, colitem.Outcome))) finally: if capture is not None: out, err = capture.reset() @@ -88,6 +84,8 @@ res.out, res.err = out, err except AttributeError: pass + if needfinish: + self.finish(colitem, res) def run(self, colitem): if self.option.collectonly and isinstance(colitem, py.test.Item): @@ -102,7 +100,8 @@ raise TypeError("%r.run() returned neither " "sequence nor None: %r" % (colitem, res)) else: - for obj in seq: + for name in seq: + obj = colitem.join(name) self.runtraced(obj) return res Modified: py/branch/py-collect/test/testing/test_collect.py ============================================================================== --- py/branch/py-collect/test/testing/test_collect.py (original) +++ py/branch/py-collect/test/testing/test_collect.py Sat Apr 9 23:42:52 2005 @@ -29,20 +29,23 @@ col = py.test.collect.Module(fn) l = col.run() assert len(l) == 2 + assert l[0] == 'test_one' + assert l[1] == 'TestClass' + items = list(col.iteritems()) assert len(items) == 2 - - assert l[0].name == 'test_one' - assert l[1].name == 'TestClass' - assert l[1].fspath == fn + for item in items: + assert item.fspath == fn def test_failing_import_directory(): class MyDirectory(py.test.collect.Directory): filefilter = py.path.checker(fnmatch='testspecial*.py') - l = MyDirectory(datadir).run() + mydir = MyDirectory(datadir) + l = mydir.run() assert len(l) == 1 - assert isinstance(l[0], py.test.collect.Module) - py.test.raises(ImportError, l[0].run) + item = mydir.join(l[0]) + assert isinstance(item, py.test.collect.Module) + py.test.raises(ImportError, item.run) def test_module_file_not_found(): fn = datadir.join('nada','no') @@ -58,8 +61,9 @@ col = py.test.collect.Module(datadir.join('disabled.py')) l = col.run() assert len(l) == 1 - assert isinstance(l[0], py.test.collect.Class) - assert not l[0].run() + colitem = col.join(l[0]) + assert isinstance(colitem, py.test.collect.Class) + assert not colitem.run() class Testsomeclass: disabled = True @@ -97,12 +101,13 @@ col = py.test.collect.Module(tfile) l = col.run() assert len(l) == 2 + l = col.multijoin(l) generator = l[0] - print l assert isinstance(generator, py.test.collect.Generator) l2 = generator.run() assert len(l2) == 2 + l2 = generator.multijoin(l2) assert isinstance(l2[0], py.test.Function) assert isinstance(l2[1], py.test.Function) assert l2[0].name == '[0]' @@ -112,10 +117,13 @@ classlist = l[1].run() assert len(classlist) == 1 - generator = classlist[0].run()[0] + classlist = l[1].multijoin(classlist) + cls = classlist[0] + generator = cls.join(cls.run()[0]) assert isinstance(generator, py.test.collect.Generator) l2 = generator.run() assert len(l2) == 2 + l2 = generator.multijoin(l2) assert isinstance(l2[0], py.test.Function) assert isinstance(l2[1], py.test.Function) assert l2[0].name == '[0]' @@ -148,7 +156,10 @@ def check_method(self): assert 23 == 23 """) + print "tmpdir", tmpdir + print "o", o from py.__impl__.test.collect import getfscollector items = list(getfscollector(o).iteritems()) + print items assert len(items) == 2 assert items[1].__class__.__name__ == 'MyFunction' Modified: py/branch/py-collect/test/testing/test_session.py ============================================================================== --- py/branch/py-collect/test/testing/test_session.py (original) +++ py/branch/py-collect/test/testing/test_session.py Sat Apr 9 23:42:52 2005 @@ -133,4 +133,4 @@ item, result = l[-1] assert item.name == 'test_4' names = item.listnames() - assert names == ['test_drive', 'ordertest', 'test_orderofexecution.py', 'Testmygroup', '()', 'test_4'] + assert names == ['ordertest', 'test_orderofexecution.py', 'Testmygroup', '()', 'test_4'] From briandorsey at codespeak.net Sun Apr 10 00:10:58 2005 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Sun, 10 Apr 2005 00:10:58 +0200 (CEST) Subject: [py-svn] r10501 - py/dist/py/tool Message-ID: <20050409221058.CE8A927B45@code1.codespeak.net> Author: briandorsey Date: Sun Apr 10 00:10:58 2005 New Revision: 10501 Added: py/dist/py/tool/ Log: Created a new tool directory. Soon to be home to a generalized utestconvert.py From briandorsey at codespeak.net Sun Apr 10 02:52:10 2005 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Sun, 10 Apr 2005 02:52:10 +0200 (CEST) Subject: [py-svn] r10502 - py/dist/py/tool Message-ID: <20050410005210.E9CFB27B7A@code1.codespeak.net> Author: briandorsey Date: Sun Apr 10 02:52:10 2005 New Revision: 10502 Added: py/dist/py/tool/utestconvert.py Log: Copying from pypy. Deleted five pypy specific lines. Added: py/dist/py/tool/utestconvert.py ============================================================================== --- (empty file) +++ py/dist/py/tool/utestconvert.py Sun Apr 10 02:52:10 2005 @@ -0,0 +1,245 @@ +import re +import sys +import parser + +d={} +# d is the dictionary of unittest changes, keyed to the old name +# used by unittest. +# d[old][0] is the new replacement function. +# d[old][1] is the operator you will substitute, or '' if there is none. +# d[old][2] is the possible number of arguments to the unittest +# function. + +# Old Unittest Name new name operator # of args +d['assertRaises'] = ('raises', '', ['Any']) +d['fail'] = ('raise AssertionError', '', [0,1]) +d['assert_'] = ('assert', '', [1,2]) +d['failIf'] = ('assert not', '', [1,2]) +d['assertEqual'] = ('assert', ' ==', [2,3]) +d['failIfEqual'] = ('assert not', ' ==', [2,3]) +d['assertNotEqual'] = ('assert', ' !=', [2,3]) +d['failUnlessEqual'] = ('assert', ' ==', [2,3]) +d['assertAlmostEqual'] = ('assert round', ' ==', [2,3,4]) +d['failIfAlmostEqual'] = ('assert not round', ' ==', [2,3,4]) +d['assertNotAlmostEqual'] = ('assert round', ' !=', [2,3,4]) +d['failUnlessAlmostEquals'] = ('assert round', ' ==', [2,3,4]) + +# the list of synonyms +d['failUnlessRaises'] = d['assertRaises'] +d['failUnless'] = d['assert_'] +d['assertEquals'] = d['assertEqual'] +d['assertNotEquals'] = d['assertNotEqual'] +d['assertAlmostEquals'] = d['assertAlmostEqual'] +d['assertNotAlmostEquals'] = d['assertNotAlmostEqual'] + +# set up the regular expressions we will need +leading_spaces = re.compile(r'^(\s*)') # this never fails + +pat = '' +for k in d.keys(): # this complicated pattern to match all unittests + pat += '|' + r'^(\s*)' + 'self.' + k + r'\(' # \tself.whatever( + +old_names = re.compile(pat[1:]) +linesep='\n' # nobody will really try to convert files not read + # in text mode, will they? + + +def blocksplitter(fp): + '''split a file into blocks that are headed by functions to rename''' + + blocklist = [] + blockstring = '' + + for line in fp: + interesting = old_names.match(line) + if interesting : + if blockstring: + blocklist.append(blockstring) + blockstring = line # reset the block + else: + blockstring += line + + blocklist.append(blockstring) + return blocklist + +def rewrite_utest(block): + '''rewrite every block to use the new utest functions''' + + '''returns the rewritten unittest, unless it ran into problems, + in which case it just returns the block unchanged. + ''' + utest = old_names.match(block) + + if not utest: + return block + + old = utest.group(0).lstrip()[5:-1] # the name we want to replace + new = d[old][0] # the name of the replacement function + op = d[old][1] # the operator you will use , or '' if there is none. + possible_args = d[old][2] # a list of the number of arguments the + # unittest function could possibly take. + + if possible_args == ['Any']: # just rename assertRaises & friends + return re.sub('self.'+old, new, block) + + message_pos = possible_args[-1] + # the remaining unittests can have an optional message to print + # when they fail. It is always the last argument to the function. + + try: + indent, argl, trailer = decompose_unittest(old, block) + + except SyntaxError: # but we couldn't parse it! + return block + + argnum = len(argl) + if argnum not in possible_args: + # sanity check - this one isn't real either + return block + + elif argnum == message_pos: + message = argl[-1] + argl = argl[:-1] + else: + message = None + + if argnum is 0 or (argnum is 1 and argnum is message_pos): #unittest fail() + string = '' + if message: + message = ' ' + message + + elif message_pos is 4: # assertAlmostEqual & friends + try: + pos = argl[2].lstrip() + except IndexError: + pos = '7' # default if none is specified + string = '(%s -%s, %s)%s 0' % (argl[0], argl[1], pos, op ) + + else: # assert_, assertEquals and all the rest + string = ' ' + op.join(argl) + + if message: + string = string + ',' + message + + return indent + new + string + trailer + +def decompose_unittest(old, block): + '''decompose the block into its component parts''' + + ''' returns indent, arglist, trailer + indent -- the indentation + arglist -- the arguments to the unittest function + trailer -- any extra junk after the closing paren, such as #commment + ''' + + indent = re.match(r'(\s*)', block).group() + pat = re.search('self.' + old + r'\(', block) + + args, trailer = get_expr(block[pat.end():], ')') + arglist = break_args(args, []) + + if arglist == ['']: # there weren't any + return indent, [], trailer + + for i in range(len(arglist)): + try: + parser.expr(arglist[i].lstrip('\t ')) + except SyntaxError: + if i == 0: + arglist[i] = '(' + arglist[i] + ')' + else: + arglist[i] = ' (' + arglist[i] + ')' + + return indent, arglist, trailer + +def break_args(args, arglist): + '''recursively break a string into a list of arguments''' + try: + first, rest = get_expr(args, ',') + if not rest: + return arglist + [first] + else: + return [first] + break_args(rest, arglist) + except SyntaxError: + return arglist + [args] + +def get_expr(s, char): + '''split a string into an expression, and the rest of the string''' + + pos=[] + for i in range(len(s)): + if s[i] == char: + pos.append(i) + if pos == []: + raise SyntaxError # we didn't find the expected char. Ick. + + for p in pos: + # make the python parser do the hard work of deciding which comma + # splits the string into two expressions + try: + parser.expr('(' + s[:p] + ')') + return s[:p], s[p+1:] + except SyntaxError: # It's not an expression yet + pass + raise SyntaxError # We never found anything that worked. + +if __name__ == '__main__': + + from optparse import OptionParser + import sys + + usage = "usage: %prog [-s [filename ...] | [-i | -c filename ...]]" + optparser = OptionParser(usage) + + def select_output (option, opt, value, optparser, **kw): + if hasattr(optparser, 'output'): + optparser.error( + 'Cannot combine -s -i and -c options. Use one only.') + else: + optparser.output = kw['output'] + + optparser.add_option("-s", "--stdout", action="callback", + callback=select_output, + callback_kwargs={'output':'stdout'}, + help="send your output to stdout") + + optparser.add_option("-i", "--inplace", action="callback", + callback=select_output, + callback_kwargs={'output':'inplace'}, + help="overwrite files in place") + + optparser.add_option("-c", "--copy", action="callback", + callback=select_output, + callback_kwargs={'output':'copy'}, + help="copy files ... fn.py --> fn_cp.py") + + options, args = optparser.parse_args() + + output = getattr(optparser, 'output', 'stdout') + + if output in ['inplace', 'copy'] and not args: + optparser.error( + '-i and -c option require at least one filename') + + if not args: + s = '' + for block in blocksplitter(sys.stdin.read()): + s += rewrite_utest(block) + sys.stdout.write(s) + + else: + for infilename in args: # no error checking to see if we can open, etc. + infile = file(infilename) + s = '' + for block in blocksplitter(infile): + s += rewrite_utest(block) + if output == 'inplace': + outfile = file(infilename, 'w+') + elif output == 'copy': # yes, just go clobber any existing .cp + outfile = file (infilename[:-3]+ '_cp.py', 'w+') + else: + outfile = sys.stdout + + outfile.write(s) + + From hpk at codespeak.net Sun Apr 10 13:50:04 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Apr 2005 13:50:04 +0200 (CEST) Subject: [py-svn] r10506 - in py/branch/py-collect/path: . testing Message-ID: <20050410115004.7ECDC27B84@code1.codespeak.net> Author: hpk Date: Sun Apr 10 13:50:04 2005 New Revision: 10506 Modified: py/branch/py-collect/path/common.py py/branch/py-collect/path/testing/fscommon.py Log: make relto() a bit more picky about the type of argument it takes. Modified: py/branch/py-collect/path/common.py ============================================================================== --- py/branch/py-collect/path/common.py (original) +++ py/branch/py-collect/path/common.py Sun Apr 10 13:50:04 2005 @@ -116,6 +116,8 @@ """ return a string which is the relative part of the path to the given 'relpath'. """ + if not isinstance(relpath, (str, PathBase)): + raise TypeError("%r: not a string or path object" %(relpath,)) strrelpath = str(relpath) if strrelpath and strrelpath[-1] != self.sep: strrelpath += self.sep Modified: py/branch/py-collect/path/testing/fscommon.py ============================================================================== --- py/branch/py-collect/path/testing/fscommon.py (original) +++ py/branch/py-collect/path/testing/fscommon.py Sun Apr 10 13:50:04 2005 @@ -118,6 +118,9 @@ url = self.root.join("samplefile") assert url.mtime() > 0 + def test_relto_wrong_type(self): + py.test.raises(TypeError, "self.root.relto(42)") + def test_visit_filesonly(self): l = [] for i in self.root.visit(checker(file=1)): From hpk at codespeak.net Sun Apr 10 15:09:55 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Apr 2005 15:09:55 +0200 (CEST) Subject: [py-svn] r10508 - py/branch/py-collect/test Message-ID: <20050410130955.3A46F27B84@code1.codespeak.net> Author: hpk Date: Sun Apr 10 15:09:55 2005 New Revision: 10508 Modified: py/branch/py-collect/test/collect.py py/branch/py-collect/test/config.py py/branch/py-collect/test/defaultconfig.py py/branch/py-collect/test/terminal.py Log: intermediate checking towards refactoring config/option handling (one of the remaining messy areas) Modified: py/branch/py-collect/test/collect.py ============================================================================== --- py/branch/py-collect/test/collect.py (original) +++ py/branch/py-collect/test/collect.py Sun Apr 10 15:09:55 2005 @@ -45,21 +45,16 @@ raise py.error.ENOENT(fspath) pkgpath = fspath.pypkgpath() if pkgpath is None: - x = Directory(fspath.dirpath()).join(fspath.basename) - x.parent = None - return x - # XXX alternatively: - #if fspath.check(file=1): - # colname = 'Module' - #else: - # colname = 'Directory' - #col = py.test.config.getconfigvalue(fspath, colname) - #return col(fspath) - current = Directory(pkgpath) - #print "pkgpath", pkgpath - for name in filter(None, fspath.relto(pkgpath).split(fspath.sep)): - #print "joining", name - current = current.join(name) + current = Directory(fspath.dirpath()).join(fspath.basename) + current.parent = None + else: + current = Directory(pkgpath) + #print "pkgpath", pkgpath + for name in filter(None, fspath.relto(pkgpath).split(fspath.sep)): + #print "joining", name + current = current.join(name) + top = current.listchain()[0] + #top._config = config return current class Collector(object): Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Sun Apr 10 15:09:55 2005 @@ -69,7 +69,7 @@ assert not hasattr(uservalues, name) setattr(uservalues, name, value) fixoptions(uservalues) - return uservalues, remaining + return uservalues, remaining # # helpers @@ -114,33 +114,32 @@ def makeparser(configpaths): # trigger loading conftest files which might add options! + parser = optionstate.parser + optionstate.option._initialconfigmodules = confmodules = [] + for p in configpaths: if not p.dirpath('__init__.py').check(file=1): - # hack: we don't want a "globally" imported - # conftest, prone to errors ... + # HACK: we don't want a "globally" imported conftest.py, + # prone to conflicts and subtle problems modname = str(p).replace('.', p.sep) - p.pyimport(modname=modname) + mod = p.pyimport(modname=modname) else: - p.pyimport() + mod = p.pyimport() + confmodules.append(mod) defaultconfig.pyimport().adddefaultoptions() - parser = optparse.OptionParser() - for groupname, groupoptions in optionstate.lastoptions: - optgroup = optparse.OptionGroup(parser, groupname) - optgroup.add_options(groupoptions) - parser.add_option_group(optgroup) - # each time we make a parser which incorporates - # all the cmdline options that users may have provided + # each time we make a parser which assembled cmdline options + # that users may have provided in initial configtest's # we want to start over parser._pytestvalues = optionstate.option optionstate.reset() return parser class optionstate: - lastoptions = [] option = optparse.Values() + parser = optparse.OptionParser() def reset(cls): - cls.lastoptions[:] = [] + cls.parser = optparse.OptionParser() cls.option = optparse.Values() reset = classmethod(reset) @@ -155,7 +154,9 @@ """ add a named group of options to the current testing session. This function gets invoked during testing session initialization. """ - optionstate.lastoptions.append((groupname, specs)) + optgroup = optparse.OptionGroup(optionstate.parser, groupname) + optgroup.add_options(specs) + optionstate.parser.add_option_group(optgroup) return optionstate.option def findconfigpaths(anchors): Modified: py/branch/py-collect/test/defaultconfig.py ============================================================================== --- py/branch/py-collect/test/defaultconfig.py (original) +++ py/branch/py-collect/test/defaultconfig.py Sun Apr 10 15:09:55 2005 @@ -44,4 +44,7 @@ Option('', '--collectonly', action="store_true", dest="collectonly", default=False, help="only collect tests, don't execute them. "), + Option('', '--traceconfig', + action="store_true", dest="traceconfig", default=False, + help="trace considerations of conftest.py files. "), ) Modified: py/branch/py-collect/test/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal.py (original) +++ py/branch/py-collect/test/terminal.py Sun Apr 10 15:09:55 2005 @@ -131,6 +131,10 @@ for x in colitems: self.out.line("test target: %s" %(x.fspath,)) + if self.option.traceconfig: + for i,x in py.builtin.enumerate(self.option._initialconfigmodules): + self.out.line("initial testconfig %d: %s" %(i, x.__file__)) + #for i, x in py.builtin.enumerate(py.test.config.configpaths): # self.out.line("initial testconfig %d: %s" %(i, x)) #additional = py.test.config.getfirst('additionalinfo') From arigo at codespeak.net Sun Apr 10 15:12:03 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 10 Apr 2005 15:12:03 +0200 (CEST) Subject: [py-svn] r10509 - py/dist/py/path/local Message-ID: <20050410131203.3E43127B84@code1.codespeak.net> Author: arigo Date: Sun Apr 10 15:12:03 2005 New Revision: 10509 Modified: py/dist/py/path/local/local.py Log: Check that the .pyc file point to the original .py file. Ignore it if it doesn't. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sun Apr 10 15:12:03 2005 @@ -344,7 +344,11 @@ if len(header) == 8: magic, timestamp = py.std.struct.unpack('<4si', header) if magic == my_magic and timestamp == my_timestamp: - return py.std.marshal.load(f) + co = py.std.marshal.load(f) + path1 = co.co_filename + path2 = str(self) + if path1 == path2 or os.path.samefile(path1, path2): + return co finally: f.close() except py.error.Error: From hpk at codespeak.net Sun Apr 10 15:31:49 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Apr 2005 15:31:49 +0200 (CEST) Subject: [py-svn] r10511 - py/branch/py-collect/test Message-ID: <20050410133149.E2FBE27B84@code1.codespeak.net> Author: hpk Date: Sun Apr 10 15:31:49 2005 New Revision: 10511 Modified: py/branch/py-collect/test/config.py py/branch/py-collect/test/defaultconfig.py py/branch/py-collect/test/run.py py/branch/py-collect/test/terminal.py Log: rename cmdline options: --session is now --looponfailing, more obvious, isn't it? -S is now -s because there is a new upcoming rule: py.test takes all lowercase short options while applications may extend test options with capital letters ... Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Sun Apr 10 15:31:49 2005 @@ -42,7 +42,7 @@ commandline arguments and config files. """ cmdlineoption, remaining = parse(args) - if not ignoreremote and (cmdlineoption.session or cmdlineoption.executable): + if not ignoreremote and (cmdlineoption.looponfailing or cmdlineoption.executable): # execute in child process, i.e. setup a frontend # pseudo-session and do the real thing on the other side from py.__impl__.test.run import FrontendSession @@ -99,8 +99,8 @@ def fixoptions(option): """ sanity checks and postprocessing of options. """ - if option.session and option.usepdb: - raise ValueError, "--session together with --pdb not supported yet." + if option.looponfailing and option.usepdb: + raise ValueError, "--looponfailing together with --pdb not supported yet." if option.executable and option.usepdb: raise ValueError, "--exec together with --pdb not supported yet." Modified: py/branch/py-collect/test/defaultconfig.py ============================================================================== --- py/branch/py-collect/test/defaultconfig.py (original) +++ py/branch/py-collect/test/defaultconfig.py Sun Apr 10 15:31:49 2005 @@ -20,7 +20,7 @@ Option('-x', '--exitfirst', action="store_true", dest="exitfirstproblem", default=False, help="exit instantly on first error or failed test."), - Option('-S', '--nocapture', + Option('-s', '--nocapture', action="store_true", dest="nocapture", default=False, help="disable catching of sys.stdout/stderr output."), Option('-l', '--showlocals', @@ -29,9 +29,9 @@ Option('', '--exec', action="store", dest="executable", default=None, help="python executable to run the tests with. "), - Option('', '--session', - action="store_true", dest="session", default=False, - help="run a test session/rerun only failing tests. "), + Option('', '--looponfailing', + action="store_true", dest="looponfailing", default=False, + help="loop on failing tests (running in a separate process)."), Option('', '--fulltrace', action="store_true", dest="fulltrace", default=False, help="Don't try to cut any tracebacks (default is to cut)"), Modified: py/branch/py-collect/test/run.py ============================================================================== --- py/branch/py-collect/test/run.py (original) +++ py/branch/py-collect/test/run.py Sun Apr 10 15:31:49 2005 @@ -135,7 +135,7 @@ rootdir = py.path.local() failures = [] while 1: - if self.option.session: + if self.option.looponfailing: while not checkpyfilechange(rootdir, statcache): py.std.time.sleep(0.4) failures = failure_master(self.option.executable or sys.executable, Modified: py/branch/py-collect/test/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal.py (original) +++ py/branch/py-collect/test/terminal.py Sun Apr 10 15:31:49 2005 @@ -114,7 +114,7 @@ super(TerminalSession, self).header(colitems) self.out.sep("=", "test process starts") option = self.option - if option.session: + if option.looponfailing: mode = 'session/child process' elif option.executable: mode = 'child process' From hpk at codespeak.net Sun Apr 10 15:54:25 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Apr 2005 15:54:25 +0200 (CEST) Subject: [py-svn] r10512 - py/branch/py-collect/test Message-ID: <20050410135425.A301627B84@code1.codespeak.net> Author: hpk Date: Sun Apr 10 15:54:25 2005 New Revision: 10512 Modified: py/branch/py-collect/test/terminal.py Log: lighterweight default header() output Modified: py/branch/py-collect/test/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal.py (original) +++ py/branch/py-collect/test/terminal.py Sun Apr 10 15:54:25 2005 @@ -121,27 +121,28 @@ else: mode = 'inprocess' self.out.line("testing-mode: %s" % mode) - self.out.line("executable : %s (%s)" % + self.out.line("executable: %s (%s)" % (py.std.sys.executable, repr_pythonversion())) - rev = py.__package__.getrev() - self.out.line("using py lib: %s " % ( - py.path.local(py.__file__).dirpath(), rev)) + + if self.option.traceconfig or self.option.verbose: + rev = py.__package__.getrev() + self.out.line("using py lib: %s " % ( + py.path.local(py.__file__).dirpath(), rev)) - for x in colitems: - self.out.line("test target: %s" %(x.fspath,)) + for x in colitems: + self.out.line("test target: %s" %(x.fspath,)) - if self.option.traceconfig: for i,x in py.builtin.enumerate(self.option._initialconfigmodules): self.out.line("initial testconfig %d: %s" %(i, x.__file__)) - #for i, x in py.builtin.enumerate(py.test.config.configpaths): - # self.out.line("initial testconfig %d: %s" %(i, x)) - #additional = py.test.config.getfirst('additionalinfo') - #if additional: - # for key, descr in additional(): - # self.out.line("%s: %s" %(key, descr)) - self.out.sep("=") + #for i, x in py.builtin.enumerate(py.test.config.configpaths): + # self.out.line("initial testconfig %d: %s" %(i, x)) + #additional = py.test.config.getfirst('additionalinfo') + #if additional: + # for key, descr in additional(): + # self.out.line("%s: %s" %(key, descr)) + self.out.line() self.starttime = now() # ------------------- From hpk at codespeak.net Sun Apr 10 16:26:44 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Apr 2005 16:26:44 +0200 (CEST) Subject: [py-svn] r10513 - in py/branch/py-collect: . test test/testing test/tkinter Message-ID: <20050410142644.96A1F27B84@code1.codespeak.net> Author: hpk Date: Sun Apr 10 16:26:44 2005 New Revision: 10513 Modified: py/branch/py-collect/__init__.py py/branch/py-collect/test/cmdline.py py/branch/py-collect/test/collect.py py/branch/py-collect/test/run.py py/branch/py-collect/test/testing/test_config.py py/branch/py-collect/test/testing/test_session.py py/branch/py-collect/test/tkinter/tkgui.py Log: shuffle py.test config API around a bit, there is no special config namespace anymore. Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/branch/py-collect/__init__.py (original) +++ py/branch/py-collect/__init__.py Sun Apr 10 16:26:44 2005 @@ -1,22 +1,23 @@ from initpkg import initpkg initpkg(__name__, exportdefs = { - 'test.skip' : ('./test/item.py', 'skip'), - 'test.addoptions' : ('./test/config.py', 'addoptions'), - 'test.skip_on_error' : ('./test/item.py', 'skip_on_error'), + # helpers for test items 'test.raises' : ('./test/raises.py', 'raises'), + 'test.skip' : ('./test/item.py', 'skip'), 'test.fail' : ('./test/item.py', 'fail'), 'test.exit' : ('./test/session.py', 'exit'), - 'test.Option' : ('./test/tool/optparse.py', 'Option'), - 'test.Session' : ('./test/session.py', 'Session'), - 'test.TerminalSession' : ('./test/terminal.py', 'TerminalSession'), - - 'test.config.init' : ('./test/config.py', 'init'), - 'test.config.parse' : ('./test/config.py', 'parse'), - 'test.config.getconfigvalue' : ('./test/config.py', 'getconfigvalue'), - 'test.ensuretemp' : ('./test/config.py', 'ensuretemp'), - + 'test.skip_on_error' : ('./test/item.py', 'skip_on_error'), 'test.compat.TestCase' : ('./test/compat.py', 'TestCase'), + # configuration/initialization related test api + 'test.addoptions' : ('./test/config.py', 'addoptions'), + 'test.init' : ('./test/config.py', 'init'), + 'test.getconfigvalue' : ('./test/config.py', 'getconfigvalue'), + 'test.ensuretemp' : ('./test/config.py', 'ensuretemp'), + 'test.Option' : ('./test/tool/optparse.py', 'Option'), + + # for customization of collecting/running tests + 'test.Session' : ('./test/session.py', 'Session'), + 'test.TerminalSession' : ('./test/terminal.py', 'TerminalSession'), 'test.collect.Collector' : ('./test/collect.py', 'Collector'), 'test.collect.Directory' : ('./test/collect.py', 'Directory'), 'test.collect.Module' : ('./test/collect.py', 'Module'), Modified: py/branch/py-collect/test/cmdline.py ============================================================================== --- py/branch/py-collect/test/cmdline.py (original) +++ py/branch/py-collect/test/cmdline.py Sun Apr 10 16:26:44 2005 @@ -6,7 +6,7 @@ def main(): args = py.std.sys.argv[1:] - session, paths = py.test.config.init(args) + session, paths = py.test.init(args) try: session.main(paths) except KeyboardInterrupt: Modified: py/branch/py-collect/test/collect.py ============================================================================== --- py/branch/py-collect/test/collect.py (original) +++ py/branch/py-collect/test/collect.py Sun Apr 10 16:26:44 2005 @@ -35,7 +35,7 @@ def configproperty(name): def fget(self): #print "retrieving %r property from %s" %(name, self.fspath) - return py.test.config.getconfigvalue(self.fspath, name) + return py.test.getconfigvalue(self.fspath, name) return property(fget) def getfscollector(fspath): Modified: py/branch/py-collect/test/run.py ============================================================================== --- py/branch/py-collect/test/run.py (original) +++ py/branch/py-collect/test/run.py Sun Apr 10 16:26:44 2005 @@ -93,7 +93,7 @@ def failure_slave(channel): """ we run this on the other side. """ args, failures = channel.receive() - session, paths = py.test.config.init(args, ignoreremote=True) + session, paths = py.test.init(args, ignoreremote=True) if failures: cols = getfailureitems(failures) else: Modified: py/branch/py-collect/test/testing/test_config.py ============================================================================== --- py/branch/py-collect/test/testing/test_config.py (original) +++ py/branch/py-collect/test/testing/test_config.py Sun Apr 10 16:26:44 2005 @@ -1,6 +1,6 @@ from __future__ import generators import py -config = py.test.config +from py.__impl__.test import config class MyClass: def getoptions(self): @@ -40,7 +40,7 @@ """)) old = o.chdir() try: - session, paths = py.test.config.init(['-g', '17']) + session, paths = py.test.init(['-g', '17']) assert session.option.gdest == 17 finally: old.chdir() Modified: py/branch/py-collect/test/testing/test_session.py ============================================================================== --- py/branch/py-collect/test/testing/test_session.py (original) +++ py/branch/py-collect/test/testing/test_session.py Sun Apr 10 16:26:44 2005 @@ -3,10 +3,11 @@ from cStringIO import StringIO tmpdir = py.test.ensuretemp('test_drive') +from py.__impl__.test.config import parse class TestDefaultSession: def test_simple(self): - session, paths = py.test.config.init([]) + session, paths = py.test.init([]) session.main(datadir / 'filetest.py') l = session.getresults(py.test.Item.Failed) assert len(l) == 2 @@ -18,7 +19,7 @@ def setup_method(self, method): self.file = StringIO() - option, _ = py.test.config.parse() + option, _ = parse() self.session = py.test.TerminalSession(option, file=self.file) #print >>f, "session %r is setup for %r" %(self.session, method) #f.flush() Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Sun Apr 10 16:26:44 2005 @@ -184,7 +184,7 @@ self.reset_repository() self.args = py.std.sys.argv[1:] - self.paths = py.test.config.init(self.args)[1] + self.paths = py.test.init(self.args)[1] self.testfilewatcher = TestFileWatcher(*self.paths) # Create the queue self.guiqueue = Queue.Queue() @@ -334,7 +334,7 @@ rerun_cols, args = channel.receive() col = getfailureitems(rerun_cols) # hack!! - frontendsession, paths = py.test.config.init(args) + frontendsession, paths = py.test.init(args) session = GuiSession(option = frontendsession.option, channel = channel) session.main(col) """) From jan at codespeak.net Sun Apr 10 16:53:55 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Sun, 10 Apr 2005 16:53:55 +0200 (CEST) Subject: [py-svn] r10515 - py/branch/py-collect/test/tkinter Message-ID: <20050410145355.C500527B84@code1.codespeak.net> Author: jan Date: Sun Apr 10 16:53:55 2005 New Revision: 10515 Modified: py/branch/py-collect/test/tkinter/tkgui.py py/branch/py-collect/test/tkinter/util.py Log: label shows module path Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Sun Apr 10 16:53:55 2005 @@ -83,7 +83,7 @@ self.attacheditorhotspots(self.text) self.text['state'] = tk.DISABLED - self.reportlabel['text'] = '%s\n%s' % (report.fspath, report.modpath) + self.reportlabel['text'] = '%s\n%s' % (report.path, report.modpath) def attacheditorhotspots(self, text): # Attach clickable regions to a Text widget. @@ -143,7 +143,7 @@ self.parent.title(str(action)) if node_id is not None: report = self.repositorycommand().find(node_id) - self.reportlabel['text'] = '%s\n%s' % (report.fspath, + self.reportlabel['text'] = '%s\n%s' % (report.path, report.modpath) def process_messages(self, manager): Modified: py/branch/py-collect/test/tkinter/util.py ============================================================================== --- py/branch/py-collect/test/tkinter/util.py (original) +++ py/branch/py-collect/test/tkinter/util.py Sun Apr 10 16:53:55 2005 @@ -130,7 +130,7 @@ 'error_report': '', 'finished': False, 'restart_params': None, # ('',('',)) - 'fspath' : '', + 'path' : '', 'modpath': '', } @@ -160,8 +160,8 @@ str_append = ' [%s]' % fspath.basename self.label = collector.name + str_append - self.fspath = self.abbrev_path(collector.fspath) - self.modpath = collector.getmodpath() + self.path = '/'.join(collector.listnames()) + #self.modpath = collector.getmodpath() self.settime() self.restart_params = (str(collector.listchain()[0].fspath), collector.listnames()) From hpk at codespeak.net Sun Apr 10 21:19:33 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Apr 2005 21:19:33 +0200 (CEST) Subject: [py-svn] r10519 - in py/branch/py-collect: . test test/testing test/tkinter Message-ID: <20050410191933.A5CE227B61@code1.codespeak.net> Author: hpk Date: Sun Apr 10 21:19:33 2005 New Revision: 10519 Modified: py/branch/py-collect/__init__.py py/branch/py-collect/test/cmdline.py py/branch/py-collect/test/collect.py py/branch/py-collect/test/config.py py/branch/py-collect/test/defaultconfig.py py/branch/py-collect/test/run.py py/branch/py-collect/test/session.py py/branch/py-collect/test/terminal.py py/branch/py-collect/test/testing/test_config.py py/branch/py-collect/test/testing/test_session.py py/branch/py-collect/test/tkinter/guisession.py py/branch/py-collect/test/tkinter/tkgui.py Log: monster style refactoring dealing with the whole bootstrap/config issue. I'll write some documentation once the dust has settled. Note that currently, looponfailing is not working but py.test.tkinter still works. more follow-up refactorings to follow Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/branch/py-collect/__init__.py (original) +++ py/branch/py-collect/__init__.py Sun Apr 10 21:19:33 2005 @@ -1,6 +1,7 @@ from initpkg import initpkg initpkg(__name__, exportdefs = { - # helpers for test items + + # helpers for use from test functions or collectors 'test.raises' : ('./test/raises.py', 'raises'), 'test.skip' : ('./test/item.py', 'skip'), 'test.fail' : ('./test/item.py', 'fail'), @@ -9,11 +10,8 @@ 'test.compat.TestCase' : ('./test/compat.py', 'TestCase'), # configuration/initialization related test api - 'test.addoptions' : ('./test/config.py', 'addoptions'), - 'test.init' : ('./test/config.py', 'init'), - 'test.getconfigvalue' : ('./test/config.py', 'getconfigvalue'), + 'test.Config' : ('./test/config.py', 'Config'), 'test.ensuretemp' : ('./test/config.py', 'ensuretemp'), - 'test.Option' : ('./test/tool/optparse.py', 'Option'), # for customization of collecting/running tests 'test.Session' : ('./test/session.py', 'Session'), @@ -27,16 +25,19 @@ 'test.Item' : ('./test/item.py', 'Item'), 'test.Function' : ('./test/item.py', 'Function'), + # 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'), + # some nice more or less magic APIs 'magic.greenlet' : ('./magic/greenlet.py', 'greenlet'), 'magic.invoke' : ('./magic/invoke.py', 'invoke'), 'magic.revoke' : ('./magic/invoke.py', 'revoke'), @@ -46,6 +47,7 @@ 'magic.View' : ('./magic/viewtype.py', 'View'), 'magic.AssertionError' : ('./magic/assertion.py', 'AssertionError'), + # generalized inspection API 'code.compile' : ('./code/source.py', 'compile_'), 'code.Source' : ('./code/source.py', 'Source'), 'code.Code' : ('./code/frame.py', 'Code'), @@ -53,14 +55,18 @@ 'code.ExceptionInfo' : ('./code/excinfo.py', 'ExceptionInfo'), 'code.Traceback' : ('./code/traceback2.py', 'Traceback'), + # backports and additions of builtins 'builtin.enumerate' : ('./builtin/enumerate.py', 'enumerate'), + # gateways into remote contexts 'execnet.SocketGateway' : ('./execnet/register.py', 'SocketGateway'), 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), 'execnet.SshGateway' : ('./execnet/register.py', 'SshGateway'), + # 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'), Modified: py/branch/py-collect/test/cmdline.py ============================================================================== --- py/branch/py-collect/test/cmdline.py (original) +++ py/branch/py-collect/test/cmdline.py Sun Apr 10 21:19:33 2005 @@ -5,10 +5,11 @@ # def main(): - args = py.std.sys.argv[1:] - session, paths = py.test.init(args) + config, args = py.test.Config.parse(py.std.sys.argv[1:]) + sessionclass = config.getsessionclass() + session = sessionclass(config) try: - session.main(paths) + session.main(args) except KeyboardInterrupt: print print "Keybordinterrupt" Modified: py/branch/py-collect/test/collect.py ============================================================================== --- py/branch/py-collect/test/collect.py (original) +++ py/branch/py-collect/test/collect.py Sun Apr 10 21:19:33 2005 @@ -35,7 +35,7 @@ def configproperty(name): def fget(self): #print "retrieving %r property from %s" %(name, self.fspath) - return py.test.getconfigvalue(self.fspath, name) + return py.test.Config.getlocalvalue(self.fspath, name) return property(fget) def getfscollector(fspath): Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Sun Apr 10 21:19:33 2005 @@ -11,6 +11,8 @@ # configbasename = 'conftest.py' + +# XXX move to Config class basetemp = None def ensuretemp(string, dir=1): """ return temporary directory path with @@ -20,85 +22,100 @@ if basetemp is None: basetemp = py.path.local.make_numbered_dir(base='pytest-') return basetemp.ensure(string, dir=dir) - -def getconfigvalue(path, name, default=dummy, trydefaultconfig=True): - """ return config value 'name' from the first conftest file found - from traversing up from the given 'path' - """ - configpaths = [p.join(configbasename) for p in path.parts(reverse=True)] - configpaths = [p for p in configpaths if p.check(file=1)] - if trydefaultconfig: - configpaths.append(defaultconfig) - for p in configpaths: - mod = p.pyimport() - if hasattr(mod, name): - return getattr(mod, name) - if default is dummy: - raise ValueError("could not find config value %r" % name) - return default - -def init(args, ignoreremote=False): - """ return session object and test paths, determined from - commandline arguments and config files. - """ - cmdlineoption, remaining = parse(args) - if not ignoreremote and (cmdlineoption.looponfailing or cmdlineoption.executable): - # execute in child process, i.e. setup a frontend - # pseudo-session and do the real thing on the other side - from py.__impl__.test.run import FrontendSession - return FrontendSession(cmdlineoption, args), remaining - Session = getsessionclass(args) - remaining = [py.path.local(x) for x in remaining] - if not remaining: - remaining.append(py.path.local()) - session = Session(cmdlineoption) - return session, remaining - -def parse(args=None): - """ return (Options, remaining args) by parsing - the given arguments. - """ - if args is None: - args = [] - anchorpaths = getanchorpaths(args) - configpaths = findconfigpaths(anchorpaths) - parser = makeparser(configpaths) - cmdlineoption, remaining = parser.parse_args(args) - uservalues = parser._pytestvalues - for name, value in vars(cmdlineoption).items(): - assert not hasattr(uservalues, name) - setattr(uservalues, name, value) - fixoptions(uservalues) - return uservalues, remaining - + +class Config(object): + """ central hub for dealing with configuration/initialization data. """ + Option = optparse.Option + + def __init__(self): + self.option = optparse.Values() + self._parser = optparse.OptionParser() + self._initialconfigmodules = [] + + # class level attributes + def _reset(cls): + cls._config = cls() + _reset = classmethod(_reset) + + def getlocalvalue(cls, path, name, default=dummy, trydefaultconfig=True): + """ return 'name' value looked up from the first conftest file + found up the path (including the path itself). + """ + configpaths = guessconfigpaths(path) + if trydefaultconfig: + configpaths.append(defaultconfig) + for p in configpaths: + mod = importconfig(p) + if hasattr(mod, name): + return getattr(mod, name) + if default is not dummy: + return default + raise ValueError("local config value not found: %r" % name) + getlocalvalue = classmethod(getlocalvalue) + + def parse(cls, args): + """ return Config object from parsing command line arguments. + """ + configpaths = guessconfigpaths(*getanchorpaths(args)) + config = bootstrapconfig(configpaths) + cmdlineoption, remaining = config._parser.parse_args(args) + for name, value in vars(cmdlineoption).items(): + setattr(config.option, name, value) + fixoptions(config.option) + if not remaining: + remaining.append(py.std.os.getcwd()) + return config, remaining + parse = classmethod(parse) + + def addoptions(cls, groupname, *specs): + """ add a named group of options to the current testing session. + This function gets invoked during testing session initialization. + """ + parser = cls._config._parser + optgroup = optparse.OptionGroup(parser, groupname) + optgroup.add_options(specs) + parser.add_option_group(optgroup) + return cls._config.option + addoptions = classmethod(addoptions) + + # instance methods dealing with initial config module handling + + def loadconfig(self, configpath): + """ load configpath as an initial config module. """ + mod = importconfig(configpath) + self._initialconfigmodules.append(mod) + return mod + + def getinitialvalue(self, name, default=dummy): + """ return first value found in initial config modules. """ + for confmodule in self._initialconfigmodules: + if hasattr(confmodule, name): + return getattr(confmodule, name) + if default is not dummy: + return default + raise ValueError("initial config value not found: %r" % name) + + def getsessionclass(self): + """ return Session class determined from cmdline options + and looked up in initial config modules. + """ + if self.option.session: + name = self.option.session + #elif self.option.tkinter: + # name = 'tkinter' + else: + name = 'terminal' + name = name.capitalize() + "Session" + return self.getinitialvalue(name) + +Config._reset() + # # helpers # -def getsessionclass(args): - """ return Session class determined from cmdlines - arguments (in a somewhat heuristic way). - """ - # find session - Session = None - options = [] - anchorpaths = getanchorpaths(args) - for anchor in anchorpaths: - tmp = getconfigvalue(anchor, 'Session', - default=None, trydefaultconfig=False) - if tmp is not None: - if Session is not None and Session[1] != tmp: - raise RuntimeError("conflicting Sessions from %s and %s" %( - Session[0], anchor)) - Session = (anchor, tmp) - if Session is None: - Session = getconfigvalue(anchor, "Session", trydefaultconfig=True) - else: - Session = Session[1] - return Session def fixoptions(option): - """ sanity checks and postprocessing of options. """ - + """ sanity checks and slight postprocessing of options. """ if option.looponfailing and option.usepdb: raise ValueError, "--looponfailing together with --pdb not supported yet." if option.executable and option.usepdb: @@ -112,57 +129,33 @@ assert exe.check() option.executable = exe -def makeparser(configpaths): - # trigger loading conftest files which might add options! - parser = optionstate.parser - optionstate.option._initialconfigmodules = confmodules = [] - for p in configpaths: - if not p.dirpath('__init__.py').check(file=1): - # HACK: we don't want a "globally" imported conftest.py, - # prone to conflicts and subtle problems - modname = str(p).replace('.', p.sep) - mod = p.pyimport(modname=modname) - else: - mod = p.pyimport() - confmodules.append(mod) - defaultconfig.pyimport().adddefaultoptions() - # each time we make a parser which assembled cmdline options - # that users may have provided in initial configtest's - # we want to start over - parser._pytestvalues = optionstate.option - optionstate.reset() - return parser - -class optionstate: - option = optparse.Values() - parser = optparse.OptionParser() - - def reset(cls): - cls.parser = optparse.OptionParser() - cls.option = optparse.Values() - reset = classmethod(reset) - -# XXX optionstate.lastoptions holds all options that are added from -# conftest's py.test.addoptions() invocations. This "global state" -# manipulation is not completely nice but how else could we allow -# test projects to add cmdline options and provide them access to -# the resulting values? (apart from exposing session initialisation -# some more which i really don't want to do) - -def addoptions(groupname, *specs): - """ add a named group of options to the current testing session. - This function gets invoked during testing session initialization. +def bootstrapconfig(configpaths): + """ return 'current' config object, after initializing + it with the given configpaths. """ - optgroup = optparse.OptionGroup(optionstate.parser, groupname) - optgroup.add_options(specs) - optionstate.parser.add_option_group(optgroup) - return optionstate.option + # XXX Config._config holds all options that are added from + # conftest's py.test.Config.addoptions() invocations. This "global state" + # manipulation is unfortunate but how else could we allow + # applications to add cmdline options and provide them + # access to the resulting config values? + + config = Config._config + # trigger loading conftest files which might add options! + for configpath in configpaths: + config.loadconfig(configpath) + config.loadconfig(defaultconfig).adddefaultoptions() + # each time we make a config object (which assembled cmdline + # options through py.test.addoptions() invocations) + # we want to reset and clean up the global state + Config._reset() + return config -def findconfigpaths(anchors): - """ return test configuration paths. """ + +def guessconfigpaths(*paths): + """ return test configuration paths from skimming the args. """ d = {} - for anchor in anchors: + for anchor in paths: for p in anchor.parts(): x = p.join(configbasename) if x.check(file=1): @@ -171,13 +164,6 @@ configpaths.sort(lambda x,y: cmp(len(str(x)), len(str(y)))) return configpaths -def getcollectors(filenames): - """ return filesystem collectors from filenames. """ - current = py.path.local() - for fn in filenames: - fullfn = current.join(fn, abs=1) - yield getfscollector(fullfn) - def getanchorpaths(args): """ yield "anchors" from skimming the args for existing files/dirs. """ current = py.path.local() @@ -190,6 +176,16 @@ l = [current] return l +def importconfig(configpath): + if not configpath.dirpath('__init__.py').check(file=1): + # HACK: we don't want a "globally" imported conftest.py, + # prone to conflicts and subtle problems + modname = str(configpath).replace('.', configpath.sep) + return configpath.pyimport(modname=modname) + else: + return configpath.pyimport() + + #XXX was needed for extracting defaults, not needed anymore? #def flattenoptions(parser): # for group in parser.option_groups: Modified: py/branch/py-collect/test/defaultconfig.py ============================================================================== --- py/branch/py-collect/test/defaultconfig.py (original) +++ py/branch/py-collect/test/defaultconfig.py Sun Apr 10 21:19:33 2005 @@ -1,7 +1,8 @@ import py -Option = py.test.Option +Option = py.test.Config.Option -Session = py.test.TerminalSession +TerminalSession = py.test.TerminalSession +#TkinterSession = py.test.TkinterSession Module = py.test.collect.Module Directory = py.test.collect.Directory @@ -13,13 +14,16 @@ additionalinfo = None def adddefaultoptions(): - py.test.addoptions('py.test standard options', + py.test.Config.addoptions('general options', Option('-v', '--verbose', action="count", dest="verbose", default=0, help="increase verbosity"), Option('-x', '--exitfirst', action="store_true", dest="exitfirstproblem", default=False, help="exit instantly on first error or failed test."), + Option('', '--pdb', + action="store_true", dest="usepdb", default=False, + help="start pdb (the Python debugger) on errors."), Option('-s', '--nocapture', action="store_true", dest="nocapture", default=False, help="disable catching of sys.stdout/stderr output."), @@ -29,18 +33,12 @@ Option('', '--exec', action="store", dest="executable", default=None, help="python executable to run the tests with. "), - Option('', '--looponfailing', - action="store_true", dest="looponfailing", default=False, - help="loop on failing tests (running in a separate process)."), Option('', '--fulltrace', action="store_true", dest="fulltrace", default=False, - help="Don't try to cut any tracebacks (default is to cut)"), + help="don't cut any tracebacks (default is to cut)"), Option('', '--nomagic', action="store_true", dest="nomagic", default=False, - help="don't invoke magic to e.g. beautify failing/error statements."), - Option('', '--pdb', - action="store_true", dest="usepdb", default=False, - help="Start pdb (the Python debugger) on errors."), + help="refrain from using magic as much as possible"), Option('', '--collectonly', action="store_true", dest="collectonly", default=False, help="only collect tests, don't execute them. "), @@ -48,3 +46,15 @@ action="store_true", dest="traceconfig", default=False, help="trace considerations of conftest.py files. "), ) + py.test.Config.addoptions('test-session related options', + #Option('', '--tkinter', + # action="store_true", dest="tkinter", default=False, + # help="use tkinter test session frontend."), + Option('', '--looponfailing', + action="store_true", dest="looponfailing", default=False, + help="loop on failing tests (running in a separate process)."), + Option('', '--session', + action="store", dest="session", default=None, + help="use given sessionclass, default is terminal."), + ) + Modified: py/branch/py-collect/test/run.py ============================================================================== --- py/branch/py-collect/test/run.py (original) +++ py/branch/py-collect/test/run.py Sun Apr 10 21:19:33 2005 @@ -93,11 +93,12 @@ def failure_slave(channel): """ we run this on the other side. """ args, failures = channel.receive() - session, paths = py.test.init(args, ignoreremote=True) + sessionclass, config = py.test.init(args, ignoreremote=True) if failures: cols = getfailureitems(failures) else: - cols = paths + cols = config.paths + session = sessionclass(config) session.shouldclose = channel.isclosed failures = session.main(cols) channel.send(failures) @@ -126,7 +127,7 @@ class FrontendSession: def __init__(self, option, args): - self.option = option + self.config.option = option self.args = args def main(self, *paths): @@ -135,12 +136,12 @@ rootdir = py.path.local() failures = [] while 1: - if self.option.looponfailing: + if self.config.option.looponfailing: while not checkpyfilechange(rootdir, statcache): py.std.time.sleep(0.4) - failures = failure_master(self.option.executable or sys.executable, + failures = failure_master(self.config.option.executable or sys.executable, self.args, failures) - if not self.option.session: + if not self.config.option.session: break print "#" * 60 print "# session mode: %d failures remaining" % len(failures) Modified: py/branch/py-collect/test/session.py ============================================================================== --- py/branch/py-collect/test/session.py (original) +++ py/branch/py-collect/test/session.py Sun Apr 10 21:19:33 2005 @@ -6,22 +6,22 @@ A Session gets test Items from Collectors, # executes the Items and sends the Outcome to the Reporter. """ - def __init__(self, option): + def __init__(self, config): self._memo = [] - self.option = option + self.config = config def shouldclose(self): return False def header(self, colitems): """ setup any neccessary resources. """ - if not self.option.nomagic: + if not self.config.option.nomagic: py.magic.invoke(assertion=1) def footer(self, colitems): """ teardown any resources we know about. """ py.test.Item.state.teardown_all() - if not self.option.nomagic: + if not self.config.option.nomagic: py.magic.revoke(assertion=1) def start(self, colitem): @@ -36,9 +36,9 @@ def warning(self, msg): raise Warning(msg) - def main(self, *objs): + def main(self, *args): """ main loop for running tests. """ - colitems = map2colitems(objs) + colitems = map2colitems(args) try: self.header(colitems) try: @@ -58,7 +58,7 @@ raise SystemExit, "received external close signal" res = capture = None - if not self.option.nocapture: # and isinstance(colitem, py.test.Item): + if not self.config.option.nocapture and isinstance(colitem, py.test.Item): capture = SimpleOutErrCapture() needfinish = False try: @@ -88,7 +88,7 @@ self.finish(colitem, res) def run(self, colitem): - if self.option.collectonly and isinstance(colitem, py.test.Item): + if self.config.option.collectonly and isinstance(colitem, py.test.Item): return res = colitem.run() if res is None: Modified: py/branch/py-collect/test/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal.py (original) +++ py/branch/py-collect/test/terminal.py Sun Apr 10 21:19:33 2005 @@ -5,8 +5,8 @@ Item = py.test.Item class TerminalSession(py.test.Session): - def __init__(self, option, file=None): - super(TerminalSession, self).__init__(option) + def __init__(self, config, file=None): + super(TerminalSession, self).__init__(config) if file is None: file = py.std.sys.stdout self.out = getout(file) @@ -19,7 +19,7 @@ def start(self, colitem): super(TerminalSession, self).start(colitem) - if self.option.collectonly: + if self.config.option.collectonly: cols = self._opencollectors self.out.line(' ' * len(cols) + repr(colitem)) cols.append(colitem) @@ -43,7 +43,7 @@ colitem.numunits = numunits if numunits > 0: fspath = colitem.fspath - if self.option.verbose == 0: + if self.config.option.verbose == 0: # old representation #parts = fspath.parts() #basename = parts.pop().basename @@ -55,7 +55,7 @@ #abbrev_fn = base + "_" + basename abbrev_fn = "/".join(colitem.listnames()) self.out.write('%s[%d] ' % (abbrev_fn, numunits)) - elif self.option.verbose > 0: + elif self.config.option.verbose > 0: #curdir = py.path.local() #if fspath.check(local=True, relto=curdir): # fspath = fspath.relto(curdir) @@ -63,7 +63,7 @@ self.out.line("+ testmodule: %s" % fspath) def start_Item(self, colitem): - if self.option.verbose >= 1: + if self.config.option.verbose >= 1: if isinstance(colitem, py.test.Item): realpath, lineno = colitem.getpathlineno() location = "%s:%d" % (realpath.basename, lineno+1) @@ -73,13 +73,13 @@ def finish(self, colitem, result): end = now() super(TerminalSession, self).finish(colitem, result) - if self.option.collectonly: + if self.config.option.collectonly: cols = self._opencollectors last = cols.pop() #assert last == colitem, "expected %r, got %r" %(last, colitem) return colitem.elapsedtime = end - colitem.start - if self.option.usepdb: + if self.config.option.usepdb: if isinstance(result, Item.Failed): print "dispatching to ppdb", colitem self.repr_failure(colitem, result) @@ -90,17 +90,17 @@ result.excinfo.value)) pdb.post_mortem(result.excinfo._excinfo[2]) if isinstance(result, (colitem.Failed,)): - if self.option.exitfirstproblem: + if self.config.option.exitfirstproblem: py.test.exit("exit on first problem configured.", item=colitem) if result is None or not isinstance(colitem, py.test.Item): if isinstance(colitem, py.test.collect.Module) \ - and self.option.verbose == 0 \ + and self.config.option.verbose == 0 \ and colitem.numunits > 0: self.out.line() return else: restype, c = self._processresult(result) - if self.option.verbose >= 1: + if self.config.option.verbose >= 1: resultstring = self.namemap.get(restype, result.__class__.__name__) resultstring += " (%.2f)" % (colitem.elapsedtime,) self.out.line(resultstring) @@ -113,7 +113,7 @@ def header(self, colitems): super(TerminalSession, self).header(colitems) self.out.sep("=", "test process starts") - option = self.option + option = self.config.option if option.looponfailing: mode = 'session/child process' elif option.executable: @@ -125,16 +125,16 @@ (py.std.sys.executable, repr_pythonversion())) - if self.option.traceconfig or self.option.verbose: + if self.config.option.traceconfig or self.config.option.verbose: rev = py.__package__.getrev() self.out.line("using py lib: %s " % ( py.path.local(py.__file__).dirpath(), rev)) for x in colitems: - self.out.line("test target: %s" %(x.fspath,)) + self.out.line("test target: %s" %(x.fspath,)) - for i,x in py.builtin.enumerate(self.option._initialconfigmodules): - self.out.line("initial testconfig %d: %s" %(i, x.__file__)) + for i,x in py.builtin.enumerate(self.config._initialconfigmodules): + self.out.line("initial conf %d: %s" %(i, x.__file__)) #for i, x in py.builtin.enumerate(py.test.config.configpaths): # self.out.line("initial testconfig %d: %s" %(i, x)) @@ -249,7 +249,7 @@ self.out.sep("_") else: self.out.sep("_ ") - if not self.option.nomagic and \ + if not self.config.option.nomagic and \ isinstance(excinfo.value, RuntimeError) and \ self.isrecursive(entry, recursioncache): self.out.line("Recursion detected (same locals & position)") @@ -311,7 +311,7 @@ return 0 def cut_traceback(self, traceback, item=None): - if self.option.fulltrace or item is None: + if self.config.option.fulltrace or item is None: return path, lineno = item.getpathlineno() for i, entry in py.builtin.enumerate(traceback): @@ -333,13 +333,13 @@ def repr_failure_explanation(self, excinfo, indent): info = None info = getattr(getattr(excinfo, 'value', ''), 'msg', '') - if not info and not self.option.nomagic: + if not info and not self.config.option.nomagic: try: info = excinfo.traceback[-1].reinterpret() # very detailed info except KeyboardInterrupt: raise except: - if self.option.verbose > 1: + if self.config.option.verbose > 1: self.out.line("[reinterpretation traceback]") py.std.traceback.print_exc(file=py.std.sys.stdout) else: @@ -363,7 +363,7 @@ self.out.line(out.strip()) def repr_locals(self, entry): - if self.option.showlocals: + if self.config.option.showlocals: self.out.sep('- ', 'locals') for name, value in entry.frame.f_locals.items(): if name == '__builtins__': Modified: py/branch/py-collect/test/testing/test_config.py ============================================================================== --- py/branch/py-collect/test/testing/test_config.py (original) +++ py/branch/py-collect/test/testing/test_config.py Sun Apr 10 21:19:33 2005 @@ -32,16 +32,16 @@ o = py.test.ensuretemp('configoptions') o.ensure("conftest.py").write(py.code.Source(""" import py - Option = py.test.Option - option = py.test.addoptions("testing group", + Option = py.test.Config.Option + option = py.test.Config.addoptions("testing group", Option('-g', '--glong', action="store", default=42, type="int", dest="gdest", help="g value."), ) """)) old = o.chdir() try: - session, paths = py.test.init(['-g', '17']) - assert session.option.gdest == 17 + config, args = py.test.Config.parse(['-g', '17']) + assert config.option.gdest == 17 finally: old.chdir() Modified: py/branch/py-collect/test/testing/test_session.py ============================================================================== --- py/branch/py-collect/test/testing/test_session.py (original) +++ py/branch/py-collect/test/testing/test_session.py Sun Apr 10 21:19:33 2005 @@ -3,11 +3,11 @@ from cStringIO import StringIO tmpdir = py.test.ensuretemp('test_drive') -from py.__impl__.test.config import parse class TestDefaultSession: def test_simple(self): - session, paths = py.test.init([]) + config, args = py.test.Config.parse([]) + session = config.getsessionclass()(config) session.main(datadir / 'filetest.py') l = session.getresults(py.test.Item.Failed) assert len(l) == 2 @@ -19,8 +19,8 @@ def setup_method(self, method): self.file = StringIO() - option, _ = parse() - self.session = py.test.TerminalSession(option, file=self.file) + config, args = py.test.Config.parse([]) + self.session = py.test.TerminalSession(config, file=self.file) #print >>f, "session %r is setup for %r" %(self.session, method) #f.flush() #print "session is setup", self.session @@ -42,7 +42,7 @@ def test_exit_first_problem(self): session = self.session - session.option.exitfirstproblem = True + session.config.option.exitfirstproblem = True session.main(str(datadir / 'filetest.py')) l = session.getresults(py.test.Item.Failed) assert len(l) == 1 @@ -51,7 +51,7 @@ def test_collectonly(self): session = self.session - session.option.collectonly = True + session.config.option.collectonly = True session.main(str(datadir / 'filetest.py')) out = self.file.getvalue() #print out @@ -78,7 +78,7 @@ """)) #print "hello" session = self.session - #session.option.nocapture = True + #session.config.option.nocapture = True print "calling main", o session.main(str(o)) print "back from main", o Modified: py/branch/py-collect/test/tkinter/guisession.py ============================================================================== --- py/branch/py-collect/test/tkinter/guisession.py (original) +++ py/branch/py-collect/test/tkinter/guisession.py Sun Apr 10 21:19:33 2005 @@ -5,8 +5,8 @@ class GuiSession(py.test.Session): - def __init__(self, option = None, channel = None): - super(GuiSession, self).__init__(option) + def __init__(self, config = None, channel = None): + super(GuiSession, self).__init__(config) self.channel = channel self.reportlist = [] @@ -34,7 +34,7 @@ def finish(self, colitem, res): super(GuiSession, self).finish(colitem, res) report = self.reportlist.pop() - report.finish(colitem, res, self.option) + report.finish(colitem, res, self.config.option) self.reportlist[-1].status.update(report.status) self.sendreport(report) #py.std.time.sleep(0.5) Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Sun Apr 10 21:19:33 2005 @@ -184,7 +184,7 @@ self.reset_repository() self.args = py.std.sys.argv[1:] - self.paths = py.test.init(self.args)[1] + self.paths = py.test.Config.parse(self.args)[1] self.testfilewatcher = TestFileWatcher(*self.paths) # Create the queue self.guiqueue = Queue.Queue() @@ -334,8 +334,8 @@ rerun_cols, args = channel.receive() col = getfailureitems(rerun_cols) # hack!! - frontendsession, paths = py.test.init(args) - session = GuiSession(option = frontendsession.option, channel = channel) + config, paths = py.test.Config.parse(args) + session = GuiSession(config = config, channel=channel) session.main(col) """) channel.send((rerun_cols, self.args)) From hpk at codespeak.net Sun Apr 10 22:32:26 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Apr 2005 22:32:26 +0200 (CEST) Subject: [py-svn] r10520 - in py/branch/py-collect: . bin test test/tkinter Message-ID: <20050410203226.E4D2227B5A@code1.codespeak.net> Author: hpk Date: Sun Apr 10 22:32:26 2005 New Revision: 10520 Removed: py/branch/py-collect/bin/py.test.tkinter Modified: py/branch/py-collect/__init__.py py/branch/py-collect/test/config.py py/branch/py-collect/test/defaultconfig.py py/branch/py-collect/test/tkinter/tkgui.py Log: move tkinter fronntend to become a cmdline option to py.test :-) Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/branch/py-collect/__init__.py (original) +++ py/branch/py-collect/__init__.py Sun Apr 10 22:32:26 2005 @@ -16,6 +16,7 @@ # for customization of collecting/running tests 'test.Session' : ('./test/session.py', 'Session'), 'test.TerminalSession' : ('./test/terminal.py', 'TerminalSession'), + 'test.TkinterSession' : ('./test/tkinter/tkgui.py', 'TkinterSession'), 'test.collect.Collector' : ('./test/collect.py', 'Collector'), 'test.collect.Directory' : ('./test/collect.py', 'Directory'), 'test.collect.Module' : ('./test/collect.py', 'Module'), Deleted: /py/branch/py-collect/bin/py.test.tkinter ============================================================================== --- /py/branch/py-collect/bin/py.test.tkinter Sun Apr 10 22:32:26 2005 +++ (empty file) @@ -1,5 +0,0 @@ -#!/usr/bin/env python - -from _findpy import py -from py.__impl__.test.tkinter import tkgui -tkgui.main() Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Sun Apr 10 22:32:26 2005 @@ -101,8 +101,8 @@ """ if self.option.session: name = self.option.session - #elif self.option.tkinter: - # name = 'tkinter' + elif self.option.tkinter: + name = 'tkinter' else: name = 'terminal' name = name.capitalize() + "Session" Modified: py/branch/py-collect/test/defaultconfig.py ============================================================================== --- py/branch/py-collect/test/defaultconfig.py (original) +++ py/branch/py-collect/test/defaultconfig.py Sun Apr 10 22:32:26 2005 @@ -2,7 +2,7 @@ Option = py.test.Config.Option TerminalSession = py.test.TerminalSession -#TkinterSession = py.test.TkinterSession +TkinterSession = py.test.TkinterSession Module = py.test.collect.Module Directory = py.test.collect.Directory @@ -47,9 +47,9 @@ help="trace considerations of conftest.py files. "), ) py.test.Config.addoptions('test-session related options', - #Option('', '--tkinter', - # action="store_true", dest="tkinter", default=False, - # help="use tkinter test session frontend."), + Option('', '--tkinter', + action="store_true", dest="tkinter", default=False, + help="use tkinter test session frontend."), Option('', '--looponfailing', action="store_true", dest="looponfailing", default=False, help="loop on failing tests (running in a separate process)."), Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Sun Apr 10 22:32:26 2005 @@ -10,7 +10,6 @@ import sha import py -from py.test import Session Item = py.test.Item Collector = py.test.collect.Collector @@ -361,12 +360,12 @@ continue break -def main(): - root = Tkinter.Tk() - client = Manager(root) - root.protocol('WM_DELETE_WINDOW', client.endApplication) - root.mainloop() - +class TkinterSession(py.test.Session): + def main(self, paths): + root = Tkinter.Tk() + client = Manager(root) + root.protocol('WM_DELETE_WINDOW', client.endApplication) + root.mainloop() if __name__ == '__main__': main() From hpk at codespeak.net Sun Apr 10 22:36:55 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Apr 2005 22:36:55 +0200 (CEST) Subject: [py-svn] r10521 - py/branch/py-collect/test Message-ID: <20050410203655.509EB27B5A@code1.codespeak.net> Author: hpk Date: Sun Apr 10 22:36:55 2005 New Revision: 10521 Added: py/branch/py-collect/test/defaultconftest.py - copied unchanged from r10520, py/branch/py-collect/test/defaultconfig.py Removed: py/branch/py-collect/test/defaultconfig.py Modified: py/branch/py-collect/test/config.py Log: internal rename Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Sun Apr 10 22:36:55 2005 @@ -3,7 +3,7 @@ import py from py.__impl__.test.tool import optparse -defaultconfig = py.magic.autopath().dirpath('defaultconfig.py') +defaultconfig = py.magic.autopath().dirpath('defaultconftest.py') dummy = object() # Deleted: /py/branch/py-collect/test/defaultconfig.py ============================================================================== --- /py/branch/py-collect/test/defaultconfig.py Sun Apr 10 22:36:55 2005 +++ (empty file) @@ -1,60 +0,0 @@ -import py -Option = py.test.Config.Option - -TerminalSession = py.test.TerminalSession -TkinterSession = py.test.TkinterSession - -Module = py.test.collect.Module -Directory = py.test.collect.Directory -Class = py.test.collect.Class -Generator = py.test.collect.Generator -Function = py.test.Function -Instance = py.test.collect.Instance - -additionalinfo = None - -def adddefaultoptions(): - py.test.Config.addoptions('general options', - Option('-v', '--verbose', - action="count", dest="verbose", default=0, - help="increase verbosity"), - Option('-x', '--exitfirst', - action="store_true", dest="exitfirstproblem", default=False, - help="exit instantly on first error or failed test."), - Option('', '--pdb', - action="store_true", dest="usepdb", default=False, - help="start pdb (the Python debugger) on errors."), - Option('-s', '--nocapture', - action="store_true", dest="nocapture", default=False, - help="disable catching of sys.stdout/stderr output."), - Option('-l', '--showlocals', - action="store_true", dest="showlocals", default=False, - help="show locals in tracebacks (disabled by default)"), - Option('', '--exec', - action="store", dest="executable", default=None, - help="python executable to run the tests with. "), - Option('', '--fulltrace', - action="store_true", dest="fulltrace", default=False, - help="don't cut any tracebacks (default is to cut)"), - Option('', '--nomagic', - action="store_true", dest="nomagic", default=False, - help="refrain from using magic as much as possible"), - Option('', '--collectonly', - action="store_true", dest="collectonly", default=False, - help="only collect tests, don't execute them. "), - Option('', '--traceconfig', - action="store_true", dest="traceconfig", default=False, - help="trace considerations of conftest.py files. "), - ) - py.test.Config.addoptions('test-session related options', - Option('', '--tkinter', - action="store_true", dest="tkinter", default=False, - help="use tkinter test session frontend."), - Option('', '--looponfailing', - action="store_true", dest="looponfailing", default=False, - help="loop on failing tests (running in a separate process)."), - Option('', '--session', - action="store", dest="session", default=None, - help="use given sessionclass, default is terminal."), - ) - From hpk at codespeak.net Sun Apr 10 22:44:22 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Apr 2005 22:44:22 +0200 (CEST) Subject: [py-svn] r10522 - in py/branch/py-collect/test: . report terminal testing tkinter Message-ID: <20050410204422.9B7D127B5A@code1.codespeak.net> Author: hpk Date: Sun Apr 10 22:44:22 2005 New Revision: 10522 Added: py/branch/py-collect/test/terminal/ (props changed) py/branch/py-collect/test/terminal/__init__.py - copied unchanged from r10519, py/branch/py-collect/test/__init__.py py/branch/py-collect/test/terminal/out.py - copied unchanged from r10519, py/branch/py-collect/test/report/text/out.py py/branch/py-collect/test/terminal/terminal.py - copied, changed from r10519, py/branch/py-collect/test/terminal.py Removed: py/branch/py-collect/test/report/ py/branch/py-collect/test/terminal.py Modified: py/branch/py-collect/test/testing/test_api.py (props changed) py/branch/py-collect/test/tkinter/util.py Log: - move terminal related stuff into subdir - remove unused report directory (although we may grow a reporting abstraction again at some point ...) Deleted: /py/branch/py-collect/test/terminal.py ============================================================================== --- /py/branch/py-collect/test/terminal.py Sun Apr 10 22:44:22 2005 +++ (empty file) @@ -1,386 +0,0 @@ -import py - -from py.__impl__.test.report.text.out import getout -from time import time as now -Item = py.test.Item - -class TerminalSession(py.test.Session): - def __init__(self, config, file=None): - super(TerminalSession, self).__init__(config) - if file is None: - file = py.std.sys.stdout - self.out = getout(file) - self._started = {} - self._opencollectors = [] - - # --------------------- - # PROGRESS information - # --------------------- - - def start(self, colitem): - super(TerminalSession, self).start(colitem) - if self.config.option.collectonly: - cols = self._opencollectors - self.out.line(' ' * len(cols) + repr(colitem)) - cols.append(colitem) - else: - cls = getattr(colitem, '__class__', None) - if cls is None: - return - for typ in py.std.inspect.getmro(cls): - meth = getattr(self, 'start_%s' % typ.__name__, None) - if meth: - meth(colitem) - break - colitem.start = py.std.time.time() - - def start_Module(self, colitem): - try: - numunits = len(list(colitem.iteritems())) - except TypeError: - numunits = -1 - - colitem.numunits = numunits - if numunits > 0: - fspath = colitem.fspath - if self.config.option.verbose == 0: - # old representation - #parts = fspath.parts() - #basename = parts.pop().basename - #while parts and parts[-1].basename in ('testing', 'test'): - # parts.pop() - #base = parts[-1].basename - #if len(base) < 13: - # base = base + "_" * (13-len(base)) - #abbrev_fn = base + "_" + basename - abbrev_fn = "/".join(colitem.listnames()) - self.out.write('%s[%d] ' % (abbrev_fn, numunits)) - elif self.config.option.verbose > 0: - #curdir = py.path.local() - #if fspath.check(local=True, relto=curdir): - # fspath = fspath.relto(curdir) - self.out.line() - self.out.line("+ testmodule: %s" % fspath) - - def start_Item(self, colitem): - if self.config.option.verbose >= 1: - if isinstance(colitem, py.test.Item): - realpath, lineno = colitem.getpathlineno() - location = "%s:%d" % (realpath.basename, lineno+1) - self.out.rewrite("%-20s %s " % ( - location, colitem.reprcall())) - - def finish(self, colitem, result): - end = now() - super(TerminalSession, self).finish(colitem, result) - if self.config.option.collectonly: - cols = self._opencollectors - last = cols.pop() - #assert last == colitem, "expected %r, got %r" %(last, colitem) - return - colitem.elapsedtime = end - colitem.start - if self.config.option.usepdb: - if isinstance(result, Item.Failed): - print "dispatching to ppdb", colitem - self.repr_failure(colitem, result) - import pdb - self.out.rewrite( - '\n%s: %s\n' - % (result.excinfo.type.__name__, - result.excinfo.value)) - pdb.post_mortem(result.excinfo._excinfo[2]) - if isinstance(result, (colitem.Failed,)): - if self.config.option.exitfirstproblem: - py.test.exit("exit on first problem configured.", item=colitem) - if result is None or not isinstance(colitem, py.test.Item): - if isinstance(colitem, py.test.collect.Module) \ - and self.config.option.verbose == 0 \ - and colitem.numunits > 0: - self.out.line() - return - else: - restype, c = self._processresult(result) - if self.config.option.verbose >= 1: - resultstring = self.namemap.get(restype, result.__class__.__name__) - resultstring += " (%.2f)" % (colitem.elapsedtime,) - self.out.line(resultstring) - else: - self.out.write(c) - - # ------------------- - # HEADER information - # ------------------- - def header(self, colitems): - super(TerminalSession, self).header(colitems) - self.out.sep("=", "test process starts") - option = self.config.option - if option.looponfailing: - mode = 'session/child process' - elif option.executable: - mode = 'child process' - else: - mode = 'inprocess' - self.out.line("testing-mode: %s" % mode) - self.out.line("executable: %s (%s)" % - (py.std.sys.executable, repr_pythonversion())) - - - if self.config.option.traceconfig or self.config.option.verbose: - rev = py.__package__.getrev() - self.out.line("using py lib: %s " % ( - py.path.local(py.__file__).dirpath(), rev)) - - for x in colitems: - self.out.line("test target: %s" %(x.fspath,)) - - for i,x in py.builtin.enumerate(self.config._initialconfigmodules): - self.out.line("initial conf %d: %s" %(i, x.__file__)) - - #for i, x in py.builtin.enumerate(py.test.config.configpaths): - # self.out.line("initial testconfig %d: %s" %(i, x)) - #additional = py.test.config.getfirst('additionalinfo') - #if additional: - # for key, descr in additional(): - # self.out.line("%s: %s" %(key, descr)) - self.out.line() - self.starttime = now() - - # ------------------- - # FOOTER information - # ------------------- - - def footer(self, colitems): - super(TerminalSession, self).footer(colitems) - self.endtime = now() - self.out.line() - self.skippedreasons() - self.failures() - self.summaryline() - - # -------------------- - # progress information - # -------------------- - typemap = { - Item.Passed: '.', - Item.Skipped: 's', - Item.Failed: 'F', - } - namemap = { - Item.Passed: 'ok', - Item.Skipped: 'SKIP', - Item.Failed: 'FAIL', - } - - def _processresult(self, testresult): - for restype, char in self.typemap.items(): - if isinstance(testresult, restype): - return restype, char - else: - raise TypeError, "not a result instance: %r" % testresult - - # -------------------- - # summary information - # -------------------- - def summaryline(self): - outlist = [] - sum = 0 - for typ in Item.Passed, Item.Failed, Item.Skipped: - l = self.getresults(typ) - if l: - outlist.append('%d %s' % (len(l), typ.__name__.lower())) - sum += len(l) - elapsed = self.endtime-self.starttime - status = "%s" % ", ".join(outlist) - self.out.sep('=', 'tests finished: %s in %4.2f seconds' % - (status, elapsed)) - - def skippedreasons(self): - texts = {} - for colitem, res in self.getresults(Item.Skipped): - tbindex = getattr(res, 'tbindex', -1) - raisingtb = res.excinfo.traceback[tbindex] - fn = raisingtb.frame.code.path - lineno = raisingtb.lineno - d = texts.setdefault(res.excinfo.exconly(), {}) - d[(fn,lineno)] = res - - if texts: - self.out.line() - self.out.sep('_', 'reasons for skipped tests') - for text, dict in texts.items(): - for (fn, lineno), res in dict.items(): - self.out.line('Skipped in %s:%d' %(fn,lineno)) - self.out.line("reason: %s" % text) - self.out.line() - - def failures(self): - l = self.getresults(Item.Failed) - if l: - self.out.sep('_') - for colitem, res in l: - self.repr_failure(colitem, res) - - def repr_failure(self, item, res): - excinfo = res.excinfo - traceback = excinfo.traceback - if item: - self.cut_traceback(traceback, item) - if not traceback: - self.out.line("empty traceback from item %r" % (item,)) - return - last = traceback[-1] - recursioncache = {} - for entry in traceback: - self.out.line("") - if entry == last: - indent = self.repr_source(entry, 'E') - self.repr_failure_explanation(excinfo, indent) - else: - self.repr_source(entry, '>') - self.out.line("") - self.out.line("[%s:%d]" %(entry.frame.code.path, entry.lineno+1)) - self.repr_locals(entry) - - # trailing info - if entry == last: - if item: - self.repr_failure_info(item, entry) - self.repr_out_err(res) - self.out.sep("_") - else: - self.out.sep("_ ") - if not self.config.option.nomagic and \ - isinstance(excinfo.value, RuntimeError) and \ - self.isrecursive(entry, recursioncache): - 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() - try: - fn, lineno = item.getpathlineno() - except TypeError: - assert isinstance(item.parent, py.test.collect.Generator) - # a generative test yielded a non-callable - fn, lineno = item.parent.getpathlineno() - if fn != entry.frame.code.path or \ - entry.frame.code.firstlineno != lineno: - self.out.line("[testcode : %s:%d]" % (fn, lineno+1)) - if root == fn: - self.out.line("[modulepath: %s]" %(modpath)) - else: - self.out.line("[modulepath: %s %s]" %(root.basename, modpath)) - - def repr_source(self, entry, marker=">"): - try: - source = entry.getsource() - except py.error.ENOENT: - self.out.line("[failure to get at sourcelines from %r]\n" % entry) - else: - source = source.deindent() - for line in source[:-1]: - self.out.line(" " + line) - lastline = source[-1] - self.out.line(marker + " " + lastline) - try: - s = str(source.getstatement(len(source)-1)) - except KeyboardInterrupt: - raise - except: - #self.out.line("[failed to get last statement]\n%s" %(source,)) - s = str(source[-1]) - #print "XXX %r" % s - return 4 + (len(s) - len(s.lstrip())) - return 0 - - def cut_traceback(self, traceback, item=None): - if self.config.option.fulltrace or item is None: - return - path, lineno = item.getpathlineno() - for i, entry in py.builtin.enumerate(traceback): - if entry.frame.code.path == path and \ - entry.frame.code.firstlineno == lineno: - del traceback[:i] - break - # get rid of all frames marked with __tracebackhide__ - l = [] - for entry in traceback: - try: - x = entry.frame.eval("__tracebackhide__") - except: - x = None - if not x: - l.append(entry) - traceback[:] = l - - def repr_failure_explanation(self, excinfo, indent): - info = None - info = getattr(getattr(excinfo, 'value', ''), 'msg', '') - if not info and not self.config.option.nomagic: - try: - info = excinfo.traceback[-1].reinterpret() # very detailed info - except KeyboardInterrupt: - raise - except: - if self.config.option.verbose > 1: - self.out.line("[reinterpretation traceback]") - py.std.traceback.print_exc(file=py.std.sys.stdout) - else: - self.out.line("[reinterpretation failed, increase " - "verbosity to see details]") - indent = " " * indent - if not info: - info = str(excinfo) - - lines = info.split('\n') - self.out.line('~' + indent[:-1] + lines.pop(0)) - for x in lines: - self.out.line(indent + x) - - def repr_out_err(self, res): - for name in 'out', 'err': - if hasattr(res, name): - out = getattr(res, name) - if out.strip(): - self.out.sep("- ", "recorded std%s" % name) - self.out.line(out.strip()) - - def repr_locals(self, entry): - if self.config.option.showlocals: - self.out.sep('- ', 'locals') - for name, value in entry.frame.f_locals.items(): - if name == '__builtins__': - self.out.line("__builtins__ = ") - elif len(repr(value)) < 70 or not isinstance(value, - (list, tuple, dict)): - self.out.line("%-10s = %r" %(name, value)) - else: - self.out.line("%-10s =\\" % (name,)) - py.std.pprint.pprint(value, stream=self.out) - -co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2', - '?', 'eval') - -def repr_pythonversion(): - v = py.std.sys.version_info - try: - return "%s.%s.%s-%s-%s" % v - except ValueError: - return str(v) Copied: py/branch/py-collect/test/terminal/terminal.py (from r10519, py/branch/py-collect/test/terminal.py) ============================================================================== --- py/branch/py-collect/test/terminal.py (original) +++ py/branch/py-collect/test/terminal/terminal.py Sun Apr 10 22:44:22 2005 @@ -1,6 +1,6 @@ import py -from py.__impl__.test.report.text.out import getout +from py.__impl__.test.terminal.out import getout from time import time as now Item = py.test.Item Modified: py/branch/py-collect/test/tkinter/util.py ============================================================================== --- py/branch/py-collect/test/tkinter/util.py (original) +++ py/branch/py-collect/test/tkinter/util.py Sun Apr 10 22:44:22 2005 @@ -2,8 +2,7 @@ import sys import py -from py.__impl__.test.report.text import out -from py.__impl__.test.terminal import TerminalSession +from py.__impl__.test.terminal import out class Null: """ Null objects always and reliably "do nothing." """ @@ -200,7 +199,7 @@ def report_failed(self, option, item, res): #XXX hack abuse of TerminalSession - terminal = TerminalSession(option) + terminal = py.test.TerminalSession(option) out = OutBuffer() terminal.out = out terminal.repr_failure(item, res) From hpk at codespeak.net Sun Apr 10 22:44:37 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Apr 2005 22:44:37 +0200 (CEST) Subject: [py-svn] r10523 - py/branch/py-collect Message-ID: <20050410204437.0A43F27B5A@code1.codespeak.net> Author: hpk Date: Sun Apr 10 22:44:36 2005 New Revision: 10523 Modified: py/branch/py-collect/__init__.py Log: forgot new entrylevel api definition Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/branch/py-collect/__init__.py (original) +++ py/branch/py-collect/__init__.py Sun Apr 10 22:44:36 2005 @@ -15,7 +15,7 @@ # for customization of collecting/running tests 'test.Session' : ('./test/session.py', 'Session'), - 'test.TerminalSession' : ('./test/terminal.py', 'TerminalSession'), + 'test.TerminalSession' : ('./test/terminal/terminal.py', 'TerminalSession'), 'test.TkinterSession' : ('./test/tkinter/tkgui.py', 'TkinterSession'), 'test.collect.Collector' : ('./test/collect.py', 'Collector'), 'test.collect.Directory' : ('./test/collect.py', 'Directory'), From hpk at codespeak.net Sun Apr 10 23:29:35 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 10 Apr 2005 23:29:35 +0200 (CEST) Subject: [py-svn] r10524 - in py/branch/py-collect/test: . tkinter Message-ID: <20050410212935.0306327B5A@code1.codespeak.net> Author: hpk Date: Sun Apr 10 23:29:35 2005 New Revision: 10524 Modified: py/branch/py-collect/test/collect.py py/branch/py-collect/test/config.py py/branch/py-collect/test/defaultconftest.py py/branch/py-collect/test/tkinter/tkgui.py Log: small renames and fixes Modified: py/branch/py-collect/test/collect.py ============================================================================== --- py/branch/py-collect/test/collect.py (original) +++ py/branch/py-collect/test/collect.py Sun Apr 10 23:29:35 2005 @@ -35,7 +35,7 @@ def configproperty(name): def fget(self): #print "retrieving %r property from %s" %(name, self.fspath) - return py.test.Config.getlocalvalue(self.fspath, name) + return py.test.Config.getvalue(name, self.fspath) return property(fget) def getfscollector(fspath): Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Sun Apr 10 23:29:35 2005 @@ -37,7 +37,7 @@ cls._config = cls() _reset = classmethod(_reset) - def getlocalvalue(cls, path, name, default=dummy, trydefaultconfig=True): + def getvalue(cls, name, path=None, default=dummy, trydefaultconfig=True): """ return 'name' value looked up from the first conftest file found up the path (including the path itself). """ @@ -50,8 +50,8 @@ return getattr(mod, name) if default is not dummy: return default - raise ValueError("local config value not found: %r" % name) - getlocalvalue = classmethod(getlocalvalue) + raise ValueError("config value not found: %r, path=%r" % (name, path)) + getvalue = classmethod(getvalue) def parse(cls, args): """ return Config object from parsing command line arguments. @@ -156,10 +156,11 @@ """ return test configuration paths from skimming the args. """ d = {} for anchor in paths: - for p in anchor.parts(): - x = p.join(configbasename) - if x.check(file=1): - d[x] = True + if anchor: + for p in anchor.parts(): + x = p.join(configbasename) + if x.check(file=1): + d[x] = True configpaths = d.keys() configpaths.sort(lambda x,y: cmp(len(str(x)), len(str(y)))) return configpaths Modified: py/branch/py-collect/test/defaultconftest.py ============================================================================== --- py/branch/py-collect/test/defaultconftest.py (original) +++ py/branch/py-collect/test/defaultconftest.py Sun Apr 10 23:29:35 2005 @@ -1,5 +1,4 @@ import py -Option = py.test.Config.Option TerminalSession = py.test.TerminalSession TkinterSession = py.test.TkinterSession @@ -14,6 +13,7 @@ additionalinfo = None def adddefaultoptions(): + Option = py.test.Config.Option py.test.Config.addoptions('general options', Option('-v', '--verbose', action="count", dest="verbose", default=0, Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Sun Apr 10 23:29:35 2005 @@ -360,13 +360,9 @@ continue break -class TkinterSession(py.test.Session): +class TkinterSession(py.test.Session): def main(self, paths): root = Tkinter.Tk() client = Manager(root) root.protocol('WM_DELETE_WINDOW', client.endApplication) root.mainloop() - -if __name__ == '__main__': - main() - From hpk at codespeak.net Mon Apr 11 00:45:46 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 11 Apr 2005 00:45:46 +0200 (CEST) Subject: [py-svn] r10525 - in py/branch/py-collect/test: . terminal testing tkinter Message-ID: <20050410224546.739DC27B5A@code1.codespeak.net> Author: hpk Date: Mon Apr 11 00:45:46 2005 New Revision: 10525 Added: py/branch/py-collect/test/terminal/remote.py - copied, changed from r10519, py/branch/py-collect/test/run.py Removed: py/branch/py-collect/test/run.py Modified: py/branch/py-collect/test/config.py py/branch/py-collect/test/testing/test_session.py py/branch/py-collect/test/tkinter/tkgui.py Log: - shuffle things some more - add some tests for --session parsing Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Mon Apr 11 00:45:46 2005 @@ -99,13 +99,8 @@ """ return Session class determined from cmdline options and looked up in initial config modules. """ - if self.option.session: - name = self.option.session - elif self.option.tkinter: - name = 'tkinter' - else: - name = 'terminal' - name = name.capitalize() + "Session" + name = self.option.session + name += 'Session' return self.getinitialvalue(name) Config._reset() @@ -129,6 +124,15 @@ assert exe.check() option.executable = exe + # setting a correct frontend session + if option.session: + name = option.session + elif option.tkinter: + name = 'tkinter' + else: + name = 'terminal' + name = name.capitalize() + option.session = name def bootstrapconfig(configpaths): """ return 'current' config object, after initializing Deleted: /py/branch/py-collect/test/run.py ============================================================================== --- /py/branch/py-collect/test/run.py Mon Apr 11 00:45:46 2005 +++ (empty file) @@ -1,152 +0,0 @@ -from __future__ import generators -import py -from py.__impl__.execnet.channel import ChannelFile, receive2file -from py.__impl__.test.config import configbasename -from py.__impl__.test.collect import getfscollector -from py.__impl__.test.session import map2colitems -import sys - -def checkpyfilechange(rootdir, statcache): - """ wait until project files are changed. """ - fil = py.path.checker(fnmatch='*.py') - rec = py.path.checker(dotfile=0) - changed = False - for path in rootdir.visit(fil, rec): - oldstat = statcache.get(path, None) - try: - statcache[path] = curstat = path.stat() - except py.error.ENOENT: - if oldstat: - del statcache[path] - print "# WARN: race condition on", path - else: - if oldstat: - if oldstat.st_mtime != curstat.st_mtime or \ - oldstat.st_size != curstat.st_size: - changed = True - print "# MODIFIED", path - else: - changed = True - return changed - -class FailingCollector(py.test.collect.Collector): - def __init__(self, faileditems): - self._faileditems = faileditems - - def __iter__(self): - for x in self._faileditems: - yield x - - -class StdouterrProxy: - def __init__(self, gateway): - self.gateway = gateway - def setup(self): - channel = self.gateway.remote_exec(""" - import sys - out, err = channel.newchannel(), channel.newchannel() - channel.send(out) - channel.send(err) - sys.stdout, sys.stderr = out.open('w'), err.open('w') - """) - self.stdout = channel.receive() - self.stderr = channel.receive() - channel.waitclose(1.0) - py.std.threading.Thread(target=receive2file, - args=(self.stdout, sys.stdout)).start() - py.std.threading.Thread(target=receive2file, - args=(self.stderr, sys.stderr)).start() - def teardown(self): - self.stdout.close() - self.stderr.close() - -def waitfinish(channel): - try: - while 1: - try: - channel.waitclose(0.1) - except (IOError, py.error.Error): - continue - else: - failures = channel.receive() - return failures - break - finally: - #print "closing down channel and gateway" - channel.close() - channel.gateway.exit() - -def failure_master(executable, args, failures): - gw = py.execnet.PopenGateway(executable) - outproxy = StdouterrProxy(gw) - outproxy.setup() - try: - channel = gw.remote_exec(""" - from py.__impl__.test.run import failure_slave - failure_slave(channel) - """) - channel.send((args, failures)) - return waitfinish(channel) - finally: - outproxy.teardown() - -def failure_slave(channel): - """ we run this on the other side. """ - args, failures = channel.receive() - sessionclass, config = py.test.init(args, ignoreremote=True) - if failures: - cols = getfailureitems(failures) - else: - cols = config.paths - session = sessionclass(config) - session.shouldclose = channel.isclosed - failures = session.main(cols) - channel.send(failures) - -def getfailureitems(failures): - l = [] - for rootpath, names in failures: - root = py.path.local(rootpath) - if root.check(dir=1): - current = py.test.collect.Directory(root).Directory(root) - elif root.check(file=1): - current = py.test.collect.Module(root).Module(root) - # root is fspath of names[0] -> pop names[0] - # slicing works with empty lists - names = names[1:] - while names: - name = names.pop(0) - try: - current = current.join(name) - except NameError: - print "WARNING: could not find %s on %r" %(name, current) - break - else: - l.append(current) - return l - -class FrontendSession: - def __init__(self, option, args): - self.config.option = option - self.args = args - - def main(self, *paths): - statcache = {} - # XXX figure out a better rootdir? - rootdir = py.path.local() - failures = [] - while 1: - if self.config.option.looponfailing: - while not checkpyfilechange(rootdir, statcache): - py.std.time.sleep(0.4) - failures = failure_master(self.config.option.executable or sys.executable, - self.args, failures) - if not self.config.option.session: - break - print "#" * 60 - print "# session mode: %d failures remaining" % len(failures) - for root, names in failures: - name = "/".join(names) # XXX - print "Failure at: %r" % (name,) - print "# watching py files below %s" % rootdir - print "# ", "^" * len(str(rootdir)) Copied: py/branch/py-collect/test/terminal/remote.py (from r10519, py/branch/py-collect/test/run.py) ============================================================================== --- py/branch/py-collect/test/run.py (original) +++ py/branch/py-collect/test/terminal/remote.py Mon Apr 11 00:45:46 2005 @@ -6,7 +6,7 @@ from py.__impl__.test.session import map2colitems import sys -def checkpyfilechange(rootdir, statcache): +def checkpyfilechange(rootdir, statcache={}): """ wait until project files are changed. """ fil = py.path.checker(fnmatch='*.py') rec = py.path.checker(dotfile=0) @@ -125,19 +125,14 @@ l.append(current) return l -class FrontendSession: - def __init__(self, option, args): - self.config.option = option - self.args = args - - def main(self, *paths): - statcache = {} +class TerminalFrontendSession: + def main(self, *args): # XXX figure out a better rootdir? rootdir = py.path.local() failures = [] while 1: if self.config.option.looponfailing: - while not checkpyfilechange(rootdir, statcache): + while not checkpyfilechange(rootdir): py.std.time.sleep(0.4) failures = failure_master(self.config.option.executable or sys.executable, self.args, failures) Modified: py/branch/py-collect/test/testing/test_session.py ============================================================================== --- py/branch/py-collect/test/testing/test_session.py (original) +++ py/branch/py-collect/test/testing/test_session.py Mon Apr 11 00:45:46 2005 @@ -13,6 +13,14 @@ assert len(l) == 2 l = session.getresults(py.test.Item.Passed) assert not l + + def test_session_parsing(self): + config, args = py.test.Config.parse(['--session=terminal']) + assert config.getsessionclass() is py.test.TerminalSession + config, args = py.test.Config.parse(['--session=tkinter']) + assert config.getsessionclass() is py.test.TkinterSession + config, args = py.test.Config.parse(['--tkinter']) + assert config.getsessionclass() is py.test.TkinterSession #f = open('/tmp/logfile', 'wa') class TestTerminalSession: Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Mon Apr 11 00:45:46 2005 @@ -328,7 +328,7 @@ from py.__impl__.test.tkinter import repository import os import time - from py.__impl__.test.run import getfailureitems + from py.__impl__.test.terminal.remote import getfailureitems rerun_cols, args = channel.receive() col = getfailureitems(rerun_cols) From hpk at codespeak.net Mon Apr 11 00:53:39 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 11 Apr 2005 00:53:39 +0200 (CEST) Subject: [py-svn] r10526 - py/branch/py-collect/test Message-ID: <20050410225339.9F01827B5A@code1.codespeak.net> Author: hpk Date: Mon Apr 11 00:53:39 2005 New Revision: 10526 Modified: py/branch/py-collect/test/defaultconftest.py Log: nicer option formatting Modified: py/branch/py-collect/test/defaultconftest.py ============================================================================== --- py/branch/py-collect/test/defaultconftest.py (original) +++ py/branch/py-collect/test/defaultconftest.py Mon Apr 11 00:53:39 2005 @@ -21,15 +21,15 @@ Option('-x', '--exitfirst', action="store_true", dest="exitfirstproblem", default=False, help="exit instantly on first error or failed test."), - Option('', '--pdb', - action="store_true", dest="usepdb", default=False, - help="start pdb (the Python debugger) on errors."), Option('-s', '--nocapture', action="store_true", dest="nocapture", default=False, help="disable catching of sys.stdout/stderr output."), Option('-l', '--showlocals', action="store_true", dest="showlocals", default=False, help="show locals in tracebacks (disabled by default)"), + Option('', '--pdb', + action="store_true", dest="usepdb", default=False, + help="start pdb (the Python debugger) on errors."), Option('', '--exec', action="store", dest="executable", default=None, help="python executable to run the tests with. "), From jan at codespeak.net Tue Apr 12 16:13:16 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Tue, 12 Apr 2005 16:13:16 +0200 (CEST) Subject: [py-svn] r10548 - py/branch/py-collect/test/tkinter Message-ID: <20050412141316.E201327B48@code1.codespeak.net> Author: jan Date: Tue Apr 12 16:13:16 2005 New Revision: 10548 Modified: py/branch/py-collect/test/tkinter/guisession.py py/branch/py-collect/test/tkinter/util.py Log: fixes exception in GuiSession Modified: py/branch/py-collect/test/tkinter/guisession.py ============================================================================== --- py/branch/py-collect/test/tkinter/guisession.py (original) +++ py/branch/py-collect/test/tkinter/guisession.py Tue Apr 12 16:13:16 2005 @@ -34,7 +34,7 @@ def finish(self, colitem, res): super(GuiSession, self).finish(colitem, res) report = self.reportlist.pop() - report.finish(colitem, res, self.config.option) + report.finish(colitem, res, self.config) self.reportlist[-1].status.update(report.status) self.sendreport(report) #py.std.time.sleep(0.5) Modified: py/branch/py-collect/test/tkinter/util.py ============================================================================== --- py/branch/py-collect/test/tkinter/util.py (original) +++ py/branch/py-collect/test/tkinter/util.py Tue Apr 12 16:13:16 2005 @@ -166,7 +166,7 @@ collector.listnames()) self.status = Status.NotExecuted() - def finish(self, collector, res, option = Null()): + def finish(self, collector, res, config = Null()): '''Session.finish should call this to set the value of error_report option is passed to Session at initialization''' @@ -181,9 +181,9 @@ self.label = collector.name + str_append if res: if Status(res) in (Status.Failed(), Status.ExceptionFailure()): - self.error_report = self.report_failed(option, collector, res) + self.error_report = self.report_failed(config, collector, res) elif Status(res) == Status.Skipped(): - self.error_report = self.report_skipped(option, collector, res) + self.error_report = self.report_skipped(config, collector, res) self.status.update(Status(res)) self.finished = True @@ -197,15 +197,15 @@ base = base + "_" * (13-len(base)) return base + "_" + basename - def report_failed(self, option, item, res): + def report_failed(self, config, item, res): #XXX hack abuse of TerminalSession - terminal = py.test.TerminalSession(option) + terminal = py.test.TerminalSession(config) out = OutBuffer() terminal.out = out terminal.repr_failure(item, res) return out.getoutput() - def report_skipped(self, option, item, res): + def report_skipped(self, config, item, res): texts = {} tbindex = getattr(res, 'tbindex', -1) raisingtb = res.excinfo.traceback[tbindex] From arigo at codespeak.net Tue Apr 12 16:24:09 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Tue, 12 Apr 2005 16:24:09 +0200 (CEST) Subject: [py-svn] r10550 - py/dist/py/path/local Message-ID: <20050412142409.A713A27B48@code1.codespeak.net> Author: arigo Date: Tue Apr 12 16:24:09 2005 New Revision: 10550 Modified: py/dist/py/path/local/local.py Log: Oups. Crashed if the co_filename stored in .pyc files doesn't exist at all. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Tue Apr 12 16:24:09 2005 @@ -347,8 +347,13 @@ co = py.std.marshal.load(f) path1 = co.co_filename path2 = str(self) - if path1 == path2 or os.path.samefile(path1, path2): + if path1 == path2: return co + try: + if os.path.samefile(path1, path2): + return co + except OSError: # probably path1 not found + pass finally: f.close() except py.error.Error: From jan at codespeak.net Tue Apr 12 16:30:59 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Tue, 12 Apr 2005 16:30:59 +0200 (CEST) Subject: [py-svn] r10553 - in py/branch/py-collect/test/tkinter: . testing Message-ID: <20050412143059.CB94527B56@code1.codespeak.net> Author: jan Date: Tue Apr 12 16:30:59 2005 New Revision: 10553 Modified: py/branch/py-collect/test/tkinter/gui.py py/branch/py-collect/test/tkinter/testing/test_util.py py/branch/py-collect/test/tkinter/tkgui.py py/branch/py-collect/test/tkinter/util.py Log: removes Status.ExceptionFailure Modified: py/branch/py-collect/test/tkinter/gui.py ============================================================================== --- py/branch/py-collect/test/tkinter/gui.py (original) +++ py/branch/py-collect/test/tkinter/gui.py Tue Apr 12 16:30:59 2005 @@ -36,7 +36,7 @@ str(Status.Passed()): self.icon_green, str(Status.Failed()): self.icon_red, str(Status.Skipped()): self.icon_yellow, - str(Status.ExceptionFailure()): self.icon_red} + } def get_icon(self, status): status = str(status) Modified: py/branch/py-collect/test/tkinter/testing/test_util.py ============================================================================== --- py/branch/py-collect/test/tkinter/testing/test_util.py (original) +++ py/branch/py-collect/test/tkinter/testing/test_util.py Tue Apr 12 16:30:59 2005 @@ -20,9 +20,6 @@ status = Status(Item.Skipped()) assert status == status.Skipped() - status = Status(Item.ExceptionFailure()) - assert status == status.ExceptionFailure() - def test_init_with_bad_name(self): status = Status('nothing') Modified: py/branch/py-collect/test/tkinter/tkgui.py ============================================================================== --- py/branch/py-collect/test/tkinter/tkgui.py (original) +++ py/branch/py-collect/test/tkinter/tkgui.py Tue Apr 12 16:30:59 2005 @@ -217,8 +217,7 @@ template = TestReport() new_repository.add(['Root'], template.copy()) for name in [str(x) for x in (Status.Failed(), - Status.Skipped(), - Status.ExceptionFailure())]: + Status.Skipped())]: new_repository.add(['Root', name], template.copy(id = name, label= name)) return new_repository Modified: py/branch/py-collect/test/tkinter/util.py ============================================================================== --- py/branch/py-collect/test/tkinter/util.py (original) +++ py/branch/py-collect/test/tkinter/util.py Tue Apr 12 16:30:59 2005 @@ -58,18 +58,14 @@ py.test.Item.Passed: _Passed, py.test.Item.Skipped: _Skipped, py.test.Item.Failed: _Failed, - py.test.Item.ExceptionFailure: _ExceptionFailure } + } def __init__(self, outcome_or_name = ''): self.str = _NotExecuted if isinstance(outcome_or_name, py.test.Item.Outcome): - # hack - if isinstance(outcome_or_name, py.test.Item.ExceptionFailure): - self.str = self.namemap[py.test.Item.ExceptionFailure] - else: - for restype, name in self.namemap.items(): - if isinstance(outcome_or_name, restype): - self.str = name + for restype, name in self.namemap.items(): + if isinstance(outcome_or_name, restype): + self.str = name else: if str(outcome_or_name) in self.ordered_list: self.str = str(outcome_or_name) @@ -180,7 +176,7 @@ str_append = ' [%s] %0.2fsecs' % (fspath.basename, self.time) self.label = collector.name + str_append if res: - if Status(res) in (Status.Failed(), Status.ExceptionFailure()): + if Status(res) == Status.Failed(): self.error_report = self.report_failed(config, collector, res) elif Status(res) == Status.Skipped(): self.error_report = self.report_skipped(config, collector, res) From arigo at codespeak.net Tue Apr 12 19:35:33 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Tue, 12 Apr 2005 19:35:33 +0200 (CEST) Subject: [py-svn] r10560 - py/dist/py/path/local Message-ID: <20050412173533.D1A2227B97@code1.codespeak.net> Author: arigo Date: Tue Apr 12 19:35:33 2005 New Revision: 10560 Modified: py/dist/py/path/local/local.py Log: no samefile() on Windows... Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Tue Apr 12 19:35:33 2005 @@ -352,7 +352,8 @@ try: if os.path.samefile(path1, path2): return co - except OSError: # probably path1 not found + except (OSError, # probably path1 not found + AttributeError): # samefile() not available pass finally: f.close() From hpk at codespeak.net Tue Apr 12 22:59:14 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 12 Apr 2005 22:59:14 +0200 (CEST) Subject: [py-svn] r10565 - py/dist/py/documentation Message-ID: <20050412205914.D878327B58@code1.codespeak.net> Author: hpk Date: Tue Apr 12 22:59:14 2005 New Revision: 10565 Modified: py/dist/py/documentation/execnet.txt Log: typo Modified: py/dist/py/documentation/execnet.txt ============================================================================== --- py/dist/py/documentation/execnet.txt (original) +++ py/dist/py/documentation/execnet.txt Tue Apr 12 22:59:14 2005 @@ -45,7 +45,7 @@ apps and especially RMI systems you often have to upgrade your server if you upgrade your client. -What about Security? Are you completly nuts? +What about Security? Are you completely nuts? -------------------------------------------- We'll talk about that later :-) From hpk at codespeak.net Tue Apr 12 22:59:48 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 12 Apr 2005 22:59:48 +0200 (CEST) Subject: [py-svn] r10566 - py/dist/py/documentation Message-ID: <20050412205948.BBB6E27B58@code1.codespeak.net> Author: hpk Date: Tue Apr 12 22:59:48 2005 New Revision: 10566 Modified: py/dist/py/documentation/execnet.txt Log: always run the tests Modified: py/dist/py/documentation/execnet.txt ============================================================================== --- py/dist/py/documentation/execnet.txt (original) +++ py/dist/py/documentation/execnet.txt Tue Apr 12 22:59:48 2005 @@ -46,7 +46,7 @@ server if you upgrade your client. What about Security? Are you completely nuts? --------------------------------------------- +--------------------------------------------- We'll talk about that later :-) From hpk at codespeak.net Wed Apr 13 14:28:29 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 13 Apr 2005 14:28:29 +0200 (CEST) Subject: [py-svn] r10573 - py/branch/py-collect/test Message-ID: <20050413122829.A218C27B5C@code1.codespeak.net> Author: hpk Date: Wed Apr 13 14:28:29 2005 New Revision: 10573 Modified: py/branch/py-collect/test/defaultconftest.py Log: reordering options a bit Modified: py/branch/py-collect/test/defaultconftest.py ============================================================================== --- py/branch/py-collect/test/defaultconftest.py (original) +++ py/branch/py-collect/test/defaultconftest.py Wed Apr 13 14:28:29 2005 @@ -30,9 +30,6 @@ Option('', '--pdb', action="store_true", dest="usepdb", default=False, help="start pdb (the Python debugger) on errors."), - Option('', '--exec', - action="store", dest="executable", default=None, - help="python executable to run the tests with. "), Option('', '--fulltrace', action="store_true", dest="fulltrace", default=False, help="don't cut any tracebacks (default is to cut)"), @@ -52,9 +49,12 @@ help="use tkinter test session frontend."), Option('', '--looponfailing', action="store_true", dest="looponfailing", default=False, - help="loop on failing tests (running in a separate process)."), + help="loop on failing test set."), Option('', '--session', action="store", dest="session", default=None, help="use given sessionclass, default is terminal."), + Option('', '--exec', + action="store", dest="executable", default=None, + help="python executable to run the tests with. "), ) From hpk at codespeak.net Wed Apr 13 14:28:47 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 13 Apr 2005 14:28:47 +0200 (CEST) Subject: [py-svn] r10574 - py/branch/py-collect/test Message-ID: <20050413122847.432D427B5C@code1.codespeak.net> Author: hpk Date: Wed Apr 13 14:28:47 2005 New Revision: 10574 Modified: py/branch/py-collect/test/config.py Log: normalizing options a bit more Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Wed Apr 13 14:28:47 2005 @@ -54,7 +54,8 @@ getvalue = classmethod(getvalue) def parse(cls, args): - """ return Config object from parsing command line arguments. + """ return Config object and remaining arguments from parsing + command line arguments. """ configpaths = guessconfigpaths(*getanchorpaths(args)) config = bootstrapconfig(configpaths) @@ -110,7 +111,7 @@ # def fixoptions(option): - """ sanity checks and slight postprocessing of options. """ + """ sanity checks and making option values canonical. """ if option.looponfailing and option.usepdb: raise ValueError, "--looponfailing together with --pdb not supported yet." if option.executable and option.usepdb: @@ -123,6 +124,8 @@ exe = py.path.local.sysfind(option.executable) assert exe.check() option.executable = exe + else: + option.executable = py.std.sys.executable # setting a correct frontend session if option.session: From hpk at codespeak.net Wed Apr 13 15:57:20 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 13 Apr 2005 15:57:20 +0200 (CEST) Subject: [py-svn] r10580 - in py/branch/py-collect/test: . terminal testing Message-ID: <20050413135720.5F95427B4B@code1.codespeak.net> Author: hpk Date: Wed Apr 13 15:57:20 2005 New Revision: 10580 Modified: py/branch/py-collect/test/session.py py/branch/py-collect/test/terminal/out.py py/branch/py-collect/test/terminal/remote.py py/branch/py-collect/test/terminal/terminal.py py/branch/py-collect/test/testing/test_session.py Log: unifying Session.main() signature & invocation making getout() a bit smarter Modified: py/branch/py-collect/test/session.py ============================================================================== --- py/branch/py-collect/test/session.py (original) +++ py/branch/py-collect/test/session.py Wed Apr 13 15:57:20 2005 @@ -36,9 +36,9 @@ def warning(self, msg): raise Warning(msg) - def main(self, *args): + def main(self, args): """ main loop for running tests. """ - colitems = map2colitems(args) + colitems = self._map2colitems(args) try: self.header(colitems) try: @@ -52,7 +52,6 @@ return [(str(item.listchain()[0].fspath), item.listnames()) for item, res in self.getresults(py.test.Item.Failed)] - def runtraced(self, colitem): if self.shouldclose(): raise SystemExit, "received external close signal" @@ -105,18 +104,19 @@ self.runtraced(obj) return res -def map2colitems(items): - # first convert all path objects into collectors - from py.__impl__.test.collect import getfscollector - colitems = [] - for item in items: - if isinstance(item, (list, tuple)): - colitems.extend(map2colitems(item)) - elif not isinstance(item, py.test.collect.Collector): - colitems.append(getfscollector(item)) - else: - colitems.append(item) - return colitems + def _map2colitems(items): + # first convert all path objects into collectors + from py.__impl__.test.collect import getfscollector + colitems = [] + for item in items: + if isinstance(item, (list, tuple)): + colitems.extend(Session._map2colitems(item)) + elif not isinstance(item, py.test.collect.Collector): + colitems.append(getfscollector(item)) + else: + colitems.append(item) + return colitems + _map2colitems = staticmethod(_map2colitems) class Exit(Exception): """ for immediate program exits without tracebacks and reporter/summary. """ Modified: py/branch/py-collect/test/terminal/out.py ============================================================================== --- py/branch/py-collect/test/terminal/out.py (original) +++ py/branch/py-collect/test/terminal/out.py Wed Apr 13 15:57:20 2005 @@ -1,6 +1,9 @@ from __future__ import generators import sys import os +import py + +from py.__impl__.execnet.channel import Channel class Out(object): tty = False @@ -67,7 +70,18 @@ def getout(file): # XXX investigate further into terminal output, this is not enough # - if False and file.isatty(): + if file is None: + file = py.std.sys.stdout + elif hasattr(file, 'send'): + file = WriteFile(file) + if hasattr(file, 'isatty') and file.isatty(): return TerminalOut(file) else: return FileOut(file) + +class WriteFile(object): + def __init__(self, writemethod): + self.write = writemethod + def flush(self): + return + Modified: py/branch/py-collect/test/terminal/remote.py ============================================================================== --- py/branch/py-collect/test/terminal/remote.py (original) +++ py/branch/py-collect/test/terminal/remote.py Wed Apr 13 15:57:20 2005 @@ -3,7 +3,6 @@ from py.__impl__.execnet.channel import ChannelFile, receive2file from py.__impl__.test.config import configbasename from py.__impl__.test.collect import getfscollector -from py.__impl__.test.session import map2colitems import sys def checkpyfilechange(rootdir, statcache={}): Modified: py/branch/py-collect/test/terminal/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal/terminal.py (original) +++ py/branch/py-collect/test/terminal/terminal.py Wed Apr 13 15:57:20 2005 @@ -1,14 +1,12 @@ import py -from py.__impl__.test.terminal.out import getout from time import time as now Item = py.test.Item +from py.__impl__.test.terminal.out import getout class TerminalSession(py.test.Session): def __init__(self, config, file=None): super(TerminalSession, self).__init__(config) - if file is None: - file = py.std.sys.stdout self.out = getout(file) self._started = {} self._opencollectors = [] Modified: py/branch/py-collect/test/testing/test_session.py ============================================================================== --- py/branch/py-collect/test/testing/test_session.py (original) +++ py/branch/py-collect/test/testing/test_session.py Wed Apr 13 15:57:20 2005 @@ -7,8 +7,8 @@ class TestDefaultSession: def test_simple(self): config, args = py.test.Config.parse([]) - session = config.getsessionclass()(config) - session.main(datadir / 'filetest.py') + session = config.getsessionclass()(config, py.std.sys.stdout) + session.main([datadir / 'filetest.py']) l = session.getresults(py.test.Item.Failed) assert len(l) == 2 l = session.getresults(py.test.Item.Passed) @@ -38,7 +38,7 @@ #f.flush() def test_terminal(self): - self.session.main(datadir / 'filetest.py') + self.session.main([datadir / 'filetest.py']) out = self.file.getvalue() #print "memo" #print self.session._memo @@ -51,7 +51,7 @@ def test_exit_first_problem(self): session = self.session session.config.option.exitfirstproblem = True - session.main(str(datadir / 'filetest.py')) + session.main([str(datadir / 'filetest.py')]) l = session.getresults(py.test.Item.Failed) assert len(l) == 1 l = session.getresults(py.test.Item.Passed) @@ -60,7 +60,7 @@ def test_collectonly(self): session = self.session session.config.option.collectonly = True - session.main(str(datadir / 'filetest.py')) + session.main([str(datadir / 'filetest.py')]) out = self.file.getvalue() #print out l = session.getresults(py.test.Item.Failed) @@ -88,7 +88,7 @@ session = self.session #session.config.option.nocapture = True print "calling main", o - session.main(str(o)) + session.main([str(o)]) print "back from main", o out = self.file.getvalue() #print out @@ -103,7 +103,7 @@ yield None """)) session = self.session - session.main(o) + session.main([o]) out = self.file.getvalue() #print out i = out.find('TypeError') From hpk at codespeak.net Wed Apr 13 17:52:37 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 13 Apr 2005 17:52:37 +0200 (CEST) Subject: [py-svn] r10583 - in py/branch/py-collect/execnet: . testing Message-ID: <20050413155237.1BACB27B52@code1.codespeak.net> Author: hpk Date: Wed Apr 13 17:52:36 2005 New Revision: 10583 Modified: py/branch/py-collect/execnet/channel.py py/branch/py-collect/execnet/gateway.py py/branch/py-collect/execnet/testing/test_gateway.py Log: intermediate approach to redirecting stdout via a separate remote_redirect() method ... (soon i'll try another aproach incorporating redirection to remote_exec directly) Modified: py/branch/py-collect/execnet/channel.py ============================================================================== --- py/branch/py-collect/execnet/channel.py (original) +++ py/branch/py-collect/execnet/channel.py Wed Apr 13 17:52:36 2005 @@ -176,12 +176,3 @@ state = self.channel.isclosed() and 'closed' or 'open' return '' %(self.channel.id, state) -def receive2file(channel, f): - while 1: - try: - out = channel.receive() - except EOFError: - break - f.write(out) - f.flush() - Modified: py/branch/py-collect/execnet/gateway.py ============================================================================== --- py/branch/py-collect/execnet/gateway.py (original) +++ py/branch/py-collect/execnet/gateway.py Wed Apr 13 17:52:36 2005 @@ -5,6 +5,7 @@ import traceback import atexit + # XXX the following line should not be here g = globals() if 'Message' not in g: @@ -229,6 +230,53 @@ self._outgoing.put(Message.CHANNEL_OPEN(channel.id, source)) return channel + def remote_redirect(self, stdout): + """ return a handle representing a redirection of of remote + end's stdout to a local file object. with handle.close() + the redirection will be reverted. + """ + handle = RedirectHandle(self, stdout) + handle.open() + return handle + +class RedirectHandle(object): + def __init__(self, gateway, stdout): + self.gateway = gateway + self.stdout = stdout + + def open(self): + channel = self.gateway.remote_exec(""" + import sys + out = channel.newchannel() + channel.send(out) + sys.__dict__.setdefault('_stdoutsubst', []).append(sys.stdout) + sys.stdout = out.open('w') + """) + self.outchannel = channel.receive() + self.thread = threading.Thread(target=self.receive2file) + self.thread.start() + + def __del__(self): + self.close() + + def close(self, timeout=1.0): + self.gateway.remote_exec(""" + import sys + channel = sys.stdout.channel + sys.stdout = sys._stdoutsubst.pop() + channel.close() + """) + self.outchannel.waitclose(timeout=1.0) + + def receive2file(self): + while 1: + try: + out = self.outchannel.receive() + except EOFError: + break + self.stdout.write(out) + self.stdout.flush() + def getid(gw, cache={}): name = gw.__class__.__name__ try: Modified: py/branch/py-collect/execnet/testing/test_gateway.py ============================================================================== --- py/branch/py-collect/execnet/testing/test_gateway.py (original) +++ py/branch/py-collect/execnet/testing/test_gateway.py Wed Apr 13 17:52:36 2005 @@ -129,6 +129,16 @@ ''' % (channel.id)) newchan.waitclose(0.3) + def test_remote_redirect_stdout(self): + out = py.std.StringIO.StringIO() + handle = self.gw.remote_redirect(stdout=out) + try: + self.gw.remote_exec("print 42") + finally: + handle.close() + s = out.getvalue() + assert s.strip() == "42" + class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): #disabled = True def test_many_popen(self): From arigo at codespeak.net Wed Apr 13 18:33:38 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Wed, 13 Apr 2005 18:33:38 +0200 (CEST) Subject: [py-svn] r10584 - py/dist/py/c-extension/greenlet Message-ID: <20050413163338.57BB627B4C@code1.codespeak.net> Author: arigo Date: Wed Apr 13 18:33:38 2005 New Revision: 10584 Modified: py/dist/py/c-extension/greenlet/greenlet.c py/dist/py/c-extension/greenlet/greenlet.h Log: Bugfix. This makes greenlets work on OS X, apparently. The problem was that a local variable 'int recursion_depth' was used to save state across a switch, and it didn't work on OS X + gcc. Moved this saved state into the PyGreenlet structure. Modified: py/dist/py/c-extension/greenlet/greenlet.c ============================================================================== --- py/dist/py/c-extension/greenlet/greenlet.c (original) +++ py/dist/py/c-extension/greenlet/greenlet.c Wed Apr 13 18:33:38 2005 @@ -259,10 +259,10 @@ - ts_passaround: NULL if PyErr_Occurred(), else a tuple of args sent to ts_target (holds a reference) */ - int err, recursion_depth; + int err; { /* save state */ PyThreadState* tstate = PyThreadState_GET(); - recursion_depth = tstate->recursion_depth; + ts_current->recursion_depth = tstate->recursion_depth; ts_current->top_frame = tstate->frame; } ts_origin = ts_current; @@ -273,7 +273,7 @@ } else { PyThreadState* tstate = PyThreadState_GET(); - tstate->recursion_depth = recursion_depth; + tstate->recursion_depth = ts_target->recursion_depth; tstate->frame = ts_target->top_frame; ts_target->top_frame = NULL; ts_current = ts_target; @@ -346,6 +346,8 @@ ts_target->stack_start = NULL; ts_target->stack_stop = (char*) mark; ts_target->stack_prev = ts_current; + ts_target->top_frame = NULL; + ts_target->recursion_depth = PyThreadState_GET()->recursion_depth; err = _PyGreen_switchstack(); /* returns twice! The 1st time with err=1: we are in the new greenlet Modified: py/dist/py/c-extension/greenlet/greenlet.h ============================================================================== --- py/dist/py/c-extension/greenlet/greenlet.h (original) +++ py/dist/py/c-extension/greenlet/greenlet.h Wed Apr 13 18:33:38 2005 @@ -19,6 +19,7 @@ struct _greenlet* parent; PyObject* run_info; struct _frame* top_frame; + int recursion_depth; } PyGreenlet; extern PyTypeObject PyGreen_Type; From hpk at codespeak.net Wed Apr 13 23:14:46 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 13 Apr 2005 23:14:46 +0200 (CEST) Subject: [py-svn] r10598 - in py/branch/py-collect/execnet: . testing Message-ID: <20050413211446.36CE027B57@code1.codespeak.net> Author: hpk Date: Wed Apr 13 23:14:46 2005 New Revision: 10598 Modified: py/branch/py-collect/execnet/channel.py py/branch/py-collect/execnet/gateway.py py/branch/py-collect/execnet/message.py py/branch/py-collect/execnet/testing/test_gateway.py Log: allow to set a callback function for receiving items on a channel. This way you don't have to loop on channel.receive(), possibly in some separate thread. Modified: py/branch/py-collect/execnet/channel.py ============================================================================== --- py/branch/py-collect/execnet/channel.py (original) +++ py/branch/py-collect/execnet/channel.py Wed Apr 13 23:14:46 2005 @@ -5,6 +5,7 @@ class Channel(object): """Communication channel between two possibly remote threads of code. """ + _callback = None def __init__(self, gateway, id): assert isinstance(id, int) self.gateway = gateway @@ -13,6 +14,14 @@ self._closeevent = threading.Event() #self._depchannel = [] + def setcallback(self, callback): + """ set this channel to invoke the given callback + for each received data item. Note that you + cannot rely on the callback to run in + any particular thread. + """ + self._callback = callback + def __repr__(self): flag = self.isclosed() and "closed" or "open" return "" % (self.id, flag) @@ -55,6 +64,12 @@ #self._depchannel.append(newchannel) self._items.put(newchannel) + def _receivedata(self, data): + if self._callback is not None: + self.gateway._dispatchcallback(self._callback, data) + else: + self._items.put(data) + def waitclose(self, timeout): """ wait until this channel is closed. Note that a closed channel may still hold items that can be received or @@ -86,6 +101,8 @@ reraised as gateway.RemoteError exceptions containing a textual representation of the remote traceback. """ + if self._callback: + raise IOError("calling receive() on channel with callback") x = self._items.get() if isinstance(x, EOFError): self._items.put(x) Modified: py/branch/py-collect/execnet/gateway.py ============================================================================== --- py/branch/py-collect/execnet/gateway.py (original) +++ py/branch/py-collect/execnet/gateway.py Wed Apr 13 23:14:46 2005 @@ -16,7 +16,7 @@ assert Message and ChannelFactory, "Import/Configuration Error" import os -debug = 0 # or open('/tmp/execnet-debug-%d' % os.getpid() , 'wa') +debug = 0 # open('/tmp/execnet-debug-%d' % os.getpid() , 'wa') sysex = (KeyboardInterrupt, SystemExit) @@ -208,12 +208,18 @@ finally: self.trace('leaving %r' % threading.currentThread()) + def _dispatchcallback(self, callback, data): + # XXX this should run in a separate thread because + # we might otherwise block the receiver thread + # where we get called from + callback(data) + # _____________________________________________________________________ # # High Level Interface # _____________________________________________________________________ - def remote_exec(self, source): + def remote_exec(self, source, callback=None): """ return channel object for communicating with the asynchronously executing 'source' code which will have a corresponding 'channel' object in its executing namespace. @@ -227,6 +233,7 @@ except ImportError: pass channel = self.channelfactory.new() + channel.setcallback(callback) self._outgoing.put(Message.CHANNEL_OPEN(channel.id, source)) return channel Modified: py/branch/py-collect/execnet/message.py ============================================================================== --- py/branch/py-collect/execnet/message.py (original) +++ py/branch/py-collect/execnet/message.py Wed Apr 13 23:14:46 2005 @@ -90,6 +90,7 @@ class CHANNEL_OPEN(Message): def received(self, gateway): channel = gateway.channelfactory.new(self.channelid) + #gateway._scheduleexec((channel, self.data)) gateway._execqueue.put((channel, self.data)) class CHANNEL_NEW(Message): @@ -101,7 +102,7 @@ class CHANNEL_DATA(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] - channel._items.put(self.data) + channel._receivedata(self.data) class CHANNEL_CLOSE(Message): def received(self, gateway): Modified: py/branch/py-collect/execnet/testing/test_gateway.py ============================================================================== --- py/branch/py-collect/execnet/testing/test_gateway.py (original) +++ py/branch/py-collect/execnet/testing/test_gateway.py Wed Apr 13 23:14:46 2005 @@ -18,7 +18,7 @@ assert isinstance(repr(msg), str) # == "" %(msg.__class__.__name__, ) -class TestChannel: +class TestPureChannel: def setup_method(self, method): self.fac = gateway.ChannelFactory(None) @@ -49,6 +49,7 @@ channel = self.fac.new() py.test.raises(IOError, channel.waitclose, timeout=0.01) + class PopenGatewayTestSetup: def setup_class(cls): cls.gw = py.execnet.PopenGateway() @@ -129,6 +130,39 @@ ''' % (channel.id)) newchan.waitclose(0.3) + def test_channel_receiver_callback(self): + l = [] + channel = self.gw.remote_exec(''' + channel.send(42) + channel.send(13) + ''', callback=l.append) + channel.waitclose(1.0) + assert l == [42,13] + + def test_channel_receiver_callback_callback_then_not(self): + l = [] + channel = self.gw.remote_exec(''' + channel.receive() + channel.send(42) + channel.send(None) + channel.send(13) + ''') + + def receive(data): + if data is not None: + l.append(data) + else: + channel.setcallback(None) + + channel.setcallback(receive) + py.test.raises(IOError, "channel.receive()") + channel.send(1) # start signal + channel.waitclose(1.0) + assert l == [42] + value = channel.receive() + assert value == 13 + assert l == [42] + def test_remote_redirect_stdout(self): out = py.std.StringIO.StringIO() handle = self.gw.remote_redirect(stdout=out) From hpk at codespeak.net Thu Apr 14 12:33:11 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Apr 2005 12:33:11 +0200 (CEST) Subject: [py-svn] r10607 - in py/branch/py-collect: . execnet execnet/testing Message-ID: <20050414103311.E4F2627BB3@code1.codespeak.net> Author: hpk Date: Thu Apr 14 12:33:11 2005 New Revision: 10607 Added: py/branch/py-collect/execnet/testing/test_threadpool.py Removed: py/branch/py-collect/execnet/testing/sshtesting.py Modified: py/branch/py-collect/conftest.py py/branch/py-collect/execnet/gateway.py py/branch/py-collect/execnet/message.py py/branch/py-collect/execnet/testing/test_gateway.py Log: - first implementation of: allow arbitrary many worker threads (some more unification regarding thread handling is outstanding) - made a py lib specific test option "-S" to allow to set ssh targets. If given, additional tests are run with respect to SshGateways. Modified: py/branch/py-collect/conftest.py ============================================================================== --- py/branch/py-collect/conftest.py (original) +++ py/branch/py-collect/conftest.py Thu Apr 14 12:33:11 2005 @@ -15,3 +15,12 @@ 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"), + ) Modified: py/branch/py-collect/execnet/gateway.py ============================================================================== --- py/branch/py-collect/execnet/gateway.py (original) +++ py/branch/py-collect/execnet/gateway.py Thu Apr 14 12:33:11 2005 @@ -4,6 +4,7 @@ import Queue import traceback import atexit +import time # XXX the following line should not be here @@ -32,6 +33,78 @@ def __repr__(self): return "%s: %s" %(self.__class__.__name__, self.formatted) +class WorkerThread(threading.Thread): + + def __init__(self, pool): + super(WorkerThread, self).__init__() + self._queue = Queue.Queue() + self._pool = pool + self.setDaemon(1) + + def run(self): + while 1: + task = self._queue.get() + assert self not in self._pool._ready + if task is None: + break + try: + func, args, kwargs = task + func(*args, **kwargs) + except: + import traceback + traceback.print_exc() + self._pool._ready.append(self) + + def handle(self, task): + self._queue.put(task) + + def stop(self): + self._queue.put(None) + +class WorkerPool(object): + _shutdown = False + def __init__(self, maxthreads=None): + self.maxthreads = maxthreads + self._numthreads = 0 + self._ready = [] + + def dispatch(self, func, *args, **kwargs): + if self._shutdown: + raise IOError("WorkerPool is already shutting down") + task = (func, args, kwargs) + try: + thread = self._ready.pop() + except IndexError: # pop from empty list + thread = self._newthread() + thread.handle(task) + + def __del__(self): + self.shutdown() + + def shutdown(self, timeout=1.0): + if not self._shutdown: + self._shutdown = True + now = time.time() + while self._numthreads: + try: + thread = self._ready.pop() + except IndexError: + if now + timeout < time.time(): + raise IOError("Timeout: could not shut down WorkerPool") + time.sleep(0.1) + else: + self._numthreads -= 1 + + def _newthread(self): + if self.maxthreads: + if self._numthreads >= self.maxthreads: + raise IOError("cannot create more threads, " + "maxthreads=%d" % (self.maxthreads,)) + thread = WorkerThread(self) + self._numthreads += 1 + thread.start() + return thread + class NamedThreadPool: def __init__(self, **kw): self._namedthreads = {} @@ -74,18 +147,15 @@ num_worker_threads = 2 RemoteError = RemoteError - def __init__(self, io, startcount=2): + def __init__(self, io, startcount=2, maxthreads=None): + self._execpool = WorkerPool() self.running = True self.io = io - self._execqueue = Queue.Queue() self._outgoing = Queue.Queue() self.channelfactory = ChannelFactory(self, startcount) self._exitlock = threading.Lock() self.pool = NamedThreadPool(receiver = self.thread_receiver, sender = self.thread_sender) - for x in range(self.num_worker_threads): - self.pool.start('executor', self.thread_executor) - self.trace("started executor thread") if not _gateways: atexit.register(cleanup_atexit) _gateways.append(self) @@ -99,9 +169,7 @@ def _stopexec(self): #self.pool.prunestopped() - for x in range(self.num_worker_threads): - self.trace("putting None to execqueue") - self._execqueue.put(None) + self._execpool.shutdown() def exit(self): # note that threads may still be scheduled to start @@ -178,35 +246,30 @@ finally: self.trace('leaving %r' % threading.currentThread()) - def thread_executor(self): + def thread_executor(self, channel, source): """ worker thread to execute source objects from the execution queue. """ try: - while 1: - task = self._execqueue.get() - if task is None: - self.trace("executor found none, leaving ...") - break - channel, source = task - try: - loc = { 'channel' : channel } - self.trace("execution starts:", repr(source)[:50]) - try: - co = compile(source+'\n', '', 'exec', 4096) - exec co in loc - finally: - self.trace("execution finished:", repr(source)[:50]) - except (KeyboardInterrupt, SystemExit): - raise - except: - excinfo = sys.exc_info() - l = traceback.format_exception(*excinfo) - errortext = "".join(l) - channel.close(errortext) - self.trace(errortext) - else: - channel.close() - finally: - self.trace('leaving %r' % threading.currentThread()) + loc = { 'channel' : channel } + self.trace("execution starts:", repr(source)[:50]) + try: + co = compile(source+'\n', '', 'exec', 4096) + exec co in loc + finally: + self.trace("execution finished:", repr(source)[:50]) + except (KeyboardInterrupt, SystemExit): + raise + except: + excinfo = sys.exc_info() + l = traceback.format_exception(*excinfo) + errortext = "".join(l) + channel.close(errortext) + self.trace(errortext) + else: + channel.close() + + def _scheduleexec(self, channel, source): + self.trace("dispatching exec") + self._execpool.dispatch(self.thread_executor, channel, source) def _dispatchcallback(self, callback, data): # XXX this should run in a separate thread because Modified: py/branch/py-collect/execnet/message.py ============================================================================== --- py/branch/py-collect/execnet/message.py (original) +++ py/branch/py-collect/execnet/message.py Thu Apr 14 12:33:11 2005 @@ -90,8 +90,7 @@ class CHANNEL_OPEN(Message): def received(self, gateway): channel = gateway.channelfactory.new(self.channelid) - #gateway._scheduleexec((channel, self.data)) - gateway._execqueue.put((channel, self.data)) + gateway._scheduleexec(channel, self.data) class CHANNEL_NEW(Message): def received(self, gateway): Deleted: /py/branch/py-collect/execnet/testing/sshtesting.py ============================================================================== --- /py/branch/py-collect/execnet/testing/sshtesting.py Thu Apr 14 12:33:11 2005 +++ (empty file) @@ -1,16 +0,0 @@ -""" -A test file that doesn't run by default to test SshGateway. -""" - -import py - -#REMOTE_HOST = 'codespeak.net' -#REMOTE_HOSTNAME = 'thoth.codespeak.net' - -def test_sshgateway(): - REMOTE_HOST = 'localhost' # you need to have a local ssh-daemon running! - REMOTE_HOSTNAME = py.std.socket.gethostname() # the remote's socket.gethostname() - gw = py.execnet.SshGateway(REMOTE_HOST) - c = gw.remote_exec('import socket; channel.send(socket.gethostname())') - msg = c.receive() - assert msg == REMOTE_HOSTNAME Modified: py/branch/py-collect/execnet/testing/test_gateway.py ============================================================================== --- py/branch/py-collect/execnet/testing/test_gateway.py (original) +++ py/branch/py-collect/execnet/testing/test_gateway.py Thu Apr 14 12:33:11 2005 @@ -1,6 +1,7 @@ import os, sys import py from py.__impl__.execnet import gateway +from py.__impl__.conftest import option mypath = py.magic.autopath() from StringIO import StringIO @@ -59,7 +60,7 @@ class BasicRemoteExecution: def test_correct_setup(self): - for x in 'sender', 'receiver', 'executor': + for x in 'sender', 'receiver': # , 'executor': assert self.gw.pool.getstarted(x) def test_remote_exec_waitclose(self): @@ -209,3 +210,10 @@ class TestSocketGateway(SocketGatewaySetup, BasicRemoteExecution): disabled = sys.platform == "win32" pass + +class TestSshGateway(BasicRemoteExecution): + def setup_class(cls): + if option.sshtarget is None: + py.test.skip("no known ssh target, use -S to set one") + cls.gw = py.execnet.SshGateway(option.sshtarget) + Added: py/branch/py-collect/execnet/testing/test_threadpool.py ============================================================================== --- (empty file) +++ py/branch/py-collect/execnet/testing/test_threadpool.py Thu Apr 14 12:33:11 2005 @@ -0,0 +1,33 @@ + +from py.__impl__.execnet.gateway import WorkerPool +import py + +def test_some(): + pool = WorkerPool() + l = [] + try: + pool.dispatch(l.append, 1) + pool.dispatch(l.append, 2) + pool.dispatch(l.append, 3) + pool.dispatch(l.append, 4) + finally: + pool.shutdown() + assert len(pool._ready) == pool._numthreads + assert len(l) == 4 + +def test_maxthreads(): + pool = WorkerPool(maxthreads=1) + def f(): + py.std.time.sleep(0.5) + try: + pool.dispatch(f) + py.test.raises(IOError, pool.dispatch, f) + finally: + pool.shutdown() + +def test_shutdown_timeout(): + pool = WorkerPool() + def f(): + py.std.time.sleep(1.5) + pool.dispatch(f) + py.test.raises(IOError, pool.shutdown, 0.2) From hpk at codespeak.net Thu Apr 14 13:13:55 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Apr 2005 13:13:55 +0200 (CEST) Subject: [py-svn] r10611 - in py/branch/py-collect/execnet: . testing Message-ID: <20050414111355.3361F27B9A@code1.codespeak.net> Author: hpk Date: Thu Apr 14 13:13:55 2005 New Revision: 10611 Modified: py/branch/py-collect/execnet/channel.py py/branch/py-collect/execnet/gateway.py py/branch/py-collect/execnet/testing/test_gateway.py Log: simplify and flexibilize the basic execnet API. you can now do: channel = gateway.newchannel() channel.remote_exec("...") in which case the channel will be used as the local connection point to the remotely executing source. You can now switch channel modes by providing a callback: l = [] channel = gateway.newchannel() channel.setcallback(l.append) channel.remote_exec("channel.send(42)") where 'l' will end up having the value 42. the gateway.remote_exec() is now merely a shortcut for creating a channel and calling it's remote_exec and returning the channel. Modified: py/branch/py-collect/execnet/channel.py ============================================================================== --- py/branch/py-collect/execnet/channel.py (original) +++ py/branch/py-collect/execnet/channel.py Thu Apr 14 13:13:55 2005 @@ -43,6 +43,9 @@ put(Message.CHANNEL_CLOSE(self.id)) self._close() + def remote_exec(self, source): + self.gateway._remote_exec(self, source) + def _close(self, finalitem=EOFError()): if self.id in self.gateway.channelfactory: del self.gateway.channelfactory[self.id] Modified: py/branch/py-collect/execnet/gateway.py ============================================================================== --- py/branch/py-collect/execnet/gateway.py (original) +++ py/branch/py-collect/execnet/gateway.py Thu Apr 14 13:13:55 2005 @@ -277,16 +277,7 @@ # where we get called from callback(data) - # _____________________________________________________________________ - # - # High Level Interface - # _____________________________________________________________________ - - def remote_exec(self, source, callback=None): - """ return channel object for communicating with the asynchronously - executing 'source' code which will have a corresponding 'channel' - object in its executing namespace. - """ + def _remote_exec(self, channel, source): try: source = str(Source(source)) except NameError: @@ -295,10 +286,28 @@ source = str(py.code.Source(source)) except ImportError: pass - channel = self.channelfactory.new() - channel.setcallback(callback) self._outgoing.put(Message.CHANNEL_OPEN(channel.id, source)) - return channel + + # _____________________________________________________________________ + # + # High Level Interface + # _____________________________________________________________________ + # + def newchannel(self): + """ return new channel object. """ + return self.channelfactory.new() + + def remote_exec(self, source): + """ 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. If a channel is provided + is will be returned as well. + """ + channel = self.newchannel() + channel.remote_exec(source) + return channel + def remote_redirect(self, stdout): """ return a handle representing a redirection of of remote @@ -330,6 +339,9 @@ self.close() def close(self, timeout=1.0): + """ close redirection on remote side and wait + for closing. + """ self.gateway.remote_exec(""" import sys channel = sys.stdout.channel Modified: py/branch/py-collect/execnet/testing/test_gateway.py ============================================================================== --- py/branch/py-collect/execnet/testing/test_gateway.py (original) +++ py/branch/py-collect/execnet/testing/test_gateway.py Thu Apr 14 13:13:55 2005 @@ -132,12 +132,14 @@ newchan.waitclose(0.3) def test_channel_receiver_callback(self): + channel = self.gw.newchannel() l = [] - channel = self.gw.remote_exec(''' + channel.setcallback(l.append) + channel.remote_exec(''' channel.send(42) channel.send(13) - ''', callback=l.append) - channel.waitclose(1.0) + ''') + channel.waitclose(1.0) assert l == [42,13] def test_channel_receiver_callback_callback_then_not(self): From hpk at codespeak.net Thu Apr 14 13:35:04 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Apr 2005 13:35:04 +0200 (CEST) Subject: [py-svn] r10612 - in py/branch/py-collect/execnet: . testing Message-ID: <20050414113504.DC1FB27B9E@code1.codespeak.net> Author: hpk Date: Thu Apr 14 13:35:04 2005 New Revision: 10612 Modified: py/branch/py-collect/execnet/gateway.py py/branch/py-collect/execnet/testing/test_gateway.py Log: fixed and simplified remote_redirect() implementation (but it's still not semantically nicely defined) Modified: py/branch/py-collect/execnet/gateway.py ============================================================================== --- py/branch/py-collect/execnet/gateway.py (original) +++ py/branch/py-collect/execnet/gateway.py Thu Apr 14 13:35:04 2005 @@ -324,16 +324,16 @@ self.stdout = stdout def open(self): - channel = self.gateway.remote_exec(""" + self.outchannel = self.gateway.newchannel() + self.outchannel.setcallback(self.stdout.write) + channel = self.gateway.remote_exec(""" import sys - out = channel.newchannel() - channel.send(out) + outchannel = channel.receive() sys.__dict__.setdefault('_stdoutsubst', []).append(sys.stdout) - sys.stdout = out.open('w') + sys.stdout = outchannel.open('w') """) - self.outchannel = channel.receive() - self.thread = threading.Thread(target=self.receive2file) - self.thread.start() + channel.send(self.outchannel) + channel.waitclose(1.0) def __del__(self): self.close() @@ -342,22 +342,16 @@ """ close redirection on remote side and wait for closing. """ - self.gateway.remote_exec(""" + c = self.gateway.remote_exec(""" import sys - channel = sys.stdout.channel + outchannel = sys.stdout.channel sys.stdout = sys._stdoutsubst.pop() - channel.close() + outchannel.close() """) self.outchannel.waitclose(timeout=1.0) - - def receive2file(self): - while 1: - try: - out = self.outchannel.receive() - except EOFError: - break - self.stdout.write(out) - self.stdout.flush() + c.waitclose(1.0) + #print "outchannel", self.outchannel + #print "channel", c def getid(gw, cache={}): name = gw.__class__.__name__ Modified: py/branch/py-collect/execnet/testing/test_gateway.py ============================================================================== --- py/branch/py-collect/execnet/testing/test_gateway.py (original) +++ py/branch/py-collect/execnet/testing/test_gateway.py Thu Apr 14 13:35:04 2005 @@ -170,9 +170,10 @@ out = py.std.StringIO.StringIO() handle = self.gw.remote_redirect(stdout=out) try: - self.gw.remote_exec("print 42") + c = self.gw.remote_exec("print 42") finally: handle.close() + c.waitclose(1.0) s = out.getvalue() assert s.strip() == "42" From hpk at codespeak.net Thu Apr 14 13:39:45 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Apr 2005 13:39:45 +0200 (CEST) Subject: [py-svn] r10613 - py/branch/py-collect/execnet Message-ID: <20050414113945.2598C27B9E@code1.codespeak.net> Author: hpk Date: Thu Apr 14 13:39:44 2005 New Revision: 10613 Modified: py/branch/py-collect/execnet/gateway.py Log: allow remote_direct()s handle.close() to not wait for remote execution to finish. Modified: py/branch/py-collect/execnet/gateway.py ============================================================================== --- py/branch/py-collect/execnet/gateway.py (original) +++ py/branch/py-collect/execnet/gateway.py Thu Apr 14 13:39:44 2005 @@ -340,7 +340,9 @@ def close(self, timeout=1.0): """ close redirection on remote side and wait - for closing. + for closing. If timeout==0 we will not + wait for the remote side to finish + resetting the redirection pipe. """ c = self.gateway.remote_exec(""" import sys @@ -348,10 +350,9 @@ sys.stdout = sys._stdoutsubst.pop() outchannel.close() """) - self.outchannel.waitclose(timeout=1.0) - c.waitclose(1.0) - #print "outchannel", self.outchannel - #print "channel", c + if timeout != 0: + self.outchannel.waitclose(timeout=1.0) + c.waitclose(1.0) def getid(gw, cache={}): name = gw.__class__.__name__ From hpk at codespeak.net Thu Apr 14 18:06:05 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Apr 2005 18:06:05 +0200 (CEST) Subject: [py-svn] r10625 - in py/branch/py-collect/execnet/testing: . test Message-ID: <20050414160605.6A31E27B9A@code1.codespeak.net> Author: hpk Date: Thu Apr 14 18:06:05 2005 New Revision: 10625 Added: py/branch/py-collect/execnet/testing/test_pickle.py - copied unchanged from r10563, py/branch/py-collect/execnet/testing/test/test_pickle.py Removed: py/branch/py-collect/execnet/testing/test/ Log: cleanup From hpk at codespeak.net Thu Apr 14 19:12:33 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 14 Apr 2005 19:12:33 +0200 (CEST) Subject: [py-svn] r10632 - in py/branch/py-collect/execnet: . testing Message-ID: <20050414171233.3EAEE27B9C@code1.codespeak.net> Author: hpk Date: Thu Apr 14 19:12:33 2005 New Revision: 10632 Modified: py/branch/py-collect/execnet/gateway.py py/branch/py-collect/execnet/testing/test_threadpool.py Log: cleanup of startup/shutdown for workerthreads Modified: py/branch/py-collect/execnet/gateway.py ============================================================================== --- py/branch/py-collect/execnet/gateway.py (original) +++ py/branch/py-collect/execnet/gateway.py Thu Apr 14 19:12:33 2005 @@ -42,18 +42,23 @@ self.setDaemon(1) def run(self): - while 1: - task = self._queue.get() - assert self not in self._pool._ready - if task is None: - break - try: - func, args, kwargs = task - func(*args, **kwargs) - except: - import traceback - traceback.print_exc() - self._pool._ready.append(self) + try: + while 1: + task = self._queue.get() + assert self not in self._pool._ready + if task is None: + break + try: + func, args, kwargs = task + func(*args, **kwargs) + except (SystemExit, KeyboardInterrupt): + break + except: + import traceback + traceback.print_exc() + self._pool._ready.append(self) + finally: + del self._pool._alive[self] def handle(self, task): self._queue.put(task) @@ -67,6 +72,7 @@ self.maxthreads = maxthreads self._numthreads = 0 self._ready = [] + self._alive = {} def dispatch(self, func, *args, **kwargs): if self._shutdown: @@ -85,7 +91,7 @@ if not self._shutdown: self._shutdown = True now = time.time() - while self._numthreads: + while self._alive: try: thread = self._ready.pop() except IndexError: @@ -93,18 +99,24 @@ raise IOError("Timeout: could not shut down WorkerPool") time.sleep(0.1) else: - self._numthreads -= 1 + thread.stop() def _newthread(self): if self.maxthreads: - if self._numthreads >= self.maxthreads: + if len(self._alive) >= self.maxthreads: raise IOError("cannot create more threads, " "maxthreads=%d" % (self.maxthreads,)) thread = WorkerThread(self) - self._numthreads += 1 thread.start() + self._alive[thread] = True return thread + def join(self): + current = threading.currentThread() + for x in self._alive.keys(): + if current != x: + x.join() + class NamedThreadPool: def __init__(self, **kw): self._namedthreads = {} @@ -161,10 +173,10 @@ _gateways.append(self) def __repr__(self): - R = len(self.pool.getstarted('receiver')) - S = len(self.pool.getstarted('sender')) + 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.values()) - return "<%s %d/%d (%d active channels)>" %(self.__class__.__name__, + return "<%s %s/%s (%d active channels)>" %(self.__class__.__name__, R, S, i) def _stopexec(self): @@ -193,6 +205,7 @@ if x != current: self.trace("joining %s" % x) x.join() + self._execpool.join() self.trace("joining threads finished, current %r" % current) def trace(self, *args): Modified: py/branch/py-collect/execnet/testing/test_threadpool.py ============================================================================== --- py/branch/py-collect/execnet/testing/test_threadpool.py (original) +++ py/branch/py-collect/execnet/testing/test_threadpool.py Thu Apr 14 19:12:33 2005 @@ -4,16 +4,17 @@ def test_some(): pool = WorkerPool() - l = [] - try: - pool.dispatch(l.append, 1) - pool.dispatch(l.append, 2) - pool.dispatch(l.append, 3) - pool.dispatch(l.append, 4) - finally: - pool.shutdown() - assert len(pool._ready) == pool._numthreads - assert len(l) == 4 + q = py.std.Queue.Queue() + num = 4 + for i in range(num): + pool.dispatch(q.put, i) + for i in range(num): + q.get() + assert len(pool._alive) == 4 + assert len(pool._ready) == 4 + pool.shutdown() + assert len(pool._alive) == 0 + assert len(pool._ready) == 0 def test_maxthreads(): pool = WorkerPool(maxthreads=1) @@ -28,6 +29,16 @@ def test_shutdown_timeout(): pool = WorkerPool() def f(): - py.std.time.sleep(1.5) + py.std.time.sleep(0.5) pool.dispatch(f) py.test.raises(IOError, pool.shutdown, 0.2) + +def test_pool_clean_shutdown(): + pool = WorkerPool() + def f(): + pass + pool.dispatch(f) + pool.dispatch(f) + pool.shutdown() + assert not pool._alive + assert not pool._ready From hpk at codespeak.net Fri Apr 15 00:00:19 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 00:00:19 +0200 (CEST) Subject: [py-svn] r10645 - py/dist/py/xmlobj Message-ID: <20050414220019.8018C27B8D@code1.codespeak.net> Author: hpk Date: Fri Apr 15 00:00:19 2005 New Revision: 10645 Modified: py/dist/py/xmlobj/html.py Log: added some missing tags, suggest by William McVey Modified: py/dist/py/xmlobj/html.py ============================================================================== --- py/dist/py/xmlobj/html.py (original) +++ py/dist/py/xmlobj/html.py Fri Apr 15 00:00:19 2005 @@ -28,7 +28,8 @@ "html,head,title,style,table,tr,tt," "td,th,link,img,meta,body,pre,br,ul," "ol,li,em,form,input,select,option," - "button,script" + "button,script,colgroup,col,map,area," + "blockquote,dl,dt,dd,strong" ).split(',') if x]) class Style(object): From hpk at codespeak.net Fri Apr 15 00:07:26 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 00:07:26 +0200 (CEST) Subject: [py-svn] r10646 - in py/dist/py/xmlobj: . testing Message-ID: <20050414220726.7CA1B27B95@code1.codespeak.net> Author: hpk Date: Fri Apr 15 00:07:26 2005 New Revision: 10646 Modified: py/dist/py/xmlobj/html.py py/dist/py/xmlobj/testing/test_html.py py/dist/py/xmlobj/visit.py Log: make html generate full empty tags aka Modified: py/dist/py/xmlobj/html.py ============================================================================== --- py/dist/py/xmlobj/html.py (original) +++ py/dist/py/xmlobj/html.py Fri Apr 15 00:07:26 2005 @@ -16,7 +16,7 @@ class HtmlTag(Tag): def unicode(self, indent=2): l = [] - HtmlVisitor(l.append, indent).visit(self) + HtmlVisitor(l.append, indent, shortempty=False).visit(self) return u"".join(l) # exported plain html namespace Modified: py/dist/py/xmlobj/testing/test_html.py ============================================================================== --- py/dist/py/xmlobj/testing/test_html.py (original) +++ py/dist/py/xmlobj/testing/test_html.py Fri Apr 15 00:07:26 2005 @@ -11,12 +11,12 @@ class body(html.body): style = html.Style(font_size = "12pt") u = unicode(my.body()) - assert u == '' + assert u == '' def test_class_None(): t = html.body(class_=None) u = unicode(t) - assert u == '' + assert u == '' def test_alternating_style(): alternating = ( Modified: py/dist/py/xmlobj/visit.py ============================================================================== --- py/dist/py/xmlobj/visit.py (original) +++ py/dist/py/xmlobj/visit.py Fri Apr 15 00:07:26 2005 @@ -6,13 +6,14 @@ class SimpleUnicodeVisitor(object): """ recursive visitor to write unicode. """ - def __init__(self, write, indent=0, curindent=0): + def __init__(self, write, indent=0, curindent=0, shortempty=True): self.write = write self.cache = {} self.visited = {} # for detection of recursion self.indent = indent self.curindent = curindent self.parents = [] + self.shortempty = shortempty # short empty tags or not def visit(self, node): """ dispatcher on node's class/bases name. """ @@ -57,7 +58,11 @@ self.write(u'' % tagname) self.curindent -= self.indent else: - self.write(u'<%s%s/>' % (tagname, self.attributes(tag))) + nameattr = tagname+self.attributes(tag) + if self.shortempty: + self.write(u'<%s/>' % (nameattr,)) + else: + self.write(u'<%s>' % (nameattr, tagname)) def attributes(self, tag): # serialize attributes From hpk at codespeak.net Fri Apr 15 02:00:53 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 02:00:53 +0200 (CEST) Subject: [py-svn] r10656 - in py/branch/py-collect/execnet: . testing Message-ID: <20050415000053.4C52327B8D@code1.codespeak.net> Author: hpk Date: Fri Apr 15 02:00:53 2005 New Revision: 10656 Modified: py/branch/py-collect/execnet/channel.py py/branch/py-collect/execnet/gateway.py py/branch/py-collect/execnet/testing/test_gateway.py py/branch/py-collect/execnet/testing/test_threadpool.py Log: - add stdout/stderr redirection per remote_exec() introduce a ThreadOut object which encapsulates e.g. sys.stdout to demultiplex to different writers according to threads. - adjust remote_redirect-implementatation accordingly Modified: py/branch/py-collect/execnet/channel.py ============================================================================== --- py/branch/py-collect/execnet/channel.py (original) +++ py/branch/py-collect/execnet/channel.py Fri Apr 15 02:00:53 2005 @@ -43,8 +43,8 @@ put(Message.CHANNEL_CLOSE(self.id)) self._close() - def remote_exec(self, source): - self.gateway._remote_exec(self, source) + def remote_exec(self, source, stdout=None, stderr=None): + self.gateway._remote_exec(self, source, stdout, stderr) def _close(self, finalitem=EOFError()): if self.id in self.gateway.channelfactory: Modified: py/branch/py-collect/execnet/gateway.py ============================================================================== --- py/branch/py-collect/execnet/gateway.py (original) +++ py/branch/py-collect/execnet/gateway.py Fri Apr 15 02:00:53 2005 @@ -1,6 +1,6 @@ import sys import os -import threading +import thread, threading import Queue import traceback import atexit @@ -34,7 +34,6 @@ return "%s: %s" %(self.__class__.__name__, self.formatted) class WorkerThread(threading.Thread): - def __init__(self, pool): super(WorkerThread, self).__init__() self._queue = Queue.Queue() @@ -155,9 +154,79 @@ thread.start() l.append(thread) +class ThreadOut(object): + def __new__(cls, obj, attrname): + """ Divert file output to per-thread writefuncs. + the given obj and attrname describe the destination + of the file. + """ + current = getattr(obj, attrname) + if isinstance(current, cls): + current._used += 1 + return current + self = object.__new__(cls) + self._tid2out = {} + self._used = 1 + self._oldout = getattr(obj, attrname) + self._defaultwriter = self._oldout.write + self._address = (obj, attrname) + setattr(obj, attrname, self) + return self + + def setdefaultwriter(self, writefunc): + self._defaultwriter = writefunc + + def resetdefault(self): + self._defaultwriter = self._oldout.write + + def softspace(): + def fget(self): + return self._get()[0] + def fset(self, value): + self._get()[0] = value + return property(fget, fset, None, "software attribute") + softspace = softspace() + + def deinstall(self): + self._used -= 1 + x = self._used + if x <= 0: + obj, attrname = self._address + setattr(obj, attrname, self._oldout) + + def setwritefunc(self, writefunc, tid=None): + assert callable(writefunc) + if tid is None: + tid = thread.get_ident() + self._tid2out[tid] = [0, writefunc] + + def delwritefunc(self, tid=None, ignoremissing=True): + if tid is None: + tid = thread.get_ident() + try: + del self._tid2out[tid] + except KeyError: + if not ignoremissing: + raise + + def _get(self): + tid = thread.get_ident() + try: + return self._tid2out[tid] + except KeyError: + return getattr(self._defaultwriter, 'softspace', 0), self._defaultwriter + + def write(self, data): + softspace, out = self._get() + out(data) + + def flush(self): + pass + class Gateway(object): num_worker_threads = 2 RemoteError = RemoteError + ThreadOut = ThreadOut def __init__(self, io, startcount=2, maxthreads=None): self._execpool = WorkerPool() @@ -259,15 +328,36 @@ finally: self.trace('leaving %r' % threading.currentThread()) - def thread_executor(self, channel, source): + def _redirect_thread_output(self, outid, errid): + l = [] + for name, id in ('stdout', outid), ('stderr', errid): + if id: + channel = self._makechannel(outid) + out = ThreadOut(sys, name) + out.setwritefunc(channel.send) + l.append((out, channel)) + def close(): + for out, channel in l: + out.delwritefunc() + channel.close() + return close + + def _makechannel(self, newid): + newchannel = Channel(self, newid) + self.channelfactory[newid] = newchannel + return newchannel + + def thread_executor(self, channel, (source, outid, errid)): """ worker thread to execute source objects from the execution queue. """ try: loc = { 'channel' : channel } self.trace("execution starts:", repr(source)[:50]) + close = self._redirect_thread_output(outid, errid) try: co = compile(source+'\n', '', 'exec', 4096) exec co in loc finally: + close() self.trace("execution finished:", repr(source)[:50]) except (KeyboardInterrupt, SystemExit): raise @@ -280,9 +370,9 @@ else: channel.close() - def _scheduleexec(self, channel, source): + def _scheduleexec(self, channel, sourcetask): self.trace("dispatching exec") - self._execpool.dispatch(self.thread_executor, channel, source) + self._execpool.dispatch(self.thread_executor, channel, sourcetask) def _dispatchcallback(self, callback, data): # XXX this should run in a separate thread because @@ -290,7 +380,7 @@ # where we get called from callback(data) - def _remote_exec(self, channel, source): + def _remote_exec(self, channel, source, stdout=None, stderr=None): try: source = str(Source(source)) except NameError: @@ -299,7 +389,20 @@ source = str(py.code.Source(source)) except ImportError: pass - self._outgoing.put(Message.CHANNEL_OPEN(channel.id, source)) + outid = self._redirectchannelid(stdout) + errid = self._redirectchannelid(stderr) + self._outgoing.put(Message.CHANNEL_OPEN(channel.id, + (source, outid, errid))) + + def _redirectchannelid(self, callback): + if callback is None: + return + if hasattr(callback, 'write'): + callback = callback.write + assert callable(callback) + chan = self.newchannel() + chan.setcallback(callback) + return chan.id # _____________________________________________________________________ # @@ -310,7 +413,7 @@ """ return new channel object. """ return self.channelfactory.new() - def remote_exec(self, source): + 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 @@ -318,55 +421,39 @@ is will be returned as well. """ channel = self.newchannel() - channel.remote_exec(source) + channel.remote_exec(source, stdout=stdout, stderr=stderr) return channel - - def remote_redirect(self, stdout): + def remote_redirect(self, stdout=None, stderr=None): """ return a handle representing a redirection of of remote end's stdout to a local file object. with handle.close() the redirection will be reverted. """ - handle = RedirectHandle(self, stdout) - handle.open() - return handle - -class RedirectHandle(object): - def __init__(self, gateway, stdout): - self.gateway = gateway - self.stdout = stdout - - def open(self): - self.outchannel = self.gateway.newchannel() - self.outchannel.setcallback(self.stdout.write) - channel = self.gateway.remote_exec(""" - import sys - outchannel = channel.receive() - sys.__dict__.setdefault('_stdoutsubst', []).append(sys.stdout) - sys.stdout = outchannel.open('w') - """) - channel.send(self.outchannel) - channel.waitclose(1.0) - - def __del__(self): - self.close() + clist = [] + for name, out in ('stdout', stdout), ('stderr', stderr): + if out: + outchannel = self.newchannel() + outchannel.setcallback(getattr(out, 'write', out)) + channel = self.remote_exec(""" + import sys + outchannel = channel.receive() + outchannel.gateway.ThreadOut(sys, %r).setdefaultwriter(outchannel.send) + """ % name) + channel.send(outchannel) + clist.append(channel) + for c in clist: + c.waitclose(1.0) + class Handle: + def close(_): + for name, out in ('stdout', stdout), ('stderr', stderr): + if out: + c = self.remote_exec(""" + import sys + channel.gateway.ThreadOut(sys, %r).resetdefault() + """ % name) + c.waitclose(1.0) + return Handle() - def close(self, timeout=1.0): - """ close redirection on remote side and wait - for closing. If timeout==0 we will not - wait for the remote side to finish - resetting the redirection pipe. - """ - c = self.gateway.remote_exec(""" - import sys - outchannel = sys.stdout.channel - sys.stdout = sys._stdoutsubst.pop() - outchannel.close() - """) - if timeout != 0: - self.outchannel.waitclose(timeout=1.0) - c.waitclose(1.0) - def getid(gw, cache={}): name = gw.__class__.__name__ try: Modified: py/branch/py-collect/execnet/testing/test_gateway.py ============================================================================== --- py/branch/py-collect/execnet/testing/test_gateway.py (original) +++ py/branch/py-collect/execnet/testing/test_gateway.py Fri Apr 15 02:00:53 2005 @@ -169,14 +169,26 @@ def test_remote_redirect_stdout(self): out = py.std.StringIO.StringIO() handle = self.gw.remote_redirect(stdout=out) - try: - c = self.gw.remote_exec("print 42") - finally: - handle.close() + c = self.gw.remote_exec("print 42") c.waitclose(1.0) + handle.close() s = out.getvalue() assert s.strip() == "42" + def test_remote_exec_redirect_multi(self): + num = 3 + l = [[] for x in range(num)] + channels = [self.gw.remote_exec("print %d" % i, stdout=l[i].append) + for i in range(num)] + for x in channels: + x.waitclose(1.0) + + for i in range(num): + subl = l[i] + assert subl + s = subl[0] + assert s.strip() == str(i) + class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): #disabled = True def test_many_popen(self): Modified: py/branch/py-collect/execnet/testing/test_threadpool.py ============================================================================== --- py/branch/py-collect/execnet/testing/test_threadpool.py (original) +++ py/branch/py-collect/execnet/testing/test_threadpool.py Fri Apr 15 02:00:53 2005 @@ -1,6 +1,7 @@ -from py.__impl__.execnet.gateway import WorkerPool +from py.__impl__.execnet.gateway import WorkerPool, ThreadOut import py +import sys def test_some(): pool = WorkerPool() @@ -11,7 +12,6 @@ for i in range(num): q.get() assert len(pool._alive) == 4 - assert len(pool._ready) == 4 pool.shutdown() assert len(pool._alive) == 0 assert len(pool._ready) == 0 @@ -42,3 +42,63 @@ pool.shutdown() assert not pool._alive assert not pool._ready + +def test_threadout_install_deinstall(): + old = sys.stdout + out = ThreadOut(sys, 'stdout') + out.deinstall() + assert old == sys.stdout + +class TestThreadOut: + def setup_method(self, method): + self.out = ThreadOut(sys, 'stdout') + def teardown_method(self, method): + self.out.deinstall() + + def test_threadout_one(self): + l = [] + self.out.setwritefunc(l.append) + print 42,13, + x = l.pop(0) + assert x == '42' + x = l.pop(0) + assert x == ' ' + x = l.pop(0) + assert x == '13' + + + def test_threadout_multi_and_default(self): + num = 3 + defaults = [] + def f(l): + self.out.setwritefunc(l.append) + print id(l), + self.out.delwritefunc() + print 1 + + self.out.setdefaultwriter(defaults.append) + pool = WorkerPool() + listlist = [] + for x in range(num): + l = [] + listlist.append(l) + pool.dispatch(f, l) + pool.shutdown() + for name, value in self.out.__dict__.items(): + print >>sys.stderr, "%s: %s" %(name, value) + for i in range(num): + item = listlist[i] + assert item ==[str(id(item))] + assert not self.out._tid2out + assert defaults + expect = ['1' for x in range(num)] + defaults = [x for x in defaults if x.strip()] + assert defaults == expect + + def test_threadout_nested(self): + # we want ThreadOuts to coexist + last = sys.stdout + out = ThreadOut(sys, 'stdout') + assert last == sys.stdout + out.deinstall() + assert last == sys.stdout From hpk at codespeak.net Fri Apr 15 13:11:37 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 13:11:37 +0200 (CEST) Subject: [py-svn] r10664 - in py/branch/py-collect: . misc/testing Message-ID: <20050415111137.27AE727B7A@code1.codespeak.net> Author: hpk Date: Fri Apr 15 13:11:36 2005 New Revision: 10664 Modified: py/branch/py-collect/initpkg.py py/branch/py-collect/misc/testing/test_initpkg.py Log: have a more telling import attribute error Modified: py/branch/py-collect/initpkg.py ============================================================================== --- py/branch/py-collect/initpkg.py (original) +++ py/branch/py-collect/initpkg.py Fri Apr 15 13:11:36 2005 @@ -63,11 +63,16 @@ e = py.path.extpy(e, modpath) return e.resolve() assert fspath.startswith('./'), \ - "%r is not an implementation path (XXX)" % extpyish + "%r is not an implementation path (XXX)" % (extpyish,) implmodule = self._loadimpl(fspath[:-3]) current = implmodule for x in modpath.split('.'): - current = getattr(current, x) + try: + current = getattr(current, x) + except AttributeError: + raise AttributeError("resolving %r failed: %s" %( + extpyish, x)) + return current def getimportname(self, path): Modified: py/branch/py-collect/misc/testing/test_initpkg.py ============================================================================== --- py/branch/py-collect/misc/testing/test_initpkg.py (original) +++ py/branch/py-collect/misc/testing/test_initpkg.py Fri Apr 15 13:11:36 2005 @@ -18,6 +18,13 @@ if not name.startswith('_'): yield checksubpackage, name +def test_resolve_attrerror(): + extpyish = "./initpkg.py", "hello" + excinfo = py.test.raises(AttributeError, "py.__package__._resolve(extpyish)") + s = str(excinfo.value) + assert s.find(extpyish[0]) != -1 + assert s.find(extpyish[1]) != -1 + def test_virtual_module_identity(): from py import path as path1 from py import path as path2 From hpk at codespeak.net Fri Apr 15 13:43:30 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 13:43:30 +0200 (CEST) Subject: [py-svn] r10665 - in py/branch/py-collect: . test test/terminal test/testing Message-ID: <20050415114330.1871527B7A@code1.codespeak.net> Author: hpk Date: Fri Apr 15 13:43:29 2005 New Revision: 10665 Modified: py/branch/py-collect/conftest.py py/branch/py-collect/test/config.py py/branch/py-collect/test/defaultconftest.py py/branch/py-collect/test/terminal/out.py py/branch/py-collect/test/terminal/remote.py py/branch/py-collect/test/terminal/terminal.py py/branch/py-collect/test/testing/test_session.py Log: - make remote execution / looponfailing work again (with the new execnet redirect mechanism, works kind of nice) - internally rename 'exitfirstproblem' to 'exitfirst' Modified: py/branch/py-collect/conftest.py ============================================================================== --- py/branch/py-collect/conftest.py (original) +++ py/branch/py-collect/conftest.py Fri Apr 15 13:43:29 2005 @@ -11,7 +11,7 @@ verbose = 0 nocapture = False collectonly = False -exitfirstproblem = False +exitfirst = False fulltrace = False showlocals = False nomagic = False Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Fri Apr 15 13:43:29 2005 @@ -59,12 +59,15 @@ """ configpaths = guessconfigpaths(*getanchorpaths(args)) config = bootstrapconfig(configpaths) + config._origargs = args cmdlineoption, remaining = config._parser.parse_args(args) for name, value in vars(cmdlineoption).items(): setattr(config.option, name, value) fixoptions(config.option) if not remaining: remaining.append(py.std.os.getcwd()) + print "remaining is", remaining + config.remaining = remaining return config, remaining parse = classmethod(parse) @@ -118,7 +121,9 @@ raise ValueError, "--exec together with --pdb not supported yet." # setting a correct executable + remote = False if option.executable is not None: + remote = True exe = py.path.local(py.std.os.path.expanduser(option.executable)) if not exe.check(): exe = py.path.local.sysfind(option.executable) @@ -127,6 +132,10 @@ else: option.executable = py.std.sys.executable + # make information available about wether we should/will be remote + option._remote = remote or option.looponfailing + option._fromremote = False + # setting a correct frontend session if option.session: name = option.session Modified: py/branch/py-collect/test/defaultconftest.py ============================================================================== --- py/branch/py-collect/test/defaultconftest.py (original) +++ py/branch/py-collect/test/defaultconftest.py Fri Apr 15 13:43:29 2005 @@ -19,7 +19,7 @@ action="count", dest="verbose", default=0, help="increase verbosity"), Option('-x', '--exitfirst', - action="store_true", dest="exitfirstproblem", default=False, + action="store_true", dest="exitfirst", default=False, help="exit instantly on first error or failed test."), Option('-s', '--nocapture', action="store_true", dest="nocapture", default=False, Modified: py/branch/py-collect/test/terminal/out.py ============================================================================== --- py/branch/py-collect/test/terminal/out.py (original) +++ py/branch/py-collect/test/terminal/out.py Fri Apr 15 13:43:29 2005 @@ -73,7 +73,7 @@ if file is None: file = py.std.sys.stdout elif hasattr(file, 'send'): - file = WriteFile(file) + file = WriteFile(file.send) if hasattr(file, 'isatty') and file.isatty(): return TerminalOut(file) else: Modified: py/branch/py-collect/test/terminal/remote.py ============================================================================== --- py/branch/py-collect/test/terminal/remote.py (original) +++ py/branch/py-collect/test/terminal/remote.py Fri Apr 15 13:43:29 2005 @@ -1,8 +1,7 @@ from __future__ import generators import py -from py.__impl__.execnet.channel import ChannelFile, receive2file -from py.__impl__.test.config import configbasename -from py.__impl__.test.collect import getfscollector +from py.__impl__.execnet.channel import ChannelFile +from py.__impl__.test.terminal.out import getout import sys def checkpyfilechange(rootdir, statcache={}): @@ -36,29 +35,6 @@ for x in self._faileditems: yield x - -class StdouterrProxy: - def __init__(self, gateway): - self.gateway = gateway - def setup(self): - channel = self.gateway.remote_exec(""" - import sys - out, err = channel.newchannel(), channel.newchannel() - channel.send(out) - channel.send(err) - sys.stdout, sys.stderr = out.open('w'), err.open('w') - """) - self.stdout = channel.receive() - self.stderr = channel.receive() - channel.waitclose(1.0) - py.std.threading.Thread(target=receive2file, - args=(self.stdout, sys.stdout)).start() - py.std.threading.Thread(target=receive2file, - args=(self.stderr, sys.stderr)).start() - def teardown(self): - self.stdout.close() - self.stderr.close() - def waitfinish(channel): try: while 1: @@ -67,37 +43,27 @@ except (IOError, py.error.Error): continue else: - failures = channel.receive() - return failures - break + return channel.receive() finally: #print "closing down channel and gateway" channel.close() channel.gateway.exit() -def failure_master(executable, args, failures): - gw = py.execnet.PopenGateway(executable) - outproxy = StdouterrProxy(gw) - outproxy.setup() - try: - channel = gw.remote_exec(""" - from py.__impl__.test.run import failure_slave - failure_slave(channel) - """) - channel.send((args, failures)) - return waitfinish(channel) - finally: - outproxy.teardown() - def failure_slave(channel): """ we run this on the other side. """ args, failures = channel.receive() - sessionclass, config = py.test.init(args, ignoreremote=True) + config, args = py.test.Config.parse(args) + # making this session definitely non-remote + config.option.executable = py.std.sys.executable + config.option.looponfailing = False + config.option._remote = False + config.option._fromremote = True if failures: cols = getfailureitems(failures) else: - cols = config.paths - session = sessionclass(config) + cols = args + print "processing", cols + session = py.test.TerminalSession(config) session.shouldclose = channel.isclosed failures = session.main(cols) channel.send(failures) @@ -124,23 +90,53 @@ l.append(current) return l -class TerminalFrontendSession: - def main(self, *args): - # XXX figure out a better rootdir? - rootdir = py.path.local() - failures = [] - while 1: - if self.config.option.looponfailing: - while not checkpyfilechange(rootdir): - py.std.time.sleep(0.4) - failures = failure_master(self.config.option.executable or sys.executable, - self.args, failures) - if not self.config.option.session: - break - print "#" * 60 - print "# session mode: %d failures remaining" % len(failures) - for root, names in failures: - name = "/".join(names) # XXX - print "Failure at: %r" % (name,) - print "# watching py files below %s" % rootdir - print "# ", "^" * len(str(rootdir)) +def failure_master(executable, out, args, failures): + gw = py.execnet.PopenGateway(executable) + channel = gw.remote_exec(""" + from py.__impl__.test.terminal.remote import failure_slave + failure_slave(channel) + """, stdout=out, stderr=out) + channel.send((args, failures)) + return waitfinish(channel) + +def getrootdir(args): + colitems = py.test.TerminalSession._map2colitems(args) + tops = [x.listchain()[0].fspath for x in colitems] + def generalize(p1, p2): + general = p1 + for x, y in zip(p1.parts(), p2.parts()): + if x != y: + break + general = x + return general + return reduce(generalize, tops) + +def main(config, file, args): + """ testing process and output happens at a remote place. """ + assert file + if hasattr(file, 'write'): + def out(data): + file.write(data) + file.flush() + else: + out = file + failures = [] + args = list(args) + rootdir = getrootdir(config.remaining) + print "rootdir", rootdir + while 1: + if config.option.looponfailing: + while not checkpyfilechange(rootdir): + py.std.time.sleep(0.4) + print "sending", args + failures = failure_master(config.option.executable, out, args, failures) + if not config.option.looponfailing: + break + print "#" * 60 + print "# looponfailing: mode: %d failures remaining" % len(failures) + for root, names in failures: + name = "/".join(names) # XXX + print "Failure at: %r" % (name,) + print "# watching py files below %s" % rootdir + print "# ", "^" * len(str(rootdir)) + Modified: py/branch/py-collect/test/terminal/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal/terminal.py (original) +++ py/branch/py-collect/test/terminal/terminal.py Fri Apr 15 13:43:29 2005 @@ -7,10 +7,20 @@ class TerminalSession(py.test.Session): def __init__(self, config, file=None): super(TerminalSession, self).__init__(config) + if file is None: + file = py.std.sys.stdout + self._file = file self.out = getout(file) self._started = {} self._opencollectors = [] + def main(self, args): + if self.config.option._remote: + from py.__impl__.test.terminal import remote + remote.main(self.config, self._file, self.config._origargs) + else: + super(TerminalSession, self).main(args) + # --------------------- # PROGRESS information # --------------------- @@ -88,7 +98,7 @@ result.excinfo.value)) pdb.post_mortem(result.excinfo._excinfo[2]) if isinstance(result, (colitem.Failed,)): - if self.config.option.exitfirstproblem: + if self.config.option.exitfirst: py.test.exit("exit on first problem configured.", item=colitem) if result is None or not isinstance(colitem, py.test.Item): if isinstance(colitem, py.test.collect.Module) \ @@ -112,12 +122,15 @@ super(TerminalSession, self).header(colitems) self.out.sep("=", "test process starts") option = self.config.option - if option.looponfailing: - mode = 'session/child process' - elif option.executable: - mode = 'child process' + modes = [] + for name in 'looponfailing', 'exitfirst', 'nomagic': + if getattr(option, name): + modes.append(name) + if option._fromremote: + modes.insert(0, 'child process') else: - mode = 'inprocess' + modes.insert(0, 'inprocess') + mode = "/".join(modes) self.out.line("testing-mode: %s" % mode) self.out.line("executable: %s (%s)" % (py.std.sys.executable, repr_pythonversion())) Modified: py/branch/py-collect/test/testing/test_session.py ============================================================================== --- py/branch/py-collect/test/testing/test_session.py (original) +++ py/branch/py-collect/test/testing/test_session.py Fri Apr 15 13:43:29 2005 @@ -50,7 +50,7 @@ def test_exit_first_problem(self): session = self.session - session.config.option.exitfirstproblem = True + session.config.option.exitfirst = True session.main([str(datadir / 'filetest.py')]) l = session.getresults(py.test.Item.Failed) assert len(l) == 1 From hpk at codespeak.net Fri Apr 15 13:47:11 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 13:47:11 +0200 (CEST) Subject: [py-svn] r10666 - in py/branch/py-collect/test: . terminal Message-ID: <20050415114711.9739F27B7A@code1.codespeak.net> Author: hpk Date: Fri Apr 15 13:47:11 2005 New Revision: 10666 Modified: py/branch/py-collect/test/config.py py/branch/py-collect/test/terminal/remote.py Log: remove some obnoxious print statements (which show up due to the early redirection!) Modified: py/branch/py-collect/test/config.py ============================================================================== --- py/branch/py-collect/test/config.py (original) +++ py/branch/py-collect/test/config.py Fri Apr 15 13:47:11 2005 @@ -66,7 +66,6 @@ fixoptions(config.option) if not remaining: remaining.append(py.std.os.getcwd()) - print "remaining is", remaining config.remaining = remaining return config, remaining parse = classmethod(parse) Modified: py/branch/py-collect/test/terminal/remote.py ============================================================================== --- py/branch/py-collect/test/terminal/remote.py (original) +++ py/branch/py-collect/test/terminal/remote.py Fri Apr 15 13:47:11 2005 @@ -62,7 +62,7 @@ cols = getfailureitems(failures) else: cols = args - print "processing", cols + #print "processing", cols session = py.test.TerminalSession(config) session.shouldclose = channel.isclosed failures = session.main(cols) @@ -123,12 +123,11 @@ failures = [] args = list(args) rootdir = getrootdir(config.remaining) - print "rootdir", rootdir + #print "rootdir", rootdir while 1: if config.option.looponfailing: while not checkpyfilechange(rootdir): py.std.time.sleep(0.4) - print "sending", args failures = failure_master(config.option.executable, out, args, failures) if not config.option.looponfailing: break From arigo at codespeak.net Fri Apr 15 17:15:25 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 15 Apr 2005 17:15:25 +0200 (CEST) Subject: [py-svn] r10677 - in py/dist/py: . c-extension/extractdict magic magic/testing Message-ID: <20050415151525.2EDDC27B7A@code1.codespeak.net> Author: arigo Date: Fri Apr 15 17:15:25 2005 New Revision: 10677 Removed: py/dist/py/c-extension/extractdict/ py/dist/py/magic/extractdict.py py/dist/py/magic/testing/test_extractdict.py Modified: py/dist/py/__init__.py Log: Removed 'extractdict', which wasn't really useful. It's now found in http://codespeak.net/svn/user/arigo/hack/misc/, if you want it. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Fri Apr 15 17:15:25 2005 @@ -39,7 +39,6 @@ 'magic.autopath' : ('./magic/autopath.py', 'autopath'), 'magic.View' : ('./magic/viewtype.py', 'View'), 'magic.AssertionError' : ('./magic/assertion.py', 'AssertionError'), - 'magic.extractdict' : ('./magic/extractdict.py', 'extractdict'), 'code.compile' : ('./code/source.py', 'compile_'), 'code.Source' : ('./code/source.py', 'Source'), Deleted: /py/dist/py/magic/extractdict.py ============================================================================== --- /py/dist/py/magic/extractdict.py Fri Apr 15 17:15:25 2005 +++ (empty file) @@ -1,5 +0,0 @@ - -import py -gdir = py.path.local(py.__file__).dirpath() -path = gdir.join('c-extension', 'extractdict', 'extractdict.c') -extractdict = path.getpymodule().extractdict Deleted: /py/dist/py/magic/testing/test_extractdict.py ============================================================================== --- /py/dist/py/magic/testing/test_extractdict.py Fri Apr 15 17:15:25 2005 +++ (empty file) @@ -1,27 +0,0 @@ -import py - -def test_extractdict(): - from py.magic import extractdict - d = extractdict(str) - assert 'join' in d - saved = d['join'] - try: - del d['join'] - assert not hasattr('', 'join') - finally: - d['join'] = saved - -def test_typeerror(): - from py.magic import extractdict - py.test.raises(TypeError, extractdict, 5) - -class X(object): - pass - -def test_emptydict(): - from py.magic import extractdict - x = X() - d1 = extractdict(x) - assert d1 is x.__dict__ - d2 = extractdict(x) - assert d2 is d1 From hpk at codespeak.net Fri Apr 15 17:37:15 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 17:37:15 +0200 (CEST) Subject: [py-svn] r10680 - in py/branch/py-collect: . execnet execnet/testing test/testing thread thread/testing Message-ID: <20050415153715.D169727B82@code1.codespeak.net> Author: hpk Date: Fri Apr 15 17:37:15 2005 New Revision: 10680 Added: py/branch/py-collect/thread/ (props changed) py/branch/py-collect/thread/__init__.py - copied unchanged from r10656, py/branch/py-collect/test/__init__.py py/branch/py-collect/thread/io.py py/branch/py-collect/thread/pool.py - copied, changed from r10656, py/branch/py-collect/execnet/gateway.py py/branch/py-collect/thread/testing/ (props changed) py/branch/py-collect/thread/testing/__init__.py - copied unchanged from r10656, py/branch/py-collect/test/__init__.py py/branch/py-collect/thread/testing/test_threadpool.py (contents, props changed) - copied, changed from r10656, py/branch/py-collect/execnet/testing/test_threadpool.py Removed: py/branch/py-collect/execnet/testing/test_threadpool.py Modified: py/branch/py-collect/__init__.py py/branch/py-collect/execnet/gateway.py py/branch/py-collect/execnet/register.py py/branch/py-collect/execnet/testing/test_gateway.py py/branch/py-collect/test/testing/test_session.py Log: refactoring thread-related stuff from py.execnet to a new namespace: py.thread. However, i am going to change it to py._thread for the time being because the design of the supplied helper-classes is somewhat floating. Samuele Pedroni pointed me to the way Java 1.5 organizes Threads which might be an interesting source (although heavily convoluted to read :-) for design considerations. Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/branch/py-collect/__init__.py (original) +++ py/branch/py-collect/__init__.py Fri Apr 15 17:37:15 2005 @@ -26,6 +26,11 @@ 'test.Item' : ('./test/item.py', 'Item'), 'test.Function' : ('./test/item.py', 'Function'), + # thread related API + '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'), Modified: py/branch/py-collect/execnet/gateway.py ============================================================================== --- py/branch/py-collect/execnet/gateway.py (original) +++ py/branch/py-collect/execnet/gateway.py Fri Apr 15 17:37:15 2005 @@ -8,11 +8,14 @@ # XXX the following line should not be here -g = globals() -if 'Message' not in g: +if 'Message' not in globals(): + import py from py.code import Source from py.__impl__.execnet.channel import ChannelFactory, Channel from py.__impl__.execnet.message import Message + ThreadOut = py.thread.ThreadOut + WorkerPool = py.thread.WorkerPool + NamedThreadPool = py.thread.NamedThreadPool assert Message and ChannelFactory, "Import/Configuration Error" @@ -33,200 +36,10 @@ def __repr__(self): return "%s: %s" %(self.__class__.__name__, self.formatted) -class WorkerThread(threading.Thread): - def __init__(self, pool): - super(WorkerThread, self).__init__() - self._queue = Queue.Queue() - self._pool = pool - self.setDaemon(1) - - def run(self): - try: - while 1: - task = self._queue.get() - assert self not in self._pool._ready - if task is None: - break - try: - func, args, kwargs = task - func(*args, **kwargs) - except (SystemExit, KeyboardInterrupt): - break - except: - import traceback - traceback.print_exc() - self._pool._ready.append(self) - finally: - del self._pool._alive[self] - - def handle(self, task): - self._queue.put(task) - - def stop(self): - self._queue.put(None) - -class WorkerPool(object): - _shutdown = False - def __init__(self, maxthreads=None): - self.maxthreads = maxthreads - self._numthreads = 0 - self._ready = [] - self._alive = {} - - def dispatch(self, func, *args, **kwargs): - if self._shutdown: - raise IOError("WorkerPool is already shutting down") - task = (func, args, kwargs) - try: - thread = self._ready.pop() - except IndexError: # pop from empty list - thread = self._newthread() - thread.handle(task) - - def __del__(self): - self.shutdown() - - def shutdown(self, timeout=1.0): - if not self._shutdown: - self._shutdown = True - now = time.time() - while self._alive: - try: - thread = self._ready.pop() - except IndexError: - if now + timeout < time.time(): - raise IOError("Timeout: could not shut down WorkerPool") - time.sleep(0.1) - else: - thread.stop() - - def _newthread(self): - if self.maxthreads: - if len(self._alive) >= self.maxthreads: - raise IOError("cannot create more threads, " - "maxthreads=%d" % (self.maxthreads,)) - thread = WorkerThread(self) - thread.start() - self._alive[thread] = True - return thread - - def join(self): - current = threading.currentThread() - for x in self._alive.keys(): - if current != x: - x.join() - -class NamedThreadPool: - def __init__(self, **kw): - self._namedthreads = {} - for name, value in kw.items(): - self.start(name, value) - - def __repr__(self): - return "" %(self._namedthreads) - - def get(self, name=None): - if name is None: - l = [] - for x in self._namedthreads.values(): - l.extend(x) - return l - else: - return self._namedthreads.get(name, []) - - def getstarted(self, name=None): - return [t for t in self.get(name) if t.isAlive()] - - def prunestopped(self, name=None): - if name is None: - for name in self.names(): - self.prunestopped(name) - else: - self._namedthreads[name] = self.getstarted(name) - - def names(self): - return self._namedthreads.keys() - - def start(self, name, func): - l = self._namedthreads.setdefault(name, []) - thread = threading.Thread(name="%s%d" % (name, len(l)), - target=func) - thread.start() - l.append(thread) - -class ThreadOut(object): - def __new__(cls, obj, attrname): - """ Divert file output to per-thread writefuncs. - the given obj and attrname describe the destination - of the file. - """ - current = getattr(obj, attrname) - if isinstance(current, cls): - current._used += 1 - return current - self = object.__new__(cls) - self._tid2out = {} - self._used = 1 - self._oldout = getattr(obj, attrname) - self._defaultwriter = self._oldout.write - self._address = (obj, attrname) - setattr(obj, attrname, self) - return self - - def setdefaultwriter(self, writefunc): - self._defaultwriter = writefunc - - def resetdefault(self): - self._defaultwriter = self._oldout.write - - def softspace(): - def fget(self): - return self._get()[0] - def fset(self, value): - self._get()[0] = value - return property(fget, fset, None, "software attribute") - softspace = softspace() - - def deinstall(self): - self._used -= 1 - x = self._used - if x <= 0: - obj, attrname = self._address - setattr(obj, attrname, self._oldout) - - def setwritefunc(self, writefunc, tid=None): - assert callable(writefunc) - if tid is None: - tid = thread.get_ident() - self._tid2out[tid] = [0, writefunc] - - def delwritefunc(self, tid=None, ignoremissing=True): - if tid is None: - tid = thread.get_ident() - try: - del self._tid2out[tid] - except KeyError: - if not ignoremissing: - raise - - def _get(self): - tid = thread.get_ident() - try: - return self._tid2out[tid] - except KeyError: - return getattr(self._defaultwriter, 'softspace', 0), self._defaultwriter - - def write(self, data): - softspace, out = self._get() - out(data) - - def flush(self): - pass - class Gateway(object): num_worker_threads = 2 RemoteError = RemoteError - ThreadOut = ThreadOut + ThreadOut = ThreadOut def __init__(self, io, startcount=2, maxthreads=None): self._execpool = WorkerPool() Modified: py/branch/py-collect/execnet/register.py ============================================================================== --- py/branch/py-collect/execnet/register.py (original) +++ py/branch/py-collect/execnet/register.py Fri Apr 15 17:37:15 2005 @@ -4,13 +4,33 @@ from py.magic import autopath ; mypath = autopath() import py -from py.__impl__.execnet import inputoutput, gateway, channel, message + +# the list of modules that must be send to the other side +# for bootstrapping gateways +# XXX we want to have a cleaner bootstrap mechanism +# by making sure early that we have the py lib available +# in a sufficient version + +startup_modules = [ + 'py.__impl__.execnet.inputoutput', + 'py.__impl__.execnet.gateway', + 'py.__impl__.execnet.channel', + 'py.__impl__.execnet.message', + 'py.__impl__.thread.io', + 'py.__impl__.thread.pool', +] + +def getsource(dottedname): + mod = __import__(dottedname, None, None, ['last']) + return inspect.getsource(mod) + +from py.__impl__.execnet import inputoutput, gateway class InstallableGateway(gateway.Gateway): """ initialize gateways on both sides of a inputoutput object. """ def __init__(self, io): self.remote_bootstrap_gateway(io) - gateway.Gateway.__init__(self, io=io, startcount=1) + super(InstallableGateway, self).__init__(io=io, startcount=1) def remote_bootstrap_gateway(self, io, extra=''): """ return Gateway with a asynchronously remotely @@ -20,15 +40,9 @@ gateway starts with odd numbers. This allows to uniquely identify channels across both sides. """ - bootstrap = [ - extra, - inspect.getsource(inputoutput), - inspect.getsource(message), - inspect.getsource(channel), - inspect.getsource(gateway), - io.server_stmt, - "Gateway(io=io, startcount=2).join()", - ] + bootstrap = ["we_are_remote=True", extra] + bootstrap += [getsource(x) for x in startup_modules] + bootstrap += [io.server_stmt, "Gateway(io=io, startcount=2).join()",] source = "\n".join(bootstrap) self.trace("sending gateway bootstrap code") io.write('%r\n' % source) Modified: py/branch/py-collect/execnet/testing/test_gateway.py ============================================================================== --- py/branch/py-collect/execnet/testing/test_gateway.py (original) +++ py/branch/py-collect/execnet/testing/test_gateway.py Fri Apr 15 17:37:15 2005 @@ -5,6 +5,24 @@ mypath = py.magic.autopath() from StringIO import StringIO +from py.__impl__.execnet.register import startup_modules, getsource + +def test_getsource_import_modules(): + for dottedname in startup_modules: + yield getsource, dottedname + +def test_getsource_no_colision(): + seen = {} + for dottedname in startup_modules: + mod = __import__(dottedname, None, None, ['last']) + for name, value in vars(mod).items(): + if py.std.inspect.isclass(value): + if name in seen: + olddottedname, oldval = seen[name] + if oldval is not value: + py.test.fail("duplicate class %r in %s and %s" % + (name, dottedname, olddottedname)) + seen[name] = (dottedname, value) class TestMessage: def test_wire_protocol(self): @@ -50,7 +68,6 @@ channel = self.fac.new() py.test.raises(IOError, channel.waitclose, timeout=0.01) - class PopenGatewayTestSetup: def setup_class(cls): cls.gw = py.execnet.PopenGateway() Deleted: /py/branch/py-collect/execnet/testing/test_threadpool.py ============================================================================== --- /py/branch/py-collect/execnet/testing/test_threadpool.py Fri Apr 15 17:37:15 2005 +++ (empty file) @@ -1,104 +0,0 @@ - -from py.__impl__.execnet.gateway import WorkerPool, ThreadOut -import py -import sys - -def test_some(): - pool = WorkerPool() - q = py.std.Queue.Queue() - num = 4 - for i in range(num): - pool.dispatch(q.put, i) - for i in range(num): - q.get() - assert len(pool._alive) == 4 - pool.shutdown() - assert len(pool._alive) == 0 - assert len(pool._ready) == 0 - -def test_maxthreads(): - pool = WorkerPool(maxthreads=1) - def f(): - py.std.time.sleep(0.5) - try: - pool.dispatch(f) - py.test.raises(IOError, pool.dispatch, f) - finally: - pool.shutdown() - -def test_shutdown_timeout(): - pool = WorkerPool() - def f(): - py.std.time.sleep(0.5) - pool.dispatch(f) - py.test.raises(IOError, pool.shutdown, 0.2) - -def test_pool_clean_shutdown(): - pool = WorkerPool() - def f(): - pass - pool.dispatch(f) - pool.dispatch(f) - pool.shutdown() - assert not pool._alive - assert not pool._ready - -def test_threadout_install_deinstall(): - old = sys.stdout - out = ThreadOut(sys, 'stdout') - out.deinstall() - assert old == sys.stdout - -class TestThreadOut: - def setup_method(self, method): - self.out = ThreadOut(sys, 'stdout') - def teardown_method(self, method): - self.out.deinstall() - - def test_threadout_one(self): - l = [] - self.out.setwritefunc(l.append) - print 42,13, - x = l.pop(0) - assert x == '42' - x = l.pop(0) - assert x == ' ' - x = l.pop(0) - assert x == '13' - - - def test_threadout_multi_and_default(self): - num = 3 - defaults = [] - def f(l): - self.out.setwritefunc(l.append) - print id(l), - self.out.delwritefunc() - print 1 - - self.out.setdefaultwriter(defaults.append) - pool = WorkerPool() - listlist = [] - for x in range(num): - l = [] - listlist.append(l) - pool.dispatch(f, l) - pool.shutdown() - for name, value in self.out.__dict__.items(): - print >>sys.stderr, "%s: %s" %(name, value) - for i in range(num): - item = listlist[i] - assert item ==[str(id(item))] - assert not self.out._tid2out - assert defaults - expect = ['1' for x in range(num)] - defaults = [x for x in defaults if x.strip()] - assert defaults == expect - - def test_threadout_nested(self): - # we want ThreadOuts to coexist - last = sys.stdout - out = ThreadOut(sys, 'stdout') - assert last == sys.stdout - out.deinstall() - assert last == sys.stdout Modified: py/branch/py-collect/test/testing/test_session.py ============================================================================== --- py/branch/py-collect/test/testing/test_session.py (original) +++ py/branch/py-collect/test/testing/test_session.py Fri Apr 15 17:37:15 2005 @@ -143,3 +143,28 @@ assert item.name == 'test_4' names = item.listnames() assert names == ['ordertest', 'test_orderofexecution.py', 'Testmygroup', '()', 'test_4'] + +class TestRemoteSession: + + def XXXtest_basic(self, method): + o = tmpdir.ensure('basicremote', dir=1) + tfile = o.join('test_remote.py') + tfile.write(py.code.Source(""" + def test_1(): + assert 1 == 0 + """)) + config, args = py.test.Config.parse(['--looponfailing']) + assert config._remote + cls = config.getsessionclass() + out = py.std.Queue.Queue() + session = cls(config, out.put) + pool = WorkerPool() + reply = pool.dispatch(session.main, [o]) + session.main([o]) + out = self.file.getvalue() + #print out + l = [] + self.session = py.test.TerminalSession(config, file=self.file) + #print >>f, "session %r is setup for %r" %(self.session, method) + #f.flush() + #print "session is setup", self.session Added: py/branch/py-collect/thread/io.py ============================================================================== --- (empty file) +++ py/branch/py-collect/thread/io.py Fri Apr 15 17:37:15 2005 @@ -0,0 +1,72 @@ + +import thread + +class ThreadOut(object): + def __new__(cls, obj, attrname): + """ Divert file output to per-thread writefuncs. + the given obj and attrname describe the destination + of the file. + """ + current = getattr(obj, attrname) + if isinstance(current, cls): + current._used += 1 + return current + self = object.__new__(cls) + self._tid2out = {} + self._used = 1 + self._oldout = getattr(obj, attrname) + self._defaultwriter = self._oldout.write + self._address = (obj, attrname) + setattr(obj, attrname, self) + return self + + def setdefaultwriter(self, writefunc): + self._defaultwriter = writefunc + + def resetdefault(self): + self._defaultwriter = self._oldout.write + + def softspace(): + def fget(self): + return self._get()[0] + def fset(self, value): + self._get()[0] = value + return property(fget, fset, None, "software attribute") + softspace = softspace() + + def deinstall(self): + self._used -= 1 + x = self._used + if x <= 0: + obj, attrname = self._address + setattr(obj, attrname, self._oldout) + + def setwritefunc(self, writefunc, tid=None): + assert callable(writefunc) + if tid is None: + tid = thread.get_ident() + self._tid2out[tid] = [0, writefunc] + + def delwritefunc(self, tid=None, ignoremissing=True): + if tid is None: + tid = thread.get_ident() + try: + del self._tid2out[tid] + except KeyError: + if not ignoremissing: + raise + + def _get(self): + tid = thread.get_ident() + try: + return self._tid2out[tid] + except KeyError: + return getattr(self._defaultwriter, 'softspace', 0), self._defaultwriter + + def write(self, data): + softspace, out = self._get() + out(data) + + def flush(self): + pass + Copied: py/branch/py-collect/thread/pool.py (from r10656, py/branch/py-collect/execnet/gateway.py) ============================================================================== --- py/branch/py-collect/execnet/gateway.py (original) +++ py/branch/py-collect/thread/pool.py Fri Apr 15 17:37:15 2005 @@ -1,37 +1,6 @@ -import sys -import os -import thread, threading -import Queue -import traceback -import atexit -import time - - -# XXX the following line should not be here -g = globals() -if 'Message' not in g: - from py.code import Source - from py.__impl__.execnet.channel import ChannelFactory, Channel - from py.__impl__.execnet.message import Message - -assert Message and ChannelFactory, "Import/Configuration Error" - -import os -debug = 0 # open('/tmp/execnet-debug-%d' % os.getpid() , 'wa') - -sysex = (KeyboardInterrupt, SystemExit) - -class RemoteError(EOFError): - """ Contains an Exceptions from the other side. """ - def __init__(self, formatted): - self.formatted = formatted - EOFError.__init__(self) - - def __str__(self): - return self.formatted - - def __repr__(self): - return "%s: %s" %(self.__class__.__name__, self.formatted) +import Queue +import threading +import time class WorkerThread(threading.Thread): def __init__(self, pool): @@ -83,9 +52,6 @@ thread = self._newthread() thread.handle(task) - def __del__(self): - self.shutdown() - def shutdown(self, timeout=1.0): if not self._shutdown: self._shutdown = True @@ -154,319 +120,3 @@ thread.start() l.append(thread) -class ThreadOut(object): - def __new__(cls, obj, attrname): - """ Divert file output to per-thread writefuncs. - the given obj and attrname describe the destination - of the file. - """ - current = getattr(obj, attrname) - if isinstance(current, cls): - current._used += 1 - return current - self = object.__new__(cls) - self._tid2out = {} - self._used = 1 - self._oldout = getattr(obj, attrname) - self._defaultwriter = self._oldout.write - self._address = (obj, attrname) - setattr(obj, attrname, self) - return self - - def setdefaultwriter(self, writefunc): - self._defaultwriter = writefunc - - def resetdefault(self): - self._defaultwriter = self._oldout.write - - def softspace(): - def fget(self): - return self._get()[0] - def fset(self, value): - self._get()[0] = value - return property(fget, fset, None, "software attribute") - softspace = softspace() - - def deinstall(self): - self._used -= 1 - x = self._used - if x <= 0: - obj, attrname = self._address - setattr(obj, attrname, self._oldout) - - def setwritefunc(self, writefunc, tid=None): - assert callable(writefunc) - if tid is None: - tid = thread.get_ident() - self._tid2out[tid] = [0, writefunc] - - def delwritefunc(self, tid=None, ignoremissing=True): - if tid is None: - tid = thread.get_ident() - try: - del self._tid2out[tid] - except KeyError: - if not ignoremissing: - raise - - def _get(self): - tid = thread.get_ident() - try: - return self._tid2out[tid] - except KeyError: - return getattr(self._defaultwriter, 'softspace', 0), self._defaultwriter - - def write(self, data): - softspace, out = self._get() - out(data) - - def flush(self): - pass - -class Gateway(object): - num_worker_threads = 2 - RemoteError = RemoteError - ThreadOut = ThreadOut - - def __init__(self, io, startcount=2, maxthreads=None): - self._execpool = WorkerPool() - self.running = True - self.io = io - self._outgoing = Queue.Queue() - self.channelfactory = ChannelFactory(self, startcount) - self._exitlock = threading.Lock() - self.pool = NamedThreadPool(receiver = self.thread_receiver, - sender = self.thread_sender) - if not _gateways: - atexit.register(cleanup_atexit) - _gateways.append(self) - - 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" - i = len(self.channelfactory.values()) - return "<%s %s/%s (%d active channels)>" %(self.__class__.__name__, - R, S, i) - - def _stopexec(self): - #self.pool.prunestopped() - self._execpool.shutdown() - - def exit(self): - # note that threads may still be scheduled to start - # during our execution! - self._exitlock.acquire() - try: - if self.running: - self.running = False - self._stopexec() - if self.pool.getstarted('sender'): - self._outgoing.put(Message.EXIT_GATEWAY()) - self.trace("exit procedure triggered, pid %d, gateway %r" % ( - os.getpid(), self)) - _gateways.remove(self) - finally: - self._exitlock.release() - - def join(self): - current = threading.currentThread() - for x in self.pool.getstarted(): - if x != current: - self.trace("joining %s" % x) - x.join() - self._execpool.join() - self.trace("joining threads finished, current %r" % current) - - def trace(self, *args): - if debug: - try: - l = "\n".join(args).split(os.linesep) - id = getid(self) - for x in l: - print >>debug, x - debug.flush() - except sysex: - raise - except: - traceback.print_exc() - def traceex(self, excinfo): - l = traceback.format_exception(*excinfo) - errortext = "".join(l) - self.trace(errortext) - - def thread_receiver(self): - """ thread to read and handle Messages half-sync-half-async. """ - try: - while 1: - try: - msg = Message.readfrom(self.io) - self.trace("received <- %r" % msg) - msg.received(self) - except sysex: - raise - except: - self.traceex(sys.exc_info()) - break - finally: - self.trace('leaving %r' % threading.currentThread()) - - def thread_sender(self): - """ thread to send Messages over the wire. """ - try: - while 1: - msg = self._outgoing.get() - try: - msg.writeto(self.io) - except: - excinfo = sys.exc_info() - self.traceex(excinfo) - msg.post_sent(self, excinfo) - raise - else: - self.trace('sent -> %r' % msg) - msg.post_sent(self) - finally: - self.trace('leaving %r' % threading.currentThread()) - - def _redirect_thread_output(self, outid, errid): - l = [] - for name, id in ('stdout', outid), ('stderr', errid): - if id: - channel = self._makechannel(outid) - out = ThreadOut(sys, name) - out.setwritefunc(channel.send) - l.append((out, channel)) - def close(): - for out, channel in l: - out.delwritefunc() - channel.close() - return close - - def _makechannel(self, newid): - newchannel = Channel(self, newid) - self.channelfactory[newid] = newchannel - return newchannel - - def thread_executor(self, channel, (source, outid, errid)): - """ worker thread to execute source objects from the execution queue. """ - try: - loc = { 'channel' : channel } - self.trace("execution starts:", repr(source)[:50]) - close = self._redirect_thread_output(outid, errid) - try: - co = compile(source+'\n', '', 'exec', 4096) - exec co in loc - finally: - close() - self.trace("execution finished:", repr(source)[:50]) - except (KeyboardInterrupt, SystemExit): - raise - except: - excinfo = sys.exc_info() - l = traceback.format_exception(*excinfo) - errortext = "".join(l) - channel.close(errortext) - self.trace(errortext) - else: - channel.close() - - def _scheduleexec(self, channel, sourcetask): - self.trace("dispatching exec") - self._execpool.dispatch(self.thread_executor, channel, sourcetask) - - def _dispatchcallback(self, callback, data): - # XXX this should run in a separate thread because - # we might otherwise block the receiver thread - # where we get called from - callback(data) - - def _remote_exec(self, channel, source, stdout=None, stderr=None): - try: - source = str(Source(source)) - except NameError: - try: - import py - source = str(py.code.Source(source)) - except ImportError: - pass - outid = self._redirectchannelid(stdout) - errid = self._redirectchannelid(stderr) - self._outgoing.put(Message.CHANNEL_OPEN(channel.id, - (source, outid, errid))) - - def _redirectchannelid(self, callback): - if callback is None: - return - if hasattr(callback, 'write'): - callback = callback.write - assert callable(callback) - chan = self.newchannel() - chan.setcallback(callback) - return chan.id - - # _____________________________________________________________________ - # - # High Level Interface - # _____________________________________________________________________ - # - def newchannel(self): - """ return new channel object. """ - return self.channelfactory.new() - - 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. If a channel is provided - is will be returned as well. - """ - channel = self.newchannel() - channel.remote_exec(source, stdout=stdout, stderr=stderr) - return channel - - def remote_redirect(self, stdout=None, stderr=None): - """ return a handle representing a redirection of of 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() - outchannel.setcallback(getattr(out, 'write', out)) - channel = self.remote_exec(""" - import sys - outchannel = channel.receive() - outchannel.gateway.ThreadOut(sys, %r).setdefaultwriter(outchannel.send) - """ % name) - channel.send(outchannel) - clist.append(channel) - for c in clist: - c.waitclose(1.0) - class Handle: - def close(_): - for name, out in ('stdout', stdout), ('stderr', stderr): - if out: - c = self.remote_exec(""" - import sys - channel.gateway.ThreadOut(sys, %r).resetdefault() - """ % name) - c.waitclose(1.0) - return Handle() - -def getid(gw, cache={}): - name = gw.__class__.__name__ - try: - return cache.setdefault(name, {})[id(gw)] - except KeyError: - cache[name][id(gw)] = x = "%s:%s.%d" %(os.getpid(), gw.__class__.__name__, len(cache[name])) - return x - -_gateways = [] -def cleanup_atexit(): - if debug: - print >>debug, "="*20 + "cleaning up" + "=" * 20 - debug.flush() - while _gateways: - x = _gateways[-1] - x.exit() Copied: py/branch/py-collect/thread/testing/test_threadpool.py (from r10656, py/branch/py-collect/execnet/testing/test_threadpool.py) ============================================================================== --- py/branch/py-collect/execnet/testing/test_threadpool.py (original) +++ py/branch/py-collect/thread/testing/test_threadpool.py Fri Apr 15 17:37:15 2005 @@ -1,8 +1,10 @@ -from py.__impl__.execnet.gateway import WorkerPool, ThreadOut import py import sys +WorkerPool = py.thread.WorkerPool +ThreadOut = py.thread.ThreadOut + def test_some(): pool = WorkerPool() q = py.std.Queue.Queue() From hpk at codespeak.net Fri Apr 15 17:39:59 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 17:39:59 +0200 (CEST) Subject: [py-svn] r10681 - py/branch/py-collect/thread/testing Message-ID: <20050415153959.2F15827B82@code1.codespeak.net> Author: hpk Date: Fri Apr 15 17:39:59 2005 New Revision: 10681 Added: py/branch/py-collect/thread/testing/test_io.py - copied, changed from r10680, py/branch/py-collect/thread/testing/test_threadpool.py py/branch/py-collect/thread/testing/test_pool.py - copied, changed from r10680, py/branch/py-collect/thread/testing/test_threadpool.py Removed: py/branch/py-collect/thread/testing/test_threadpool.py Log: split tests according to impl files Copied: py/branch/py-collect/thread/testing/test_io.py (from r10680, py/branch/py-collect/thread/testing/test_threadpool.py) ============================================================================== --- py/branch/py-collect/thread/testing/test_threadpool.py (original) +++ py/branch/py-collect/thread/testing/test_io.py Fri Apr 15 17:39:59 2005 @@ -1,106 +1,66 @@ import py -import sys +import sys -WorkerPool = py.thread.WorkerPool +WorkerPool = py.thread.WorkerPool ThreadOut = py.thread.ThreadOut -def test_some(): - pool = WorkerPool() - q = py.std.Queue.Queue() - num = 4 - for i in range(num): - pool.dispatch(q.put, i) - for i in range(num): - q.get() - assert len(pool._alive) == 4 - pool.shutdown() - assert len(pool._alive) == 0 - assert len(pool._ready) == 0 - -def test_maxthreads(): - pool = WorkerPool(maxthreads=1) - def f(): - py.std.time.sleep(0.5) - try: - pool.dispatch(f) - py.test.raises(IOError, pool.dispatch, f) - finally: - pool.shutdown() - -def test_shutdown_timeout(): - pool = WorkerPool() - def f(): - py.std.time.sleep(0.5) - pool.dispatch(f) - py.test.raises(IOError, pool.shutdown, 0.2) - -def test_pool_clean_shutdown(): - pool = WorkerPool() - def f(): - pass - pool.dispatch(f) - pool.dispatch(f) - pool.shutdown() - assert not pool._alive - assert not pool._ready - -def test_threadout_install_deinstall(): - old = sys.stdout - out = ThreadOut(sys, 'stdout') - out.deinstall() - assert old == sys.stdout - -class TestThreadOut: - def setup_method(self, method): - self.out = ThreadOut(sys, 'stdout') - def teardown_method(self, method): - self.out.deinstall() - - def test_threadout_one(self): +def test_threadout_install_deinstall(): + old = sys.stdout + out = ThreadOut(sys, 'stdout') + out.deinstall() + assert old == sys.stdout + +class TestThreadOut: + def setup_method(self, method): + self.out = ThreadOut(sys, 'stdout') + def teardown_method(self, method): + self.out.deinstall() + + def test_threadout_one(self): l = [] - self.out.setwritefunc(l.append) + self.out.setwritefunc(l.append) print 42,13, - x = l.pop(0) - assert x == '42' - x = l.pop(0) + x = l.pop(0) + assert x == '42' + x = l.pop(0) assert x == ' ' - x = l.pop(0) - assert x == '13' + x = l.pop(0) + assert x == '13' - def test_threadout_multi_and_default(self): - num = 3 + def test_threadout_multi_and_default(self): + num = 3 defaults = [] - def f(l): - self.out.setwritefunc(l.append) + def f(l): + self.out.setwritefunc(l.append) print id(l), - self.out.delwritefunc() - print 1 + self.out.delwritefunc() + print 1 - self.out.setdefaultwriter(defaults.append) - pool = WorkerPool() + self.out.setdefaultwriter(defaults.append) + pool = WorkerPool() listlist = [] - for x in range(num): + for x in range(num): l = [] - listlist.append(l) - pool.dispatch(f, l) - pool.shutdown() - for name, value in self.out.__dict__.items(): - print >>sys.stderr, "%s: %s" %(name, value) - for i in range(num): + listlist.append(l) + pool.dispatch(f, l) + pool.shutdown() + for name, value in self.out.__dict__.items(): + print >>sys.stderr, "%s: %s" %(name, value) + for i in range(num): item = listlist[i] assert item ==[str(id(item))] - assert not self.out._tid2out - assert defaults + assert not self.out._tid2out + assert defaults expect = ['1' for x in range(num)] defaults = [x for x in defaults if x.strip()] - assert defaults == expect + assert defaults == expect - def test_threadout_nested(self): - # we want ThreadOuts to coexist + def test_threadout_nested(self): + # we want ThreadOuts to coexist last = sys.stdout - out = ThreadOut(sys, 'stdout') - assert last == sys.stdout - out.deinstall() - assert last == sys.stdout + out = ThreadOut(sys, 'stdout') + assert last == sys.stdout + out.deinstall() + assert last == sys.stdout Copied: py/branch/py-collect/thread/testing/test_pool.py (from r10680, py/branch/py-collect/thread/testing/test_threadpool.py) ============================================================================== --- py/branch/py-collect/thread/testing/test_threadpool.py (original) +++ py/branch/py-collect/thread/testing/test_pool.py Fri Apr 15 17:39:59 2005 @@ -1,106 +1,46 @@ import py -import sys +import sys -WorkerPool = py.thread.WorkerPool +WorkerPool = py.thread.WorkerPool ThreadOut = py.thread.ThreadOut -def test_some(): - pool = WorkerPool() - q = py.std.Queue.Queue() +def test_some(): + pool = WorkerPool() + q = py.std.Queue.Queue() num = 4 - for i in range(num): - pool.dispatch(q.put, i) - for i in range(num): - q.get() - assert len(pool._alive) == 4 - pool.shutdown() - assert len(pool._alive) == 0 - assert len(pool._ready) == 0 - -def test_maxthreads(): - pool = WorkerPool(maxthreads=1) - def f(): - py.std.time.sleep(0.5) - try: - pool.dispatch(f) + for i in range(num): + pool.dispatch(q.put, i) + for i in range(num): + q.get() + assert len(pool._alive) == 4 + pool.shutdown() + assert len(pool._alive) == 0 + assert len(pool._ready) == 0 + +def test_maxthreads(): + pool = WorkerPool(maxthreads=1) + def f(): + py.std.time.sleep(0.5) + try: + pool.dispatch(f) py.test.raises(IOError, pool.dispatch, f) - finally: - pool.shutdown() + finally: + pool.shutdown() -def test_shutdown_timeout(): - pool = WorkerPool() - def f(): - py.std.time.sleep(0.5) - pool.dispatch(f) - py.test.raises(IOError, pool.shutdown, 0.2) - -def test_pool_clean_shutdown(): - pool = WorkerPool() - def f(): - pass - pool.dispatch(f) - pool.dispatch(f) +def test_shutdown_timeout(): + pool = WorkerPool() + def f(): + py.std.time.sleep(0.5) + pool.dispatch(f) + py.test.raises(IOError, pool.shutdown, 0.2) + +def test_pool_clean_shutdown(): + pool = WorkerPool() + def f(): + pass + pool.dispatch(f) + pool.dispatch(f) pool.shutdown() - assert not pool._alive - assert not pool._ready - -def test_threadout_install_deinstall(): - old = sys.stdout - out = ThreadOut(sys, 'stdout') - out.deinstall() - assert old == sys.stdout - -class TestThreadOut: - def setup_method(self, method): - self.out = ThreadOut(sys, 'stdout') - def teardown_method(self, method): - self.out.deinstall() - - def test_threadout_one(self): - l = [] - self.out.setwritefunc(l.append) - print 42,13, - x = l.pop(0) - assert x == '42' - x = l.pop(0) - assert x == ' ' - x = l.pop(0) - assert x == '13' - - - def test_threadout_multi_and_default(self): - num = 3 - defaults = [] - def f(l): - self.out.setwritefunc(l.append) - print id(l), - self.out.delwritefunc() - print 1 - - self.out.setdefaultwriter(defaults.append) - pool = WorkerPool() - listlist = [] - for x in range(num): - l = [] - listlist.append(l) - pool.dispatch(f, l) - pool.shutdown() - for name, value in self.out.__dict__.items(): - print >>sys.stderr, "%s: %s" %(name, value) - for i in range(num): - item = listlist[i] - assert item ==[str(id(item))] - assert not self.out._tid2out - assert defaults - expect = ['1' for x in range(num)] - defaults = [x for x in defaults if x.strip()] - assert defaults == expect - - def test_threadout_nested(self): - # we want ThreadOuts to coexist - last = sys.stdout - out = ThreadOut(sys, 'stdout') - assert last == sys.stdout - out.deinstall() - assert last == sys.stdout + assert not pool._alive + assert not pool._ready Deleted: /py/branch/py-collect/thread/testing/test_threadpool.py ============================================================================== --- /py/branch/py-collect/thread/testing/test_threadpool.py Fri Apr 15 17:39:59 2005 +++ (empty file) @@ -1,106 +0,0 @@ - -import py -import sys - -WorkerPool = py.thread.WorkerPool -ThreadOut = py.thread.ThreadOut - -def test_some(): - pool = WorkerPool() - q = py.std.Queue.Queue() - num = 4 - for i in range(num): - pool.dispatch(q.put, i) - for i in range(num): - q.get() - assert len(pool._alive) == 4 - pool.shutdown() - assert len(pool._alive) == 0 - assert len(pool._ready) == 0 - -def test_maxthreads(): - pool = WorkerPool(maxthreads=1) - def f(): - py.std.time.sleep(0.5) - try: - pool.dispatch(f) - py.test.raises(IOError, pool.dispatch, f) - finally: - pool.shutdown() - -def test_shutdown_timeout(): - pool = WorkerPool() - def f(): - py.std.time.sleep(0.5) - pool.dispatch(f) - py.test.raises(IOError, pool.shutdown, 0.2) - -def test_pool_clean_shutdown(): - pool = WorkerPool() - def f(): - pass - pool.dispatch(f) - pool.dispatch(f) - pool.shutdown() - assert not pool._alive - assert not pool._ready - -def test_threadout_install_deinstall(): - old = sys.stdout - out = ThreadOut(sys, 'stdout') - out.deinstall() - assert old == sys.stdout - -class TestThreadOut: - def setup_method(self, method): - self.out = ThreadOut(sys, 'stdout') - def teardown_method(self, method): - self.out.deinstall() - - def test_threadout_one(self): - l = [] - self.out.setwritefunc(l.append) - print 42,13, - x = l.pop(0) - assert x == '42' - x = l.pop(0) - assert x == ' ' - x = l.pop(0) - assert x == '13' - - - def test_threadout_multi_and_default(self): - num = 3 - defaults = [] - def f(l): - self.out.setwritefunc(l.append) - print id(l), - self.out.delwritefunc() - print 1 - - self.out.setdefaultwriter(defaults.append) - pool = WorkerPool() - listlist = [] - for x in range(num): - l = [] - listlist.append(l) - pool.dispatch(f, l) - pool.shutdown() - for name, value in self.out.__dict__.items(): - print >>sys.stderr, "%s: %s" %(name, value) - for i in range(num): - item = listlist[i] - assert item ==[str(id(item))] - assert not self.out._tid2out - assert defaults - expect = ['1' for x in range(num)] - defaults = [x for x in defaults if x.strip()] - assert defaults == expect - - def test_threadout_nested(self): - # we want ThreadOuts to coexist - last = sys.stdout - out = ThreadOut(sys, 'stdout') - assert last == sys.stdout - out.deinstall() - assert last == sys.stdout From hpk at codespeak.net Fri Apr 15 17:41:39 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 17:41:39 +0200 (CEST) Subject: [py-svn] r10682 - in py/branch/py-collect: . execnet thread/testing Message-ID: <20050415154139.0303027B82@code1.codespeak.net> Author: hpk Date: Fri Apr 15 17:41:39 2005 New Revision: 10682 Modified: py/branch/py-collect/__init__.py py/branch/py-collect/execnet/gateway.py py/branch/py-collect/thread/testing/test_io.py py/branch/py-collect/thread/testing/test_pool.py Log: moved py.thread to py._thread to indicate that you can rely on its API on your own risk. Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/branch/py-collect/__init__.py (original) +++ py/branch/py-collect/__init__.py Fri Apr 15 17:41:39 2005 @@ -1,22 +1,22 @@ from initpkg import initpkg initpkg(__name__, exportdefs = { - # helpers for use from test functions or collectors + # helpers for use from test functions or collectors 'test.raises' : ('./test/raises.py', 'raises'), 'test.skip' : ('./test/item.py', 'skip'), 'test.fail' : ('./test/item.py', 'fail'), 'test.exit' : ('./test/session.py', 'exit'), 'test.skip_on_error' : ('./test/item.py', 'skip_on_error'), - 'test.compat.TestCase' : ('./test/compat.py', 'TestCase'), + 'test.compat.TestCase' : ('./test/compat.py', 'TestCase'), - # configuration/initialization related test api - 'test.Config' : ('./test/config.py', 'Config'), - 'test.ensuretemp' : ('./test/config.py', 'ensuretemp'), + # configuration/initialization related test api + 'test.Config' : ('./test/config.py', 'Config'), + 'test.ensuretemp' : ('./test/config.py', 'ensuretemp'), - # for customization of collecting/running tests + # for customization of collecting/running tests 'test.Session' : ('./test/session.py', 'Session'), - 'test.TerminalSession' : ('./test/terminal/terminal.py', 'TerminalSession'), - 'test.TkinterSession' : ('./test/tkinter/tkgui.py', 'TkinterSession'), + 'test.TerminalSession' : ('./test/terminal/terminal.py', 'TerminalSession'), + 'test.TkinterSession' : ('./test/tkinter/tkgui.py', 'TkinterSession'), 'test.collect.Collector' : ('./test/collect.py', 'Collector'), 'test.collect.Directory' : ('./test/collect.py', 'Directory'), 'test.collect.Module' : ('./test/collect.py', 'Module'), @@ -26,25 +26,25 @@ 'test.Item' : ('./test/item.py', 'Item'), 'test.Function' : ('./test/item.py', 'Function'), - # thread related API - 'thread.WorkerPool' : ('./thread/pool.py', 'WorkerPool'), - 'thread.NamedThreadPool' : ('./thread/pool.py', 'NamedThreadPool'), - 'thread.ThreadOut' : ('./thread/io.py', 'ThreadOut'), + # 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 + # hook into the top-level standard library 'std' : ('./misc/std.py', 'std'), 'process.cmdexec' : ('./process/cmdexec.py', 'cmdexec'), - # path implementations + # 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'), - # some nice more or less magic APIs - 'magic.greenlet' : ('./magic/greenlet.py', 'greenlet'), + # some nice more or less 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'), @@ -53,7 +53,7 @@ 'magic.View' : ('./magic/viewtype.py', 'View'), 'magic.AssertionError' : ('./magic/assertion.py', 'AssertionError'), - # generalized inspection API + # generalized inspection API 'code.compile' : ('./code/source.py', 'compile_'), 'code.Source' : ('./code/source.py', 'Source'), 'code.Code' : ('./code/frame.py', 'Code'), @@ -61,20 +61,20 @@ 'code.ExceptionInfo' : ('./code/excinfo.py', 'ExceptionInfo'), 'code.Traceback' : ('./code/traceback2.py', 'Traceback'), - # backports and additions of builtins + # backports and additions of builtins 'builtin.enumerate' : ('./builtin/enumerate.py', 'enumerate'), - # gateways into remote contexts + # gateways into remote contexts 'execnet.SocketGateway' : ('./execnet/register.py', 'SocketGateway'), 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), 'execnet.SshGateway' : ('./execnet/register.py', 'SshGateway'), - # error module, defining all errno's as Classes + # 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'), + # 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'), }) Modified: py/branch/py-collect/execnet/gateway.py ============================================================================== --- py/branch/py-collect/execnet/gateway.py (original) +++ py/branch/py-collect/execnet/gateway.py Fri Apr 15 17:41:39 2005 @@ -13,9 +13,9 @@ from py.code import Source from py.__impl__.execnet.channel import ChannelFactory, Channel from py.__impl__.execnet.message import Message - ThreadOut = py.thread.ThreadOut - WorkerPool = py.thread.WorkerPool - NamedThreadPool = py.thread.NamedThreadPool + ThreadOut = py._thread.ThreadOut + WorkerPool = py._thread.WorkerPool + NamedThreadPool = py._thread.NamedThreadPool assert Message and ChannelFactory, "Import/Configuration Error" Modified: py/branch/py-collect/thread/testing/test_io.py ============================================================================== --- py/branch/py-collect/thread/testing/test_io.py (original) +++ py/branch/py-collect/thread/testing/test_io.py Fri Apr 15 17:41:39 2005 @@ -2,8 +2,8 @@ import py import sys -WorkerPool = py.thread.WorkerPool -ThreadOut = py.thread.ThreadOut +WorkerPool = py._thread.WorkerPool +ThreadOut = py._thread.ThreadOut def test_threadout_install_deinstall(): old = sys.stdout Modified: py/branch/py-collect/thread/testing/test_pool.py ============================================================================== --- py/branch/py-collect/thread/testing/test_pool.py (original) +++ py/branch/py-collect/thread/testing/test_pool.py Fri Apr 15 17:41:39 2005 @@ -2,8 +2,8 @@ import py import sys -WorkerPool = py.thread.WorkerPool -ThreadOut = py.thread.ThreadOut +WorkerPool = py._thread.WorkerPool +ThreadOut = py._thread.ThreadOut def test_some(): pool = WorkerPool() From hpk at codespeak.net Fri Apr 15 18:32:07 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 18:32:07 +0200 (CEST) Subject: [py-svn] r10690 - in py/branch/py-collect/thread: . testing Message-ID: <20050415163207.68A4227B7A@code1.codespeak.net> Author: hpk Date: Fri Apr 15 18:32:07 2005 New Revision: 10690 Modified: py/branch/py-collect/thread/pool.py py/branch/py-collect/thread/testing/test_pool.py Log: when dispatching to a pool return a Reply object which you can reply.get() on for results (possibly supllying a timeout). Exceptions will be propagated on reply.get(). Modified: py/branch/py-collect/thread/pool.py ============================================================================== --- py/branch/py-collect/thread/pool.py (original) +++ py/branch/py-collect/thread/pool.py Fri Apr 15 18:32:07 2005 @@ -1,6 +1,7 @@ import Queue import threading import time +import sys class WorkerThread(threading.Thread): def __init__(self, pool): @@ -12,28 +13,60 @@ def run(self): try: while 1: - task = self._queue.get() + reply = self._queue.get() + task = reply.task assert self not in self._pool._ready if task is None: break try: func, args, kwargs = task - func(*args, **kwargs) + result = func(*args, **kwargs) except (SystemExit, KeyboardInterrupt): break except: - import traceback - traceback.print_exc() + reply.setexcinfo(sys.exc_info()) + else: + reply.set(result) self._pool._ready.append(self) finally: del self._pool._alive[self] def handle(self, task): - self._queue.put(task) + reply = Reply(task) + self._queue.put(reply) + return reply def stop(self): self._queue.put(None) +class Reply(object): + _excinfo = None + def __init__(self, task): + self.task = task + self._queue = Queue.Queue() + + def set(self, result): + self._queue.put(result) + + def setexcinfo(self, excinfo): + self._excinfo = excinfo + self._queue.put(None) + + def __del__(self): + if self._excinfo: + print "unhandled exception %s" %(self._excinfo,) + + def get(self, timeout=None): + try: + result = self._queue.get(timeout=timeout) + except Queue.Empty: + raise IOError("timeout waiting for task %r" %(self.task,)) + excinfo = self._excinfo + if excinfo: + self._excinfo = None + raise excinfo[0], excinfo[1], excinfo[2] + return result + class WorkerPool(object): _shutdown = False def __init__(self, maxthreads=None): @@ -50,7 +83,7 @@ thread = self._ready.pop() except IndexError: # pop from empty list thread = self._newthread() - thread.handle(task) + return thread.handle(task) def shutdown(self, timeout=1.0): if not self._shutdown: Modified: py/branch/py-collect/thread/testing/test_pool.py ============================================================================== --- py/branch/py-collect/thread/testing/test_pool.py (original) +++ py/branch/py-collect/thread/testing/test_pool.py Fri Apr 15 18:32:07 2005 @@ -18,6 +18,29 @@ assert len(pool._alive) == 0 assert len(pool._ready) == 0 +def test_get(): + pool = WorkerPool() + def f(): + return 42 + reply = pool.dispatch(f) + result = reply.get() + assert result == 42 + +def test_get_timeout(): + pool = WorkerPool() + def f(): + py.std.time.sleep(0.2) + return 42 + reply = pool.dispatch(f) + py.test.raises(IOError, "reply.get(timeout=0.01)") + +def test_get_excinfo(): + pool = WorkerPool() + def f(): + raise ValueError("42") + reply = pool.dispatch(f) + excinfo = py.test.raises(ValueError, "reply.get(1.0)") + def test_maxthreads(): pool = WorkerPool(maxthreads=1) def f(): From hpk at codespeak.net Fri Apr 15 18:33:27 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 18:33:27 +0200 (CEST) Subject: [py-svn] r10691 - py/branch/py-collect/thread Message-ID: <20050415163327.9C8A827B7A@code1.codespeak.net> Author: hpk Date: Fri Apr 15 18:33:27 2005 New Revision: 10691 Modified: py/branch/py-collect/thread/pool.py Log: remove __del__ which cannot work this simple way anyway. Modified: py/branch/py-collect/thread/pool.py ============================================================================== --- py/branch/py-collect/thread/pool.py (original) +++ py/branch/py-collect/thread/pool.py Fri Apr 15 18:33:27 2005 @@ -52,10 +52,6 @@ self._excinfo = excinfo self._queue.put(None) - def __del__(self): - if self._excinfo: - print "unhandled exception %s" %(self._excinfo,) - def get(self, timeout=None): try: result = self._queue.get(timeout=timeout) From hpk at codespeak.net Fri Apr 15 18:55:50 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 18:55:50 +0200 (CEST) Subject: [py-svn] r10693 - in py/branch/py-collect/path/local: . testing Message-ID: <20050415165550.A5EC527B7A@code1.codespeak.net> Author: hpk Date: Fri Apr 15 18:55:50 2005 New Revision: 10693 Modified: py/branch/py-collect/path/local/local.py py/branch/py-collect/path/local/testing/test_local.py Log: allow to pass in absolute path into sysfind() Modified: py/branch/py-collect/path/local/local.py ============================================================================== --- py/branch/py-collect/path/local/local.py (original) +++ py/branch/py-collect/path/local/local.py Fri Apr 15 18:55:50 2005 @@ -474,7 +474,7 @@ tryadd = ('',) for x in paths: for addext in tryadd: - p = py.path.local(x).join(name) + addext + p = py.path.local(x).join(name, abs=True) + addext if p.check(file=1): if checker: if not checker(p): Modified: py/branch/py-collect/path/local/testing/test_local.py ============================================================================== --- py/branch/py-collect/path/local/testing/test_local.py (original) +++ py/branch/py-collect/path/local/testing/test_local.py Fri Apr 15 18:55:50 2005 @@ -149,6 +149,13 @@ py.path.local.sysfind('jaksdkasldqwe') """) + def test_sysfind_absolute(self): + x = py.path.local.sysfind('test') + assert x.check(file=1) + y = py.path.local.sysfind(str(x)) + assert y.check(file=1) + assert y == x + def test_sysfind_multiple(self): dir = py.test.ensuretemp('sysfind') env = py.std.os.environ From hpk at codespeak.net Fri Apr 15 19:13:30 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 19:13:30 +0200 (CEST) Subject: [py-svn] r10694 - py/branch/py-collect/test/testing Message-ID: <20050415171330.4581927B7A@code1.codespeak.net> Author: hpk Date: Fri Apr 15 19:13:30 2005 New Revision: 10694 Modified: py/branch/py-collect/test/testing/test_session.py Log: add a test for --exec Modified: py/branch/py-collect/test/testing/test_session.py ============================================================================== --- py/branch/py-collect/test/testing/test_session.py (original) +++ py/branch/py-collect/test/testing/test_session.py Fri Apr 15 19:13:30 2005 @@ -146,25 +146,24 @@ class TestRemoteSession: - def XXXtest_basic(self, method): - o = tmpdir.ensure('basicremote', dir=1) - tfile = o.join('test_remote.py') + def test_exec(self): + o = tmpdir.ensure('remote', dir=1) + tfile = o.join('test_exec.py') tfile.write(py.code.Source(""" def test_1(): assert 1 == 0 """)) - config, args = py.test.Config.parse(['--looponfailing']) - assert config._remote + print py.std.sys.executable + config, args = py.test.Config.parse( + ['--exec=' + py.std.sys.executable, + str(o)]) + assert config.option._remote cls = config.getsessionclass() - out = py.std.Queue.Queue() - session = cls(config, out.put) - pool = WorkerPool() - reply = pool.dispatch(session.main, [o]) - session.main([o]) - out = self.file.getvalue() - #print out - l = [] - self.session = py.test.TerminalSession(config, file=self.file) - #print >>f, "session %r is setup for %r" %(self.session, method) - #f.flush() - #print "session is setup", self.session + out = [] # out = py.std.Queue.Queue() + session = cls(config, out.append) + session.main([o]) + for s in out: + if s.find('1 failed') != -1: + break + else: + py.test.fail("did not see test_1 failure") From hpk at codespeak.net Fri Apr 15 19:25:43 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 19:25:43 +0200 (CEST) Subject: [py-svn] r10697 - py/branch/py-collect/execnet Message-ID: <20050415172543.870A027B7A@code1.codespeak.net> Author: hpk Date: Fri Apr 15 19:25:43 2005 New Revision: 10697 Modified: py/branch/py-collect/execnet/gateway.py Log: remove unused imports Modified: py/branch/py-collect/execnet/gateway.py ============================================================================== --- py/branch/py-collect/execnet/gateway.py (original) +++ py/branch/py-collect/execnet/gateway.py Fri Apr 15 19:25:43 2005 @@ -1,10 +1,9 @@ import sys import os -import thread, threading +import threading import Queue import traceback import atexit -import time # XXX the following line should not be here From hpk at codespeak.net Fri Apr 15 19:26:28 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 19:26:28 +0200 (CEST) Subject: [py-svn] r10699 - in py/branch/py-collect/test: terminal testing Message-ID: <20050415172628.AD81827B7A@code1.codespeak.net> Author: hpk Date: Fri Apr 15 19:26:28 2005 New Revision: 10699 Modified: py/branch/py-collect/test/terminal/terminal.py py/branch/py-collect/test/testing/test_session.py Log: fix --looponfailing (including a not completely nice test) Modified: py/branch/py-collect/test/terminal/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal/terminal.py (original) +++ py/branch/py-collect/test/terminal/terminal.py Fri Apr 15 19:26:28 2005 @@ -17,9 +17,9 @@ def main(self, args): if self.config.option._remote: from py.__impl__.test.terminal import remote - remote.main(self.config, self._file, self.config._origargs) + return remote.main(self.config, self._file, self.config._origargs) else: - super(TerminalSession, self).main(args) + return super(TerminalSession, self).main(args) # --------------------- # PROGRESS information Modified: py/branch/py-collect/test/testing/test_session.py ============================================================================== --- py/branch/py-collect/test/testing/test_session.py (original) +++ py/branch/py-collect/test/testing/test_session.py Fri Apr 15 19:26:28 2005 @@ -167,3 +167,30 @@ break else: py.test.fail("did not see test_1 failure") + + def test_looponfailing(self): + o = tmpdir.ensure('looponfailing', dir=1) + tfile = o.join('test_looponfailing.py') + tfile.write(py.code.Source(""" + def test_1(): + assert 1 == 0 + """)) + print py.std.sys.executable + config, args = py.test.Config.parse(['--looponfailing', str(o)]) + assert config.option._remote + cls = config.getsessionclass() + out = py.std.Queue.Queue() + session = cls(config, out.put) + pool = py._thread.WorkerPool() + reply = pool.dispatch(session.main, [str(o)]) + while 1: + s = out.get(timeout=2.0) + if s.find('1 failed') != -1: + break + else: + py.test.fail("did not see test_1 failure") + # XXX we would like to have a cleaner way to finish + try: + reply.get(timeout=0.5) + except IOError, e: + assert str(e).lower().find('timeout') != -1 From hpk at codespeak.net Fri Apr 15 19:37:48 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 19:37:48 +0200 (CEST) Subject: [py-svn] r10701 - in py/branch/py-collect/test: . terminal testing Message-ID: <20050415173748.8CAF427B7A@code1.codespeak.net> Author: hpk Date: Fri Apr 15 19:37:48 2005 New Revision: 10701 Modified: py/branch/py-collect/test/session.py py/branch/py-collect/test/terminal/remote.py py/branch/py-collect/test/testing/test_session.py Log: fix and test determination of interal getrootdir() some more. Modified: py/branch/py-collect/test/session.py ============================================================================== --- py/branch/py-collect/test/session.py (original) +++ py/branch/py-collect/test/session.py Fri Apr 15 19:37:48 2005 @@ -57,7 +57,8 @@ raise SystemExit, "received external close signal" res = capture = None - if not self.config.option.nocapture and isinstance(colitem, py.test.Item): + # XXX capturing output even for collectors? + if not self.config.option.nocapture: # and isinstance(colitem, py.test.Item): capture = SimpleOutErrCapture() needfinish = False try: Modified: py/branch/py-collect/test/terminal/remote.py ============================================================================== --- py/branch/py-collect/test/terminal/remote.py (original) +++ py/branch/py-collect/test/terminal/remote.py Fri Apr 15 19:37:48 2005 @@ -109,7 +109,10 @@ break general = x return general - return reduce(generalize, tops) + p =reduce(generalize, tops) + if p.check(file=1): + p = p.dirpath() + return p def main(config, file, args): """ testing process and output happens at a remote place. """ Modified: py/branch/py-collect/test/testing/test_session.py ============================================================================== --- py/branch/py-collect/test/testing/test_session.py (original) +++ py/branch/py-collect/test/testing/test_session.py Fri Apr 15 19:37:48 2005 @@ -144,7 +144,24 @@ names = item.listnames() assert names == ['ordertest', 'test_orderofexecution.py', 'Testmygroup', '()', 'test_4'] -class TestRemoteSession: +from py.__impl__.test.terminal.remote import getrootdir +class TestRemote: + def test_rootdir_is_package(self): + d = tmpdir.ensure('rootdirtest1', dir=1) + d.ensure('__init__.py') + x1 = d.ensure('subdir', '__init__.py') + x2 = d.ensure('subdir2', '__init__.py') + x3 = d.ensure('subdir3', 'noinit', '__init__.py') + assert getrootdir([x1]) == d + assert getrootdir([x2]) == d + assert getrootdir([x1,x2]) == d + assert getrootdir([x3,x2]) == d + assert getrootdir([x2,x3]) == d + + def test_rootdir_is_not_package(self): + one = tmpdir.ensure('rootdirtest1', 'hello') + rootdir = getrootdir([one]) + assert rootdir == one.dirpath() def test_exec(self): o = tmpdir.ensure('remote', dir=1) From hpk at codespeak.net Fri Apr 15 19:42:43 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 19:42:43 +0200 (CEST) Subject: [py-svn] r10702 - py/branch/py-collect/test Message-ID: <20050415174243.AC2BB27B7A@code1.codespeak.net> Author: hpk Date: Fri Apr 15 19:42:43 2005 New Revision: 10702 Modified: py/branch/py-collect/test/session.py Log: fix cleanup when we receive a closedown from remote Modified: py/branch/py-collect/test/session.py ============================================================================== --- py/branch/py-collect/test/session.py (original) +++ py/branch/py-collect/test/session.py Fri Apr 15 19:42:43 2005 @@ -54,7 +54,7 @@ def runtraced(self, colitem): if self.shouldclose(): - raise SystemExit, "received external close signal" + raise Exit, "received external close signal" res = capture = None # XXX capturing output even for collectors? From hpk at codespeak.net Fri Apr 15 20:00:50 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 20:00:50 +0200 (CEST) Subject: [py-svn] r10705 - in py/branch/py-collect/test: . testing Message-ID: <20050415180050.D321427B7A@code1.codespeak.net> Author: hpk Date: Fri Apr 15 20:00:50 2005 New Revision: 10705 Modified: py/branch/py-collect/test/session.py py/branch/py-collect/test/testing/test_collect.py Log: fixing and extending the test. also turning off stdout/stdin capturing on --pdb Modified: py/branch/py-collect/test/session.py ============================================================================== --- py/branch/py-collect/test/session.py (original) +++ py/branch/py-collect/test/session.py Fri Apr 15 20:00:50 2005 @@ -58,8 +58,9 @@ res = capture = None # XXX capturing output even for collectors? - if not self.config.option.nocapture: # and isinstance(colitem, py.test.Item): - capture = SimpleOutErrCapture() + if not self.config.option.nocapture: + if isinstance(colitem, py.test.Item) or not self.config.option.usepdb: + capture = SimpleOutErrCapture() needfinish = False try: self.start(colitem) Modified: py/branch/py-collect/test/testing/test_collect.py ============================================================================== --- py/branch/py-collect/test/testing/test_collect.py (original) +++ py/branch/py-collect/test/testing/test_collect.py Fri Apr 15 20:00:50 2005 @@ -137,7 +137,7 @@ pass class Directory(py.test.collect.Directory): def filefilter(self, fspath): - return fspath.check(basestarts='check_') + return fspath.check(basestarts='check_', ext='.py') class myfuncmixin: Function = MyFunction def funcnamefilter(self, name): @@ -149,17 +149,20 @@ class Instance(myfuncmixin, py.test.collect.Instance): pass """) - o.ensure('somedir', 'check_something.py').write("""if 1: + checkfile = o.ensure('somedir', 'check_something.py') + checkfile.write("""if 1: def check_func(): assert 42 == 42 class CustomTestClass: def check_method(self): assert 23 == 23 """) - print "tmpdir", tmpdir - print "o", o + from py.__impl__.test.collect import getfscollector - items = list(getfscollector(o).iteritems()) - print items - assert len(items) == 2 - assert items[1].__class__.__name__ == 'MyFunction' + for x in (o, checkfile, checkfile.dirpath()): + print "checking that %s returns custom items" % (x,) + col = getfscollector(x) + items = list(col.iteritems()) + print "col", col + assert len(items) == 2 + assert items[1].__class__.__name__ == 'MyFunction' From hpk at codespeak.net Fri Apr 15 20:49:17 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 20:49:17 +0200 (CEST) Subject: [py-svn] r10710 - py/branch/py-collect/test Message-ID: <20050415184917.A2F2A27B48@code1.codespeak.net> Author: hpk Date: Fri Apr 15 20:49:17 2005 New Revision: 10710 Modified: py/branch/py-collect/test/session.py Log: fail early Modified: py/branch/py-collect/test/session.py ============================================================================== --- py/branch/py-collect/test/session.py (original) +++ py/branch/py-collect/test/session.py Fri Apr 15 20:49:17 2005 @@ -103,6 +103,7 @@ else: for name in seq: obj = colitem.join(name) + assert obj is not None self.runtraced(obj) return res From hpk at codespeak.net Fri Apr 15 21:35:03 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 15 Apr 2005 21:35:03 +0200 (CEST) Subject: [py-svn] r10712 - in py/branch/py-collect/test: . testing Message-ID: <20050415193503.04DA927B75@code1.codespeak.net> Author: hpk Date: Fri Apr 15 21:35:03 2005 New Revision: 10712 Modified: py/branch/py-collect/test/collect.py py/branch/py-collect/test/testing/test_collect.py Log: make lookup of custom Collectors more explicit for filesystem paths. Modified: py/branch/py-collect/test/collect.py ============================================================================== --- py/branch/py-collect/test/collect.py (original) +++ py/branch/py-collect/test/collect.py Fri Apr 15 21:35:03 2005 @@ -45,9 +45,14 @@ raise py.error.ENOENT(fspath) pkgpath = fspath.pypkgpath() if pkgpath is None: - current = Directory(fspath.dirpath()).join(fspath.basename) - current.parent = None + if fspath.check(file=1): + clsname = 'Module' + else: + clsname = 'Directory' + fscol = py.test.Config.getvalue(clsname, fspath) + current = fscol(fspath) else: + Directory = py.test.Config.getvalue('Directory', pkgpath) current = Directory(pkgpath) #print "pkgpath", pkgpath for name in filter(None, fspath.relto(pkgpath).split(fspath.sep)): @@ -211,7 +216,8 @@ if x.check(file=1): return self.Module(x, parent=self) elif x.check(dir=1): - return self.Directory(x, parent=self) + Directory = py.test.Config.getvalue('Directory', x) + return Directory(x, parent=self) class PyCollectorMixin(object): def funcnamefilter(self, name): Modified: py/branch/py-collect/test/testing/test_collect.py ============================================================================== --- py/branch/py-collect/test/testing/test_collect.py (original) +++ py/branch/py-collect/test/testing/test_collect.py Fri Apr 15 21:35:03 2005 @@ -160,9 +160,21 @@ from py.__impl__.test.collect import getfscollector for x in (o, checkfile, checkfile.dirpath()): - print "checking that %s returns custom items" % (x,) + #print "checking that %s returns custom items" % (x,) col = getfscollector(x) items = list(col.iteritems()) - print "col", col + #print "col", col assert len(items) == 2 assert items[1].__class__.__name__ == 'MyFunction' + + # test that running a session works from the directories + old = o.chdir() + try: + config, args = py.test.Config.parse([]) + out = py.std.cStringIO.StringIO() + session = config.getsessionclass()(config, out) + session.main(args) + l = session.getresults(py.test.Item.Passed) + assert len(l) == 2 + finally: + old.chdir() From hpk at codespeak.net Sat Apr 16 13:25:49 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Apr 2005 13:25:49 +0200 (CEST) Subject: [py-svn] r10726 - py/branch/py-collect/code py/branch/py-collect/code/testing pypy/branch/pycollect-dist-pypy/pypy/tool pypy/branch/pycollect-dist-pypy/pypy/tool/test Message-ID: <20050416112549.BEE8D27B64@code1.codespeak.net> Author: hpk Date: Sat Apr 16 13:25:49 2005 New Revision: 10726 Modified: py/branch/py-collect/code/excinfo.py py/branch/py-collect/code/testing/test_excinfo.py pypy/branch/pycollect-dist-pypy/pypy/tool/pytestsupport.py pypy/branch/pycollect-dist-pypy/pypy/tool/test/test_pytestsupport.py Log: add some introspection methods to both py and pypy's ExceptionInfo objects. Modified: py/branch/py-collect/code/excinfo.py ============================================================================== --- py/branch/py-collect/code/excinfo.py (original) +++ py/branch/py-collect/code/excinfo.py Sat Apr 16 13:25:49 2005 @@ -32,6 +32,9 @@ text = text[:-1] return text + def errisinstance(self, exc): + return isinstance(self.value, exc) + def __str__(self): return self.exception_text Modified: py/branch/py-collect/code/testing/test_excinfo.py ============================================================================== --- py/branch/py-collect/code/testing/test_excinfo.py (original) +++ py/branch/py-collect/code/testing/test_excinfo.py Sat Apr 16 13:25:49 2005 @@ -112,3 +112,6 @@ excinfo = py.test.raises(ValueError, h) assert excinfo.exception_text.startswith('ValueError') +def test_excinfo_errisinstance(): + excinfo = py.test.raises(ValueError, h) + assert excinfo.errisinstance(ValueError) Modified: pypy/branch/pycollect-dist-pypy/pypy/tool/pytestsupport.py ============================================================================== --- pypy/branch/pycollect-dist-pypy/pypy/tool/pytestsupport.py (original) +++ pypy/branch/pycollect-dist-pypy/pypy/tool/pytestsupport.py Sat Apr 16 13:25:49 2005 @@ -44,6 +44,21 @@ self.operr = operr self.traceback = AppTraceback(self.operr.application_traceback) + def exconly(self): + return '(application-level) ' + self.operr.errorstr(self.space) + + def errisinstance(self, exc): + clsname = exc.__name__ + try: + w_exc = getattr(self.space, 'w_' + clsname) + except KeyboardInterrupt: + raise + except: + pass + else: + return self.operr.match(self.space, w_exc) + return False + def __str__(self): return '(application-level) ' + self.operr.errorstr(self.space) Modified: pypy/branch/pycollect-dist-pypy/pypy/tool/test/test_pytestsupport.py ============================================================================== --- pypy/branch/pycollect-dist-pypy/pypy/tool/test/test_pytestsupport.py (original) +++ pypy/branch/pycollect-dist-pypy/pypy/tool/test/test_pytestsupport.py Sat Apr 16 13:25:49 2005 @@ -5,7 +5,7 @@ from pypy.interpreter.argument import Arguments from pypy.interpreter.pycode import PyCode from pypy.interpreter.pyframe import PyFrame -from pypy.tool.pytestsupport import AppFrame, build_pytest_assertion +from pypy.tool.pytestsupport import AppFrame, build_pytest_assertion, AppExceptionInfo def somefunc(x): @@ -53,4 +53,18 @@ assert e.msg == "Failed" +def test_appexecinfo(space): + try: + space.appexec([], "(): raise ValueError") + except OperationError, e: + appex = AppExceptionInfo(space, e) + else: + py.test.fail("did not raise!") + assert appex.exconly().find('ValueError') != -1 + assert appex.errisinstance(ValueError) + assert not appex.errisinstance(RuntimeError) + class A: + pass + assert not appex.errisinstance(A) + From hpk at codespeak.net Sat Apr 16 13:31:55 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Apr 2005 13:31:55 +0200 (CEST) Subject: [py-svn] r10727 - in py/branch/py-collect/test: terminal testing Message-ID: <20050416113155.693FF27B5D@code1.codespeak.net> Author: hpk Date: Sat Apr 16 13:31:55 2005 New Revision: 10727 Modified: py/branch/py-collect/test/terminal/terminal.py py/branch/py-collect/test/testing/test_session.py Log: - some clean up terminal reporting - add tests for verbosity options Modified: py/branch/py-collect/test/terminal/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal/terminal.py (original) +++ py/branch/py-collect/test/terminal/terminal.py Sat Apr 16 13:31:55 2005 @@ -43,32 +43,13 @@ colitem.start = py.std.time.time() def start_Module(self, colitem): - try: - numunits = len(list(colitem.iteritems())) - except TypeError: - numunits = -1 - - colitem.numunits = numunits - if numunits > 0: - fspath = colitem.fspath - if self.config.option.verbose == 0: - # old representation - #parts = fspath.parts() - #basename = parts.pop().basename - #while parts and parts[-1].basename in ('testing', 'test'): - # parts.pop() - #base = parts[-1].basename - #if len(base) < 13: - # base = base + "_" * (13-len(base)) - #abbrev_fn = base + "_" + basename - abbrev_fn = "/".join(colitem.listnames()) - self.out.write('%s[%d] ' % (abbrev_fn, numunits)) - elif self.config.option.verbose > 0: - #curdir = py.path.local() - #if fspath.check(local=True, relto=curdir): - # fspath = fspath.relto(curdir) - self.out.line() - self.out.line("+ testmodule: %s" % fspath) + if self.config.option.verbose == 0: + colitem.numitems = -1 # colitem.getnumitems() + abbrev_fn = "/".join(colitem.listnames()) + self.out.write('%s[%d] ' % (abbrev_fn, colitem.numitems)) + else: + self.out.line() + self.out.line("+ testmodule: %s" % colitem.fspath) def start_Item(self, colitem): if self.config.option.verbose >= 1: @@ -92,10 +73,7 @@ print "dispatching to ppdb", colitem self.repr_failure(colitem, result) import pdb - self.out.rewrite( - '\n%s: %s\n' - % (result.excinfo.type.__name__, - result.excinfo.value)) + self.out.rewrite('\n%s: %s\n' % (result.excinfo.exconly(),)) pdb.post_mortem(result.excinfo._excinfo[2]) if isinstance(result, (colitem.Failed,)): if self.config.option.exitfirst: @@ -103,7 +81,7 @@ if result is None or not isinstance(colitem, py.test.Item): if isinstance(colitem, py.test.collect.Module) \ and self.config.option.verbose == 0 \ - and colitem.numunits > 0: + and colitem.numitems != 0: self.out.line() return else: @@ -260,9 +238,8 @@ self.out.sep("_") else: self.out.sep("_ ") - if not self.config.option.nomagic and \ - isinstance(excinfo.value, RuntimeError) and \ - self.isrecursive(entry, recursioncache): + if not self.config.option.nomagic and excinfo.errisinstance(RuntimeError) \ + and self.isrecursive(entry, recursioncache): self.out.line("Recursion detected (same locals & position)") self.out.sep("!") break Modified: py/branch/py-collect/test/testing/test_session.py ============================================================================== --- py/branch/py-collect/test/testing/test_session.py (original) +++ py/branch/py-collect/test/testing/test_session.py Sat Apr 16 13:31:55 2005 @@ -14,6 +14,24 @@ l = session.getresults(py.test.Item.Passed) assert not l + def test_simple_verbose(self): + config, args = py.test.Config.parse(['--verbose']) + session = config.getsessionclass()(config, py.std.sys.stdout) + session.main([datadir / 'filetest.py']) + l = session.getresults(py.test.Item.Failed) + assert len(l) == 2 + l = session.getresults(py.test.Item.Passed) + assert not l + + def test_simple_verbose_verbose(self): + config, args = py.test.Config.parse(['-v', '-v']) + session = config.getsessionclass()(config, py.std.sys.stdout) + session.main([datadir / 'filetest.py']) + l = session.getresults(py.test.Item.Failed) + assert len(l) == 2 + l = session.getresults(py.test.Item.Passed) + assert not l + def test_session_parsing(self): config, args = py.test.Config.parse(['--session=terminal']) assert config.getsessionclass() is py.test.TerminalSession @@ -21,6 +39,7 @@ assert config.getsessionclass() is py.test.TkinterSession config, args = py.test.Config.parse(['--tkinter']) assert config.getsessionclass() is py.test.TkinterSession + #f = open('/tmp/logfile', 'wa') class TestTerminalSession: @@ -48,6 +67,7 @@ assert len(l) == 2 assert out.find('2 failed') != -1 + def test_exit_first_problem(self): session = self.session session.config.option.exitfirst = True From hpk at codespeak.net Sat Apr 16 14:28:09 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Apr 2005 14:28:09 +0200 (CEST) Subject: [py-svn] r10728 - in py/branch/py-collect/test: . terminal testing Message-ID: <20050416122809.49F9427B5E@code1.codespeak.net> Author: hpk Date: Sat Apr 16 14:28:09 2005 New Revision: 10728 Modified: py/branch/py-collect/test/collect.py py/branch/py-collect/test/item.py py/branch/py-collect/test/terminal/terminal.py py/branch/py-collect/test/testing/test_collect.py Log: added a sane way to try to determine the number of from a flattened collectors. In case of failures, -1 is returned. This gets rid of some annoying exceptions. Basically you should not visit a collector tree (via colitem.run()) without being prepared for failures. Modified: py/branch/py-collect/test/collect.py ============================================================================== --- py/branch/py-collect/test/collect.py (original) +++ py/branch/py-collect/test/collect.py Sat Apr 16 14:28:09 2005 @@ -80,6 +80,8 @@ "given 'msg' argument is not a string" ) self.__dict__.update(kwargs) def __repr__(self): + return getattr(self, 'msg', "%s instance" % (self.__class__,)) + def __str__(self): return getattr(self, 'msg', object.__repr__(self)) class Passed(Outcome): pass class Failed(Outcome): pass @@ -161,14 +163,30 @@ newl.append(x.name) return ".".join(newl) - def iteritems(self): - """ yield all Items from flattended collector instance. """ - if isinstance(self, py.test.Item): - yield self - else: - for x in self.run(): - for y in self.join(x).iteritems(): - yield y + def getnum(self, stopitems=()): + """ return the number of 'stopitems' instances on this collector. + if any collector down the chain fails, -1 is returned. + """ + count = 0 + for x in self.tryiter(stopitems=stopitems): + if isinstance(x, py.test.Item.Failed): + return -1 + count += 1 + return count + + def tryiter(self, stopitems=()): + """ yield stop items from flattening the collectors. """ + if isinstance(self, stopitems): + yield self + else: + try: + for x in self.run(): + for y in self.join(x).tryiter(stopitems=stopitems): + yield y + except KeyboardInterrupt: + raise + except: + yield py.test.Item.Failed(excinfo=py.code.ExceptionInfo()) def fspath(): def fget(self): Modified: py/branch/py-collect/test/item.py ============================================================================== --- py/branch/py-collect/test/item.py (original) +++ py/branch/py-collect/test/item.py Sat Apr 16 14:28:09 2005 @@ -63,12 +63,6 @@ """ target(*args) - def reprcall(self): - """ return a string representing a call to the underlying - test function. - """ - return "%s%r" % (self.getmodpath(), self.args) - def setup(self): if getattr(self.obj, 'im_self', None): name = 'setup_method' Modified: py/branch/py-collect/test/terminal/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal/terminal.py (original) +++ py/branch/py-collect/test/terminal/terminal.py Sat Apr 16 14:28:09 2005 @@ -44,7 +44,7 @@ def start_Module(self, colitem): if self.config.option.verbose == 0: - colitem.numitems = -1 # colitem.getnumitems() + colitem.numitems = colitem.getnum(py.test.Item) abbrev_fn = "/".join(colitem.listnames()) self.out.write('%s[%d] ' % (abbrev_fn, colitem.numitems)) else: @@ -56,8 +56,7 @@ if isinstance(colitem, py.test.Item): realpath, lineno = colitem.getpathlineno() location = "%s:%d" % (realpath.basename, lineno+1) - self.out.rewrite("%-20s %s " % ( - location, colitem.reprcall())) + self.out.rewrite("%-20s %s " % (location, colitem.getmodpath())) def finish(self, colitem, result): end = now() @@ -73,7 +72,7 @@ print "dispatching to ppdb", colitem self.repr_failure(colitem, result) import pdb - self.out.rewrite('\n%s: %s\n' % (result.excinfo.exconly(),)) + self.out.rewrite('\n%s\n' % (result.excinfo.exconly(),)) pdb.post_mortem(result.excinfo._excinfo[2]) if isinstance(result, (colitem.Failed,)): if self.config.option.exitfirst: Modified: py/branch/py-collect/test/testing/test_collect.py ============================================================================== --- py/branch/py-collect/test/testing/test_collect.py (original) +++ py/branch/py-collect/test/testing/test_collect.py Sat Apr 16 14:28:09 2005 @@ -24,6 +24,22 @@ assert cur.parent == col.parent assert cur.fspath == cur.fspath +def test_tryiter(): + fn = datadir / 'filetest.py' + col = py.test.collect.Module(fn) + for item in col.tryiter(py.test.Item): + assert not isinstance(item, py.test.Item.Failed) + assert item.fspath == fn + +def test_tryiter_syntaxerror(): + modpath = datadir.join('syntax_error.py') + col = py.test.collect.Module(modpath) + l = list(col.tryiter(py.test.Item)) + assert len(l) == 1 + for x in l: + assert isinstance(x, py.test.Item.Failed) + assert x.excinfo.errisinstance(SyntaxError) + def test_finds_tests(): fn = datadir / 'filetest.py' col = py.test.collect.Module(fn) @@ -32,10 +48,16 @@ assert l[0] == 'test_one' assert l[1] == 'TestClass' - items = list(col.iteritems()) - assert len(items) == 2 - for item in items: - assert item.fspath == fn +def test_getnum(): + fn = datadir / 'filetest.py' + col = py.test.collect.Module(fn) + assert col.getnum(py.test.Item) == 2 + +def test_getnum_syntaxerror(): + fn = datadir / 'syntax_error.py' + col = py.test.collect.Module(fn) + assert col.getnum(py.test.Item) == -1 + assert col.getnum() == -1 def test_failing_import_directory(): class MyDirectory(py.test.collect.Directory): @@ -162,10 +184,8 @@ for x in (o, checkfile, checkfile.dirpath()): #print "checking that %s returns custom items" % (x,) col = getfscollector(x) - items = list(col.iteritems()) - #print "col", col - assert len(items) == 2 - assert items[1].__class__.__name__ == 'MyFunction' + assert col.getnum(py.test.Item) == 2 + #assert items[1].__class__.__name__ == 'MyFunction' # test that running a session works from the directories old = o.chdir() From hpk at codespeak.net Sat Apr 16 14:41:56 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Apr 2005 14:41:56 +0200 (CEST) Subject: [py-svn] r10729 - in py/branch/py-collect/test: terminal testing Message-ID: <20050416124156.4E32927B5E@code1.codespeak.net> Author: hpk Date: Sat Apr 16 14:41:56 2005 New Revision: 10729 Modified: py/branch/py-collect/test/terminal/terminal.py py/branch/py-collect/test/testing/test_session.py Log: at least show something sensible on syntax errors Modified: py/branch/py-collect/test/terminal/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal/terminal.py (original) +++ py/branch/py-collect/test/terminal/terminal.py Sat Apr 16 14:41:56 2005 @@ -320,7 +320,8 @@ def repr_failure_explanation(self, excinfo, indent): info = None info = getattr(getattr(excinfo, 'value', ''), 'msg', '') - if not info and not self.config.option.nomagic: + if not info and not self.config.option.nomagic \ + and not excinfo.errisinstance(SyntaxError): try: info = excinfo.traceback[-1].reinterpret() # very detailed info except KeyboardInterrupt: @@ -332,10 +333,17 @@ else: self.out.line("[reinterpretation failed, increase " "verbosity to see details]") - indent = " " * indent - if not info: + #elif excinfo.errisinstance(SyntaxError): + #newex = SyntaxError('\n'.join([ + #"".join(self.lines[:ex.lineno]), + #" " * ex.offset + '^', + #"syntax error probably generated here: %s" % filename])) + #newex.offset = ex.offset + #newex.lineno = ex.lineno + #newex.text = ex.text + else: info = str(excinfo) - + indent = " " * indent lines = info.split('\n') self.out.line('~' + indent[:-1] + lines.pop(0)) for x in lines: Modified: py/branch/py-collect/test/testing/test_session.py ============================================================================== --- py/branch/py-collect/test/testing/test_session.py (original) +++ py/branch/py-collect/test/testing/test_session.py Sat Apr 16 14:41:56 2005 @@ -67,6 +67,14 @@ assert len(l) == 2 assert out.find('2 failed') != -1 + def test_syntax_error_module(self): + session = self.session + session.main([str(datadir / 'syntax_error.py')]) + l = session.getresults(py.test.Item.Failed) + assert len(l) == 1 + out = self.file.getvalue() + assert out.find(str('syntax_error.py')) != -1 + assert out.find(str('not python')) != -1 def test_exit_first_problem(self): session = self.session From hpk at codespeak.net Sat Apr 16 18:30:27 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Apr 2005 18:30:27 +0200 (CEST) Subject: [py-svn] r10734 - py/branch/py-collect/test Message-ID: <20050416163027.19F2A27B5E@code1.codespeak.net> Author: hpk Date: Sat Apr 16 18:30:26 2005 New Revision: 10734 Modified: py/branch/py-collect/test/item.py Log: hide some details in tracebacks Modified: py/branch/py-collect/test/item.py ============================================================================== --- py/branch/py-collect/test/item.py (original) +++ py/branch/py-collect/test/item.py Sat Apr 16 18:30:26 2005 @@ -88,6 +88,7 @@ # def skip(msg="unknown reason", tbindex=-2): """ skip with the given Message. """ + __tracebackhide__ = True raise py.test.Item.Skipped(msg=msg, tbindex=tbindex) def skip_on_error(func, *args, **kwargs): @@ -101,5 +102,6 @@ def fail(msg="unknown failure"): """ fail with the given Message. """ + __tracebackhide__ = True raise py.test.Item.Failed(msg=msg, tbindex=-2) From hpk at codespeak.net Sat Apr 16 18:32:22 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Apr 2005 18:32:22 +0200 (CEST) Subject: [py-svn] r10735 - in py/branch/py-collect: code code/testing magic/testing test/terminal Message-ID: <20050416163222.8DFB727B5E@code1.codespeak.net> Author: hpk Date: Sat Apr 16 18:32:22 2005 New Revision: 10735 Modified: py/branch/py-collect/code/excinfo.py py/branch/py-collect/code/testing/test_excinfo.py py/branch/py-collect/magic/testing/test_assertion.py py/branch/py-collect/magic/testing/test_invoke.py py/branch/py-collect/test/terminal/terminal.py Log: sanitize failure explanations and exception representation Modified: py/branch/py-collect/code/excinfo.py ============================================================================== --- py/branch/py-collect/code/excinfo.py (original) +++ py/branch/py-collect/code/excinfo.py Sat Apr 16 18:32:22 2005 @@ -6,35 +6,34 @@ """ wraps sys.exc_info() objects and offers help for navigating the traceback. """ + _striptext = '' def __init__(self, tup=None, exprinfo=None): # NB. all attributes are private! Subclasses or other # ExceptionInfo-like classes may have different attributes. - strip = '' if tup is None: tup = sys.exc_info() if exprinfo is None and isinstance(tup[1], py.magic.AssertionError): exprinfo = tup[1].msg if exprinfo and exprinfo.startswith('assert '): - strip = 'AssertionError: ' + self._striptext = 'AssertionError: ' self._excinfo = tup self.type, self.value, tb = self._excinfo self.traceback = py.code.Traceback(tb) - # get the text representation of the exception - text = self.exconly() - if text.startswith(strip): - text = text[len(strip):] - self.exception_text = text - def exconly(self): + def exconly(self, tryshort=False): lines = py.std.traceback.format_exception_only(self.type, self.value) text = ''.join(lines) if text.endswith('\n'): text = text[:-1] + if tryshort: + if text.startswith(self._striptext): + text = text[len(self._striptext):] return text def errisinstance(self, exc): return isinstance(self.value, exc) def __str__(self): - return self.exception_text + # XXX wrong str + return self.exconly() Modified: py/branch/py-collect/code/testing/test_excinfo.py ============================================================================== --- py/branch/py-collect/code/testing/test_excinfo.py (original) +++ py/branch/py-collect/code/testing/test_excinfo.py Sat Apr 16 18:32:22 2005 @@ -108,9 +108,9 @@ # names = [x.frame.code.name for x in entries] # assert names == ['h','g'] -def test_excinfo_exception_text(): +def test_excinfo_exconly(): excinfo = py.test.raises(ValueError, h) - assert excinfo.exception_text.startswith('ValueError') + assert excinfo.exconly().startswith('ValueError') def test_excinfo_errisinstance(): excinfo = py.test.raises(ValueError, h) Modified: py/branch/py-collect/magic/testing/test_assertion.py ============================================================================== --- py/branch/py-collect/magic/testing/test_assertion.py (original) +++ py/branch/py-collect/magic/testing/test_assertion.py Sat Apr 16 18:32:22 2005 @@ -26,7 +26,7 @@ finally: i = 42 """) - s = excinfo.exception_text + s = excinfo.exconly() assert s.find("takes no argument") != -1 #def g(): Modified: py/branch/py-collect/magic/testing/test_invoke.py ============================================================================== --- py/branch/py-collect/magic/testing/test_invoke.py (original) +++ py/branch/py-collect/magic/testing/test_invoke.py Sat Apr 16 18:32:22 2005 @@ -4,7 +4,7 @@ def check_assertion(): excinfo = py.test.raises(AssertionError, "assert 1 == 2") - assert excinfo.exception_text == "assert 1 == 2" + assert excinfo.exconly(tryshort=True) == "assert 1 == 2" def test_invoke_assertion(): py.magic.invoke(assertion=True) Modified: py/branch/py-collect/test/terminal/terminal.py ============================================================================== --- py/branch/py-collect/test/terminal/terminal.py (original) +++ py/branch/py-collect/test/terminal/terminal.py Sat Apr 16 18:32:22 2005 @@ -318,36 +318,40 @@ traceback[:] = l def repr_failure_explanation(self, excinfo, indent): - info = None - info = getattr(getattr(excinfo, 'value', ''), 'msg', '') - if not info and not self.config.option.nomagic \ - and not excinfo.errisinstance(SyntaxError): - try: - info = excinfo.traceback[-1].reinterpret() # very detailed info - except KeyboardInterrupt: - raise - except: - if self.config.option.verbose > 1: - self.out.line("[reinterpretation traceback]") - py.std.traceback.print_exc(file=py.std.sys.stdout) - else: - self.out.line("[reinterpretation failed, increase " - "verbosity to see details]") - #elif excinfo.errisinstance(SyntaxError): - #newex = SyntaxError('\n'.join([ - #"".join(self.lines[:ex.lineno]), - #" " * ex.offset + '^', - #"syntax error probably generated here: %s" % filename])) - #newex.offset = ex.offset - #newex.lineno = ex.lineno - #newex.text = ex.text - else: - info = str(excinfo) + indent = " " * indent - lines = info.split('\n') - self.out.line('~' + indent[:-1] + lines.pop(0)) + # get the real exception information out + lines = excinfo.exconly(tryshort=True).split('\n') + self.out.line('>' + indent[:-1] + lines.pop(0)) for x in lines: self.out.line(indent + x) + return + + # XXX reinstate the following with a --magic option? + # the following line gets user-supplied messages (e.g. + # for "assert 0, 'custom message'") + msg = getattr(getattr(excinfo, 'value', ''), 'msg', '') + info = None + if not msg: + special = excinfo.errisinstance((SyntaxError, SystemExit, KeyboardInterrupt)) + if not self.config.option.nomagic and not special: + try: + info = excinfo.traceback[-1].reinterpret() # very detailed info + except KeyboardInterrupt: + raise + except: + if self.config.option.verbose >= 1: + self.out.line("[reinterpretation traceback]") + py.std.traceback.print_exc(file=py.std.sys.stdout) + else: + self.out.line("[reinterpretation failed, increase " + "verbosity to see details]") + # print reinterpreted info if any + if info: + lines = info.split('\n') + self.out.line('>' + indent[:-1] + lines.pop(0)) + for x in lines: + self.out.line(indent + x) def repr_out_err(self, res): for name in 'out', 'err': From hpk at codespeak.net Sat Apr 16 19:03:50 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Apr 2005 19:03:50 +0200 (CEST) Subject: [py-svn] r10737 - py/branch/py-collect/documentation Message-ID: <20050416170350.B24D127B5E@code1.codespeak.net> Author: hpk Date: Sat Apr 16 19:03:50 2005 New Revision: 10737 Modified: py/branch/py-collect/documentation/rest_test.py Log: generalize Modified: py/branch/py-collect/documentation/rest_test.py ============================================================================== --- py/branch/py-collect/documentation/rest_test.py (original) +++ py/branch/py-collect/documentation/rest_test.py Sat Apr 16 19:03:50 2005 @@ -3,8 +3,7 @@ import py from py.__impl__.misc import rest -pydir = py.magic.autopath(vars(py)).dirpath() -docdir = py.path.svnwc(pydir.join('documentation')) +docdir = py.path.svnwc(py.magic.autopath().dirpath()) def restcheck(path): try: From hpk at codespeak.net Sat Apr 16 20:23:27 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Apr 2005 20:23:27 +0200 (CEST) Subject: [py-svn] r10740 - py/dist/py/code Message-ID: <20050416182327.19E7A27B5E@code1.codespeak.net> Author: hpk Date: Sat Apr 16 20:23:26 2005 New Revision: 10740 Modified: py/dist/py/code/traceback.py (props changed) Log: don't look From hpk at codespeak.net Sat Apr 16 20:36:09 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Apr 2005 20:36:09 +0200 (CEST) Subject: [py-svn] r10741 - in py/branch/py-collect: . bin c-extension c-extension/greenlet code code/testing documentation execnet magic magic/testing misc misc/testing path/local path/svn path/svn/testing process tool xmlobj xmlobj/testing Message-ID: <20050416183609.4F02E27B5E@code1.codespeak.net> Author: hpk Date: Sat Apr 16 20:36:08 2005 New Revision: 10741 Added: py/branch/py-collect/bin/py.countloc (props changed) - copied unchanged from r10740, py/dist/py/bin/py.countloc py/branch/py-collect/code/testing/test_code.py (props changed) - copied unchanged from r10740, py/dist/py/code/testing/test_code.py py/branch/py-collect/tool/ - copied from r10740, py/dist/py/tool/ py/branch/py-collect/tool/__init__.py py/branch/py-collect/tool/utestconvert.py - copied unchanged from r10740, py/dist/py/tool/utestconvert.py Modified: py/branch/py-collect/__init__.py py/branch/py-collect/bin/_findpy.py py/branch/py-collect/c-extension/ (props changed) py/branch/py-collect/c-extension/greenlet/greenlet.c py/branch/py-collect/c-extension/greenlet/greenlet.h py/branch/py-collect/c-extension/greenlet/test_remote.py (props changed) py/branch/py-collect/code/source.py py/branch/py-collect/code/testing/test_source.py py/branch/py-collect/documentation/execnet.txt py/branch/py-collect/documentation/test.txt py/branch/py-collect/execnet/channel.py py/branch/py-collect/magic/assertion.py py/branch/py-collect/magic/invoke.py py/branch/py-collect/magic/testing/test_assertion.py py/branch/py-collect/magic/testing/test_invoke.py py/branch/py-collect/misc/error.py py/branch/py-collect/misc/testing/test_initpkg.py py/branch/py-collect/path/local/local.py py/branch/py-collect/path/svn/svncommon.py py/branch/py-collect/path/svn/testing/test_urlcommand.py py/branch/py-collect/path/svn/testing/test_wccommand.py py/branch/py-collect/path/svn/wccommand.py py/branch/py-collect/process/cmdexec.py py/branch/py-collect/xmlobj/html.py py/branch/py-collect/xmlobj/testing/test_html.py py/branch/py-collect/xmlobj/visit.py Log: remerge the trunk into the branch ... Modified: py/branch/py-collect/__init__.py ============================================================================== --- py/branch/py-collect/__init__.py (original) +++ py/branch/py-collect/__init__.py Sat Apr 16 20:36:08 2005 @@ -56,7 +56,7 @@ # generalized inspection API 'code.compile' : ('./code/source.py', 'compile_'), 'code.Source' : ('./code/source.py', 'Source'), - 'code.Code' : ('./code/frame.py', 'Code'), + 'code.Code' : ('./code/code.py', 'Code'), 'code.Frame' : ('./code/frame.py', 'Frame'), 'code.ExceptionInfo' : ('./code/excinfo.py', 'ExceptionInfo'), 'code.Traceback' : ('./code/traceback2.py', 'Traceback'), Modified: py/branch/py-collect/bin/_findpy.py ============================================================================== --- py/branch/py-collect/bin/_findpy.py (original) +++ py/branch/py-collect/bin/_findpy.py Sat Apr 16 20:36:08 2005 @@ -27,6 +27,6 @@ if not searchpy(abspath(os.curdir)): if not searchpy(opd(abspath(sys.argv[0]))): if not searchpy(opd(__file__)): - raise SystemExit, "Could not find 'py' package!" + pass # let's hope it is just on sys.path import py Modified: py/branch/py-collect/c-extension/greenlet/greenlet.c ============================================================================== --- py/branch/py-collect/c-extension/greenlet/greenlet.c (original) +++ py/branch/py-collect/c-extension/greenlet/greenlet.c Sat Apr 16 20:36:08 2005 @@ -259,10 +259,10 @@ - ts_passaround: NULL if PyErr_Occurred(), else a tuple of args sent to ts_target (holds a reference) */ - int err, recursion_depth; + int err; { /* save state */ PyThreadState* tstate = PyThreadState_GET(); - recursion_depth = tstate->recursion_depth; + ts_current->recursion_depth = tstate->recursion_depth; ts_current->top_frame = tstate->frame; } ts_origin = ts_current; @@ -273,7 +273,7 @@ } else { PyThreadState* tstate = PyThreadState_GET(); - tstate->recursion_depth = recursion_depth; + tstate->recursion_depth = ts_target->recursion_depth; tstate->frame = ts_target->top_frame; ts_target->top_frame = NULL; ts_current = ts_target; @@ -346,6 +346,8 @@ ts_target->stack_start = NULL; ts_target->stack_stop = (char*) mark; ts_target->stack_prev = ts_current; + ts_target->top_frame = NULL; + ts_target->recursion_depth = PyThreadState_GET()->recursion_depth; err = _PyGreen_switchstack(); /* returns twice! The 1st time with err=1: we are in the new greenlet Modified: py/branch/py-collect/c-extension/greenlet/greenlet.h ============================================================================== --- py/branch/py-collect/c-extension/greenlet/greenlet.h (original) +++ py/branch/py-collect/c-extension/greenlet/greenlet.h Sat Apr 16 20:36:08 2005 @@ -19,6 +19,7 @@ struct _greenlet* parent; PyObject* run_info; struct _frame* top_frame; + int recursion_depth; } PyGreenlet; extern PyTypeObject PyGreen_Type; Modified: py/branch/py-collect/code/source.py ============================================================================== --- py/branch/py-collect/code/source.py (original) +++ py/branch/py-collect/code/source.py Sat Apr 16 20:36:08 2005 @@ -1,6 +1,8 @@ from __future__ import generators import sys import inspect +import py +cpy_compile = compile # DON'T IMPORT PY HERE @@ -13,18 +15,31 @@ de = kwargs.get('deindent', True) rstrip = kwargs.get('rstrip', True) for part in parts: + if not part: + partlines = [] if isinstance(part, Source): partlines = part.lines - elif isinstance(part, str): - if rstrip: - part = part.rstrip() + elif isinstance(part, (unicode, str)): partlines = part.split('\n') + if rstrip: + while partlines: + if partlines[-1].strip(): + break + partlines.pop() else: partlines = getsource(part, deindent=de).lines if de: partlines = deindent(partlines) lines.extend(partlines) + def __eq__(self, other): + try: + return self.lines == other.lines + except AttributeError: + if isinstance(other, str): + return str(self) == other + return False + def __getitem__(self, key): if isinstance(key, int): return self.lines[key] @@ -43,14 +58,13 @@ """ return new source object with trailing and leading blank lines removed. """ - for start in range(0, len(self)): - if not self.lines[start].isspace(): - break - for end in range(len(self)-1, -1, -1): - if not self.lines[end].isspace(): - break + start, end = 0, len(self) + while start < end and not self.lines[start].strip(): + start += 1 + while end > start and not self.lines[end-1].strip(): + end -= 1 source = Source() - source.lines[:] = self.lines[start:end+1] + source.lines[:] = self.lines[start:end] return source def putaround(self, before='', after='', indent=' ' * 4): @@ -64,6 +78,14 @@ newsource.lines = before.lines + lines + after.lines return newsource + def indent(self, indent=' ' * 4): + """ return a copy of the source object with + all lines indented by the given indent-string. + """ + newsource = Source() + newsource.lines = [(indent+line) for line in self.lines] + return newsource + def getstatement(self, lineno): """ return Source statement which contains the given linenumber (counted from 0). @@ -125,9 +147,6 @@ newsource.lines[:] = deindent(self.lines, offset) return newsource - def __len__(self): - return len(self.lines) - def isparseable(self, deindent=True): """ return True if source is parseable, heuristically deindenting it by default. @@ -148,24 +167,25 @@ return "\n".join(self.lines) def compile(self, filename=None, mode='exec', - flag=generators.compiler_flag): + flag=generators.compiler_flag, dont_inherit=0): """ return compiled code object. if filename is None invent an artificial filename which displays the source/line position of the caller frame. """ - if filename is None: + if not filename or py.path.local(filename).check(file=0): frame = sys._getframe(1) # the caller - filename = '<%s:%d>' % (frame.f_code.co_filename, - frame.f_lineno) - source = str(self)+'\n' + filename = '%s<%s:%d>' % (filename, frame.f_code.co_filename, + frame.f_lineno) + source = "\n".join(self.lines) + '\n' try: - co = compile(source, filename, mode, flag) + co = cpy_compile(source, filename, mode, flag) except SyntaxError, ex: # re-represent syntax errors from parsing python strings - newex = SyntaxError('\n'.join([ - "".join(self.lines[:ex.lineno]), - " " * ex.offset + '^', - "syntax error probably generated here: %s" % filename])) + msglines = self.lines[:ex.lineno] + if ex.offset: + msglines.append(" "*ex.offset + '^') + msglines.append("syntax error probably generated here: %s" % filename) + newex = SyntaxError('\n'.join(msglines)) newex.offset = ex.offset newex.lineno = ex.lineno newex.text = ex.text @@ -173,14 +193,15 @@ else: co_filename = MyStr(filename) co_filename.__source__ = self - return newcode_withfilename(co, co_filename) + return py.code.Code(co).new(rec=1, co_filename=co_filename) + #return newcode_withfilename(co, co_filename) # # public API shortcut functions # def compile_(source, filename=None, mode='exec', flags= - generators.compiler_flag): + generators.compiler_flag, dont_inherit=0): """ compile the given source to a raw code object, which points back to the source code through "co_filename.__source__". All code objects @@ -192,6 +213,13 @@ co = s.compile(filename, mode, flags) return co + +# +# various helper functions +# +class MyStr(str): + """ custom string which allows to add attributes. """ + def getsource(obj, **kwargs): if hasattr(obj, 'func_code'): obj = obj.func_code @@ -200,7 +228,6 @@ try: fullsource = obj.co_filename.__source__ except AttributeError: - import inspect strsrc = inspect.getsource(obj) assert isinstance(strsrc, str) return Source(strsrc, **kwargs) @@ -209,12 +236,6 @@ end = fullsource.getblockend(lineno) return fullsource[lineno:end+1] -# -# various helper functions -# -class MyStr(str): - """ custom string which allows to add attributes. """ - def deindent(lines, offset=None): # XXX maybe use the tokenizer to properly handle multiline # strings etc.pp? @@ -235,36 +256,3 @@ newlines.append(line) return newlines -def newcode(fromcode, **kwargs): - names = [x for x in dir(fromcode) if x[:3] == 'co_'] - for name in names: - if name not in kwargs: - kwargs[name] = getattr(fromcode, name) - import new - return new.code( - kwargs['co_argcount'], - kwargs['co_nlocals'], - kwargs['co_stacksize'], - kwargs['co_flags'], - kwargs['co_code'], - kwargs['co_consts'], - kwargs['co_names'], - kwargs['co_varnames'], - kwargs['co_filename'], - kwargs['co_name'], - kwargs['co_firstlineno'], - kwargs['co_lnotab'], - kwargs['co_freevars'], - kwargs['co_cellvars'], - ) - -def newcode_withfilename(co, co_filename): - newconstlist = [] - cotype = type(co) - for c in co.co_consts: - if isinstance(c, cotype): - c = newcode_withfilename(c, co_filename) - newconstlist.append(c) - return newcode(co, co_consts = tuple(newconstlist), - co_filename = co_filename) - Modified: py/branch/py-collect/code/testing/test_source.py ============================================================================== --- py/branch/py-collect/code/testing/test_source.py (original) +++ py/branch/py-collect/code/testing/test_source.py Sat Apr 16 20:36:08 2005 @@ -19,6 +19,11 @@ """, rstrip=True) assert str(x) == "\n3" +def test_unicode(): + x = Source(unicode("4")) + assert str(x) == "4" + + def test_source_from_function(): source = py.code.Source(test_source_str_function) assert str(source).startswith('def test_source_str_function():') @@ -47,6 +52,26 @@ else: x = 23""" +def test_source_putaround(): + source = Source() + source = source.putaround(""" + if 1: + x=1 + """) + assert str(source).strip() == "if 1:\n x=1" + +def test_source_strips(): + source = Source("") + assert source == Source() + assert str(source) == '' + assert source.strip() == source + +def test_source_strip_multiline(): + source = Source() + source.lines = ["", " hello", " "] + source2 = source.strip() + assert source2.lines == [" hello"] + def test_syntaxerror_rerepresentation(): ex = py.test.raises(SyntaxError, py.code.compile, 'x x') assert ex.value.lineno == 1 @@ -84,41 +109,6 @@ l = [x for x in self.source] assert len(l) == 4 - -class TestCodeHacks: - def test_newcode(self): - from py.__impl__.code.source import newcode - def f(): - pass - co_filename = 'hello' - c = newcode(f.func_code, co_filename=co_filename) - assert c.co_filename is co_filename - - def test_newcode_withfilename(self): - from py.__impl__.code.source import newcode_withfilename - source = py.code.Source(""" - def f(): - def g(): - pass - """) - co = compile(str(source)+'\n', 'nada', 'exec') - obj = 'hello' - newco = newcode_withfilename(co, obj) - def walkcode(co): - for x in co.co_consts: - if isinstance(x, type(co)): - for y in walkcode(x): - yield y - yield co - - names = [] - for code in walkcode(newco): - assert newco.co_filename == obj - assert newco.co_filename is obj - names.append(code.co_name) - assert 'f' in names - assert 'g' in names - class TestSourceParsingAndCompiling: source = Source("""\ def f(x): @@ -132,6 +122,11 @@ exec co assert x == 3 + def test_compile_unicode(self): + co = py.code.compile(unicode('u"\xc3\xa5"', 'utf8'), mode='eval') + val = eval(co) + assert isinstance(val, unicode) + def test_compile_and_getsource_simple(self): co = py.code.compile("x=3") exec co @@ -179,6 +174,9 @@ #print "block", str(block) assert str(stmt).strip().startswith('assert') + def test_offsetless_synerr(self): + py.test.raises(SyntaxError, py.code.compile, "lambda a,a: 0", mode='eval') + def test_getstartingblock_singleline(): class A: def __init__(self, *args): Modified: py/branch/py-collect/documentation/execnet.txt ============================================================================== --- py/branch/py-collect/documentation/execnet.txt (original) +++ py/branch/py-collect/documentation/execnet.txt Sat Apr 16 20:36:08 2005 @@ -45,8 +45,8 @@ apps and especially RMI systems you often have to upgrade your server if you upgrade your client. -What about Security? Are you completly nuts? --------------------------------------------- +What about Security? Are you completely nuts? +--------------------------------------------- We'll talk about that later :-) @@ -144,8 +144,8 @@ f.close() """ # open a gateway to a fresh child process - contentgateway = py.execnet.SSHGateway('codespeak.net', identity) - channel = contentgateway.remote_exec_async(contentserverbootstrap) + contentgateway = py.execnet.SshGateway('codespeak.net') + channel = contentgateway.remote_exec(contentserverbootstrap) for fn in somefilelist: channel.send(fn) Modified: py/branch/py-collect/documentation/test.txt ============================================================================== --- py/branch/py-collect/documentation/test.txt (original) +++ py/branch/py-collect/documentation/test.txt Sat Apr 16 20:36:08 2005 @@ -35,9 +35,10 @@ py.test -This will automatically collect any Python module that starts with -``test_`` in the whole directory hierarchy, starting with the current -directory, and run them. +This will automatically collect and run any Python module whose filenames +start with ``test_`` from the directory and any subdirectories, starting +with the current directory, and run them. Each Python test module is +inspect for test methods starting with ``test_``. Basic Features of ``py.test`` ============================= @@ -81,14 +82,15 @@ automatic collection of tests on all levels ------------------------------------------- -The automated test collection process collects files -from all subdirectories with a leading ``test_`` in their -filename and then every function with a leading ``test_`` -or ``Test`` in the case of classes. The collecting process -can be customized at each level. (see `collection process`_ +The automated test collection process walks the current +directory (or the directory given as a command line argument) +and all its subdirectories and collects python modules with a +leading ``test_`` filename. From each test module every function +with a leading ``test_`` or class with a leading ``Test`` name +is collected. The collecting process can be customized at +directory, module or class level. (see `collection process`_ for some implementation details). - .. _`generative tests`: generative tests: yielding more tests @@ -475,3 +477,61 @@ .. _`getting started`: getting_started.html .. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev + + +Future/Planned Features of py.test +================================== + +Please note that the following descriptions of future features +sound factual although they aren't implemented yet. This +allows easy migration to real documentation later. +Nevertheless, none of the described planned features is +set in stone, yet. In fact, they are open to discussion on +py-dev at codespeak dot net. + +Hey, if you want to suggest new features or command line options +for py.test it would be great if you could do it by providing +documentation for the feature. Welcome to documentation driven +development :-) + +selecting tests by queries/full text search +------------------------------------------- + +You can selectively run tests by specifiying words +on the command line in a google-like way. Example:: + + py.test simple + +will run all tests that are found from the current directory +and that have the word "simple" somewhere in their `test address`_. +``test_simple1`` and ``TestSomething.test_whatever_simpleton`` would both +qualify. If you want to exclude the latter test method you could say:: + + py.test -- simple -simpleton + +Note that the doubledash "--" signals the end of option parsing so +that "-simpleton" will not be misinterpreted as a command line option. + +Interpreting positional arguments as specifying search queries +means that you can only restrict the set of tests. There is no way to +say "run all 'simple' in addition to all 'complex' tests". If this proves +to be a problem we can probably come up with a command line option +that allows to specify multiple queries which all add to the set of +tests-to-consider. + +.. _`test address`: + +the concept of a test address +----------------------------- + +For specifiying tests it is convenient to define the notion +of a *test address*, representable as a filesystem path and a +list of names leading to a test item. If represented as a single +string the path and names are separated by a `/` character, for example: + + ``somedir/somepath.py/TestClass/test_method`` + +Such representations can be used to memoize failing tests +by writing them out in a file or communicating them across +process and computer boundaries. + Modified: py/branch/py-collect/execnet/channel.py ============================================================================== --- py/branch/py-collect/execnet/channel.py (original) +++ py/branch/py-collect/execnet/channel.py Sat Apr 16 20:36:08 2005 @@ -113,11 +113,13 @@ return x def __iter__(self): - while 1: - try: - yield self.receive() - except EOFError: - raise StopIteration + return self + + def next(self): + try: + return self.receive() + except EOFError: + raise StopIteration # # helpers Modified: py/branch/py-collect/magic/assertion.py ============================================================================== --- py/branch/py-collect/magic/assertion.py (original) +++ py/branch/py-collect/magic/assertion.py Sat Apr 16 20:36:08 2005 @@ -7,20 +7,23 @@ class AssertionError(BuiltinAssertionError): def __init__(self, *args): BuiltinAssertionError.__init__(self, *args) - f = sys._getframe(1) - try: - source = py.code.Frame(f).statement - source = str(source).strip() - except py.error.ENOENT: - source = None - # this can also occur during reinterpretation, when the - # co_filename is set to "". - if source: - self.msg = exprinfo.interpret(source, f, should_fail=True) - if not self.args: - self.args = (self.msg,) - else: - self.msg = None + if args: + self.msg = args[0] + else: + f = sys._getframe(1) + try: + source = py.code.Frame(f).statement + source = str(source).strip() + except py.error.ENOENT: + source = None + # this can also occur during reinterpretation, when the + # co_filename is set to "". + if source: + self.msg = exprinfo.interpret(source, f, should_fail=True) + if not self.args: + self.args = (self.msg,) + else: + self.msg = None def invoke(): py.magic.patch(__builtin__, 'AssertionError', AssertionError) Modified: py/branch/py-collect/magic/invoke.py ============================================================================== --- py/branch/py-collect/magic/invoke.py (original) +++ py/branch/py-collect/magic/invoke.py Sat Apr 16 20:36:08 2005 @@ -1,5 +1,7 @@ +import py +import __builtin__ as cpy_builtin -def invoke(assertion=False): +def invoke(assertion=False, compile=False): """ invoke magic, currently you can specify: assertion patches the builtin AssertionError to try to give @@ -10,10 +12,13 @@ if assertion: from py.__impl__.magic import assertion assertion.invoke() + if compile: + py.magic.patch(cpy_builtin, 'compile', py.code.compile ) -def revoke(assertion=False): +def revoke(assertion=False, compile=False): """ revoke previously invoked magic (see invoke()).""" if assertion: from py.__impl__.magic import assertion assertion.revoke() - + if compile: + py.magic.revert(cpy_builtin, 'compile') Modified: py/branch/py-collect/magic/testing/test_assertion.py ============================================================================== --- py/branch/py-collect/magic/testing/test_assertion.py (original) +++ py/branch/py-collect/magic/testing/test_assertion.py Sat Apr 16 20:36:08 2005 @@ -16,6 +16,12 @@ s = str(e) assert s.startswith('assert 2 == 3\n') +def test_assert_with_explicit_message(): + try: + assert f() == 3, "hello" + except AssertionError, e: + assert e.msg == 'hello' + def test_assert_within_finally(): class A: def f(): Modified: py/branch/py-collect/magic/testing/test_invoke.py ============================================================================== --- py/branch/py-collect/magic/testing/test_invoke.py (original) +++ py/branch/py-collect/magic/testing/test_invoke.py Sat Apr 16 20:36:08 2005 @@ -13,3 +13,17 @@ finally: py.magic.revoke(assertion=True) +def test_invoke_compile(): + py.magic.invoke(compile=True) + try: + co = compile("""if 1: + def f(): + return 1 + \n""", '', 'exec') + d = {} + exec co in d + assert py.code.Source(d['f']) + finally: + py.magic.revoke(compile=True) + + Modified: py/branch/py-collect/misc/error.py ============================================================================== --- py/branch/py-collect/misc/error.py (original) +++ py/branch/py-collect/misc/error.py Sat Apr 16 20:36:08 2005 @@ -19,7 +19,8 @@ _winerrnomap = { 3: py.std.errno.ENOENT, - 267: py.std.errno.ENOTDIR, + 267: py.std.errno.ENOTDIR, + 5: py.std.errno.EACCES, # anything better? } ModuleType = type(py) Modified: py/branch/py-collect/misc/testing/test_initpkg.py ============================================================================== --- py/branch/py-collect/misc/testing/test_initpkg.py (original) +++ py/branch/py-collect/misc/testing/test_initpkg.py Sat Apr 16 20:36:08 2005 @@ -44,6 +44,7 @@ base.join('documentation',), base.join('test', 'testing', 'import_test'), base.join('c-extension',), + base.join('magic', 'greenlet.py'), base.join('bin'), base.join('execnet', 'bin'), ) Modified: py/branch/py-collect/path/local/local.py ============================================================================== --- py/branch/py-collect/path/local/local.py (original) +++ py/branch/py-collect/path/local/local.py Sat Apr 16 20:36:08 2005 @@ -59,14 +59,14 @@ path = path.strpath # initialize the path self = object.__new__(cls) - if path is None: + if not path: self.strpath = os.getcwd() - elif not path: + elif isinstance(path, str): + self.strpath = os.path.abspath(os.path.normpath(str(path))) + else: raise ValueError( "can only pass None, Path instances " "or non-empty strings to LocalPath") - else: - self.strpath = os.path.abspath(os.path.normpath(str(path))) assert isinstance(self.strpath, str) return self @@ -422,7 +422,17 @@ if len(header) == 8: magic, timestamp = py.std.struct.unpack('<4si', header) if magic == my_magic and timestamp == my_timestamp: - return py.std.marshal.load(f) + co = py.std.marshal.load(f) + path1 = co.co_filename + path2 = str(self) + if path1 == path2: + return co + try: + if os.path.samefile(path1, path2): + return co + except (OSError, # probably path1 not found + AttributeError): # samefile() not available + pass finally: f.close() except py.error.Error: @@ -458,7 +468,7 @@ stdout, stderr,) return stdout - def sysfind(self, name, checker=None): + def sysfind(cls, name, checker=None): """ return a path object found by looking at the systems underlying PATH specification. If the checker is not None it will be invoked to filter matching paths. If a binary @@ -503,7 +513,7 @@ dpath = cls(tempfile.mktemp()) try: dpath.mkdir() - except path.FileExists: + except (py.error.EEXIST, py.error.EPERM, py.error.EACCES): continue return dpath raise py.error.ENOENT(dpath, "could not create tempdir, %d tries" % tries) Modified: py/branch/py-collect/path/svn/svncommon.py ============================================================================== --- py/branch/py-collect/path/svn/svncommon.py (original) +++ py/branch/py-collect/path/svn/svncommon.py Sat Apr 16 20:36:08 2005 @@ -3,7 +3,6 @@ """ import os, sys, time, re import py -from py import path, process from py.__impl__.path import common #_______________________________________________________________ Modified: py/branch/py-collect/path/svn/testing/test_urlcommand.py ============================================================================== --- py/branch/py-collect/path/svn/testing/test_urlcommand.py (original) +++ py/branch/py-collect/path/svn/testing/test_urlcommand.py Sat Apr 16 20:36:08 2005 @@ -1,5 +1,5 @@ import py -from svntestbase import CommonCommandAndBindingTests, getrepowc +from py.__impl__.path.svn.testing.svntestbase import CommonCommandAndBindingTests, getrepowc class TestSvnCommandPath(CommonCommandAndBindingTests): def setup_class(cls): Modified: py/branch/py-collect/path/svn/testing/test_wccommand.py ============================================================================== --- py/branch/py-collect/path/svn/testing/test_wccommand.py (original) +++ py/branch/py-collect/path/svn/testing/test_wccommand.py Sat Apr 16 20:36:08 2005 @@ -1,5 +1,5 @@ import py -from svntestbase import CommonSvnTests, getrepowc +from py.__impl__.path.svn.testing.svntestbase import CommonSvnTests, getrepowc class TestWCSvnCommandPath(CommonSvnTests): Modified: py/branch/py-collect/path/svn/wccommand.py ============================================================================== --- py/branch/py-collect/path/svn/wccommand.py (original) +++ py/branch/py-collect/path/svn/wccommand.py Sat Apr 16 20:36:08 2005 @@ -9,7 +9,6 @@ """ import os, sys, time, re -from py import path import py from py.__impl__.path import common from py.__impl__.path.svn import cache @@ -22,7 +21,11 @@ def __new__(cls, wcpath=None): self = object.__new__(cls) - self.localpath = path.local(wcpath) + if isinstance(wcpath, cls): + if wcpath.__class__ == cls: + return wcpath + wcpath = wcpath.localpath + self.localpath = py.path.local(wcpath) return self strpath = property(lambda x: str(x.localpath), None, None, "string path") @@ -43,7 +46,7 @@ def svnurl(self): """ return current SvnPath for this WC-item. """ info = self.info() - return path.svnurl(info.url) + return py.path.svnurl(info.url) url = property(_geturl, None, None, "url of this WC item") @@ -131,6 +134,7 @@ ignored and considered always true (because of underlying svn semantics. """ + assert rec, "svn cannot remove non-recursively" flags = [] if force: flags.append('--force') @@ -357,14 +361,14 @@ def get(self, spec): return self.localpath.get(spec) - class Checkers(path.local.Checkers): + class Checkers(py.path.local.Checkers): def __init__(self, path): self.svnwcpath = path self.path = path.localpath def versioned(self): try: s = self.svnwcpath.info() - except py.error.ENOENT: + except (py.error.ENOENT, py.error.EEXIST): return False except py.process.cmdexec.Error, e: if e.err.find('is not a working copy')!=-1: Modified: py/branch/py-collect/process/cmdexec.py ============================================================================== --- py/branch/py-collect/process/cmdexec.py (original) +++ py/branch/py-collect/process/cmdexec.py Sat Apr 16 20:36:08 2005 @@ -79,7 +79,14 @@ Note that this method can currently deadlock because we don't have WaitForMultipleObjects in the std-python api. + + Further note that the rules for quoting are very special + under Windows. Do a HELP CMD in a shell, and tell me if + you understand this. For now, I try to do a fix. """ + print "*****", cmd + if '"' in cmd and not cmd.startswith('""'): + cmd = '"%s"' % cmd stdin, stdout, stderr = os.popen3(cmd) out = stdout.read() err = stderr.read() Added: py/branch/py-collect/tool/__init__.py ============================================================================== --- (empty file) +++ py/branch/py-collect/tool/__init__.py Sat Apr 16 20:36:08 2005 @@ -0,0 +1 @@ +# Modified: py/branch/py-collect/xmlobj/html.py ============================================================================== --- py/branch/py-collect/xmlobj/html.py (original) +++ py/branch/py-collect/xmlobj/html.py Sat Apr 16 20:36:08 2005 @@ -2,16 +2,34 @@ """ -from py.xml import Namespace +from py.xml import Namespace, Tag +from py.__impl__.xmlobj.visit import SimpleUnicodeVisitor + +class HtmlVisitor(SimpleUnicodeVisitor): + def repr_attribute(self, attrs, name): + if name == 'class_': + value = getattr(attrs, name) + if value is None: + return + return super(HtmlVisitor, self).repr_attribute(attrs, name) + +class HtmlTag(Tag): + def unicode(self, indent=2): + l = [] + HtmlVisitor(l.append, indent, shortempty=False).visit(self) + return u"".join(l) # exported plain html namespace class html(Namespace): + __tagclass__ = HtmlTag __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," "td,th,link,img,meta,body,pre,br,ul," - "ol,li,em,form,input,select,option" + "ol,li,em,form,input,select,option," + "button,script,colgroup,col,map,area," + "blockquote,dl,dt,dd,strong" ).split(',') if x]) class Style(object): Modified: py/branch/py-collect/xmlobj/testing/test_html.py ============================================================================== --- py/branch/py-collect/xmlobj/testing/test_html.py (original) +++ py/branch/py-collect/xmlobj/testing/test_html.py Sat Apr 16 20:36:08 2005 @@ -11,7 +11,12 @@ class body(html.body): style = html.Style(font_size = "12pt") u = unicode(my.body()) - assert u == '' + assert u == '' + +def test_class_None(): + t = html.body(class_=None) + u = unicode(t) + assert u == '' def test_alternating_style(): alternating = ( Modified: py/branch/py-collect/xmlobj/visit.py ============================================================================== --- py/branch/py-collect/xmlobj/visit.py (original) +++ py/branch/py-collect/xmlobj/visit.py Sat Apr 16 20:36:08 2005 @@ -6,13 +6,14 @@ class SimpleUnicodeVisitor(object): """ recursive visitor to write unicode. """ - def __init__(self, write, indent=0, curindent=0): + def __init__(self, write, indent=0, curindent=0, shortempty=True): self.write = write self.cache = {} self.visited = {} # for detection of recursion self.indent = indent self.curindent = curindent self.parents = [] + self.shortempty = shortempty # short empty tags or not def visit(self, node): """ dispatcher on node's class/bases name. """ @@ -57,7 +58,11 @@ self.write(u'' % tagname) self.curindent -= self.indent else: - self.write(u'<%s%s/>' % (tagname, self.attributes(tag))) + nameattr = tagname+self.attributes(tag) + if self.shortempty: + self.write(u'<%s/>' % (nameattr,)) + else: + self.write(u'<%s>' % (nameattr, tagname)) def attributes(self, tag): # serialize attributes @@ -65,14 +70,19 @@ attrlist.sort() l = [] for name in attrlist: - if name[0] != '_': - value = getattr(tag.attr, name) - if name.endswith('_'): - name = name[:-1] - l.append(u' %s="%s"' % (name, escape(unicode(value)))) + res = self.repr_attribute(tag.attr, name) + if res is not None: + l.append(res) l.extend(self.getstyle(tag)) return u"".join(l) + def repr_attribute(self, attrs, name): + if name[:2] != '__': + value = getattr(attrs, name) + if name.endswith('_'): + name = name[:-1] + return u' %s="%s"' % (name, escape(unicode(value))) + def getstyle(self, tag): """ return attribute list suitable for styling. """ try: From hpk at codespeak.net Sat Apr 16 20:45:00 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Apr 2005 20:45:00 +0200 (CEST) Subject: [py-svn] r10742 - py/branch/py-collect/tool Message-ID: <20050416184500.BD6DD27B5E@code1.codespeak.net> Author: hpk Date: Sat Apr 16 20:45:00 2005 New Revision: 10742 Modified: py/branch/py-collect/tool/ (props changed) py/branch/py-collect/tool/__init__.py (props changed) py/branch/py-collect/tool/utestconvert.py (props changed) Log: fixeol From arigo at codespeak.net Sat Apr 16 20:51:49 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sat, 16 Apr 2005 20:51:49 +0200 (CEST) Subject: [py-svn] r10743 - py/branch/py-collect/c-extension/greenlet Message-ID: <20050416185149.2070F27B5E@code1.codespeak.net> Author: arigo Date: Sat Apr 16 20:51:48 2005 New Revision: 10743 Added: py/branch/py-collect/c-extension/greenlet/ - copied from r10741, py/dist/py/c-extension/greenlet/ Log: Moved the greenlets from the trunk to the branch. From arigo at codespeak.net Sat Apr 16 22:01:49 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sat, 16 Apr 2005 22:01:49 +0200 (CEST) Subject: [py-svn] r10745 - py/dist/py Message-ID: <20050416200149.6E27427B5E@code1.codespeak.net> Author: arigo Date: Sat Apr 16 22:01:49 2005 New Revision: 10745 Removed: py/dist/py/ Log: The py trunk is to be replaced by the py-collect branch. Please see the py-dev mailing list for a description of the changes that occurred in the branch. Getting rid of the trunk... From arigo at codespeak.net Sat Apr 16 22:01:57 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sat, 16 Apr 2005 22:01:57 +0200 (CEST) Subject: [py-svn] r10746 - in py: branch/py-collect dist/py Message-ID: <20050416200157.DD1D727B5E@code1.codespeak.net> Author: arigo Date: Sat Apr 16 22:01:57 2005 New Revision: 10746 Added: py/dist/py/ - copied from r10745, py/branch/py-collect/ Removed: py/branch/py-collect/ Log: Replaced the py lib trunk with the py-collect branch. From hpk at codespeak.net Sat Apr 16 22:25:54 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Apr 2005 22:25:54 +0200 (CEST) Subject: [py-svn] r10749 - py/dist/py/test Message-ID: <20050416202554.3BE4E27B5E@code1.codespeak.net> Author: hpk Date: Sat Apr 16 22:25:54 2005 New Revision: 10749 Modified: py/dist/py/test/config.py py/dist/py/test/defaultconftest.py Log: avoid early importing of tkinter and terminal-session Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Sat Apr 16 22:25:54 2005 @@ -104,7 +104,10 @@ """ name = self.option.session name += 'Session' - return self.getinitialvalue(name) + try: + self.getinitialvalue(name) + except ValueError: + return getattr(py.test, name) Config._reset() Modified: py/dist/py/test/defaultconftest.py ============================================================================== --- py/dist/py/test/defaultconftest.py (original) +++ py/dist/py/test/defaultconftest.py Sat Apr 16 22:25:54 2005 @@ -1,8 +1,5 @@ import py -TerminalSession = py.test.TerminalSession -TkinterSession = py.test.TkinterSession - Module = py.test.collect.Module Directory = py.test.collect.Directory Class = py.test.collect.Class From hpk at codespeak.net Sat Apr 16 22:31:10 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Apr 2005 22:31:10 +0200 (CEST) Subject: [py-svn] r10750 - py/dist/py/test/terminal Message-ID: <20050416203110.B555127B5E@code1.codespeak.net> Author: hpk Date: Sat Apr 16 22:31:10 2005 New Revision: 10750 Modified: py/dist/py/test/terminal/terminal.py Log: fix formatting bug Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Sat Apr 16 22:31:10 2005 @@ -80,7 +80,7 @@ if result is None or not isinstance(colitem, py.test.Item): if isinstance(colitem, py.test.collect.Module) \ and self.config.option.verbose == 0 \ - and colitem.numitems != 0: + and colitem.numitems >= 0: self.out.line() return else: From hpk at codespeak.net Sat Apr 16 22:57:21 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 16 Apr 2005 22:57:21 +0200 (CEST) Subject: [py-svn] r10753 - in py/dist/py/test: . testing Message-ID: <20050416205721.828C727B5E@code1.codespeak.net> Author: hpk Date: Sat Apr 16 22:57:21 2005 New Revision: 10753 Modified: py/dist/py/test/raises.py py/dist/py/test/testing/test_raises.py Log: allow raises-strings to be full statements (well it accidentally worked before, but now it's more explicit) Modified: py/dist/py/test/raises.py ============================================================================== --- py/dist/py/test/raises.py (original) +++ py/dist/py/test/raises.py Sat Apr 16 22:57:21 2005 @@ -17,7 +17,7 @@ #print "raises frame scope: %r" % frame.f_locals source = py.code.Source(expr) try: - eval(source.compile(), frame.f_globals, loc) + exec source.compile() in frame.f_globals, loc #del __traceback__ # XXX didn'T mean f_globals == f_locals something special? # this is destroyed here ... Modified: py/dist/py/test/testing/test_raises.py ============================================================================== --- py/dist/py/test/testing/test_raises.py (original) +++ py/dist/py/test/testing/test_raises.py Sat Apr 16 22:57:21 2005 @@ -7,6 +7,9 @@ def test_raises(self): test.raises(ValueError, "int('qwe')") + def test_raises_exec(self): + test.raises(ValueError, "a,x = []") + def test_raises_syntax_error(self): test.raises(SyntaxError, "qwe qwe qwe") From jan at codespeak.net Sat Apr 16 23:11:02 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Sat, 16 Apr 2005 23:11:02 +0200 (CEST) Subject: [py-svn] r10755 - py/dist/py/test/tkinter Message-ID: <20050416211102.A39AD27B44@code1.codespeak.net> Author: jan Date: Sat Apr 16 23:11:02 2005 New Revision: 10755 Modified: py/dist/py/test/tkinter/util.py Log: fix file pattern Modified: py/dist/py/test/tkinter/util.py ============================================================================== --- py/dist/py/test/tkinter/util.py (original) +++ py/dist/py/test/tkinter/util.py Sat Apr 16 23:11:02 2005 @@ -128,6 +128,7 @@ 'path' : '', 'modpath': '', } + Status = Status def fromChannel(cls, kwdict): ''' TestReport.fromChannel(report.toChannel()) == report ''' @@ -255,7 +256,7 @@ def check_files(self): '''returns (changed files, deleted files)''' - fil = py.path.checker(fnmatch='*.py') + fil = py.path.checker(fnmatch='[!.]*.py') rec = py.path.checker(dotfile=0) files = [] From hpk at codespeak.net Sun Apr 17 12:44:33 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Apr 2005 12:44:33 +0200 (CEST) Subject: [py-svn] r10760 - py/dist/py/documentation Message-ID: <20050417104433.655EB27B6D@code1.codespeak.net> Author: hpk Date: Sun Apr 17 12:44:33 2005 New Revision: 10760 Modified: py/dist/py/documentation/rest_test.py Log: fail more nicely with docutils Modified: py/dist/py/documentation/rest_test.py ============================================================================== --- py/dist/py/documentation/rest_test.py (original) +++ py/dist/py/documentation/rest_test.py Sun Apr 17 12:44:33 2005 @@ -11,7 +11,13 @@ except ImportError: py.test.skip("docutils not importable") # this helper will raise errors instead of warnings - rest.process(path) + try: + rest.process(path) + except KeyboardInterrupt: + raise + except: + # we assume docutils printed info on stdout + py.test.fail("docutils processing failed, see captured stderr") #assert not out def test_rest_files(): From arigo at codespeak.net Sun Apr 17 12:48:55 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 17 Apr 2005 12:48:55 +0200 (CEST) Subject: [py-svn] r10761 - py/dist/py/test Message-ID: <20050417104855.39A2D27B5D@code1.codespeak.net> Author: arigo Date: Sun Apr 17 12:48:55 2005 New Revision: 10761 Modified: py/dist/py/test/config.py Log: Oups. Missing a return here. Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Sun Apr 17 12:48:55 2005 @@ -105,7 +105,7 @@ name = self.option.session name += 'Session' try: - self.getinitialvalue(name) + return self.getinitialvalue(name) except ValueError: return getattr(py.test, name) From arigo at codespeak.net Sun Apr 17 13:23:46 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 17 Apr 2005 13:23:46 +0200 (CEST) Subject: [py-svn] r10764 - in py/dist/py/test: terminal testing Message-ID: <20050417112346.BE12A27B5B@code1.codespeak.net> Author: arigo Date: Sun Apr 17 13:23:46 2005 New Revision: 10764 Modified: py/dist/py/test/terminal/terminal.py py/dist/py/test/testing/test_session.py Log: Refactored a bit the Terminal._processresult() method. Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Sun Apr 17 13:23:46 2005 @@ -84,12 +84,12 @@ self.out.line() return else: - restype, c = self._processresult(result) if self.config.option.verbose >= 1: - resultstring = self.namemap.get(restype, result.__class__.__name__) + resultstring = self.repr_progress_long_result(colitem, result) resultstring += " (%.2f)" % (colitem.elapsedtime,) self.out.line(resultstring) - else: + else: + c = self.repr_progress_short_result(colitem, result) self.out.write(c) # ------------------- @@ -159,10 +159,17 @@ Item.Failed: 'FAIL', } - def _processresult(self, testresult): + def repr_progress_short_result(self, item, testresult): for restype, char in self.typemap.items(): if isinstance(testresult, restype): - return restype, char + return char + else: + raise TypeError, "not a result instance: %r" % testresult + + def repr_progress_long_result(self, item, testresult): + for restype, char in self.namemap.items(): + if isinstance(testresult, restype): + return char else: raise TypeError, "not a result instance: %r" % testresult 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 Sun Apr 17 13:23:46 2005 @@ -34,11 +34,11 @@ def test_session_parsing(self): config, args = py.test.Config.parse(['--session=terminal']) - assert config.getsessionclass() is py.test.TerminalSession + assert issubclass(config.getsessionclass(), py.test.TerminalSession) config, args = py.test.Config.parse(['--session=tkinter']) - assert config.getsessionclass() is py.test.TkinterSession + assert issubclass(config.getsessionclass(), py.test.TkinterSession) config, args = py.test.Config.parse(['--tkinter']) - assert config.getsessionclass() is py.test.TkinterSession + assert issubclass(config.getsessionclass(), py.test.TkinterSession) #f = open('/tmp/logfile', 'wa') From hpk at codespeak.net Sun Apr 17 13:31:08 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Apr 2005 13:31:08 +0200 (CEST) Subject: [py-svn] r10766 - py/dist/py/test/terminal Message-ID: <20050417113108.C948C27B5B@code1.codespeak.net> Author: hpk Date: Sun Apr 17 13:31:08 2005 New Revision: 10766 Modified: py/dist/py/test/terminal/remote.py Log: look for custom TerminalSessions on the slave/remote side as well. Modified: py/dist/py/test/terminal/remote.py ============================================================================== --- py/dist/py/test/terminal/remote.py (original) +++ py/dist/py/test/terminal/remote.py Sun Apr 17 13:31:08 2005 @@ -63,7 +63,7 @@ else: cols = args #print "processing", cols - session = py.test.TerminalSession(config) + session = config.getsessionclass()(config) session.shouldclose = channel.isclosed failures = session.main(cols) channel.send(failures) From hpk at codespeak.net Sun Apr 17 13:45:26 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Apr 2005 13:45:26 +0200 (CEST) Subject: [py-svn] r10768 - py/dist/py/documentation Message-ID: <20050417114526.BBF0027B5F@code1.codespeak.net> Author: hpk Date: Sun Apr 17 13:45:26 2005 New Revision: 10768 Modified: py/dist/py/documentation/coding-style.txt py/dist/py/documentation/rest_test.py Log: more extensive documentation checking use -R to check remote links as well (by default only internal links are checked) Modified: py/dist/py/documentation/coding-style.txt ============================================================================== --- py/dist/py/documentation/coding-style.txt (original) +++ py/dist/py/documentation/coding-style.txt Sun Apr 17 13:45:26 2005 @@ -68,7 +68,6 @@ `future_` book. Communication is considered a key here to make sure that the py lib develops in a consistent way. -.. _test-design: ../devel/testdesign.html .. _`PEP 8 Style Guide for Python Code`: http://www.python.org/peps/pep-0008.html .. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev .. _`future`: future.html Modified: py/dist/py/documentation/rest_test.py ============================================================================== --- py/dist/py/documentation/rest_test.py (original) +++ py/dist/py/documentation/rest_test.py Sun Apr 17 13:45:26 2005 @@ -2,24 +2,62 @@ import py from py.__impl__.misc import rest +from py.__impl__.documentation import conftest docdir = py.path.svnwc(py.magic.autopath().dirpath()) -def restcheck(path): +def checkdocutils(): try: import docutils except ImportError: py.test.skip("docutils not importable") - # this helper will raise errors instead of warnings + +def restcheck(path): + checkdocutils() try: + # this helper will raise errors instead of warnings rest.process(path) except KeyboardInterrupt: raise except: # we assume docutils printed info on stdout py.test.fail("docutils processing failed, see captured stderr") - #assert not out def test_rest_files(): for x in docdir.listdir('*.txt'): yield restcheck, x + yield linkcheck, x + +def linkcheck(path): + ddir = docdir.localpath + + for lineno, line in py.builtin.enumerate(path.readlines()): + line = line.strip() + if line.startswith('.. _'): + l = line.split(':', 1) + if len(l) != 2: + continue + tryfn = l[1].strip() + if tryfn.startswith('http:'): + if not conftest.option.checkremote: + continue + try: + print "trying remote", tryfn + py.std.urllib2.urlopen(tryfn) + except py.std.urllib2.HTTPError: + py.test.fail("remote reference error %r in %s:%d" %( + tryfn, path.basename, lineno+1)) + elif tryfn.endswith('.html'): + # assume it should be a file + fn = ddir.join(tryfn) + fn = fn.new(ext='.txt') + if not fn.check(file=1): + py.test.fail("reference error %r in %s:%d" %( + tryfn, path.basename, lineno+1)) + else: + # yes, what else? + pass + + + + From hpk at codespeak.net Sun Apr 17 13:45:40 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Apr 2005 13:45:40 +0200 (CEST) Subject: [py-svn] r10769 - py/dist/py/documentation Message-ID: <20050417114540.268F027B5F@code1.codespeak.net> Author: hpk Date: Sun Apr 17 13:45:39 2005 New Revision: 10769 Added: py/dist/py/documentation/conftest.py Log: forgot to add the option Added: py/dist/py/documentation/conftest.py ============================================================================== --- (empty file) +++ py/dist/py/documentation/conftest.py Sun Apr 17 13:45:39 2005 @@ -0,0 +1,9 @@ +import py +Option = py.test.Config.Option +option = py.test.Config.addoptions("documentation check options", + Option('-R', '--checkremote', + action="store_true", dest="checkremote", default=False, + help="check remote links in ReST files" + ) +) + From hpk at codespeak.net Sun Apr 17 13:50:58 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Apr 2005 13:50:58 +0200 (CEST) Subject: [py-svn] r10770 - py/dist/py/documentation Message-ID: <20050417115058.6118827B5F@code1.codespeak.net> Author: hpk Date: Sun Apr 17 13:50:58 2005 New Revision: 10770 Added: py/dist/py/documentation/__init__.py Log: damn it, another forgotten file Added: py/dist/py/documentation/__init__.py ============================================================================== --- (empty file) +++ py/dist/py/documentation/__init__.py Sun Apr 17 13:50:58 2005 @@ -0,0 +1 @@ +# From hpk at codespeak.net Sun Apr 17 14:34:58 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Apr 2005 14:34:58 +0200 (CEST) Subject: [py-svn] r10772 - py/dist/py/test Message-ID: <20050417123458.22DFB27B5F@code1.codespeak.net> Author: hpk Date: Sun Apr 17 14:34:57 2005 New Revision: 10772 Modified: py/dist/py/test/config.py Log: set default values immediate on py.test.addoptions so that subdirectories will at least see their option default values Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Sun Apr 17 14:34:57 2005 @@ -78,6 +78,9 @@ optgroup = optparse.OptionGroup(parser, groupname) optgroup.add_options(specs) parser.add_option_group(optgroup) + for opt in specs: + if hasattr(opt, 'default'): + setattr(cls._config.option, opt.dest, opt.default) return cls._config.option addoptions = classmethod(addoptions) From hpk at codespeak.net Sun Apr 17 14:35:57 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Apr 2005 14:35:57 +0200 (CEST) Subject: [py-svn] r10773 - in py/dist/py: test test/testing test/tkinter test/tkinter/testing thread Message-ID: <20050417123557.BCBF827B5F@code1.codespeak.net> Author: hpk Date: Sun Apr 17 14:35:57 2005 New Revision: 10773 Modified: py/dist/py/test/collect.py py/dist/py/test/testing/test_collect.py py/dist/py/test/tkinter/repository.py py/dist/py/test/tkinter/testing/test_util.py py/dist/py/test/tkinter/util.py py/dist/py/thread/pool.py Log: some python2.2 compatibility changes Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Sun Apr 17 14:35:57 2005 @@ -1,4 +1,3 @@ - """ Collect test items at filesystem and python module levels. @@ -29,6 +28,7 @@ ... """ +from __future__ import generators import py isclass = py.std.inspect.isclass @@ -306,7 +306,8 @@ def _getobj(self): return self.parent.obj() def Function(self): - return getattr(self.obj, 'Function', super(Instance, self).Function) + return getattr(self.obj, 'Function', + Collector.Function.__get__(self)) # XXX for python 2.2 Function = property(Function) class Generator(Collector): 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 Sun Apr 17 14:35:57 2005 @@ -108,6 +108,7 @@ o = tmpdir.ensure('generativetest', dir=1) tfile = o.join('test_generative.py') tfile.write(py.code.Source(""" + from __future__ import generators # python2.2! def func1(arg, arg2): assert arg == arg2 Modified: py/dist/py/test/tkinter/repository.py ============================================================================== --- py/dist/py/test/tkinter/repository.py (original) +++ py/dist/py/test/tkinter/repository.py Sun Apr 17 14:35:57 2005 @@ -91,7 +91,7 @@ return [(key, self.find(key)) for key in self.keys(startkey)] -class OrderedDict(UserDict.DictMixin): +class OrderedDict(UserDict.DictMixin): '''like a normal dict, but keys are ordered by time of setting''' def __init__(self, *args, **kwargs): self._dict = dict(*args, **kwargs) Modified: py/dist/py/test/tkinter/testing/test_util.py ============================================================================== --- py/dist/py/test/tkinter/testing/test_util.py (original) +++ py/dist/py/test/tkinter/testing/test_util.py Sun Apr 17 14:35:57 2005 @@ -1,4 +1,4 @@ - +from __future__ import generators from py.__impl__.test.tkinter import util from py.__impl__.test.tkinter.util import Status, TestReport, OutBuffer import py Modified: py/dist/py/test/tkinter/util.py ============================================================================== --- py/dist/py/test/tkinter/util.py (original) +++ py/dist/py/test/tkinter/util.py Sun Apr 17 14:35:57 2005 @@ -79,9 +79,7 @@ def update(self, status): '''merge self and status, self will be set to the "higher" status in ordered_list''' - name_int_map = dict( - py.std.itertools.izip(self.ordered_list, - py.std.itertools.count())) + name_int_map = dict(zip(self.ordered_list, range(len(self.ordered_list)))) self.str = self.ordered_list[max([name_int_map[i] for i in (str(status), self.str)])] Modified: py/dist/py/thread/pool.py ============================================================================== --- py/dist/py/thread/pool.py (original) +++ py/dist/py/thread/pool.py Sun Apr 17 14:35:57 2005 @@ -5,7 +5,7 @@ class WorkerThread(threading.Thread): def __init__(self, pool): - super(WorkerThread, self).__init__() + threading.Thread.__init__(self) self._queue = Queue.Queue() self._pool = pool self.setDaemon(1) @@ -52,11 +52,26 @@ self._excinfo = excinfo self._queue.put(None) + def _get_with_timeout(self, timeout): + # taken from python2.3's Queue.get() + # we want to run on python2.2 here + delay = 0.0005 # 500 us -> initial delay of 1 ms + endtime = time.time() + timeout + while 1: + try: + return self._queue.get_nowait() + except Queue.Empty: + remaining = endtime - time.time() + if remaining <= 0: #time is over and no element arrived + raise IOError("timeout waiting for task %r" %(self.task,)) + delay = min(delay * 2, remaining, .05) + time.sleep(delay) #reduce CPU usage by using a sleep + def get(self, timeout=None): - try: - result = self._queue.get(timeout=timeout) - except Queue.Empty: - raise IOError("timeout waiting for task %r" %(self.task,)) + if timeout is not None: + result = self._get_with_timeout(timeout) + else: + result = self._queue.get() excinfo = self._excinfo if excinfo: self._excinfo = None From hpk at codespeak.net Sun Apr 17 16:33:03 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Apr 2005 16:33:03 +0200 (CEST) Subject: [py-svn] r10775 - py/dist/py/misc Message-ID: <20050417143303.9903E27B39@code1.codespeak.net> Author: hpk Date: Sun Apr 17 16:33:03 2005 New Revision: 10775 Modified: py/dist/py/misc/rest.py Log: allow for an explicit encoding (defaults to 'latin1') Modified: py/dist/py/misc/rest.py ============================================================================== --- py/dist/py/misc/rest.py (original) +++ py/dist/py/misc/rest.py Sun Apr 17 16:33:03 2005 @@ -8,7 +8,7 @@ def log(msg): pass -def convert_rest_html(source, source_path, stylesheet=None): +def convert_rest_html(source, source_path, stylesheet=None, encoding='latin1'): """ return html latin1-encoded document for the given input. source a ReST-string sourcepath where to look for includes (basically) @@ -18,14 +18,14 @@ kwargs = { 'stylesheet' : stylesheet, 'traceback' : 1, - 'output_encoding' : 'latin1', + 'output_encoding' : encoding, #'halt' : 0, # 'info', 'halt_level' : 2, } return publish_string(source, str(source_path), writer_name='html', settings_overrides=kwargs) -def process(txtpath): +def process(txtpath, encoding='latin1'): """ process a textfile """ log("processing %s" % txtpath) assert txtpath.check(ext='.txt') @@ -37,8 +37,8 @@ stylesheet = style.basename else: stylesheet = None - doc = convert_rest_html(txtpath.read(), txtpath, - stylesheet=stylesheet) + content = unicode(txtpath.read(), encoding) + doc = convert_rest_html(content, txtpath, stylesheet=stylesheet, encoding=encoding) htmlpath.write(doc) #log("wrote %r" % htmlpath) #if txtpath.check(svnwc=1, versioned=1): From hpk at codespeak.net Sun Apr 17 17:34:30 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Apr 2005 17:34:30 +0200 (CEST) Subject: [py-svn] r10776 - py/dist/py/documentation Message-ID: <20050417153430.03F9E27B47@code1.codespeak.net> Author: hpk Date: Sun Apr 17 17:34:30 2005 New Revision: 10776 Removed: py/dist/py/documentation/rest_test.py Modified: py/dist/py/documentation/conftest.py Log: move doctest checking (including local and remote link checks!) into a semi-reusable conftest.py Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Sun Apr 17 17:34:30 2005 @@ -1,4 +1,7 @@ +from __future__ import generators import py +from py.__impl__.misc import rest + Option = py.test.Config.Option option = py.test.Config.addoptions("documentation check options", Option('-R', '--checkremote', @@ -7,3 +10,105 @@ ) ) +docdir = py.path.svnwc(py.magic.autopath().dirpath()) +ddir = docdir.localpath + +def checkdocutils(): + try: + import docutils + except ImportError: + py.test.skip("docutils not importable") + +def restcheck(path): + checkdocutils() + import docutils.utils + try: + # this helper will raise errors instead of warnings + rest.process(path) + except KeyboardInterrupt: + raise + except docutils.utils.SystemMessage: + # we assume docutils printed info on stdout + py.test.fail("docutils processing failed, see captured stderr") + +# +# hooking into py.test collector's chain ... +# because we generate subtests for all link checks +# it is a bit more convoluted than is strictly neccessary +# to perform the tests + +class DocDirectory(py.test.collect.Directory): + def run(self): + results = super(DocDirectory, self).run() + for x in self.fspath.listdir('*.txt', sort=True): + results.append(x.basename) + return results + + def join(self, name): + if not name.endswith('.txt'): + return super(DocDirectory, self).join(name) + p = self.fspath.join(name) + if p.check(file=1): + return DocChecker(p, parent=self) +Directory = DocDirectory + +class DocChecker(py.test.collect.Module): + def setup(self): + pass + def teardown(self): + pass + def run(self): + return [self.fspath.basename, 'checklinks'] + def join(self, name): + if name == self.fspath.basename: + return DocumentationItem(name, parent=self) + elif name == 'checklinks': + return LinkCheckerGenerator(name, self) + +class DocumentationItem(py.test.Item): + def run(self): + mypath = self.fspath + restcheck(py.path.svnwc(mypath)) + #linkcheck(mypath) + +class LinkCheckerGenerator(py.test.collect.Generator): + def _getobj(self): + path = self.fspath + def genlinkchecks(): + for lineno, line in py.builtin.enumerate(path.readlines()): + line = line.strip() + if line.startswith('.. _'): + l = line.split(':', 1) + if len(l) != 2: + continue + tryfn = l[1].strip() + if tryfn.startswith('http:'): + if option.checkremote: + yield urlcheck, tryfn, path, lineno + elif tryfn.endswith('.html'): + yield localrefcheck, tryfn, path, lineno + else: + # yes, what else? + pass + + return genlinkchecks + +def urlcheck(tryfn, path, lineno): + try: + print "trying remote", tryfn + py.std.urllib2.urlopen(tryfn) + except py.std.urllib2.HTTPError: + py.test.fail("remote reference error %r in %s:%d" %( + tryfn, path.basename, lineno+1)) + +def localrefcheck(tryfn, path, lineno): + # assume it should be a file + fn = ddir.join(tryfn) + fn = fn.new(ext='.txt') + if not fn.check(file=1): + py.test.fail("reference error %r in %s:%d" %( + tryfn, path.basename, lineno+1)) + + + + Deleted: /py/dist/py/documentation/rest_test.py ============================================================================== --- /py/dist/py/documentation/rest_test.py Sun Apr 17 17:34:30 2005 +++ (empty file) @@ -1,63 +0,0 @@ -from __future__ import generators - -import py -from py.__impl__.misc import rest -from py.__impl__.documentation import conftest - -docdir = py.path.svnwc(py.magic.autopath().dirpath()) - -def checkdocutils(): - try: - import docutils - except ImportError: - py.test.skip("docutils not importable") - -def restcheck(path): - checkdocutils() - try: - # this helper will raise errors instead of warnings - rest.process(path) - except KeyboardInterrupt: - raise - except: - # we assume docutils printed info on stdout - py.test.fail("docutils processing failed, see captured stderr") - -def test_rest_files(): - for x in docdir.listdir('*.txt'): - yield restcheck, x - yield linkcheck, x - -def linkcheck(path): - ddir = docdir.localpath - - for lineno, line in py.builtin.enumerate(path.readlines()): - line = line.strip() - if line.startswith('.. _'): - l = line.split(':', 1) - if len(l) != 2: - continue - tryfn = l[1].strip() - if tryfn.startswith('http:'): - if not conftest.option.checkremote: - continue - try: - print "trying remote", tryfn - py.std.urllib2.urlopen(tryfn) - except py.std.urllib2.HTTPError: - py.test.fail("remote reference error %r in %s:%d" %( - tryfn, path.basename, lineno+1)) - elif tryfn.endswith('.html'): - # assume it should be a file - fn = ddir.join(tryfn) - fn = fn.new(ext='.txt') - if not fn.check(file=1): - py.test.fail("reference error %r in %s:%d" %( - tryfn, path.basename, lineno+1)) - else: - # yes, what else? - pass - - - - From hpk at codespeak.net Sun Apr 17 17:40:28 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Apr 2005 17:40:28 +0200 (CEST) Subject: [py-svn] r10777 - py/dist/py/documentation Message-ID: <20050417154028.A01F927B47@code1.codespeak.net> Author: hpk Date: Sun Apr 17 17:40:28 2005 New Revision: 10777 Modified: py/dist/py/documentation/conftest.py Log: get rid of global state Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Sun Apr 17 17:40:28 2005 @@ -10,9 +10,6 @@ ) ) -docdir = py.path.svnwc(py.magic.autopath().dirpath()) -ddir = docdir.localpath - def checkdocutils(): try: import docutils @@ -103,7 +100,7 @@ def localrefcheck(tryfn, path, lineno): # assume it should be a file - fn = ddir.join(tryfn) + fn = path.dirpath(tryfn) fn = fn.new(ext='.txt') if not fn.check(file=1): py.test.fail("reference error %r in %s:%d" %( From hpk at codespeak.net Sun Apr 17 17:57:54 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Apr 2005 17:57:54 +0200 (CEST) Subject: [py-svn] r10779 - py/dist/py/documentation Message-ID: <20050417155754.BC8D927B47@code1.codespeak.net> Author: hpk Date: Sun Apr 17 17:57:54 2005 New Revision: 10779 Modified: py/dist/py/documentation/conftest.py Log: improve the doc-checking some more. Now you can see via '-v' or '--collectonly' which links are to be checked. Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Sun Apr 17 17:57:54 2005 @@ -60,7 +60,7 @@ if name == self.fspath.basename: return DocumentationItem(name, parent=self) elif name == 'checklinks': - return LinkCheckerGenerator(name, self) + return LinkCheckerMaker(name, self) class DocumentationItem(py.test.Item): def run(self): @@ -68,27 +68,41 @@ restcheck(py.path.svnwc(mypath)) #linkcheck(mypath) -class LinkCheckerGenerator(py.test.collect.Generator): - def _getobj(self): - path = self.fspath - def genlinkchecks(): - for lineno, line in py.builtin.enumerate(path.readlines()): - line = line.strip() - if line.startswith('.. _'): - l = line.split(':', 1) - if len(l) != 2: - continue - tryfn = l[1].strip() - if tryfn.startswith('http:'): - if option.checkremote: - yield urlcheck, tryfn, path, lineno - elif tryfn.endswith('.html'): - yield localrefcheck, tryfn, path, lineno - else: - # yes, what else? - pass - - return genlinkchecks +class LinkCheckerMaker(py.test.collect.Collector): + def run(self): + l = [] + for call, tryfn, path, lineno in genlinkchecks(self.fspath): + l.append(tryfn) + return l + + def join(self, name): + for call, tryfn, path, lineno in genlinkchecks(self.fspath): + if tryfn == name: + return CheckLink(name, parent=self, args=(tryfn, path, lineno), obj=call) + +class CheckLink(py.test.Function): + def setup(self): + pass + def teardown(self): + pass + +# generating functions + args as single tests +def genlinkchecks(path): + for lineno, line in py.builtin.enumerate(path.readlines()): + line = line.strip() + if line.startswith('.. _'): + l = line.split(':', 1) + if len(l) != 2: + continue + tryfn = l[1].strip() + if tryfn.startswith('http:'): + if option.checkremote: + yield urlcheck, tryfn, path, lineno + elif tryfn.endswith('.html'): + yield localrefcheck, tryfn, path, lineno + else: + # yes, what else? + pass def urlcheck(tryfn, path, lineno): try: From hpk at codespeak.net Sun Apr 17 19:49:43 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Apr 2005 19:49:43 +0200 (CEST) Subject: [py-svn] r10785 - py/dist/py/documentation Message-ID: <20050417174943.DEA6627B4B@code1.codespeak.net> Author: hpk Date: Sun Apr 17 19:49:43 2005 New Revision: 10785 Modified: py/dist/py/documentation/conftest.py Log: parse out params part of URL Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Sun Apr 17 19:49:43 2005 @@ -98,11 +98,13 @@ if tryfn.startswith('http:'): if option.checkremote: yield urlcheck, tryfn, path, lineno - elif tryfn.endswith('.html'): - yield localrefcheck, tryfn, path, lineno else: - # yes, what else? - pass + s = tryfn + i = s.find('#') + if i != -1: + tryfn = tryfn[:i] + if tryfn.endswith('.html'): + yield localrefcheck, tryfn, path, lineno def urlcheck(tryfn, path, lineno): try: From hpk at codespeak.net Sun Apr 17 20:50:12 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 17 Apr 2005 20:50:12 +0200 (CEST) Subject: [py-svn] r10787 - py/dist/py/documentation Message-ID: <20050417185012.2AE1627B4B@code1.codespeak.net> Author: hpk Date: Sun Apr 17 20:50:11 2005 New Revision: 10787 Modified: py/dist/py/documentation/conftest.py Log: implement "anchor" checking (in a hackish but working way it seems) Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Sun Apr 17 20:50:11 2005 @@ -99,11 +99,12 @@ if option.checkremote: yield urlcheck, tryfn, path, lineno else: - s = tryfn - i = s.find('#') + i = tryfn.find('#') if i != -1: - tryfn = tryfn[:i] - if tryfn.endswith('.html'): + checkfn = tryfn[:i] + else: + checkfn = tryfn + if checkfn.endswith('.html'): yield localrefcheck, tryfn, path, lineno def urlcheck(tryfn, path, lineno): @@ -116,12 +117,30 @@ def localrefcheck(tryfn, path, lineno): # assume it should be a file + i = tryfn.find('#') + if i != -1: + anchor = tryfn[i+1:] + tryfn = tryfn[:i] + else: + anchor = '' fn = path.dirpath(tryfn) fn = fn.new(ext='.txt') if not fn.check(file=1): py.test.fail("reference error %r in %s:%d" %( tryfn, path.basename, lineno+1)) - - - + if anchor: + source = unicode(fn.read(), 'latin1') + source = source.lower().replace('-', ' ') # aehem + anchor = anchor.replace('-', ' ') + match2 = ".. _`%s`:" % anchor + match3 = ".. _%s:" % anchor + candidates = (anchor, match2, match3) + print "candidates", repr(candidates) + for line in source.split('\n'): + line = line.strip() + if line in candidates: + break + else: + py.test.fail("anchor reference error %s#%s in %s:%d" %( + tryfn, anchor, path.basename, lineno+1)) From hpk at codespeak.net Mon Apr 18 13:42:57 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Apr 2005 13:42:57 +0200 (CEST) Subject: [py-svn] r10809 - py/dist/py/documentation Message-ID: <20050418114257.EEA3B27B5B@code1.codespeak.net> Author: hpk Date: Mon Apr 18 13:42:57 2005 New Revision: 10809 Modified: py/dist/py/documentation/releasescheme.txt Log: punt on the hard part (or does somebody want to come up with an idea how we would allow the py lib to be integrated into an application that gets distributed via e.g. py2exe? ) Modified: py/dist/py/documentation/releasescheme.txt ============================================================================== --- py/dist/py/documentation/releasescheme.txt (original) +++ py/dist/py/documentation/releasescheme.txt Mon Apr 18 13:42:57 2005 @@ -26,24 +26,20 @@ things via distutils. Start by issueing the following shell command (or a graphical equivalent):: - svn co http://codespeak.net/svn/py/dist dist-py + svn co http://codespeak.net/svn/py/dist py-dist -You will then want to make sure that the `dist-py/py/bin` directory -is on your shell search path. This allows you to use "py.test" from -the command line. +Now we want to make sure that the `py-dist/py/bin` directory +gets on your shell search path in order to have "py.test" +directly available. You basically have two choices: -If you not only want to use `py.test` but want to import -the py lib within your application you have two choices: +1) include in your local shell environment startup the equivalent + of ``eval `python .../py-dist/py/env.py```. + XXX describe this in more detail (rip out from `getting-started`_). -1) you make sure that `dist-py` is on your python search - path, e.g. via setting the `PYTHONPATH`. (you may - also consider the `svn-external scenario`_ if you - need to include the py lib in your application). +2) go to the `py-dist` directory and run `python setup.py install`. -2) go to the `dist-py` directory and run `python setup.py install`. - -If you now want to upgrade your version of the py lib -you can simply issue:: +If you later want to upgrade your version of the py lib +to the newest release you simply issue:: svn up @@ -60,11 +56,11 @@ svn switch http://codespeak.net/svn/py/branch/py-X.Y If you choose the option No. 2) above you have to repeat -the distutils install after a checkout, aka -`python setup.py install`. +the distutils install after each checkout/switch. .. _`svn-external scenario`: + Installation Scenario "svn + include py lib as an external" =========================================================== @@ -103,15 +99,7 @@ py http://codespeak.net/svn/py/tag/py-X.Y.Z +Integrating the py lib into your distribution +---------------------------------------------- -Integrating the py lib into your distutils `setup.py` ------------------------------------------------------ - -XXX, maybe something like:: - - import py # XXX make sure this is the accomopanying py lib - - def setup( - ... - .... XXX - ) +XXX From hpk at codespeak.net Mon Apr 18 15:16:02 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Apr 2005 15:16:02 +0200 (CEST) Subject: [py-svn] r10813 - py/dist/py/documentation Message-ID: <20050418131602.3919427B5B@code1.codespeak.net> Author: hpk Date: Mon Apr 18 15:16:02 2005 New Revision: 10813 Modified: py/dist/py/documentation/releasescheme.txt Log: fix ResT Modified: py/dist/py/documentation/releasescheme.txt ============================================================================== --- py/dist/py/documentation/releasescheme.txt (original) +++ py/dist/py/documentation/releasescheme.txt Mon Apr 18 15:16:02 2005 @@ -34,10 +34,12 @@ 1) include in your local shell environment startup the equivalent of ``eval `python .../py-dist/py/env.py```. - XXX describe this in more detail (rip out from `getting-started`_). + XXX describe this in more detail (rip out from `getting started`_). 2) go to the `py-dist` directory and run `python setup.py install`. +.. _`getting started`: getting_started.html + If you later want to upgrade your version of the py lib to the newest release you simply issue:: From hpk at codespeak.net Mon Apr 18 17:59:23 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Apr 2005 17:59:23 +0200 (CEST) Subject: [py-svn] r10816 - in py/dist/py: . execnet execnet/testing path/local Message-ID: <20050418155923.D594B27B5B@code1.codespeak.net> Author: hpk Date: Mon Apr 18 17:59:23 2005 New Revision: 10816 Modified: py/dist/py/execnet/register.py py/dist/py/execnet/testing/test_gateway.py py/dist/py/initpkg.py py/dist/py/path/local/local.py Log: fix the 'fromlist' argument to a more conventional argumenent. Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Mon Apr 18 17:59:23 2005 @@ -21,7 +21,7 @@ ] def getsource(dottedname): - mod = __import__(dottedname, None, None, ['last']) + mod = __import__(dottedname, None, None, ['__doc__']) return inspect.getsource(mod) from py.__impl__.execnet import inputoutput, gateway 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 Mon Apr 18 17:59:23 2005 @@ -14,7 +14,7 @@ def test_getsource_no_colision(): seen = {} for dottedname in startup_modules: - mod = __import__(dottedname, None, None, ['last']) + mod = __import__(dottedname, None, None, ['__doc__']) for name, value in vars(mod).items(): if py.std.inspect.isclass(value): if name in seen: Modified: py/dist/py/initpkg.py ============================================================================== --- py/dist/py/initpkg.py (original) +++ py/dist/py/initpkg.py Mon Apr 18 17:59:23 2005 @@ -91,7 +91,7 @@ parts = [x.strip() for x in relfile.split('/') if x and x!= '.'] modpath = ".".join([self.implmodule.__name__] + parts) #print "trying import", modpath - return __import__(modpath, None, None, ['name']) + return __import__(modpath, None, None, ['__doc__']) def exportitems(self): return self.exportdefs.items() Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Mon Apr 18 17:59:23 2005 @@ -378,7 +378,7 @@ self._prependsyspath(self.dirpath()) modname = self.purebasename try: - return __import__(modname, None, None, ['something']) + return __import__(modname, None, None, ['__doc__']) except ImportError: if modname in sys.modules: del sys.modules[modname] From hpk at codespeak.net Mon Apr 18 18:12:31 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 18 Apr 2005 18:12:31 +0200 (CEST) Subject: [py-svn] r10819 - py/dist/py/thread Message-ID: <20050418161231.8D4D527B52@code1.codespeak.net> Author: hpk Date: Mon Apr 18 18:12:31 2005 New Revision: 10819 Modified: py/dist/py/thread/pool.py Log: fix stop-logic Modified: py/dist/py/thread/pool.py ============================================================================== --- py/dist/py/thread/pool.py (original) +++ py/dist/py/thread/pool.py Mon Apr 18 18:12:31 2005 @@ -14,10 +14,10 @@ try: while 1: reply = self._queue.get() - task = reply.task - assert self not in self._pool._ready - if task is None: + if reply is None: break + assert self not in self._pool._ready + task = reply.task try: func, args, kwargs = task result = func(*args, **kwargs) From hpk at codespeak.net Tue Apr 19 13:27:59 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Apr 2005 13:27:59 +0200 (CEST) Subject: [py-svn] r10846 - py/dist/py/documentation Message-ID: <20050419112759.3624C27B40@code1.codespeak.net> Author: hpk Date: Tue Apr 19 13:27:59 2005 New Revision: 10846 Modified: py/dist/py/documentation/getting_started.txt py/dist/py/documentation/releasescheme.txt Log: adjustments, recommended way is now to 'svn co' things to py-dist instead of dist-py. This allows command line completion to remain sensible. Modified: py/dist/py/documentation/getting_started.txt ============================================================================== --- py/dist/py/documentation/getting_started.txt (original) +++ py/dist/py/documentation/getting_started.txt Tue Apr 19 13:27:59 2005 @@ -21,10 +21,10 @@ Checkout the py lib distribution tree with subversion, e.g. use:: - svn co http://codespeak.net/svn/py/dist dist-py + svn co http://codespeak.net/svn/py/dist py-dist to checkout the code, documentation, tool and example tree -into a ``dist-py`` checkout directory. Your naming desire may vary +into a ``py-dist`` checkout directory. Your naming desire may vary for your local checkout directory. If you experience problems with the subversion checkout e.g. @@ -37,13 +37,13 @@ ------------- You need to put the checkout-directory into your ``PYTHONPATH`` -and you want to have the ``dist-py/py/bin/py.test`` script in +and you want to have the ``py-dist/py/bin/py.test`` script in your system path, which lets you execute test files and directories. There already is a convenient way for Bash/Shell based systems to setup the ``PYTHONPATH`` as well as the shell ``PATH``, insert:: - eval `python ~/path/to/dist-py/py/env.py` + eval `python ~/path/to/py-dist/py/env.py` into your ``.bash_profile``. Of course, you need to specify your own checkout-directory. Modified: py/dist/py/documentation/releasescheme.txt ============================================================================== --- py/dist/py/documentation/releasescheme.txt (original) +++ py/dist/py/documentation/releasescheme.txt Tue Apr 19 13:27:59 2005 @@ -1,9 +1,9 @@ The directory release layout of the repository:: - svn/py/dist # latest released code - svn/py/tag/py-X.Y.Z # tagged releases - svn/py/branch/py-X.Y # contains release branch development - svn/py/trunk # head development + svn/py/dist # latest released code + svn/py/tag/py-X.Y.Z # tagged releases + svn/py/branch/py-X.Y # contains release branch development + svn/py/trunk # head development Scenario "no svn and just let me play, please" ============================================== From hpk at codespeak.net Tue Apr 19 13:38:37 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Apr 2005 13:38:37 +0200 (CEST) Subject: [py-svn] r10847 - py/dist/py/documentation Message-ID: <20050419113837.0377427B40@code1.codespeak.net> Author: hpk Date: Tue Apr 19 13:38:37 2005 New Revision: 10847 Added: py/dist/py/documentation/getting-started.txt - copied unchanged from r10846, py/dist/py/documentation/getting_started.txt Removed: py/dist/py/documentation/getting_started.txt Modified: py/dist/py/documentation/api.txt py/dist/py/documentation/future.txt py/dist/py/documentation/index.txt py/dist/py/documentation/releasescheme.txt py/dist/py/documentation/test.txt Log: reshaping the index page for the py lib and some renamining (with referential integrity checked via py.test ...) Modified: py/dist/py/documentation/api.txt ============================================================================== --- py/dist/py/documentation/api.txt (original) +++ py/dist/py/documentation/api.txt Tue Apr 19 13:38:37 2005 @@ -16,4 +16,4 @@ documentation may state additional restrictions which take precedence. -.. _`getting started`: getting_started.html +.. _`getting started`: getting-started.html Modified: py/dist/py/documentation/future.txt ============================================================================== --- py/dist/py/documentation/future.txt (original) +++ py/dist/py/documentation/future.txt Tue Apr 19 13:38:37 2005 @@ -343,7 +343,7 @@ visions would make the link-checker a snap. It could serve as a nice script in our ``example`` tree. -.. _`getting started`: getting_started.html +.. _`getting started`: getting-started.html .. _`restructured text`: http://docutils.sourceforge.net/docs/user/rst/quickref.html .. _`python standard library`: http://www.python.org/doc/2.3.4/lib/lib.html .. _`xpython EuroPython 2004 talk`: http://codespeak.net/svn/user/hpk/talks/xpython-talk.txt Deleted: /py/dist/py/documentation/getting_started.txt ============================================================================== --- /py/dist/py/documentation/getting_started.txt Tue Apr 19 13:38:37 2005 +++ (empty file) @@ -1,122 +0,0 @@ -Getting started with the py lib -=============================== - -.. contents:: -.. sectnum:: - -Obtaining the current py lib -============================ - -Due to the nature of its innovative goals `the py lib`_ can't be -easily released without a certain API consistency. Nevertheless, -the API is pretty stable in many respects and very -well tested. So we invite you to participate and -use it - especially if you share `the frustrations with -current python package development`_. - -.. _`the py lib`: index.html - -getting it via subversion -------------------------- - -Checkout the py lib distribution tree with subversion, e.g. use:: - - svn co http://codespeak.net/svn/py/dist py-dist - -to checkout the code, documentation, tool and example tree -into a ``py-dist`` checkout directory. Your naming desire may vary -for your local checkout directory. - -If you experience problems with the subversion checkout e.g. -because you have a http-proxy in between that doesn't proxy -DAV requests you can try to use "codespeak.net:8080" instead -of just "codespeak.net". Alternatively, you may tweak -your local subversion installation. - -setting it up -------------- - -You need to put the checkout-directory into your ``PYTHONPATH`` -and you want to have the ``py-dist/py/bin/py.test`` script in -your system path, which lets you execute test files and directories. - -There already is a convenient way for Bash/Shell based systems -to setup the ``PYTHONPATH`` as well as the shell ``PATH``, insert:: - - eval `python ~/path/to/py-dist/py/env.py` - -into your ``.bash_profile``. Of course, you need to -specify your own checkout-directory. - -If you know of a good developer-style way of doing the -equivalent on win32 (non-cygwin) environments, tell us_. - -And no, we don't provide a distutils-install until -we have settled on a convenient way to upgrade seamlessly -via an `svn up` while at the same time allowing -installs/upgrades via the distutils `setup.py` way. - -upgrading it ------------- - -Well, easy. Go to your checkout directory and issue:: - - svn up - -have fun and `get an account`_ :-) - - -Participating in development -============================ - -Coding and communication ------------------------- - -We are practicing what could be called documentation, -vision, discussion and automated test driven development. -In the `future`_ book we try to layout visions and ideas for -the near coding feature to give a means for preliminary -feedback before code hits the ground. - -With our `coding style`_ we are mostly following -cpython guidance with some additional restrictions -some of which projects like twisted_ or zope3_ have -adopted in similar ways. - -.. _`zope3`: http://www.zope3.org -.. _twisted: http://www.twistedmatrix.org -.. _`get an account`: -.. _future: future.html - -The py-dev and py-svn mailing lists ------------------------------------ - -If you feel the desire to help tackle bugs and fixes, -or support resolution of some `frustrations`_ or to -just lurk in then please subscribe to one or both -of our mailinglists: - - `py-dev developers list`_ - -and our - - `py-svn general commit mailing list`_ - -get an account on codespeak ---------------------------- - -codespeak_ is generally deploying a very liberal committing -scheme. If you know someone who is active on codespeak already -or you are otherwise known in the community then you will most -probably just get access. But even if you are new to the -python developer community you may still get one if you -want to improve things and can be expected to honour the -style of coding and communication. - -.. _`coding style`: coding-style.html -.. _`frustrations`: -.. _`the frustrations with current python package development`: why_py.html#frustrations -.. _us: http://codespeak.net/mailman/listinfo/py-dev -.. _codespeak: http://codespeak.net/ -.. _`py-dev developers list`: http://codespeak.net/mailman/listinfo/py-dev -.. _`py-svn general commit mailing list`: http://codespeak.net/mailman/listinfo/py-svn Modified: py/dist/py/documentation/index.txt ============================================================================== --- py/dist/py/documentation/index.txt (original) +++ py/dist/py/documentation/index.txt Tue Apr 19 13:38:37 2005 @@ -5,11 +5,21 @@ development process addressing important deployment, versioning, testing and documentation issues - seen primarily from the perspective of a FOSS (Free and -Open Source) developer. At the same time, or maybe -just because of that, the py lib is very usable -for real life python applications.* +Open Source) developer. While the primary focus +is on developer activities this does not preclude +usage of the py lib for applications.* - `why what how py?`_ describes a bit of the motivation and background +Important Links: + + `getting started`_ quick start for using py.test and the py lib. + + **py-dev at codespeak net** the development mailing list + + **#pylib on irc.freenode.net** (upcoming) development IRC channel + + `why what how py?`_, describing motivation and background of the py lib + +Usage Documentation: `py.test`_ introduces to the new'n'easy **py.test** utility @@ -24,19 +34,17 @@ `future`_ handles development visions and plans for the near future. Note that some parts of these texts refer to future development and -do not exactly reflect the current state. - -**Welcome to documentation and test driven development** :-) - -There is some preliminary documentation for `getting started`_ +do not reflect the current state. **Welcome to documentation and +test driven development** :-) +.. _`getting started`: getting-started.html .. _`py.execnet`: execnet.html .. _`py.magic.greenlet`: greenlet.html .. _`py.test`: test.html .. _`py.xml`: xml.html .. _`Why What how py?`: why_py.html .. _`future`: future.html -.. _`getting started`: getting_started.html +.. _`getting started`: getting-started.html .. _`miscellaneous features`: misc.html Modified: py/dist/py/documentation/releasescheme.txt ============================================================================== --- py/dist/py/documentation/releasescheme.txt (original) +++ py/dist/py/documentation/releasescheme.txt Tue Apr 19 13:38:37 2005 @@ -38,7 +38,7 @@ 2) go to the `py-dist` directory and run `python setup.py install`. -.. _`getting started`: getting_started.html +.. _`getting started`: getting-started.html If you later want to upgrade your version of the py lib to the newest release you simply issue:: Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Tue Apr 19 13:38:37 2005 @@ -475,7 +475,7 @@ allow plain test functions (without being in a class) and allow classes to simply mean "grouping" of tests. -.. _`getting started`: getting_started.html +.. _`getting started`: getting-started.html .. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev From hpk at codespeak.net Tue Apr 19 13:46:31 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Apr 2005 13:46:31 +0200 (CEST) Subject: [py-svn] r10848 - in py/dist/py: execnet execnet/testing test/terminal Message-ID: <20050419114631.5EB2F27B40@code1.codespeak.net> Author: hpk Date: Tue Apr 19 13:46:31 2005 New Revision: 10848 Modified: py/dist/py/execnet/channel.py py/dist/py/execnet/gateway.py py/dist/py/execnet/message.py py/dist/py/execnet/testing/test_gateway.py py/dist/py/test/terminal/remote.py Log: refactoring/reshuffling execnet a bit Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Tue Apr 19 13:46:31 2005 @@ -26,12 +26,52 @@ flag = self.isclosed() and "closed" or "open" return "" % (self.id, flag) + # + # internal methods, called from the receiver thread + # + def _do_close(self, finalitem=EOFError()): + if self.id in self.gateway.channelfactory: + del self.gateway.channelfactory[self.id] + self._finalitem = finalitem + #for x in self._depchannel: + # x._close() + self._items.put(finalitem) + self._closeevent.set() + + + def _do_receivechannel(self, newid): + """ receive a remotely created new (sub)channel. """ + newchannel = Channel(self.gateway, newid) + self.gateway.channelfactory[newid] = newchannel + #self._depchannel.append(newchannel) + self._items.put(newchannel) + + def _do_receivedata(self, data): + if self._callback is not None: + self.gateway._dispatchcallback(self._callback, data) + else: + self._items.put(data) + + def _do_scheduleexec(self, sourcetask): + self.gateway._scheduleexec(channel=self, sourcetask=sourcetask) + + # + # public API for channel objects + # def isclosed(self): + """ return True if the channel is closed. A closed + channel may still hold items. + """ return self._closeevent.isSet() - def open(self, mode='w'): + def makefile(self, mode='w', proxyclose=True): + """ return a file-like object. Only supported mode right + now is 'w' for binary writes. By default, closing + the file will close the channel. Pass proxyclose=False + if you want to ignore file.close()s. + """ assert mode == 'w' - return ChannelFile(self) + return ChannelFile(channel=self, proxyclose=proxyclose) def close(self, error=None): """ close down this channel on both sides. """ @@ -41,38 +81,16 @@ put(Message.CHANNEL_CLOSE_ERROR(self.id, str(error))) else: put(Message.CHANNEL_CLOSE(self.id)) - self._close() + self._do_close() def remote_exec(self, source, stdout=None, stderr=None): self.gateway._remote_exec(self, source, stdout, stderr) - def _close(self, finalitem=EOFError()): - if self.id in self.gateway.channelfactory: - del self.gateway.channelfactory[self.id] - self._finalitem = finalitem - #for x in self._depchannel: - # x._close() - self._items.put(finalitem) - self._closeevent.set() - def newchannel(self): - """ return a new channel. """ + """ return a new independent channel.""" chan = self.gateway.channelfactory.new() return chan - def _receivechannel(self, newid): - """ receive a remotely created new (sub)channel. """ - newchannel = Channel(self.gateway, newid) - self.gateway.channelfactory[newid] = newchannel - #self._depchannel.append(newchannel) - self._items.put(newchannel) - - def _receivedata(self, data): - if self._callback is not None: - self.gateway._dispatchcallback(self._callback, data) - else: - self._items.put(data) - def waitclose(self, timeout): """ wait until this channel is closed. Note that a closed channel may still hold items that can be received or @@ -146,11 +164,7 @@ self._lock.release() def __contains__(self, key): - self._lock.acquire() - try: - return key in self._dict - finally: - self._lock.release() + return key in self._dict def values(self): self._lock.acquire() @@ -160,11 +174,8 @@ self._lock.release() def __getitem__(self, key): - self._lock.acquire() - try: - return self._dict[key] - finally: - self._lock.release() + return self._dict[key] + def __setitem__(self, key, value): self._lock.acquire() try: @@ -180,21 +191,24 @@ class ChannelFile: - def __init__(self, channel): + def __init__(self, channel, proxyclose=True): self.channel = channel + self._proxyclose = proxyclose def write(self, out): if self.channel.isclosed(): - return + return + # XXX: raise IOError, "channel %r already closed" %(self.channel,) self.channel.send(out) def flush(self): pass def close(self): - self.channel.close() + if self._proxyclose: + self.channel.close() def __repr__(self): state = self.channel.isclosed() and 'closed' or 'open' - return '' %(self.channel.id, state) + return '' %(self.channel.id, state) Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Tue Apr 19 13:46:31 2005 @@ -19,7 +19,7 @@ assert Message and ChannelFactory, "Import/Configuration Error" import os -debug = 0 # open('/tmp/execnet-debug-%d' % os.getpid() , 'wa') +debug = open('/tmp/execnet-debug-%d' % os.getpid() , 'wa') sysex = (KeyboardInterrupt, SystemExit) Modified: py/dist/py/execnet/message.py ============================================================================== --- py/dist/py/execnet/message.py (original) +++ py/dist/py/execnet/message.py Tue Apr 19 13:46:31 2005 @@ -65,7 +65,7 @@ # executes in receiver thread gateway._stopexec() for x in gateway.channelfactory.values(): - x._close() + x._do_close() gateway._outgoing.put(self.STOP_RECEIVING()) raise SystemExit def post_sent(self, gateway, excinfo=None): @@ -81,37 +81,40 @@ # already. With sockets closing it would raise # a Transport Not Connected exception for x in gateway.channelfactory.values(): - x._close() + x._do_close() raise SystemExit def post_sent(self, gateway, excinfo=None): + # after we sent STOP_RECEIVING we don't + # want to write anything more anymore. gateway.io.close_write() raise SystemExit class CHANNEL_OPEN(Message): def received(self, gateway): channel = gateway.channelfactory.new(self.channelid) - gateway._scheduleexec(channel, self.data) + channel._do_scheduleexec(self.data) class CHANNEL_NEW(Message): def received(self, gateway): newid = self.data channel = gateway.channelfactory[self.channelid] - channel._receivechannel(newid) + channel._do_receivechannel(newid) class CHANNEL_DATA(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] - channel._receivedata(self.data) + channel._do_receivedata(self.data) class CHANNEL_CLOSE(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] - channel._close() + channel._do_close() class CHANNEL_CLOSE_ERROR(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] - channel._close(gateway.RemoteError(self.data)) + channel._do_close(gateway.RemoteError(self.data)) + classes = [x for x in locals().values() if hasattr(x, '__bases__')] classes.sort(lambda x,y : cmp(x.__name__, y.__name__)) i = 0 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 Tue Apr 19 13:46:31 2005 @@ -77,12 +77,12 @@ class BasicRemoteExecution: def test_correct_setup(self): - for x in 'sender', 'receiver': # , 'executor': + for x in 'sender', 'receiver': assert self.gw.pool.getstarted(x) def test_remote_exec_waitclose(self): channel = self.gw.remote_exec('pass') - channel.waitclose(timeout=3.0) + channel.waitclose(timeout=1.0) def test_remote_exec_channel_anonymous(self): channel = self.gw.remote_exec(''' @@ -111,14 +111,14 @@ assert x == 42 py.test.raises(gateway.RemoteError, channel.receive) - def test_channel_close(self): + def test_channel__do_close(self): channel = self.gw.channelfactory.new() - channel._close() + channel._do_close() channel.waitclose(0.1) - def test_channel_close_error(self): + def test_channel__do_close_error(self): channel = self.gw.channelfactory.new() - channel._close(gateway.RemoteError("error")) + channel._do_close(gateway.RemoteError("error")) py.test.raises(gateway.RemoteError, channel.waitclose, 0.01) def test_channel_iter(self): @@ -206,6 +206,17 @@ s = subl[0] assert s.strip() == str(i) + def future_test_channel_file(self): + self.gw.remote_exec(""" + f = channel.makefile() + print >>f, "hello world" + f.close() + channel.send(42) + """) + for x in channel.receive(): + print x + assert 0 + class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): #disabled = True def test_many_popen(self): Modified: py/dist/py/test/terminal/remote.py ============================================================================== --- py/dist/py/test/terminal/remote.py (original) +++ py/dist/py/test/terminal/remote.py Tue Apr 19 13:46:31 2005 @@ -1,6 +1,5 @@ from __future__ import generators import py -from py.__impl__.execnet.channel import ChannelFile from py.__impl__.test.terminal.out import getout import sys From hpk at codespeak.net Tue Apr 19 14:13:44 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Apr 2005 14:13:44 +0200 (CEST) Subject: [py-svn] r10850 - in py/dist/py/execnet: . testing Message-ID: <20050419121344.A873327B42@code1.codespeak.net> Author: hpk Date: Tue Apr 19 14:13:44 2005 New Revision: 10850 Modified: py/dist/py/execnet/channel.py py/dist/py/execnet/gateway.py py/dist/py/execnet/message.py py/dist/py/execnet/testing/test_gateway.py Log: - move RemoteError to channel object. - test makefile() Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Tue Apr 19 14:13:44 2005 @@ -3,9 +3,22 @@ if 'Message' not in globals(): from py.__impl__.execnet.message import Message +class RemoteError(EOFError): + """ Contains an Exceptions from the other side. """ + def __init__(self, formatted): + self.formatted = formatted + EOFError.__init__(self) + + def __str__(self): + return self.formatted + + def __repr__(self): + return "%s: %s" %(self.__class__.__name__, self.formatted) + class Channel(object): """Communication channel between two possibly remote threads of code. """ _callback = None + RemoteError = RemoteError def __init__(self, gateway, id): assert isinstance(id, int) self.gateway = gateway @@ -64,13 +77,13 @@ """ return self._closeevent.isSet() - def makefile(self, mode='w', proxyclose=True): + def makefile(self, mode='w', proxyclose=False): """ return a file-like object. Only supported mode right - now is 'w' for binary writes. By default, closing - the file will close the channel. Pass proxyclose=False - if you want to ignore file.close()s. + now is 'w' for binary writes. If you want to have + a subsequent file.close() mean to close the channel + as well, then pass proxyclose=True. """ - assert mode == 'w' + assert mode == 'w', "mode %r not availabe" %(mode,) return ChannelFile(channel=self, proxyclose=proxyclose) def close(self, error=None): @@ -95,13 +108,13 @@ """ wait until this channel is closed. Note that a closed channel may still hold items that can be received or send. Also note that exceptions from the other side will be - reraised as gateway.RemoteError exceptions containing + reraised as channel.RemoteError exceptions containing a textual representation of the remote traceback. """ self._closeevent.wait(timeout=timeout) if not self._closeevent.isSet(): raise IOError, "Timeout" - if isinstance(self._finalitem, self.gateway.RemoteError): + if isinstance(self._finalitem, self.RemoteError): raise self._finalitem def send(self, item): @@ -119,7 +132,7 @@ """receives an item that was sent from the other side, possibly blocking if there is none. Note that exceptions from the other side will be - reraised as gateway.RemoteError exceptions containing + reraised as channel.RemoteError exceptions containing a textual representation of the remote traceback. """ if self._callback: Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Tue Apr 19 14:13:44 2005 @@ -23,21 +23,8 @@ sysex = (KeyboardInterrupt, SystemExit) -class RemoteError(EOFError): - """ Contains an Exceptions from the other side. """ - def __init__(self, formatted): - self.formatted = formatted - EOFError.__init__(self) - - def __str__(self): - return self.formatted - - def __repr__(self): - return "%s: %s" %(self.__class__.__name__, self.formatted) - class Gateway(object): num_worker_threads = 2 - RemoteError = RemoteError ThreadOut = ThreadOut def __init__(self, io, startcount=2, maxthreads=None): Modified: py/dist/py/execnet/message.py ============================================================================== --- py/dist/py/execnet/message.py (original) +++ py/dist/py/execnet/message.py Tue Apr 19 14:13:44 2005 @@ -113,7 +113,7 @@ class CHANNEL_CLOSE_ERROR(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] - channel._do_close(gateway.RemoteError(self.data)) + channel._do_close(channel.RemoteError(self.data)) classes = [x for x in locals().values() if hasattr(x, '__bases__')] classes.sort(lambda x,y : cmp(x.__name__, y.__name__)) 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 Tue Apr 19 14:13:44 2005 @@ -95,7 +95,7 @@ def test_channel_close_and_then_receive_error(self): channel = self.gw.remote_exec('raise ValueError') - py.test.raises(gateway.RemoteError, channel.receive) + py.test.raises(channel.RemoteError, channel.receive) def test_channel_finish_and_then_EOFError(self): channel = self.gw.remote_exec('channel.send(42)') @@ -109,7 +109,7 @@ channel = self.gw.remote_exec('channel.send(42) ; raise ValueError') x = channel.receive() assert x == 42 - py.test.raises(gateway.RemoteError, channel.receive) + py.test.raises(channel.RemoteError, channel.receive) def test_channel__do_close(self): channel = self.gw.channelfactory.new() @@ -118,8 +118,8 @@ def test_channel__do_close_error(self): channel = self.gw.channelfactory.new() - channel._do_close(gateway.RemoteError("error")) - py.test.raises(gateway.RemoteError, channel.waitclose, 0.01) + channel._do_close(channel.RemoteError("error")) + py.test.raises(channel.RemoteError, channel.waitclose, 0.01) def test_channel_iter(self): channel = self.gw.remote_exec(""" @@ -206,16 +206,28 @@ s = subl[0] assert s.strip() == str(i) - def future_test_channel_file(self): - self.gw.remote_exec(""" + def test_channel_file(self): + channel = self.gw.remote_exec(""" f = channel.makefile() print >>f, "hello world" f.close() channel.send(42) """) - for x in channel.receive(): - print x - assert 0 + first = channel.receive() + channel.receive() + assert first.strip() == 'hello world' + second = channel.receive() + assert second == 42 + + def test_channel_file_proxyclose(self): + channel = self.gw.remote_exec(""" + f = channel.makefile(proxyclose=True) + print >>f, "hello world" + f.close() + channel.send(42) + """) + first = channel.receive() + channel.receive() + assert first.strip() == 'hello world' + py.test.raises(EOFError, channel.receive) class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): #disabled = True From hpk at codespeak.net Tue Apr 19 14:35:47 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Apr 2005 14:35:47 +0200 (CEST) Subject: [py-svn] r10854 - in py/dist/py/execnet: . testing Message-ID: <20050419123547.9C86527B43@code1.codespeak.net> Author: hpk Date: Tue Apr 19 14:35:47 2005 New Revision: 10854 Modified: py/dist/py/execnet/channel.py py/dist/py/execnet/gateway.py py/dist/py/execnet/register.py py/dist/py/execnet/testing/test_gateway.py Log: making execnet work on remote sides that don't have a py lib Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Tue Apr 19 14:35:47 2005 @@ -42,13 +42,13 @@ # # internal methods, called from the receiver thread # - def _do_close(self, finalitem=EOFError()): + def _do_close(self, stickyerror=EOFError()): if self.id in self.gateway.channelfactory: del self.gateway.channelfactory[self.id] - self._finalitem = finalitem + self._stickyerror = stickyerror #for x in self._depchannel: # x._close() - self._items.put(finalitem) + self._items.put(ENDMARKER) self._closeevent.set() @@ -105,23 +105,25 @@ return chan def waitclose(self, timeout): - """ wait until this channel is closed. Note that a closed - channel may still hold items that can be received or - send. Also note that exceptions from the other side will be - reraised as channel.RemoteError exceptions containing - a textual representation of the remote traceback. + """ wait until this channel is closed. A closed + channel may still hold receiveable items. waitclose() + reraises exceptions from executing code on the other + side as channel.RemoteErrors containing a a textual + representation of the remote traceback. """ self._closeevent.wait(timeout=timeout) if not self._closeevent.isSet(): raise IOError, "Timeout" - if isinstance(self._finalitem, self.RemoteError): - raise self._finalitem + if isinstance(self._stickyerror, self.RemoteError): + raise self._stickyerror def send(self, item): """sends the given item to the other side of the channel, possibly blocking if the sender queue is full. Note that an item needs to be marshallable. """ + if self.isclosed(): + raise IOError, "cannot send via %r anymore" %(self,) if isinstance(item, Channel): data = Message.CHANNEL_NEW(self.id, item.id) else: @@ -138,10 +140,11 @@ if self._callback: raise IOError("calling receive() on channel with callback") x = self._items.get() - if isinstance(x, EOFError): - self._items.put(x) - raise x - return x + if x is ENDMARKER: + self._items.put(x) # for other receivers + raise self._stickyerror + else: + return x def __iter__(self): return self @@ -156,6 +159,8 @@ # helpers # +ENDMARKER = object() + class ChannelFactory(object): def __init__(self, gateway, startcount=1): self._dict = dict() Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Tue Apr 19 14:35:47 2005 @@ -7,7 +7,7 @@ # XXX the following line should not be here -if 'Message' not in globals(): +if 'ThreadOut' not in globals(): import py from py.code import Source from py.__impl__.execnet.channel import ChannelFactory, Channel @@ -16,10 +16,8 @@ WorkerPool = py._thread.WorkerPool NamedThreadPool = py._thread.NamedThreadPool -assert Message and ChannelFactory, "Import/Configuration Error" - import os -debug = open('/tmp/execnet-debug-%d' % os.getpid() , 'wa') +debug = 0 # open('/tmp/execnet-debug-%d' % os.getpid() , 'wa') sysex = (KeyboardInterrupt, SystemExit) Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Tue Apr 19 14:35:47 2005 @@ -12,12 +12,12 @@ # in a sufficient version startup_modules = [ + 'py.__impl__.thread.io', + 'py.__impl__.thread.pool', 'py.__impl__.execnet.inputoutput', 'py.__impl__.execnet.gateway', - 'py.__impl__.execnet.channel', 'py.__impl__.execnet.message', - 'py.__impl__.thread.io', - 'py.__impl__.thread.pool', + 'py.__impl__.execnet.channel', ] def getsource(dottedname): 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 Tue Apr 19 14:35:47 2005 @@ -84,6 +84,11 @@ channel = self.gw.remote_exec('pass') channel.waitclose(timeout=1.0) + def test_remote_exec_error_after_close(self): + channel = self.gw.remote_exec('pass') + channel.waitclose(timeout=1.0) + py.test.raises(IOError, channel.send, 0) + def test_remote_exec_channel_anonymous(self): channel = self.gw.remote_exec(''' obj = channel.receive() From hpk at codespeak.net Tue Apr 19 14:39:56 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Apr 2005 14:39:56 +0200 (CEST) Subject: [py-svn] r10855 - py/dist/py/execnet/testing Message-ID: <20050419123956.08C4627B43@code1.codespeak.net> Author: hpk Date: Tue Apr 19 14:39:55 2005 New Revision: 10855 Modified: py/dist/py/execnet/testing/test_gateway.py Log: add a test that makes sure that 'py' is not imported on the other side too early 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 Tue Apr 19 14:39:55 2005 @@ -80,6 +80,15 @@ for x in 'sender', 'receiver': assert self.gw.pool.getstarted(x) + def test_correct_setup_no_py(self): + channel = self.gw.remote_exec(""" + import sys + channel.send(sys.modules.keys()) + """) + remotemodules = channel.receive() + assert 'py' not in remotemodules, ( + "py should not be imported on remote side") + def test_remote_exec_waitclose(self): channel = self.gw.remote_exec('pass') channel.waitclose(timeout=1.0) From hpk at codespeak.net Tue Apr 19 17:14:03 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Apr 2005 17:14:03 +0200 (CEST) Subject: [py-svn] r10860 - in py/dist/py/execnet: . testing Message-ID: <20050419151403.BDD3927B43@code1.codespeak.net> Author: hpk Date: Tue Apr 19 17:14:03 2005 New Revision: 10860 Modified: py/dist/py/execnet/channel.py py/dist/py/execnet/gateway.py py/dist/py/execnet/message.py py/dist/py/execnet/register.py py/dist/py/execnet/testing/test_gateway.py Log: rename internal methods to make code more readable. Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Tue Apr 19 17:14:03 2005 @@ -42,7 +42,7 @@ # # internal methods, called from the receiver thread # - def _do_close(self, stickyerror=EOFError()): + def _local_close(self, stickyerror=EOFError()): if self.id in self.gateway.channelfactory: del self.gateway.channelfactory[self.id] self._stickyerror = stickyerror @@ -52,21 +52,21 @@ self._closeevent.set() - def _do_receivechannel(self, newid): + def _local_receivechannel(self, newid): """ receive a remotely created new (sub)channel. """ newchannel = Channel(self.gateway, newid) self.gateway.channelfactory[newid] = newchannel #self._depchannel.append(newchannel) self._items.put(newchannel) - def _do_receivedata(self, data): + def _local_receivedata(self, data): if self._callback is not None: - self.gateway._dispatchcallback(self._callback, data) + self.gateway._local_dispatchcallback(self._callback, data) else: self._items.put(data) - def _do_scheduleexec(self, sourcetask): - self.gateway._scheduleexec(channel=self, sourcetask=sourcetask) + def _local_schedulexec(self, sourcetask): + self.gateway._local_schedulexec(channel=self, sourcetask=sourcetask) # # public API for channel objects @@ -94,10 +94,7 @@ put(Message.CHANNEL_CLOSE_ERROR(self.id, str(error))) else: put(Message.CHANNEL_CLOSE(self.id)) - self._do_close() - - def remote_exec(self, source, stdout=None, stderr=None): - self.gateway._remote_exec(self, source, stdout, stderr) + self._local_close() def newchannel(self): """ return a new independent channel.""" @@ -164,7 +161,7 @@ class ChannelFactory(object): def __init__(self, gateway, startcount=1): self._dict = dict() - self._lock = threading.RLock() + self._lock = threading.Lock() self.gateway = gateway self.count = startcount Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Tue Apr 19 17:14:03 2005 @@ -45,34 +45,45 @@ return "<%s %s/%s (%d active channels)>" %(self.__class__.__name__, R, S, i) - def _stopexec(self): - #self.pool.prunestopped() - self._execpool.shutdown() + def _local_trystopexec(self): + try: + self._execpool.shutdown() + except IOError: + return False + return True def exit(self): + """ initiate full gateway teardown. + Note that the teardown of sender/receiver threads happens + asynchronously and timeouts on stopping worker execution + threads are ignored. You can issue join() or join(joinexec=False) + if you want to wait for a full teardown (possibly excluding + execution threads). + """ # note that threads may still be scheduled to start # during our execution! self._exitlock.acquire() try: if self.running: self.running = False - self._stopexec() - if self.pool.getstarted('sender'): - self._outgoing.put(Message.EXIT_GATEWAY()) - self.trace("exit procedure triggered, pid %d, gateway %r" % ( - os.getpid(), self)) + if not self.pool.getstarted('sender'): + raise IOError("sender thread not alive anymore!") + self._outgoing.put(Message.EXIT_GATEWAY()) + self.trace("exit procedure triggered, pid %d " % (os.getpid(),)) _gateways.remove(self) finally: self._exitlock.release() - def join(self): + def join(self, joinexec=True): current = threading.currentThread() for x in self.pool.getstarted(): if x != current: self.trace("joining %s" % x) x.join() - self._execpool.join() - self.trace("joining threads finished, current %r" % current) + self.trace("joining sender/reciver threads finished, current %r" % current) + if joinexec: + self._execpool.join() + self.trace("joining execution threads finished, current %r" % current) def trace(self, *args): if debug: @@ -125,11 +136,11 @@ finally: self.trace('leaving %r' % threading.currentThread()) - def _redirect_thread_output(self, outid, errid): + def _local_redirect_thread_output(self, outid, errid): l = [] for name, id in ('stdout', outid), ('stderr', errid): if id: - channel = self._makechannel(outid) + channel = self._local_makechannelobject(outid) out = ThreadOut(sys, name) out.setwritefunc(channel.send) l.append((out, channel)) @@ -139,7 +150,7 @@ channel.close() return close - def _makechannel(self, newid): + def _local_makechannelobject(self, newid): newchannel = Channel(self, newid) self.channelfactory[newid] = newchannel return newchannel @@ -149,7 +160,7 @@ try: loc = { 'channel' : channel } self.trace("execution starts:", repr(source)[:50]) - close = self._redirect_thread_output(outid, errid) + close = self._local_redirect_thread_output(outid, errid) try: co = compile(source+'\n', '', 'exec', 4096) exec co in loc @@ -167,31 +178,17 @@ else: channel.close() - def _scheduleexec(self, channel, sourcetask): + def _local_schedulexec(self, channel, sourcetask): self.trace("dispatching exec") self._execpool.dispatch(self.thread_executor, channel, sourcetask) - def _dispatchcallback(self, callback, data): + def _local_dispatchcallback(self, callback, data): # XXX this should run in a separate thread because # we might otherwise block the receiver thread # where we get called from callback(data) - def _remote_exec(self, channel, source, stdout=None, stderr=None): - try: - source = str(Source(source)) - except NameError: - try: - import py - source = str(py.code.Source(source)) - except ImportError: - pass - outid = self._redirectchannelid(stdout) - errid = self._redirectchannelid(stderr) - self._outgoing.put(Message.CHANNEL_OPEN(channel.id, - (source, outid, errid))) - - def _redirectchannelid(self, callback): + def _newredirectchannelid(self, callback): if callback is None: return if hasattr(callback, 'write'): @@ -210,15 +207,26 @@ """ return new channel object. """ return self.channelfactory.new() - def remote_exec(self, source, stdout=None, stderr=None): + def remote_exec(self, source, stdout=None, stderr=None, channel=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. If a channel is provided - is will be returned as well. + provided a new channel will be created. """ - channel = self.newchannel() - channel.remote_exec(source, stdout=stdout, stderr=stderr) + try: + source = str(Source(source)) + except NameError: + try: + import py + source = str(py.code.Source(source)) + except ImportError: + pass + if channel is None: + channel = self.newchannel() + outid = self._newredirectchannelid(stdout) + errid = self._newredirectchannelid(stderr) + self._outgoing.put(Message.CHANNEL_OPEN(channel.id, + (source, outid, errid))) return channel def remote_redirect(self, stdout=None, stderr=None): Modified: py/dist/py/execnet/message.py ============================================================================== --- py/dist/py/execnet/message.py (original) +++ py/dist/py/execnet/message.py Tue Apr 19 17:14:03 2005 @@ -63,14 +63,14 @@ class EXIT_GATEWAY(Message): def received(self, gateway): # executes in receiver thread - gateway._stopexec() for x in gateway.channelfactory.values(): - x._do_close() + x._local_close() gateway._outgoing.put(self.STOP_RECEIVING()) + gateway._local_trystopexec() raise SystemExit def post_sent(self, gateway, excinfo=None): - gateway._stopexec() # executes in sender thread + gateway._local_trystopexec() gateway.io.close_write() raise SystemExit @@ -81,7 +81,7 @@ # already. With sockets closing it would raise # a Transport Not Connected exception for x in gateway.channelfactory.values(): - x._do_close() + x._local_close() raise SystemExit def post_sent(self, gateway, excinfo=None): # after we sent STOP_RECEIVING we don't @@ -92,28 +92,28 @@ class CHANNEL_OPEN(Message): def received(self, gateway): channel = gateway.channelfactory.new(self.channelid) - channel._do_scheduleexec(self.data) + channel._local_schedulexec(self.data) class CHANNEL_NEW(Message): def received(self, gateway): newid = self.data channel = gateway.channelfactory[self.channelid] - channel._do_receivechannel(newid) + channel._local_receivechannel(newid) class CHANNEL_DATA(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] - channel._do_receivedata(self.data) + channel._local_receivedata(self.data) class CHANNEL_CLOSE(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] - channel._do_close() + channel._local_close() class CHANNEL_CLOSE_ERROR(Message): def received(self, gateway): channel = gateway.channelfactory[self.channelid] - channel._do_close(channel.RemoteError(self.data)) + channel._local_close(channel.RemoteError(self.data)) classes = [x for x in locals().values() if hasattr(x, '__bases__')] classes.sort(lambda x,y : cmp(x.__name__, y.__name__)) Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Tue Apr 19 17:14:03 2005 @@ -42,7 +42,7 @@ """ bootstrap = ["we_are_remote=True", extra] bootstrap += [getsource(x) for x in startup_modules] - bootstrap += [io.server_stmt, "Gateway(io=io, startcount=2).join()",] + bootstrap += [io.server_stmt, "Gateway(io=io, startcount=2).join(joinexec=False)",] source = "\n".join(bootstrap) self.trace("sending gateway bootstrap code") io.write('%r\n' % source) 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 Tue Apr 19 17:14:03 2005 @@ -125,14 +125,14 @@ assert x == 42 py.test.raises(channel.RemoteError, channel.receive) - def test_channel__do_close(self): + def test_channel__local_close(self): channel = self.gw.channelfactory.new() - channel._do_close() + channel._local_close() channel.waitclose(0.1) - def test_channel__do_close_error(self): + def test_channel__local_close_error(self): channel = self.gw.channelfactory.new() - channel._do_close(channel.RemoteError("error")) + channel._local_close(channel.RemoteError("error")) py.test.raises(channel.RemoteError, channel.waitclose, 0.01) def test_channel_iter(self): @@ -166,7 +166,7 @@ channel = self.gw.newchannel() l = [] channel.setcallback(l.append) - channel.remote_exec(''' + self.gw.remote_exec(channel=channel, source=''' channel.send(42) channel.send(13) ''') @@ -243,6 +243,22 @@ assert first.strip() == 'hello world' py.test.raises(EOFError, channel.receive) +#class TestBlockingIssues: +# def test_join_blocked_execution_gateway(self): +# gateway = py.execnet.PopenGateway() +# channel = gateway.remote_exec(""" +# time.sleep(5.0) +# """) +# def doit(): +# gateway.exit() +# gateway.join(joinexec=True) +# return 17 +# +# pool = py._thread.WorkerPool() +# reply = pool.dispatch(doit) +# x = reply.get(timeout=1.0) +# assert x == 17 + class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): #disabled = True def test_many_popen(self): From hpk at codespeak.net Tue Apr 19 17:53:27 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Apr 2005 17:53:27 +0200 (CEST) Subject: [py-svn] r10862 - in py/dist/py/execnet: . testing Message-ID: <20050419155327.A942F27B43@code1.codespeak.net> Author: hpk Date: Tue Apr 19 17:53:27 2005 New Revision: 10862 Modified: py/dist/py/execnet/channel.py py/dist/py/execnet/gateway.py py/dist/py/execnet/testing/test_gateway.py Log: minimize API a bit to remove ambiguity and semantic problems. - no setcallback() anymore! Instead you have to create a channel like so: channel = gateway.newchannel(receiver=mycallback) and can pass it on to gateway.remote_exec gateway.remote_exec(channel=channel, source=""" ... """) This avoids subtle problems where you set the 'callback' while some items were already received and sit in the Queue. - some more internal renaming. Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Tue Apr 19 17:53:27 2005 @@ -17,24 +17,16 @@ class Channel(object): """Communication channel between two possibly remote threads of code. """ - _callback = None RemoteError = RemoteError - def __init__(self, gateway, id): + def __init__(self, gateway, id, receiver=None): assert isinstance(id, int) self.gateway = gateway self.id = id + self._receiver = receiver self._items = Queue.Queue() self._closeevent = threading.Event() #self._depchannel = [] - def setcallback(self, callback): - """ set this channel to invoke the given callback - for each received data item. Note that you - cannot rely on the callback to run in - any particular thread. - """ - self._callback = callback - def __repr__(self): flag = self.isclosed() and "closed" or "open" return "" % (self.id, flag) @@ -43,29 +35,27 @@ # internal methods, called from the receiver thread # def _local_close(self, stickyerror=EOFError()): - if self.id in self.gateway.channelfactory: - del self.gateway.channelfactory[self.id] + self.gateway._del_channelmapping(self.id) self._stickyerror = stickyerror - #for x in self._depchannel: - # x._close() self._items.put(ENDMARKER) self._closeevent.set() - def _local_receivechannel(self, newid): """ receive a remotely created new (sub)channel. """ + # executes in receiver thread newchannel = Channel(self.gateway, newid) self.gateway.channelfactory[newid] = newchannel - #self._depchannel.append(newchannel) - self._items.put(newchannel) + self._local_receivedata(newchannel) def _local_receivedata(self, data): - if self._callback is not None: - self.gateway._local_dispatchcallback(self._callback, data) + # executes in receiver thread + if self._receiver is not None: + self._receiver(data) else: self._items.put(data) def _local_schedulexec(self, sourcetask): + # executes in receiver thread self.gateway._local_schedulexec(channel=self, sourcetask=sourcetask) # @@ -96,11 +86,6 @@ put(Message.CHANNEL_CLOSE(self.id)) self._local_close() - def newchannel(self): - """ return a new independent channel.""" - chan = self.gateway.channelfactory.new() - return chan - def waitclose(self, timeout): """ wait until this channel is closed. A closed channel may still hold receiveable items. waitclose() @@ -120,7 +105,7 @@ Note that an item needs to be marshallable. """ if self.isclosed(): - raise IOError, "cannot send via %r anymore" %(self,) + raise IOError, "cannot send to %r" %(self,) if isinstance(item, Channel): data = Message.CHANNEL_NEW(self.id, item.id) else: @@ -134,8 +119,8 @@ reraised as channel.RemoteError exceptions containing a textual representation of the remote traceback. """ - if self._callback: - raise IOError("calling receive() on channel with callback") + if self._receiver: + raise IOError("calling receive() on channel with receiver callback") x = self._items.get() if x is ENDMARKER: self._items.put(x) # for other receivers @@ -161,48 +146,48 @@ class ChannelFactory(object): def __init__(self, gateway, startcount=1): self._dict = dict() - self._lock = threading.Lock() + self._writelock = threading.Lock() self.gateway = gateway self.count = startcount - def new(self, id=None): + def new(self, id=None, receiver=None): """ create a new Channel with 'id' (or create new id if None). """ - self._lock.acquire() + self._writelock.acquire() try: if id is None: id = self.count self.count += 2 - channel = Channel(self.gateway, id) + channel = Channel(self.gateway, id, receiver=receiver) self._dict[id] = channel return channel finally: - self._lock.release() + self._writelock.release() def __contains__(self, key): return key in self._dict def values(self): - self._lock.acquire() + self._writelock.acquire() try: return self._dict.values() finally: - self._lock.release() + self._writelock.release() def __getitem__(self, key): return self._dict[key] def __setitem__(self, key, value): - self._lock.acquire() + self._writelock.acquire() try: self._dict[key] = value finally: - self._lock.release() + self._writelock.release() def __delitem__(self, key): - self._lock.acquire() + self._writelock.acquire() try: del self._dict[key] finally: - self._lock.release() + self._writelock.release() class ChannelFile: @@ -211,9 +196,6 @@ self._proxyclose = proxyclose def write(self, out): - if self.channel.isclosed(): - return - # XXX: raise IOError, "channel %r already closed" %(self.channel,) self.channel.send(out) def flush(self): Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Tue Apr 19 17:53:27 2005 @@ -6,6 +6,15 @@ import atexit +# note that the whole code of this module (as well as some +# other modules) execute not only on the local side but +# also on any gateway's remote side. On such remote sides +# we cannot assume the py library to be there and +# InstallableGateway.remote_bootstrap_gateway() (located +# in register.py) will take care to send source fragments +# 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 if 'ThreadOut' not in globals(): import py @@ -52,38 +61,6 @@ return False return True - def exit(self): - """ initiate full gateway teardown. - Note that the teardown of sender/receiver threads happens - asynchronously and timeouts on stopping worker execution - threads are ignored. You can issue join() or join(joinexec=False) - if you want to wait for a full teardown (possibly excluding - execution threads). - """ - # note that threads may still be scheduled to start - # during our execution! - self._exitlock.acquire() - try: - if self.running: - self.running = False - if not self.pool.getstarted('sender'): - raise IOError("sender thread not alive anymore!") - self._outgoing.put(Message.EXIT_GATEWAY()) - self.trace("exit procedure triggered, pid %d " % (os.getpid(),)) - _gateways.remove(self) - finally: - self._exitlock.release() - - def join(self, joinexec=True): - current = threading.currentThread() - for x in self.pool.getstarted(): - if x != current: - self.trace("joining %s" % x) - x.join() - self.trace("joining sender/reciver threads finished, current %r" % current) - if joinexec: - self._execpool.join() - self.trace("joining execution threads finished, current %r" % current) def trace(self, *args): if debug: @@ -182,11 +159,13 @@ self.trace("dispatching exec") self._execpool.dispatch(self.thread_executor, channel, sourcetask) - def _local_dispatchcallback(self, callback, data): - # XXX this should run in a separate thread because - # we might otherwise block the receiver thread - # where we get called from - callback(data) + def _del_channelmapping(self, id, ignoremissing=False): + self.trace("deleting channel mapping %r" %(id,)) + try: + del self.channelfactory[id] + except KeyError: + if not ignoremissing: + raise def _newredirectchannelid(self, callback): if callback is None: @@ -194,8 +173,7 @@ if hasattr(callback, 'write'): callback = callback.write assert callable(callback) - chan = self.newchannel() - chan.setcallback(callback) + chan = self.newchannel(receiver=callback) return chan.id # _____________________________________________________________________ @@ -203,9 +181,12 @@ # High Level Interface # _____________________________________________________________________ # - def newchannel(self): - """ return new channel object. """ - return self.channelfactory.new() + 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 remote_exec(self, source, stdout=None, stderr=None, channel=None): """ return channel object for communicating with the asynchronously @@ -237,8 +218,7 @@ clist = [] for name, out in ('stdout', stdout), ('stderr', stderr): if out: - outchannel = self.newchannel() - outchannel.setcallback(getattr(out, 'write', out)) + outchannel = self.newchannel(receiver=getattr(out, 'write', out)) channel = self.remote_exec(""" import sys outchannel = channel.receive() @@ -259,6 +239,39 @@ c.waitclose(1.0) return Handle() + def exit(self): + """ initiate full gateway teardown. + Note that the teardown of sender/receiver threads happens + asynchronously and timeouts on stopping worker execution + threads are ignored. You can issue join() or join(joinexec=False) + if you want to wait for a full teardown (possibly excluding + execution threads). + """ + # note that threads may still be scheduled to start + # during our execution! + self._exitlock.acquire() + try: + if self.running: + self.running = False + if not self.pool.getstarted('sender'): + raise IOError("sender thread not alive anymore!") + self._outgoing.put(Message.EXIT_GATEWAY()) + self.trace("exit procedure triggered, pid %d " % (os.getpid(),)) + _gateways.remove(self) + finally: + self._exitlock.release() + + def join(self, joinexec=True): + current = threading.currentThread() + for x in self.pool.getstarted(): + if x != current: + self.trace("joining %s" % x) + x.join() + self.trace("joining sender/reciver threads finished, current %r" % current) + if joinexec: + self._execpool.join() + self.trace("joining execution threads finished, current %r" % current) + def getid(gw, cache={}): name = gw.__class__.__name__ try: 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 Tue Apr 19 17:53:27 2005 @@ -145,7 +145,7 @@ def test_channel_passing_over_channel(self): channel = self.gw.remote_exec(''' - c = channel.newchannel() + c = channel.gateway.newchannel() channel.send(c) c.send(42) ''') @@ -163,40 +163,17 @@ newchan.waitclose(0.3) def test_channel_receiver_callback(self): - channel = self.gw.newchannel() l = [] - channel.setcallback(l.append) + channel = self.gw.newchannel(receiver=l.append) self.gw.remote_exec(channel=channel, source=''' channel.send(42) channel.send(13) + #channel.send(channel.gateway.newchannel()) ''') channel.waitclose(1.0) + assert len(l) assert l == [42,13] - def test_channel_receiver_callback_callback_then_not(self): - l = [] - channel = self.gw.remote_exec(''' - channel.receive() - channel.send(42) - channel.send(None) - channel.send(13) - ''') - - def receive(data): - if data is not None: - l.append(data) - else: - channel.setcallback(None) - - channel.setcallback(receive) - py.test.raises(IOError, "channel.receive()") - channel.send(1) # start signal - channel.waitclose(1.0) - assert l == [42] - value = channel.receive() - assert value == 13 - assert l == [42] - def test_remote_redirect_stdout(self): out = py.std.StringIO.StringIO() handle = self.gw.remote_redirect(stdout=out) @@ -232,6 +209,12 @@ second = channel.receive() assert second == 42 + def test_channel_file_write_error(self): + channel = self.gw.remote_exec("pass") + f = channel.makefile() + channel.waitclose(1.0) + py.test.raises(IOError, f.write, 'hello') + def test_channel_file_proxyclose(self): channel = self.gw.remote_exec(""" f = channel.makefile(proxyclose=True) From hpk at codespeak.net Tue Apr 19 17:55:18 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Apr 2005 17:55:18 +0200 (CEST) Subject: [py-svn] r10863 - in py/dist/py/execnet: . testing Message-ID: <20050419155518.B4D6927B43@code1.codespeak.net> Author: hpk Date: Tue Apr 19 17:55:18 2005 New Revision: 10863 Modified: py/dist/py/execnet/channel.py py/dist/py/execnet/testing/test_gateway.py Log: extended callback test to work with sent channels as well. Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Tue Apr 19 17:55:18 2005 @@ -25,7 +25,6 @@ self._receiver = receiver self._items = Queue.Queue() self._closeevent = threading.Event() - #self._depchannel = [] def __repr__(self): flag = self.isclosed() and "closed" or "open" 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 Tue Apr 19 17:55:18 2005 @@ -168,11 +168,12 @@ self.gw.remote_exec(channel=channel, source=''' channel.send(42) channel.send(13) - #channel.send(channel.gateway.newchannel()) + channel.send(channel.gateway.newchannel()) ''') channel.waitclose(1.0) - assert len(l) - assert l == [42,13] + assert len(l) == 3 + assert l[:2] == [42,13] + assert isinstance(l[2], channel.__class__) def test_remote_redirect_stdout(self): out = py.std.StringIO.StringIO() From hpk at codespeak.net Tue Apr 19 20:56:19 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Apr 2005 20:56:19 +0200 (CEST) Subject: [py-svn] r10872 - in py/dist/py/thread: . testing Message-ID: <20050419185619.BC3C527B43@code1.codespeak.net> Author: hpk Date: Tue Apr 19 20:56:19 2005 New Revision: 10872 Modified: py/dist/py/thread/pool.py py/dist/py/thread/testing/test_io.py py/dist/py/thread/testing/test_pool.py Log: streamline pool api a bit. Modified: py/dist/py/thread/pool.py ============================================================================== --- py/dist/py/thread/pool.py (original) +++ py/dist/py/thread/pool.py Tue Apr 19 20:56:19 2005 @@ -27,11 +27,15 @@ reply.setexcinfo(sys.exc_info()) else: reply.set(result) - self._pool._ready.append(self) + self._pool._ready[self] = True finally: del self._pool._alive[self] + try: + del self._pool._ready[self] + except KeyError: + pass - def handle(self, task): + def send(self, task): reply = Reply(task) self._queue.put(reply) return reply @@ -79,36 +83,26 @@ return result class WorkerPool(object): - _shutdown = False + _shuttingdown = False def __init__(self, maxthreads=None): self.maxthreads = maxthreads - self._numthreads = 0 - self._ready = [] + self._ready = {} self._alive = {} def dispatch(self, func, *args, **kwargs): - if self._shutdown: + if self._shuttingdown: raise IOError("WorkerPool is already shutting down") - task = (func, args, kwargs) try: - thread = self._ready.pop() - except IndexError: # pop from empty list + thread, _ = self._ready.popitem() + except KeyError: # pop from empty list thread = self._newthread() - return thread.handle(task) + return thread.send((func, args, kwargs)) - def shutdown(self, timeout=1.0): - if not self._shutdown: - self._shutdown = True - now = time.time() - while self._alive: - try: - thread = self._ready.pop() - except IndexError: - if now + timeout < time.time(): - raise IOError("Timeout: could not shut down WorkerPool") - time.sleep(0.1) - else: - thread.stop() + def shutdown(self): + if not self._shuttingdown: + self._shuttingdown = True + for t in self._alive.keys(): + t.stop() def _newthread(self): if self.maxthreads: @@ -120,11 +114,19 @@ self._alive[thread] = True return thread - def join(self): + def join(self, timeout=None): current = threading.currentThread() - for x in self._alive.keys(): - if current != x: - x.join() + now = time.time() + while self._alive: + try: + thread, _ = self._ready.popitem() + except KeyError: + if timeout and time.time() >= now + timeout: + raise IOError("timeout waiting for threads") + time.sleep(0.1) + else: + thread.join() + class NamedThreadPool: def __init__(self, **kw): Modified: py/dist/py/thread/testing/test_io.py ============================================================================== --- py/dist/py/thread/testing/test_io.py (original) +++ py/dist/py/thread/testing/test_io.py Tue Apr 19 20:56:19 2005 @@ -37,7 +37,6 @@ print id(l), self.out.delwritefunc() print 1 - self.out.setdefaultwriter(defaults.append) pool = WorkerPool() listlist = [] @@ -48,6 +47,7 @@ pool.shutdown() for name, value in self.out.__dict__.items(): print >>sys.stderr, "%s: %s" %(name, value) + pool.join(2.0) for i in range(num): item = listlist[i] assert item ==[str(id(item))] 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 Tue Apr 19 20:56:19 2005 @@ -15,6 +15,7 @@ q.get() assert len(pool._alive) == 4 pool.shutdown() + pool.join(timeout=1.0) assert len(pool._alive) == 0 assert len(pool._ready) == 0 @@ -56,7 +57,8 @@ def f(): py.std.time.sleep(0.5) pool.dispatch(f) - py.test.raises(IOError, pool.shutdown, 0.2) + pool.shutdown() + py.test.raises(IOError, pool.join, 0.2) def test_pool_clean_shutdown(): pool = WorkerPool() @@ -65,5 +67,6 @@ pool.dispatch(f) pool.dispatch(f) pool.shutdown() + pool.join(timeout=1.0) assert not pool._alive assert not pool._ready From hpk at codespeak.net Tue Apr 19 20:59:23 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Apr 2005 20:59:23 +0200 (CEST) Subject: [py-svn] r10873 - py/dist/py/execnet Message-ID: <20050419185923.5D5D727B43@code1.codespeak.net> Author: hpk Date: Tue Apr 19 20:59:23 2005 New Revision: 10873 Modified: py/dist/py/execnet/gateway.py Log: don't wait for finished exec threads Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Tue Apr 19 20:59:23 2005 @@ -55,12 +55,7 @@ R, S, i) def _local_trystopexec(self): - try: - self._execpool.shutdown() - except IOError: - return False - return True - + self._execpool.shutdown() def trace(self, *args): if debug: From hpk at codespeak.net Tue Apr 19 21:42:44 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 19 Apr 2005 21:42:44 +0200 (CEST) Subject: [py-svn] r10875 - py/dist/py/test/terminal Message-ID: <20050419194244.29F4E27B43@code1.codespeak.net> Author: hpk Date: Tue Apr 19 21:42:43 2005 New Revision: 10875 Modified: py/dist/py/test/terminal/remote.py Log: remove obnoxious close Modified: py/dist/py/test/terminal/remote.py ============================================================================== --- py/dist/py/test/terminal/remote.py (original) +++ py/dist/py/test/terminal/remote.py Tue Apr 19 21:42:43 2005 @@ -45,7 +45,6 @@ return channel.receive() finally: #print "closing down channel and gateway" - channel.close() channel.gateway.exit() def failure_slave(channel): From hpk at codespeak.net Wed Apr 20 09:27:53 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 20 Apr 2005 09:27:53 +0200 (CEST) Subject: [py-svn] r10901 - py/dist/py/test/terminal Message-ID: <20050420072753.48A6127B43@code1.codespeak.net> Author: hpk Date: Wed Apr 20 09:27:53 2005 New Revision: 10901 Modified: py/dist/py/test/terminal/terminal.py Log: print out module name before counting test items Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Wed Apr 20 09:27:53 2005 @@ -44,9 +44,10 @@ def start_Module(self, colitem): if self.config.option.verbose == 0: - colitem.numitems = colitem.getnum(py.test.Item) abbrev_fn = "/".join(colitem.listnames()) - self.out.write('%s[%d] ' % (abbrev_fn, colitem.numitems)) + self.out.write('%s' % (abbrev_fn, )) + colitem.numitems = colitem.getnum(py.test.Item) + self.out.write('[%d] ' % (colitem.numitems,)) else: self.out.line() self.out.line("+ testmodule: %s" % colitem.fspath) From jan at codespeak.net Wed Apr 20 12:10:45 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Wed, 20 Apr 2005 12:10:45 +0200 (CEST) Subject: [py-svn] r10907 - in py/dist/py: . test/tkinter test/tkinter/testing test/tkinter/testing/data Message-ID: <20050420101045.A338427B3E@code1.codespeak.net> Author: jan Date: Wed Apr 20 12:10:45 2005 New Revision: 10907 Added: py/dist/py/test/tkinter/backend.py py/dist/py/test/tkinter/testing/data/ py/dist/py/test/tkinter/testing/data/__init__.py py/dist/py/test/tkinter/testing/data/filetest.py py/dist/py/test/tkinter/testing/test_backend.py py/dist/py/test/tkinter/testing/test_tixgui.py py/dist/py/test/tkinter/tixgui.py Modified: py/dist/py/__init__.py py/dist/py/test/tkinter/util.py Log: Complete rewrite of the Tkinter Frontend uses the new channel callback start with py.test --tkinter Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Wed Apr 20 12:10:45 2005 @@ -16,7 +16,7 @@ # for customization of collecting/running tests 'test.Session' : ('./test/session.py', 'Session'), 'test.TerminalSession' : ('./test/terminal/terminal.py', 'TerminalSession'), - 'test.TkinterSession' : ('./test/tkinter/tkgui.py', 'TkinterSession'), + 'test.TkinterSession' : ('./test/tkinter/tixgui.py', 'TixSession'), 'test.collect.Collector' : ('./test/collect.py', 'Collector'), 'test.collect.Directory' : ('./test/collect.py', 'Directory'), 'test.collect.Module' : ('./test/collect.py', 'Module'), Added: py/dist/py/test/tkinter/backend.py ============================================================================== --- (empty file) +++ py/dist/py/test/tkinter/backend.py Wed Apr 20 12:10:45 2005 @@ -0,0 +1,142 @@ +'''Backend for running tests and creating a Repository of testreports.''' +import py +import repository +import util + +Null = util.Null + +class TestRepository(repository.Repository): + '''stores only TestReport''' + disabled = True + ReportClass = util.TestReport2 + failed_id = [repr(ReportClass.Status.Failed())] + skipped_id = [repr(ReportClass.Status.Skipped())] + + def __init__(self): + super(TestRepository, self).__init__() + self.add([], self.ReportClass()) + failedreport = self.ReportClass() + failedreport.full_id = self.failed_id + failedreport.label = 'Failed Tests' + self.add(self.failed_id, failedreport) + skippedreport = self.ReportClass() + skippedreport.full_id = self.skipped_id + skippedreport.label = 'Skipped Tests' + self.add(self.skipped_id, skippedreport) + + def root_status(self): + root_status = self.ReportClass.Status.NotExecuted() + if len(self.keys()) > 2: + root_status = self.ReportClass.Status.Passed() + if len(self.find_children(self.skipped_id)): + root_status = self.ReportClass.Status.Skipped() + if len(self.find_children(self.failed_id)): + root_status = self.ReportClass.Status.Failed() + return root_status + + def delete_all(self, key): + super(TestRepository, self).delete_all(key) + new_repos = TestRepository() + for new_key in new_repos.keys(): + if not self.haskey(new_key): + self.add(new_key, new_repos.find(new_key)) + + def delete(self, key): + super(TestRepository, self).delete(key) + new_repos = TestRepository() + if new_repos.haskey(key): + self.add(key, new_repos.find(key)) + + def add_report(self, report): + if not report.full_id: + self.add([], report) + return + if report.error_report: + if report.status == self.ReportClass.Status.Failed(): + self.add(self.failed_id + [report.id], report) + elif report.status == self.ReportClass.Status.Skipped(): + self.add(self.skipped_id + [report.id], report) + self.add(report.full_id, report) + + def add_report_from_channel(self, report_str): + report = self.ReportClass.fromChannel(report_str) + self.add_report(report) + return report.full_id[:] + +class RepositoryBackend: + + def __init__(self, config = Null()): + self.testrepository = TestRepository() + self.channel = Null() + self.queue = py.std.Queue.Queue() + self._message_callback = Null() + self._messages_callback = Null() + self.config = config + + def running(self): + '''are there tests running?''' + if self.channel: + return not self.channel.isclosed() + return False + running = property(running) + + def shutdown(self): + if self.running: + self.channel.close() + + def get_repository(self): + '''return the repository''' + return self.testrepository + + def set_message_callback(self, callback = Null()): + self._message_callback = callback + + def set_messages_callback(self, callback = Null()): + self._messages_callback = callback + + def update(self): + """Check if there is something new in the queue.""" + changed_report_ids = [] + while self.queue.qsize(): + try: + report_str = self.queue.get(0) + id = report_str + if report_str is not None: + id = self.testrepository.add_report_from_channel(report_str) + changed_report_ids.append(id) + self._message_callback(id) + except py.std.Queue.Empty: + pass + self._messages_callback(changed_report_ids) + + def start_tests(self, config = None, args = [], tests = []): + if config is None: + config = self.config + self.testrepository = TestRepository() + gateway = py.execnet.PopenGateway(config.option.executable) + self.channel = gateway.newchannel(receiver = self.queue.put) + gateway.remote_exec(channel = self.channel, source = ''' + import py + from py.__impl__.test.tkinter.backend import remote + + args, tests = channel.receive() + remote(channel, tests = tests, args = args) + ''') + self.channel.send((args, tests)) + +def remote(channel, tests = [], args = []): + import py + from py.__impl__.test.tkinter.guisession import GuiSession + from py.__impl__.test.terminal.remote import getfailureitems + + config, testfiles = py.test.Config.parse(args) + if tests: + cols = getfailureitems(tests) + else: + cols = testfiles + print 'cols:', cols + #session = config.getsessionclass()(config) + session = GuiSession(config = config, channel=channel) + session.shouldclose = channel.isclosed + session.main(cols) + Added: py/dist/py/test/tkinter/testing/data/__init__.py ============================================================================== Added: py/dist/py/test/tkinter/testing/data/filetest.py ============================================================================== --- (empty file) +++ py/dist/py/test/tkinter/testing/data/filetest.py Wed Apr 20 12:10:45 2005 @@ -0,0 +1,7 @@ + +def test_one(): + assert 42 == 42 + +class TestClass(object): + def test_method_one(self): + assert 42 == 42 Added: py/dist/py/test/tkinter/testing/test_backend.py ============================================================================== --- (empty file) +++ py/dist/py/test/tkinter/testing/test_backend.py Wed Apr 20 12:10:45 2005 @@ -0,0 +1,104 @@ +import py +from py.__impl__.test.tkinter import backend + +RepositoryBackend = backend.RepositoryBackend +TestRepository = backend.TestRepository + +datadir = py.magic.autopath().dirpath('data') +from cStringIO import StringIO + + +class Test_TestRepository: + + def check_repository_has_failed_and_skipped_folder(self, repos): + assert repos.find([repr(TestRepository.ReportClass.Status.Failed())]) + assert repos.find([repr(TestRepository.ReportClass.Status.Skipped())]) + + def test_repository_has_failed_and_skipped_folder(self): + repos = TestRepository() + self.check_repository_has_failed_and_skipped_folder(repos) + + def test_repository_has_failed_and_skipped_folder_after_delete_all(self): + repos = TestRepository() + self.check_repository_has_failed_and_skipped_folder(repos) + repos.delete_all([]) + self.check_repository_has_failed_and_skipped_folder(repos) + + def test_repository_has_failed_and_skipped_folder_after_delete(self): + repos = TestRepository() + self.check_repository_has_failed_and_skipped_folder(repos) + repos.delete([str(TestRepository.ReportClass.Status.Failed())]) + self.check_repository_has_failed_and_skipped_folder(repos) + repos.delete([str(TestRepository.ReportClass.Status.Failed())]) + self.check_repository_has_failed_and_skipped_folder(repos) + + def test_add_report_from_channel(self): + full_id = ['start', 'next', 'end'] + report = TestRepository.ReportClass() + report.full_id = full_id + + repos = TestRepository() + id = repos.add_report_from_channel(report.to_channel()) + assert id == full_id + assert repos.haskey(full_id) + +class TestRepositoryBackend: + + def setup_method(self, method): + self.backend = RepositoryBackend() + + def test_get_repository(self): + assert isinstance(self.backend.get_repository(), TestRepository) + + def test_running_property(self): + backend = RepositoryBackend() + assert not self.backend.running + + def test_update_callback(self): + l = [] + self.backend.set_message_callback(l.append) + self.backend.queue.put(None) + self.backend.update() + assert len(l) == 1 + assert l[0] is None + + def test_processs_messeges_callback(self): + l = [] + self.backend.set_message_callback(l.append) + self.backend.queue.put(None) + self.backend.update() + assert len(l) == 1 + assert l[0] is None + + def test_start_tests(self): + config, args = py.test.Config.parse([]) + self.backend.start_tests(config = config, + args = [str(datadir / 'filetest.py')], + tests = []) + while self.backend.running: + self.backend.update() + repos = self.backend.get_repository() + print repos.keys() + print repos.find() + assert repos.find(['py', + 'test', + 'tkinter', + 'testing', + 'data', + 'filetest.py', + 'TestClass']) + +def test_remote(): + class ChannelMock: + def __init__(self): + self.sendlist = [] + def send(self, value): + self.sendlist.append(value) + def isclosed(self): + return False + + channel = ChannelMock() + backend.remote(channel, args = [str(datadir / 'filetest.py')], tests = []) + #py.std.pprint.pprint(channel.sendlist) + assert channel.sendlist + Added: py/dist/py/test/tkinter/testing/test_tixgui.py ============================================================================== --- (empty file) +++ py/dist/py/test/tkinter/testing/test_tixgui.py Wed Apr 20 12:10:45 2005 @@ -0,0 +1,8 @@ + +import py + +def test_import_tix(): + import Tix + root = Tix.Tk() + root.tk.eval('package require Tix') + Added: py/dist/py/test/tkinter/tixgui.py ============================================================================== --- (empty file) +++ py/dist/py/test/tkinter/tixgui.py Wed Apr 20 12:10:45 2005 @@ -0,0 +1,265 @@ +import py +from py.__impl__.test.tkinter import backend +from py.__impl__.test.tkinter import util +Null = util.Null + + +import ScrolledText +from Tkinter import PhotoImage +import Tix +from Tkconstants import * +import re +import os + +class StatusBar(Tix.Frame): + + def __init__(self, master=None, **kw): + if master is None: + master = Tk() + apply(Tix.Frame.__init__, (self, master), kw) + self.labels = {} + + def set_label(self, name, text='', side=LEFT): + if not self.labels.has_key(name): + label = Tix.Label(self, bd=1, relief=SUNKEN, anchor=W) + label.pack(side=side) + self.labels[name] = label + else: + label = self.labels[name] + label.config(text='%s:\t%s' % (name,text)) + + def _get_label(self, name): + if not self.labels.has_key(name): + self.set_label(name, 'NoText') + return self.labels[name] + + def update(self, testrepository): + failed = testrepository.keys(testrepository.failed_id) + self.set_label('Failed', str(len(failed))) + self._get_label('Failed').configure(bg = 'Red', fg = 'White') + skipped = testrepository.keys(testrepository.skipped_id) + self.set_label('Skipped', str(len(skipped))) + self._get_label('Skipped').configure(bg = 'Yellow') + self.set_label('Collectors', + str(len(testrepository.keys())-2)) + +class ReportListBox(Tix.LabelFrame): + + def __init__(self, *args, **kwargs): + Tix.LabelFrame.__init__(self, *args, **kwargs) + self.callback = Null() + self.data = {} + self.createwidgets() + self.label.configure(text = 'Idle') + + def createwidgets(self): + self.listbox = Tix.Listbox(self.frame, foreground='red', + selectmode=SINGLE) + + self.scrollbar = Tix.Scrollbar(self.frame, command=self.listbox.yview) + self.scrollbar.pack(side = RIGHT, fill = Y, anchor = N) + self.listbox.pack(side = LEFT, fill = BOTH, expand = YES, anchor = NW) + self.listbox.configure(yscrollcommand = self.scrollbar.set) + + def set_callback(self, callback): + self.callback = callback + self.listbox.bind('', self.do_callback) + + def do_callback(self, *args): + report_ids = [self.data[self.listbox.get(int(item))] for item in self.listbox.curselection()] + for report_id in report_ids: + self.callback(report_id) + + def update_label(self, report_path): + label = report_path + if not label: + label = 'Idle' + self.label.configure(text = label) + + def update_list(self, repository): + failed_childs = repository.find_children(repository.failed_id) + skipped_childs = repository.find_children(repository.skipped_id) + if len(failed_childs + skipped_childs) == len(self.data.keys()): + return + old_selection = [int(sel) for sel in self.listbox.curselection()] + self.listbox.delete(0, END) + self.data = {} + for report_id in failed_childs + skipped_childs: + report = repository.find(report_id) + label = '%s: %s' % (report.status, report.label) + self.data[label] = report.full_id + self.listbox.insert(END, label) + for index in old_selection: + try: + self.listbox.select_set(index) + except: + pass + + + + + +class TixGui: + + def __init__(self, parent, config): + self._parent = parent + self._config = config + self._should_stop = False + #XXX needed? + self._paths = [] + self.backend = backend.RepositoryBackend(config) + self.createwidgets() + self.timer_update() + + def createwidgets(self): + self._buttonframe = Tix.Frame(self._parent) + self._entry = Tix.LabelEntry(self._buttonframe, labelside = LEFT, + label = 'Tests:') + self._entry.pack(side = LEFT, fill = X, expand = YES) + self._entry.entry.configure(width=80) + self._entry.entry.bind('', self.start_tests) + self._stop = Tix.Button(self._buttonframe, text = 'Stop', + command = self.stop) + self._stop.pack(side = RIGHT) + self._run = Tix.Button(self._buttonframe, text = 'Run', + command = self.start_tests) + self._run.pack(side = RIGHT) + self._buttonframe.pack(side = TOP, fill = X) + self._reportlist = ReportListBox(self._parent, width = 400) + self._reportlist.pack(side = TOP, fill = BOTH, expand = YES) + self._reportlist.set_callback(self.show_error) + #self._tree = reporttree.ReportTree(self._parent, backend = self.backend) + #self._tree.pack(side = TOP, fill = BOTH, expand = YES) + self._statusbar = StatusBar(self._parent) + self._statusbar.pack(side=BOTTOM, fill=X) + self.update_status(self.backend.get_repository()) + + + def show_error(self, report_id): + report = self.backend.get_repository().find(report_id) + window = Tix.Toplevel(self._parent) + window.title(report.label) + window.protocol('WM_DELETE_WINDOW', window.quit) + Tix.Label(window, text='%s: %s' % (report.status, report.label), + foreground="red", justify=LEFT).pack(anchor=W) + text = ScrolledText.ScrolledText(window) + text.tag_config('sel', relief=FLAT) + text.insert(END, report.error_report) + if len(report.error_report.splitlines()) < 20: + text.config(height=len(report.error_report.splitlines()) + 5) + text.yview_pickplace(END) + text['state'] = DISABLED + text['cursor'] = window['cursor'] + self.attacheditorhotspots(text) + text.pack(expand=1, fill=BOTH) + ## + b = Tix.Button(window, text="Close", + command=window.quit) + b.pack(side=BOTTOM) + b.focus_set() + window.bind('', lambda e, w=window: w.quit()) + window.mainloop() + window.destroy() + + def attacheditorhotspots(self, text): + # Attach clickable regions to a Text widget. + filelink = re.compile(r"""\[(?:testcode\s*:)?\s*(.+):(\d+)\]""") + skippedlink = re.compile(r"""in\s+(/.*):(\d+)\s+""") + lines = text.get('1.0', END).splitlines(1) + if not lines: + return + tagname = '' + start, end = 0,0 + for index, line in enumerate(lines): + match = filelink.search(line) + if match is None: + match = skippedlink.search(line) + if match is None: + continue + file, line = match.group(1, 2) + start, end = match.span() + tagname = "ref%d" % index + text.tag_add(tagname, + "%d.%d" % (index + 1, start), + "%d.%d" % (index + 1, end)) + text.tag_bind(tagname, "", + lambda e, n=tagname: + e.widget.tag_config(n, underline=1)) + text.tag_bind(tagname, "", + lambda e, n=tagname: + e.widget.tag_config(n, underline=0)) + text.tag_bind(tagname, "", + lambda e, self=self, f=file, l=line: + self.launch_editor(f, l)) + + def launch_editor(self, file, line): + editor = (py.std.os.environ.get('PYUNIT_EDITOR', None) or + py.std.os.environ.get('EDITOR_REMOTE', None) or + os.environ.get('EDITOR', None) or "emacsclient --no-wait ") + #"emacsclient --no-wait ") + if editor: + print "%s +%s %s" % (editor, line, file) + #py.process.cmdexec('%s +%s %s' % (editor, line, file)) + os.system('%s +%s %s' % (editor, line, file)) + + def timer_update(self): + self.backend.update() + self._parent.after(100, self.timer_update) + + def start_tests(self, dont_care_event=None): + paths = self._entry.entry.get().split(',') + self.backend.set_messages_callback(self.messages_callback) + self.backend.set_message_callback(self.message_callback) + self.backend.start_tests(args = paths) + + def update_status(self, repository): + self._statusbar.update(repository) + bgcolor = 'White' + fgcolor = 'Black' + root_status = repository.root_status() + if root_status == repository.ReportClass.Status.Failed(): + bgcolor = 'Red' + fgcolor = 'White' + elif root_status == repository.ReportClass.Status.Skipped() : + bgcolor = 'Yellow' + self._entry.entry.configure(bg = bgcolor, fg = fgcolor) + + def messages_callback(self, report_ids): + if not report_ids: + return + repository = self.backend.get_repository() + self.update_status(repository) + self._reportlist.update_list(repository) + + def message_callback(self, report_id): + if report_id is None: + report_label = None + else: + report_label = self.backend.get_repository().find(report_id).path + self._reportlist.update_label(report_label) + + def set_paths(self, paths): + self._paths = paths + self._entry.entry.insert(END, ', '.join(paths)) + + def stop(self): + self.backend.shutdown() + self.backend.update() + self.messages_callback([None]) + self.message_callback(None) + + def shutdown(self): + self.should_stop = True + self.backend.shutdown() + py.std.sys.exit() + +class TixSession: + def __init__(self, config): + self.config = config + + def main(self, paths): + root = Tix.Tk() + tixgui = TixGui(root, self.config) + tixgui.set_paths(paths) + root.protocol('WM_DELETE_WINDOW', tixgui.shutdown) + root.mainloop() Modified: py/dist/py/test/tkinter/util.py ============================================================================== --- py/dist/py/test/tkinter/util.py (original) +++ py/dist/py/test/tkinter/util.py Wed Apr 20 12:10:45 2005 @@ -240,6 +240,26 @@ channel_dict.update(kwargs) return TestReport.fromChannel(channel_dict) +class TestReport2(TestReport): + '''Will replace TestReport.''' + + template = {'time' : 0, + 'label': 'Root', + 'id': 'Root', + 'full_id': [], + 'status': Status.NotExecuted(), + 'report': 'NoReport', + 'error_report': '', + 'finished': False, + 'restart_params': None, # ('',('',)) + 'path' : '', + 'modpath': '', + } + Status = Status + + + + class TestFileWatcher: '''watches files or paths''' def __init__(self, *paths): From jan at codespeak.net Wed Apr 20 12:39:32 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Wed, 20 Apr 2005 12:39:32 +0200 (CEST) Subject: [py-svn] r10908 - py/dist/py/test/tkinter Message-ID: <20050420103932.8150527B3E@code1.codespeak.net> Author: jan Date: Wed Apr 20 12:39:32 2005 New Revision: 10908 Modified: py/dist/py/test/tkinter/tixgui.py Log: fix layout Modified: py/dist/py/test/tkinter/tixgui.py ============================================================================== --- py/dist/py/test/tkinter/tixgui.py (original) +++ py/dist/py/test/tkinter/tixgui.py Wed Apr 20 12:39:32 2005 @@ -116,7 +116,7 @@ self._entry = Tix.LabelEntry(self._buttonframe, labelside = LEFT, label = 'Tests:') self._entry.pack(side = LEFT, fill = X, expand = YES) - self._entry.entry.configure(width=80) + #self._entry.entry.configure(width=80) self._entry.entry.bind('', self.start_tests) self._stop = Tix.Button(self._buttonframe, text = 'Stop', command = self.stop) @@ -125,7 +125,8 @@ command = self.start_tests) self._run.pack(side = RIGHT) self._buttonframe.pack(side = TOP, fill = X) - self._reportlist = ReportListBox(self._parent, width = 400) + self._reportlist = ReportListBox(self._parent) + self._reportlist.label.configure(width=80,anchor=W ) self._reportlist.pack(side = TOP, fill = BOTH, expand = YES) self._reportlist.set_callback(self.show_error) #self._tree = reporttree.ReportTree(self._parent, backend = self.backend) From jan at codespeak.net Wed Apr 20 18:30:02 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Wed, 20 Apr 2005 18:30:02 +0200 (CEST) Subject: [py-svn] r10932 - py/dist/py/test/tkinter Message-ID: <20050420163002.D8B0327B3D@code1.codespeak.net> Author: jan Date: Wed Apr 20 18:30:02 2005 New Revision: 10932 Modified: py/dist/py/test/tkinter/guisession.py py/dist/py/test/tkinter/tixgui.py Log: fix wrong test id add 'Green Bar' add time to statusbar Modified: py/dist/py/test/tkinter/guisession.py ============================================================================== --- py/dist/py/test/tkinter/guisession.py (original) +++ py/dist/py/test/tkinter/guisession.py Wed Apr 20 18:30:02 2005 @@ -1,6 +1,6 @@ '''GuiSession builds TestReport instances and sends them to tkgui.Manager''' import py -from util import TestReport +from util import TestReport2 as TestReport from py.__impl__.test.session import Exit, SimpleOutErrCapture class GuiSession(py.test.Session): Modified: py/dist/py/test/tkinter/tixgui.py ============================================================================== --- py/dist/py/test/tkinter/tixgui.py (original) +++ py/dist/py/test/tkinter/tixgui.py Wed Apr 20 18:30:02 2005 @@ -42,6 +42,11 @@ self._get_label('Skipped').configure(bg = 'Yellow') self.set_label('Collectors', str(len(testrepository.keys())-2)) + root_report = testrepository.find() + if root_report.status != testrepository.ReportClass.Status.NotExecuted(): + self.set_label('Time', '%0.2f seconds' % testrepository.find().time) + else: + self.set_label('Time', '%0.2f seconds' % 0.0) class ReportListBox(Tix.LabelFrame): @@ -218,7 +223,9 @@ bgcolor = 'White' fgcolor = 'Black' root_status = repository.root_status() - if root_status == repository.ReportClass.Status.Failed(): + if root_status == repository.ReportClass.Status.Passed(): + bgcolor = 'Green' + elif root_status == repository.ReportClass.Status.Failed(): bgcolor = 'Red' fgcolor = 'White' elif root_status == repository.ReportClass.Status.Skipped() : From jan at codespeak.net Wed Apr 20 19:56:55 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Wed, 20 Apr 2005 19:56:55 +0200 (CEST) Subject: [py-svn] r10938 - py/dist/py/test/tkinter Message-ID: <20050420175655.9427927B3E@code1.codespeak.net> Author: jan Date: Wed Apr 20 19:56:55 2005 New Revision: 10938 Modified: py/dist/py/test/tkinter/backend.py Log: reuse existing gateway Modified: py/dist/py/test/tkinter/backend.py ============================================================================== --- py/dist/py/test/tkinter/backend.py (original) +++ py/dist/py/test/tkinter/backend.py Wed Apr 20 19:56:55 2005 @@ -72,6 +72,7 @@ self._message_callback = Null() self._messages_callback = Null() self.config = config + self.gateway = None def running(self): '''are there tests running?''' @@ -83,6 +84,8 @@ def shutdown(self): if self.running: self.channel.close() + self.channel.gateway.exit() + self.gateway = None def get_repository(self): '''return the repository''' @@ -113,9 +116,10 @@ if config is None: config = self.config self.testrepository = TestRepository() - gateway = py.execnet.PopenGateway(config.option.executable) - self.channel = gateway.newchannel(receiver = self.queue.put) - gateway.remote_exec(channel = self.channel, source = ''' + if not self.gateway: + 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 = ''' import py from py.__impl__.test.tkinter.backend import remote From hpk at codespeak.net Thu Apr 21 12:31:43 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 21 Apr 2005 12:31:43 +0200 (CEST) Subject: [py-svn] r10963 - py/dist/py/documentation Message-ID: <20050421103143.643B727B41@code1.codespeak.net> Author: hpk Date: Thu Apr 21 12:31:43 2005 New Revision: 10963 Modified: py/dist/py/documentation/releasescheme.txt Log: reference PyPy's svn help document Modified: py/dist/py/documentation/releasescheme.txt ============================================================================== --- py/dist/py/documentation/releasescheme.txt (original) +++ py/dist/py/documentation/releasescheme.txt Thu Apr 21 12:31:43 2005 @@ -21,7 +21,7 @@ ------------------------------------------------------------- If you have a subversion client (you can easily install -one :-) it is recommended that you choose the following +one, look at PyPy's `svn help document`_) it is recommended that you choose the following method to install, irrespective if you want to install things via distutils. Start by issueing the following shell command (or a graphical equivalent):: @@ -39,6 +39,7 @@ 2) go to the `py-dist` directory and run `python setup.py install`. .. _`getting started`: getting-started.html +.. _`svn help document`: http://codespeak.net/pypy/index.cgi?doc/getting_started.html#subversion If you later want to upgrade your version of the py lib to the newest release you simply issue:: From hpk at codespeak.net Thu Apr 21 12:36:36 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 21 Apr 2005 12:36:36 +0200 (CEST) Subject: [py-svn] r10964 - py/dist/py/documentation Message-ID: <20050421103636.57A2E27B41@code1.codespeak.net> Author: hpk Date: Thu Apr 21 12:36:36 2005 New Revision: 10964 Modified: py/dist/py/documentation/getting-started.txt Log: reference release scheme document Modified: py/dist/py/documentation/getting-started.txt ============================================================================== --- py/dist/py/documentation/getting-started.txt (original) +++ py/dist/py/documentation/getting-started.txt Thu Apr 21 12:36:36 2005 @@ -51,10 +51,15 @@ If you know of a good developer-style way of doing the equivalent on win32 (non-cygwin) environments, tell us_. -And no, we don't provide a distutils-install until +And no, we don't yet provide a distutils-install until we have settled on a convenient way to upgrade seamlessly via an `svn up` while at the same time allowing installs/upgrades via the distutils `setup.py` way. +Our `releasescheme document`_ holds some preliminary +planning on how future releaes of the py lib will +look like. + +.. _`releasescheme document`: releasescheme.txt upgrading it ------------ From jan at codespeak.net Thu Apr 21 12:46:34 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Thu, 21 Apr 2005 12:46:34 +0200 (CEST) Subject: [py-svn] r10967 - py/dist/py/test/tkinter Message-ID: <20050421104634.46D6027B41@code1.codespeak.net> Author: jan Date: Thu Apr 21 12:46:34 2005 New Revision: 10967 Modified: py/dist/py/test/tkinter/tixgui.py Log: add a file selection dialog strip spaces from filenames Modified: py/dist/py/test/tkinter/tixgui.py ============================================================================== --- py/dist/py/test/tkinter/tixgui.py (original) +++ py/dist/py/test/tkinter/tixgui.py Thu Apr 21 12:46:34 2005 @@ -118,16 +118,14 @@ def createwidgets(self): self._buttonframe = Tix.Frame(self._parent) - self._entry = Tix.LabelEntry(self._buttonframe, labelside = LEFT, - label = 'Tests:') + self._entry = Tix.FileEntry(self._buttonframe, label = 'Tests:', + command = self.start_tests) self._entry.pack(side = LEFT, fill = X, expand = YES) - #self._entry.entry.configure(width=80) - self._entry.entry.bind('', self.start_tests) self._stop = Tix.Button(self._buttonframe, text = 'Stop', command = self.stop) self._stop.pack(side = RIGHT) self._run = Tix.Button(self._buttonframe, text = 'Run', - command = self.start_tests) + command = self._entry.invoke) self._run.pack(side = RIGHT) self._buttonframe.pack(side = TOP, fill = X) self._reportlist = ReportListBox(self._parent) @@ -213,7 +211,7 @@ self._parent.after(100, self.timer_update) def start_tests(self, dont_care_event=None): - paths = self._entry.entry.get().split(',') + paths = [path.strip() for path in self._entry.entry.get().split(',')] self.backend.set_messages_callback(self.messages_callback) self.backend.set_message_callback(self.message_callback) self.backend.start_tests(args = paths) From hpk at codespeak.net Thu Apr 21 13:09:16 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 21 Apr 2005 13:09:16 +0200 (CEST) Subject: [py-svn] r10971 - py/dist/py/path/local Message-ID: <20050421110916.813DD27B44@code1.codespeak.net> Author: hpk Date: Thu Apr 21 13:09:16 2005 New Revision: 10971 Modified: py/dist/py/path/local/local.py Log: if a path given to sysfind() is already absolute don't do any path lookups Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Thu Apr 21 13:09:16 2005 @@ -476,20 +476,26 @@ Note: This is probably not working on plain win32 systems but may work on cygwin. """ - if py.std.sys.platform == 'win32': - paths = py.std.os.environ['Path'].split(';') - tryadd = '', '.exe', '.com', '.bat' # XXX add more? + if os.path.isabs(name): + p = py.path.local(name) + if p.check(file=1): + return p else: - paths = py.std.os.environ['PATH'].split(':') - tryadd = ('',) - 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 + if py.std.sys.platform == 'win32': + paths = py.std.os.environ['Path'].split(';') + tryadd = '', '.exe', '.com', '.bat' # XXX add more? + else: + paths = py.std.os.environ['PATH'].split(':') + tryadd = ('',) + + 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 raise py.error.ENOENT(name) sysfind = classmethod(sysfind) #""" From hpk at codespeak.net Thu Apr 21 13:13:34 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 21 Apr 2005 13:13:34 +0200 (CEST) Subject: [py-svn] r10972 - py/dist/py/documentation Message-ID: <20050421111334.633A627B3E@code1.codespeak.net> Author: hpk Date: Thu Apr 21 13:13:34 2005 New Revision: 10972 Modified: py/dist/py/documentation/getting-started.txt py/dist/py/documentation/index.txt Log: headlines for index page fix link Modified: py/dist/py/documentation/getting-started.txt ============================================================================== --- py/dist/py/documentation/getting-started.txt (original) +++ py/dist/py/documentation/getting-started.txt Thu Apr 21 13:13:34 2005 @@ -59,7 +59,7 @@ planning on how future releaes of the py lib will look like. -.. _`releasescheme document`: releasescheme.txt +.. _`releasescheme document`: releasescheme.html upgrading it ------------ Modified: py/dist/py/documentation/index.txt ============================================================================== --- py/dist/py/documentation/index.txt (original) +++ py/dist/py/documentation/index.txt Thu Apr 21 13:13:34 2005 @@ -9,7 +9,8 @@ is on developer activities this does not preclude usage of the py lib for applications.* -Important Links: +Important Links +--------------- `getting started`_ quick start for using py.test and the py lib. @@ -19,7 +20,8 @@ `why what how py?`_, describing motivation and background of the py lib -Usage Documentation: +Usage Documentation +------------------- `py.test`_ introduces to the new'n'easy **py.test** utility From hpk at codespeak.net Thu Apr 21 22:42:12 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 21 Apr 2005 22:42:12 +0200 (CEST) Subject: [py-svn] r11001 - in py/dist/py: . misc/testing test/tkinter Message-ID: <20050421204212.7A9F327B53@code1.codespeak.net> Author: hpk Date: Thu Apr 21 22:42:12 2005 New Revision: 11001 Added: py/dist/py/test/tkinter/tixsession.py - copied, changed from r10994, py/dist/py/test/tkinter/tixgui.py Modified: py/dist/py/__init__.py py/dist/py/misc/testing/test_initpkg.py Log: let tests mostly pass on platforms that don't have tkinter or tix. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Thu Apr 21 22:42:12 2005 @@ -16,7 +16,7 @@ # for customization of collecting/running tests 'test.Session' : ('./test/session.py', 'Session'), 'test.TerminalSession' : ('./test/terminal/terminal.py', 'TerminalSession'), - 'test.TkinterSession' : ('./test/tkinter/tixgui.py', 'TixSession'), + 'test.TkinterSession' : ('./test/tkinter/tixsession.py', 'TixSession'), 'test.collect.Collector' : ('./test/collect.py', 'Collector'), 'test.collect.Directory' : ('./test/collect.py', 'Directory'), 'test.collect.Module' : ('./test/collect.py', 'Module'), Modified: py/dist/py/misc/testing/test_initpkg.py ============================================================================== --- py/dist/py/misc/testing/test_initpkg.py (original) +++ py/dist/py/misc/testing/test_initpkg.py Thu Apr 21 22:42:12 2005 @@ -36,6 +36,7 @@ def test_importall(): base = py.path.local(py.__file__).dirpath() nodirs = ( + base.join('test', 'tkinter'), base.join('test', 'testing', 'data'), base.join('test', 'testing', 'test'), base.join('magic', 'greenlet.py'), Copied: py/dist/py/test/tkinter/tixsession.py (from r10994, py/dist/py/test/tkinter/tixgui.py) ============================================================================== --- py/dist/py/test/tkinter/tixgui.py (original) +++ py/dist/py/test/tkinter/tixsession.py Thu Apr 21 22:42:12 2005 @@ -1,263 +1,4 @@ import py -from py.__impl__.test.tkinter import backend -from py.__impl__.test.tkinter import util -Null = util.Null - - -import ScrolledText -from Tkinter import PhotoImage -import Tix -from Tkconstants import * -import re -import os - -class StatusBar(Tix.Frame): - - def __init__(self, master=None, **kw): - if master is None: - master = Tk() - apply(Tix.Frame.__init__, (self, master), kw) - self.labels = {} - - def set_label(self, name, text='', side=LEFT): - if not self.labels.has_key(name): - label = Tix.Label(self, bd=1, relief=SUNKEN, anchor=W) - label.pack(side=side) - self.labels[name] = label - else: - label = self.labels[name] - label.config(text='%s:\t%s' % (name,text)) - - def _get_label(self, name): - if not self.labels.has_key(name): - self.set_label(name, 'NoText') - return self.labels[name] - - def update(self, testrepository): - failed = testrepository.keys(testrepository.failed_id) - self.set_label('Failed', str(len(failed))) - self._get_label('Failed').configure(bg = 'Red', fg = 'White') - skipped = testrepository.keys(testrepository.skipped_id) - self.set_label('Skipped', str(len(skipped))) - self._get_label('Skipped').configure(bg = 'Yellow') - self.set_label('Collectors', - str(len(testrepository.keys())-2)) - root_report = testrepository.find() - if root_report.status != testrepository.ReportClass.Status.NotExecuted(): - self.set_label('Time', '%0.2f seconds' % testrepository.find().time) - else: - self.set_label('Time', '%0.2f seconds' % 0.0) - -class ReportListBox(Tix.LabelFrame): - - def __init__(self, *args, **kwargs): - Tix.LabelFrame.__init__(self, *args, **kwargs) - self.callback = Null() - self.data = {} - self.createwidgets() - self.label.configure(text = 'Idle') - - def createwidgets(self): - self.listbox = Tix.Listbox(self.frame, foreground='red', - selectmode=SINGLE) - - self.scrollbar = Tix.Scrollbar(self.frame, command=self.listbox.yview) - self.scrollbar.pack(side = RIGHT, fill = Y, anchor = N) - self.listbox.pack(side = LEFT, fill = BOTH, expand = YES, anchor = NW) - self.listbox.configure(yscrollcommand = self.scrollbar.set) - - def set_callback(self, callback): - self.callback = callback - self.listbox.bind('', self.do_callback) - - def do_callback(self, *args): - report_ids = [self.data[self.listbox.get(int(item))] for item in self.listbox.curselection()] - for report_id in report_ids: - self.callback(report_id) - - def update_label(self, report_path): - label = report_path - if not label: - label = 'Idle' - self.label.configure(text = label) - - def update_list(self, repository): - failed_childs = repository.find_children(repository.failed_id) - skipped_childs = repository.find_children(repository.skipped_id) - if len(failed_childs + skipped_childs) == len(self.data.keys()): - return - old_selection = [int(sel) for sel in self.listbox.curselection()] - self.listbox.delete(0, END) - self.data = {} - for report_id in failed_childs + skipped_childs: - report = repository.find(report_id) - label = '%s: %s' % (report.status, report.label) - self.data[label] = report.full_id - self.listbox.insert(END, label) - for index in old_selection: - try: - self.listbox.select_set(index) - except: - pass - - - - - -class TixGui: - - def __init__(self, parent, config): - self._parent = parent - self._config = config - self._should_stop = False - #XXX needed? - self._paths = [] - self.backend = backend.RepositoryBackend(config) - self.createwidgets() - self.timer_update() - - def createwidgets(self): - self._buttonframe = Tix.Frame(self._parent) - self._entry = Tix.FileEntry(self._buttonframe, label = 'Tests:', - command = self.start_tests) - self._entry.pack(side = LEFT, fill = X, expand = YES) - self._stop = Tix.Button(self._buttonframe, text = 'Stop', - command = self.stop) - self._stop.pack(side = RIGHT) - self._run = Tix.Button(self._buttonframe, text = 'Run', - command = self._entry.invoke) - self._run.pack(side = RIGHT) - self._buttonframe.pack(side = TOP, fill = X) - self._reportlist = ReportListBox(self._parent) - self._reportlist.label.configure(width=80,anchor=W ) - self._reportlist.pack(side = TOP, fill = BOTH, expand = YES) - self._reportlist.set_callback(self.show_error) - #self._tree = reporttree.ReportTree(self._parent, backend = self.backend) - #self._tree.pack(side = TOP, fill = BOTH, expand = YES) - self._statusbar = StatusBar(self._parent) - self._statusbar.pack(side=BOTTOM, fill=X) - self.update_status(self.backend.get_repository()) - - - def show_error(self, report_id): - report = self.backend.get_repository().find(report_id) - window = Tix.Toplevel(self._parent) - window.title(report.label) - window.protocol('WM_DELETE_WINDOW', window.quit) - Tix.Label(window, text='%s: %s' % (report.status, report.label), - foreground="red", justify=LEFT).pack(anchor=W) - text = ScrolledText.ScrolledText(window) - text.tag_config('sel', relief=FLAT) - text.insert(END, report.error_report) - if len(report.error_report.splitlines()) < 20: - text.config(height=len(report.error_report.splitlines()) + 5) - text.yview_pickplace(END) - text['state'] = DISABLED - text['cursor'] = window['cursor'] - self.attacheditorhotspots(text) - text.pack(expand=1, fill=BOTH) - ## - b = Tix.Button(window, text="Close", - command=window.quit) - b.pack(side=BOTTOM) - b.focus_set() - window.bind('', lambda e, w=window: w.quit()) - window.mainloop() - window.destroy() - - def attacheditorhotspots(self, text): - # Attach clickable regions to a Text widget. - filelink = re.compile(r"""\[(?:testcode\s*:)?\s*(.+):(\d+)\]""") - skippedlink = re.compile(r"""in\s+(/.*):(\d+)\s+""") - lines = text.get('1.0', END).splitlines(1) - if not lines: - return - tagname = '' - start, end = 0,0 - for index, line in enumerate(lines): - match = filelink.search(line) - if match is None: - match = skippedlink.search(line) - if match is None: - continue - file, line = match.group(1, 2) - start, end = match.span() - tagname = "ref%d" % index - text.tag_add(tagname, - "%d.%d" % (index + 1, start), - "%d.%d" % (index + 1, end)) - text.tag_bind(tagname, "", - lambda e, n=tagname: - e.widget.tag_config(n, underline=1)) - text.tag_bind(tagname, "", - lambda e, n=tagname: - e.widget.tag_config(n, underline=0)) - text.tag_bind(tagname, "", - lambda e, self=self, f=file, l=line: - self.launch_editor(f, l)) - - def launch_editor(self, file, line): - editor = (py.std.os.environ.get('PYUNIT_EDITOR', None) or - py.std.os.environ.get('EDITOR_REMOTE', None) or - os.environ.get('EDITOR', None) or "emacsclient --no-wait ") - #"emacsclient --no-wait ") - if editor: - print "%s +%s %s" % (editor, line, file) - #py.process.cmdexec('%s +%s %s' % (editor, line, file)) - os.system('%s +%s %s' % (editor, line, file)) - - def timer_update(self): - self.backend.update() - self._parent.after(100, self.timer_update) - - def start_tests(self, dont_care_event=None): - paths = [path.strip() for path in self._entry.entry.get().split(',')] - self.backend.set_messages_callback(self.messages_callback) - self.backend.set_message_callback(self.message_callback) - self.backend.start_tests(args = paths) - - def update_status(self, repository): - self._statusbar.update(repository) - bgcolor = 'White' - fgcolor = 'Black' - root_status = repository.root_status() - if root_status == repository.ReportClass.Status.Passed(): - bgcolor = 'Green' - elif root_status == repository.ReportClass.Status.Failed(): - bgcolor = 'Red' - fgcolor = 'White' - elif root_status == repository.ReportClass.Status.Skipped() : - bgcolor = 'Yellow' - self._entry.entry.configure(bg = bgcolor, fg = fgcolor) - - def messages_callback(self, report_ids): - if not report_ids: - return - repository = self.backend.get_repository() - self.update_status(repository) - self._reportlist.update_list(repository) - - def message_callback(self, report_id): - if report_id is None: - report_label = None - else: - report_label = self.backend.get_repository().find(report_id).path - self._reportlist.update_label(report_label) - - def set_paths(self, paths): - self._paths = paths - self._entry.entry.insert(END, ', '.join(paths)) - - def stop(self): - self.backend.shutdown() - self.backend.update() - self.messages_callback([None]) - self.message_callback(None) - - def shutdown(self): - self.should_stop = True - self.backend.shutdown() - py.std.sys.exit() class TixSession: def __init__(self, config): @@ -265,6 +6,7 @@ def main(self, paths): root = Tix.Tk() + from tixgui import TixGui tixgui = TixGui(root, self.config) tixgui.set_paths(paths) root.protocol('WM_DELETE_WINDOW', tixgui.shutdown) From hpk at codespeak.net Thu Apr 21 23:48:29 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 21 Apr 2005 23:48:29 +0200 (CEST) Subject: [py-svn] r11006 - in py/dist/py/thread: . testing Message-ID: <20050421214829.9E1FE27B5B@code1.codespeak.net> Author: hpk Date: Thu Apr 21 23:48:29 2005 New Revision: 11006 Modified: py/dist/py/thread/pool.py py/dist/py/thread/testing/test_pool.py Log: fixing tests and improving readability of the WorkerPools Modified: py/dist/py/thread/pool.py ============================================================================== --- py/dist/py/thread/pool.py (original) +++ py/dist/py/thread/pool.py Thu Apr 21 23:48:29 2005 @@ -3,46 +3,6 @@ import time import sys -class WorkerThread(threading.Thread): - def __init__(self, pool): - threading.Thread.__init__(self) - self._queue = Queue.Queue() - self._pool = pool - self.setDaemon(1) - - def run(self): - try: - while 1: - reply = self._queue.get() - if reply is None: - break - assert self not in self._pool._ready - task = reply.task - try: - func, args, kwargs = task - result = func(*args, **kwargs) - except (SystemExit, KeyboardInterrupt): - break - except: - reply.setexcinfo(sys.exc_info()) - else: - reply.set(result) - self._pool._ready[self] = True - finally: - del self._pool._alive[self] - try: - del self._pool._ready[self] - except KeyError: - pass - - def send(self, task): - reply = Reply(task) - self._queue.put(reply) - return reply - - def stop(self): - self._queue.put(None) - class Reply(object): _excinfo = None def __init__(self, task): @@ -81,6 +41,44 @@ self._excinfo = None raise excinfo[0], excinfo[1], excinfo[2] return result + +class WorkerThread(threading.Thread): + def __init__(self, pool): + threading.Thread.__init__(self) + self._queue = Queue.Queue() + self._pool = pool + self.setDaemon(1) + + def run(self): + try: + while 1: + reply = self._queue.get() + assert self not in self._pool._ready + task = reply.task + try: + func, args, kwargs = task + result = func(*args, **kwargs) + except (SystemExit, KeyboardInterrupt): + break + except: + reply.setexcinfo(sys.exc_info()) + else: + reply.set(result) + self._pool._ready[self] = True + finally: + del self._pool._alive[self] + try: + del self._pool._ready[self] + except KeyError: + pass + + def send(self, task): + reply = Reply(task) + self._queue.put(reply) + return reply + + def stop(self): + self._queue.put(SystemExit) class WorkerPool(object): _shuttingdown = False @@ -90,43 +88,46 @@ self._alive = {} def dispatch(self, func, *args, **kwargs): + """ return Reply object for the asynchronous dispatch + of the given func(*args, **kwargs) in a + separate worker thread. + """ if self._shuttingdown: raise IOError("WorkerPool is already shutting down") try: thread, _ = self._ready.popitem() except KeyError: # pop from empty list + if self.maxthreads and len(self._alive) >= self.maxthreads: + raise IOError("can't create more than %d threads." % + (self.maxthreads,)) thread = self._newthread() return thread.send((func, args, kwargs)) + def _newthread(self): + thread = WorkerThread(self) + self._alive[thread] = True + thread.start() + return thread + def shutdown(self): if not self._shuttingdown: self._shuttingdown = True for t in self._alive.keys(): t.stop() - def _newthread(self): - if self.maxthreads: - if len(self._alive) >= self.maxthreads: - raise IOError("cannot create more threads, " - "maxthreads=%d" % (self.maxthreads,)) - thread = WorkerThread(self) - thread.start() - self._alive[thread] = True - return thread - def join(self, timeout=None): current = threading.currentThread() - now = time.time() - while self._alive: - try: - thread, _ = self._ready.popitem() - except KeyError: - if timeout and time.time() >= now + timeout: - raise IOError("timeout waiting for threads") - time.sleep(0.1) - else: - thread.join() - + deadline = delta = None + if timeout is not None: + deadline = time.time() + timeout + for thread in self._alive.keys(): + if deadline: + delta = deadline - time.time() + if delta <= 0: + raise IOError("timeout while joining threads") + thread.join(timeout=delta) + if thread.isAlive(): + raise IOError("timeout while joining threads") class NamedThreadPool: def __init__(self, **kw): 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 Apr 21 23:48:29 2005 @@ -9,8 +9,13 @@ pool = WorkerPool() q = py.std.Queue.Queue() num = 4 + + def f(i): + q.put(i) + while q.qsize(): + py.std.time.sleep(0.01) for i in range(num): - pool.dispatch(q.put, i) + pool.dispatch(f, i) for i in range(num): q.get() assert len(pool._alive) == 4 @@ -52,13 +57,17 @@ finally: pool.shutdown() -def test_shutdown_timeout(): +def test_join_timeout(): pool = WorkerPool() + q = py.std.Queue.Queue() def f(): - py.std.time.sleep(0.5) - pool.dispatch(f) + q.get() + reply = pool.dispatch(f) pool.shutdown() - py.test.raises(IOError, pool.join, 0.2) + py.test.raises(IOError, pool.join, 0.01) + q.put(None) + reply.get(timeout=1.0) + pool.join(timeout=0.1) def test_pool_clean_shutdown(): pool = WorkerPool() From hpk at codespeak.net Thu Apr 21 23:53:20 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 21 Apr 2005 23:53:20 +0200 (CEST) Subject: [py-svn] r11007 - py/dist/py/test/tkinter/testing Message-ID: <20050421215320.41C5527B5B@code1.codespeak.net> Author: hpk Date: Thu Apr 21 23:53:20 2005 New Revision: 11007 Modified: py/dist/py/test/tkinter/testing/test_tixgui.py Log: turn test failure into a skip message Modified: py/dist/py/test/tkinter/testing/test_tixgui.py ============================================================================== --- py/dist/py/test/tkinter/testing/test_tixgui.py (original) +++ py/dist/py/test/tkinter/testing/test_tixgui.py Thu Apr 21 23:53:20 2005 @@ -2,7 +2,10 @@ import py def test_import_tix(): - import Tix + try: + import Tix + except ImportError: + py.test.skip("cannot import Tix") root = Tix.Tk() root.tk.eval('package require Tix') From hpk at codespeak.net Thu Apr 21 23:54:22 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 21 Apr 2005 23:54:22 +0200 (CEST) Subject: [py-svn] r11008 - py/dist/py/test/tkinter/testing Message-ID: <20050421215422.B589827B5B@code1.codespeak.net> Author: hpk Date: Thu Apr 21 23:54:22 2005 New Revision: 11008 Modified: py/dist/py/test/tkinter/testing/test_tixgui.py Log: slightly better style for skipping tests (in case some real more Tix tests are added) Modified: py/dist/py/test/tkinter/testing/test_tixgui.py ============================================================================== --- py/dist/py/test/tkinter/testing/test_tixgui.py (original) +++ py/dist/py/test/tkinter/testing/test_tixgui.py Thu Apr 21 23:54:22 2005 @@ -1,11 +1,15 @@ import py -def test_import_tix(): +def setup_module(mod): try: import Tix except ImportError: py.test.skip("cannot import Tix") + else: + mod.Tix = Tix + +def test_import_tix(): root = Tix.Tk() root.tk.eval('package require Tix') From jan at codespeak.net Fri Apr 22 16:22:14 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 22 Apr 2005 16:22:14 +0200 (CEST) Subject: [py-svn] r11023 - py/dist/py/test/tkinter Message-ID: <20050422142214.9CC0027B60@code1.codespeak.net> Author: jan Date: Fri Apr 22 16:22:14 2005 New Revision: 11023 Modified: py/dist/py/test/tkinter/tixgui.py Log: use Tkinter only / no Tix needed Modified: py/dist/py/test/tkinter/tixgui.py ============================================================================== --- py/dist/py/test/tkinter/tixgui.py (original) +++ py/dist/py/test/tkinter/tixgui.py Fri Apr 22 16:22:14 2005 @@ -3,25 +3,29 @@ from py.__impl__.test.tkinter import util Null = util.Null - import ScrolledText from Tkinter import PhotoImage -import Tix -from Tkconstants import * +import Tkinter import re import os -class StatusBar(Tix.Frame): +myfont = ('Helvetica', 9, 'normal') + +class StatusBar(Tkinter.Frame): + + font = ('Helvetica', 10, 'normal') def __init__(self, master=None, **kw): if master is None: master = Tk() - apply(Tix.Frame.__init__, (self, master), kw) + Tkinter.Frame.__init__(self, master, **kw) self.labels = {} - def set_label(self, name, text='', side=LEFT): + def set_label(self, name, text='', side=Tkinter.LEFT): if not self.labels.has_key(name): - label = Tix.Label(self, bd=1, relief=SUNKEN, anchor=W) + label = Tkinter.Label(self, bd=1, relief=Tkinter.SUNKEN, + anchor=Tkinter.W, + font=self.font) label.pack(side=side) self.labels[name] = label else: @@ -48,30 +52,43 @@ else: self.set_label('Time', '%0.2f seconds' % 0.0) -class ReportListBox(Tix.LabelFrame): +class ReportListBox(Tkinter.LabelFrame): + + font = myfont def __init__(self, *args, **kwargs): - Tix.LabelFrame.__init__(self, *args, **kwargs) + Tkinter.LabelFrame.__init__(self, *args, **kwargs) self.callback = Null() self.data = {} + self.label = Tkinter.Label(self) + self.label.configure(font = self.font, width = 80, anchor = Tkinter.W) + self.configure(labelwidget=self.label) self.createwidgets() self.label.configure(text = 'Idle') + self.configure(font = myfont) + def createwidgets(self): - self.listbox = Tix.Listbox(self.frame, foreground='red', - selectmode=SINGLE) + self.listbox = Tkinter.Listbox(self, foreground='red', + selectmode=Tkinter.SINGLE, font = self.font) - self.scrollbar = Tix.Scrollbar(self.frame, command=self.listbox.yview) - self.scrollbar.pack(side = RIGHT, fill = Y, anchor = N) - self.listbox.pack(side = LEFT, fill = BOTH, expand = YES, anchor = NW) - self.listbox.configure(yscrollcommand = self.scrollbar.set) + self.scrollbar = Tkinter.Scrollbar(self, command=self.listbox.yview) + self.scrollbar.pack(side = Tkinter.RIGHT, fill = Tkinter.Y, + anchor = Tkinter.N) + self.listbox.pack(side = Tkinter.LEFT, + fill = Tkinter.BOTH, expand = Tkinter.YES, + anchor = Tkinter.NW) + self.listbox.configure(yscrollcommand = self.scrollbar.set, + bg = 'White',selectbackground= 'Red', + takefocus= Tkinter.YES) def set_callback(self, callback): self.callback = callback self.listbox.bind('', self.do_callback) def do_callback(self, *args): - report_ids = [self.data[self.listbox.get(int(item))] for item in self.listbox.curselection()] + report_ids = [self.data[self.listbox.get(int(item))] + for item in self.listbox.curselection()] for report_id in report_ids: self.callback(report_id) @@ -87,13 +104,13 @@ if len(failed_childs + skipped_childs) == len(self.data.keys()): return old_selection = [int(sel) for sel in self.listbox.curselection()] - self.listbox.delete(0, END) + self.listbox.delete(0, Tkinter.END) self.data = {} for report_id in failed_childs + skipped_childs: report = repository.find(report_id) label = '%s: %s' % (report.status, report.label) self.data[label] = report.full_id - self.listbox.insert(END, label) + self.listbox.insert(Tkinter.END, label) for index in old_selection: try: self.listbox.select_set(index) @@ -102,10 +119,25 @@ - +class LabelEntry(Tkinter.Frame): + + font = myfont + + def __init__(self, *args, **kwargs): + Tkinter.Frame.__init__(self, *args, **kwargs) + self.label = Tkinter.Label(self) + self.label.configure(font = self.font) + self.label.pack(side = Tkinter.LEFT) + self.entry = Tkinter.Entry(self) + self.entry.configure(font = self.font) + self.entry.pack(side = Tkinter.LEFT, expand = Tkinter.YES, + fill = Tkinter.X) + class TixGui: + font = ('Helvetica', 9, 'normal') + def __init__(self, parent, config): self._parent = parent self._config = config @@ -117,49 +149,50 @@ self.timer_update() def createwidgets(self): - self._buttonframe = Tix.Frame(self._parent) - self._entry = Tix.FileEntry(self._buttonframe, label = 'Tests:', - command = self.start_tests) - self._entry.pack(side = LEFT, fill = X, expand = YES) - self._stop = Tix.Button(self._buttonframe, text = 'Stop', - command = self.stop) - self._stop.pack(side = RIGHT) - self._run = Tix.Button(self._buttonframe, text = 'Run', - command = self._entry.invoke) - self._run.pack(side = RIGHT) - self._buttonframe.pack(side = TOP, fill = X) + self._buttonframe = Tkinter.Frame(self._parent) + self._entry = LabelEntry(self._buttonframe) + self._entry.label.configure(text = 'Enter test name:') + self._entry.entry.bind('', self.start_tests) + self._entry.pack(side = Tkinter.LEFT, fill = Tkinter.X, + expand = Tkinter.YES) + self._stop = Tkinter.Button(self._buttonframe, text = 'Stop', + command = self.stop, font = self.font) + self._stop.pack(side = Tkinter.RIGHT) + self._run = Tkinter.Button(self._buttonframe, text = 'Run', + command = self.start_tests, font = self.font) + self._run.pack(side = Tkinter.RIGHT) + self._buttonframe.pack(side = Tkinter.TOP, fill = Tkinter.X) self._reportlist = ReportListBox(self._parent) - self._reportlist.label.configure(width=80,anchor=W ) - self._reportlist.pack(side = TOP, fill = BOTH, expand = YES) + self._reportlist.pack(side = Tkinter.TOP, fill = Tkinter.BOTH, + expand = Tkinter.YES) self._reportlist.set_callback(self.show_error) - #self._tree = reporttree.ReportTree(self._parent, backend = self.backend) - #self._tree.pack(side = TOP, fill = BOTH, expand = YES) self._statusbar = StatusBar(self._parent) - self._statusbar.pack(side=BOTTOM, fill=X) + self._statusbar.pack(side= Tkinter.BOTTOM, fill=Tkinter.X) self.update_status(self.backend.get_repository()) def show_error(self, report_id): report = self.backend.get_repository().find(report_id) - window = Tix.Toplevel(self._parent) + window = Tkinter.Toplevel(self._parent) window.title(report.label) window.protocol('WM_DELETE_WINDOW', window.quit) - Tix.Label(window, text='%s: %s' % (report.status, report.label), - foreground="red", justify=LEFT).pack(anchor=W) + Tkinter.Label(window, text='%s: %s' % (report.status, report.label), + foreground="red", justify=Tkinter.LEFT).pack(anchor=Tkinter.W) text = ScrolledText.ScrolledText(window) - text.tag_config('sel', relief=FLAT) - text.insert(END, report.error_report) + text.configure(bg = 'White', font = ('Helvetica', 11, 'normal')) + text.tag_config('sel', relief=Tkinter.FLAT) + text.insert(Tkinter.END, report.error_report) if len(report.error_report.splitlines()) < 20: text.config(height=len(report.error_report.splitlines()) + 5) - text.yview_pickplace(END) - text['state'] = DISABLED + text.yview_pickplace(Tkinter.END) + text['state'] = Tkinter.DISABLED text['cursor'] = window['cursor'] self.attacheditorhotspots(text) - text.pack(expand=1, fill=BOTH) - ## - b = Tix.Button(window, text="Close", - command=window.quit) - b.pack(side=BOTTOM) + text.pack(expand=Tkinter.YES, fill=Tkinter.BOTH) + + b = Tkinter.Button(window, text="Close", + command=window.quit, font = self.font) + b.pack(side=Tkinter.BOTTOM) b.focus_set() window.bind('', lambda e, w=window: w.quit()) window.mainloop() @@ -169,7 +202,7 @@ # Attach clickable regions to a Text widget. filelink = re.compile(r"""\[(?:testcode\s*:)?\s*(.+):(\d+)\]""") skippedlink = re.compile(r"""in\s+(/.*):(\d+)\s+""") - lines = text.get('1.0', END).splitlines(1) + lines = text.get('1.0', Tkinter.END).splitlines(1) if not lines: return tagname = '' @@ -211,6 +244,8 @@ self._parent.after(100, self.timer_update) def start_tests(self, dont_care_event=None): + if self.backend.running: + return paths = [path.strip() for path in self._entry.entry.get().split(',')] self.backend.set_messages_callback(self.messages_callback) self.backend.set_message_callback(self.message_callback) @@ -246,7 +281,7 @@ def set_paths(self, paths): self._paths = paths - self._entry.entry.insert(END, ', '.join(paths)) + self._entry.entry.insert(Tkinter.END, ', '.join(paths)) def stop(self): self.backend.shutdown() @@ -264,7 +299,7 @@ self.config = config def main(self, paths): - root = Tix.Tk() + root = Tkinter.Tk() tixgui = TixGui(root, self.config) tixgui.set_paths(paths) root.protocol('WM_DELETE_WINDOW', tixgui.shutdown) From jan at codespeak.net Fri Apr 22 16:22:40 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 22 Apr 2005 16:22:40 +0200 (CEST) Subject: [py-svn] r11024 - py/dist/py/test/tkinter Message-ID: <20050422142240.D323B27B60@code1.codespeak.net> Author: jan Date: Fri Apr 22 16:22:40 2005 New Revision: 11024 Modified: py/dist/py/test/tkinter/backend.py Log: don't reuse gateway Modified: py/dist/py/test/tkinter/backend.py ============================================================================== --- py/dist/py/test/tkinter/backend.py (original) +++ py/dist/py/test/tkinter/backend.py Fri Apr 22 16:22:40 2005 @@ -72,7 +72,6 @@ self._message_callback = Null() self._messages_callback = Null() self.config = config - self.gateway = None def running(self): '''are there tests running?''' @@ -85,7 +84,6 @@ if self.running: self.channel.close() self.channel.gateway.exit() - self.gateway = None def get_repository(self): '''return the repository''' @@ -113,11 +111,12 @@ self._messages_callback(changed_report_ids) def start_tests(self, config = None, args = [], tests = []): + if self.running: + return if config is None: config = self.config self.testrepository = TestRepository() - if not self.gateway: - self.gateway = py.execnet.PopenGateway(config.option.executable) + 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 = ''' import py From hpk at codespeak.net Fri Apr 22 16:47:00 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 22 Apr 2005 16:47:00 +0200 (CEST) Subject: [py-svn] r11026 - py/dist/py/documentation Message-ID: <20050422144700.34C1127BC2@code1.codespeak.net> Author: hpk Date: Fri Apr 22 16:46:59 2005 New Revision: 11026 Modified: py/dist/py/documentation/conftest.py py/dist/py/documentation/future.txt py/dist/py/documentation/xml.txt Log: fix some links (py.test -R documentation is great :-) Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Fri Apr 22 16:46:59 2005 @@ -111,9 +111,9 @@ try: print "trying remote", tryfn py.std.urllib2.urlopen(tryfn) - except py.std.urllib2.HTTPError: - py.test.fail("remote reference error %r in %s:%d" %( - tryfn, path.basename, lineno+1)) + except (py.std.urllib2.URLError, py.std.urllib2.HTTPError), e: + py.test.fail("remote reference error %r in %s:%d\n%s" %( + tryfn, path.basename, lineno+1, e)) def localrefcheck(tryfn, path, lineno): # assume it should be a file Modified: py/dist/py/documentation/future.txt ============================================================================== --- py/dist/py/documentation/future.txt (original) +++ py/dist/py/documentation/future.txt Fri Apr 22 16:46:59 2005 @@ -436,4 +436,4 @@ For this, we would like to move the lazy list into the py lib's namespace, most probably at `py.builtin.lazylist`. -.. _`lazy list`: http://codespeak.net/svn/user/arigo/hack/collect +.. _`lazy list`: http://codespeak.net/svn/user/arigo/hack/misc/collect.py Modified: py/dist/py/documentation/xml.txt ============================================================================== --- py/dist/py/documentation/xml.txt (original) +++ py/dist/py/documentation/xml.txt Fri Apr 22 16:46:59 2005 @@ -169,5 +169,5 @@ your Tags. Hum, it's probably harder to explain this than to actually code it :-) -.. _Nevow: http://www.divmod.org/Home/Projects/Nevow/ +.. _Nevow: http://www.divmod.org/projects/nevow .. _`py.test`: test.html From jan at codespeak.net Fri Apr 22 17:19:04 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 22 Apr 2005 17:19:04 +0200 (CEST) Subject: [py-svn] r11027 - py/dist/py/test/tkinter Message-ID: <20050422151904.37D8D27B61@code1.codespeak.net> Author: jan Date: Fri Apr 22 17:19:03 2005 New Revision: 11027 Modified: py/dist/py/test/tkinter/tixsession.py Log: fix import in tixsession Modified: py/dist/py/test/tkinter/tixsession.py ============================================================================== --- py/dist/py/test/tkinter/tixsession.py (original) +++ py/dist/py/test/tkinter/tixsession.py Fri Apr 22 17:19:03 2005 @@ -5,7 +5,8 @@ self.config = config def main(self, paths): - root = Tix.Tk() + import Tkinter + root = Tkinter.Tk() from tixgui import TixGui tixgui = TixGui(root, self.config) tixgui.set_paths(paths) From jan at codespeak.net Fri Apr 22 18:09:52 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 22 Apr 2005 18:09:52 +0200 (CEST) Subject: [py-svn] r11030 - py/dist/py/test/tkinter Message-ID: <20050422160952.A22A727B62@code1.codespeak.net> Author: jan Date: Fri Apr 22 18:09:52 2005 New Revision: 11030 Removed: py/dist/py/test/tkinter/gui.py py/dist/py/test/tkinter/tkgui.py py/dist/py/test/tkinter/tktree.py Log: delete old files Deleted: /py/dist/py/test/tkinter/gui.py ============================================================================== --- /py/dist/py/test/tkinter/gui.py Fri Apr 22 18:09:52 2005 +++ (empty file) @@ -1,180 +0,0 @@ -from tktree import Tree, Node -from util import TestReport, Status, Null -from Tkinter import PhotoImage -import py -Item = py.test.Item -Collector = py.test.collect.Collector - - -class ResultTree(Tree): - - def __init__(self, *args, **kwargs): - kwdict = {'dist_x': 19, 'dist_y': 19, - 'selection_callback': Null(), - 'double_click_callback': Null()} - kwdict.update(kwargs) - self.set_selection_callback(kwdict['selection_callback']) - self.set_double_click_callback(kwdict['double_click_callback']) - del kwdict['selection_callback'] - del kwdict['double_click_callback'] - self.setup_icons() - - Tree.__init__(self, *args, **kwdict) - self.save_view() - self.save_selection() - self.bind('<4>', self.prev) - self.bind('<5>', self.next) - - def setup_icons(self): - iconpath = py.magic.autopath().dirpath('icon') - self.icon_green = PhotoImage(file = str(iconpath.join('green.gif'))) - self.icon_red = PhotoImage(file = str(iconpath.join('red.gif'))) - self.icon_yellow = PhotoImage(file = str(iconpath.join('yellow.gif'))) - self.icon_gray = PhotoImage(file = str(iconpath.join('gray.gif'))) - - self.icon_map = {str(Status.NotExecuted()): self.icon_gray, - str(Status.Passed()): self.icon_green, - str(Status.Failed()): self.icon_red, - str(Status.Skipped()): self.icon_yellow, - } - - def get_icon(self, status): - status = str(status) - return self.icon_map[status] - - def add_node(self, *args, **kwargs): - report = kwargs.setdefault('report', TestReport()) - icon = self.get_icon(report.status) - kwdict = {'collapsed_icon': icon, 'expanded_icon': icon} - del kwargs['report'] - kwdict.update(kwargs) - Tree.add_node(self, *args, **kwdict) - - def set_selection_callback(self, callback): - self._selection_callback = callback - - def set_double_click_callback(self, callback): - self._double_click_callback = callback - - def double_click_callback(self, node = None): - self._double_click_callback(self.pos) - - def save_view(self): - self.saved_view = self.get_expanded_state() - - def save_selection(self): - self.saved_selection_id = self.cursor_node(None).full_id() - - def restore_view(self): - self.apply_expanded_state(self.saved_view) - - def restore_selection(self): - if self.find_full_id(self.saved_selection_id): - self.move_cursor(self.find_full_id(self.saved_selection_id)) - - def next(self, event = None): - Tree.next(self, event) - self.save_selection() - - def prev(self, event = None): - Tree.prev(self, event) - self.save_selection() - - - def move_cursor(self, node): - Tree.move_cursor(self, node) - self._selection_callback(node) - - def listnodes(self, startnode = None): - if startnode is None: - startnode = self.root - ret = [startnode] - for child in startnode.children(): - ret.append(child) - ret.extend(self.listnodes(child)) - return ret - - - def get_expanded_state(self, key = None): - "returns [node.full_id() for all_nodes if node.expanded() == True" - if key is None: - key = self.root.full_id() - if not self.find_full_id(key) or not self.find_full_id(key).expanded(): - return [] - startnode = self.find_full_id(key) - return [node.full_id() for node in self.listnodes(startnode) if node.expanded()] - - def apply_expanded_state(self, id_list): - ids = [item for item in id_list] - ids.sort() - for id in ids: - node = self.find_full_id(id) - if node is None or not node.expandable(): - continue - node.expand() - pass - - def get_parent(self, node_id): - node = self.find_full_id(node_id) - if node: - return node.parent() - return self.find_full_id(node_id[:-1]) - - def update_root(self, report): - self.root.set_collapsed_icon(self.get_icon(report.status)) - self.root.set_expanded_icon(self.get_icon(report.status)) - self.root.set_label(report.label) - - def update_node(self, node_id): - if node_id == self.root.full_id() or self.get_parent(node_id) is None: - parent = self.root - else: - parent = self.get_parent(node_id) - - if not parent.expandable(): - self.update_node(parent.full_id()) - return - - if parent.expanded(): - parent.toggle_state() - parent.toggle_state() - self.restore_view() - self.restore_selection() - - - -class ResultNode(Node): - - def __init__(self, *args, **kwargs): - self.args = args - self.kwargs = kwargs - Node.__init__(self, *args, **kwargs) - self.widget.tag_bind(self.symbol, '', - self.PVT_click_select, add='+') - self.widget.tag_bind(self.label, '', - self.PVT_click_select, add='+') - self.widget.tag_bind(self.symbol, '', - self.PVT_double_click, add='+') - self.widget.tag_bind(self.label, '', - self.PVT_double_click, add='+') - - - - def get_node_args(self): - return self.args, self.kwargs - - def PVT_click(self, event): - Node.PVT_click(self, event) - self.widget.save_selection() - self.widget.save_view() - - def PVT_click_select(self, event): - # not interested in dnd, so just do a select - self.widget.move_cursor(self) - self.widget.save_selection() - - def PVT_double_click(self, event): - self.widget.double_click_callback(self) - - - Deleted: /py/dist/py/test/tkinter/tkgui.py ============================================================================== --- /py/dist/py/test/tkinter/tkgui.py Fri Apr 22 18:09:52 2005 +++ (empty file) @@ -1,367 +0,0 @@ -import Tkinter -tk = Tkinter -import ScrolledText -import time -import threading -import Queue -import sys -import os -import re -import sha - -import py -Item = py.test.Item -Collector = py.test.collect.Collector - -import repository -import util -from util import TestReport, Status, Null, TestFileWatcher -import gui - - -class TkMain: - def __init__(self, parent, queue, endcommand, startcommand, repositorycommand, manager = Null()): - self.parent = parent - self.queue = queue - self.endcommand = endcommand - self.startcommand = startcommand - self.repositorycommand = repositorycommand - self.manager = manager - # GUI - self.text = Null() - self.createwidgets() - start = Tkinter.Button(parent, text='Start', command=startcommand) - start.pack(side = tk.TOP) - done = Tkinter.Button(parent, text='Done', command=endcommand) - done.pack(side = tk.TOP) - - - def createwidgets(self): - self.mainframe = tk.Frame(self.parent) - self.reportlabel = tk.Label(self.mainframe, text = ' \n ', anchor=tk.W) - self.reportlabel.pack() - self.treeframe = tk.Frame(self.mainframe) - self.tree = gui.ResultTree(master=self.treeframe, root_id='Root', - get_contents_callback=self.create_new_nodes, - width=400, node_class = gui.ResultNode, - selection_callback=self.selection_callback, - double_click_callback = self.double_click_callback) - sb=tk.Scrollbar(self.treeframe) - sb.pack(side=tk.RIGHT, fill = tk.Y) - self.tree.configure(yscrollcommand=sb.set) - sb.configure(command=self.tree.yview) - - sb=tk.Scrollbar(self.treeframe, orient=tk.HORIZONTAL) - sb.pack(side=tk.BOTTOM, fill = tk.X) - self.tree.configure(xscrollcommand=sb.set) - sb.configure(command=self.tree.xview) - - self.tree.pack(side = tk.LEFT, expand=tk.YES, fill = tk.BOTH) - self.treeframe.pack(side = tk.TOP, expand = tk.YES, fill = tk.BOTH) - - - self.text = ScrolledText.ScrolledText(self.mainframe) - self.text.pack(side = tk.TOP, expand=tk.YES, fill = tk.BOTH) - self.mainframe.pack(side = tk.LEFT, expand = tk.YES, fill = tk.BOTH) - - def double_click_callback(self, node): - self.startcommand(rerun_ids=[node.full_id()]) - - def selection_callback(self, node): - # check if node exists - if not self.repositorycommand().haskey(node.full_id()): - return - report = self.repositorycommand().find(node.full_id()) - self.text['state'] = tk.NORMAL - self.text.delete(1.0, tk.END) - self.text.tag_config('sel', relief=tk.FLAT) - self.text.insert(tk.END, report.error_report) - #if len(report.error_report.splitlines()) < 20: - # self.text.config(height=len(report.error_report.splitlines()) + 5) - #self.text.yview_pickplace(tk.END) - self.attacheditorhotspots(self.text) - self.text['state'] = tk.DISABLED - - self.reportlabel['text'] = '%s\n%s' % (report.path, report.modpath) - - def attacheditorhotspots(self, text): - # Attach clickable regions to a Text widget. - filelink = re.compile(r"""\[(?:testcode\s*:)?\s*(.+):(\d+)\]""") - skippedlink = re.compile(r"""in\s+(/.*):(\d+)\s+""") - lines = text.get('1.0', tk.END).splitlines(1) - if not lines: - return - tagname = '' - start, end = 0,0 - for index, line in enumerate(lines): - match = filelink.search(line) - if match is None: - match = skippedlink.search(line) - if match is None: - continue - file, line = match.group(1, 2) - start, end = match.span() - tagname = "ref%d" % index - text.tag_add(tagname, - "%d.%d" % (index + 1, start), - "%d.%d" % (index + 1, end)) - text.tag_bind(tagname, "", - lambda e, n=tagname: - e.widget.tag_config(n, underline=1)) - text.tag_bind(tagname, "", - lambda e, n=tagname: - e.widget.tag_config(n, underline=0)) - text.tag_bind(tagname, "", - lambda e, self=self, f=file, l=line: - self.launch_editor(f, l)) - - def launch_editor(self, file, line): - editor = (py.std.os.environ.get('PYUNIT_EDITOR', None) or - py.std.os.environ.get('EDITOR_REMOTE', None) or - os.environ.get('EDITOR', None) or "emacsclient --no-wait ") - #"emacsclient --no-wait ") - if editor: - print "%s +%s %s" % (editor, line, file) - #py.process.cmdexec('%s +%s %s' % (editor, line, file)) - os.system('%s +%s %s' % (editor, line, file)) - - def create_new_nodes(self, node): - repos = self.repositorycommand() - id = node.full_id() - for child in repos.find_children(id): - if not repos.haskeyandvalue(child): - continue - report = repos.find(child) - name = report.label - folder = 0 - if repos.find_children(child): - folder = 1 - self.tree.add_node(name=name, id=report.id, flag=folder, report = report) - - def display_action(self, action, node_id=None): - self.parent.title(str(action)) - if node_id is not None: - report = self.repositorycommand().find(node_id) - self.reportlabel['text'] = '%s\n%s' % (report.path, - report.modpath) - - def process_messages(self, manager): - """ - Handle all the messages currently in the queue (if any). - """ - while self.queue.qsize(): - try: - full_id = self.queue.get(0) - if full_id is None: - root_status = self.repositorycommand().find(self.tree.root.full_id()).status - self.display_action(root_status) - if root_status != Status.Passed(): - self.parent.bell() - py.std.time.sleep(0.2) - self.parent.bell() - else: - self.tree.update_node(full_id) - self.display_action('running: ' + str(full_id[-1]), full_id ) - except Queue.Empty: - pass - self.tree.update_root(self.repositorycommand().find(self.tree.root.full_id())) - - -class Manager: - """ - Launch the main part of the GUI and the worker thread. periodicCall and - endApplication could reside in the GUI part, but putting them here - means that you have all the thread controls in a single place. - """ - def __init__(self, parent): - """ - Start the GUI and the asynchronous threads. We are in the main - (original) thread of the application, which will later be used by - the GUI. - """ - self.parent = parent - - self.reset_repository() - self.args = py.std.sys.argv[1:] - self.paths = py.test.Config.parse(self.args)[1] - self.testfilewatcher = TestFileWatcher(*self.paths) - # Create the queue - self.guiqueue = Queue.Queue() - self.reporterqueue = Queue.Queue() - - self.should_stop = False - - # Set up the GUI part - self.gui = TkMain(parent, self.guiqueue, self.endApplication, - self.start_tests, self.getrepository, manager = self) - - # Set up the thread to do asynchronous I/O - self.thread = Null() - # Start the periodic call in the GUI to check if the queue contains - # anything - self.check_messages() - - def reset_repository(self, ids = []): - if not ids: - self.repository = self.get_new_repository() - return - for id in ids: - self.repository.delete_all(id) - for key, value in self.get_new_repository().items(): - if not self.repository.haskey(key): - self.repository.add(key, value) - - - def get_new_repository(self): - new_repository = repository.Repository() - template = TestReport() - new_repository.add(['Root'], template.copy()) - for name in [str(x) for x in (Status.Failed(), - Status.Skipped())]: - new_repository.add(['Root', name], template.copy(id = name, label= name)) - return new_repository - - - def thread_is_alive(self): - return self.thread.isAlive() == True - running = property(thread_is_alive) - - - def getrepository(self): - return self.repository - - - def check_messages(self): - """ - Check every 200 ms if there is something new in the queue. - """ - while self.reporterqueue.qsize(): - try: - report_fromChannel = self.reporterqueue.get(0) - if report_fromChannel is None: - self.guiqueue.put(None) - else: - report = TestReport.fromChannel(report_fromChannel) - root_id = TestReport().full_id - # keep root up-to-date - self.repository.find(root_id).status.update(report.status) - if report.full_id == root_id: - # don't overwrite overall status on partial run - report.status = Status(self.repository.find(root_id).status) - else: - # hack: prefix Root for all Tests, except Root - report.full_id = ['Root'] + report.full_id - id = report.full_id - self.repository.add(id, report) - added_name = self.add_status(report) - if added_name is not None: - self.guiqueue.put(added_name) - self.guiqueue.put(id) - except Queue.Empty: - pass - - self.gui.process_messages(self) - if self.should_stop: - # This is the brutal stop of the system. You may want to do - # some cleanup before actually shutting it down. - import sys - sys.exit(1) - if not self.running and self.testfilewatcher.changed(): - self.start_tests() - self.parent.after(200, self.check_messages) - - def add_status(self, newreport): - report = newreport.copy() - status = report.status - if report.error_report: - folder = ('Root', str(report.status)) - # unique id, for failing tests with the same name - report.id = report.id + str(report.full_id) - id = folder + (report.id,) - self.repository.find(folder).status.update(report.status) - self.repository.add(id, report) - return id - - def endApplication(self): - self.should_stop = True - #print 'System is shutting down now!' - #print 'join thread %s' % self.thread - self.thread.join() - #print 'Thread is down' - - - def get_restart_params_list(self, id): - repos = self.getrepository() - if repos.find(id).restart_params is not None: - return [repos.find(id).restart_params] - params = [] - for child_id in repos.find_children(id): - params.extend(self.get_restart_params_list(child_id)) - return params - - def start_tests(self, filenames = [], rerun_ids = []): - if self.running: - return - rerun_cols = [] - for id in rerun_ids: - rerun_cols.extend(self.get_restart_params_list(id)) - - self.reset_repository(rerun_ids) - if filenames == []: - filenames = [str(p) for p in self.paths] - kwargs = {'rerun_cols': rerun_cols} - if rerun_cols == []: - kwargs['rerun_cols'] = [(filename,[]) for filename in filenames] - - self.thread = threading.Thread(target=self.run_tests_threaded, - kwargs = kwargs) - self.thread.start() - - - def run_tests_threaded(self, rerun_cols = None): - gw = py.execnet.PopenGateway(sys.executable) - try: - channel = gw.remote_exec(""" - import py - from py.__impl__.test.tkinter.guisession import GuiSession - from py.__impl__.test.tkinter import repository - import os - import time - from py.__impl__.test.terminal.remote import getfailureitems - - rerun_cols, args = channel.receive() - col = getfailureitems(rerun_cols) - # hack!! - config, paths = py.test.Config.parse(args) - session = GuiSession(config = config, channel=channel) - session.main(col) - """) - channel.send((rerun_cols, self.args)) - self.receive_all_data(channel) - self.channel_waitclose(channel) - finally: - channel.close() - channel.gateway.exit() - - def receive_all_data(self, channel): - while True: - data = channel.receive() - self.reporterqueue.put(data) - if data is None: - break - - - def channel_waitclose(self, channel): - while 1: - try: - channel.waitclose(0.1) - except (IOError, py.error.Error): - continue - break - -class TkinterSession(py.test.Session): - def main(self, paths): - root = Tkinter.Tk() - client = Manager(root) - root.protocol('WM_DELETE_WINDOW', client.endApplication) - root.mainloop() Deleted: /py/dist/py/test/tkinter/tktree.py ============================================================================== --- /py/dist/py/test/tkinter/tktree.py Fri Apr 22 18:09:52 2005 +++ (empty file) @@ -1,911 +0,0 @@ -# Highly optimized Tkinter tree control -# by Charles E. "Gene" Cash -# -# This is documented more fully on my homepage at -# http://home.cfl.rr.com/genecash/ and if it's not there, look in the Vaults -# of Parnassus at http://www.vex.net/parnassus/ which I promise to keep -# updated. -# -# Thanks to Laurent Claustre for sending lots of helpful -# bug reports. -# -# This copyright license is intended to be similar to the FreeBSD license. -# -# Copyright 1998 Gene Cash All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the -# distribution. -# -# THIS SOFTWARE IS PROVIDED BY GENE CASH ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -# This means you may do anything you want with this code, except claim you -# wrote it. Also, if it breaks you get to keep both pieces. -# -# 02-DEC-98 Started writing code. -# 22-NOV-99 Changed garbage collection to a better algorithm. -# 28-AUG-01 Added logic to deal with exceptions in user callbacks. -# 02-SEP-01 Fixed hang when closing last node. -# 07-SEP-01 Added binding tracking so nodes got garbage-collected. -# Also fixed subclass call to initialize Canvas to properly deal -# with variable arguments and keyword arguments. -# 11-SEP-01 Bugfix for unbinding code. -# 13-OCT-01 Added delete & insert methods for nodes (by email request). -# LOTS of code cleanup. -# Changed leading double underscores to PVT nomenclature. -# Added ability to pass Node subclass to Tree constructor. -# Removed after_callback since subclassing Node is better idea. -# 15-OCT-01 Finally added drag'n'drop support. It consisted of a simple -# change to the Node PVT_click method, and addition of logic like -# the example in Tkdnd.py. It took 3 days to grok the Tkdnd -# example and 2 hours to make the code changes. Plus another 1/2 -# day to get a working where() function. -# 16-OCT-01 Incorporated fixes to delete() and dnd_commit() bugs by -# Laurent Claustre . -# 17-OCT-01 Added find_full_id() and cursor_node() methods. -# 18-OCT-01 Fixes to delete() on root during collapse and with -# drag-in-progress flag by Laurent Claustre . -# 10-FEB-02 Fix to prev_visible() by Nicolas Pascal . -# Fixes which made insert_before()/insert_after() actually work. -# Also added expand/collapse indicators like Internet Explorer -# as requested by Nicolas. -# 11-FEB-02 Another fix to prev_visible(). It works this time. Honest. -# 31-MAY-02 Added documentation strings so the new PYthon 2.2 help function -# is a little more useful. -# 19-AUG-02 Minor fix to eliminate crash in "treedemo-icons.py" caused by -# referencing expand/collapse indicators when lines are turned off. -# 15-OCT-02 Used new idiom for calling Canvas superclass. -# 18-NOV-02 Fixed bug discovered by Amanjit Gill , where -# I didn't pass "master" properly to the Canvas superclass. Sigh. -# One step forward, one step back. - -import Tkdnd -from Tkinter import * - -#------------------------------------------------------------------------------ -def report_callback_exception(): - """report exception on sys.stderr.""" - import traceback - import sys - - sys.stderr.write("Exception in Tree control callback\n") - traceback.print_exc() - -#------------------------------------------------------------------------------ -class Struct: - """Helper object for add_node() method""" - def __init__(self): - pass - -#------------------------------------------------------------------------------ -class Node: - """Tree helper class that's instantiated for each element in the tree. It - has several useful attributes: - parent_node - immediate parent node - id - id assigned at creation - expanded_icon - image displayed when folder is expanded to display - children - collapsed_icon - image displayed when node is not a folder or folder is - collapsed. - parent_widget - reference to tree widget that contains node. - expandable_flag - is true when node is a folder that may be expanded or - collapsed. - expanded_flag - true to indicate node is currently expanded. - h_line - canvas line to left of node image. - v_line - canvas line below node image that connects children. - indic - expand/collapse canvas image. - label - canvas text label - symbol - current canvas image - - Please note that methods prefixed PVT_* are not meant to be used by - client programs.""" - - def __init__(self, parent_node, id, collapsed_icon, x, y, - parent_widget=None, expanded_icon=None, label=None, - expandable_flag=0): - """Create node and initialize it. This also displays the node at the - given position on the canvas, and binds mouseclicks.""" - # immediate parent node - self.parent_node=parent_node - # internal name used to manipulate things - self.id=id - # bitmaps to be displayed - self.expanded_icon=expanded_icon - self.collapsed_icon=collapsed_icon - # tree widget we belong to - if parent_widget: - self.widget=parent_widget - else: - self.widget=parent_node.widget - # for speed - sw=self.widget - # our list of child nodes - self.child_nodes=[] - # flag that node can be expanded - self.expandable_flag=expandable_flag - self.expanded_flag=0 - # add line - if parent_node and sw.line_flag: - self.h_line=sw.create_line(x, y, x-sw.dist_x, y) - else: - self.h_line=None - self.v_line=None - # draw approprate image - self.symbol=sw.create_image(x, y, image=self.collapsed_icon) - # add expand/collapse indicator - self.indic=None - if expandable_flag and sw.line_flag and sw.plus_icon and sw.minus_icon: - self.indic=sw.create_image(x-sw.dist_x, y, image=sw.plus_icon) - # add label - self.label=sw.create_text(x+sw.text_offset, y, text=label, anchor='w') - # single-click to expand/collapse - if self.indic: - sw.tag_bind(self.indic, '<1>', self.PVT_click) - else: - sw.tag_bind(self.symbol, '<1>', self.PVT_click) - ## Modification: symbol always clickable - #sw.tag_bind(self.symbol, '<1>', self.PVT_click) - - # for drag'n'drop target detection - sw.tag_bind(self.symbol, '', self.PVT_enter) - sw.tag_bind(self.label, '', self.PVT_enter) - - # for testing (gotta make sure nodes get properly GC'ed) - #def __del__(self): - # print self.full_id(), 'deleted' - - # ----- PUBLIC METHODS ----- - def set_collapsed_icon(self, icon): - """Set node's collapsed image""" - self.collapsed_icon=icon - if not self.expanded_flag: - self.widget.itemconfig(self.symbol, image=icon) - - def set_expanded_icon(self, icon): - """Set node's expanded image""" - self.expanded_icon=icon - if self.expanded_flag: - self.widget.itemconfig(self.symbol, image=icon) - - def parent(self): - """Return node's parent node""" - return self.parent_node - - def prev_sib(self): - """Return node's previous sibling (the child immediately above it)""" - i=self.parent_node.child_nodes.index(self)-1 - if i >= 0: - return self.parent_node.child_nodes[i] - else: - return None - - def next_sib(self): - """Return node's next sibling (the child immediately below it)""" - i=self.parent_node.child_nodes.index(self)+1 - if i < len(self.parent_node.child_nodes): - return self.parent_node.child_nodes[i] - else: - return None - - def next_visible(self): - """Return next lower visible node""" - n=self - if n.child_nodes: - # if you can go right, do so - return n.child_nodes[0] - while n.parent_node: - # move to next sibling - i=n.parent_node.child_nodes.index(n)+1 - if i < len(n.parent_node.child_nodes): - return n.parent_node.child_nodes[i] - # if no siblings, move to parent's sibling - n=n.parent_node - # we're at bottom - return self - - def prev_visible(self): - """Return next higher visible node""" - n=self - if n.parent_node: - i=n.parent_node.child_nodes.index(n)-1 - if i < 0: - return n.parent_node - else: - j=n.parent_node.child_nodes[i] - return j.PVT_last() - else: - return n - - def children(self): - """Return list of node's children""" - return self.child_nodes[:] - - def get_label(self): - """Return string containing text of current label""" - return self.widget.itemcget(self.label, 'text') - - def set_label(self, label): - """Set current text label""" - self.widget.itemconfig(self.label, text=label) - - def expanded(self): - """Returns true if node is currently expanded, false otherwise""" - return self.expanded_flag - - def expandable(self): - """Returns true if node can be expanded (i.e. if it's a folder)""" - return self.expandable_flag - - def full_id(self): - """Return list of IDs of all parents and node ID""" - if self.parent_node: - return self.parent_node.full_id()+(self.id,) - else: - return (self.id,) - - def expand(self): - """Expand node if possible""" - if not self.expanded_flag: - self.PVT_set_state(1) - - def collapse(self): - """Collapse node if possible""" - if self.expanded_flag: - self.PVT_set_state(0) - - def delete(self, me_too=1): - """Delete node from tree. ("me_too" is a hack not to be used by - external code, please!)""" - sw=self.widget - if not self.parent_node and me_too: - # can't delete the root node - raise ValueError, "can't delete root node" - self.PVT_delete_subtree() - # move everything up so that distance to next subnode is correct - n=self.next_visible() - x1, y1=sw.coords(self.symbol) - x2, y2=sw.coords(n.symbol) - if me_too: - dist=y2-y1 - else: - dist=y2-y1-sw.dist_y - self.PVT_tag_move(-dist) - n=self - if me_too: - if sw.pos == self: - # move cursor if it points to current node - sw.move_cursor(self.parent_node) - self.PVT_unbind_all() - sw.delete(self.symbol) - sw.delete(self.label) - sw.delete(self.h_line) - sw.delete(self.v_line) - sw.delete(self.indic) - self.parent_node.child_nodes.remove(self) - # break circular ref now, so parent may be GC'ed later - n=self.parent_node - self.parent_node=None - n.PVT_cleanup_lines() - n.PVT_update_scrollregion() - - def insert_before(self, nodes): - """Insert list of nodes as siblings before this node. Call parent - node's add_node() function to generate the list of nodes.""" - i=self.parent_node.child_nodes.index(self) - self.parent_node.PVT_insert(nodes, i, self.prev_visible()) - - def insert_after(self, nodes): - """Insert list of nodes as siblings after this node. Call parent - node's add_node() function to generate the list of nodes.""" - i=self.parent_node.child_nodes.index(self)+1 - self.parent_node.PVT_insert(nodes, i, self.PVT_last()) - - def insert_children(self, nodes): - """Insert list of nodes as children of this node. Call node's - add_node() function to generate the list of nodes.""" - self.PVT_insert(nodes, 0, self) - - def toggle_state(self): - """Toggle node's state between expanded and collapsed, if possible""" - if self.expandable_flag: - if self.expanded_flag: - self.PVT_set_state(0) - else: - self.PVT_set_state(1) - - # ----- functions for drag'n'drop support ----- - def PVT_enter(self, event): - """detect mouse hover for drag'n'drop""" - self.widget.target=self - - def dnd_end(self, target, event): - """Notification that dnd processing has been ended. It DOES NOT imply - that we've been dropped somewhere useful, we could have just been - dropped into deep space and nothing happened to any data structures, - or it could have been just a plain mouse-click w/o any dragging.""" - if not self.widget.drag: - # if there's been no dragging, it was just a mouse click - self.widget.move_cursor(self) - self.toggle_state() - self.widget.drag=0 - - # ----- PRIVATE METHODS (prefixed with "PVT_") ----- - # these methods are subject to change, so please try not to use them - def PVT_last(self): - """Return bottom-most node in subtree""" - n=self - while n.child_nodes: - n=n.child_nodes[-1] - return n - - def PVT_find(self, search): - """Used by searching functions""" - if self.id != search[0]: - # this actually only goes tilt if root doesn't match - return None - if len(search) == 1: - return self - # get list of children IDs - i=map(lambda x: x.id, self.child_nodes) - # if there is a child that matches, search it - try: - return self.child_nodes[i.index(search[1])].PVT_find(search[1:]) - except: - return None - - def PVT_insert(self, nodes, pos, below): - """Create and insert new children. "nodes" is list previously created - via calls to add_list(). "pos" is index in the list of children where - the new nodes are inserted. "below" is node which new children should - appear immediately below.""" - if not self.expandable_flag: - raise TypeError, 'not an expandable node' - # for speed - sw=self.widget - # expand and insert children - children=[] - self.expanded_flag=1 - sw.itemconfig(self.symbol, image=self.expanded_icon) - if sw.minus_icon and sw.line_flag: - sw.itemconfig(self.indic, image=sw.minus_icon) - if len(nodes): - # move stuff to make room - below.PVT_tag_move(sw.dist_y*len(nodes)) - # get position of first new child - xp, dummy=sw.coords(self.symbol) - dummy, yp=sw.coords(below.symbol) - xp=xp+sw.dist_x - yp=yp+sw.dist_y - # create vertical line - if sw.line_flag and not self.v_line: - self.v_line=sw.create_line( - xp, yp, - xp, yp+sw.dist_y*len(nodes)) - sw.tag_lower(self.v_line, self.symbol) - n=sw.node_class - for i in nodes: - # add new subnodes, they'll draw themselves - # this is a very expensive call - children.append( - n(parent_node=self, expandable_flag=i.flag, label=i.name, - id=i.id, collapsed_icon=i.collapsed_icon, - expanded_icon=i.expanded_icon, x=xp, y=yp)) - yp=yp+sw.dist_y - self.child_nodes[pos:pos]=children - self.PVT_cleanup_lines() - self.PVT_update_scrollregion() - sw.move_cursor(sw.pos) - - def PVT_set_state(self, state): - """Common code forexpanding/collapsing folders. It's not re-entrant, - and there are certain cases in which we can be called again before - we're done, so we use a mutex.""" - while self.widget.spinlock: - pass - self.widget.spinlock=1 - # expand & draw our subtrees - if state: - self.child_nodes=[] - self.widget.new_nodes=[] - if self.widget.get_contents_callback: - # this callback needs to make multiple calls to add_node() - try: - self.widget.get_contents_callback(self) - except: - report_callback_exception() - self.PVT_insert(self.widget.new_nodes, 0, self) - # collapse and delete subtrees - else: - self.expanded_flag=0 - self.widget.itemconfig(self.symbol, image=self.collapsed_icon) - if self.indic: - self.widget.itemconfig(self.indic, image=self.widget.plus_icon) - self.delete(0) - # release mutex - self.widget.spinlock=0 - - def PVT_cleanup_lines(self): - """Resize connecting lines""" - if self.widget.line_flag: - n=self - while n: - if n.child_nodes: - x1, y1=self.widget.coords(n.symbol) - x2, y2=self.widget.coords(n.child_nodes[-1].symbol) - self.widget.coords(n.v_line, x1, y1, x1, y2) - n=n.parent_node - - def PVT_update_scrollregion(self): - """Update scroll region for new size""" - x1, y1, x2, y2=self.widget.bbox('all') - self.widget.configure(scrollregion=(x1, y1, x2+5, y2+5)) - - def PVT_delete_subtree(self): - """Recursively delete subtree & clean up cyclic references to make - garbage collection happy""" - sw=self.widget - sw.delete(self.v_line) - self.v_line=None - for i in self.child_nodes: - # delete node's subtree, if any - i.PVT_delete_subtree() - i.PVT_unbind_all() - # delete widgets from canvas - sw.delete(i.symbol) - sw.delete(i.label) - sw.delete(i.h_line) - sw.delete(i.v_line) - sw.delete(i.indic) - # break circular reference - i.parent_node=None - # move cursor if it's in deleted subtree - if sw.pos in self.child_nodes: - sw.move_cursor(self) - # now subnodes will be properly garbage collected - self.child_nodes=[] - - def PVT_unbind_all(self): - """Unbind callbacks so node gets garbage-collected. This wasn't easy - to figure out the proper way to do this. See also tag_bind() for the - Tree widget itself.""" - for j in (self.symbol, self.label, self.indic, self.h_line, - self.v_line): - for k in self.widget.bindings.get(j, ()): - self.widget.tag_unbind(j, k[0], k[1]) - - def PVT_tag_move(self, dist): - """Move everything below current icon, to make room for subtree using - the Disney magic of item tags. This is the secret of making - everything as fast as it is.""" - # mark everything below current node as movable - bbox1=self.widget.bbox(self.widget.root.symbol, self.label) - bbox2=self.widget.bbox('all') - self.widget.dtag('move') - self.widget.addtag('move', 'overlapping', - bbox2[0], bbox1[3], bbox2[2], bbox2[3]) - # untag cursor & node so they don't get moved too - self.widget.dtag(self.widget.cursor_box, 'move') - self.widget.dtag(self.symbol, 'move') - self.widget.dtag(self.label, 'move') - # now do the move of all the tagged objects - self.widget.move('move', 0, dist) - - def PVT_click(self, event): - """Handle mouse clicks by kicking off possible drag'n'drop - processing""" - if self.widget.drop_callback: - if Tkdnd.dnd_start(self, event): - x1, y1, x2, y2=self.widget.bbox(self.symbol) - self.x_off=(x1-x2)/2 - self.y_off=(y1-y2)/2 - else: - # no callback, don't bother with drag'n'drop - self.widget.drag=0 - self.dnd_end(None, None) - -#------------------------------------------------------------------------------ -class Tree(Canvas): - # do we have enough possible arguments?!?!?! - def __init__(self, master, root_id, root_label='', - get_contents_callback=None, dist_x=15, dist_y=15, - text_offset=10, line_flag=1, expanded_icon=None, - collapsed_icon=None, regular_icon=None, plus_icon=None, - minus_icon=None, node_class=Node, drop_callback=None, - *args, **kw_args): - # pass args to superclass (new idiom from Python 2.2) - Canvas.__init__(self, master, *args, **kw_args) - - # this allows to subclass Node and pass our class in - self.node_class=node_class - # keep track of node bindings - self.bindings={} - # cheap mutex spinlock - self.spinlock=0 - # flag to see if there's been any d&d dragging - self.drag=0 - # default images (BASE64-encoded GIF files) - if expanded_icon == None: - self.expanded_icon=PhotoImage( - data='R0lGODlhEAANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \ - 'ACH5BAEAAAEALAAAAAAQAA0AAAM6GCrM+jCIQamIbw6ybXNSx3GVB' \ - 'YRiygnA534Eq5UlO8jUqLYsquuy0+SXap1CxBHr+HoBjoGndDpNAAA7') - else: - self.expanded_icon=expanded_icon - if collapsed_icon == None: - self.collapsed_icon=PhotoImage( - data='R0lGODlhDwANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \ - 'ACH5BAEAAAEALAAAAAAPAA0AAAMyGCHM+lAMMoeAT9Jtm5NDKI4Wo' \ - 'FXcJphhipanq7Kvu8b1dLc5tcuom2foAQQAyKRSmQAAOw==') - else: - self.collapsed_icon=collapsed_icon - if regular_icon == None: - self.regular_icon=PhotoImage( - data='R0lGODlhCwAOAJEAAAAAAICAgP///8DAwCH5BAEAAAMALAAA' \ - 'AAALAA4AAAIphA+jA+JuVgtUtMQePJlWCgSN9oSTV5lkKQpo2q5W+' \ - 'wbzuJrIHgw1WgAAOw==') - else: - self.regular_icon=regular_icon - if plus_icon == None: - self.plus_icon=PhotoImage( - data='R0lGODdhCQAJAPEAAAAAAH9/f////wAAACwAAAAACQAJAAAC' \ - 'FIyPoiu2sJyCyoF7W3hxz850CFIA\nADs=') - else: - self.plus_icon=plus_icon - if minus_icon == None: - self.minus_icon=PhotoImage( - data='R0lGODdhCQAJAPEAAAAAAH9/f////wAAACwAAAAACQAJAAAC' \ - 'EYyPoivG614LAlg7ZZbxoR8UADs=') - else: - self.minus_icon=minus_icon - # horizontal distance that subtrees are indented - self.dist_x=dist_x - # vertical distance between rows - self.dist_y=dist_y - # how far to offset text label - self.text_offset=text_offset - # flag controlling connecting line display - self.line_flag=line_flag - # called just before subtree expand/collapse - self.get_contents_callback=get_contents_callback - # called after drag'n'drop - self.drop_callback=drop_callback - # create root node to get the ball rolling - self.root=node_class(parent_node=None, label=root_label, - id=root_id, expandable_flag=1, - collapsed_icon=self.collapsed_icon, - expanded_icon=self.expanded_icon, - x=dist_x, y=dist_y, parent_widget=self) - # configure for scrollbar(s) - x1, y1, x2, y2=self.bbox('all') - self.configure(scrollregion=(x1, y1, x2+5, y2+5)) - # add a cursor - self.cursor_box=self.create_rectangle(0, 0, 0, 0) - self.move_cursor(self.root) - # make it easy to point to control - self.bind('', self.PVT_mousefocus) - # totally arbitrary yet hopefully intuitive default keybindings - # stole 'em from ones used by microsoft tree control - # page-up/page-down - self.bind('', self.pagedown) - self.bind('', self.pageup) - # arrow-up/arrow-down - self.bind('', self.next) - self.bind('', self.prev) - # arrow-left/arrow-right - self.bind('', self.ascend) - # (hold this down and you expand the entire tree) - self.bind('', self.descend) - # home/end - self.bind('', self.first) - self.bind('', self.last) - # space bar - self.bind('', self.toggle) - - # ----- PRIVATE METHODS (prefixed with "PVT_") ----- - # these methods are subject to change, so please try not to use them - def PVT_mousefocus(self, event): - """Soak up event argument when moused-over""" - self.focus_set() - - # ----- PUBLIC METHODS ----- - def tag_bind(self, tag, seq, *args, **kw_args): - """Keep track of callback bindings so we can delete them later. I - shouldn't have to do this!!!!""" - # pass args to superclass - func_id=apply(Canvas.tag_bind, (self, tag, seq)+args, kw_args) - # save references - self.bindings[tag]=self.bindings.get(tag, [])+[(seq, func_id)] - - def add_list(self, list=None, name=None, id=None, flag=0, - expanded_icon=None, collapsed_icon=None): - """Add node construction info to list""" - n=Struct() - n.name=name - n.id=id - n.flag=flag - if collapsed_icon: - n.collapsed_icon=collapsed_icon - else: - if flag: - # it's expandable, use closed folder icon - n.collapsed_icon=self.collapsed_icon - else: - # it's not expandable, use regular file icon - n.collapsed_icon=self.regular_icon - if flag: - if expanded_icon: - n.expanded_icon=expanded_icon - else: - n.expanded_icon=self.expanded_icon - else: - # not expandable, don't need an icon - n.expanded_icon=None - if list == None: - list=[] - list.append(n) - return list - - def add_node(self, name=None, id=None, flag=0, expanded_icon=None, - collapsed_icon=None): - """Add a node during get_contents_callback()""" - self.add_list(self.new_nodes, name, id, flag, expanded_icon, - collapsed_icon) - - def find_full_id(self, search): - """Search for a node""" - return self.root.PVT_find(search) - - def cursor_node(self, search): - """Return node under cursor""" - return self.pos - - def see(self, *items): - """Scroll (in a series of nudges) so items are visible""" - x1, y1, x2, y2=apply(self.bbox, items) - while x2 > self.canvasx(0)+self.winfo_width(): - old=self.canvasx(0) - self.xview('scroll', 1, 'units') - # avoid endless loop if we can't scroll - if old == self.canvasx(0): - break - while y2 > self.canvasy(0)+self.winfo_height(): - old=self.canvasy(0) - self.yview('scroll', 1, 'units') - if old == self.canvasy(0): - break - # done in this order to ensure upper-left of object is visible - while x1 < self.canvasx(0): - old=self.canvasx(0) - self.xview('scroll', -1, 'units') - if old == self.canvasx(0): - break - while y1 < self.canvasy(0): - old=self.canvasy(0) - self.yview('scroll', -1, 'units') - if old == self.canvasy(0): - break - - def move_cursor(self, node): - """Move cursor to node""" - self.pos=node - x1, y1, x2, y2=self.bbox(node.symbol, node.label) - self.coords(self.cursor_box, x1-1, y1-1, x2+1, y2+1) - self.see(node.symbol, node.label) - - def toggle(self, event=None): - """Expand/collapse subtree""" - self.pos.toggle_state() - - def next(self, event=None): - """Move to next lower visible node""" - self.move_cursor(self.pos.next_visible()) - - def prev(self, event=None): - """Move to next higher visible node""" - self.move_cursor(self.pos.prev_visible()) - - def ascend(self, event=None): - """Move to immediate parent""" - if self.pos.parent_node: - # move to parent - self.move_cursor(self.pos.parent_node) - - def descend(self, event=None): - """Move right, expanding as we go""" - if self.pos.expandable_flag: - self.pos.expand() - if self.pos.child_nodes: - # move to first subnode - self.move_cursor(self.pos.child_nodes[0]) - return - # if no subnodes, move to next sibling - self.next() - - def first(self, event=None): - """Go to root node""" - # move to root node - self.move_cursor(self.root) - - def last(self, event=None): - """Go to last visible node""" - # move to bottom-most node - self.move_cursor(self.root.PVT_last()) - - def pageup(self, event=None): - """Previous page""" - n=self.pos - j=self.winfo_height()/self.dist_y - for i in range(j-3): - n=n.prev_visible() - self.yview('scroll', -1, 'pages') - self.move_cursor(n) - - def pagedown(self, event=None): - """Next page""" - n=self.pos - j=self.winfo_height()/self.dist_y - for i in range(j-3): - n=n.next_visible() - self.yview('scroll', 1, 'pages') - self.move_cursor(n) - - # ----- functions for drag'n'drop support ----- - def where(self, event): - """Determine drag location in canvas coordinates. event.x & event.y - don't seem to be what we want.""" - # where the corner of the canvas is relative to the screen: - x_org=self.winfo_rootx() - y_org=self.winfo_rooty() - # where the pointer is relative to the canvas widget, - # including scrolling - x=self.canvasx(event.x_root-x_org) - y=self.canvasy(event.y_root-y_org) - return x, y - - def dnd_accept(self, source, event): - """Accept dnd messages, i.e. we're a legit drop target, and we do - implement d&d functions.""" - self.target=None - return self - - def dnd_enter(self, source, event): - """Get ready to drag or drag has entered widget (create drag - object)""" - # this flag lets us know there's been drag motion - self.drag=1 - x, y=self.where(event) - x1, y1, x2, y2=source.widget.bbox(source.symbol, source.label) - dx, dy=x2-x1, y2-y1 - # create dragging icon - if source.expanded_flag: - self.dnd_symbol=self.create_image(x, y, - image=source.expanded_icon) - else: - self.dnd_symbol=self.create_image(x, y, - image=source.collapsed_icon) - self.dnd_label=self.create_text(x+self.text_offset, y, - text=source.get_label(), - justify='left', - anchor='w') - - def dnd_motion(self, source, event): - """Move drag icon""" - self.drag=1 - x, y=self.where(event) - x1, y1, x2, y2=self.bbox(self.dnd_symbol, self.dnd_label) - self.move(self.dnd_symbol, x-x1+source.x_off, y-y1+source.y_off) - self.move(self.dnd_label, x-x1+source.x_off, y-y1+source.y_off) - - def dnd_leave(self, source, event): - """Finish dragging or drag has left widget (destroy drag object)""" - self.delete(self.dnd_symbol) - self.delete(self.dnd_label) - - def dnd_commit(self, source, event): - """Object has been dropped here""" - # call our own dnd_leave() to clean up - self.dnd_leave(source, event) - # process pending events to detect target node - # update_idletasks() doesn't do the trick if source & target are - # on different widgets - self.update() - if not self.target: - # no target node - return - # we must update data structures based on the drop - if self.drop_callback: - try: - # called with dragged node and target node - # this is where a file manager would move the actual file - # it must also move the nodes around as it wishes - self.drop_callback(source, self.target) - except: - report_callback_exception() - -#------------------------------------------------------------------------------ -# the good 'ol test/demo code -if __name__ == '__main__': - import os - import sys - - # default routine to get contents of subtree - # supply this for a different type of app - # argument is the node object being expanded - # should call add_node() - def get_contents(node): - path=apply(os.path.join, node.full_id()) - for filename in os.listdir(path): - full=os.path.join(path, filename) - name=filename - folder=0 - if os.path.isdir(full): - # it's a directory - folder=1 - elif not os.path.isfile(full): - # but it's not a file - name=name+' (special)' - if os.path.islink(full): - # it's a link - name=name+' (link to '+os.readlink(full)+')' - node.widget.add_node(name=name, id=filename, flag=folder) - - root=Tk() - root.title(os.path.basename(sys.argv[0])) - tree=os.sep - if sys.platform == 'win32': - # we could call the root "My Computer" and mess with get_contents() - # to return "A:", "B:", "C:", ... etc. as it's children, but that - # would just be terminally cute and I'd have to shoot myself - tree='C:'+os.sep - - # create the control - t=Tree(master=root, - root_id=tree, - root_label=tree, - get_contents_callback=get_contents, - width=300) - t.grid(row=0, column=0, sticky='nsew') - - # make expandable - root.grid_rowconfigure(0, weight=1) - root.grid_columnconfigure(0, weight=1) - - # add scrollbars - sb=Scrollbar(root) - sb.grid(row=0, column=1, sticky='ns') - t.configure(yscrollcommand=sb.set) - sb.configure(command=t.yview) - - sb=Scrollbar(root, orient=HORIZONTAL) - sb.grid(row=1, column=0, sticky='ew') - t.configure(xscrollcommand=sb.set) - sb.configure(command=t.xview) - - # must get focus so keys work for demo - t.focus_set() - - # we could do without this, but it's nice and friendly to have - Button(root, text='Quit', command=root.quit).grid(row=2, column=0, - columnspan=2) - - # expand out the root - t.root.expand() - - root.mainloop() From jan at codespeak.net Fri Apr 22 18:17:06 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 22 Apr 2005 18:17:06 +0200 (CEST) Subject: [py-svn] r11032 - in py/dist/py: . test/tkinter Message-ID: <20050422161706.28BA727B61@code1.codespeak.net> Author: jan Date: Fri Apr 22 18:17:05 2005 New Revision: 11032 Added: py/dist/py/test/tkinter/tkgui.py - copied, changed from r11030, py/dist/py/test/tkinter/tixgui.py py/dist/py/test/tkinter/tksession.py - copied, changed from r11030, py/dist/py/test/tkinter/tixsession.py Modified: py/dist/py/__init__.py Log: rename files and classes to reflect the change from tix to tkinter Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Fri Apr 22 18:17:05 2005 @@ -16,7 +16,7 @@ # for customization of collecting/running tests 'test.Session' : ('./test/session.py', 'Session'), 'test.TerminalSession' : ('./test/terminal/terminal.py', 'TerminalSession'), - 'test.TkinterSession' : ('./test/tkinter/tixsession.py', 'TixSession'), + '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'), Copied: py/dist/py/test/tkinter/tkgui.py (from r11030, py/dist/py/test/tkinter/tixgui.py) ============================================================================== --- py/dist/py/test/tkinter/tixgui.py (original) +++ py/dist/py/test/tkinter/tkgui.py Fri Apr 22 18:17:05 2005 @@ -134,7 +134,7 @@ fill = Tkinter.X) -class TixGui: +class TkGui: font = ('Helvetica', 9, 'normal') @@ -294,13 +294,3 @@ self.backend.shutdown() py.std.sys.exit() -class TixSession: - def __init__(self, config): - self.config = config - - def main(self, paths): - root = Tkinter.Tk() - tixgui = TixGui(root, self.config) - tixgui.set_paths(paths) - root.protocol('WM_DELETE_WINDOW', tixgui.shutdown) - root.mainloop() Copied: py/dist/py/test/tkinter/tksession.py (from r11030, py/dist/py/test/tkinter/tixsession.py) ============================================================================== --- py/dist/py/test/tkinter/tixsession.py (original) +++ py/dist/py/test/tkinter/tksession.py Fri Apr 22 18:17:05 2005 @@ -1,14 +1,14 @@ import py -class TixSession: +class TkSession: def __init__(self, config): self.config = config def main(self, paths): import Tkinter root = Tkinter.Tk() - from tixgui import TixGui - tixgui = TixGui(root, self.config) - tixgui.set_paths(paths) - root.protocol('WM_DELETE_WINDOW', tixgui.shutdown) + from tkgui import TkGui + tkgui = TkGui(root, self.config) + tkgui.set_paths(paths) + root.protocol('WM_DELETE_WINDOW', tkgui.shutdown) root.mainloop() From jan at codespeak.net Fri Apr 22 18:19:36 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 22 Apr 2005 18:19:36 +0200 (CEST) Subject: [py-svn] r11033 - py/dist/py/test/tkinter Message-ID: <20050422161936.AF65C27B61@code1.codespeak.net> Author: jan Date: Fri Apr 22 18:19:36 2005 New Revision: 11033 Removed: py/dist/py/test/tkinter/tixgui.py py/dist/py/test/tkinter/tixsession.py Log: remove tix Deleted: /py/dist/py/test/tkinter/tixgui.py ============================================================================== --- /py/dist/py/test/tkinter/tixgui.py Fri Apr 22 18:19:36 2005 +++ (empty file) @@ -1,306 +0,0 @@ -import py -from py.__impl__.test.tkinter import backend -from py.__impl__.test.tkinter import util -Null = util.Null - -import ScrolledText -from Tkinter import PhotoImage -import Tkinter -import re -import os - -myfont = ('Helvetica', 9, 'normal') - -class StatusBar(Tkinter.Frame): - - font = ('Helvetica', 10, 'normal') - - def __init__(self, master=None, **kw): - if master is None: - master = Tk() - Tkinter.Frame.__init__(self, master, **kw) - self.labels = {} - - def set_label(self, name, text='', side=Tkinter.LEFT): - if not self.labels.has_key(name): - label = Tkinter.Label(self, bd=1, relief=Tkinter.SUNKEN, - anchor=Tkinter.W, - font=self.font) - label.pack(side=side) - self.labels[name] = label - else: - label = self.labels[name] - label.config(text='%s:\t%s' % (name,text)) - - def _get_label(self, name): - if not self.labels.has_key(name): - self.set_label(name, 'NoText') - return self.labels[name] - - def update(self, testrepository): - failed = testrepository.keys(testrepository.failed_id) - self.set_label('Failed', str(len(failed))) - self._get_label('Failed').configure(bg = 'Red', fg = 'White') - skipped = testrepository.keys(testrepository.skipped_id) - self.set_label('Skipped', str(len(skipped))) - self._get_label('Skipped').configure(bg = 'Yellow') - self.set_label('Collectors', - str(len(testrepository.keys())-2)) - root_report = testrepository.find() - if root_report.status != testrepository.ReportClass.Status.NotExecuted(): - self.set_label('Time', '%0.2f seconds' % testrepository.find().time) - else: - self.set_label('Time', '%0.2f seconds' % 0.0) - -class ReportListBox(Tkinter.LabelFrame): - - font = myfont - - def __init__(self, *args, **kwargs): - Tkinter.LabelFrame.__init__(self, *args, **kwargs) - self.callback = Null() - self.data = {} - self.label = Tkinter.Label(self) - self.label.configure(font = self.font, width = 80, anchor = Tkinter.W) - self.configure(labelwidget=self.label) - self.createwidgets() - self.label.configure(text = 'Idle') - self.configure(font = myfont) - - - def createwidgets(self): - self.listbox = Tkinter.Listbox(self, foreground='red', - selectmode=Tkinter.SINGLE, font = self.font) - - self.scrollbar = Tkinter.Scrollbar(self, command=self.listbox.yview) - self.scrollbar.pack(side = Tkinter.RIGHT, fill = Tkinter.Y, - anchor = Tkinter.N) - self.listbox.pack(side = Tkinter.LEFT, - fill = Tkinter.BOTH, expand = Tkinter.YES, - anchor = Tkinter.NW) - self.listbox.configure(yscrollcommand = self.scrollbar.set, - bg = 'White',selectbackground= 'Red', - takefocus= Tkinter.YES) - - def set_callback(self, callback): - self.callback = callback - self.listbox.bind('', self.do_callback) - - def do_callback(self, *args): - report_ids = [self.data[self.listbox.get(int(item))] - for item in self.listbox.curselection()] - for report_id in report_ids: - self.callback(report_id) - - def update_label(self, report_path): - label = report_path - if not label: - label = 'Idle' - self.label.configure(text = label) - - def update_list(self, repository): - failed_childs = repository.find_children(repository.failed_id) - skipped_childs = repository.find_children(repository.skipped_id) - if len(failed_childs + skipped_childs) == len(self.data.keys()): - return - old_selection = [int(sel) for sel in self.listbox.curselection()] - self.listbox.delete(0, Tkinter.END) - self.data = {} - for report_id in failed_childs + skipped_childs: - report = repository.find(report_id) - label = '%s: %s' % (report.status, report.label) - self.data[label] = report.full_id - self.listbox.insert(Tkinter.END, label) - for index in old_selection: - try: - self.listbox.select_set(index) - except: - pass - - - -class LabelEntry(Tkinter.Frame): - - font = myfont - - def __init__(self, *args, **kwargs): - Tkinter.Frame.__init__(self, *args, **kwargs) - self.label = Tkinter.Label(self) - self.label.configure(font = self.font) - self.label.pack(side = Tkinter.LEFT) - self.entry = Tkinter.Entry(self) - self.entry.configure(font = self.font) - self.entry.pack(side = Tkinter.LEFT, expand = Tkinter.YES, - fill = Tkinter.X) - - -class TixGui: - - font = ('Helvetica', 9, 'normal') - - def __init__(self, parent, config): - self._parent = parent - self._config = config - self._should_stop = False - #XXX needed? - self._paths = [] - self.backend = backend.RepositoryBackend(config) - self.createwidgets() - self.timer_update() - - def createwidgets(self): - self._buttonframe = Tkinter.Frame(self._parent) - self._entry = LabelEntry(self._buttonframe) - self._entry.label.configure(text = 'Enter test name:') - self._entry.entry.bind('', self.start_tests) - self._entry.pack(side = Tkinter.LEFT, fill = Tkinter.X, - expand = Tkinter.YES) - self._stop = Tkinter.Button(self._buttonframe, text = 'Stop', - command = self.stop, font = self.font) - self._stop.pack(side = Tkinter.RIGHT) - self._run = Tkinter.Button(self._buttonframe, text = 'Run', - command = self.start_tests, font = self.font) - self._run.pack(side = Tkinter.RIGHT) - self._buttonframe.pack(side = Tkinter.TOP, fill = Tkinter.X) - self._reportlist = ReportListBox(self._parent) - self._reportlist.pack(side = Tkinter.TOP, fill = Tkinter.BOTH, - expand = Tkinter.YES) - self._reportlist.set_callback(self.show_error) - self._statusbar = StatusBar(self._parent) - self._statusbar.pack(side= Tkinter.BOTTOM, fill=Tkinter.X) - self.update_status(self.backend.get_repository()) - - - def show_error(self, report_id): - report = self.backend.get_repository().find(report_id) - window = Tkinter.Toplevel(self._parent) - window.title(report.label) - window.protocol('WM_DELETE_WINDOW', window.quit) - Tkinter.Label(window, text='%s: %s' % (report.status, report.label), - foreground="red", justify=Tkinter.LEFT).pack(anchor=Tkinter.W) - text = ScrolledText.ScrolledText(window) - text.configure(bg = 'White', font = ('Helvetica', 11, 'normal')) - text.tag_config('sel', relief=Tkinter.FLAT) - text.insert(Tkinter.END, report.error_report) - if len(report.error_report.splitlines()) < 20: - text.config(height=len(report.error_report.splitlines()) + 5) - text.yview_pickplace(Tkinter.END) - text['state'] = Tkinter.DISABLED - text['cursor'] = window['cursor'] - self.attacheditorhotspots(text) - text.pack(expand=Tkinter.YES, fill=Tkinter.BOTH) - - b = Tkinter.Button(window, text="Close", - command=window.quit, font = self.font) - b.pack(side=Tkinter.BOTTOM) - b.focus_set() - window.bind('', lambda e, w=window: w.quit()) - window.mainloop() - window.destroy() - - def attacheditorhotspots(self, text): - # Attach clickable regions to a Text widget. - filelink = re.compile(r"""\[(?:testcode\s*:)?\s*(.+):(\d+)\]""") - skippedlink = re.compile(r"""in\s+(/.*):(\d+)\s+""") - lines = text.get('1.0', Tkinter.END).splitlines(1) - if not lines: - return - tagname = '' - start, end = 0,0 - for index, line in enumerate(lines): - match = filelink.search(line) - if match is None: - match = skippedlink.search(line) - if match is None: - continue - file, line = match.group(1, 2) - start, end = match.span() - tagname = "ref%d" % index - text.tag_add(tagname, - "%d.%d" % (index + 1, start), - "%d.%d" % (index + 1, end)) - text.tag_bind(tagname, "", - lambda e, n=tagname: - e.widget.tag_config(n, underline=1)) - text.tag_bind(tagname, "", - lambda e, n=tagname: - e.widget.tag_config(n, underline=0)) - text.tag_bind(tagname, "", - lambda e, self=self, f=file, l=line: - self.launch_editor(f, l)) - - def launch_editor(self, file, line): - editor = (py.std.os.environ.get('PYUNIT_EDITOR', None) or - py.std.os.environ.get('EDITOR_REMOTE', None) or - os.environ.get('EDITOR', None) or "emacsclient --no-wait ") - #"emacsclient --no-wait ") - if editor: - print "%s +%s %s" % (editor, line, file) - #py.process.cmdexec('%s +%s %s' % (editor, line, file)) - os.system('%s +%s %s' % (editor, line, file)) - - def timer_update(self): - self.backend.update() - self._parent.after(100, self.timer_update) - - def start_tests(self, dont_care_event=None): - if self.backend.running: - return - paths = [path.strip() for path in self._entry.entry.get().split(',')] - self.backend.set_messages_callback(self.messages_callback) - self.backend.set_message_callback(self.message_callback) - self.backend.start_tests(args = paths) - - def update_status(self, repository): - self._statusbar.update(repository) - bgcolor = 'White' - fgcolor = 'Black' - root_status = repository.root_status() - if root_status == repository.ReportClass.Status.Passed(): - bgcolor = 'Green' - elif root_status == repository.ReportClass.Status.Failed(): - bgcolor = 'Red' - fgcolor = 'White' - elif root_status == repository.ReportClass.Status.Skipped() : - bgcolor = 'Yellow' - self._entry.entry.configure(bg = bgcolor, fg = fgcolor) - - def messages_callback(self, report_ids): - if not report_ids: - return - repository = self.backend.get_repository() - self.update_status(repository) - self._reportlist.update_list(repository) - - def message_callback(self, report_id): - if report_id is None: - report_label = None - else: - report_label = self.backend.get_repository().find(report_id).path - self._reportlist.update_label(report_label) - - def set_paths(self, paths): - self._paths = paths - self._entry.entry.insert(Tkinter.END, ', '.join(paths)) - - def stop(self): - self.backend.shutdown() - self.backend.update() - self.messages_callback([None]) - self.message_callback(None) - - def shutdown(self): - self.should_stop = True - self.backend.shutdown() - py.std.sys.exit() - -class TixSession: - def __init__(self, config): - self.config = config - - def main(self, paths): - root = Tkinter.Tk() - tixgui = TixGui(root, self.config) - tixgui.set_paths(paths) - root.protocol('WM_DELETE_WINDOW', tixgui.shutdown) - root.mainloop() Deleted: /py/dist/py/test/tkinter/tixsession.py ============================================================================== --- /py/dist/py/test/tkinter/tixsession.py Fri Apr 22 18:19:36 2005 +++ (empty file) @@ -1,14 +0,0 @@ -import py - -class TixSession: - def __init__(self, config): - self.config = config - - def main(self, paths): - import Tkinter - root = Tkinter.Tk() - from tixgui import TixGui - tixgui = TixGui(root, self.config) - tixgui.set_paths(paths) - root.protocol('WM_DELETE_WINDOW', tixgui.shutdown) - root.mainloop() From jan at codespeak.net Fri Apr 22 18:33:12 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 22 Apr 2005 18:33:12 +0200 (CEST) Subject: [py-svn] r11034 - py/dist/py/test/tkinter/testing Message-ID: <20050422163312.E43BE27B61@code1.codespeak.net> Author: jan Date: Fri Apr 22 18:33:12 2005 New Revision: 11034 Added: py/dist/py/test/tkinter/testing/test_guisession.py - copied unchanged from r11033, py/dist/py/test/tkinter/testing/test_guidriver.py Removed: py/dist/py/test/tkinter/testing/test_guidriver.py py/dist/py/test/tkinter/testing/test_tixgui.py Log: remove old test rename test_guidriver to test_guisession Deleted: /py/dist/py/test/tkinter/testing/test_guidriver.py ============================================================================== --- /py/dist/py/test/tkinter/testing/test_guidriver.py Fri Apr 22 18:33:12 2005 +++ (empty file) @@ -1,62 +0,0 @@ - -import py -from py.__impl__.test.tkinter import guisession -from py.__impl__.test.tkinter.util import Status, TestReport, Null -GuiSession = guisession.GuiSession - -class TestGuiSession: - - class ChannelMock: - - def __init__(self, receinvelist = []): - self.reset(receinvelist) - - def reset(self, receivelist = []): - self.receivelist = receivelist - self.sendlist = [] - - def send(self, obj): - self.sendlist.append(obj) - - def receive(self): - return self.receivelist.pop(0) - - def setup_method(self, method): - self.channel = self.ChannelMock() - self.session = GuiSession(Null(), self.channel) - self.collitems = [Null(), Null()] - - def test_header_sends_report_with_id_root(self): - self.session.header(self.collitems) - - assert self.channel.sendlist != [] - report = TestReport.fromChannel(self.channel.sendlist[0]) - assert report.status == Status.NotExecuted() - assert report.id == 'Root' - assert report.label == 'Root' - - def test_footer_sends_report_and_None(self): - self.session.header(self.collitems) - self.session.footer(self.collitems) - - assert self.channel.sendlist != [] - assert self.channel.sendlist[-1] is None - report = TestReport.fromChannel(self.channel.sendlist[-2]) - assert report.status == Status.NotExecuted() - assert report.id == 'Root' - -## def test_status_is_passed_to_root(self): -## self.session.header(self.collitems) -## self.session.start(self.collitems[0]) -## self.session.finish(self.collitems[0], py.test.collect.Collector.Failed()) -## self.session.footer(self.collitems) - -## assert self.channel.sendlist[-1] is None -## assert self.channel.sendlist.pop() is None - -## report = TestReport.fromChannel(self.channel.sendlist[-1]) -## assert report.name == 'Root' -## assert report.status == Status.Failed() - - - Deleted: /py/dist/py/test/tkinter/testing/test_tixgui.py ============================================================================== --- /py/dist/py/test/tkinter/testing/test_tixgui.py Fri Apr 22 18:33:12 2005 +++ (empty file) @@ -1,15 +0,0 @@ - -import py - -def setup_module(mod): - try: - import Tix - except ImportError: - py.test.skip("cannot import Tix") - else: - mod.Tix = Tix - -def test_import_tix(): - root = Tix.Tk() - root.tk.eval('package require Tix') - From hpk at codespeak.net Fri Apr 22 21:42:46 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 22 Apr 2005 21:42:46 +0200 (CEST) Subject: [py-svn] r11037 - py/dist/py/bin Message-ID: <20050422194246.30EC027B61@code1.codespeak.net> Author: hpk Date: Fri Apr 22 21:42:45 2005 New Revision: 11037 Removed: py/dist/py/bin/check_python_path Log: move old checker script out ... Deleted: /py/dist/py/bin/check_python_path ============================================================================== --- /py/dist/py/bin/check_python_path Fri Apr 22 21:42:45 2005 +++ (empty file) @@ -1,30 +0,0 @@ -#!/usr/bin/python -from __future__ import generators - -import sys, os -from py.path import local - -def search_path(item): - dirs = [os.curdir] - PYTHONPATH = os.getenv('PYTHONPATH') - dirs.extend(PYTHONPATH.split(':')) - dirs.extend(sys.path) - results = {} - for fn in dirs: - p = local(fn) - # XXX zip files - pyfile = p.join(item).new(ext='.py') - if pyfile.check(file=1) and pyfile not in results: - results[pyfile] = 1 - yield pyfile - - pydir = p.join(item) - if pydir.check(dir=1) and pydir.join('__init__.py').check() and pydir not in results: - results[pydir] = 1 - yield pydir - -if __name__ == '__main__': - item = sys.argv[1] - for x in search_path(item): - print x - From hpk at codespeak.net Fri Apr 22 23:37:53 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 22 Apr 2005 23:37:53 +0200 (CEST) Subject: [py-svn] r11041 - py/dist/py/test/testing/data Message-ID: <20050422213753.65F6C27B52@code1.codespeak.net> Author: hpk Date: Fri Apr 22 23:37:53 2005 New Revision: 11041 Removed: py/dist/py/test/testing/data/__init__.py Log: no __init__.py for datadir with syntaxerror test modules etc.pp. Deleted: /py/dist/py/test/testing/data/__init__.py ============================================================================== --- /py/dist/py/test/testing/data/__init__.py Fri Apr 22 23:37:53 2005 +++ (empty file) @@ -1 +0,0 @@ -# From hpk at codespeak.net Fri Apr 22 23:50:23 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 22 Apr 2005 23:50:23 +0200 (CEST) Subject: [py-svn] r11042 - in py/dist/py/test/tkinter/testing: . data Message-ID: <20050422215023.A2F0227B61@code1.codespeak.net> Author: hpk Date: Fri Apr 22 23:50:23 2005 New Revision: 11042 Modified: py/dist/py/test/tkinter/testing/data/ (props changed) py/dist/py/test/tkinter/testing/data/__init__.py (props changed) py/dist/py/test/tkinter/testing/data/filetest.py (props changed) py/dist/py/test/tkinter/testing/test_backend.py (props changed) Log: fixeol / property changes on svn:eol-style and svn:ignore From hpk at codespeak.net Sat Apr 23 00:05:49 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 23 Apr 2005 00:05:49 +0200 (CEST) Subject: [py-svn] r11044 - in py/dist: . py py/misc Message-ID: <20050422220549.88AC227B61@code1.codespeak.net> Author: hpk Date: Sat Apr 23 00:05:49 2005 New Revision: 11044 Added: py/dist/py/misc/_dist.py py/dist/setup.py Modified: py/dist/ (props changed) py/dist/py/__init__.py py/dist/py/initpkg.py Log: preliminary distutils setup.py support slightly hackish but that may just be neccessary fluff, who knows. currently the setup.py installs all .svn files which i am not 100% sure yet that it is a good idea. However, i like that i can just go to the installed package and issue "svn st" and "svn info" and even "svn up". Also when someone patches things on his system he can simply sent a "svn diff" for making a patch. and so on. And what does disk space cost these days? Oh, and c-extension modules are not handled at all yet. and the distutils hacks could use a few tests, actually. well, but it's a start, i guess. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Sat Apr 23 00:05:49 2005 @@ -1,5 +1,25 @@ +""" +the py lib is a development support library featuring +py.test, an interactive testing tool which supports +unittesting and ReST-integrity testing with practically +no boilerplate. +""" from initpkg import initpkg -initpkg(__name__, exportdefs = { + +initpkg(__name__, + name = "py", + description = "py.test and the py lib", + version = "0.8.0-prealpha", + url = "http://codespeak.net/py", + download_url = "http://codespeak.net/download/py-0.8.0-pre-alpha.tgz", + 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'), Modified: py/dist/py/initpkg.py ============================================================================== --- py/dist/py/initpkg.py (original) +++ py/dist/py/initpkg.py Sat Apr 23 00:05:49 2005 @@ -204,10 +204,12 @@ # Bootstrap Virtual Module Hierarchy # --------------------------------------------------- -def initpkg(pkgname, exportdefs): +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 = [] Added: py/dist/py/misc/_dist.py ============================================================================== --- (empty file) +++ py/dist/py/misc/_dist.py Sat Apr 23 00:05:49 2005 @@ -0,0 +1,112 @@ +import py +import sys +from distutils import sysconfig +from distutils import core + + +class Params: + """ a crazy hack to convince distutils to please + install all of our files inside the package. + """ + _sitepackages = py.path.local(sysconfig.get_python_lib()) + def __init__(self, pkgmod): + name = pkgmod.__name__ + self._pkgdir = py.path.local(pkgmod.__file__).dirpath() + self._rootdir = self._pkgdir.dirpath() + self._pkgtarget = self._sitepackages.join(name) + self._datadict = {} + self.packages = [] + self.scripts = [] + self.hacktree() + self.data_files = self._datadict.items() + self.data_files.sort() + self.packages.sort() + self.scripts.sort() + + def hacktree(self): + for p in self._pkgdir.visit(): + if p.check(file=1): + if p.ext in ('.pyc', '.pyo'): + continue + if p.ext == '.py': + self.addpythonfile(p) + elif p.dirpath().basename == 'bin': + self.scripts.append(p.relto(self._rootdir)) + self.adddatafile(p) + else: + self.adddatafile(p) + else: + if not p.listdir(): + self.adddatafile(p.ensure('dummy')) + + def adddatafile(self, p): + if p.ext in ('.pyc', 'pyo'): + return + target = self._pkgtarget.join(p.dirpath().relto(self._pkgdir)) + l = self._datadict.setdefault(str(target), []) + l.append(p.relto(self._rootdir)) + + def addpythonfile(self, p): + parts = p.parts() + for above in p.parts(reverse=True)[1:]: + if self._pkgdir.relto(above): + dottedname = p.dirpath().relto(self._rootdir).replace(p.sep, '.') + if dottedname not in self.packages: + self.packages.append(dottedname) + break + if not above.join('__init__.py').check(): + self.adddatafile(p) + #print "warning, added data file", p + break + +#if sys.platform != 'win32': +# scripts.remove('py/bin/pytest.cmd') +#else: +# scripts.remove('py/bin/py.test') +# + +# helpers: +def checknonsvndir(p): + if p.basename != '.svn' and p.check(dir=1): + return True + +def dump(params): + print "packages" + for x in params.packages: + print "package ", x + print + print "scripts" + for x in params.scripts: + print "script ", x + print + + print "data files" + for x in params.data_files: + print "data file ", x + print + +def setup(pkg): + """ invoke distutils on a given package. + """ + params = Params(pkg) + #dump(params) + source = getattr(pkg, '__package__', pkg) + + kw = {} + namelist = list(core.setup_keywords) + namelist.extend(['packages', 'scripts', 'data_files']) + for name in namelist: + try: + kw[name] = getattr(source, name, + getattr(params, name)) + except AttributeError: + pass + #print "couldn't find", name + + #script_args = sys.argv[1:] + #if 'install' in script_args: + # script_args = ['--quiet'] + script_args + # #print "installing", py + + #py.std.pprint.pprint(kw) + core.setup(**kw) Added: py/dist/setup.py ============================================================================== --- (empty file) +++ py/dist/setup.py Sat Apr 23 00:05:49 2005 @@ -0,0 +1,2 @@ +import py +py._dist.setup(py) From hpk at codespeak.net Sat Apr 23 01:22:01 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 23 Apr 2005 01:22:01 +0200 (CEST) Subject: [py-svn] r11046 - py/dist/py Message-ID: <20050422232201.0136B27B61@code1.codespeak.net> Author: hpk Date: Sat Apr 23 01:22:01 2005 New Revision: 11046 Modified: py/dist/py/__init__.py Log: better docstring Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Sat Apr 23 01:22:01 2005 @@ -1,8 +1,7 @@ -""" +"""\ the py lib is a development support library featuring py.test, an interactive testing tool which supports -unittesting and ReST-integrity testing with practically -no boilerplate. +unittesting with practically no boilerplate. """ from initpkg import initpkg From hpk at codespeak.net Sat Apr 23 12:33:36 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 23 Apr 2005 12:33:36 +0200 (CEST) Subject: [py-svn] r11049 - in py/dist/py: path/local test test/terminal test/testing Message-ID: <20050423103336.9723527B60@code1.codespeak.net> Author: hpk Date: Sat Apr 23 12:33:36 2005 New Revision: 11049 Modified: py/dist/py/path/local/local.py py/dist/py/test/collect.py py/dist/py/test/session.py py/dist/py/test/terminal/terminal.py py/dist/py/test/testing/test_collect.py py/dist/py/test/testing/test_session.py Log: - fixed nested import problem that Ian Bicking reported on py-dev - improved tracebacks for this case - it's slightly hackish. in fact, a bit more refactoring is needed to make things nice again. Probably we may want to use Armin's "lazy list" approach which allows to hide&memoize an iterator with a plain list interface. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sat Apr 23 12:33:36 2005 @@ -377,13 +377,7 @@ if ensuresyspath: self._prependsyspath(self.dirpath()) modname = self.purebasename - try: - return __import__(modname, None, None, ['__doc__']) - except ImportError: - if modname in sys.modules: - del sys.modules[modname] - raise - raise ImportError("cannot import fspath %s" %(self,)) + return __import__(modname, None, None, ['__doc__']) else: try: return sys.modules[modname] Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Sat Apr 23 12:33:36 2005 @@ -74,6 +74,7 @@ Function = configproperty('Function') Generator = configproperty('Generator') + _stickyfailure = None class Outcome: def __init__(self, **kwargs): assert 'msg' not in kwargs or isinstance(kwargs['msg'], str), ( @@ -186,7 +187,9 @@ except KeyboardInterrupt: raise except: - yield py.test.Item.Failed(excinfo=py.code.ExceptionInfo()) + x = py.test.Item.Failed(excinfo=py.code.ExceptionInfo()) + self._stickyfailure = x + yield x def fspath(): def fget(self): Modified: py/dist/py/test/session.py ============================================================================== --- py/dist/py/test/session.py (original) +++ py/dist/py/test/session.py Sat Apr 23 12:33:36 2005 @@ -66,6 +66,8 @@ self.start(colitem) needfinish = True try: + if colitem._stickyfailure: + raise colitem._stickyfailure res = self.run(colitem) except (KeyboardInterrupt, Exit): raise Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Sat Apr 23 12:33:36 2005 @@ -308,15 +308,26 @@ 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(traceback): - if entry.frame.code.path == path and \ - entry.frame.code.firstlineno == lineno: - del traceback[:i] - break + 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 traceback: + for entry in newtraceback: try: x = entry.frame.eval("__tracebackhide__") except: 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 Sat Apr 23 12:33:36 2005 @@ -5,12 +5,13 @@ tmpdir = py.test.ensuretemp('test_collect') def test_failing_import_execfile(): + py.test.skip("memoizing exceptions needs refactoring") fn = datadir / 'failingimport.py' - col = py.test.collect.Module(fn) - def _import(): - py.test.raises(ImportError, col.run) - _import() - _import() + dest = tmpdir.join('failing_import.py') + fn.copy(dest) + col = py.test.collect.Module(dest) + assert isinstance(list(col.tryiter())[0], ImportError) + assert isinstance(list(col.tryiter())[0], ImportError) def XXXtest_finds_root(): fn = datadir / 'filetest.py' 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 Sat Apr 23 12:33:36 2005 @@ -172,6 +172,25 @@ names = item.listnames() assert names == ['ordertest', 'test_orderofexecution.py', 'Testmygroup', '()', 'test_4'] + def test_nested_import_error(self): + o = tmpdir.ensure('Ians_importfailure', dir=1) + tfile = o.join('test_import_fail.py') + tfile.write(py.code.Source(""" + import import_fails + def test_this(): + assert import_fails.a == 1 + """)) + o.join('import_fails.py').write(py.code.Source(""" + import does_not_work + a = 1 + """)) + session = self.session + session.main([o]) + l = session.getresults(py.test.Item.Failed) + assert len(l) == 1 + item, outcome = l[0] + assert str(outcome.excinfo).find('does_not_work') != -1 + from py.__impl__.test.terminal.remote import getrootdir class TestRemote: def test_rootdir_is_package(self): @@ -239,3 +258,5 @@ reply.get(timeout=0.5) except IOError, e: assert str(e).lower().find('timeout') != -1 + + From hpk at codespeak.net Sat Apr 23 18:11:58 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 23 Apr 2005 18:11:58 +0200 (CEST) Subject: [py-svn] r11050 - in py/dist/py/test: . terminal Message-ID: <20050423161158.28B3027B60@code1.codespeak.net> Author: hpk Date: Sat Apr 23 18:11:57 2005 New Revision: 11050 Modified: py/dist/py/test/session.py py/dist/py/test/terminal/terminal.py Log: - KeyboardInterrupt propagate without displaying trail/footer information - the py lib version is added as info by default, i get too confused otherwise :-) Modified: py/dist/py/test/session.py ============================================================================== --- py/dist/py/test/session.py (original) +++ py/dist/py/test/session.py Sat Apr 23 18:11:57 2005 @@ -44,7 +44,11 @@ try: for colitem in colitems: self.runtraced(colitem) - finally: + except KeyboardInterrupt: + raise + except: + self.footer(colitems) + else: self.footer(colitems) except Exit, ex: pass Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Sat Apr 23 18:11:57 2005 @@ -112,12 +112,11 @@ self.out.line("testing-mode: %s" % mode) self.out.line("executable: %s (%s)" % (py.std.sys.executable, repr_pythonversion())) - + rev = py.__package__.getrev() + self.out.line("using py lib: %s " % ( + py.path.local(py.__file__).dirpath(), rev)) if self.config.option.traceconfig or self.config.option.verbose: - rev = py.__package__.getrev() - self.out.line("using py lib: %s " % ( - py.path.local(py.__file__).dirpath(), rev)) for x in colitems: self.out.line("test target: %s" %(x.fspath,)) From hpk at codespeak.net Sat Apr 23 20:21:23 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 23 Apr 2005 20:21:23 +0200 (CEST) Subject: [py-svn] r11051 - py/dist/py/documentation Message-ID: <20050423182123.C933E27B66@code1.codespeak.net> Author: hpk Date: Sat Apr 23 20:21:23 2005 New Revision: 11051 Added: py/dist/py/documentation/TODO.txt Log: add a TODO list, helping to plan for a 0.8.0 release. Added: py/dist/py/documentation/TODO.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/TODO.txt Sat Apr 23 20:21:23 2005 @@ -0,0 +1,33 @@ +Things to do before 0.8.0 +------------------------- + +distutils install +----------------- + +* see if things work on Win32 + +* do something about c-extensions both on unix-ish + and win32 systems + + +py.test +------- + +* adjust py.test documentation to reflect new + collector/session architecture + +* document py.test's conftest.py approach + +* put Armin's collect class into py.__builtin__ + +* try get rid of Collect.tryiter() in favour of + using Armin's collect class + +* hide py.test.TerminalSession and TkinterSession? + +misc +---- + +* get Armin or Christian to fix greenlets on + recent gcc's (compile fails at least for + switch_x86_unix.h) From hpk at codespeak.net Sat Apr 23 21:40:13 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 23 Apr 2005 21:40:13 +0200 (CEST) Subject: [py-svn] r11052 - py/dist/py/path/local py/dist/py/path/local/testing py/dist/py/test pypy/dist/pypy/tool Message-ID: <20050423194013.19B2F27B66@code1.codespeak.net> Author: hpk Date: Sat Apr 23 21:40:12 2005 New Revision: 11052 Modified: py/dist/py/path/local/local.py py/dist/py/path/local/testing/test_local.py py/dist/py/test/config.py pypy/dist/pypy/tool/udir.py Log: change signature of make_numbered_dir Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sat Apr 23 21:40:12 2005 @@ -519,9 +519,9 @@ raise py.error.ENOENT(dpath, "could not create tempdir, %d tries" % tries) mkdtemp = classmethod(mkdtemp) - def make_numbered_dir(cls, rootdir=None, base = 'session-', keep=3): + def make_numbered_dir(cls, prefix='session-', rootdir=None, keep=3): """ return unique directory with a number greater than the current - maximum one. The number is assumed to start directly after base. + maximum one. The number is assumed to start directly after prefix. if keep is true directories with a number less than (maxnum-keep) will be removed. """ @@ -529,15 +529,16 @@ rootdir = cls.get_temproot() def parse_num(path): - """ parse the number out of a path (if it matches the base) """ + """ parse the number out of a path (if it matches the prefix) """ bn = path.basename - if bn.startswith(base): + if bn.startswith(prefix): try: - return int(bn[len(base):]) + return int(bn[len(prefix):]) except ValueError: pass - # compute the maximum number currently in use with the base + # compute the maximum number currently in use with the + # prefix maxnum = -1 for path in rootdir.listdir(): num = parse_num(path) @@ -545,7 +546,7 @@ maxnum = max(maxnum, num) # make the new directory - udir = rootdir.mkdir(base + str(maxnum+1)) + udir = rootdir.mkdir(prefix + str(maxnum+1)) # prune old directories if keep: Modified: py/dist/py/path/local/testing/test_local.py ============================================================================== --- py/dist/py/path/local/testing/test_local.py (original) +++ py/dist/py/path/local/testing/test_local.py Sat Apr 23 21:40:12 2005 @@ -192,7 +192,7 @@ root = self.tmpdir root.ensure('base.not_an_int', dir=1) for i in range(10): - numdir = local.make_numbered_dir(root, 'base.', keep=2) + numdir = local.make_numbered_dir(prefix='base.', rootdir=root, keep=2) assert numdir.check() assert numdir.basename == 'base.%d' %i if i>=1: Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Sat Apr 23 21:40:12 2005 @@ -20,7 +20,7 @@ """ global basetemp if basetemp is None: - basetemp = py.path.local.make_numbered_dir(base='pytest-') + basetemp = py.path.local.make_numbered_dir(prefix='pytest-') return basetemp.ensure(string, dir=dir) class Config(object): Modified: pypy/dist/pypy/tool/udir.py ============================================================================== --- pypy/dist/pypy/tool/udir.py (original) +++ pypy/dist/pypy/tool/udir.py Sat Apr 23 21:40:12 2005 @@ -2,5 +2,5 @@ from py.path import local -udir = local.make_numbered_dir(base='usession-', keep=3) +udir = local.make_numbered_dir(prefix='usession-', keep=3) From hpk at codespeak.net Sat Apr 23 23:27:30 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 23 Apr 2005 23:27:30 +0200 (CEST) Subject: [py-svn] r11053 - py/dist/py/test/tool Message-ID: <20050423212730.F3B0627B66@code1.codespeak.net> Author: hpk Date: Sat Apr 23 23:27:30 2005 New Revision: 11053 Modified: py/dist/py/test/tool/outerrcapture.py Log: short cut name for SimpleOutErrCapture Modified: py/dist/py/test/tool/outerrcapture.py ============================================================================== --- py/dist/py/test/tool/outerrcapture.py (original) +++ py/dist/py/test/tool/outerrcapture.py Sat Apr 23 23:27:30 2005 @@ -32,3 +32,5 @@ o.seek(0) e.seek(0) return o,e + +simplecapture = SimpleOutErrCapture From hpk at codespeak.net Sun Apr 24 00:24:31 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Apr 2005 00:24:31 +0200 (CEST) Subject: [py-svn] r11054 - in py/dist/py: . bin misc Message-ID: <20050423222431.273E427B66@code1.codespeak.net> Author: hpk Date: Sun Apr 24 00:24:30 2005 New Revision: 11054 Added: py/dist/py/bin/_makepyrelease.py (contents, props changed) Modified: py/dist/py/__init__.py py/dist/py/initpkg.py py/dist/py/misc/_dist.py Log: first stab at an automatic "makepyrelease" script that puts things on codespeak, downloads it on a test account, unpacks and tests it, all from on the interactive command line. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Sun Apr 24 00:24:30 2005 @@ -6,11 +6,10 @@ from initpkg import initpkg initpkg(__name__, - name = "py", description = "py.test and the py lib", - version = "0.8.0-prealpha", + version = "0.8.0-pre-alpha", url = "http://codespeak.net/py", - download_url = "http://codespeak.net/download/py-0.8.0-pre-alpha.tgz", + download_url = "http://codespeak.net/download/py/py-0.8.0-pre-alpha.tar.gz", license = "MIT license", platforms = ['unix', 'linux', 'cygwin'], author = "holger krekel & others", Added: py/dist/py/bin/_makepyrelease.py ============================================================================== --- (empty file) +++ py/dist/py/bin/_makepyrelease.py Sun Apr 24 00:24:30 2005 @@ -0,0 +1,179 @@ +#!/usr/bin/python + +from _findpy import py +import sys + +pydir = py.path.local(py.__file__).dirpath() +rootdir = pydir.dirpath() + +def gen_manifest(): + pywc = py.path.svnwc(pydir) + status = pywc.status(rec=True) + #assert not status.modified + #assert not status.deleted + #assert not status.added + versioned = dict([(x.localpath,1) for x in status.allpath()]) + + l = [] + for x in rootdir.visit(): + if x.check(file=1): + names = [y.basename for y in x.parts()] + if '.svn' in names: + l.append(x) + elif x in versioned: + l.append(x) + l.append(rootdir / "setup.py") + l = [x.relto(rootdir) for x in l] + l.append("") + s = "\n".join(l) + return s + +def trace(arg): + lines = str(arg).split('\n') + prefix = "[trace] " + prefix = "* " + indent = len(prefix) + ispace = " " * indent + lines = [ispace + line for line in lines] + if lines: + lines[0] = prefix + lines[0][indent:] + for line in lines: + print >>py.std.sys.stdout, line + +def make_distfiles(tmpdir): + """ return distdir with tar.gz and zipfile. """ + manifest = tmpdir.join('MANIFEST') + trace("generating %s" %(manifest,)) + content = gen_manifest() + manifest.write(content) + trace("wrote %d files into manifest file" %len(content.split('\n'))) + + distdir = tmpdir.ensure('dist', dir=1) + oldir = rootdir.chdir() + try: + trace("invoking sdist, generating into %s" % (distdir,)) + py._dist.setup(py, script_name="setup.py", + script_args=('-q', 'sdist', '--no-prune', + '-m', str(manifest), + '--formats=gztar,zip', + '-d', str(distdir))) + finally: + oldir.chdir() + return distdir + + +def pytest(unpacked): + trace("py-testing %s" % unpacked) + old = unpacked.chdir() + try: + import os + os.system("python py/bin/py.test py") + finally: + old.chdir() + +def unpackremotetar(tmpdir, strurl): + import tarfile, urllib + f = urllib.urlopen(strurl) + basename = strurl.split('/')[-1] + target = tmpdir.join(basename) + trace("downloading to %s" %(target,)) + target.write(f.read()) + + trace("extracting to %s" %(target,)) + old = tmpdir.chdir() + try: + py.process.cmdexec("tar zxf %s" %(target,)) + finally: + old.chdir() + prefix = '.tar.gz' + assert basename.endswith(prefix) + stripped = basename[:-len(prefix)] + unpacked = tmpdir.join(stripped) + assert unpacked + return unpacked + +def checksvnworks(unpacked): + pywc = py.path.svnwc(unpacked.join('py')) + trace("checking versioning works: %s" %(pywc,)) + status = pywc.status(rec=True) + assert not status.modified + assert not status.deleted + assert not status.unknown + +def pytest_remote(address, url): + gw = py.execnet.SshGateway(address) + basename = url[url.rfind('/')+1:] + purebasename = basename[:-len('.tar.gz')] + + def mytrace(x, l=[]): + l.append(x) + if x.endswith('\n'): + trace("".join(l)) + l[:] = [] + + channel = gw.remote_exec(stdout=mytrace, stderr=sys.stderr, source=""" + url = %(url)r + basename = %(basename)r + purebasename = %(purebasename)r + import os, urllib + f = urllib.urlopen(url) + print "reading from", url + s = f.read() + f.close() + f = open(basename, 'w') + f.write(s) + f.close() + if os.path.exists(purebasename): + import shutil + shutil.rmtree(purebasename) + os.system("tar zxf %%s" %% (basename,)) + print "unpacked", purebasename + os.chdir(purebasename) + print "testing at %(address)s ..." + #os.system("python py/bin/py.test py") + import commands + status, output = commands.getstatusoutput("python py/bin/py.test py") + #print output + print "status:", status + + """ % locals()) + channel.waitclose(200.0) + +if __name__ == '__main__': + py.magic.invoke(assertion=True) + version = py.std.sys.argv[1] + assert py.__package__.version == version, ( + "py package has version %s\nlocation: %s" % + (py.__package__.version, pydir)) + + tmpdir = py.path.local.get_temproot().join('makepyrelease-%s' % version) + if tmpdir.check(): + trace("removing %s" %(tmpdir,)) + tmpdir.remove() + tmpdir.mkdir() + trace("using tmpdir %s" %(tmpdir,)) + + distdir = make_distfiles(tmpdir) + targz = distdir.join('py-%s.tar.gz' % version) + zip = distdir.join('py-%s.zip' % version) + files = targz, zip + for fn in files: + assert fn.check(file=1) + + remotedir = 'codespeak.net://www/codespeak.net/htdocs/download/py/' + source = " ".join([str(x) for x in files]) + trace("rsyncing %(source)s to %(remotedir)s" % locals()) + py.process.cmdexec("rsync -avz %(source)s %(remotedir)s" % locals()) + + ddir = tmpdir.ensure('download', dir=1) + URL = py.__package__.download_url # 'http://codespeak.net/download/py/' + unpacked = unpackremotetar(ddir, URL) + assert unpacked == ddir.join("py-%s" % (version,)) + + checksvnworks(unpacked) + #pytest(unpacked) + + pytest_remote('test at codespeak.net', py.__package__.download_url) + + + Modified: py/dist/py/initpkg.py ============================================================================== --- py/dist/py/initpkg.py (original) +++ py/dist/py/initpkg.py Sun Apr 24 00:24:30 2005 @@ -35,6 +35,7 @@ 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__'), \ Modified: py/dist/py/misc/_dist.py ============================================================================== --- py/dist/py/misc/_dist.py (original) +++ py/dist/py/misc/_dist.py Sun Apr 24 00:24:30 2005 @@ -85,28 +85,23 @@ print "data file ", x print -def setup(pkg): +def setup(pkg, **kw): """ invoke distutils on a given package. """ params = Params(pkg) #dump(params) source = getattr(pkg, '__package__', pkg) - - kw = {} namelist = list(core.setup_keywords) namelist.extend(['packages', 'scripts', 'data_files']) for name in namelist: - try: - kw[name] = getattr(source, name, - getattr(params, name)) - except AttributeError: - pass - #print "couldn't find", name + for ns in (source, params): + if hasattr(ns, name): + kw[name] = getattr(ns, name) + break #script_args = sys.argv[1:] #if 'install' in script_args: # script_args = ['--quiet'] + script_args # #print "installing", py - #py.std.pprint.pprint(kw) core.setup(**kw) From hpk at codespeak.net Sun Apr 24 00:39:24 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Apr 2005 00:39:24 +0200 (CEST) Subject: [py-svn] r11055 - py/dist/py/documentation Message-ID: <20050423223924.B666127B66@code1.codespeak.net> Author: hpk Date: Sun Apr 24 00:39:24 2005 New Revision: 11055 Modified: py/dist/py/documentation/releasescheme.txt Log: fix planned release directory structure Modified: py/dist/py/documentation/releasescheme.txt ============================================================================== --- py/dist/py/documentation/releasescheme.txt (original) +++ py/dist/py/documentation/releasescheme.txt Sun Apr 24 00:39:24 2005 @@ -1,8 +1,13 @@ -The directory release layout of the repository:: + +py subversion directory structure +================================= + +The directory release layout of the repository is +going to follow this scheme:: svn/py/dist # latest released code - svn/py/tag/py-X.Y.Z # tagged releases - svn/py/branch/py-X.Y # contains release branch development + svn/py/tag/X.Y.Z # tagged releases + svn/py/branch/X.Y # contains release branch development svn/py/trunk # head development Scenario "no svn and just let me play, please" From hpk at codespeak.net Sun Apr 24 02:24:23 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Apr 2005 02:24:23 +0200 (CEST) Subject: [py-svn] r11059 - py/dist/py/documentation Message-ID: <20050424002423.1016227B66@code1.codespeak.net> Author: hpk Date: Sun Apr 24 02:24:22 2005 New Revision: 11059 Modified: py/dist/py/documentation/TODO.txt Log: ReST fix Modified: py/dist/py/documentation/TODO.txt ============================================================================== --- py/dist/py/documentation/TODO.txt (original) +++ py/dist/py/documentation/TODO.txt Sun Apr 24 02:24:22 2005 @@ -1,5 +1,5 @@ Things to do before 0.8.0 -------------------------- +========================= distutils install ----------------- From arigo at codespeak.net Sun Apr 24 13:16:16 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 24 Apr 2005 13:16:16 +0200 (CEST) Subject: [py-svn] r11402 - py/dist/py/c-extension/greenlet Message-ID: <20050424111616.7D34E27B52@code1.codespeak.net> Author: arigo Date: Sun Apr 24 13:16:16 2005 New Revision: 11402 Modified: py/dist/py/c-extension/greenlet/greenlet.c Log: Check that slp_platformselect.h actually detected the platform. Modified: py/dist/py/c-extension/greenlet/greenlet.c ============================================================================== --- py/dist/py/c-extension/greenlet/greenlet.c (original) +++ py/dist/py/c-extension/greenlet/greenlet.c Sun Apr 24 13:16:16 2005 @@ -249,6 +249,11 @@ #define SLP_EVAL #include "slp_platformselect.h" +#ifndef STACK_MAGIC +#error "greenlet needs to be ported to this platform,\ + or teached how to detect your compiler properly." +#endif + static int g_switchstack(void) { From arigo at codespeak.net Sun Apr 24 14:12:54 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 24 Apr 2005 14:12:54 +0200 (CEST) Subject: [py-svn] r11403 - py/dist/py/c-extension/greenlet Message-ID: <20050424121254.3AA2C27B5F@code1.codespeak.net> Author: arigo Date: Sun Apr 24 14:12:53 2005 New Revision: 11403 Added: py/dist/py/c-extension/greenlet/switch_amd64_unix.h - copied, changed from r11401, py/dist/py/c-extension/greenlet/switch_x86_unix.h Modified: py/dist/py/c-extension/greenlet/slp_platformselect.h Log: X86-64 support code. This code has been posted as a patch for Stackless by Hye-Shik Chang on the stackless mailing list. Modified: py/dist/py/c-extension/greenlet/slp_platformselect.h ============================================================================== --- py/dist/py/c-extension/greenlet/slp_platformselect.h (original) +++ py/dist/py/c-extension/greenlet/slp_platformselect.h Sun Apr 24 14:12:53 2005 @@ -6,6 +6,8 @@ #include "switch_x86_msvc.h" /* MS Visual Studio on X86 */ #elif defined(__GNUC__) && defined(__i386__) #include "switch_x86_unix.h" /* gcc on X86 */ +#elif defined(__GNUC__) && defined(__amd64__) +#include "switch_amd64_unix.h" /* gcc on amd64 */ #elif defined(__GNUC__) && defined(__PPC__) && defined(__linux__) #include "switch_ppc_unix.h" /* gcc on PowerPC */ #elif defined(__GNUC__) && defined(__ppc__) && defined(__APPLE__) Copied: py/dist/py/c-extension/greenlet/switch_amd64_unix.h (from r11401, py/dist/py/c-extension/greenlet/switch_x86_unix.h) ============================================================================== --- py/dist/py/c-extension/greenlet/switch_x86_unix.h (original) +++ py/dist/py/c-extension/greenlet/switch_amd64_unix.h Sun Apr 24 14:12:53 2005 @@ -2,6 +2,8 @@ * this is the internal transfer function. * * HISTORY + * 01-Apr-04 Hye-Shik Chang + * Ported from i386 to amd64. * 24-Nov-02 Christian Tismer * needed to add another magic constant to insure * that f in slp_eval_frame(PyFrameObject *f) @@ -27,24 +29,27 @@ /* the above works fine with gcc 2.96, but 2.95.3 wants this */ #define STACK_MAGIC 0 +#define REGS_TO_SAVE "rdx", "rbx", "r12", "r13", "r14", "r15" + + static int slp_switch(void) { - register int *stackref, stsizediff; - __asm__ volatile ("" : : : "ebx", "esi", "edi"); - __asm__ ("movl %%esp, %0" : "=g" (stackref)); + register long *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("movq %%rsp, %0" : "=g" (stackref)); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( - "addl %0, %%esp\n" - "addl %0, %%ebp\n" + "addq %0, %%rsp\n" + "addq %0, %%rbp\n" : : "r" (stsizediff) ); SLP_RESTORE_STATE(); return 0; } - __asm__ volatile ("" : : : "ebx", "esi", "edi"); + __asm__ volatile ("" : : : REGS_TO_SAVE); } #endif From arigo at codespeak.net Sun Apr 24 14:18:45 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 24 Apr 2005 14:18:45 +0200 (CEST) Subject: [py-svn] r11404 - py/dist/py/c-extension/greenlet Message-ID: <20050424121845.3235627B5F@code1.codespeak.net> Author: arigo Date: Sun Apr 24 14:18:44 2005 New Revision: 11404 Modified: py/dist/py/c-extension/greenlet/slp_platformselect.h Log: Looks safer in this order. Modified: py/dist/py/c-extension/greenlet/slp_platformselect.h ============================================================================== --- py/dist/py/c-extension/greenlet/slp_platformselect.h (original) +++ py/dist/py/c-extension/greenlet/slp_platformselect.h Sun Apr 24 14:18:44 2005 @@ -4,10 +4,10 @@ #if defined(MS_WIN32) && !defined(MS_WIN64) && defined(_M_IX86) #include "switch_x86_msvc.h" /* MS Visual Studio on X86 */ -#elif defined(__GNUC__) && defined(__i386__) -#include "switch_x86_unix.h" /* gcc on X86 */ #elif defined(__GNUC__) && defined(__amd64__) #include "switch_amd64_unix.h" /* gcc on amd64 */ +#elif defined(__GNUC__) && defined(__i386__) +#include "switch_x86_unix.h" /* gcc on X86 */ #elif defined(__GNUC__) && defined(__PPC__) && defined(__linux__) #include "switch_ppc_unix.h" /* gcc on PowerPC */ #elif defined(__GNUC__) && defined(__ppc__) && defined(__APPLE__) From hpk at codespeak.net Sun Apr 24 15:18:48 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Apr 2005 15:18:48 +0200 (CEST) Subject: [py-svn] r11405 - py/dist/py Message-ID: <20050424131848.C3C9527B56@code1.codespeak.net> Author: hpk Date: Sun Apr 24 15:18:48 2005 New Revision: 11405 Modified: py/dist/py/__init__.py (contents, props changed) Log: added reference information for svn-revisions Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Sun Apr 24 15:18:48 2005 @@ -7,6 +7,8 @@ initpkg(__name__, description = "py.test and the py lib", + revision = '$LastChangedRevision$', + lastchangedate = '$LastChangedDate$', version = "0.8.0-pre-alpha", url = "http://codespeak.net/py", download_url = "http://codespeak.net/download/py/py-0.8.0-pre-alpha.tar.gz", From hpk at codespeak.net Sun Apr 24 15:49:00 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Apr 2005 15:49:00 +0200 (CEST) Subject: [py-svn] r11409 - py/dist/py/misc Message-ID: <20050424134900.E8BC327B56@code1.codespeak.net> Author: hpk Date: Sun Apr 24 15:49:00 2005 New Revision: 11409 Modified: py/dist/py/misc/_dist.py Log: kill the build directory on install target Modified: py/dist/py/misc/_dist.py ============================================================================== --- py/dist/py/misc/_dist.py (original) +++ py/dist/py/misc/_dist.py Sun Apr 24 15:49:00 2005 @@ -105,3 +105,8 @@ # #print "installing", py #py.std.pprint.pprint(kw) core.setup(**kw) + if 'install' in sys.argv[1:]: + x = params._rootdir.join('build') + if x.check(): + print "removing", x + x.remove() From hpk at codespeak.net Sun Apr 24 17:57:07 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Apr 2005 17:57:07 +0200 (CEST) Subject: [py-svn] r11410 - in py/dist/py: bin misc Message-ID: <20050424155707.6C78D27B56@code1.codespeak.net> Author: hpk Date: Sun Apr 24 17:57:07 2005 New Revision: 11410 Modified: py/dist/py/bin/_findpy.py py/dist/py/misc/_dist.py Log: install all scripts from bin/ directories Modified: py/dist/py/bin/_findpy.py ============================================================================== --- py/dist/py/bin/_findpy.py (original) +++ py/dist/py/bin/_findpy.py Sun Apr 24 17:57:07 2005 @@ -1,3 +1,5 @@ +#!/usr/bin/python + # # find and import a version of 'py' # @@ -30,3 +32,6 @@ pass # let's hope it is just on sys.path import py + +if __name__ == '__main__': + print "py lib is at", py.__file__ Modified: py/dist/py/misc/_dist.py ============================================================================== --- py/dist/py/misc/_dist.py (original) +++ py/dist/py/misc/_dist.py Sun Apr 24 17:57:07 2005 @@ -28,11 +28,11 @@ if p.check(file=1): if p.ext in ('.pyc', '.pyo'): continue - if p.ext == '.py': - self.addpythonfile(p) - elif p.dirpath().basename == 'bin': + if p.dirpath().basename == 'bin': self.scripts.append(p.relto(self._rootdir)) self.adddatafile(p) + elif p.ext == '.py': + self.addpythonfile(p) else: self.adddatafile(p) else: From hpk at codespeak.net Sun Apr 24 17:59:24 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Apr 2005 17:59:24 +0200 (CEST) Subject: [py-svn] r11411 - in py/dist/py/execnet: . bin script Message-ID: <20050424155924.4E21D27B56@code1.codespeak.net> Author: hpk Date: Sun Apr 24 17:59:24 2005 New Revision: 11411 Added: py/dist/py/execnet/script/ - copied from r11409, py/dist/py/execnet/bin/ Removed: py/dist/py/execnet/bin/ Modified: py/dist/py/execnet/register.py Log: move bin to script due to new setup.py's autoinstall of things in a bin-directories Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Sun Apr 24 17:59:24 2005 @@ -113,7 +113,7 @@ else: host, port = hostport socketserverbootstrap = py.code.Source( - mypath.dirpath('bin', 'socketserver.py').read(), + mypath.dirpath('script', 'socketserver.py').read(), """ import socket sock = bind_and_listen((%r, %r)) From hpk at codespeak.net Sun Apr 24 18:07:52 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Apr 2005 18:07:52 +0200 (CEST) Subject: [py-svn] r11409 - py/dist/py/misc Message-ID: <20050424160752.9414627B56@code1.codespeak.net> Author: hpk Date: Sun Apr 24 15:49:00 2005 New Revision: 11409 Modified: py/dist/py/misc/_dist.py Log: kill the build directory on install target Modified: py/dist/py/misc/_dist.py ============================================================================== --- py/dist/py/misc/_dist.py (original) +++ py/dist/py/misc/_dist.py Sun Apr 24 15:49:00 2005 @@ -105,3 +105,8 @@ # #print "installing", py #py.std.pprint.pprint(kw) core.setup(**kw) + if 'install' in sys.argv[1:]: + x = params._rootdir.join('build') + if x.check(): + print "removing", x + x.remove() From hpk at codespeak.net Sun Apr 24 23:47:04 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 24 Apr 2005 23:47:04 +0200 (CEST) Subject: [py-svn] r11413 - in py/dist/py: . bin misc Message-ID: <20050424214704.C0A6E27B99@code1.codespeak.net> Author: hpk Date: Sun Apr 24 23:47:04 2005 New Revision: 11413 Modified: py/dist/py/__init__.py py/dist/py/bin/_makepyrelease.py py/dist/py/misc/_dist.py Log: - don't put .svn files into distributions - enhance the _makepyrelease script a bit to build a win32 installer as well (untested) Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Sun Apr 24 23:47:04 2005 @@ -7,11 +7,11 @@ initpkg(__name__, description = "py.test and the py lib", - revision = '$LastChangedRevision$', + revision = int('$LastChangedRevision$'.split(':')[1][:-1]), lastchangedate = '$LastChangedDate$', - version = "0.8.0-pre-alpha", + version = "0.8.0-alpha1", url = "http://codespeak.net/py", - download_url = "http://codespeak.net/download/py/py-0.8.0-pre-alpha.tar.gz", + download_url = "http://codespeak.net/download/py/py-0.8.0-alpha1.tar.gz", license = "MIT license", platforms = ['unix', 'linux', 'cygwin'], author = "holger krekel & others", Modified: py/dist/py/bin/_makepyrelease.py ============================================================================== --- py/dist/py/bin/_makepyrelease.py (original) +++ py/dist/py/bin/_makepyrelease.py Sun Apr 24 23:47:04 2005 @@ -15,7 +15,7 @@ versioned = dict([(x.localpath,1) for x in status.allpath()]) l = [] - for x in rootdir.visit(): + for x in rootdir.visit(None, lambda x: x.basename != '.svn'): if x.check(file=1): names = [y.basename for y in x.parts()] if '.svn' in names: @@ -57,6 +57,10 @@ '-m', str(manifest), '--formats=gztar,zip', '-d', str(distdir))) + py._dist.setup(py, script_name="setup.py", + script_args=('-q', 'bdist_wininst', + #'-m', str(manifest), + '-d', str(distdir))) finally: oldir.chdir() return distdir @@ -156,21 +160,21 @@ distdir = make_distfiles(tmpdir) targz = distdir.join('py-%s.tar.gz' % version) zip = distdir.join('py-%s.zip' % version) - files = targz, zip + files = distdir.listdir() for fn in files: assert fn.check(file=1) remotedir = 'codespeak.net://www/codespeak.net/htdocs/download/py/' - source = " ".join([str(x) for x in files]) + source = distdir # " ".join([str(x) for x in files]) trace("rsyncing %(source)s to %(remotedir)s" % locals()) - py.process.cmdexec("rsync -avz %(source)s %(remotedir)s" % locals()) + py.process.cmdexec("rsync -avz %(source)s/ %(remotedir)s" % locals()) ddir = tmpdir.ensure('download', dir=1) URL = py.__package__.download_url # 'http://codespeak.net/download/py/' unpacked = unpackremotetar(ddir, URL) assert unpacked == ddir.join("py-%s" % (version,)) - checksvnworks(unpacked) + #checksvnworks(unpacked) #pytest(unpacked) pytest_remote('test at codespeak.net', py.__package__.download_url) Modified: py/dist/py/misc/_dist.py ============================================================================== --- py/dist/py/misc/_dist.py (original) +++ py/dist/py/misc/_dist.py Sun Apr 24 23:47:04 2005 @@ -24,7 +24,7 @@ self.scripts.sort() def hacktree(self): - for p in self._pkgdir.visit(): + for p in self._pkgdir.visit(None, lambda x: x.basename != '.svn'): if p.check(file=1): if p.ext in ('.pyc', '.pyo'): continue @@ -35,9 +35,9 @@ self.addpythonfile(p) else: self.adddatafile(p) - else: - if not p.listdir(): - self.adddatafile(p.ensure('dummy')) + #else: + # if not p.listdir(): + # self.adddatafile(p.ensure('dummy')) def adddatafile(self, p): if p.ext in ('.pyc', 'pyo'): From hpk at codespeak.net Tue Apr 26 11:09:51 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 26 Apr 2005 11:09:51 +0200 (CEST) Subject: [py-svn] r11443 - py/dist/py/test Message-ID: <20050426090951.418B527B9E@code1.codespeak.net> Author: hpk Date: Tue Apr 26 11:09:51 2005 New Revision: 11443 Modified: py/dist/py/test/collect.py Log: show module name in Module.__repr__ Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Tue Apr 26 11:09:51 2005 @@ -267,6 +267,12 @@ return self.Function(name, parent=self) class Module(PyCollectorMixin, FSCollector): + + def __repr__(self): + return "<%s %r (%s)>" %(self.__class__.__name__, + self.name, + self.obj.__name__) + def obj(self): try: return self._obj From hpk at codespeak.net Tue Apr 26 11:22:20 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 26 Apr 2005 11:22:20 +0200 (CEST) Subject: [py-svn] r11444 - in py/dist/py: . bin code documentation execnet execnet/testing magic magic/testing misc/testing path/extpy path/extpy/testing path/gateway path/local path/local/popen5/testing path/local/testing path/svn path/svn/testing path/testing test test/terminal test/testing test/tkinter test/tkinter/testing test/tool test/tool/testing xmlobj Message-ID: <20050426092220.027E827B97@code1.codespeak.net> Author: hpk Date: Tue Apr 26 11:22:20 2005 New Revision: 11444 Modified: py/dist/py/__init__.py py/dist/py/bin/py.test py/dist/py/code/traceback2.py py/dist/py/documentation/conftest.py py/dist/py/execnet/channel.py py/dist/py/execnet/gateway.py py/dist/py/execnet/register.py py/dist/py/execnet/testing/test_gateway.py py/dist/py/initpkg.py py/dist/py/magic/assertion.py py/dist/py/magic/autopath.py py/dist/py/magic/invoke.py py/dist/py/magic/testing/test_exprinfo.py py/dist/py/misc/testing/test_cache.py py/dist/py/misc/testing/test_initpkg.py py/dist/py/path/extpy/extpy.py py/dist/py/path/extpy/testing/test_extpy.py py/dist/py/path/gateway/remotepath.py py/dist/py/path/local/local.py py/dist/py/path/local/popen5/testing/test_subprocess.py py/dist/py/path/local/testing/test_local.py py/dist/py/path/svn/svncommon.py py/dist/py/path/svn/testing/svntestbase.py py/dist/py/path/svn/testing/test_urlcommand.py py/dist/py/path/svn/testing/test_wccommand.py py/dist/py/path/svn/urlcommand.py py/dist/py/path/svn/wccommand.py py/dist/py/path/testing/fscommon.py py/dist/py/path/testing/test_api.py py/dist/py/test/config.py py/dist/py/test/session.py py/dist/py/test/terminal/out.py py/dist/py/test/terminal/remote.py py/dist/py/test/terminal/terminal.py py/dist/py/test/testing/test_collect.py py/dist/py/test/testing/test_config.py py/dist/py/test/testing/test_session.py py/dist/py/test/tkinter/backend.py py/dist/py/test/tkinter/guisession.py py/dist/py/test/tkinter/testing/test_backend.py py/dist/py/test/tkinter/testing/test_guisession.py py/dist/py/test/tkinter/testing/test_repository.py py/dist/py/test/tkinter/testing/test_util.py py/dist/py/test/tkinter/tkgui.py py/dist/py/test/tkinter/util.py py/dist/py/test/tool/optparse.py py/dist/py/test/tool/testing/test_outerrcapture.py py/dist/py/xmlobj/html.py py/dist/py/xmlobj/xml.py Log: great renaming: __impl__ -> __ This change does not affect any use of the public py lib API but disrupts every use of implementation level stuff. You just need to rename '__impl__' to '__'. Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Tue Apr 26 11:22:20 2005 @@ -32,6 +32,7 @@ # 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'), Modified: py/dist/py/bin/py.test ============================================================================== --- py/dist/py/bin/py.test (original) +++ py/dist/py/bin/py.test Tue Apr 26 11:22:20 2005 @@ -1,5 +1,4 @@ #!/usr/bin/env python from _findpy import py -from py.__impl__.test.cmdline import main -main() +py.test.cmdline.main() Modified: py/dist/py/code/traceback2.py ============================================================================== --- py/dist/py/code/traceback2.py (original) +++ py/dist/py/code/traceback2.py Tue Apr 26 11:22:20 2005 @@ -26,7 +26,7 @@ """Reinterpret the failing statement and returns a detailed information about what operations are performed.""" if self.exprinfo is None: - from py.__impl__.magic import exprinfo + from py.__.magic import exprinfo source = str(self.statement).strip() x = exprinfo.interpret(source, self.frame, should_fail=True) if not isinstance(x, str): Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Tue Apr 26 11:22:20 2005 @@ -1,6 +1,6 @@ from __future__ import generators import py -from py.__impl__.misc import rest +from py.__.misc import rest Option = py.test.Config.Option option = py.test.Config.addoptions("documentation check options", Modified: py/dist/py/execnet/channel.py ============================================================================== --- py/dist/py/execnet/channel.py (original) +++ py/dist/py/execnet/channel.py Tue Apr 26 11:22:20 2005 @@ -1,7 +1,7 @@ import threading import Queue if 'Message' not in globals(): - from py.__impl__.execnet.message import Message + from py.__.execnet.message import Message class RemoteError(EOFError): """ Contains an Exceptions from the other side. """ Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Tue Apr 26 11:22:20 2005 @@ -19,8 +19,8 @@ if 'ThreadOut' not in globals(): import py from py.code import Source - from py.__impl__.execnet.channel import ChannelFactory, Channel - from py.__impl__.execnet.message import Message + from py.__.execnet.channel import ChannelFactory, Channel + from py.__.execnet.message import Message ThreadOut = py._thread.ThreadOut WorkerPool = py._thread.WorkerPool NamedThreadPool = py._thread.NamedThreadPool Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Tue Apr 26 11:22:20 2005 @@ -12,19 +12,19 @@ # in a sufficient version startup_modules = [ - 'py.__impl__.thread.io', - 'py.__impl__.thread.pool', - 'py.__impl__.execnet.inputoutput', - 'py.__impl__.execnet.gateway', - 'py.__impl__.execnet.message', - 'py.__impl__.execnet.channel', + 'py.__.thread.io', + 'py.__.thread.pool', + 'py.__.execnet.inputoutput', + 'py.__.execnet.gateway', + 'py.__.execnet.message', + 'py.__.execnet.channel', ] def getsource(dottedname): mod = __import__(dottedname, None, None, ['__doc__']) return inspect.getsource(mod) -from py.__impl__.execnet import inputoutput, gateway +from py.__.execnet import inputoutput, gateway class InstallableGateway(gateway.Gateway): """ initialize gateways on both sides of a inputoutput object. """ 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 Tue Apr 26 11:22:20 2005 @@ -1,11 +1,11 @@ import os, sys import py -from py.__impl__.execnet import gateway -from py.__impl__.conftest import option +from py.__.execnet import gateway +from py.__.conftest import option mypath = py.magic.autopath() from StringIO import StringIO -from py.__impl__.execnet.register import startup_modules, getsource +from py.__.execnet.register import startup_modules, getsource def test_getsource_import_modules(): for dottedname in startup_modules: Modified: py/dist/py/initpkg.py ============================================================================== --- py/dist/py/initpkg.py (original) +++ py/dist/py/initpkg.py Tue Apr 26 11:22:20 2005 @@ -42,14 +42,14 @@ "unsupported reinitialization of %r" % pkgmodule pkgmodule.__package__ = self - # make available pkgname.__impl__ - implname = name + '.' + '__impl__' + # 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.__impl__ = self.implmodule + pkgmodule.__ = self.implmodule setmodule(implname, self.implmodule) # inhibit further direct filesystem imports through the package module del pkgmodule.__path__ Modified: py/dist/py/magic/assertion.py ============================================================================== --- py/dist/py/magic/assertion.py (original) +++ py/dist/py/magic/assertion.py Tue Apr 26 11:22:20 2005 @@ -1,6 +1,6 @@ import __builtin__, sys import py -from py.__impl__.magic import exprinfo +from py.__.magic import exprinfo BuiltinAssertionError = __builtin__.AssertionError Modified: py/dist/py/magic/autopath.py ============================================================================== --- py/dist/py/magic/autopath.py (original) +++ py/dist/py/magic/autopath.py Tue Apr 26 11:22:20 2005 @@ -1,6 +1,6 @@ import os, sys from py.path import local -from py.__impl__.path.common import PathStr +from py.__.path.common import PathStr def autopath(globs=None, basefile='__init__.py'): """ return the (local) path of the "current" file pointed to by globals Modified: py/dist/py/magic/invoke.py ============================================================================== --- py/dist/py/magic/invoke.py (original) +++ py/dist/py/magic/invoke.py Tue Apr 26 11:22:20 2005 @@ -10,7 +10,7 @@ a useful error message. """ if assertion: - from py.__impl__.magic import assertion + from py.__.magic import assertion assertion.invoke() if compile: py.magic.patch(cpy_builtin, 'compile', py.code.compile ) @@ -18,7 +18,7 @@ def revoke(assertion=False, compile=False): """ revoke previously invoked magic (see invoke()).""" if assertion: - from py.__impl__.magic import assertion + from py.__.magic import assertion assertion.revoke() if compile: py.magic.revert(cpy_builtin, 'compile') Modified: py/dist/py/magic/testing/test_exprinfo.py ============================================================================== --- py/dist/py/magic/testing/test_exprinfo.py (original) +++ py/dist/py/magic/testing/test_exprinfo.py Tue Apr 26 11:22:20 2005 @@ -1,7 +1,7 @@ import sys import py -from py.__impl__.magic.exprinfo import getmsg, interpret +from py.__.magic.exprinfo import getmsg, interpret def getexcinfo(exc, obj, *args, **kwargs): try: Modified: py/dist/py/misc/testing/test_cache.py ============================================================================== --- py/dist/py/misc/testing/test_cache.py (original) +++ py/dist/py/misc/testing/test_cache.py Tue Apr 26 11:22:20 2005 @@ -1,5 +1,5 @@ import py -from py.__impl__.misc.cache import BuildcostAccessCache, AgingCache +from py.__.misc.cache import BuildcostAccessCache, AgingCache class BasicCacheAPITest: cache = None Modified: py/dist/py/misc/testing/test_initpkg.py ============================================================================== --- py/dist/py/misc/testing/test_initpkg.py (original) +++ py/dist/py/misc/testing/test_initpkg.py Tue Apr 26 11:22:20 2005 @@ -11,7 +11,7 @@ assert getattr(obj, '__map__') == {} def test_dir(): - from py.__impl__.initpkg import Module + from py.__.initpkg import Module for name in dir(py): if name == 'magic': # greenlets don't work everywhere, we don't care here continue @@ -47,7 +47,7 @@ base.join('c-extension',), base.join('magic', 'greenlet.py'), base.join('bin'), - base.join('execnet', 'bin'), + base.join('execnet', 'script'), ) for p in base.visit('*.py', py.path.checker(dotfile=0)): relpath = p.new(ext='').relto(base) @@ -57,7 +57,7 @@ break else: relpath = relpath.replace(base.sep, '.') - modpath = 'py.__impl__.%s' % relpath + modpath = 'py.__.%s' % relpath yield check_import, modpath def check_import(modpath): Modified: py/dist/py/path/extpy/extpy.py ============================================================================== --- py/dist/py/path/extpy/extpy.py (original) +++ py/dist/py/path/extpy/extpy.py Tue Apr 26 11:22:20 2005 @@ -6,7 +6,7 @@ """ from __future__ import generators import py -from py.__impl__.path import common +from py.__.path import common import sys import inspect moduletype = type(py) Modified: py/dist/py/path/extpy/testing/test_extpy.py ============================================================================== --- py/dist/py/path/extpy/testing/test_extpy.py (original) +++ py/dist/py/path/extpy/testing/test_extpy.py Tue Apr 26 11:22:20 2005 @@ -1,7 +1,7 @@ import os import py -from py.__impl__.path.testing import common +from py.__.path.testing import common mypath = py.magic.autopath().dirpath('inc_test_extpy.py') Modified: py/dist/py/path/gateway/remotepath.py ============================================================================== --- py/dist/py/path/gateway/remotepath.py (original) +++ py/dist/py/path/gateway/remotepath.py Tue Apr 26 11:22:20 2005 @@ -1,5 +1,5 @@ import py, itertools -from py.__impl__.path import common +from py.__.path import common COUNTER = itertools.count() Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Tue Apr 26 11:22:20 2005 @@ -8,12 +8,12 @@ from __future__ import generators import sys, os, stat import py -from py.__impl__.path import common +from py.__.path import common if sys.platform == 'win32': - from py.__impl__.path.local.win import WinMixin as PlatformMixin + from py.__.path.local.win import WinMixin as PlatformMixin else: - from py.__impl__.path.local.posix import PosixMixin as PlatformMixin + from py.__.path.local.posix import PosixMixin as PlatformMixin class LocalPath(common.FSPathBase, PlatformMixin): """ Local path implementation offering access/modification @@ -396,7 +396,7 @@ def getpymodule(self): if self.ext != '.c': return super(LocalPath, self).getpymodule() - from py.__impl__.misc.buildcmodule import make_module_from_c + from py.__.misc.buildcmodule import make_module_from_c mod = make_module_from_c(self) return mod @@ -452,7 +452,7 @@ to be executed. Note that this process is directly invoked and not through a system shell. """ - from py.__impl__.path.local.popen5.subprocess import Popen, PIPE + from py.__.path.local.popen5.subprocess import Popen, PIPE argv = map(str, argv) proc = Popen([str(self)] + list(argv), stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate() Modified: py/dist/py/path/local/popen5/testing/test_subprocess.py ============================================================================== --- py/dist/py/path/local/popen5/testing/test_subprocess.py (original) +++ py/dist/py/path/local/popen5/testing/test_subprocess.py Tue Apr 26 11:22:20 2005 @@ -1,7 +1,7 @@ import sys import os import py -from py.__impl__.path.local.popen5 import subprocess +from py.__.path.local.popen5 import subprocess mswindows = (sys.platform == "win32") 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 Tue Apr 26 11:22:20 2005 @@ -1,6 +1,6 @@ import py from py.path import local, checker -from py.__impl__.path.testing.fscommon import CommonFSTests, setuptestfs +from py.__.path.testing.fscommon import CommonFSTests, setuptestfs class LocalSetup: def setup_class(cls): Modified: py/dist/py/path/svn/svncommon.py ============================================================================== --- py/dist/py/path/svn/svncommon.py (original) +++ py/dist/py/path/svn/svncommon.py Tue Apr 26 11:22:20 2005 @@ -3,7 +3,7 @@ """ import os, sys, time, re import py -from py.__impl__.path import common +from py.__.path import common #_______________________________________________________________ Modified: py/dist/py/path/svn/testing/svntestbase.py ============================================================================== --- py/dist/py/path/svn/testing/svntestbase.py (original) +++ py/dist/py/path/svn/testing/svntestbase.py Tue Apr 26 11:22:20 2005 @@ -1,7 +1,7 @@ import py from py import path, test, process -from py.__impl__.path.testing.fscommon import CommonFSTests, setuptestfs -from py.__impl__.path.svn import cache +from py.__.path.testing.fscommon import CommonFSTests, setuptestfs +from py.__.path.svn import cache mypath = py.magic.autopath() repodump = mypath.dirpath('repotest.dump') Modified: py/dist/py/path/svn/testing/test_urlcommand.py ============================================================================== --- py/dist/py/path/svn/testing/test_urlcommand.py (original) +++ py/dist/py/path/svn/testing/test_urlcommand.py Tue Apr 26 11:22:20 2005 @@ -1,5 +1,5 @@ import py -from py.__impl__.path.svn.testing.svntestbase import CommonCommandAndBindingTests, getrepowc +from py.__.path.svn.testing.svntestbase import CommonCommandAndBindingTests, getrepowc class TestSvnCommandPath(CommonCommandAndBindingTests): def setup_class(cls): Modified: py/dist/py/path/svn/testing/test_wccommand.py ============================================================================== --- py/dist/py/path/svn/testing/test_wccommand.py (original) +++ py/dist/py/path/svn/testing/test_wccommand.py Tue Apr 26 11:22:20 2005 @@ -1,5 +1,5 @@ import py -from py.__impl__.path.svn.testing.svntestbase import CommonSvnTests, getrepowc +from py.__.path.svn.testing.svntestbase import CommonSvnTests, getrepowc class TestWCSvnCommandPath(CommonSvnTests): Modified: py/dist/py/path/svn/urlcommand.py ============================================================================== --- py/dist/py/path/svn/urlcommand.py (original) +++ py/dist/py/path/svn/urlcommand.py Tue Apr 26 11:22:20 2005 @@ -8,9 +8,9 @@ import os, sys, time, re import py from py import path, process -from py.__impl__.path import common -from py.__impl__.path.svn import svncommon -from py.__impl__.misc.cache import BuildcostAccessCache, AgingCache +from py.__.path import common +from py.__.path.svn import svncommon +from py.__.misc.cache import BuildcostAccessCache, AgingCache class SvnCommandPath(svncommon.SvnPathBase): _lsrevcache = BuildcostAccessCache(maxentries=128) Modified: py/dist/py/path/svn/wccommand.py ============================================================================== --- py/dist/py/path/svn/wccommand.py (original) +++ py/dist/py/path/svn/wccommand.py Tue Apr 26 11:22:20 2005 @@ -10,9 +10,9 @@ import os, sys, time, re import py -from py.__impl__.path import common -from py.__impl__.path.svn import cache -from py.__impl__.path.svn import svncommon +from py.__.path import common +from py.__.path.svn import cache +from py.__.path.svn import svncommon DEBUG = 0 @@ -378,7 +378,7 @@ return True def log(self, rev_start=None, rev_end=1, verbose=False): - from py.__impl__.path.svn.command import _Head, LogEntry + from py.__.path.svn.command import _Head, LogEntry assert self.check() # make it simpler for the pipe rev_start = rev_start is None and _Head or rev_start rev_end = rev_end is None and _Head or rev_end Modified: py/dist/py/path/testing/fscommon.py ============================================================================== --- py/dist/py/path/testing/fscommon.py (original) +++ py/dist/py/path/testing/fscommon.py Tue Apr 26 11:22:20 2005 @@ -1,5 +1,5 @@ import py -from py.__impl__.path.testing import common +from py.__.path.testing import common checker = py.path.checker 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 Apr 26 11:22:20 2005 @@ -1,6 +1,6 @@ from py import path, test import py -from py.__impl__.path.svn.testing.test_wccommand import getrepowc +from py.__.path.svn.testing.test_wccommand import getrepowc class TestAPI: Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Tue Apr 26 11:22:20 2005 @@ -1,7 +1,7 @@ from __future__ import generators import py -from py.__impl__.test.tool import optparse +from py.__.test.tool import optparse defaultconfig = py.magic.autopath().dirpath('defaultconftest.py') dummy = object() Modified: py/dist/py/test/session.py ============================================================================== --- py/dist/py/test/session.py (original) +++ py/dist/py/test/session.py Tue Apr 26 11:22:20 2005 @@ -1,5 +1,5 @@ import py -from py.__impl__.test.tool.outerrcapture import SimpleOutErrCapture +from py.__.test.tool.outerrcapture import SimpleOutErrCapture class Session(object): """ @@ -115,7 +115,7 @@ def _map2colitems(items): # first convert all path objects into collectors - from py.__impl__.test.collect import getfscollector + from py.__.test.collect import getfscollector colitems = [] for item in items: if isinstance(item, (list, tuple)): Modified: py/dist/py/test/terminal/out.py ============================================================================== --- py/dist/py/test/terminal/out.py (original) +++ py/dist/py/test/terminal/out.py Tue Apr 26 11:22:20 2005 @@ -3,7 +3,7 @@ import os import py -from py.__impl__.execnet.channel import Channel +from py.__.execnet.channel import Channel class Out(object): tty = False Modified: py/dist/py/test/terminal/remote.py ============================================================================== --- py/dist/py/test/terminal/remote.py (original) +++ py/dist/py/test/terminal/remote.py Tue Apr 26 11:22:20 2005 @@ -1,6 +1,6 @@ from __future__ import generators import py -from py.__impl__.test.terminal.out import getout +from py.__.test.terminal.out import getout import sys def checkpyfilechange(rootdir, statcache={}): @@ -91,7 +91,7 @@ def failure_master(executable, out, args, failures): gw = py.execnet.PopenGateway(executable) channel = gw.remote_exec(""" - from py.__impl__.test.terminal.remote import failure_slave + from py.__.test.terminal.remote import failure_slave failure_slave(channel) """, stdout=out, stderr=out) channel.send((args, failures)) Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Tue Apr 26 11:22:20 2005 @@ -2,7 +2,7 @@ from time import time as now Item = py.test.Item -from py.__impl__.test.terminal.out import getout +from py.__.test.terminal.out import getout class TerminalSession(py.test.Session): def __init__(self, config, file=None): @@ -16,7 +16,7 @@ def main(self, args): if self.config.option._remote: - from py.__impl__.test.terminal import remote + from py.__.test.terminal import remote return remote.main(self.config, self._file, self.config._origargs) else: return super(TerminalSession, self).main(args) 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 Tue Apr 26 11:22:20 2005 @@ -182,7 +182,7 @@ assert 23 == 23 """) - from py.__impl__.test.collect import getfscollector + from py.__.test.collect import getfscollector for x in (o, checkfile, checkfile.dirpath()): #print "checking that %s returns custom items" % (x,) col = getfscollector(x) Modified: py/dist/py/test/testing/test_config.py ============================================================================== --- py/dist/py/test/testing/test_config.py (original) +++ py/dist/py/test/testing/test_config.py Tue Apr 26 11:22:20 2005 @@ -1,6 +1,6 @@ from __future__ import generators import py -from py.__impl__.test import config +from py.__.test import config class MyClass: def getoptions(self): @@ -46,7 +46,7 @@ old.chdir() #def test_config_order(): -# from py.__impl__.test import config +# from py.__.test import config # o = py.test.ensuretemp('configorder') # o.ensure('conftest.py').write('x=1 ; import py ; py._x = [x]') # o.ensure('a/conftest.py').write('x=2 ; import py ; py._x.append(x)') @@ -62,7 +62,7 @@ # assert py._x == [1,2,3] # #def test_getconfigvalue(): -# from py.__impl__.test import config +# from py.__.test import config # cfg = config.Config() # o = py.test.ensuretemp('configtest') # o.ensure('conftest.py').write('x=1') 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 Tue Apr 26 11:22:20 2005 @@ -191,7 +191,7 @@ item, outcome = l[0] assert str(outcome.excinfo).find('does_not_work') != -1 -from py.__impl__.test.terminal.remote import getrootdir +from py.__.test.terminal.remote import getrootdir class TestRemote: def test_rootdir_is_package(self): d = tmpdir.ensure('rootdirtest1', dir=1) Modified: py/dist/py/test/tkinter/backend.py ============================================================================== --- py/dist/py/test/tkinter/backend.py (original) +++ py/dist/py/test/tkinter/backend.py Tue Apr 26 11:22:20 2005 @@ -120,7 +120,7 @@ self.channel = self.gateway.newchannel(receiver = self.queue.put) self.gateway.remote_exec(channel = self.channel, source = ''' import py - from py.__impl__.test.tkinter.backend import remote + from py.__.test.tkinter.backend import remote args, tests = channel.receive() remote(channel, tests = tests, args = args) @@ -129,8 +129,8 @@ def remote(channel, tests = [], args = []): import py - from py.__impl__.test.tkinter.guisession import GuiSession - from py.__impl__.test.terminal.remote import getfailureitems + from py.__.test.tkinter.guisession import GuiSession + from py.__.test.terminal.remote import getfailureitems config, testfiles = py.test.Config.parse(args) if tests: Modified: py/dist/py/test/tkinter/guisession.py ============================================================================== --- py/dist/py/test/tkinter/guisession.py (original) +++ py/dist/py/test/tkinter/guisession.py Tue Apr 26 11:22:20 2005 @@ -1,7 +1,7 @@ '''GuiSession builds TestReport instances and sends them to tkgui.Manager''' import py from util import TestReport2 as TestReport -from py.__impl__.test.session import Exit, SimpleOutErrCapture +from py.__.test.session import Exit, SimpleOutErrCapture class GuiSession(py.test.Session): Modified: py/dist/py/test/tkinter/testing/test_backend.py ============================================================================== --- py/dist/py/test/tkinter/testing/test_backend.py (original) +++ py/dist/py/test/tkinter/testing/test_backend.py Tue Apr 26 11:22:20 2005 @@ -1,5 +1,5 @@ import py -from py.__impl__.test.tkinter import backend +from py.__.test.tkinter import backend RepositoryBackend = backend.RepositoryBackend TestRepository = backend.TestRepository Modified: py/dist/py/test/tkinter/testing/test_guisession.py ============================================================================== --- py/dist/py/test/tkinter/testing/test_guisession.py (original) +++ py/dist/py/test/tkinter/testing/test_guisession.py Tue Apr 26 11:22:20 2005 @@ -1,7 +1,7 @@ import py -from py.__impl__.test.tkinter import guisession -from py.__impl__.test.tkinter.util import Status, TestReport, Null +from py.__.test.tkinter import guisession +from py.__.test.tkinter.util import Status, TestReport, Null GuiSession = guisession.GuiSession class TestGuiSession: Modified: py/dist/py/test/tkinter/testing/test_repository.py ============================================================================== --- py/dist/py/test/tkinter/testing/test_repository.py (original) +++ py/dist/py/test/tkinter/testing/test_repository.py Tue Apr 26 11:22:20 2005 @@ -1,5 +1,5 @@ -from py.__impl__.test.tkinter.repository import Repository, OrderedDict, OrderedDictMemo +from py.__.test.tkinter.repository import Repository, OrderedDict, OrderedDictMemo import py Item = py.test.Item Modified: py/dist/py/test/tkinter/testing/test_util.py ============================================================================== --- py/dist/py/test/tkinter/testing/test_util.py (original) +++ py/dist/py/test/tkinter/testing/test_util.py Tue Apr 26 11:22:20 2005 @@ -1,6 +1,6 @@ from __future__ import generators -from py.__impl__.test.tkinter import util -from py.__impl__.test.tkinter.util import Status, TestReport, OutBuffer +from py.__.test.tkinter import util +from py.__.test.tkinter.util import Status, TestReport, OutBuffer import py Item = py.test.Item Modified: py/dist/py/test/tkinter/tkgui.py ============================================================================== --- py/dist/py/test/tkinter/tkgui.py (original) +++ py/dist/py/test/tkinter/tkgui.py Tue Apr 26 11:22:20 2005 @@ -1,6 +1,6 @@ import py -from py.__impl__.test.tkinter import backend -from py.__impl__.test.tkinter import util +from py.__.test.tkinter import backend +from py.__.test.tkinter import util Null = util.Null import ScrolledText Modified: py/dist/py/test/tkinter/util.py ============================================================================== --- py/dist/py/test/tkinter/util.py (original) +++ py/dist/py/test/tkinter/util.py Tue Apr 26 11:22:20 2005 @@ -2,7 +2,7 @@ import sys import py -from py.__impl__.test.terminal import out +from py.__.test.terminal import out class Null: """ Null objects always and reliably "do nothing." """ @@ -90,7 +90,7 @@ return not self.__eq__(other) class OutBuffer(out.Out): - '''Simple MockObject for py.__impl__.test.report.text.out.Out. + '''Simple MockObject for py.__.test.report.text.out.Out. Used to get the output of TerminalSession.''' def __init__(self, fullwidth = 80 -1): self.output = [] Modified: py/dist/py/test/tool/optparse.py ============================================================================== --- py/dist/py/test/tool/optparse.py (original) +++ py/dist/py/test/tool/optparse.py Tue Apr 26 11:22:20 2005 @@ -69,7 +69,7 @@ import sys, os import types -from py.__impl__.test.tool import textwrap +from py.__.test.tool import textwrap class OptParseError (Exception): def __init__ (self, msg): Modified: py/dist/py/test/tool/testing/test_outerrcapture.py ============================================================================== --- py/dist/py/test/tool/testing/test_outerrcapture.py (original) +++ py/dist/py/test/tool/testing/test_outerrcapture.py Tue Apr 26 11:22:20 2005 @@ -1,6 +1,6 @@ import sys import py -from py.__impl__.test.tool.outerrcapture import SimpleOutErrCapture +from py.__.test.tool.outerrcapture import SimpleOutErrCapture def test_capturing_simple(): cap = SimpleOutErrCapture() Modified: py/dist/py/xmlobj/html.py ============================================================================== --- py/dist/py/xmlobj/html.py (original) +++ py/dist/py/xmlobj/html.py Tue Apr 26 11:22:20 2005 @@ -3,7 +3,7 @@ """ from py.xml import Namespace, Tag -from py.__impl__.xmlobj.visit import SimpleUnicodeVisitor +from py.__.xmlobj.visit import SimpleUnicodeVisitor class HtmlVisitor(SimpleUnicodeVisitor): def repr_attribute(self, attrs, name): Modified: py/dist/py/xmlobj/xml.py ============================================================================== --- py/dist/py/xmlobj/xml.py (original) +++ py/dist/py/xmlobj/xml.py Tue Apr 26 11:22:20 2005 @@ -15,7 +15,7 @@ return self.unicode(indent=0) def unicode(self, indent=2): - from py.__impl__.xmlobj.visit import SimpleUnicodeVisitor + from py.__.xmlobj.visit import SimpleUnicodeVisitor l = [] SimpleUnicodeVisitor(l.append, indent).visit(self) return u"".join(l) From jan at codespeak.net Tue Apr 26 11:45:22 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Tue, 26 Apr 2005 11:45:22 +0200 (CEST) Subject: [py-svn] r11448 - py/dist/py/test/tkinter Message-ID: <20050426094522.F3B2C27B97@code1.codespeak.net> Author: jan Date: Tue Apr 26 11:45:22 2005 New Revision: 11448 Modified: py/dist/py/test/tkinter/backend.py Log: kill remote process when finished, don't wait for the garbage collector to do that Modified: py/dist/py/test/tkinter/backend.py ============================================================================== --- py/dist/py/test/tkinter/backend.py (original) +++ py/dist/py/test/tkinter/backend.py Tue Apr 26 11:45:22 2005 @@ -2,6 +2,7 @@ import py import repository import util +import threading Null = util.Null @@ -68,6 +69,7 @@ def __init__(self, config = Null()): self.testrepository = TestRepository() self.channel = Null() + self.waitfinish_thread = Null() self.queue = py.std.Queue.Queue() self._message_callback = Null() self._messages_callback = Null() @@ -83,7 +85,8 @@ def shutdown(self): if self.running: self.channel.close() - self.channel.gateway.exit() + if self.waitfinish_thread.isAlive(): + self.waitfinish_thread.join() def get_repository(self): '''return the repository''' @@ -109,7 +112,7 @@ except py.std.Queue.Empty: pass self._messages_callback(changed_report_ids) - + def start_tests(self, config = None, args = [], tests = []): if self.running: return @@ -126,6 +129,27 @@ remote(channel, tests = tests, args = args) ''') self.channel.send((args, tests)) + self.waitfinish_thread = threading.Thread(target = self.waitfinish, + args = [self.channel]) + self.waitfinish_thread.start() + + def waitfinish(self, channel): + try: + while 1: + try: + channel.waitclose(1) + except (IOError, py.error.Error): + continue + break + finally: + try: + channel.gateway.exit() + except EOFError: + # the gateway receiver callback will get woken up + # and see an EOFError exception + pass + + def remote(channel, tests = [], args = []): import py From hpk at codespeak.net Tue Apr 26 12:08:58 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 26 Apr 2005 12:08:58 +0200 (CEST) Subject: [py-svn] r11451 - py/dist/py/documentation Message-ID: <20050426100858.91BF927B9C@code1.codespeak.net> Author: hpk Date: Tue Apr 26 12:08:58 2005 New Revision: 11451 Modified: py/dist/py/documentation/conftest.py Log: quick fix to make --collectonly work nicely on the custom checker. Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Tue Apr 26 12:08:58 2005 @@ -46,10 +46,14 @@ return super(DocDirectory, self).join(name) p = self.fspath.join(name) if p.check(file=1): - return DocChecker(p, parent=self) + return ReSTChecker(p, parent=self) Directory = DocDirectory -class DocChecker(py.test.collect.Module): +class ReSTChecker(py.test.collect.Module): + + def __repr__(self): + return py.test.collect.Collector.__repr__(self) + def setup(self): pass def teardown(self): From jan at codespeak.net Tue Apr 26 12:16:10 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Tue, 26 Apr 2005 12:16:10 +0200 (CEST) Subject: [py-svn] r11452 - py/dist/py/test/tkinter Message-ID: <20050426101610.3843327B9C@code1.codespeak.net> Author: jan Date: Tue Apr 26 12:16:10 2005 New Revision: 11452 Modified: py/dist/py/test/tkinter/backend.py Log: fixed args of thread Modified: py/dist/py/test/tkinter/backend.py ============================================================================== --- py/dist/py/test/tkinter/backend.py (original) +++ py/dist/py/test/tkinter/backend.py Tue Apr 26 12:16:10 2005 @@ -64,6 +64,29 @@ self.add_report(report) return report.full_id[:] + +class ReportStore: + ReportClass = util.TestReport2 + + def __init__(self): + self._reports = [] + + def add(self, report): + self._reports.append(report) + + def get(self, **kwargs): + filter_dict = {'failed': self._select_failed } + selected_reports = [] + for name, function in filter_dict.items(): + if kwargs.has_key(name) and kwargs[name] == True: + selected_reports.extend([report for report in self._reports + if function(report)]) + return selected_reports + + def _select_failed(self, report): + return report.status == self.ReportClass.Status.Failed() + + class RepositoryBackend: def __init__(self, config = Null()): @@ -129,25 +152,24 @@ remote(channel, tests = tests, args = args) ''') self.channel.send((args, tests)) - self.waitfinish_thread = threading.Thread(target = self.waitfinish, - args = [self.channel]) + self.waitfinish_thread = threading.Thread(target = waitfinish, args = (self.channel,)) self.waitfinish_thread.start() - def waitfinish(self, channel): - try: - while 1: - try: - channel.waitclose(1) - except (IOError, py.error.Error): - continue - break - finally: +def waitfinish(channel): + try: + while 1: try: - channel.gateway.exit() - except EOFError: - # the gateway receiver callback will get woken up - # and see an EOFError exception - pass + channel.waitclose(1) + except (IOError, py.error.Error): + continue + break + finally: + try: + channel.gateway.exit() + except EOFError: + # the gateway receiver callback will get woken up + # and see an EOFError exception + pass From hpk at codespeak.net Tue Apr 26 12:21:46 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 26 Apr 2005 12:21:46 +0200 (CEST) Subject: [py-svn] r11453 - py/dist/py/documentation Message-ID: <20050426102146.B2C0F27B9C@code1.codespeak.net> Author: hpk Date: Tue Apr 26 12:21:46 2005 New Revision: 11453 Modified: py/dist/py/documentation/conftest.py Log: another rename to have nicer ReST related names Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Tue Apr 26 12:21:46 2005 @@ -62,15 +62,14 @@ return [self.fspath.basename, 'checklinks'] def join(self, name): if name == self.fspath.basename: - return DocumentationItem(name, parent=self) + return ReSTSyntaxTest(name, parent=self) elif name == 'checklinks': return LinkCheckerMaker(name, self) -class DocumentationItem(py.test.Item): +class ReSTSyntaxTest(py.test.Item): def run(self): mypath = self.fspath restcheck(py.path.svnwc(mypath)) - #linkcheck(mypath) class LinkCheckerMaker(py.test.collect.Collector): def run(self): From hpk at codespeak.net Tue Apr 26 12:32:28 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 26 Apr 2005 12:32:28 +0200 (CEST) Subject: [py-svn] r11455 - py/dist/py/test Message-ID: <20050426103228.67CE827B9C@code1.codespeak.net> Author: hpk Date: Tue Apr 26 12:32:28 2005 New Revision: 11455 Modified: py/dist/py/test/item.py py/dist/py/test/session.py Log: move setup/teardown to the Function test item. Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Tue Apr 26 12:32:28 2005 @@ -30,12 +30,13 @@ self.stack.append(col) class Item(py.test.collect.Collector): - state = SetupState() + pass class Function(Item): """ a Function Item is responsible for setting up and executing a Python callable test object. """ + state = SetupState() def __init__(self, name, parent, args=(), obj=_dummy): self.name = name self.parent = parent Modified: py/dist/py/test/session.py ============================================================================== --- py/dist/py/test/session.py (original) +++ py/dist/py/test/session.py Tue Apr 26 12:32:28 2005 @@ -20,7 +20,7 @@ def footer(self, colitems): """ teardown any resources we know about. """ - py.test.Item.state.teardown_all() + py.test.Function.state.teardown_all() if not self.config.option.nomagic: py.magic.revoke(assertion=1) From hpk at codespeak.net Tue Apr 26 12:59:15 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 26 Apr 2005 12:59:15 +0200 (CEST) Subject: [py-svn] r11463 - py/dist/py/bin Message-ID: <20050426105915.A11C527B9C@code1.codespeak.net> Author: hpk Date: Tue Apr 26 12:59:15 2005 New Revision: 11463 Modified: py/dist/py/bin/py.rest Log: fix occurence of __impl__ Modified: py/dist/py/bin/py.rest ============================================================================== --- py/dist/py/bin/py.rest (original) +++ py/dist/py/bin/py.rest Tue Apr 26 12:59:15 2005 @@ -12,7 +12,7 @@ import os, sys from _findpy import py -from py.__impl__.misc import rest +from py.__.misc import rest if __name__=='__main__': basedir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))) From hpk at codespeak.net Tue Apr 26 13:03:38 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 26 Apr 2005 13:03:38 +0200 (CEST) Subject: [py-svn] r11465 - py/dist/py/documentation Message-ID: <20050426110338.8C52627B9C@code1.codespeak.net> Author: hpk Date: Tue Apr 26 13:03:38 2005 New Revision: 11465 Modified: py/dist/py/documentation/test.txt py/dist/py/documentation/xml.txt Log: bring test documentation somewhat up to date Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Tue Apr 26 13:03:38 2005 @@ -214,7 +214,7 @@ ``py.test`` there are three scopes for which you can provide hooks to manage such state. Again, ``py.test`` will detect these hooks in modules on a name basis. The following module-level hooks will -automatically be called by the driver:: +automatically be called by the session:: def setup_module(module): """ setup up any state specific to the execution @@ -256,10 +256,10 @@ All setup/teardown methods are optional. You could have a ``setup_module`` but no ``teardown_module`` and the other way round. -Note that while the test driver guarantees that for every ``setup`` a +Note that while the test session guarantees that for every ``setup`` a corresponding ``teardown`` will be invoked (if it exists) it does *not* guarantee that any ``setup`` is called only happens once. For -example, the driver might decide to call the ``setup_module`` / +example, the session might decide to call the ``setup_module`` / ``teardown_module`` pair more than once during the execution of a test module. @@ -318,167 +318,290 @@ your setup function callable. Did we mention that lazyness is a virtue? -The three components of ``py.test`` -=================================== +Collecting and running tests / implementation remarks +====================================================== In order to customize ``py.test`` it's good to understand -its basic architure:: +its basic architure (WARNING: these are not guaranteed +yet to stay the way they are now!):: ___________________ | | | Collector | |___________________| / \ - | Item.execute(driver) + | Item.run() | ^ receive test Items / | /execute test Item | / - ___________________/ ________________ - | | send events | | - | Driver |----------------------->| Reporter | - |___________________| |________________| + ___________________/ + | | + | Session | + |___________________| - ....................... - . configuration . - . cmdline options . - ....................... + ............................. + . conftest.py configuration . + . cmdline options . + ............................. -The *Driver* basically receives test *Items* from a *Collector*, -executes them via the ``Item.execute()`` method, and takes the -outcome (possibly an exception) and sends it over to the -*reporter* instance. +The *Session* basically receives test *Items* from a *Collector*, +and executes them via the ``Item.run()`` method. It monitors +the outcome of the test and reports about failures and successes. .. _`collection process`: Collectors and the test collection process ------------------------------------------ -The collecting process is iterative, i.e. the driver -get test Items one by one and can thus start immediately -to execute the first collected test Item. At the same time, -the collectors are completly shielded from any driving, -execution or reporting details. - -The ``driver`` invokes the iteration protocol, i.e. the -``__iter__`` method of Collectors. These methods yield more (sub) -*Collectors* or test *Items*. They should usually not raise -exceptions but yield back a specific CollectError. This is to -avoid that a collecting error breaks the whole collection -chain. It is at the drivers discretion to react to errors -from collectors. - -The default DirectoryCollector collects test files that -match the glob patterns ``test_*.py`` or ``*_test.py`` and it -recurses into any directory that doesn't start with a leading -dot (e.g. ``.svn`` direcotries is ignored). - -Another ``PyCollector`` then recurses into the test files and -collects functions and methods that have a leading ``test_``. -By default, methods are only collected if their class starts -with ``Test``. - -Drivers bind it all together ----------------------------- - -Drivers serve as the glue code between the various parts of -the interacting ``py.test`` objects, more specifically -they: - -- iterate over Collectors, which yield more Collectors or Items. - -- call the ``Item.execute()`` method in order to execute - a test - -- send "result" and other events to the reporter so that - the users gets informed about progress and problems - in possibly custom ways. - -.. _reporter: - -Reporters process test events ------------------------------ - -A driver typically invokes certain methods of a Reporter -for various points in the testing process. A reporter -is reponsible for representing the testing process -to the user or other programs. These are the events -(function calls) that the reporter receives:: - - start(), end() are invoked only from the outmost driver - to allow a reporter to write headers and - footers - - open(collector) is called when a collector is about - to be iterated. This method should return - a "close" method (or None) which will be - called when the collector is finished iterating. - - startitem(item) is called before executing a single test item - enditem(result) is called when execution of a single test item - finished. The result.item attribute points back - to the Test item the result object belongs to. +The collecting process is iterative, i.e. the session +traverses the *collector tree*. Here is an example of such +a tree, generated with the command ``py.test --collectonly py/xmlobj``:: + + + + + + + + + + + + + + + + + +By default all directories not starting with a dot are traversed, +looking for ``test_*.py`` and ``*_test.py`` files. Those files +are imported under their `package name`_. + +.. _`collector API`: + +collector API invoked by sessions +--------------------------------- + +Apart from initialization the default session object invokes +a very uniform API on collectors and test items: + +*colitem.run()* + + returns a list of names available from this collector. + You can return an empty list. Callers of this method + must take care to catch exceptions properly. The session + object guards its calls to ``colitem.run()`` in its + ``session.runtraced(colitem)`` method, including + catching of stdout. + +*colitem.join(name)* + + return a child item from the given name. Usually the + session feeds the join method with each name obtained + from ``colitem.run()``. If the return value is None + it means the ``colitem`` was not able to resolve + with the given name. + +*colitem.parent* + + attribute pointing to the parent collector. + +*colitem.name* + + name of this sub item. This is the name that is + passed to ``colitem.join()`` above. + +test items are collectors as well +--------------------------------- + +To make the reporting life simple for the session object +items offer a ``run()`` method as well. In fact the session +distinguishes "collectors" from "items" solely by interpreting +their return value. If it is a list, then we recurse into +it, otherwise we consider the "test" as passed. + +.. _`package name`: + +constructing the package name for modules +----------------------------------------- + +Test modules are imported under their fully qualified +name. Given a module ``path`` the fully qualified package +name is constructed as follows: + +* determine the last "upward" directory that contains + an ``__init__.py`` file. + +* insert this base dir into sys.path as its first item + +* import the root package -Customizing the py.test process +* determine the fully qualified name for the module located + at ``path`` ... + + * if the imported root package has a __package__ object + call ``getimportname(path)`` + + * otherwise use the relative path of the module path to + the base dir and turn slashes into dots. + +The Module collector will eventually issue a +``__import__(mod_fqdnname, ...)`` to finally get to +the live module object. + + +Module Collector +----------------- + +The default Module collector looks for test functions +and test classes/methods. Test functions and methods +are prefixed ``test`` by default. Test classes must +start with a capitalized ``Test``. + +Reporting hooks of the session object +------------------------------------- + +Part of the default session API deals with reporting +test outcomes and collection details. These methods +are reponsible for representing the testing process +to the user or other programs:: + +** session.header() and **session.footer** + + usually invoked once for a whole test session run. + +** session.start(colitem) ** + + invoked before the ``colitem.run()`` is invoked + +** session.finish(colitem, outcome) ** + + invoked after a colitem is run. + +PLEASE NOTE that these methods are likely to change soon +because the session object now has too many names that aren't +easily distinguishable regarding their purposes. +It is also likely that collectors/test items will +become more self-responsible for presenting outcomes +in textual ways. Currently session object have to +know too much about the representation of +failures/successes to the user which makes it +harder than neccessary to write custom test items. + +Customizing the testing process =============================== + +customizing the collecting and running process +----------------------------------------------- + +To introduce different test items you can create +one or more ``conftest.py`` files in your project. +When the collection process traverses directories +and modules the default collectors will produce +custom Collectors and Items if they are found +in a local ``conftest.py`` file. + +example: perform additional ReST checs +++++++++++++++++++++++++++++++++++++++ + +With your custom collectors or items you can completely +derive from the standard way of collecting and running +tests in a localized manner. Let's look at an example. +If you invoke ``py.test --collectonly py/documentation`` +then you get:: + + + + + + + + + + + + + + + + + ... + +In ``py/documentation/conftest.py`` you find the following +customization:: + + class DocDirectory(py.test.collect.Directory): + + def run(self): + results = super(DocDirectory, self).run() + for x in self.fspath.listdir('*.txt', sort=True): + results.append(x.basename) + return results + + def join(self, name): + if not name.endswith('.txt'): + return super(DocDirectory, self).join(name) + p = self.fspath.join(name) + if p.check(file=1): + return ReSTChecker(p, parent=self) + + Directory = DocDirectory + +The existence of the 'Directory' name in the +``pypy/documentation/conftest.py`` module makes the collection +process defer to our custom "DocDirectory" collector. We extend +the set of collected test items by ``ReSTChecker`` instances +which themselves create ``ReSTSyntaxTest`` and ``LinkCheckerMaker`` +items. All of this instances (need to) follow the `collector API`_. + Customizing the collection process in a module ---------------------------------------------- -*Warning: details of the collection process are subject to refactorings -and thus details may change. If you are customizing py.test at -"Item" level then you definitely want to be subscribed to -the `py-dev mailing list`_.* +*REPEATED WARNING: details of the collection and running process are +still subject to refactorings and thus details will change. +If you are customizing py.test at "Item" level then you +definitely want to be subscribed to the `py-dev mailing list`_ +to follow ongoing development.* If you have a module where you want to take responsibility for collecting your own test Items and possibly even for executing a test then you can provide `generative tests`_ that yield callables and possibly arguments as a tuple. This should -serve most immediate purposes. +serve some immediate purposes like paramtrized tests. -Another extension possibility goes deeper into the machinery +The other extension possibility goes deeper into the machinery and allows you to specify a custom test ``Item`` class which is responsible for setting up and executing an underlying -test. You can integrate your custom ``py.test.Item`` subclass -by putting an ``Item`` binding on a test class. Or you can +test. [XXX not working: You can integrate your custom ``py.test.Item`` subclass +by binding an ``Item`` name to a test class.] Or you can take over larger parts of the collection process by putting Items in a ``conftest.py`` configuration file. -The collection process constantly looks at such configuration files -to determine appropriate collectors at ``Directory``, ``Module`` -or ``Class`` level by looking for these very names. - -When providing ``Items`` you may have to deal with ``py.path.extpy()`` -paths. In general, an extpy-path allows to address a python object -on a filesystem. *Addressability of test Items* is a major concern -because we want to memorize failing tests across py.test invocations. -The extpy-path is also walked in order to setup/teardown resources. -A ``py.path.extpy()`` has two parts, a filesystem path pointing to -a module and a dotted path leading to the python object. - -Customizing execution of Items ------------------------------- - -- Items allow total control of executing their contained test - method. ``iteminstance.execute()`` will get called by the - driver in order to actually execute a test. Thus a custom - ``execute()`` method can pass arguments to test functions. - -- Item.execute() methods can provide custom paramters +The collection process constantly looks at according names +in the *chain of conftest.py* modules to determine collectors +and items at ``Directory``, ``Module``, ``Class``, ``Function`` +or ``Generator`` level. Note that except for ``Function`` +all classes are pure collectors, i.e. will return a list +of names (possibly empty). + +Customizing execution of Functions +---------------------------------- + +- Function test items allow total control of executing their + contained test method. ``function.run()`` will get called by the + session in order to actually run a test with proper setup/teardown. + +- ``Function.execute(target, *args) methods are invoked by + the default ``Function.run()`` to actually execute a pytho + function with the given (usually empty set of) arguments. (a db-connection, some initialized application entity, whatever) to the actual underlying method being invoked. -- by providing custom collectors you can easily produce - your own Item instances - -- lift some of the arbitrary restrictions of unittest.py: - allow plain test functions (without being in a class) and - allow classes to simply mean "grouping" of tests. - .. _`getting started`: getting-started.html .. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev - Future/Planned Features of py.test ================================== Modified: py/dist/py/documentation/xml.txt ============================================================================== --- py/dist/py/documentation/xml.txt (original) +++ py/dist/py/documentation/xml.txt Tue Apr 26 13:03:38 2005 @@ -17,7 +17,6 @@ itself and especially its API in html or xml. .. _xist: http://www.livinglogic.de/Python/xist/index.html -.. _`Reporter`: test.html#reporter .. _`exchange data`: execnet.html#exchange-data a pythonic object model , please From hpk at codespeak.net Tue Apr 26 13:09:35 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 26 Apr 2005 13:09:35 +0200 (CEST) Subject: [py-svn] r11466 - py/dist/py/documentation Message-ID: <20050426110935.EF3B127B9C@code1.codespeak.net> Author: hpk Date: Tue Apr 26 13:09:35 2005 New Revision: 11466 Modified: py/dist/py/documentation/test.txt Log: refine test documentation and fix ReST Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Tue Apr 26 13:09:35 2005 @@ -431,10 +431,13 @@ name. Given a module ``path`` the fully qualified package name is constructed as follows: -* determine the last "upward" directory that contains - an ``__init__.py`` file. +* determine the last "upward" directory from ``path`` that + contains an ``__init__.py`` file. Going upwards + means repeatedly calling the ``dirpath()`` method + on a path object (which returns the parent directory + as a path object). -* insert this base dir into sys.path as its first item +* insert this base directory into sys.path as its first item * import the root package @@ -442,23 +445,25 @@ at ``path`` ... * if the imported root package has a __package__ object - call ``getimportname(path)`` + then call its ``getimportname(path)`` * otherwise use the relative path of the module path to the base dir and turn slashes into dots. -The Module collector will eventually issue a +The Module collector will eventually trigger ``__import__(mod_fqdnname, ...)`` to finally get to -the live module object. +the live module object. +Side note: this whole logic is performed by local path +object's ``pyimport()`` method. Module Collector ----------------- The default Module collector looks for test functions -and test classes/methods. Test functions and methods +and test classes and methods. Test functions and methods are prefixed ``test`` by default. Test classes must -start with a capitalized ``Test``. +start with a capitalized ``Test`` prefix. Reporting hooks of the session object ------------------------------------- @@ -466,9 +471,9 @@ Part of the default session API deals with reporting test outcomes and collection details. These methods are reponsible for representing the testing process -to the user or other programs:: +to the user or other programs: -** session.header() and **session.footer** +** session.header() and session.footer** usually invoked once for a whole test session run. From hpk at codespeak.net Tue Apr 26 13:13:14 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 26 Apr 2005 13:13:14 +0200 (CEST) Subject: [py-svn] r11468 - py/dist/py/documentation Message-ID: <20050426111314.552A527B9C@code1.codespeak.net> Author: hpk Date: Tue Apr 26 13:13:14 2005 New Revision: 11468 Modified: py/dist/py/documentation/test.txt Log: more fixes and refinements Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Tue Apr 26 13:13:14 2005 @@ -598,11 +598,9 @@ contained test method. ``function.run()`` will get called by the session in order to actually run a test with proper setup/teardown. -- ``Function.execute(target, *args) methods are invoked by - the default ``Function.run()`` to actually execute a pytho +- ``Function.execute(target, *args)`` methods are invoked by + the default ``Function.run()`` to actually execute a python function with the given (usually empty set of) arguments. - (a db-connection, some initialized application entity, whatever) - to the actual underlying method being invoked. .. _`getting started`: getting-started.html .. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev From hpk at codespeak.net Tue Apr 26 13:30:05 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 26 Apr 2005 13:30:05 +0200 (CEST) Subject: [py-svn] r11470 - py/dist/py/documentation Message-ID: <20050426113005.DB83E27B9C@code1.codespeak.net> Author: hpk Date: Tue Apr 26 13:30:05 2005 New Revision: 11470 Modified: py/dist/py/documentation/getting-started.txt py/dist/py/documentation/index.txt Log: slight reordering, making py-dev a link ... Modified: py/dist/py/documentation/getting-started.txt ============================================================================== --- py/dist/py/documentation/getting-started.txt (original) +++ py/dist/py/documentation/getting-started.txt Tue Apr 26 13:30:05 2005 @@ -74,6 +74,20 @@ Participating in development ============================ +The py-dev and py-svn mailing lists +----------------------------------- + +If you feel the desire to help tackle bugs and fixes, +or support resolution of some `frustrations`_ or to +just lurk in then please subscribe to one or both +of our mailinglists: + + `py-dev developers list`_ + +and our + + `py-svn general commit mailing list`_ + Coding and communication ------------------------ @@ -93,29 +107,14 @@ .. _`get an account`: .. _future: future.html -The py-dev and py-svn mailing lists ------------------------------------ - -If you feel the desire to help tackle bugs and fixes, -or support resolution of some `frustrations`_ or to -just lurk in then please subscribe to one or both -of our mailinglists: - - `py-dev developers list`_ - -and our - - `py-svn general commit mailing list`_ - get an account on codespeak --------------------------- -codespeak_ is generally deploying a very liberal committing -scheme. If you know someone who is active on codespeak already -or you are otherwise known in the community then you will most -probably just get access. But even if you are new to the -python developer community you may still get one if you -want to improve things and can be expected to honour the +codespeak_ is employing a pretty liberal committing scheme. If you know +someone who is active on codespeak already or you are otherwise known in +the community then you will most probably just get access. But even if +you are new to the python developer community you may still get one if +you want to improve things and can be expected to honour the style of coding and communication. .. _`coding style`: coding-style.html Modified: py/dist/py/documentation/index.txt ============================================================================== --- py/dist/py/documentation/index.txt (original) +++ py/dist/py/documentation/index.txt Tue Apr 26 13:30:05 2005 @@ -14,7 +14,7 @@ `getting started`_ quick start for using py.test and the py lib. - **py-dev at codespeak net** the development mailing list + `py-dev at codespeak net`_ the development mailing list **#pylib on irc.freenode.net** (upcoming) development IRC channel @@ -41,6 +41,7 @@ .. _`getting started`: getting-started.html +.. _`py-dev at codespeak net`: http://codespeak.net/mailman/listinfo/py-dev .. _`py.execnet`: execnet.html .. _`py.magic.greenlet`: greenlet.html .. _`py.test`: test.html From hpk at codespeak.net Tue Apr 26 23:50:13 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 26 Apr 2005 23:50:13 +0200 (CEST) Subject: [py-svn] r11494 - py/dist/py/documentation Message-ID: <20050426215013.B88C727B4B@code1.codespeak.net> Author: hpk Date: Tue Apr 26 23:50:13 2005 New Revision: 11494 Modified: py/dist/py/documentation/test.txt Log: fixed ReST and some phrasings Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Tue Apr 26 23:50:13 2005 @@ -473,32 +473,40 @@ are reponsible for representing the testing process to the user or other programs: -** session.header() and session.footer** +*session.header()* - usually invoked once for a whole test session run. + invoked once by ``session.run()`` before the whole + test session starts. -** session.start(colitem) ** - invoked before the ``colitem.run()`` is invoked +*session.footer()* -** session.finish(colitem, outcome) ** + invoked once by ``session.run()`` after the collection + and running process finished. - invoked after a colitem is run. -PLEASE NOTE that these methods are likely to change soon -because the session object now has too many names that aren't -easily distinguishable regarding their purposes. -It is also likely that collectors/test items will -become more self-responsible for presenting outcomes -in textual ways. Currently session object have to -know too much about the representation of -failures/successes to the user which makes it -harder than neccessary to write custom test items. +*session.start(colitem)* + + invoked before for each ``colitem.run()`` invocation + + +*session.finish(colitem, outcome)* + + invoked after each ``colitem.run()`` invocation + + +XXX the names of these session objects are likely to change +soon because the session object now has too many names that +aren't easily distinguishable regarding their purposes. It is +also likely that collectors/test items will become more +self-responsible for presenting outcomes in textual ways. +Currently, session object have to know too much about the +representation of failures/successes to the user which makes +it harder than neccessary to write custom test items. Customizing the testing process =============================== - customizing the collecting and running process ----------------------------------------------- @@ -562,6 +570,7 @@ which themselves create ``ReSTSyntaxTest`` and ``LinkCheckerMaker`` items. All of this instances (need to) follow the `collector API`_. + Customizing the collection process in a module ---------------------------------------------- @@ -582,21 +591,25 @@ is responsible for setting up and executing an underlying test. [XXX not working: You can integrate your custom ``py.test.Item`` subclass by binding an ``Item`` name to a test class.] Or you can -take over larger parts of the collection process by putting -Items in a ``conftest.py`` configuration file. +extend the collection process for a whole directory tree +by putting Items in a ``conftest.py`` configuration file. The collection process constantly looks at according names in the *chain of conftest.py* modules to determine collectors and items at ``Directory``, ``Module``, ``Class``, ``Function`` -or ``Generator`` level. Note that except for ``Function`` -all classes are pure collectors, i.e. will return a list +or ``Generator`` level. Note that, right now, except for ``Function`` +items all classes are pure collectors, i.e. will return a list of names (possibly empty). +XXX implement doctests as alternatives to ``Function`` items. + Customizing execution of Functions ---------------------------------- - Function test items allow total control of executing their contained test method. ``function.run()`` will get called by the - session in order to actually run a test with proper setup/teardown. + session in order to actually run a test. The method is responsible + for performing proper setup/teardown ("Test Fixtures") for a + Function test. - ``Function.execute(target, *args)`` methods are invoked by the default ``Function.run()`` to actually execute a python From hpk at codespeak.net Wed Apr 27 11:10:24 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 27 Apr 2005 11:10:24 +0200 (CEST) Subject: [py-svn] r11504 - in py/dist/py/test: . terminal testing Message-ID: <20050427091024.A426D27B58@code1.codespeak.net> Author: hpk Date: Wed Apr 27 11:10:24 2005 New Revision: 11504 Modified: py/dist/py/test/session.py py/dist/py/test/terminal/terminal.py py/dist/py/test/testing/test_collect.py py/dist/py/test/testing/test_session.py Log: rename to make the code more readable. We are really dealing with items and outcomes. Modified: py/dist/py/test/session.py ============================================================================== --- py/dist/py/test/session.py (original) +++ py/dist/py/test/session.py Wed Apr 27 11:10:24 2005 @@ -27,10 +27,10 @@ def start(self, colitem): pass - def finish(self, colitem, res): - self._memo.append((colitem, res)) + def finish(self, colitem, outcome): + self._memo.append((colitem, outcome)) - def getresults(self, cls): + def getitemoutcomepairs(self, cls): return [x for x in self._memo if isinstance(x[1], cls)] def warning(self, msg): @@ -54,13 +54,13 @@ pass # return [(fspath as string, [names as string])] return [(str(item.listchain()[0].fspath), item.listnames()) - for item, res in self.getresults(py.test.Item.Failed)] + for item, outcome in self.getitemoutcomepairs(py.test.Item.Failed)] def runtraced(self, colitem): if self.shouldclose(): raise Exit, "received external close signal" - res = capture = None + outcome = capture = None # XXX capturing output even for collectors? if not self.config.option.nocapture: if isinstance(colitem, py.test.Item) or not self.config.option.usepdb: @@ -72,27 +72,26 @@ try: if colitem._stickyfailure: raise colitem._stickyfailure - res = self.run(colitem) + outcome = self.run(colitem) except (KeyboardInterrupt, Exit): raise - except colitem.Outcome, res: - if not hasattr(res, 'excinfo'): - res.excinfo = py.code.ExceptionInfo() + except colitem.Outcome, outcome: + if not hasattr(outcome, 'excinfo'): + outcome.excinfo = py.code.ExceptionInfo() except: excinfo = py.code.ExceptionInfo() - res = colitem.Failed(excinfo=excinfo) - else: - assert (res is None or - isinstance(res, (list, colitem.Outcome))) + outcome = colitem.Failed(excinfo=excinfo) + assert (outcome is None or + isinstance(outcome, (list, colitem.Outcome))) finally: if capture is not None: out, err = capture.reset() try: - res.out, res.err = out, err + outcome.out, outcome.err = out, err except AttributeError: pass if needfinish: - self.finish(colitem, res) + self.finish(colitem, outcome) def run(self, colitem): if self.config.option.collectonly and isinstance(colitem, py.test.Item): Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Wed Apr 27 11:10:24 2005 @@ -59,9 +59,9 @@ location = "%s:%d" % (realpath.basename, lineno+1) self.out.rewrite("%-20s %s " % (location, colitem.getmodpath())) - def finish(self, colitem, result): + def finish(self, colitem, outcome): end = now() - super(TerminalSession, self).finish(colitem, result) + super(TerminalSession, self).finish(colitem, outcome) if self.config.option.collectonly: cols = self._opencollectors last = cols.pop() @@ -69,16 +69,16 @@ return colitem.elapsedtime = end - colitem.start if self.config.option.usepdb: - if isinstance(result, Item.Failed): + if isinstance(outcome, Item.Failed): print "dispatching to ppdb", colitem - self.repr_failure(colitem, result) + self.repr_failure(colitem, outcome) import pdb - self.out.rewrite('\n%s\n' % (result.excinfo.exconly(),)) - pdb.post_mortem(result.excinfo._excinfo[2]) - if isinstance(result, (colitem.Failed,)): + self.out.rewrite('\n%s\n' % (outcome.excinfo.exconly(),)) + pdb.post_mortem(outcome.excinfo._excinfo[2]) + if isinstance(outcome, (colitem.Failed,)): if self.config.option.exitfirst: py.test.exit("exit on first problem configured.", item=colitem) - if result is None or not isinstance(colitem, py.test.Item): + if outcome is None or not isinstance(colitem, py.test.Item): if isinstance(colitem, py.test.collect.Module) \ and self.config.option.verbose == 0 \ and colitem.numitems >= 0: @@ -86,11 +86,11 @@ return else: if self.config.option.verbose >= 1: - resultstring = self.repr_progress_long_result(colitem, result) + resultstring = self.repr_progress_long_result(colitem, outcome) resultstring += " (%.2f)" % (colitem.elapsedtime,) self.out.line(resultstring) else: - c = self.repr_progress_short_result(colitem, result) + c = self.repr_progress_short_result(colitem, outcome) self.out.write(c) # ------------------- @@ -159,19 +159,19 @@ Item.Failed: 'FAIL', } - def repr_progress_short_result(self, item, testresult): - for restype, char in self.typemap.items(): - if isinstance(testresult, restype): + def repr_progress_short_result(self, item, outcome): + for outcometype, char in self.typemap.items(): + if isinstance(outcome, outcometype): return char else: - raise TypeError, "not a result instance: %r" % testresult + raise TypeError, "not an Outomce instance: %r" % (outcome,) - def repr_progress_long_result(self, item, testresult): - for restype, char in self.namemap.items(): - if isinstance(testresult, restype): + def repr_progress_long_result(self, item, outcome): + for outcometype, char in self.namemap.items(): + if isinstance(outcome, outcometype): return char else: - raise TypeError, "not a result instance: %r" % testresult + raise TypeError, "not an Outcome instance: %r" % (outcome,) # -------------------- # summary information @@ -180,7 +180,7 @@ outlist = [] sum = 0 for typ in Item.Passed, Item.Failed, Item.Skipped: - l = self.getresults(typ) + l = self.getitemoutcomepairs(typ) if l: outlist.append('%d %s' % (len(l), typ.__name__.lower())) sum += len(l) @@ -191,33 +191,35 @@ def skippedreasons(self): texts = {} - for colitem, res in self.getresults(Item.Skipped): - tbindex = getattr(res, 'tbindex', -1) - raisingtb = res.excinfo.traceback[tbindex] + for colitem, outcome in self.getitemoutcomepairs(Item.Skipped): + tbindex = getattr(outcome, 'tbindex', -1) + raisingtb = outcome.excinfo.traceback[tbindex] fn = raisingtb.frame.code.path lineno = raisingtb.lineno - d = texts.setdefault(res.excinfo.exconly(), {}) - d[(fn,lineno)] = res + d = texts.setdefault(outcome.excinfo.exconly(), {}) + d[(fn,lineno)] = outcome if texts: self.out.line() self.out.sep('_', 'reasons for skipped tests') for text, dict in texts.items(): - for (fn, lineno), res in dict.items(): + for (fn, lineno), outcome in dict.items(): self.out.line('Skipped in %s:%d' %(fn,lineno)) self.out.line("reason: %s" % text) self.out.line() def failures(self): - l = self.getresults(Item.Failed) + l = self.getitemoutcomepairs(Item.Failed) if l: self.out.sep('_') - for colitem, res in l: - self.repr_failure(colitem, res) + for colitem, outcome in l: + self.repr_failure(colitem, outcome) - def repr_failure(self, item, res): - excinfo = res.excinfo + def repr_failure(self, item, outcome): + excinfo = outcome.excinfo traceback = excinfo.traceback + #print "repr_failures sees traceback" + #py.std.pprint.pprint(traceback) if item: self.cut_traceback(traceback, item) if not traceback: @@ -240,7 +242,7 @@ if entry == last: if item: self.repr_failure_info(item, entry) - self.repr_out_err(res) + self.repr_out_err(outcome) self.out.sep("_") else: self.out.sep("_ ") @@ -371,10 +373,10 @@ for x in lines: self.out.line(indent + x) - def repr_out_err(self, res): + def repr_out_err(self, outcome): for name in 'out', 'err': - if hasattr(res, name): - out = getattr(res, name) + if hasattr(outcome, name): + out = getattr(outcome, name) if out.strip(): self.out.sep("- ", "recorded std%s" % name) self.out.line(out.strip()) 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 Wed Apr 27 11:10:24 2005 @@ -196,7 +196,7 @@ out = py.std.cStringIO.StringIO() session = config.getsessionclass()(config, out) session.main(args) - l = session.getresults(py.test.Item.Passed) + l = session.getitemoutcomepairs(py.test.Item.Passed) assert len(l) == 2 finally: old.chdir() 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 Wed Apr 27 11:10:24 2005 @@ -9,27 +9,27 @@ config, args = py.test.Config.parse([]) session = config.getsessionclass()(config, py.std.sys.stdout) session.main([datadir / 'filetest.py']) - l = session.getresults(py.test.Item.Failed) + l = session.getitemoutcomepairs(py.test.Item.Failed) assert len(l) == 2 - l = session.getresults(py.test.Item.Passed) + l = session.getitemoutcomepairs(py.test.Item.Passed) assert not l def test_simple_verbose(self): config, args = py.test.Config.parse(['--verbose']) session = config.getsessionclass()(config, py.std.sys.stdout) session.main([datadir / 'filetest.py']) - l = session.getresults(py.test.Item.Failed) + l = session.getitemoutcomepairs(py.test.Item.Failed) assert len(l) == 2 - l = session.getresults(py.test.Item.Passed) + l = session.getitemoutcomepairs(py.test.Item.Passed) assert not l def test_simple_verbose_verbose(self): config, args = py.test.Config.parse(['-v', '-v']) session = config.getsessionclass()(config, py.std.sys.stdout) session.main([datadir / 'filetest.py']) - l = session.getresults(py.test.Item.Failed) + l = session.getitemoutcomepairs(py.test.Item.Failed) assert len(l) == 2 - l = session.getresults(py.test.Item.Passed) + l = session.getitemoutcomepairs(py.test.Item.Passed) assert not l def test_session_parsing(self): @@ -63,14 +63,14 @@ #print self.session._memo #print "out" #print out - l = self.session.getresults(py.test.Item.Failed) + l = self.session.getitemoutcomepairs(py.test.Item.Failed) assert len(l) == 2 assert out.find('2 failed') != -1 def test_syntax_error_module(self): session = self.session session.main([str(datadir / 'syntax_error.py')]) - l = session.getresults(py.test.Item.Failed) + l = session.getitemoutcomepairs(py.test.Item.Failed) assert len(l) == 1 out = self.file.getvalue() assert out.find(str('syntax_error.py')) != -1 @@ -80,9 +80,9 @@ session = self.session session.config.option.exitfirst = True session.main([str(datadir / 'filetest.py')]) - l = session.getresults(py.test.Item.Failed) + l = session.getitemoutcomepairs(py.test.Item.Failed) assert len(l) == 1 - l = session.getresults(py.test.Item.Passed) + l = session.getitemoutcomepairs(py.test.Item.Passed) assert not l def test_collectonly(self): @@ -91,7 +91,7 @@ session.main([str(datadir / 'filetest.py')]) out = self.file.getvalue() #print out - l = session.getresults(py.test.Item.Failed) + l = session.getitemoutcomepairs(py.test.Item.Failed) #if l: # x = l[0][1].excinfo # print x.exconly() @@ -162,9 +162,9 @@ session = self.session session.main([o]) - l = session.getresults(py.test.Item.Failed) + l = session.getitemoutcomepairs(py.test.Item.Failed) assert len(l) == 0 - l = session.getresults(py.test.Item.Passed) + l = session.getitemoutcomepairs(py.test.Item.Passed) assert len(l) == 7 # also test listnames() here ... item, result = l[-1] @@ -186,7 +186,7 @@ """)) session = self.session session.main([o]) - l = session.getresults(py.test.Item.Failed) + l = session.getitemoutcomepairs(py.test.Item.Failed) assert len(l) == 1 item, outcome = l[0] assert str(outcome.excinfo).find('does_not_work') != -1 From hpk at codespeak.net Wed Apr 27 12:02:20 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 27 Apr 2005 12:02:20 +0200 (CEST) Subject: [py-svn] r11506 - in py/dist/py/test: . terminal testing tkinter Message-ID: <20050427100220.74CA327B58@code1.codespeak.net> Author: hpk Date: Wed Apr 27 12:02:20 2005 New Revision: 11506 Modified: py/dist/py/test/collect.py py/dist/py/test/compat.py py/dist/py/test/item.py py/dist/py/test/raises.py py/dist/py/test/session.py py/dist/py/test/terminal/terminal.py py/dist/py/test/testing/test_raises.py py/dist/py/test/testing/test_session.py py/dist/py/test/tkinter/util.py Log: - pass more precise arguments to Outcomes (no general keyword-args anymore) - get rid of obsolete 'tbindex' argument. Since some time we just use '__tracebackhide__=True' in functions which we don't wan't by default to show up in tracebacks. (--fulltrace disables this, btw) - added raises-output test - refactored a bit the way to get at test outcomes - use the name 'outcome' instead of 'res' (not renamed in the tkinter-session, though, i leave that to Jan :-) Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Wed Apr 27 12:02:20 2005 @@ -76,19 +76,27 @@ _stickyfailure = None class Outcome: - def __init__(self, **kwargs): - assert 'msg' not in kwargs or isinstance(kwargs['msg'], str), ( - "given 'msg' argument is not a string" ) - self.__dict__.update(kwargs) + msg = None + excinfo = None + def __init__(self, msg=None, excinfo=None): + if msg is not None: + self.msg = msg + if excinfo is not None: + self.excinfo = excinfo def __repr__(self): return getattr(self, 'msg', "%s instance" % (self.__class__,)) def __str__(self): return getattr(self, 'msg', object.__repr__(self)) class Passed(Outcome): pass class Failed(Outcome): pass - class ExceptionFailure(Failed): msg = 'DID NOT RAISE' - class Skipped(Outcome): pass + class ExceptionFailure(Failed): + msg = 'DID NOT RAISE' + def __init__(self, expr, expected, msg=None, excinfo=None): + Collector.Failed.__init__(self, msg=msg, excinfo=excinfo) + self.expr = expr + self.expected = expected + class Skipped(Outcome): pass def __repr__(self): return "<%s %r>" %(self.__class__.__name__, self.name) Modified: py/dist/py/test/compat.py ============================================================================== --- py/dist/py/test/compat.py (original) +++ py/dist/py/test/compat.py Wed Apr 27 12:02:20 2005 @@ -47,8 +47,9 @@ for name in names: items.append(""" def %(name)s(self, %(sig)s): + __tracebackhide__ = True if %(expr)s: - raise py.test.Item.Failed(tbindex=-2, msg=%(sigsubst)r %% (%(sig)s)) + raise py.test.Item.Failed(msg=%(sigsubst)r %% (%(sig)s)) """ % locals() ) source = "".join(items) Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Wed Apr 27 12:02:20 2005 @@ -87,22 +87,23 @@ # # triggering specific outcomes while executing Items # -def skip(msg="unknown reason", tbindex=-2): +def skip(msg="unknown reason"): """ skip with the given Message. """ __tracebackhide__ = True - raise py.test.Item.Skipped(msg=msg, tbindex=tbindex) + raise py.test.Item.Skipped(msg=msg) def skip_on_error(func, *args, **kwargs): """ skip test if the given call fails. """ + __tracebackhide__ = True try: return func(*args, **kwargs) except Exception, e: s = py.code.ExceptionInfo().exconly() name = getattr(func, '__name__', func) - py.test.skip("%s(...) -> %s" %(name, s.rstrip(), ), tbindex=-3) + py.test.skip("%s(...) -> %s" %(name, s.rstrip(), )) def fail(msg="unknown failure"): """ fail with the given Message. """ __tracebackhide__ = True - raise py.test.Item.Failed(msg=msg, tbindex=-2) + raise py.test.Item.Failed(msg=msg) Modified: py/dist/py/test/raises.py ============================================================================== --- py/dist/py/test/raises.py (original) +++ py/dist/py/test/raises.py Wed Apr 27 12:02:20 2005 @@ -23,8 +23,7 @@ # this is destroyed here ... except ExpectedException: return py.code.ExceptionInfo() - raise ExceptionFailure(expr=expr, expected=ExpectedException, - tbindex = -2) + raise ExceptionFailure(expr=expr, expected=ExpectedException) else: func = args[0] assert callable Modified: py/dist/py/test/session.py ============================================================================== --- py/dist/py/test/session.py (original) +++ py/dist/py/test/session.py Wed Apr 27 12:02:20 2005 @@ -76,7 +76,7 @@ except (KeyboardInterrupt, Exit): raise except colitem.Outcome, outcome: - if not hasattr(outcome, 'excinfo'): + if outcome.excinfo is None: outcome.excinfo = py.code.ExceptionInfo() except: excinfo = py.code.ExceptionInfo() Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Wed Apr 27 12:02:20 2005 @@ -189,11 +189,23 @@ self.out.sep('=', 'tests finished: %s in %4.2f seconds' % (status, elapsed)) + def getlastvisible(self, sourcetraceback): + traceback = sourcetraceback[:] + while traceback: + entry = traceback.pop() + try: + x = entry.frame.eval("__tracebackhide__") + except: + x = False + if not x: + return entry + else: + return sourcetraceback[-1] + def skippedreasons(self): texts = {} for colitem, outcome in self.getitemoutcomepairs(Item.Skipped): - tbindex = getattr(outcome, 'tbindex', -1) - raisingtb = outcome.excinfo.traceback[tbindex] + raisingtb = self.getlastvisible(outcome.excinfo.traceback) fn = raisingtb.frame.code.path lineno = raisingtb.lineno d = texts.setdefault(outcome.excinfo.exconly(), {}) @@ -204,7 +216,7 @@ self.out.sep('_', 'reasons for skipped tests') for text, dict in texts.items(): for (fn, lineno), outcome in dict.items(): - self.out.line('Skipped in %s:%d' %(fn,lineno)) + self.out.line('Skipped in %s:%d' %(fn, lineno+1)) self.out.line("reason: %s" % text) self.out.line() Modified: py/dist/py/test/testing/test_raises.py ============================================================================== --- py/dist/py/test/testing/test_raises.py (original) +++ py/dist/py/test/testing/test_raises.py Wed Apr 27 12:02:20 2005 @@ -15,3 +15,4 @@ def test_raises_function(self): test.raises(ValueError, int, 'hello') + 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 Wed Apr 27 12:02:20 2005 @@ -137,6 +137,21 @@ i = out.find('TypeError') assert i != -1 + def test_raises_output(self): + o = tmpdir.ensure('raisestest', dir=1) + tfile = o.join('test_raisesoutput.py') + tfile.write(py.code.Source(""" + import py + def test_raises_doesnt(): + py.test.raises(ValueError, int, "3") + """)) + session = self.session + session.main([o]) + out = self.file.getvalue() + if not out.find("DID NOT RAISE") != -1: + print out + py.test.fail("incorrect raises() output") + def test_order_of_execution(self): o = tmpdir.ensure('ordertest', dir=1) tfile = o.join('test_orderofexecution.py') Modified: py/dist/py/test/tkinter/util.py ============================================================================== --- py/dist/py/test/tkinter/util.py (original) +++ py/dist/py/test/tkinter/util.py Wed Apr 27 12:02:20 2005 @@ -202,8 +202,7 @@ def report_skipped(self, config, item, res): texts = {} - tbindex = getattr(res, 'tbindex', -1) - raisingtb = res.excinfo.traceback[tbindex] + raisingtb = self.getlastvisible(res.excinfo.traceback) fn = raisingtb.frame.code.path lineno = raisingtb.lineno d = texts.setdefault(res.excinfo.exconly(), {}) From arigo at codespeak.net Wed Apr 27 12:06:46 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Wed, 27 Apr 2005 12:06:46 +0200 (CEST) Subject: [py-svn] r11507 - py/dist/py/documentation Message-ID: <20050427100646.B781A27B58@code1.codespeak.net> Author: arigo Date: Wed Apr 27 12:06:46 2005 New Revision: 11507 Modified: py/dist/py/documentation/greenlet.txt Log: Added the greenlet example from my ACCU lightning talk. Modified: py/dist/py/documentation/greenlet.txt ============================================================================== --- py/dist/py/documentation/greenlet.txt (original) +++ py/dist/py/documentation/greenlet.txt Wed Apr 27 12:06:46 2005 @@ -30,6 +30,75 @@ .. _`Stackless`: http://www.stackless.com .. _`test_generator.py`: http://codespeak.net/svn/user/arigo/greenlet/test_generator.py +Example +------- + +Let's consider a system controlled by a terminal-like console, where the user +types commands. Assume that the input comes character by character. In such +a system, there will typically be a loop like the following one:: + + def process_commands(*args): + while True: + line = '' + while not line.endswith('\n'): + line += read_next_char() + if line == 'quit\n': + print "are you sure?" + if read_next_char() != 'y': + continue # ignore the command + process_command(line) + +Now assume that you want to plug this program into a GUI. Most GUI toolkits +are event-based. They will invoke a call-back for each character the user +presses. [Replace "GUI" with "XML expat parser" if that rings more bells to +you ``:-)``] In this setting, it is difficult to implement the +read_next_char() function needed by the code above. We have two incompatible +functions:: + + def event_keydown(key): + ?? + + def read_next_char(): + ?? should wait for the next event_keydown() call + +You might consider doing that with threads. Greenlets are an alternate +solution that don't have the related locking and shutdown problems. You +start the process_commands() function in its own, separate greenlet, and +then you exchange the keypresses with it as follows:: + + def event_keydown(key): + # jump into g_processor, sending it the key + g_processor.switch(key) + + def read_next_char(): + # g_self is g_processor in this simple example + g_self = greenlet.getcurrent() + # jump to the parent (main) greenlet, waiting for the next key + next_char = g_self.parent.switch() + return next_char + + g_processor = greenlet(process_commands) + g_processor.switch(*args) # input arguments to process_commands() + + gui.mainloop() + +In this example, the execution flow is: when read_next_char() is called, it +is part of the g_processor greenlet, so when it switches to its parent +greenlet, it resumes execution in the top-level main loop (the GUI). When +the GUI calls event_keydown(), it switches to g_processor, which means that +the execution jumps back wherever it was suspended in that greenlet -- in +this case, to the switch() instruction in read_next_char() -- and the ``key`` +argument in event_keydown() is passed as the return value of the switch() in +read_next_char(). + +Note that read_next_char() will be suspended and resumed with its call stack +preserved, so that it will itself return to different positions in +process_commands() depending on where it was originally called from. This +allows the logic of the program to be kept in a nice control-flow way; we +don't have to completely rewrite process_commands() to turn it into a state +machine. + + Usage ===== From jan at codespeak.net Wed Apr 27 14:27:21 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Wed, 27 Apr 2005 14:27:21 +0200 (CEST) Subject: [py-svn] r11512 - in py/dist/py/thread: . testing Message-ID: <20050427122721.4DCD827B7B@code1.codespeak.net> Author: jan Date: Wed Apr 27 14:27:21 2005 New Revision: 11512 Modified: py/dist/py/thread/pool.py py/dist/py/thread/testing/test_pool.py Log: fix bug in py/thread/pool.py * WorkerPool calls WorkerThread.stop() without removing the WorkerThread from WorkerPool._ready -> an AssertionError is raised in pool.py line 56 * WorkerThread.stop adds SystemExit to the queue instead of a Reply instance -> unpacking raises an error Modified: py/dist/py/thread/pool.py ============================================================================== --- py/dist/py/thread/pool.py (original) +++ py/dist/py/thread/pool.py Wed Apr 27 14:27:21 2005 @@ -52,7 +52,9 @@ def run(self): try: while 1: - reply = self._queue.get() + reply = self._queue.get() + if reply is SystemExit: + break assert self not in self._pool._ready task = reply.task try: 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 Wed Apr 27 14:27:21 2005 @@ -70,6 +70,8 @@ pool.join(timeout=0.1) def test_pool_clean_shutdown(): + from py.__.test.tool.outerrcapture import SimpleOutErrCapture + capture = SimpleOutErrCapture() pool = WorkerPool() def f(): pass @@ -79,3 +81,7 @@ pool.join(timeout=1.0) assert not pool._alive assert not pool._ready + out, err = capture.reset() + print out + print >>sys.stderr, err + assert err == '' From jan at codespeak.net Wed Apr 27 15:18:08 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Wed, 27 Apr 2005 15:18:08 +0200 (CEST) Subject: [py-svn] r11517 - py/dist/py/test/tkinter Message-ID: <20050427131808.122B427B8E@code1.codespeak.net> Author: jan Date: Wed Apr 27 15:18:07 2005 New Revision: 11517 Modified: py/dist/py/test/tkinter/backend.py py/dist/py/test/tkinter/guisession.py py/dist/py/test/tkinter/util.py Log: use the name 'outcome' instead of 'res' remove TestReport2 Modified: py/dist/py/test/tkinter/backend.py ============================================================================== --- py/dist/py/test/tkinter/backend.py (original) +++ py/dist/py/test/tkinter/backend.py Wed Apr 27 15:18:07 2005 @@ -9,7 +9,7 @@ class TestRepository(repository.Repository): '''stores only TestReport''' disabled = True - ReportClass = util.TestReport2 + ReportClass = util.TestReport failed_id = [repr(ReportClass.Status.Failed())] skipped_id = [repr(ReportClass.Status.Skipped())] @@ -66,7 +66,7 @@ class ReportStore: - ReportClass = util.TestReport2 + ReportClass = util.TestReport def __init__(self): self._reports = [] Modified: py/dist/py/test/tkinter/guisession.py ============================================================================== --- py/dist/py/test/tkinter/guisession.py (original) +++ py/dist/py/test/tkinter/guisession.py Wed Apr 27 15:18:07 2005 @@ -1,6 +1,6 @@ '''GuiSession builds TestReport instances and sends them to tkgui.Manager''' import py -from util import TestReport2 as TestReport +from util import TestReport from py.__.test.session import Exit, SimpleOutErrCapture class GuiSession(py.test.Session): @@ -31,10 +31,10 @@ self.reportlist.append(report) self.sendreport(report) - def finish(self, colitem, res): - super(GuiSession, self).finish(colitem, res) + def finish(self, colitem, outcome): + super(GuiSession, self).finish(colitem, outcome) report = self.reportlist.pop() - report.finish(colitem, res, self.config) + report.finish(colitem, outcome, self.config) self.reportlist[-1].status.update(report.status) self.sendreport(report) #py.std.time.sleep(0.5) Modified: py/dist/py/test/tkinter/util.py ============================================================================== --- py/dist/py/test/tkinter/util.py (original) +++ py/dist/py/test/tkinter/util.py Wed Apr 27 15:18:07 2005 @@ -117,7 +117,7 @@ template = {'time' : 0, 'label': 'Root', 'id': 'Root', - 'full_id': ['Root'], + 'full_id': [], 'status': Status.NotExecuted(), 'report': 'NoReport', 'error_report': '', @@ -125,6 +125,7 @@ 'restart_params': None, # ('',('',)) 'path' : '', 'modpath': '', + 'is_item': False, } Status = Status @@ -200,17 +201,18 @@ terminal.repr_failure(item, res) return out.getoutput() - def report_skipped(self, config, item, res): + def report_skipped(self, config, item, outcome): texts = {} - raisingtb = self.getlastvisible(res.excinfo.traceback) + terminal = py.test.TerminalSession(config) + raisingtb = terminal.getlastvisible(outcome.excinfo.traceback) fn = raisingtb.frame.code.path lineno = raisingtb.lineno - d = texts.setdefault(res.excinfo.exconly(), {}) - d[(fn, lineno)] = res + d = texts.setdefault(outcome.excinfo.exconly(), {}) + d[(fn, lineno)] = outcome out = OutBuffer() out.sep('_', 'reasons for skipped tests') for text, dict in texts.items(): - for (fn, lineno), res in dict.items(): + for (fn, lineno), outcome in dict.items(): out.line('Skipped in %s:%d' %(fn, lineno)) out.line("reason: %s" % text) @@ -239,25 +241,6 @@ channel_dict.update(kwargs) return TestReport.fromChannel(channel_dict) -class TestReport2(TestReport): - '''Will replace TestReport.''' - - template = {'time' : 0, - 'label': 'Root', - 'id': 'Root', - 'full_id': [], - 'status': Status.NotExecuted(), - 'report': 'NoReport', - 'error_report': '', - 'finished': False, - 'restart_params': None, # ('',('',)) - 'path' : '', - 'modpath': '', - } - Status = Status - - - class TestFileWatcher: '''watches files or paths''' From hpk at codespeak.net Thu Apr 28 16:29:29 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 28 Apr 2005 16:29:29 +0200 (CEST) Subject: [py-svn] r11590 - in py/dist/py: . execnet Message-ID: <20050428142929.12C3D27BD3@code1.codespeak.net> Author: hpk Date: Thu Apr 28 16:29:28 2005 New Revision: 11590 Modified: py/dist/py/__init__.py py/dist/py/execnet/register.py Log: try to nicely kill the depending process on unix platforms Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Thu Apr 28 16:29:28 2005 @@ -1,7 +1,7 @@ """\ the py lib is a development support library featuring py.test, an interactive testing tool which supports -unittesting with practically no boilerplate. +unit-testing with practically no boilerplate. """ from initpkg import initpkg Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Thu Apr 28 16:29:28 2005 @@ -69,8 +69,13 @@ self.trace("waiting for pid %s" % pid) try: os.waitpid(pid, 0) - except OSError: - self.trace("child process %s already dead?" %pid) + except KeyboardInterrupt: + if sys.platform != "win32": + os.kill(pid, 15) + raise + except OSError, e: + self.trace("child process %s already dead? error:%s" % + (str(e), pid)) class PopenGateway(PopenCmdGateway): # use sysfind/sysexec/subprocess instead of os.popen? From hpk at codespeak.net Fri Apr 29 15:02:50 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 29 Apr 2005 15:02:50 +0200 (CEST) Subject: [py-svn] r11621 - py/dist/py/test Message-ID: <20050429130250.9970427BF7@code1.codespeak.net> Author: hpk Date: Fri Apr 29 15:02:50 2005 New Revision: 11621 Modified: py/dist/py/test/collect.py py/dist/py/test/raises.py Log: refactor internal interfaces slightly Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Fri Apr 29 15:02:50 2005 @@ -76,27 +76,26 @@ _stickyfailure = None class Outcome: - msg = None - excinfo = None def __init__(self, msg=None, excinfo=None): - if msg is not None: - self.msg = msg - if excinfo is not None: - self.excinfo = excinfo + self.msg = msg + self.excinfo = excinfo def __repr__(self): - return getattr(self, 'msg', "%s instance" % (self.__class__,)) - def __str__(self): - return getattr(self, 'msg', object.__repr__(self)) - class Passed(Outcome): pass - class Failed(Outcome): pass + if self.msg: + return self.msg + return "<%s instance>" %(self.__class__.__name__,) + __str__ = __repr__ + + class Passed(Outcome): + pass + class Failed(Outcome): + pass class ExceptionFailure(Failed): - msg = 'DID NOT RAISE' def __init__(self, expr, expected, msg=None, excinfo=None): Collector.Failed.__init__(self, msg=msg, excinfo=excinfo) self.expr = expr self.expected = expected - - class Skipped(Outcome): pass + class Skipped(Outcome): + pass def __repr__(self): return "<%s %r>" %(self.__class__.__name__, self.name) Modified: py/dist/py/test/raises.py ============================================================================== --- py/dist/py/test/raises.py (original) +++ py/dist/py/test/raises.py Fri Apr 29 15:02:50 2005 @@ -23,7 +23,6 @@ # this is destroyed here ... except ExpectedException: return py.code.ExceptionInfo() - raise ExceptionFailure(expr=expr, expected=ExpectedException) else: func = args[0] assert callable @@ -36,4 +35,5 @@ if k: k = ', ' + k expr = '%s(%r%s)' %(func.__name__, args, k) - raise ExceptionFailure(expr=args, expected=ExpectedException) + raise ExceptionFailure(msg="DID NOT RAISE", + expr=args, expected=ExpectedException) From hpk at codespeak.net Fri Apr 29 15:19:30 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 29 Apr 2005 15:19:30 +0200 (CEST) Subject: [py-svn] r11622 - in py/dist/py/test: . terminal Message-ID: <20050429131930.8232C27BF7@code1.codespeak.net> Author: hpk Date: Fri Apr 29 15:19:30 2005 New Revision: 11622 Modified: py/dist/py/test/collect.py py/dist/py/test/terminal/terminal.py Log: try harder to preserve test outcomes across tryiter()/run() invocations. Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Fri Apr 29 15:19:30 2005 @@ -193,6 +193,9 @@ yield y except KeyboardInterrupt: raise + except self.Outcome, e: + self._stickyfailure = e + yield e except: x = py.test.Item.Failed(excinfo=py.code.ExceptionInfo()) self._stickyfailure = x @@ -276,9 +279,7 @@ class Module(PyCollectorMixin, FSCollector): def __repr__(self): - return "<%s %r (%s)>" %(self.__class__.__name__, - self.name, - self.obj.__name__) + return "<%s %r>" % (self.__class__.__name__, self.name) def obj(self): try: Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Fri Apr 29 15:19:30 2005 @@ -230,6 +230,7 @@ def repr_failure(self, item, outcome): excinfo = outcome.excinfo traceback = excinfo.traceback + #print "repr_failures sees item", item #print "repr_failures sees traceback" #py.std.pprint.pprint(traceback) if item: From hpk at codespeak.net Fri Apr 29 16:58:45 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 29 Apr 2005 16:58:45 +0200 (CEST) Subject: [py-svn] r11630 - py/dist/py/test/terminal Message-ID: <20050429145845.564FB27BE1@code1.codespeak.net> Author: hpk Date: Fri Apr 29 16:58:45 2005 New Revision: 11630 Modified: py/dist/py/test/terminal/terminal.py Log: in default non-verbose mode show path names relative to current working dir Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Fri Apr 29 16:58:45 2005 @@ -4,6 +4,17 @@ Item = py.test.Item from py.__.test.terminal.out import getout +def getrelpath(source, dest): + base = source.common(dest) + if not base: + return None + # with posix local paths '/' is always a common base + relsource = source.__class__(dest).relto(base) + reldest = source.relto(base) + n = reldest.count(source.sep) + target = source.sep.join(('..', )*n + (relsource, )) + return target + class TerminalSession(py.test.Session): def __init__(self, config, file=None): super(TerminalSession, self).__init__(config) @@ -44,7 +55,7 @@ def start_Module(self, colitem): if self.config.option.verbose == 0: - abbrev_fn = "/".join(colitem.listnames()) + abbrev_fn = getrelpath(py.path.local('.xxx.'), colitem.fspath) self.out.write('%s' % (abbrev_fn, )) colitem.numitems = colitem.getnum(py.test.Item) self.out.write('[%d] ' % (colitem.numitems,)) From hpk at codespeak.net Fri Apr 29 17:03:43 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 29 Apr 2005 17:03:43 +0200 (CEST) Subject: [py-svn] r11632 - py/dist/py/test/tkinter Message-ID: <20050429150343.5D17227BF0@code1.codespeak.net> Author: hpk Date: Fri Apr 29 17:03:43 2005 New Revision: 11632 Modified: py/dist/py/test/tkinter/tkgui.py Log: i don't know other's eyes but 12 point sounds like a reasonable default font size (i wouldn't even mind a bit more :-) Modified: py/dist/py/test/tkinter/tkgui.py ============================================================================== --- py/dist/py/test/tkinter/tkgui.py (original) +++ py/dist/py/test/tkinter/tkgui.py Fri Apr 29 17:03:43 2005 @@ -9,11 +9,11 @@ import re import os -myfont = ('Helvetica', 9, 'normal') +myfont = ('Helvetica', 12, 'normal') class StatusBar(Tkinter.Frame): - font = ('Helvetica', 10, 'normal') + font = ('Helvetica', 14, 'normal') def __init__(self, master=None, **kw): if master is None: From hpk at codespeak.net Sat Apr 30 23:51:42 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 30 Apr 2005 23:51:42 +0200 (CEST) Subject: [py-svn] r11663 - in py/dist/py: path path/local path/local/testing test/terminal Message-ID: <20050430215142.2ECE927B9F@code1.codespeak.net> Author: hpk Date: Sat Apr 30 23:51:42 2005 New Revision: 11663 Modified: py/dist/py/path/common.py py/dist/py/path/local/local.py py/dist/py/path/local/testing/test_local.py py/dist/py/test/terminal/terminal.py Log: - fixed chdir() invocations in test_local - fixed dumpobj() method and add a 'bin' parameter added tests Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Sat Apr 30 23:51:42 2005 @@ -158,12 +158,9 @@ def __cmp__(self, other): try: - try: - return cmp(self.strpath, other.strpath) - except AttributeError: - return cmp(str(self), str(other)) # self.path, other.path) - except: - self._except(sys.exc_info()) + return cmp(self.strpath, other.strpath) + except AttributeError: + return cmp(str(self), str(other)) # self.path, other.path) def __repr__(self): return repr(str(self)) @@ -308,15 +305,12 @@ def loadobj(self): """ return object unpickled from self.read() """ + f = self.open('rb') try: - f = self.open('rb') - try: - from cPickle import load - return load(f) - finally: - f.close() - except: - self._except(sys.exc_info()) + from cPickle import load + return self._callex(load, f) + finally: + f.close() def move(self, target): if target.relto(self): Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sat Apr 30 23:51:42 2005 @@ -231,11 +231,11 @@ def rename(self, target): return self._callex(os.rename, str(self), str(target)) - def dumpobj(self, obj): + def dumpobj(self, obj, bin=1): """ pickle object into path location""" f = self.open('wb') try: - self._callex(py.std.cPickle.dump, obj, f) + self._callex(py.std.cPickle.dump, obj, f, bin) finally: f.close() Modified: py/dist/py/path/local/testing/test_local.py ============================================================================== --- py/dist/py/path/local/testing/test_local.py (original) +++ py/dist/py/path/local/testing/test_local.py Sat Apr 30 23:51:42 2005 @@ -19,13 +19,12 @@ assert str(local()) == py.std.os.getcwd() def test_initialize_reldir(self): - curdir = py.std.os.curdir + old = self.root.chdir() try: - py.std.os.chdir(str(self.root)) p = local('samplefile') assert p.check() finally: - py.std.os.chdir(curdir) + old.chdir() def test_eq_with_strings(self): path1 = self.root.join('sampledir') @@ -38,22 +37,23 @@ def test_dump(self): import tempfile - try: - fd, name = tempfile.mkstemp() - f = py.std.os.fdopen(fd) - except AttributeError: - name = tempfile.mktemp() - f = open(name, 'w+') - try: - d = {'answer' : 42} - path = local(name) - path.dumpobj(d) - from cPickle import load - dnew = load(f) - assert d == dnew - finally: - f.close() - py.std.os.remove(name) + for bin in 0, 1: + try: + fd, name = tempfile.mkstemp() + f = py.std.os.fdopen(fd) + except AttributeError: + name = tempfile.mktemp() + f = open(name, 'w+') + try: + d = {'answer' : 42} + path = local(name) + path.dumpobj(d, bin=bin) + from cPickle import load + dnew = load(f) + assert d == dnew + finally: + f.close() + py.std.os.remove(name) def test_setmtime(self): import tempfile @@ -179,7 +179,7 @@ def test_sysexec(self): x = py.path.local.sysfind('ls') out = x.sysexec() - for x in py.path.local(): + for x in py.path.local().listdir(lambda x: x.check(dir=0)): assert out.find(x.basename) != -1 def test_sysexec_failing(self): Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Sat Apr 30 23:51:42 2005 @@ -9,10 +9,10 @@ if not base: return None # with posix local paths '/' is always a common base - relsource = source.__class__(dest).relto(base) - reldest = source.relto(base) - n = reldest.count(source.sep) - target = source.sep.join(('..', )*n + (relsource, )) + relsource = source.relto(base) + reldest = dest.relto(base) + n = relsource.count(source.sep) + target = dest.sep.join(('..', )*n + (reldest, )) return target class TerminalSession(py.test.Session): From hpk at codespeak.net Sat Apr 30 23:57:40 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 30 Apr 2005 23:57:40 +0200 (CEST) Subject: [py-svn] r11664 - in py/dist/py: misc path path/local path/local/testing path/svn path/testing Message-ID: <20050430215740.987C127BC2@code1.codespeak.net> Author: hpk Date: Sat Apr 30 23:57:40 2005 New Revision: 11664 Modified: py/dist/py/misc/rest.py py/dist/py/path/common.py py/dist/py/path/local/local.py py/dist/py/path/local/testing/test_local.py py/dist/py/path/svn/wccommand.py py/dist/py/path/testing/fscommon.py Log: renamed loadobj to load() renamed dumpobj to dump() to better match the pickle-names. Modified: py/dist/py/misc/rest.py ============================================================================== --- py/dist/py/misc/rest.py (original) +++ py/dist/py/misc/rest.py Sat Apr 30 23:57:40 2005 @@ -43,5 +43,5 @@ #log("wrote %r" % htmlpath) #if txtpath.check(svnwc=1, versioned=1): # info = txtpath.info() - # svninfopath.dumpobj(info) + # svninfopath.dump(info) Modified: py/dist/py/path/common.py ============================================================================== --- py/dist/py/path/common.py (original) +++ py/dist/py/path/common.py Sat Apr 30 23:57:40 2005 @@ -303,7 +303,7 @@ finally: f.close() - def loadobj(self): + def load(self): """ return object unpickled from self.read() """ f = self.open('rb') try: Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Sat Apr 30 23:57:40 2005 @@ -231,7 +231,7 @@ def rename(self, target): return self._callex(os.rename, str(self), str(target)) - def dumpobj(self, obj, bin=1): + def dump(self, obj, bin=1): """ pickle object into path location""" f = self.open('wb') try: Modified: py/dist/py/path/local/testing/test_local.py ============================================================================== --- py/dist/py/path/local/testing/test_local.py (original) +++ py/dist/py/path/local/testing/test_local.py Sat Apr 30 23:57:40 2005 @@ -47,7 +47,7 @@ try: d = {'answer' : 42} path = local(name) - path.dumpobj(d, bin=bin) + path.dump(d, bin=bin) from cPickle import load dnew = load(f) assert d == dnew Modified: py/dist/py/path/svn/wccommand.py ============================================================================== --- py/dist/py/path/svn/wccommand.py (original) +++ py/dist/py/path/svn/wccommand.py Sat Apr 30 23:57:40 2005 @@ -40,8 +40,8 @@ assert isinstance(self._url, str) return self._url - def dumpobj(self, obj): - return self.localpath.dumpobj(obj) + def dump(self, obj): + return self.localpath.dump(obj) def svnurl(self): """ return current SvnPath for this WC-item. """ Modified: py/dist/py/path/testing/fscommon.py ============================================================================== --- py/dist/py/path/testing/fscommon.py (original) +++ py/dist/py/path/testing/fscommon.py Sat Apr 30 23:57:40 2005 @@ -17,7 +17,7 @@ execfilepy.write('x=42') d = {1:2, 'hello': 'world', 'answer': 42} - path.ensure('samplepickle').dumpobj(d) + path.ensure('samplepickle').dump(d) sampledir = path.ensure('sampledir', dir=1) sampledir.ensure('otherfile') @@ -130,7 +130,7 @@ def test_load(self): p = self.root.join('samplepickle') - obj = p.loadobj() + obj = p.load() assert type(obj) is dict assert obj.get('answer',None) == 42