From hpk at codespeak.net Sun May 1 09:50:55 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 1 May 2005 09:50:55 +0200 (CEST) Subject: [py-svn] r11682 - py/dist/py/execnet Message-ID: <20050501075055.E17FB27B85@code1.codespeak.net> Author: hpk Date: Sun May 1 09:50:55 2005 New Revision: 11682 Modified: py/dist/py/execnet/register.py Log: change calling signature for SshGateway in a pretty backward compatible way. Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Sun May 1 09:50:55 2005 @@ -137,17 +137,16 @@ remote_install = classmethod(remote_install) class SshGateway(PopenCmdGateway): - def __init__(self, host, port=None, username=None, remotepython='python'): - if port is not None: - host = '%s:%d' % (host, port) + def __init__(self, sshaddress, remotepython='python', identity=None): remotecmd = '%s -u -c "exec input()"' % (remotepython,) - cmdline = [host, remotecmd] - if username is not None: - cmdline[:0] = ['-l', username] + cmdline = [sshaddress, remotecmd] # XXX Unix style quoting for i in range(len(cmdline)): cmdline[i] = "'" + cmdline[i].replace("'", "'\\''") + "'" - cmdline.insert(0, 'ssh') + cmd = 'ssh' + if identity is not None: + cmd += ' -i %s' % (identity,) + cmdline.insert(0, cmd) super(SshGateway, self).__init__(' '.join(cmdline)) class ExecGateway(PopenGateway): From hpk at codespeak.net Sun May 1 16:57:41 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 1 May 2005 16:57:41 +0200 (CEST) Subject: [py-svn] r11702 - in py/dist/py/test/tool: . testing Message-ID: <20050501145741.CB97D27C19@code1.codespeak.net> Author: hpk Date: Sun May 1 16:57:41 2005 New Revision: 11702 Modified: py/dist/py/test/tool/outerrcapture.py py/dist/py/test/tool/testing/test_outerrcapture.py Log: add callcapture() helper Modified: py/dist/py/test/tool/outerrcapture.py ============================================================================== --- py/dist/py/test/tool/outerrcapture.py (original) +++ py/dist/py/test/tool/outerrcapture.py Sun May 1 16:57:41 2005 @@ -33,4 +33,10 @@ e.seek(0) return o,e -simplecapture = SimpleOutErrCapture +def callcapture(func, *args, **kwargs): + so = SimpleOutErrCapture() + try: + res = func(*args, **kwargs) + finally: + out, err = so.reset() + return res, out, err 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 Sun May 1 16:57:41 2005 @@ -1,6 +1,6 @@ import sys import py -from py.__.test.tool.outerrcapture import SimpleOutErrCapture +from py.__.test.tool.outerrcapture import SimpleOutErrCapture, callcapture def test_capturing_simple(): cap = SimpleOutErrCapture() @@ -38,3 +38,15 @@ print "hello" cap2.reset() py.test.raises(AttributeError, "cap2.reset()") + +def test_callcapture(): + def func(x, y): + print x + print >>py.std.sys.stderr, y + return 42 + + res, out, err = callcapture(func, 3, y=4) + assert res == 42 + assert out.startswith("3") + assert err.startswith("4") + From hpk at codespeak.net Mon May 2 02:05:22 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 2 May 2005 02:05:22 +0200 (CEST) Subject: [py-svn] r11753 - in py/dist/py: misc misc/testing test test/tool test/tool/testing thread/testing Message-ID: <20050502000522.6A57B27C26@code1.codespeak.net> Author: hpk Date: Mon May 2 02:05:21 2005 New Revision: 11753 Added: py/dist/py/misc/simplecapture.py - copied unchanged from r11702, py/dist/py/test/tool/outerrcapture.py py/dist/py/misc/testing/test_simplecapture.py - copied, changed from r11702, py/dist/py/test/tool/testing/test_outerrcapture.py Removed: py/dist/py/test/tool/outerrcapture.py py/dist/py/test/tool/testing/ Modified: py/dist/py/test/session.py py/dist/py/thread/testing/test_pool.py Log: move output capturing to better location Copied: py/dist/py/misc/testing/test_simplecapture.py (from r11702, py/dist/py/test/tool/testing/test_outerrcapture.py) ============================================================================== --- py/dist/py/test/tool/testing/test_outerrcapture.py (original) +++ py/dist/py/misc/testing/test_simplecapture.py Mon May 2 02:05:21 2005 @@ -1,6 +1,6 @@ import sys import py -from py.__.test.tool.outerrcapture import SimpleOutErrCapture, callcapture +from py.__.misc.simplecapture import SimpleOutErrCapture, callcapture def test_capturing_simple(): cap = SimpleOutErrCapture() Modified: py/dist/py/test/session.py ============================================================================== --- py/dist/py/test/session.py (original) +++ py/dist/py/test/session.py Mon May 2 02:05:21 2005 @@ -1,5 +1,5 @@ import py -from py.__.test.tool.outerrcapture import SimpleOutErrCapture +from py.__.misc.simplecapture import SimpleOutErrCapture class Session(object): """ Deleted: /py/dist/py/test/tool/outerrcapture.py ============================================================================== --- /py/dist/py/test/tool/outerrcapture.py Mon May 2 02:05:21 2005 +++ (empty file) @@ -1,42 +0,0 @@ -""" - -capture stdout/stderr - -""" -import sys -try: from cStringIO import StringIO -except ImportError: from StringIO import StringIO - -class SimpleOutErrCapture: - """ capture sys.stdout/sys.stderr (but not system level fd 1 and 2). - - this captures only "In-Memory" and is currently intended to be - used by the unittest package to capture print-statements in tests. - """ - def __init__(self): - self.oldout = sys.stdout - self.olderr = sys.stderr - sys.stdout = self.newout = StringIO() - sys.stderr = self.newerr = StringIO() - - def reset(self): - """ return captured output and restore sys.stdout/err.""" - x, y = self.done() - return x.read(), y.read() - - def done(self): - o,e = sys.stdout, sys.stderr - sys.stdout, sys.stderr = self.oldout, self.olderr - del self.oldout, self.olderr - o, e = self.newout, self.newerr - o.seek(0) - e.seek(0) - return o,e - -def callcapture(func, *args, **kwargs): - so = SimpleOutErrCapture() - try: - res = func(*args, **kwargs) - finally: - out, err = so.reset() - return res, out, err 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 Mon May 2 02:05:21 2005 @@ -70,7 +70,7 @@ pool.join(timeout=0.1) def test_pool_clean_shutdown(): - from py.__.test.tool.outerrcapture import SimpleOutErrCapture + from py.__.misc.simplecapture import SimpleOutErrCapture capture = SimpleOutErrCapture() pool = WorkerPool() def f(): From hpk at codespeak.net Mon May 2 13:10:27 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 2 May 2005 13:10:27 +0200 (CEST) Subject: [py-svn] r11776 - in py/dist/py/test: . tkinter Message-ID: <20050502111027.EA47727B9C@code1.codespeak.net> Author: hpk Date: Mon May 2 13:10:27 2005 New Revision: 11776 Modified: py/dist/py/test/collect.py py/dist/py/test/item.py py/dist/py/test/session.py py/dist/py/test/tkinter/guisession.py Log: refactor internal capturing towards making colitems responsible for performing stdout/stderr capturing now collectors usually have an 'option' attribute which points to a session's options. They get it from via an external setattr and otherwise look it up with their parent. Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Mon May 2 13:10:27 2005 @@ -66,6 +66,7 @@ def __init__(self, name, parent=None): self.name = name self.parent = parent + self.option = getattr(parent, 'option', None) Module = configproperty('Module') Directory = configproperty('Directory') @@ -219,6 +220,9 @@ return property(fget, fset, None, "containing filesystem path") fspath = fspath() + def getcapture(self): + return None # by default collectors don't capture output + class FSCollector(Collector): def __init__(self, fspath, parent=None): if isinstance(fspath, str): Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Mon May 2 13:10:27 2005 @@ -1,6 +1,7 @@ import py from inspect import isclass, ismodule +from py.__.misc.simplecapture import SimpleOutErrCapture _dummy = object() @@ -30,7 +31,11 @@ self.stack.append(col) class Item(py.test.collect.Collector): - pass + def getcapture(self): + """ return capture object or None for the given colitem. """ + # XXX capturing output even for collectors? + if not self.option.nocapture and not self.option.usepdb: + return SimpleOutErrCapture() class Function(Item): """ a Function Item is responsible for setting up @@ -38,8 +43,7 @@ """ state = SetupState() def __init__(self, name, parent, args=(), obj=_dummy): - self.name = name - self.parent = parent + super(Function, self).__init__(name, parent) self.args = args if obj is not _dummy: self._obj = obj Modified: py/dist/py/test/session.py ============================================================================== --- py/dist/py/test/session.py (original) +++ py/dist/py/test/session.py Mon May 2 13:10:27 2005 @@ -1,5 +1,4 @@ import py -from py.__.misc.simplecapture import SimpleOutErrCapture class Session(object): """ @@ -43,6 +42,7 @@ self.header(colitems) try: for colitem in colitems: + colitem.option = self.config.option self.runtraced(colitem) except KeyboardInterrupt: raise @@ -60,12 +60,9 @@ if self.shouldclose(): raise Exit, "received external close signal" - 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: - capture = SimpleOutErrCapture() + capture = colitem.getcapture() needfinish = False + outcome = None try: self.start(colitem) needfinish = True @@ -89,7 +86,9 @@ try: outcome.out, outcome.err = out, err except AttributeError: - pass + pass # XXX lost output is not so nice actually + # should probably store it + # somewhere else if needfinish: self.finish(colitem, outcome) Modified: py/dist/py/test/tkinter/guisession.py ============================================================================== --- py/dist/py/test/tkinter/guisession.py (original) +++ py/dist/py/test/tkinter/guisession.py Mon May 2 13:10:27 2005 @@ -1,7 +1,6 @@ '''GuiSession builds TestReport instances and sends them to tkgui.Manager''' import py from util import TestReport -from py.__.test.session import Exit, SimpleOutErrCapture class GuiSession(py.test.Session): From jan at codespeak.net Mon May 2 14:42:52 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Mon, 2 May 2005 14:42:52 +0200 (CEST) Subject: [py-svn] r11778 - py/dist/py/test/tkinter Message-ID: <20050502124252.780FE27B8E@code1.codespeak.net> Author: jan Date: Mon May 2 14:42:52 2005 New Revision: 11778 Modified: py/dist/py/test/tkinter/tkgui.py Log: * reuse 'Error'-Window Modified: py/dist/py/test/tkinter/tkgui.py ============================================================================== --- py/dist/py/test/tkinter/tkgui.py (original) +++ py/dist/py/test/tkinter/tkgui.py Mon May 2 14:42:52 2005 @@ -133,7 +133,76 @@ self.entry.pack(side = Tkinter.LEFT, expand = Tkinter.YES, fill = Tkinter.X) +class ReportFrame(Tkinter.Frame): + font = myfont + def __init__(self, *args, **kwargs): + Tkinter.Frame.__init__(self, *args, **kwargs) + self.report = Null() + self.label = Tkinter.Label(self,foreground="red", justify=Tkinter.LEFT) + self.label.pack(anchor=Tkinter.W) + self.label.configure(font = self.font) + self.label.pack(side = Tkinter.TOP) + self.text = ScrolledText.ScrolledText(self) + self.text.configure(bg = 'White', font = ('Helvetica', 11, 'normal')) + self.text.tag_config('sel', relief=Tkinter.FLAT) + self.text.pack(expand=Tkinter.YES, fill=Tkinter.BOTH, + side = Tkinter.TOP) + + def set_report(self, report): + self.report = report + self.label.configure(text ='%s: %s' % (self.report.status, + self.report.label), + foreground="red", justify=Tkinter.LEFT) + self.text['state'] = Tkinter.NORMAL + self.text.delete(1.0, Tkinter.END) + self.text.insert(Tkinter.END, self.report.error_report) + self.text.yview_pickplace(Tkinter.END) + self.text['state'] = Tkinter.DISABLED + self.attacheditorhotspots(self.text) + + def launch_editor(self, file, line): + editor = (py.std.os.environ.get('PYTHON_EDITOR', None) or + py.std.os.environ.get('EDITOR_REMOTE', None) or + os.environ.get('EDITOR', None) or "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 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)) + + + class TkGui: font = ('Helvetica', 9, 'normal') @@ -147,6 +216,8 @@ self.backend = backend.RepositoryBackend(config) self.createwidgets() self.timer_update() + self.report_window = Null() + self.report_frame = Null() def createwidgets(self): self._buttonframe = Tkinter.Frame(self._parent) @@ -170,74 +241,24 @@ 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() + if not self.report_window: + self.report_window = Tkinter.Toplevel(self._parent) + self.report_window.protocol('WM_DELETE_WINDOW', + self.report_window.withdraw) + b = Tkinter.Button(self.report_window, text="Close", + command=self.report_window.withdraw) + b.pack(side=Tkinter.BOTTOM) + #b.focus_set() + self.report_frame = ReportFrame(self.report_window) + self.report_frame.pack() + elif self.report_window.state() != Tkinter.NORMAL: + self.report_window.deiconify() + + self.report_window.title(report.label) + self.report_frame.set_report(report) - 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() From hpk at codespeak.net Mon May 2 18:29:22 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 2 May 2005 18:29:22 +0200 (CEST) Subject: [py-svn] r11797 - in py/dist/py/test: . terminal testing Message-ID: <20050502162922.BF16C27B85@code1.codespeak.net> Author: hpk Date: Mon May 2 18:29:22 2005 New Revision: 11797 Modified: py/dist/py/test/collect.py py/dist/py/test/item.py py/dist/py/test/session.py py/dist/py/test/terminal/terminal.py py/dist/py/test/testing/test_session.py Log: clean up capturing of stdout/stderr Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Mon May 2 18:29:22 2005 @@ -67,6 +67,7 @@ self.name = name self.parent = parent self.option = getattr(parent, 'option', None) + self.fspath = getattr(parent, 'fspath', None) Module = configproperty('Module') Directory = configproperty('Directory') @@ -202,33 +203,18 @@ self._stickyfailure = x yield x - def fspath(): - def fget(self): - try: - return self._fspath - except AttributeError: - parent = self.parent - while parent: - try: - self._fspath = parent.fspath - except AttributeError: - parent = parent.parent - else: - return self._fspath - def fset(self, value): - self._fspath = value - return property(fget, fset, None, "containing filesystem path") - fspath = fspath() - - def getcapture(self): + captured_out = captured_err = None + def startcapture(self): + return None # by default collectors don't capture output + def finishcapture(self): return None # by default collectors don't capture output class FSCollector(Collector): def __init__(self, fspath, parent=None): if isinstance(fspath, str): fspath = py.path.local(fspath) - self.fspath = fspath super(FSCollector, self).__init__(fspath.basename, parent) + self.fspath = fspath class Directory(FSCollector): def filefilter(self, path): Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Mon May 2 18:29:22 2005 @@ -31,11 +31,14 @@ self.stack.append(col) class Item(py.test.collect.Collector): - def getcapture(self): - """ return capture object or None for the given colitem. """ - # XXX capturing output even for collectors? + def startcapture(self): if not self.option.nocapture and not self.option.usepdb: - return SimpleOutErrCapture() + self._capture = SimpleOutErrCapture() + def finishcapture(self): + if hasattr(self, '_capture'): + capture = self._capture + del self._capture + self.captured_out, self.captured_err = capture.reset() class Function(Item): """ a Function Item is responsible for setting up Modified: py/dist/py/test/session.py ============================================================================== --- py/dist/py/test/session.py (original) +++ py/dist/py/test/session.py Mon May 2 18:29:22 2005 @@ -60,37 +60,29 @@ if self.shouldclose(): raise Exit, "received external close signal" - capture = colitem.getcapture() - needfinish = False outcome = None + colitem.startcapture() try: self.start(colitem) - needfinish = True - try: - if colitem._stickyfailure: - raise colitem._stickyfailure - outcome = self.run(colitem) - except (KeyboardInterrupt, Exit): - raise - except colitem.Outcome, outcome: - if outcome.excinfo is None: - outcome.excinfo = py.code.ExceptionInfo() - except: - excinfo = py.code.ExceptionInfo() - 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: - outcome.out, outcome.err = out, err - except AttributeError: - pass # XXX lost output is not so nice actually - # should probably store it - # somewhere else - if needfinish: + try: + try: + if colitem._stickyfailure: + raise colitem._stickyfailure + outcome = self.run(colitem) + except (KeyboardInterrupt, Exit): + raise + except colitem.Outcome, outcome: + if outcome.excinfo is None: + outcome.excinfo = py.code.ExceptionInfo() + except: + excinfo = py.code.ExceptionInfo() + outcome = colitem.Failed(excinfo=excinfo) + assert (outcome is None or + isinstance(outcome, (list, colitem.Outcome))) + finally: self.finish(colitem, outcome) + finally: + colitem.finishcapture() 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 Mon May 2 18:29:22 2005 @@ -266,7 +266,7 @@ if entry == last: if item: self.repr_failure_info(item, entry) - self.repr_out_err(outcome) + self.repr_out_err(item) self.out.sep("_") else: self.out.sep("_ ") @@ -397,13 +397,13 @@ for x in lines: self.out.line(indent + x) - def repr_out_err(self, outcome): + def repr_out_err(self, colitem): for name in 'out', 'err': - if hasattr(outcome, name): - out = getattr(outcome, name) - if out.strip(): - self.out.sep("- ", "recorded std%s" % name) - self.out.line(out.strip()) + attrname = 'captured_' + name + obj = getattr(colitem, attrname) + if obj: + self.out.sep("- ", "recorded std%s" % name) + self.out.line(obj) def repr_locals(self, entry): if self.config.option.showlocals: Modified: py/dist/py/test/testing/test_session.py ============================================================================== --- py/dist/py/test/testing/test_session.py (original) +++ py/dist/py/test/testing/test_session.py Mon May 2 18:29:22 2005 @@ -137,6 +137,35 @@ i = out.find('TypeError') assert i != -1 + def test_capturing_hooks(self): + o = tmpdir.ensure('capturing', dir=1) + tfile = o.join('test_capturing.py').write(py.code.Source(""" + import py + def test_raises_doesnt(): + print 42 + print >>py.std.sys.stderr, 23 + """)) + conftest = o.join('conftest.py').write(py.code.Source(""" + import py + from py.__.misc.simplecapture import SimpleOutErrCapture + class Function(py.test.Function): + def startcapture(self): + self._mycapture = SimpleOutErrCapture() + + def finishcapture(self): + self._testmycapture = self._mycapture.reset() + """)) + session = self.session + session.main([o]) + l = session.getitemoutcomepairs(py.test.Item.Passed) + assert len(l) == 1 + item = l[0][0] + assert hasattr(item, '_testmycapture') + print item._testmycapture + out, err = item._testmycapture + assert int(out.strip()) == 42 + assert int(err.strip()) == 23 + def test_raises_output(self): o = tmpdir.ensure('raisestest', dir=1) tfile = o.join('test_raisesoutput.py') From hpk at codespeak.net Mon May 2 18:39:53 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 2 May 2005 18:39:53 +0200 (CEST) Subject: [py-svn] r11798 - py/dist/py/test Message-ID: <20050502163953.2DCEA27B85@code1.codespeak.net> Author: hpk Date: Mon May 2 18:39:52 2005 New Revision: 11798 Modified: py/dist/py/test/defaultconftest.py py/dist/py/test/session.py Log: wow! i hadn't noticed how easy it has become to add keyword-selection support. Modified: py/dist/py/test/defaultconftest.py ============================================================================== --- py/dist/py/test/defaultconftest.py (original) +++ py/dist/py/test/defaultconftest.py Mon May 2 18:39:52 2005 @@ -21,6 +21,9 @@ Option('-s', '--nocapture', action="store_true", dest="nocapture", default=False, help="disable catching of sys.stdout/stderr output."), + Option('-k', + action="store", dest="keyword", default='', + help="select tests by given keyword."), Option('-l', '--showlocals', action="store_true", dest="showlocals", default=False, help="show locals in tracebacks (disabled by default)"), Modified: py/dist/py/test/session.py ============================================================================== --- py/dist/py/test/session.py (original) +++ py/dist/py/test/session.py Mon May 2 18:39:52 2005 @@ -87,6 +87,13 @@ def run(self, colitem): if self.config.option.collectonly and isinstance(colitem, py.test.Item): return + if isinstance(colitem, py.test.Item): + keyword = self.config.option.keyword + if keyword: + name = " ".join(colitem.listnames()) + if name.find(keyword) == -1: + py.test.skip("test not selected by keyword %s" %(keyword,)) + res = colitem.run() if res is None: return py.test.Item.Passed() From hpk at codespeak.net Mon May 2 18:43:24 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 2 May 2005 18:43:24 +0200 (CEST) Subject: [py-svn] r11799 - py/dist/py/documentation Message-ID: <20050502164324.0D7E127B85@code1.codespeak.net> Author: hpk Date: Mon May 2 18:43:23 2005 New Revision: 11799 Modified: py/dist/py/documentation/test.txt Log: explain new keyword argument as a feature Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Mon May 2 18:43:23 2005 @@ -128,6 +128,18 @@ and does not need to complete before your first test items are executed. +selecting tests by keyword +-------------------------- + +You can selectively run tests by specifiying a keyword +on the command line. Example:: + + py.test -k simple + +will run all tests that are found from the current directory +and that have the word "simple" somewhere in the `test address`_ +leading up to a test item. Directory, file as well as class +and function/method names are part of the test address. no interference with cmdline utilities -------------------------------------- From hpk at codespeak.net Mon May 2 19:40:11 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 2 May 2005 19:40:11 +0200 (CEST) Subject: [py-svn] r11805 - in py/dist/py: documentation test test/testing Message-ID: <20050502174011.3876527B85@code1.codespeak.net> Author: hpk Date: Mon May 2 19:40:11 2005 New Revision: 11805 Modified: py/dist/py/documentation/test.txt py/dist/py/test/collect.py py/dist/py/test/defaultconftest.py py/dist/py/test/session.py py/dist/py/test/testing/test_session.py Log: flesh out keyword support some more Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Mon May 2 19:40:11 2005 @@ -134,12 +134,14 @@ You can selectively run tests by specifiying a keyword on the command line. Example:: - py.test -k simple + py.test -k test_simple will run all tests that are found from the current directory -and that have the word "simple" somewhere in the `test address`_ -leading up to a test item. Directory, file as well as class -and function/method names are part of the test address. +and where the word "test_simple" equals the start of one part of the +path leading up to the test item. Directory and file basenames as well +as function, class and function/method names each form a possibly +matching name. (Note that the exact semantics are still +experimental but should always remain intuitive). no interference with cmdline utilities -------------------------------------- Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Mon May 2 19:40:11 2005 @@ -157,6 +157,9 @@ def listnames(self): return [x.name for x in self.listchain()] + def haskeyword(self, keyword): + return self.name.startswith(keyword) + def getmodpath(self): """ return dotted module path (relative to the containing). """ inmodule = False Modified: py/dist/py/test/defaultconftest.py ============================================================================== --- py/dist/py/test/defaultconftest.py (original) +++ py/dist/py/test/defaultconftest.py Mon May 2 19:40:11 2005 @@ -23,7 +23,7 @@ help="disable catching of sys.stdout/stderr output."), Option('-k', action="store", dest="keyword", default='', - help="select tests by given keyword."), + help="select tests if they match the given keyword"), Option('-l', '--showlocals', action="store_true", dest="showlocals", default=False, help="show locals in tracebacks (disabled by default)"), Modified: py/dist/py/test/session.py ============================================================================== --- py/dist/py/test/session.py (original) +++ py/dist/py/test/session.py Mon May 2 19:40:11 2005 @@ -90,9 +90,11 @@ if isinstance(colitem, py.test.Item): keyword = self.config.option.keyword if keyword: - name = " ".join(colitem.listnames()) - if name.find(keyword) == -1: - py.test.skip("test not selected by keyword %s" %(keyword,)) + for subitem in colitem.listchain(): + if subitem.haskeyword(keyword): + break + else: + py.test.skip("test not selected by keyword %r" %(keyword,)) res = colitem.run() if res is None: Modified: py/dist/py/test/testing/test_session.py ============================================================================== --- py/dist/py/test/testing/test_session.py (original) +++ py/dist/py/test/testing/test_session.py Mon May 2 19:40:11 2005 @@ -40,6 +40,42 @@ config, args = py.test.Config.parse(['--tkinter']) assert issubclass(config.getsessionclass(), py.test.TkinterSession) +class TestKeywordSelection: + def test_select_simple(self): + config, args = py.test.Config.parse(['-k', 'test_one']) + session = config.getsessionclass()(config, py.std.sys.stdout) + session.main([datadir / 'filetest.py']) + l = session.getitemoutcomepairs(py.test.Item.Failed) + assert len(l) == 1 + item = l[0][0] + assert item.name == 'test_one' + l = session.getitemoutcomepairs(py.test.Item.Skipped) + assert len(l) == 1 + + def test_select_extra_keywords(self): + o = tmpdir.ensure('selecttest', dir=1) + tfile = o.join('test_select.py').write(py.code.Source(""" + def test_1(): + pass + class TestClass: + def test_2(self): + pass + """)) + conftest = o.join('conftest.py').write(py.code.Source(""" + import py + class Class(py.test.collect.Class): + def haskeyword(self, keyword): + return keyword == 'XXX' or \ + super(Class, self).haskeyword(keyword) + """)) + config, args = py.test.Config.parse(['-k', 'XXX']) + session = config.getsessionclass()(config) + session.main([o]) + l = session.getitemoutcomepairs(py.test.Item.Passed) + assert len(l) == 1 + assert l[0][0].name == 'test_2' + l = session.getitemoutcomepairs(py.test.Item.Skipped) + assert l[0][0].name == 'test_1' #f = open('/tmp/logfile', 'wa') class TestTerminalSession: From hpk at codespeak.net Tue May 3 11:28:31 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 3 May 2005 11:28:31 +0200 (CEST) Subject: [py-svn] r11834 - py/dist/py/documentation Message-ID: <20050503092831.9EBEE27B72@code1.codespeak.net> Author: hpk Date: Tue May 3 11:28:31 2005 New Revision: 11834 Modified: py/dist/py/documentation/test.txt Log: a few fixes Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Tue May 3 11:28:31 2005 @@ -501,7 +501,7 @@ *session.start(colitem)* - invoked before for each ``colitem.run()`` invocation + invoked before each ``colitem.run()`` invocation *session.finish(colitem, outcome)* @@ -588,11 +588,11 @@ Customizing the collection process in a module ---------------------------------------------- -*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.* + 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 From hpk at codespeak.net Tue May 3 11:32:28 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 3 May 2005 11:32:28 +0200 (CEST) Subject: [py-svn] r11835 - py/dist/py/documentation Message-ID: <20050503093228.5A12727B72@code1.codespeak.net> Author: hpk Date: Tue May 3 11:32:28 2005 New Revision: 11835 Modified: py/dist/py/documentation/test.txt Log: insert a ref/note about keyword selection Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Tue May 3 11:32:28 2005 @@ -128,6 +128,8 @@ and does not need to complete before your first test items are executed. +.. _`selection by keyword`: + selecting tests by keyword -------------------------- @@ -650,6 +652,9 @@ selecting tests by queries/full text search ------------------------------------------- + Note: there already is experimental support for test `selection by keyword`_. + Otherwise the following is not yet implemented + You can selectively run tests by specifiying words on the command line in a google-like way. Example:: From hpk at codespeak.net Tue May 3 12:36:16 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 3 May 2005 12:36:16 +0200 (CEST) Subject: [py-svn] r11839 - in py/dist/py/test: . terminal testing Message-ID: <20050503103616.BB57727B82@code1.codespeak.net> Author: hpk Date: Tue May 3 12:36:16 2005 New Revision: 11839 Modified: py/dist/py/test/collect.py py/dist/py/test/terminal/terminal.py py/dist/py/test/testing/test_session.py Log: do stdout/stderr capturing for module collectors by default and differentiate captured out/err with the terminal output some more (to avoid loosing info) Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Tue May 3 12:36:16 2005 @@ -30,6 +30,7 @@ """ from __future__ import generators import py +from py.__.misc.simplecapture import SimpleOutErrCapture isclass = py.std.inspect.isclass def configproperty(name): @@ -211,6 +212,8 @@ return None # by default collectors don't capture output def finishcapture(self): return None # by default collectors don't capture output + def getouterr(self): + return self.captured_out, self.captured_err class FSCollector(Collector): def __init__(self, fspath, parent=None): @@ -271,6 +274,15 @@ class Module(PyCollectorMixin, FSCollector): + def startcapture(self): + if not self.option.nocapture and not self.option.usepdb: + self._capture = SimpleOutErrCapture() + def finishcapture(self): + if hasattr(self, '_capture'): + capture = self._capture + del self._capture + self.captured_out, self.captured_err = capture.reset() + def __repr__(self): return "<%s %r>" % (self.__class__.__name__, self.name) Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Tue May 3 12:36:16 2005 @@ -398,13 +398,12 @@ self.out.line(indent + x) def repr_out_err(self, colitem): - for name in 'out', 'err': - attrname = 'captured_' + name - obj = getattr(colitem, attrname) - if obj: - self.out.sep("- ", "recorded std%s" % name) - self.out.line(obj) - + for parent in colitem.listchain(): + for name, obj in zip(['out', 'err'], parent.getouterr()): + if obj: + self.out.sep("- ", "%s: recorded std%s" % (parent.name, name)) + self.out.line(obj) + def repr_locals(self, entry): if self.config.option.showlocals: self.out.sep('- ', 'locals') 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 May 3 12:36:16 2005 @@ -1,6 +1,7 @@ import py datadir = py.magic.autopath().dirpath('data') from cStringIO import StringIO +from py.__.misc.simplecapture import callcapture tmpdir = py.test.ensuretemp('test_drive') @@ -173,11 +174,12 @@ i = out.find('TypeError') assert i != -1 - def test_capturing_hooks(self): + def test_capturing_hooks_simple(self): o = tmpdir.ensure('capturing', dir=1) tfile = o.join('test_capturing.py').write(py.code.Source(""" import py - def test_raises_doesnt(): + print "module level output" + def test_capturing(): print 42 print >>py.std.sys.stderr, 23 """)) @@ -192,7 +194,7 @@ self._testmycapture = self._mycapture.reset() """)) session = self.session - session.main([o]) + session.main([o]) l = session.getitemoutcomepairs(py.test.Item.Passed) assert len(l) == 1 item = l[0][0] @@ -202,6 +204,13 @@ assert int(out.strip()) == 42 assert int(err.strip()) == 23 + assert isinstance(item.parent, py.test.collect.Module) + out, err = item.parent.getouterr() + assert out.find('module level output') != -1 + allout = self.file.getvalue() + print "allout:", allout + assert allout.find('module level output') != -1 + def test_raises_output(self): o = tmpdir.ensure('raisestest', dir=1) tfile = o.join('test_raisesoutput.py') From hpk at codespeak.net Tue May 3 15:02:43 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 3 May 2005 15:02:43 +0200 (CEST) Subject: [py-svn] r11855 - in py/dist/py/test: . testing Message-ID: <20050503130243.0218A27B42@code1.codespeak.net> Author: hpk Date: Tue May 3 15:02:43 2005 New Revision: 11855 Modified: py/dist/py/test/defaultconftest.py py/dist/py/test/session.py py/dist/py/test/testing/test_session.py Log: flesh out keyword support, you can supply a google-style keyword expression, e.g. py.test -k "something -otherthing" will run tests that have something but not otherthing in their test name. Modified: py/dist/py/test/defaultconftest.py ============================================================================== --- py/dist/py/test/defaultconftest.py (original) +++ py/dist/py/test/defaultconftest.py Tue May 3 15:02:43 2005 @@ -23,7 +23,7 @@ help="disable catching of sys.stdout/stderr output."), Option('-k', action="store", dest="keyword", default='', - help="select tests if they match the given keyword"), + help="only run test items matching the given (google-style) keyword expression"), Option('-l', '--showlocals', action="store_true", dest="showlocals", default=False, help="show locals in tracebacks (disabled by default)"), Modified: py/dist/py/test/session.py ============================================================================== --- py/dist/py/test/session.py (original) +++ py/dist/py/test/session.py Tue May 3 15:02:43 2005 @@ -84,18 +84,29 @@ finally: colitem.finishcapture() + def skipbykeyword(self, colitem): + keyword = self.config.option.keyword + if not keyword: + return + chain = colitem.listchain() + for key in filter(None, keyword.split()): + eor = key[:1] == '-' + if eor: + key = key[1:] + if not (eor ^ self._matchonekeyword(key, chain)): + py.test.skip("test not selected by keyword %r" %(keyword,)) + + def _matchonekeyword(self, key, chain): + for subitem in chain: + if subitem.haskeyword(key): + return True + return False + def run(self, colitem): if self.config.option.collectonly and isinstance(colitem, py.test.Item): return if isinstance(colitem, py.test.Item): - keyword = self.config.option.keyword - if keyword: - for subitem in colitem.listchain(): - if subitem.haskeyword(keyword): - break - else: - py.test.skip("test not selected by keyword %r" %(keyword,)) - + self.skipbykeyword(colitem) res = colitem.run() if res is None: return py.test.Item.Passed() 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 May 3 15:02:43 2005 @@ -66,17 +66,21 @@ import py class Class(py.test.collect.Class): def haskeyword(self, keyword): - return keyword == 'XXX' or \ + return keyword == 'xxx' or \ super(Class, self).haskeyword(keyword) """)) - config, args = py.test.Config.parse(['-k', 'XXX']) - session = config.getsessionclass()(config) - session.main([o]) - l = session.getitemoutcomepairs(py.test.Item.Passed) - assert len(l) == 1 - assert l[0][0].name == 'test_2' - l = session.getitemoutcomepairs(py.test.Item.Skipped) - assert l[0][0].name == 'test_1' + for keyword in ('xxx', 'xxx test_2', 'TestClass', 'xxx -test_1', + 'TestClass test_2', 'xxx TestClass test_2',): + f = StringIO() + config, args = py.test.Config.parse(['-k', keyword]) + session = config.getsessionclass()(config, f) + session.main([o]) + print "keyword", repr(keyword) + l = session.getitemoutcomepairs(py.test.Item.Passed) + assert len(l) == 1 + assert l[0][0].name == 'test_2' + l = session.getitemoutcomepairs(py.test.Item.Skipped) + assert l[0][0].name == 'test_1' #f = open('/tmp/logfile', 'wa') class TestTerminalSession: @@ -182,6 +186,10 @@ def test_capturing(): print 42 print >>py.std.sys.stderr, 23 + def test_capturing_error(): + print 1 + print >>py.std.sys.stderr, 2 + raise ValueError """)) conftest = o.join('conftest.py').write(py.code.Source(""" import py @@ -209,7 +217,8 @@ assert out.find('module level output') != -1 allout = self.file.getvalue() print "allout:", allout - assert allout.find('module level output') != -1 + assert allout.find('module level output') != -1, ( + "session didn't show module output") def test_raises_output(self): o = tmpdir.ensure('raisestest', dir=1) From hpk at codespeak.net Tue May 3 22:29:57 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 3 May 2005 22:29:57 +0200 (CEST) Subject: [py-svn] r11902 - py/dist/py/documentation Message-ID: <20050503202957.1C96327B85@code1.codespeak.net> Author: hpk Date: Tue May 3 22:29:56 2005 New Revision: 11902 Modified: py/dist/py/documentation/test.txt Log: shift select documentation towards the collections features Modified: py/dist/py/documentation/test.txt ============================================================================== --- py/dist/py/documentation/test.txt (original) +++ py/dist/py/documentation/test.txt Tue May 3 22:29:56 2005 @@ -112,6 +112,23 @@ to get run, notably ``check(42)``, ``check(17)`` and ``check(49)`` of which the middle one will obviously fail. +selecting tests by keyword +-------------------------- + +You can selectively run tests by specifiying a keyword +on the command line. Example:: + + py.test -k test_simple + +will run all tests that are found from the current directory +and where the word "test_simple" equals the start of one part of the +path leading up to the test item. Directory and file basenames as well +as function, class and function/method names each form a possibly +matching name. + + Note that the exact semantics are still experimental but + should always remain intuitive. + testing with multiple python versions / executables --------------------------------------------------- @@ -130,21 +147,6 @@ .. _`selection by keyword`: -selecting tests by keyword --------------------------- - -You can selectively run tests by specifiying a keyword -on the command line. Example:: - - py.test -k test_simple - -will run all tests that are found from the current directory -and where the word "test_simple" equals the start of one part of the -path leading up to the test item. Directory and file basenames as well -as function, class and function/method names each form a possibly -matching name. (Note that the exact semantics are still -experimental but should always remain intuitive). - no interference with cmdline utilities -------------------------------------- From hpk at codespeak.net Thu May 5 00:29:28 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 5 May 2005 00:29:28 +0200 (CEST) Subject: [py-svn] r11950 - in py/dist/py/test: . testing Message-ID: <20050504222928.5A14827B5D@code1.codespeak.net> Author: hpk Date: Thu May 5 00:29:28 2005 New Revision: 11950 Modified: py/dist/py/test/collect.py py/dist/py/test/testing/test_collect.py Log: ignore CVS and _darcs directories Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Thu May 5 00:29:28 2005 @@ -229,7 +229,8 @@ (pb.startswith('test_') or pb.endswith('_test'))) def recfilter(self, path): - return path.check(dotfile=0) + return path.check(dotfile=0) and \ + path.basename not in ('CVS', '_darcs') def run(self): l = self.fspath.listdir() Modified: py/dist/py/test/testing/test_collect.py ============================================================================== --- py/dist/py/test/testing/test_collect.py (original) +++ py/dist/py/test/testing/test_collect.py Thu May 5 00:29:28 2005 @@ -49,6 +49,20 @@ assert l[0] == 'test_one' assert l[1] == 'TestClass' +def test_ignored_certain_directories(): + tmp = py.test.ensuretemp("ignore_certain_directories") + tmp.ensure("_darcs", 'test_notfound.py') + tmp.ensure("CVS", 'test_notfound.py') + tmp.ensure(".whatever", 'test_notfound.py') + tmp.ensure("normal", 'test_found.py') + tmp.ensure('test_found.py') + + colitem = py.test.collect.Directory(tmp) + items = list(colitem.tryiter(stopitems=(py.test.collect.Module,))) + assert len(items) == 2 + for item in items: + assert item.name == 'test_found.py' + def test_getnum(): fn = datadir / 'filetest.py' col = py.test.collect.Module(fn) From hpk at codespeak.net Thu May 5 00:31:26 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 5 May 2005 00:31:26 +0200 (CEST) Subject: [py-svn] r11951 - in py/dist/py/test: . testing Message-ID: <20050504223126.E05C027B5D@code1.codespeak.net> Author: hpk Date: Thu May 5 00:31:26 2005 New Revision: 11951 Modified: py/dist/py/test/collect.py py/dist/py/test/testing/test_collect.py Log: also add {arch} to the list of ignored directories Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Thu May 5 00:31:26 2005 @@ -230,7 +230,7 @@ def recfilter(self, path): return path.check(dotfile=0) and \ - path.basename not in ('CVS', '_darcs') + path.basename not in ('CVS', '_darcs', '{arch}') def run(self): l = self.fspath.listdir() Modified: py/dist/py/test/testing/test_collect.py ============================================================================== --- py/dist/py/test/testing/test_collect.py (original) +++ py/dist/py/test/testing/test_collect.py Thu May 5 00:31:26 2005 @@ -53,6 +53,7 @@ tmp = py.test.ensuretemp("ignore_certain_directories") tmp.ensure("_darcs", 'test_notfound.py') tmp.ensure("CVS", 'test_notfound.py') + tmp.ensure("{arch}", 'test_notfound.py') tmp.ensure(".whatever", 'test_notfound.py') tmp.ensure("normal", 'test_found.py') tmp.ensure('test_found.py') From hpk at codespeak.net Thu May 5 00:38:13 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 5 May 2005 00:38:13 +0200 (CEST) Subject: [py-svn] r11952 - py/dist/py/test Message-ID: <20050504223813.579C127B5D@code1.codespeak.net> Author: hpk Date: Thu May 5 00:38:13 2005 New Revision: 11952 Modified: py/dist/py/test/collect.py Log: fixed module docstring Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Thu May 5 00:38:13 2005 @@ -3,24 +3,19 @@ Collectors and test items form a tree. The difference 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. -All collectors and items have a "name" and +is smalll. Collectors usually return a list of child +collectors/items whereas items usually return None +indicating a successful test run. - names = [x.name for x in colitem. - -so that driving reporting can uniformly represent item - -The is a schematic example of the tree of collectors and test items:: +The is a schematic example of a tree of collectors and test items:: Directory Module Class - Instance # empty setup/teardown + Instance Function Generator - ANY + ... Function Generator Function From hpk at codespeak.net Thu May 5 03:05:30 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 5 May 2005 03:05:30 +0200 (CEST) Subject: [py-svn] r11957 - in py/dist/py/test: . terminal testing Message-ID: <20050505010530.4D79427B60@code1.codespeak.net> Author: hpk Date: Thu May 5 03:05:30 2005 New Revision: 11957 Modified: 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 Log: - add new startiteration() protocol that allows sessions to report the number of to-be-iterated items cleanlier. so we get rid of colitem.getnum() and sanitize colitem.tryiter() Maybe it would a good idea to implement something like a 'lazily computed collector tree' so that if reporting code actually does look ahead via colitem.run()/join() it would actually precompute a part of the tree which the following real run could work with. Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Thu May 5 03:05:30 2005 @@ -172,35 +172,17 @@ newl.append(x.name) return ".".join(newl) - def getnum(self, stopitems=()): - """ return the number of 'stopitems' instances on this collector. - if any collector down the chain fails, -1 is returned. + def tryiter(self, stopitems=None): + """ yield stop item instances from flattening the collector. """ - 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 stopitems is None: + stopitems = py.test.Item 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 self.Outcome, e: - self._stickyfailure = e - yield e - except: - x = py.test.Item.Failed(excinfo=py.code.ExceptionInfo()) - self._stickyfailure = x - yield x + for x in self.run(): + for y in self.join(x).tryiter(stopitems): + yield y captured_out = captured_err = None def startcapture(self): @@ -286,7 +268,16 @@ try: return self._obj except AttributeError: - self._obj = obj = self.fspath.pyimport() + failure = getattr(self, '_stickyfailure', None) + if failure is not None: + raise failure[0], failure[1], failure[2] + try: + self._obj = obj = self.fspath.pyimport() + except KeyboardInterrupt: + raise + except: + self._stickyfailure = py.std.sys.exc_info() + raise return obj obj = property(obj, None, None, "module object") Modified: py/dist/py/test/session.py ============================================================================== --- py/dist/py/test/session.py (original) +++ py/dist/py/test/session.py Thu May 5 03:05:30 2005 @@ -29,6 +29,9 @@ def finish(self, colitem, outcome): self._memo.append((colitem, outcome)) + def startiteration(self, colitem, subitems): + pass + def getitemoutcomepairs(self, cls): return [x for x in self._memo if isinstance(x[1], cls)] @@ -81,9 +84,34 @@ isinstance(outcome, (list, colitem.Outcome))) finally: self.finish(colitem, outcome) + if isinstance(outcome, colitem.Failed) and self.config.option.exitfirst: + py.test.exit("exit on first problem configured.", item=colitem) finally: colitem.finishcapture() + def run(self, colitem): + if self.config.option.collectonly and isinstance(colitem, py.test.Item): + return + if isinstance(colitem, py.test.Item): + self.skipbykeyword(colitem) + res = colitem.run() + if res is None: + return py.test.Item.Passed() + elif not isinstance(res, (list, tuple)): + raise TypeError("%r.run() returned neither " + "list, tuple nor None: %r" % (colitem, res)) + else: + finish = self.startiteration(colitem, res) + try: + for name in res: + obj = colitem.join(name) + assert obj is not None + self.runtraced(obj) + finally: + if finish: + finish() + return res + def skipbykeyword(self, colitem): keyword = self.config.option.keyword if not keyword: @@ -102,26 +130,6 @@ return True return False - def run(self, colitem): - if self.config.option.collectonly and isinstance(colitem, py.test.Item): - return - if isinstance(colitem, py.test.Item): - self.skipbykeyword(colitem) - res = colitem.run() - 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 name in seq: - obj = colitem.join(name) - assert obj is not None - self.runtraced(obj) - return res def _map2colitems(items): # first convert all path objects into collectors Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Thu May 5 03:05:30 2005 @@ -57,12 +57,19 @@ if self.config.option.verbose == 0: 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,)) else: self.out.line() self.out.line("+ testmodule: %s" % colitem.fspath) + def startiteration(self, colitem, subitems): + if (isinstance(colitem, py.test.collect.Module) + and self.config.option.verbose == 0): + sum = 0 + for sub in subitems: + sum += len(list(colitem.join(sub).tryiter())) + self.out.write('[%d] ' % sum) + return self.out.line + def start_Item(self, colitem): if self.config.option.verbose >= 1: if isinstance(colitem, py.test.Item): @@ -86,16 +93,7 @@ import pdb 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 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: - self.out.line() - return - else: + if isinstance(colitem, py.test.Item): if self.config.option.verbose >= 1: resultstring = self.repr_progress_long_result(colitem, outcome) resultstring += " (%.2f)" % (colitem.elapsedtime,) @@ -104,6 +102,7 @@ c = self.repr_progress_short_result(colitem, outcome) self.out.write(c) + # ------------------- # HEADER information # ------------------- Modified: py/dist/py/test/testing/test_collect.py ============================================================================== --- py/dist/py/test/testing/test_collect.py (original) +++ py/dist/py/test/testing/test_collect.py Thu May 5 03:05:30 2005 @@ -5,13 +5,12 @@ tmpdir = py.test.ensuretemp('test_collect') def test_failing_import_execfile(): - py.test.skip("memoizing exceptions needs refactoring") fn = datadir / 'failingimport.py' 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) + py.test.raises(ImportError, col.run) + py.test.raises(ImportError, col.run) def XXXtest_finds_root(): fn = datadir / 'filetest.py' @@ -25,22 +24,6 @@ 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) @@ -59,22 +42,11 @@ tmp.ensure('test_found.py') colitem = py.test.collect.Directory(tmp) - items = list(colitem.tryiter(stopitems=(py.test.collect.Module,))) + items = list(colitem.tryiter(py.test.collect.Module)) assert len(items) == 2 for item in items: assert item.name == 'test_found.py' -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): filefilter = py.path.checker(fnmatch='testspecial*.py') @@ -201,7 +173,7 @@ for x in (o, checkfile, checkfile.dirpath()): #print "checking that %s returns custom items" % (x,) col = getfscollector(x) - assert col.getnum(py.test.Item) == 2 + assert len(list(col.tryiter(py.test.Item))) == 2 #assert items[1].__class__.__name__ == 'MyFunction' # test that running a session works from the directories From hpk at codespeak.net Thu May 5 03:19:02 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 5 May 2005 03:19:02 +0200 (CEST) Subject: [py-svn] r11959 - py/dist/py/test/terminal Message-ID: <20050505011902.EE8AC27B64@code1.codespeak.net> Author: hpk Date: Thu May 5 03:19:02 2005 New Revision: 11959 Modified: py/dist/py/test/terminal/terminal.py Log: handle failed module imports more gracefully regarding reporting Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Thu May 5 03:19:02 2005 @@ -93,6 +93,9 @@ import pdb self.out.rewrite('\n%s\n' % (outcome.excinfo.exconly(),)) pdb.post_mortem(outcome.excinfo._excinfo[2]) + if (isinstance(outcome, py.test.Item.Failed) and + isinstance(colitem, py.test.collect.Module)): + self.out.line(" FAILED TO LOAD MODULE") if isinstance(colitem, py.test.Item): if self.config.option.verbose >= 1: resultstring = self.repr_progress_long_result(colitem, outcome) From arigo at codespeak.net Fri May 6 12:00:32 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 6 May 2005 12:00:32 +0200 (CEST) Subject: [py-svn] r12004 - py/dist/py/test/terminal Message-ID: <20050506100032.958D527B5D@code1.codespeak.net> Author: arigo Date: Fri May 6 12:00:32 2005 New Revision: 12004 Modified: py/dist/py/test/terminal/terminal.py Log: The rules about what Session.finish() can receive as an 'outcome' are unclear: some assert say it can be None, but TerminalSession crashes then. Quick fix for now. The problem shows up in PyPy's translator/test/test_geninterp.py rev 11976. Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Fri May 6 12:00:32 2005 @@ -177,14 +177,16 @@ if isinstance(outcome, outcometype): return char else: - raise TypeError, "not an Outomce instance: %r" % (outcome,) + #raise TypeError, "not an Outomce instance: %r" % (outcome,) + return '?' 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 an Outcome instance: %r" % (outcome,) + #raise TypeError, "not an Outcome instance: %r" % (outcome,) + return 'UNKNOWN' # -------------------- # summary information From arigo at codespeak.net Fri May 6 12:07:23 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 6 May 2005 12:07:23 +0200 (CEST) Subject: [py-svn] r12005 - py/dist/py/magic Message-ID: <20050506100723.BEDF927B5D@code1.codespeak.net> Author: arigo Date: Fri May 6 12:07:23 2005 New Revision: 12005 Modified: py/dist/py/magic/assertion.py Log: User code can call AssertionError with a non-string argument, but py.test expects the .msg attribute to be a string. (The problem shows up e.g. in PyPy's translator/test/test_geninterp.py rev 11976.) Modified: py/dist/py/magic/assertion.py ============================================================================== --- py/dist/py/magic/assertion.py (original) +++ py/dist/py/magic/assertion.py Fri May 6 12:07:23 2005 @@ -8,7 +8,7 @@ def __init__(self, *args): BuiltinAssertionError.__init__(self, *args) if args: - self.msg = args[0] + self.msg = str(args[0]) else: f = sys._getframe(1) try: From hpk at codespeak.net Fri May 6 12:36:57 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 6 May 2005 12:36:57 +0200 (CEST) Subject: [py-svn] r12006 - in py/dist/py/test: . terminal Message-ID: <20050506103657.15EF427B5D@code1.codespeak.net> Author: hpk Date: Fri May 6 12:36:56 2005 New Revision: 12006 Modified: py/dist/py/test/collect.py py/dist/py/test/item.py py/dist/py/test/terminal/remote.py Log: make building the collector's tree more persistent by introducing "buildname2items()" which is responsible for building a dictionary mapping names to collectors/items. the default run()/join() methods now build this dictionary and cache it. The net result is that py lib's own and PyPy's test runs run some 10% faster and are somewhat saner! Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Fri May 6 12:36:56 2005 @@ -41,19 +41,17 @@ raise py.error.ENOENT(fspath) pkgpath = fspath.pypkgpath() if pkgpath is None: - if fspath.check(file=1): - clsname = 'Module' - else: - clsname = 'Directory' + clsname = fspath.check(file=1) and 'Module' or '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)): - #print "joining", name + names = fspath.relto(pkgpath).split(fspath.sep) + for name in names: current = current.join(name) + assert current, "joining %r resulted in None!" % (names,) top = current.listchain()[0] #top._config = config return current @@ -104,6 +102,12 @@ except AttributeError: return False + def __cmp__(self, other): + s1 = self.getsortvalue() + s2 = other.getsortvalue() + #print "cmp", s1, s2 + return cmp(s1, s2) + def obj(): def fget(self): try: @@ -119,13 +123,6 @@ def _getobj(self): return getattr(self.parent.obj, self.name) - def sortvalue(self): - """ sorting function helper to bring test methods in - the same order as in their file. - """ - for x in self.run(): - return 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] @@ -184,6 +181,26 @@ for y in self.join(x).tryiter(stopitems): yield y + def _prepare(self): + if not hasattr(self, 'name2items'): + self.name2items = self.buildname2items() + + def buildname2items(self): + raise NotImplementedError, "abstract" + + def getsortvalue(self): + return self.name + + def run(self): + self._prepare() + itemlist = self.name2items.values() + itemlist.sort() + return [x.name for x in itemlist] + + def join(self, name): + self._prepare() + return self.name2items.get(name, None) + captured_out = captured_err = None def startcapture(self): return None # by default collectors don't capture output @@ -194,8 +211,7 @@ class FSCollector(Collector): def __init__(self, fspath, parent=None): - if isinstance(fspath, str): - fspath = py.path.local(fspath) + fspath = py.path.local(fspath) super(FSCollector, self).__init__(fspath.basename, parent) self.fspath = fspath @@ -209,20 +225,27 @@ return path.check(dotfile=0) and \ path.basename not in ('CVS', '_darcs', '{arch}') - def run(self): - l = self.fspath.listdir() - l.sort() - 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))] + def buildname2items(self): + d = {} + for p in self.fspath.listdir(): + x = self.makeitem(p.basename, self.filefilter, self.recfilter) + if x is not None: + d[p.basename] = x + return d + + def makeitem(self, basename, filefilter=None, recfilter=None): + p = self.fspath.join(basename) + if p.check(file=1) and (not filefilter or filefilter(p)): + return self.Module(p, parent=self) + elif p.check(dir=1) and (not recfilter or recfilter(p)): + Directory = py.test.Config.getvalue('Directory', p) + return Directory(p, parent=self) def join(self, name): - x = self.fspath.join(name) - if x.check(file=1): - return self.Module(x, parent=self) - elif x.check(dir=1): - Directory = py.test.Config.getvalue('Directory', x) - return Directory(x, parent=self) + x = super(Directory, self).join(name) + if x is None: + x = self.makeitem(name) + return x class PyCollectorMixin(object): def funcnamefilter(self, name): @@ -230,28 +253,20 @@ def classnamefilter(self, name): return name.startswith('Test') - def run(self): - l = [] + def buildname2items(self): + d = {} for name in dir(self.obj): - if self.funcnamefilter(name) or self.classnamefilter(name): - x = self.join(name) - if x is not None: - l.append((x.sortvalue(), name)) - l.sort() - return [x[1] for x in l] - - def join(self, name): - obj = getattr(self.obj, name) - if isclass(obj): - return self.Class(name, parent=self) - elif callable(obj): - if obj.func_code.co_flags & 32: # generator function - return self.Generator(name, parent=self) - else: - return self.Function(name, parent=self) + obj = getattr(self.obj, name) + if self.classnamefilter(name) and isclass(obj): + d[name] = self.Class(name, parent=self) + elif self.funcnamefilter(name) and callable(obj): + if obj.func_code.co_flags & 32: # generator function + d[name] = self.Generator(name, parent=self) + else: + d[name] = self.Function(name, parent=self) + return d class Module(PyCollectorMixin, FSCollector): - def startcapture(self): if not self.option.nocapture and not self.option.usepdb: self._capture = SimpleOutErrCapture() @@ -289,6 +304,7 @@ if hasattr(self.obj, 'teardown_module'): self.obj.teardown_module(self.obj) + class Class(PyCollectorMixin, Collector): def run(self): if getattr(self.obj, 'disabled', 0): @@ -311,6 +327,10 @@ teardown_class = getattr(teardown_class, 'im_func', teardown_class) teardown_class(self.obj) + def getsortvalue(self): + for x in self.tryiter((py.test.collect.Generator, py.test.Item)): + return x.getsortvalue() + class Instance(PyCollectorMixin, Collector): def _getobj(self): return self.parent.obj() @@ -320,37 +340,16 @@ Function = property(Function) class Generator(Collector): - def run(self): - #def iterator(): - # for i,x in py.builtin.enumerate(self.obj()): - # yield self.join("[%d]" % i) - #return py.builtin.collect(iterator()) - self._objlist = l = [] - namelist = [] + def buildname2items(self): + d = {} for i, x in py.builtin.enumerate(self.obj()): - call,args = self.getcallargs(x) + call, args = self.getcallargs(x) if not callable(call): raise TypeError("yielded test %r not callable" %(call,)) - l.append(x) - namelist.append("[%d]" % i) - return namelist - - def join(self, name): - if name[:1] != '[' or name[-1:] != ']': - raise NameError("%r is not an index" %(name,)) - num = int(name[1:-1]) - try: - objlist = self._objlist - except AttributeError: - self._objlist = objlist = list(self.obj()) - for i, x in py.builtin.enumerate(objlist): - if i == num: - if isinstance(x, (Collector, )): - return x - call, args = self.getcallargs(x) - assert callable(call) - return self.Function(name, self, args, obj=call) - + name = "[%d]" % i + d[name] = self.Function(name, self, args, obj=call) + return d + def getcallargs(self, obj): if isinstance(obj, (tuple, list)): call, args = obj[0], obj[1:] @@ -362,5 +361,5 @@ code = py.code.Code(self.obj) return code.path, code.firstlineno - def sortvalue(self): + def getsortvalue(self): return self.getpathlineno() Modified: py/dist/py/test/item.py ============================================================================== --- py/dist/py/test/item.py (original) +++ py/dist/py/test/item.py Fri May 6 12:36:56 2005 @@ -58,7 +58,7 @@ code = py.code.Code(self.obj) return code.path, code.firstlineno - def sortvalue(self): + def getsortvalue(self): return self.getpathlineno() def run(self): Modified: py/dist/py/test/terminal/remote.py ============================================================================== --- py/dist/py/test/terminal/remote.py (original) +++ py/dist/py/test/terminal/remote.py Fri May 6 12:36:56 2005 @@ -97,17 +97,20 @@ channel.send((args, failures)) return waitfinish(channel) +def generalize(p1, p2): + general = p1 + for x, y in zip(p1.parts(), p2.parts()): + if x != y: + break + general = x + return general + 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 - p =reduce(generalize, tops) + tops = [] + for arg in args: + p = py.path.local(arg) + tops.append(p.pypkgpath() or p) + p = reduce(generalize, tops) if p.check(file=1): p = p.dirpath() return p From hpk at codespeak.net Fri May 6 12:39:52 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 6 May 2005 12:39:52 +0200 (CEST) Subject: [py-svn] r12007 - py/dist/py/magic/testing Message-ID: <20050506103952.21D2327B5D@code1.codespeak.net> Author: hpk Date: Fri May 6 12:39:51 2005 New Revision: 12007 Modified: py/dist/py/magic/testing/test_assertion.py Log: added a test for armin's fix Modified: py/dist/py/magic/testing/test_assertion.py ============================================================================== --- py/dist/py/magic/testing/test_assertion.py (original) +++ py/dist/py/magic/testing/test_assertion.py Fri May 6 12:39:51 2005 @@ -57,3 +57,12 @@ except AssertionError, e: s = str(e) assert s.startswith('assert 2 ==') + +def test_assert_non_string_message(): + class A: + def __str__(self): + return "hello" + try: + assert 0 == 1, A() + except AssertionError, e: + assert e.msg == "hello" From hpk at codespeak.net Fri May 6 12:41:30 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 6 May 2005 12:41:30 +0200 (CEST) Subject: [py-svn] r12008 - py/dist/py/test Message-ID: <20050506104130.7EC7427B5D@code1.codespeak.net> Author: hpk Date: Fri May 6 12:41:30 2005 New Revision: 12008 Modified: py/dist/py/test/collect.py Log: it doesn't really make sense to regard the name2items cache as a public attribute, make that clear for now Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Fri May 6 12:41:30 2005 @@ -182,8 +182,8 @@ yield y def _prepare(self): - if not hasattr(self, 'name2items'): - self.name2items = self.buildname2items() + if not hasattr(self, '_name2items'): + self._name2items = self.buildname2items() def buildname2items(self): raise NotImplementedError, "abstract" @@ -193,13 +193,13 @@ def run(self): self._prepare() - itemlist = self.name2items.values() + itemlist = self._name2items.values() itemlist.sort() return [x.name for x in itemlist] def join(self, name): self._prepare() - return self.name2items.get(name, None) + return self._name2items.get(name, None) captured_out = captured_err = None def startcapture(self): From jan at codespeak.net Fri May 6 13:37:14 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 6 May 2005 13:37:14 +0200 (CEST) Subject: [py-svn] r12009 - in py/dist/py/test/tkinter: . testing Message-ID: <20050506113714.22DE727B5D@code1.codespeak.net> Author: jan Date: Fri May 6 13:37:13 2005 New Revision: 12009 Modified: py/dist/py/test/tkinter/backend.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_util.py py/dist/py/test/tkinter/tkgui.py py/dist/py/test/tkinter/util.py Log: * some (internal) renaming and refactoring * displays number of passed tests Modified: py/dist/py/test/tkinter/backend.py ============================================================================== --- py/dist/py/test/tkinter/backend.py (original) +++ py/dist/py/test/tkinter/backend.py Fri May 6 13:37:13 2005 @@ -8,7 +8,6 @@ class TestRepository(repository.Repository): '''stores only TestReport''' - disabled = True ReportClass = util.TestReport failed_id = [repr(ReportClass.Status.Failed())] skipped_id = [repr(ReportClass.Status.Skipped())] @@ -69,28 +68,65 @@ ReportClass = util.TestReport def __init__(self): - self._reports = [] + self._reports = {} + self._repository = TestRepository() + self.root = self.ReportClass() + #self.add(self.ReportClass()) def add(self, report): - self._reports.append(report) + if not self._update_root(report): + self._reports[tuple(report.full_id)] = report + self._repository.add_report(report) + + def _update_root(self, report): + if report.id == self.ReportClass.root_id: + self.root = report + return True + else: + self.root.status.update(report.status) + return False + def add_report_from_channel(self, report_str): + report = self.ReportClass.fromChannel(report_str) + self.add(report) + return report.full_id[:] + def get(self, **kwargs): - filter_dict = {'failed': self._select_failed } + filter_dict = {'failed': self._select_failed, + 'skipped': self._select_skipped, + 'passed_item': self._select_passed_item, + 'id': self._select_by_id} + + if kwargs.get('id', None) == tuple(): + return [self.root] + 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)]) + functions = [filter_dict[name] for name in kwargs.keys() + if filter_dict.has_key(name)] + for report in self._reports.values(): + if False not in [function(report, **kwargs) + for function in functions]: + selected_reports.append(report) return selected_reports - def _select_failed(self, report): - return report.status == self.ReportClass.Status.Failed() + def _select_failed(self, report, **kwargs): + return report.status == self.ReportClass.Status.Failed() and report.error_report != '' + + def _select_skipped(self, report, **kwargs): + return report.status == self.ReportClass.Status.Skipped() + def _select_passed_item(self, report, **kwargs): + return report.status == self.ReportClass.Status.Passed() and report.is_item == True -class RepositoryBackend: + def _select_by_id(self, report, **kwargs): + id = kwargs['id'] + return report.full_id == id + +class ReportBackend: def __init__(self, config = Null()): self.testrepository = TestRepository() + self.reportstore = ReportStore() self.channel = Null() self.waitfinish_thread = Null() self.queue = py.std.Queue.Queue() @@ -115,6 +151,9 @@ '''return the repository''' return self.testrepository + def get_store(self): + return self.reportstore + def set_message_callback(self, callback = Null()): self._message_callback = callback @@ -129,7 +168,8 @@ report_str = self.queue.get(0) id = report_str if report_str is not None: - id = self.testrepository.add_report_from_channel(report_str) + id = self.testrepository.add_report_from_channel(report_str) + self.reportstore.add_report_from_channel(report_str) changed_report_ids.append(id) self._message_callback(id) except py.std.Queue.Empty: @@ -142,6 +182,7 @@ if config is None: config = self.config self.testrepository = TestRepository() + self.reportstore = ReportStore() self.gateway = py.execnet.PopenGateway(config.option.executable) self.channel = self.gateway.newchannel(receiver = self.queue.put) self.gateway.remote_exec(channel = self.channel, source = ''' 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 Fri May 6 13:37:13 2005 @@ -1,8 +1,9 @@ import py from py.__.test.tkinter import backend -RepositoryBackend = backend.RepositoryBackend +RepositoryBackend = backend.ReportBackend TestRepository = backend.TestRepository +ReportStore = backend.ReportStore datadir = py.magic.autopath().dirpath('data') from cStringIO import StringIO @@ -41,6 +42,95 @@ id = repos.add_report_from_channel(report.to_channel()) assert id == full_id assert repos.haskey(full_id) + + +class TestReportStore: + + def setup_method(self, method): + self.store = ReportStore() + + self.report_failed = ReportStore.ReportClass() + self.report_failed.status = ReportStore.ReportClass.Status.Failed() + self.report_failed.full_id = ('report_failed') + self.report_failed.id = '1' + + self.report_failed_item = self.report_failed.copy() + self.report_failed_item.error_report = 'Error' + self.report_failed_item.full_id = ('report_failed_item') + self.report_failed_item.id = '1' + + self.report_skipped = ReportStore.ReportClass() + self.report_skipped.status = ReportStore.ReportClass.Status.Skipped() + self.report_skipped.full_id = ('report_skipped') + self.report_skipped.id = '1' + + self.report_passed = ReportStore.ReportClass() + self.report_passed.status = ReportStore.ReportClass.Status.Passed() + self.report_passed.full_id = ('report_passed') + self.report_passed.id = '1' + + self.report_passed_item = self.report_passed.copy() + self.report_passed_item.is_item = True + self.report_passed_item.full_id = ('report_passed_item') + self.report_passed_item.id = '1' + + def fill_store(self): + self.store.add(self.report_failed) + self.store.add(self.report_failed_item) + self.store.add(self.report_skipped) + self.store.add(self.report_passed) + self.store.add(self.report_passed_item) + + def test_get_failed(self): + self.fill_store() + print self.store._reports.keys() + assert len(self.store.get(failed = True)) == 1 + assert self.store.get(failed = True) == [self.report_failed_item] + + def test_get_skipped(self): + self.fill_store() + print self.store._reports.keys() + assert len(self.store.get(skipped = True)) == 1 + assert self.store.get(skipped = True) == [self.report_skipped] + + def test_get_passed_item(self): + self.fill_store() + assert len(self.store.get(passed_item = True)) == 1 + assert self.store.get(passed_item = True) == [self.report_passed_item] + + def test_select_failed(self): + assert self.store._select_failed(self.report_failed) == False + assert self.store._select_failed(self.report_failed_item) == True + assert self.store._select_failed(self.report_skipped) == False + + def test_select_skipped(self): + assert self.store._select_skipped(self.report_failed) == False + assert self.store._select_skipped(self.report_skipped) == True + + def test_select_passed_item(self): + assert self.store._select_passed_item(self.report_failed) == False + assert self.store._select_passed_item(self.report_skipped) == False + assert self.store._select_passed_item(self.report_passed) == False + assert self.store._select_passed_item(self.report_passed_item) == True + + def test_select_by_id(self): + assert self.store._select_by_id(self.report_passed_item, + id = ['id']) == False + id = ['my', 'special', 'report', 'id'] + report = self.store.ReportClass() + report.full_id = id[:] + assert self.store._select_by_id(report, id = id) == True + assert self.store._select_by_id(report, id = id[:-1]) == False + + def test_add_report_from_channel(self): + full_id = ['start', 'next', 'end'] + report = ReportStore.ReportClass() + report.full_id = full_id + + id = self.store.add_report_from_channel(report.to_channel()) + assert id == full_id + + class TestRepositoryBackend: 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 Fri May 6 13:37:13 2005 @@ -32,7 +32,7 @@ assert self.channel.sendlist != [] report = TestReport.fromChannel(self.channel.sendlist[0]) assert report.status == Status.NotExecuted() - assert report.id == 'Root' + assert report.id == TestReport.root_id assert report.label == 'Root' def test_footer_sends_report_and_None(self): @@ -43,7 +43,7 @@ assert self.channel.sendlist[-1] is None report = TestReport.fromChannel(self.channel.sendlist[-2]) assert report.status == Status.NotExecuted() - assert report.id == 'Root' + assert report.id == TestReport.root_id ## def test_status_is_passed_to_root(self): ## self.session.header(self.collitems) 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 Fri May 6 13:37:13 2005 @@ -61,7 +61,7 @@ def test_start(self): self.testresult.start(self.collector) - assert self.testresult.full_id == self.collector.listnames() + assert self.testresult.full_id == tuple(self.collector.listnames()) assert self.testresult.time != 0 assert self.testresult.status == Status.NotExecuted() @@ -73,13 +73,6 @@ 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): @@ -95,7 +88,13 @@ self.testresult.status = Status.Failed() assert result2.status != self.testresult.status - + + def test_is_item_attribute(self): + self.testresult.start(py.test.Item('test_is_item_attribute item', + parent = self.collector)) + assert self.testresult.is_item == True + self.testresult.start(self.collector) + assert self.testresult.is_item == False class Test_OutBuffer: Modified: py/dist/py/test/tkinter/tkgui.py ============================================================================== --- py/dist/py/test/tkinter/tkgui.py (original) +++ py/dist/py/test/tkinter/tkgui.py Fri May 6 13:37:13 2005 @@ -37,18 +37,17 @@ 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))) + def update(self, reportstore): + self.set_label('Failed', str(len(reportstore.get(failed = True)))) self._get_label('Failed').configure(bg = 'Red', fg = 'White') - skipped = testrepository.keys(testrepository.skipped_id) - self.set_label('Skipped', str(len(skipped))) + self.set_label('Skipped', str(len(reportstore.get(skipped = True)))) 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) + self.set_label('Passed', + str(len(reportstore.get(passed_item = True)))) + self._get_label('Passed').configure(bg = 'Green', fg = 'Black') + root_report = reportstore.root + if root_report.time < 7*24*60*60: + self.set_label('Time', '%0.2f seconds' % root_report.time) else: self.set_label('Time', '%0.2f seconds' % 0.0) @@ -98,16 +97,15 @@ 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) + def update_list(self, reportstore): + failed_childs = reportstore.get(failed = True) + skipped_childs = reportstore.get(skipped = True) 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) + for report in failed_childs + skipped_childs: label = '%s: %s' % (report.status, report.label) self.data[label] = report.full_id self.listbox.insert(Tkinter.END, label) @@ -213,7 +211,7 @@ self._should_stop = False #XXX needed? self._paths = [] - self.backend = backend.RepositoryBackend(config) + self.backend = backend.ReportBackend(config) self.createwidgets() self.timer_update() self.report_window = Null() @@ -239,7 +237,7 @@ 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()) + self.update_status(self.backend.get_store()) def show_error(self, report_id): report = self.backend.get_repository().find(report_id) @@ -262,7 +260,7 @@ def timer_update(self): self.backend.update() - self._parent.after(100, self.timer_update) + self._parent.after(200, self.timer_update) def start_tests(self, dont_care_event=None): if self.backend.running: @@ -272,17 +270,17 @@ self.backend.set_message_callback(self.message_callback) self.backend.start_tests(args = paths) - def update_status(self, repository): - self._statusbar.update(repository) + def update_status(self, reportstore): + self._statusbar.update(reportstore) bgcolor = 'White' fgcolor = 'Black' - root_status = repository.root_status() - if root_status == repository.ReportClass.Status.Passed(): + root_status = reportstore.root.status + if root_status == reportstore.ReportClass.Status.Passed(): bgcolor = 'Green' - elif root_status == repository.ReportClass.Status.Failed(): + elif root_status == reportstore.ReportClass.Status.Failed(): bgcolor = 'Red' fgcolor = 'White' - elif root_status == repository.ReportClass.Status.Skipped() : + elif root_status == reportstore.ReportClass.Status.Skipped() : bgcolor = 'Yellow' self._entry.entry.configure(bg = bgcolor, fg = fgcolor) @@ -290,14 +288,14 @@ if not report_ids: return repository = self.backend.get_repository() - self.update_status(repository) - self._reportlist.update_list(repository) + self.update_status(self.backend.get_store()) + self._reportlist.update_list(self.backend.get_store()) 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 + report_label = self.backend.get_store().get(id = report_id)[0].path self._reportlist.update_label(report_label) def set_paths(self, paths): Modified: py/dist/py/test/tkinter/util.py ============================================================================== --- py/dist/py/test/tkinter/util.py (original) +++ py/dist/py/test/tkinter/util.py Fri May 6 13:37:13 2005 @@ -114,10 +114,11 @@ class TestReport(object): '''"Channel-save" report of a py.test.Collector.Outcome instance''' + root_id = 'TestReport Root ID' template = {'time' : 0, 'label': 'Root', - 'id': 'Root', - 'full_id': [], + 'id': root_id, + 'full_id': tuple(), 'status': Status.NotExecuted(), 'report': 'NoReport', 'error_report': '', @@ -145,7 +146,7 @@ def start(self, collector): '''Session.start should call this to init the report''' - self.full_id = collector.listnames() + self.full_id = tuple(collector.listnames()) self.id = collector.name if collector.getpathlineno(): # save for Null() in test_util.py fspath, lineno = collector.getpathlineno() @@ -161,6 +162,7 @@ self.restart_params = (str(collector.listchain()[0].fspath), collector.listnames()) self.status = Status.NotExecuted() + self.is_item = isinstance(collector, py.test.Item) def finish(self, collector, res, config = Null()): '''Session.finish should call this to set the From ggheo at codespeak.net Fri May 6 23:20:21 2005 From: ggheo at codespeak.net (ggheo at codespeak.net) Date: Fri, 6 May 2005 23:20:21 +0200 (CEST) Subject: [py-svn] r12034 - py/dist/py/misc Message-ID: <20050506212021.9F82B27B4F@code1.codespeak.net> Author: ggheo Date: Fri May 6 23:20:21 2005 New Revision: 12034 Modified: py/dist/py/misc/_dist.py Log: Added win32-specific functionality: - add \Lib\site-packages\py\test\bin to Path registry value in Environment registry key - propagate changes to current command prompt Modified: py/dist/py/misc/_dist.py ============================================================================== --- py/dist/py/misc/_dist.py (original) +++ py/dist/py/misc/_dist.py Fri May 6 23:20:21 2005 @@ -1,8 +1,14 @@ import py -import sys +import sys, os from distutils import sysconfig from distutils import core +winextensions = 1 +if sys.platform == 'win32': + try: + import _winreg, win32gui, win32con + except ImportError: + winextensions = 0 class Params: """ a crazy hack to convince distutils to please @@ -65,7 +71,7 @@ # scripts.remove('py/bin/py.test') # -# helpers: +### helpers: def checknonsvndir(p): if p.basename != '.svn' and p.check(dir=1): return True @@ -85,6 +91,41 @@ print "data file ", x print +def addbindir2path(): + if sys.platform != 'win32' or not winextensions: + return + + # Add py/bin to PATH environment variable + bindir = os.path.join(sysconfig.get_python_lib(), "py", "bin") + reg = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) + key = r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment" + path = get_registry_value(reg, key, "Path") + path += ";" + bindir + print "Setting PATH to:", path + set_registry_value(reg, key, "Path", path) + #print "Current PATH is:", get_registry_value(reg, key, "Path") + + # Propagate changes throughout the system + win32gui.SendMessageTimeout(win32con.HWND_BROADCAST, + win32con.WM_SETTINGCHANGE, 0, "Environment", + win32con.SMTO_ABORTIFHUNG, 5000) + + # Propagate changes to current command prompt + os.system("set PATH=%s" % path) + +def get_registry_value(reg, key, value_name): + k = _winreg.OpenKey(reg, key) + value = _winreg.QueryValueEx(k, value_name)[0] + _winreg.CloseKey(k) + return value + +def set_registry_value(reg, key, value_name, value): + k = _winreg.OpenKey(reg, key, 0, _winreg.KEY_WRITE) + _winreg.SetValueEx(k, value_name, 0, _winreg.REG_SZ, value) + _winreg.CloseKey(k) + +### end helpers + def setup(pkg, **kw): """ invoke distutils on a given package. """ @@ -106,6 +147,7 @@ #py.std.pprint.pprint(kw) core.setup(**kw) if 'install' in sys.argv[1:]: + addbindir2path() x = params._rootdir.join('build') if x.check(): print "removing", x From hpk at codespeak.net Sat May 7 21:36:18 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 7 May 2005 21:36:18 +0200 (CEST) Subject: [py-svn] r12054 - py/dist/py/test Message-ID: <20050507193618.4941A27B4D@code1.codespeak.net> Author: hpk Date: Sat May 7 21:36:17 2005 New Revision: 12054 Modified: py/dist/py/test/collect.py Log: don't iterate over empty names Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Sat May 7 21:36:17 2005 @@ -48,7 +48,7 @@ Directory = py.test.Config.getvalue('Directory', pkgpath) current = Directory(pkgpath) #print "pkgpath", pkgpath - names = fspath.relto(pkgpath).split(fspath.sep) + names = filter(None, fspath.relto(pkgpath).split(fspath.sep)) for name in names: current = current.join(name) assert current, "joining %r resulted in None!" % (names,) From lac at strakt.com Sun May 8 01:59:37 2005 From: lac at strakt.com (Laura Creighton) Date: Sun, 08 May 2005 01:59:37 +0200 Subject: [py-svn] r12054 - py/dist/py/test In-Reply-To: Message from hpk@codespeak.net of "Sat, 07 May 2005 21:36:18 +0200." <20050507193618.4941A27B4D@code1.codespeak.net> References: <20050507193618.4941A27B4D@code1.codespeak.net> Message-ID: <200505072359.j47Nxbfw029203@theraft.strakt.com> In a message of Sat, 07 May 2005 21:36:18 +0200, hpk at codespeak.net writes: >Author: hpk >Date: Sat May 7 21:36:17 2005 >New Revision: 12054 > >Modified: > py/dist/py/test/collect.py >Log: >don't iterate over empty names > > >Modified: py/dist/py/test/collect.py >========================================================================= >===== >--- py/dist/py/test/collect.py (original) >+++ py/dist/py/test/collect.py Sat May 7 21:36:17 2005 >@@ -48,7 +48,7 @@ > Directory = py.test.Config.getvalue('Directory', pkgpath) > current = Directory(pkgpath) > #print "pkgpath", pkgpath >- names = fspath.relto(pkgpath).split(fspath.sep) >+ names = filter(None, fspath.relto(pkgpath).split(fspath.sep)) > for name in names: > current = current.join(name) > assert current, "joining %r resulted in None!" % (names,) >_______________________________________________ >py-svn mailing list >py-svn at codespeak.net >http://codespeak.net/mailman/listinfo/py-svn This seems to fail the 'straightforwardness test' .... any real reason we need filter there, or am i just lost? Laura From ggheo at codespeak.net Mon May 9 17:52:29 2005 From: ggheo at codespeak.net (ggheo at codespeak.net) Date: Mon, 9 May 2005 17:52:29 +0200 (CEST) Subject: [py-svn] r12121 - py/dist/py/misc Message-ID: <20050509155229.B313A27BE2@code1.codespeak.net> Author: ggheo Date: Mon May 9 17:52:29 2005 New Revision: 12121 Modified: py/dist/py/misc/_dist.py Log: Create win32 subdirectory of py\bin directory and put win32-specific batch files in it. Modified: py/dist/py/misc/_dist.py ============================================================================== --- py/dist/py/misc/_dist.py (original) +++ py/dist/py/misc/_dist.py Mon May 9 17:52:29 2005 @@ -96,7 +96,7 @@ return # Add py/bin to PATH environment variable - bindir = os.path.join(sysconfig.get_python_lib(), "py", "bin") + bindir = os.path.join(sysconfig.get_python_lib(), "py", "bin", "win32") reg = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) key = r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment" path = get_registry_value(reg, key, "Path") From ggheo at codespeak.net Mon May 9 17:53:35 2005 From: ggheo at codespeak.net (ggheo at codespeak.net) Date: Mon, 9 May 2005 17:53:35 +0200 (CEST) Subject: [py-svn] r12122 - py/dist/py/bin/win32 Message-ID: <20050509155335.BC3C827BE2@code1.codespeak.net> Author: ggheo Date: Mon May 9 17:53:35 2005 New Revision: 12122 Added: py/dist/py/bin/win32/ py/dist/py/bin/win32/py.cleanup.cmd py/dist/py/bin/win32/py.countloc.cmd py/dist/py/bin/win32/py.lookup.cmd py/dist/py/bin/win32/py.rest.cmd py/dist/py/bin/win32/py.test.cmd Log: Batch files that call the 'real' py.test, py.cleanup etc. scripts in the bin directory. The batch files are installed in bin\win32. Added: py/dist/py/bin/win32/py.cleanup.cmd ============================================================================== --- (empty file) +++ py/dist/py/bin/win32/py.cleanup.cmd Mon May 9 17:53:35 2005 @@ -0,0 +1,2 @@ + at echo off +python %~dp0\..\py.cleanup %* \ No newline at end of file Added: py/dist/py/bin/win32/py.countloc.cmd ============================================================================== --- (empty file) +++ py/dist/py/bin/win32/py.countloc.cmd Mon May 9 17:53:35 2005 @@ -0,0 +1,2 @@ + at echo off +python %~dp0\..\py.countloc %* \ No newline at end of file Added: py/dist/py/bin/win32/py.lookup.cmd ============================================================================== --- (empty file) +++ py/dist/py/bin/win32/py.lookup.cmd Mon May 9 17:53:35 2005 @@ -0,0 +1,2 @@ + at echo off +python %~dp0\..\py.lookup %* \ No newline at end of file Added: py/dist/py/bin/win32/py.rest.cmd ============================================================================== --- (empty file) +++ py/dist/py/bin/win32/py.rest.cmd Mon May 9 17:53:35 2005 @@ -0,0 +1,2 @@ + at echo off +python %~dp0\..\py.rest %* \ No newline at end of file Added: py/dist/py/bin/win32/py.test.cmd ============================================================================== --- (empty file) +++ py/dist/py/bin/win32/py.test.cmd Mon May 9 17:53:35 2005 @@ -0,0 +1,2 @@ + at echo off +python %~dp0\..\py.test %* \ No newline at end of file From ggheo at codespeak.net Tue May 10 23:14:42 2005 From: ggheo at codespeak.net (ggheo at codespeak.net) Date: Tue, 10 May 2005 23:14:42 +0200 (CEST) Subject: [py-svn] r12168 - py/dist/py/path/local Message-ID: <20050510211442.0A7B227C08@code1.codespeak.net> Author: ggheo Date: Tue May 10 23:14:41 2005 New Revision: 12168 Modified: py/dist/py/path/local/local.py Log: test_local.py was failing on WinXP in testExecutionOnWindows().test_sysfind. On WinXP, the directories in PATH sometimes contain the special %SystemRoot% variable, which was not correctly expanded in the sysfind() method, and thus the cmd.exe file was not found anywhere. The same test was working fine though on Win2K Pro. I modified the paths list in LocalPath.sysfind() via a list comprehension. Hopefully everybody running py.py has a Python version which supports list comprehensions :-) Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Tue May 10 23:14:41 2005 @@ -6,7 +6,7 @@ """ from __future__ import generators -import sys, os, stat +import sys, os, stat, re import py from py.__.path import common @@ -477,6 +477,7 @@ else: if py.std.sys.platform == 'win32': paths = py.std.os.environ['Path'].split(';') + paths = [re.sub('%SystemRoot%', os.environ['SYSTEMROOT'], path) for path in paths] tryadd = '', '.exe', '.com', '.bat' # XXX add more? else: paths = py.std.os.environ['PATH'].split(':') @@ -484,7 +485,7 @@ for x in paths: for addext in tryadd: - p = py.path.local(x).join(name, abs=True) + addext + p = py.path.local(x).join(name, abs=True) + addext if p.check(file=1): if checker: if not checker(p): From jan at codespeak.net Wed May 11 14:35:29 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Wed, 11 May 2005 14:35:29 +0200 (CEST) Subject: [py-svn] r12174 - py/dist/py/test/tkinter Message-ID: <20050511123529.8BF6827C19@code1.codespeak.net> Author: jan Date: Wed May 11 14:35:29 2005 New Revision: 12174 Modified: py/dist/py/test/tkinter/guisession.py Log: show stdout/stderr in 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 May 11 14:35:29 2005 @@ -32,6 +32,7 @@ def finish(self, colitem, outcome): super(GuiSession, self).finish(colitem, outcome) + colitem.finishcapture() report = self.reportlist.pop() report.finish(colitem, outcome, self.config) self.reportlist[-1].status.update(report.status) From ggheo at codespeak.net Wed May 11 16:59:26 2005 From: ggheo at codespeak.net (ggheo at codespeak.net) Date: Wed, 11 May 2005 16:59:26 +0200 (CEST) Subject: [py-svn] r12192 - py/dist/py/test Message-ID: <20050511145926.85D6227C19@code1.codespeak.net> Author: ggheo Date: Wed May 11 16:59:26 2005 New Revision: 12192 Modified: py/dist/py/test/cmdline.py Log: Fixed typo (Keybord --> Keyboard). Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Wed May 11 16:59:26 2005 @@ -12,5 +12,5 @@ session.main(args) except KeyboardInterrupt: print - print "Keybordinterrupt" + print "KeyboardInterrupt" raise SystemExit, 2 From hpk at codespeak.net Thu May 12 13:51:58 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 12 May 2005 13:51:58 +0200 (CEST) Subject: [py-svn] r12214 - py/dist/py/misc/testing Message-ID: <20050512115158.21DD827BE7@code1.codespeak.net> Author: hpk Date: Thu May 12 13:51:57 2005 New Revision: 12214 Modified: py/dist/py/misc/testing/test_cache.py Log: sanitized cache evition test slightly 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 Thu May 12 13:51:57 2005 @@ -51,8 +51,13 @@ def test_cache_eviction(self): self.cache.getorbuild(17, lambda: 17) - py.std.time.sleep(self.maxsecs*1.3) - assert self.cache.getentry(17) is None + endtime = py.std.time.time() + self.maxsecs * 10 + while py.std.time.time() < endtime: + if self.cache.getentry(17) is None: + break + py.std.time.sleep(self.maxsecs*0.3) + else: + py.test.fail("waiting for cache eviction failed") def test_prune_lowestweight(): maxsecs = 0.05 From jan at codespeak.net Fri May 13 14:05:40 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 13 May 2005 14:05:40 +0200 (CEST) Subject: [py-svn] r12233 - in py/dist/py/test/tkinter: . testing testing/data Message-ID: <20050513120540.E183127B95@code1.codespeak.net> Author: jan Date: Fri May 13 14:05:40 2005 New Revision: 12233 Added: py/dist/py/test/tkinter/event.py py/dist/py/test/tkinter/testing/test_capture_out_err.py Modified: py/dist/py/test/tkinter/backend.py py/dist/py/test/tkinter/guisession.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_guisession.py py/dist/py/test/tkinter/tkgui.py py/dist/py/test/tkinter/util.py Log: * added minimal layout Modified: py/dist/py/test/tkinter/backend.py ============================================================================== --- py/dist/py/test/tkinter/backend.py (original) +++ py/dist/py/test/tkinter/backend.py Fri May 13 14:05:40 2005 @@ -68,17 +68,18 @@ ReportClass = util.TestReport def __init__(self): - self._reports = {} + self._reports = repository.OrderedDict() self._repository = TestRepository() self.root = self.ReportClass() #self.add(self.ReportClass()) def add(self, report): - if not self._update_root(report): - self._reports[tuple(report.full_id)] = report + if not self._check_root(report): + #print '/'.join(report.full_id) + self._reports[report.full_id] = report self._repository.add_report(report) - def _update_root(self, report): + def _check_root(self, report): if report.id == self.ReportClass.root_id: self.root = report return True @@ -92,32 +93,39 @@ return report.full_id[:] def get(self, **kwargs): - filter_dict = {'failed': self._select_failed, - 'skipped': self._select_skipped, - 'passed_item': self._select_passed_item, - 'id': self._select_by_id} - + # ensure failed > skipped > passed_item + filter_dict = repository.OrderedDict() + filter_dict['failed'] = self._select_failed + filter_dict['skipped']= self._select_skipped + filter_dict['passed_item']= self._select_passed_item + filter_dict['id']= self._select_by_id + if kwargs.get('id', None) == tuple(): return [self.root] selected_reports = [] functions = [filter_dict[name] for name in kwargs.keys() if filter_dict.has_key(name)] - for report in self._reports.values(): - if False not in [function(report, **kwargs) - for function in functions]: - selected_reports.append(report) + for function in functions: + selected_reports.extend([rep for rep in self._reports.values() + if function(rep, **kwargs)]) return selected_reports def _select_failed(self, report, **kwargs): - return report.status == self.ReportClass.Status.Failed() and report.error_report != '' - + if kwargs['failed']: + return report.status == self.ReportClass.Status.Failed() and report.error_report != '' + return False + def _select_skipped(self, report, **kwargs): - return report.status == self.ReportClass.Status.Skipped() - + if kwargs['skipped']: + return report.status == self.ReportClass.Status.Skipped() + return False + def _select_passed_item(self, report, **kwargs): - return report.status == self.ReportClass.Status.Passed() and report.is_item == True - + if kwargs['passed_item']: + return report.status == self.ReportClass.Status.Passed() and report.is_item == True + return False + def _select_by_id(self, report, **kwargs): id = kwargs['id'] return report.full_id == id @@ -125,7 +133,6 @@ class ReportBackend: def __init__(self, config = Null()): - self.testrepository = TestRepository() self.reportstore = ReportStore() self.channel = Null() self.waitfinish_thread = Null() @@ -146,10 +153,6 @@ self.channel.close() if self.waitfinish_thread.isAlive(): self.waitfinish_thread.join() - - def get_repository(self): - '''return the repository''' - return self.testrepository def get_store(self): return self.reportstore @@ -163,18 +166,22 @@ def update(self): """Check if there is something new in the queue.""" changed_report_ids = [] - while self.queue.qsize(): + while not self.queue.empty(): try: - report_str = self.queue.get(0) + report_str = self.queue.get(False) id = report_str if report_str is not None: - id = self.testrepository.add_report_from_channel(report_str) - self.reportstore.add_report_from_channel(report_str) + id = self.reportstore.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 debug_queue_put(self, item): + report = ReportStore.ReportClass.fromChannel(item) + print '/'.join(report.full_id) + self.queue.put(item) def start_tests(self, config = None, args = [], tests = []): if self.running: @@ -200,7 +207,7 @@ try: while 1: try: - channel.waitclose(1) + channel.waitclose(0.5) except (IOError, py.error.Error): continue break @@ -216,7 +223,7 @@ def remote(channel, tests = [], args = []): import py - from py.__.test.tkinter.guisession import GuiSession + from py.__.test.tkinter.guisession import ReportSession from py.__.test.terminal.remote import getfailureitems config, testfiles = py.test.Config.parse(args) @@ -224,9 +231,7 @@ cols = getfailureitems(tests) else: cols = testfiles - print 'cols:', cols - #session = config.getsessionclass()(config) - session = GuiSession(config = config, channel=channel) + session = ReportSession(config = config, channel=channel) session.shouldclose = channel.isclosed session.main(cols) Added: py/dist/py/test/tkinter/event.py ============================================================================== --- (empty file) +++ py/dist/py/test/tkinter/event.py Fri May 13 14:05:40 2005 @@ -0,0 +1,19 @@ + +class Event: + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + self.callbacks = [] + + + def __call__(self, *args, **kwargs): + default_kwargs = kwargs.copy() + default_kwargs.update(kwargs) + for callable in self.callbacks: + callable(*args, **default_kwargs) + + def subscribe(self, callable): + self.callbacks.append(callable) + + def remove(self, callable): + while f in self.callbacks: self.callbacks.remove(f) Modified: py/dist/py/test/tkinter/guisession.py ============================================================================== --- py/dist/py/test/tkinter/guisession.py (original) +++ py/dist/py/test/tkinter/guisession.py Fri May 13 14:05:40 2005 @@ -2,36 +2,37 @@ import py from util import TestReport -class GuiSession(py.test.Session): +class ReportSession(py.test.Session): def __init__(self, config = None, channel = None): - super(GuiSession, self).__init__(config) + super(ReportSession, self).__init__(config) self.channel = channel self.reportlist = [] def header(self, colitems): - super(GuiSession, self).header(colitems) + super(ReportSession, self).header(colitems) report = TestReport() report.settime() self.reportlist = [report] self.sendreport(report) def footer(self, colitems): - super(GuiSession, self).footer(colitems) + super(ReportSession, self).footer(colitems) report = self.reportlist.pop() report.settime() self.sendreport(report) self.channel.send(None) def start(self, colitem): - super(GuiSession, self).start(colitem) + super(ReportSession, self).start(colitem) report = TestReport() report.start(colitem) self.reportlist.append(report) self.sendreport(report) def finish(self, colitem, outcome): - super(GuiSession, self).finish(colitem, outcome) + super(ReportSession, self).finish(colitem, outcome) + colitem.finishcapture() colitem.finishcapture() report = self.reportlist.pop() report.finish(colitem, outcome, self.config) Modified: py/dist/py/test/tkinter/testing/data/filetest.py ============================================================================== --- py/dist/py/test/tkinter/testing/data/filetest.py (original) +++ py/dist/py/test/tkinter/testing/data/filetest.py Fri May 13 14:05:40 2005 @@ -1,3 +1,4 @@ +import py def test_one(): assert 42 == 42 @@ -5,3 +6,8 @@ class TestClass(object): def test_method_one(self): assert 42 == 42 + +def test_print_and_fail(): + print 'STDOUT', + print >>py.std.sys.stderr, 'STDERR', + py.test.fail() 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 Fri May 13 14:05:40 2005 @@ -1,7 +1,7 @@ import py from py.__.test.tkinter import backend -RepositoryBackend = backend.ReportBackend +ReportBackend = backend.ReportBackend TestRepository = backend.TestRepository ReportStore = backend.ReportStore @@ -99,19 +99,36 @@ assert self.store.get(passed_item = True) == [self.report_passed_item] def test_select_failed(self): - assert self.store._select_failed(self.report_failed) == False - assert self.store._select_failed(self.report_failed_item) == True - assert self.store._select_failed(self.report_skipped) == False + assert self.store._select_failed(self.report_failed, + failed = True) == False + assert self.store._select_failed(self.report_failed_item, + failed = True) == True + assert self.store._select_failed(self.report_failed_item, + failed=False) == False + assert self.store._select_failed(self.report_skipped, + failed = True, + skipped = True) == False def test_select_skipped(self): - assert self.store._select_skipped(self.report_failed) == False - assert self.store._select_skipped(self.report_skipped) == True + assert self.store._select_skipped(self.report_failed, + skipped = True) == False + assert self.store._select_skipped(self.report_skipped, + skipped = False) == False + assert self.store._select_skipped(self.report_skipped, + skipped = True) == True def test_select_passed_item(self): - assert self.store._select_passed_item(self.report_failed) == False - assert self.store._select_passed_item(self.report_skipped) == False - assert self.store._select_passed_item(self.report_passed) == False - assert self.store._select_passed_item(self.report_passed_item) == True + assert self.store._select_passed_item(self.report_failed, + passed_item = True) == False + assert self.store._select_passed_item(self.report_skipped, + passed_item = True, + skipped = True) == False + assert self.store._select_passed_item(self.report_passed, + passed_item = True) == False + assert self.store._select_passed_item(self.report_passed_item, + passed_item = False) == False + assert self.store._select_passed_item(self.report_passed_item, + passed_item = True) == True def test_select_by_id(self): assert self.store._select_by_id(self.report_passed_item, @@ -132,16 +149,16 @@ -class TestRepositoryBackend: +class TestReportBackend: def setup_method(self, method): - self.backend = RepositoryBackend() + self.backend = ReportBackend() - def test_get_repository(self): - assert isinstance(self.backend.get_repository(), TestRepository) + def test_get_store(self): + assert isinstance(self.backend.get_store(), ReportStore) def test_running_property(self): - backend = RepositoryBackend() + backend = ReportBackend() assert not self.backend.running def test_update_callback(self): @@ -167,10 +184,8 @@ tests = []) while self.backend.running: self.backend.update() - repos = self.backend.get_repository() - print repos.keys() - print repos.find() - assert repos.find(['py', + store = self.backend.get_store() + assert store._repository.find(['py', 'test', 'tkinter', 'testing', Added: py/dist/py/test/tkinter/testing/test_capture_out_err.py ============================================================================== --- (empty file) +++ py/dist/py/test/tkinter/testing/test_capture_out_err.py Fri May 13 14:05:40 2005 @@ -0,0 +1,19 @@ +import py +from py.__.test.tkinter import backend +ReportBackend = backend.ReportBackend + +datadir = py.magic.autopath().dirpath('data') + +def test_capture_out_err(): + config, args = py.test.Config.parse([]) + backend = ReportBackend() + backend.start_tests(config = config, + args = [str(datadir / 'filetest.py')], + tests = []) + while backend.running: + backend.update() + store = backend.get_store() + assert len(store.get(failed = True)) == 1 + failed = store.get(failed = True)[0] + assert failed.stdout == 'STDOUT' + assert failed.stderr == 'STDERR' 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 Fri May 13 14:05:40 2005 @@ -2,9 +2,9 @@ import py from py.__.test.tkinter import guisession from py.__.test.tkinter.util import Status, TestReport, Null -GuiSession = guisession.GuiSession +ReportSession = guisession.ReportSession -class TestGuiSession: +class TestReportSession: class ChannelMock: @@ -23,7 +23,7 @@ def setup_method(self, method): self.channel = self.ChannelMock() - self.session = GuiSession(Null(), self.channel) + self.session = ReportSession(Null(), self.channel) self.collitems = [Null(), Null()] def test_header_sends_report_with_id_root(self): Modified: py/dist/py/test/tkinter/tkgui.py ============================================================================== --- py/dist/py/test/tkinter/tkgui.py (original) +++ py/dist/py/test/tkinter/tkgui.py Fri May 13 14:05:40 2005 @@ -1,7 +1,9 @@ import py from py.__.test.tkinter import backend from py.__.test.tkinter import util +from py.__.test.tkinter import event Null = util.Null +Event = event.Event import ScrolledText from Tkinter import PhotoImage @@ -20,31 +22,77 @@ master = Tk() Tkinter.Frame.__init__(self, master, **kw) self.labels = {} + self.callback = Null() + self.tkvariable_dict = {'failed': Tkinter.BooleanVar(), + 'skipped': Tkinter.BooleanVar(), + 'passed_item': Tkinter.BooleanVar()} + self.init_widgets() + + def init_widgets(self): + def get_button(text, var): + b = Tkinter.Checkbutton(self, text=text, + variable=var, + onvalue=True, offvalue=False, + anchor=Tkinter.W, + font=self.font, indicatoron = False, + command = self.do_callback) + b.select() + return b + + self.add_widget('Failed', + get_button('Failed', self.tkvariable_dict['failed'])) + self._get_widget('Failed').configure(bg = 'Red3', fg = 'White', + highlightcolor = 'Red', + selectcolor = 'Red', + activebackground= 'Red', + activeforeground = 'White') + self.add_widget('Skipped', + get_button('Skipped', self.tkvariable_dict['skipped'])) + self._get_widget('Skipped').configure(bg = 'Yellow3', + highlightcolor = 'Yellow', + selectcolor = 'Yellow', + activebackground= 'Yellow') + b = get_button('Passed', self.tkvariable_dict['passed_item']) + b.deselect() + self.add_widget('Passed', b) + self._get_widget('Passed').configure(bg = 'Green3', fg = 'Black', + highlightcolor = 'Black', + selectcolor = 'Green', + activeforeground = 'Black', + activebackground= 'Green') + + def set_filter_args_callback(self, callback): + self.callback = callback + def do_callback(self,): + kwargs = dict([(key, var.get()) + for key, var in self.tkvariable_dict.items()]) + self.callback(**kwargs) + 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 + self.add_widget(name, label, side) else: label = self.labels[name] label.config(text='%s:\t%s' % (name,text)) - def _get_label(self, name): + def add_widget(self, name, widget, side=Tkinter.LEFT): + widget.pack(side=side) + self.labels[name] = widget + + def _get_widget(self, name): if not self.labels.has_key(name): self.set_label(name, 'NoText') return self.labels[name] - def update(self, reportstore): + def update_all(self, reportstore = Null(), ids = []): self.set_label('Failed', str(len(reportstore.get(failed = True)))) - self._get_label('Failed').configure(bg = 'Red', fg = 'White') self.set_label('Skipped', str(len(reportstore.get(skipped = True)))) - self._get_label('Skipped').configure(bg = 'Yellow') self.set_label('Passed', str(len(reportstore.get(passed_item = True)))) - self._get_label('Passed').configure(bg = 'Green', fg = 'Black') root_report = reportstore.root if root_report.time < 7*24*60*60: self.set_label('Time', '%0.2f seconds' % root_report.time) @@ -59,6 +107,7 @@ Tkinter.LabelFrame.__init__(self, *args, **kwargs) self.callback = Null() self.data = {} + self.filter_kwargs = {'skipped': True, 'failed': True} self.label = Tkinter.Label(self) self.label.configure(font = self.font, width = 80, anchor = Tkinter.W) self.configure(labelwidget=self.label) @@ -91,29 +140,29 @@ for report_id in report_ids: self.callback(report_id) - def update_label(self, report_path): - label = report_path + def set_filter_kwargs(self, **kwargs): + self.filter_kwargs = kwargs + + def update_label(self, report): + label = report.path if not label: label = 'Idle' self.label.configure(text = label) def update_list(self, reportstore): - failed_childs = reportstore.get(failed = True) - skipped_childs = reportstore.get(skipped = True) - if len(failed_childs + skipped_childs) == len(self.data.keys()): - return - old_selection = [int(sel) for sel in self.listbox.curselection()] + reports = reportstore.get(**self.filter_kwargs) + old_selections = [self.listbox.get(int(sel)) + for sel in self.listbox.curselection()] self.listbox.delete(0, Tkinter.END) self.data = {} - for report in failed_childs + skipped_childs: + for report in reports: 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 + if label in old_selections: + self.listbox.select_set(Tkinter.END) + self.listbox.see(Tkinter.END) + @@ -131,16 +180,29 @@ self.entry.pack(side = Tkinter.LEFT, expand = Tkinter.YES, fill = Tkinter.X) -class ReportFrame(Tkinter.Frame): + def update(self, reportstore = Null(), ids = []): + bgcolor = 'White' + fgcolor = 'Black' + root_status = reportstore.root.status + if root_status == reportstore.ReportClass.Status.Passed(): + bgcolor = 'Green' + elif root_status == reportstore.ReportClass.Status.Failed(): + bgcolor = 'Red' + fgcolor = 'White' + elif root_status == reportstore.ReportClass.Status.Skipped() : + bgcolor = 'Yellow' + self.entry.configure(bg = bgcolor, fg = fgcolor) + +class ReportFrame(Tkinter.LabelFrame): font = myfont def __init__(self, *args, **kwargs): - Tkinter.Frame.__init__(self, *args, **kwargs) + Tkinter.LabelFrame.__init__(self, *args, **kwargs) self.report = Null() self.label = Tkinter.Label(self,foreground="red", justify=Tkinter.LEFT) self.label.pack(anchor=Tkinter.W) self.label.configure(font = self.font) - self.label.pack(side = Tkinter.TOP) + self.configure(labelwidget = self.label) self.text = ScrolledText.ScrolledText(self) self.text.configure(bg = 'White', font = ('Helvetica', 11, 'normal')) self.text.tag_config('sel', relief=Tkinter.FLAT) @@ -158,7 +220,13 @@ self.text.yview_pickplace(Tkinter.END) self.text['state'] = Tkinter.DISABLED self.attacheditorhotspots(self.text) - + + def clear(self): + self.label.configure(text = '') + self.text['state'] = Tkinter.NORMAL + self.text.delete(1.0, Tkinter.END) + self.text['state'] = Tkinter.DISABLED + def launch_editor(self, file, line): editor = (py.std.os.environ.get('PYTHON_EDITOR', None) or py.std.os.environ.get('EDITOR_REMOTE', None) or @@ -200,6 +268,60 @@ self.launch_editor(f, l)) +class MinimalButton(Tkinter.Button): + + format_string = '%dF / %dS / %dP' + + def __init__(self, *args, **kwargs): + Tkinter.Button.__init__(self, *args, **kwargs) + self.configure(text = 'Start/Stop') #self.format_string % (0,0,0)) + + def update_label(self, reportstore): + failed = len(reportstore.get(failed = True)) + skipped = len(reportstore.get(skipped = True)) + passed = len(reportstore.get(passed_item = True)) + #self.configure(text = self.format_string % (failed, skipped, passed)) + bgcolor = 'Green' + fgcolor = 'Black' + highlightcolor = 'Green', + activebackground= 'Green', + activeforeground = 'Black' + + if skipped > 0: + bgcolor = 'Yellow' + activebackground = 'Yellow' + if failed > 0: + bgcolor = 'Red' + fgcolor = 'White' + activebackground= 'Red', + activeforeground = 'White' + + self.configure(bg = bgcolor, fg = fgcolor, + highlightcolor = highlightcolor, + activeforeground = activeforeground, + activebackground = activebackground) + +class LayoutManager: + + def __init__(self): + self.current_layout = 0 + self.layout_dict = {} + + def add(self, widget, layout_ids, **kwargs): + for id in layout_ids: + list = self.layout_dict.setdefault(id, []) + list.append((widget, kwargs)) + + def set_layout(self, layout = 0): + if self.current_layout != layout: + widget_list = self.layout_dict.get(self.current_layout, []) + for widget, kwargs in widget_list: + widget.pack_forget() + self.current_layout = layout + widget_list = self.layout_dict.get(self.current_layout, []) + for widget, kwargs in widget_list: + widget.pack(**kwargs) + class TkGui: @@ -212,35 +334,90 @@ #XXX needed? self._paths = [] self.backend = backend.ReportBackend(config) + self._layoutmanager = LayoutManager() + + self._layout_toggle_var = Tkinter.StringVar() + self._layout_toggle_var.set('<') + # events + self.on_report_updated = Event() + self.on_report_store_updated = Event() + self.on_show_report = Event() + self.on_run_tests = Event() + self.createwidgets() self.timer_update() self.report_window = Null() self.report_frame = Null() + def createwidgets(self): + add = self._layoutmanager.add self._buttonframe = Tkinter.Frame(self._parent) + self._layoutbutton = Tkinter.Button(self._buttonframe, padx=0, + relief=Tkinter.GROOVE, + textvariable=self._layout_toggle_var, + command=self.toggle_layout) + add(self._layoutbutton, [0,1], side= Tkinter.LEFT) 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) + add(self._entry, [0], side = Tkinter.LEFT, fill = Tkinter.X, + expand = Tkinter.YES) + + self._run_stop_button = Tkinter.Button(self._buttonframe, text = 'Run', + command = self.toggle_action, font = self.font) + add(self._run_stop_button, [0], side = Tkinter.RIGHT) + + self._minimalbutton = MinimalButton(self._buttonframe, font = self.font, + command = self.toggle_action) + add(self._minimalbutton, [1], side = Tkinter.RIGHT, + fill = Tkinter.X, expand = Tkinter.YES) + add(self._buttonframe, [0,1], side = Tkinter.TOP, fill = Tkinter.X) + self._statusbar = StatusBar(self._parent) - self._statusbar.pack(side= Tkinter.BOTTOM, fill=Tkinter.X) + self._statusbar.set_filter_args_callback(self.filter_args_changed) + add(self._statusbar, [0,1], side= Tkinter.BOTTOM, fill=Tkinter.X) + + self._reportlist = ReportListBox(self._parent) + add(self._reportlist, [0], side = Tkinter.TOP, fill = Tkinter.BOTH, + expand = Tkinter.YES) + self._reportlist.set_callback(self.show_report) + self._report_frame = ReportFrame(self._parent) + add(self._report_frame, [0], side = Tkinter.TOP, fill = Tkinter.BOTH, + expand = Tkinter.YES) + + #self.on_show_report.subscribe(self.show_report) + self.on_report_updated.subscribe(self._reportlist.update_label) + self.on_report_store_updated.subscribe(self._reportlist.update_list) + self.on_report_store_updated.subscribe(self._minimalbutton.update_label) + self.on_report_store_updated.subscribe(self.update_status) + + self._layoutmanager.set_layout(0) self.update_status(self.backend.get_store()) + self.messages_callback(None) + + def toggle_layout(self): + if self._layout_toggle_var.get() == '>': + self._layout_toggle_var.set('<') + self._layoutmanager.set_layout(0) + else: + self._layout_toggle_var.set('>') + self._layoutmanager.set_layout(1) + self._parent.geometry('') + + def filter_args_changed(self, **kwargs): + self._reportlist.set_filter_kwargs(**kwargs) + self._reportlist.update_list(self.backend.get_store()) - def show_error(self, report_id): - report = self.backend.get_repository().find(report_id) + def show_report(self, report_id): + if report_id is None: + self._report_frame.clear() + else: + report = self.backend.get_store().get(id = report_id)[0] + self._report_frame.set_report(report) + + def show_error_window(self, report_id): + report = self.backend.get_store().get(id = report_id)[0] if not self.report_window: self.report_window = Tkinter.Toplevel(self._parent) self.report_window.protocol('WM_DELETE_WINDOW', @@ -260,6 +437,12 @@ def timer_update(self): self.backend.update() + if self.backend.running: + action = 'Stop' + else: + action = 'Run' + self._run_stop_button.configure(text = action) + self._minimalbutton.configure(text = action) self._parent.after(200, self.timer_update) def start_tests(self, dont_care_event=None): @@ -270,38 +453,34 @@ self.backend.set_message_callback(self.message_callback) self.backend.start_tests(args = paths) + self.show_report(None) + def update_status(self, reportstore): - self._statusbar.update(reportstore) - bgcolor = 'White' - fgcolor = 'Black' - root_status = reportstore.root.status - if root_status == reportstore.ReportClass.Status.Passed(): - bgcolor = 'Green' - elif root_status == reportstore.ReportClass.Status.Failed(): - bgcolor = 'Red' - fgcolor = 'White' - elif root_status == reportstore.ReportClass.Status.Skipped() : - bgcolor = 'Yellow' - self._entry.entry.configure(bg = bgcolor, fg = fgcolor) + self._statusbar.update_all(reportstore = reportstore, ids = []) + self._entry.update(reportstore = reportstore, ids = []) def messages_callback(self, report_ids): if not report_ids: return - repository = self.backend.get_repository() - self.update_status(self.backend.get_store()) - self._reportlist.update_list(self.backend.get_store()) + self.on_report_store_updated(self.backend.get_store()) def message_callback(self, report_id): if report_id is None: - report_label = None + report = Null() else: - report_label = self.backend.get_store().get(id = report_id)[0].path - self._reportlist.update_label(report_label) + report = self.backend.get_store().get(id = report_id)[0] + self.on_report_updated(report) def set_paths(self, paths): self._paths = paths self._entry.entry.insert(Tkinter.END, ', '.join(paths)) + def toggle_action(self): + if self.backend.running: + self.stop() + else: + self.start_tests() + def stop(self): self.backend.shutdown() self.backend.update() Modified: py/dist/py/test/tkinter/util.py ============================================================================== --- py/dist/py/test/tkinter/util.py (original) +++ py/dist/py/test/tkinter/util.py Fri May 13 14:05:40 2005 @@ -127,6 +127,8 @@ 'path' : '', 'modpath': '', 'is_item': False, + 'stdout': '', + 'stderr': '', } Status = Status @@ -183,6 +185,8 @@ elif Status(res) == Status.Skipped(): self.error_report = self.report_skipped(config, collector, res) self.status.update(Status(res)) + out, err = collector.getouterr() + self.stdout, self.stderr = str(out), str(err) self.finished = True def abbrev_path(self, fspath): @@ -201,6 +205,7 @@ out = OutBuffer() terminal.out = out terminal.repr_failure(item, res) + #terminal.repr_out_err(item) return out.getoutput() def report_skipped(self, config, item, outcome): From jan at codespeak.net Fri May 13 16:02:58 2005 From: jan at codespeak.net (jan at codespeak.net) Date: Fri, 13 May 2005 16:02:58 +0200 (CEST) Subject: [py-svn] r12237 - in py/dist/py/test/tkinter: . testing Message-ID: <20050513140258.C373027B9E@code1.codespeak.net> Author: jan Date: Fri May 13 16:02:58 2005 New Revision: 12237 Added: py/dist/py/test/tkinter/reportsession.py - copied unchanged from r12233, py/dist/py/test/tkinter/guisession.py py/dist/py/test/tkinter/testing/test_reportsession.py - copied, changed from r12233, py/dist/py/test/tkinter/testing/test_guisession.py Removed: py/dist/py/test/tkinter/guisession.py py/dist/py/test/tkinter/testing/test_guisession.py Modified: py/dist/py/test/tkinter/backend.py Log: * renamed guisession.py -> reportsession.py Modified: py/dist/py/test/tkinter/backend.py ============================================================================== --- py/dist/py/test/tkinter/backend.py (original) +++ py/dist/py/test/tkinter/backend.py Fri May 13 16:02:58 2005 @@ -223,7 +223,7 @@ def remote(channel, tests = [], args = []): import py - from py.__.test.tkinter.guisession import ReportSession + from py.__.test.tkinter.reportsession import ReportSession from py.__.test.terminal.remote import getfailureitems config, testfiles = py.test.Config.parse(args) Deleted: /py/dist/py/test/tkinter/guisession.py ============================================================================== --- /py/dist/py/test/tkinter/guisession.py Fri May 13 16:02:58 2005 +++ (empty file) @@ -1,48 +0,0 @@ -'''GuiSession builds TestReport instances and sends them to tkgui.Manager''' -import py -from util import TestReport - -class ReportSession(py.test.Session): - - def __init__(self, config = None, channel = None): - super(ReportSession, self).__init__(config) - self.channel = channel - self.reportlist = [] - - def header(self, colitems): - super(ReportSession, self).header(colitems) - report = TestReport() - report.settime() - self.reportlist = [report] - self.sendreport(report) - - def footer(self, colitems): - super(ReportSession, self).footer(colitems) - report = self.reportlist.pop() - report.settime() - self.sendreport(report) - self.channel.send(None) - - def start(self, colitem): - super(ReportSession, self).start(colitem) - report = TestReport() - report.start(colitem) - self.reportlist.append(report) - self.sendreport(report) - - def finish(self, colitem, outcome): - super(ReportSession, self).finish(colitem, outcome) - colitem.finishcapture() - colitem.finishcapture() - report = self.reportlist.pop() - report.finish(colitem, outcome, self.config) - 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 - Deleted: /py/dist/py/test/tkinter/testing/test_guisession.py ============================================================================== --- /py/dist/py/test/tkinter/testing/test_guisession.py Fri May 13 16:02:58 2005 +++ (empty file) @@ -1,62 +0,0 @@ - -import py -from py.__.test.tkinter import guisession -from py.__.test.tkinter.util import Status, TestReport, Null -ReportSession = guisession.ReportSession - -class TestReportSession: - - 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 = ReportSession(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 == TestReport.root_id - 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 == TestReport.root_id - -## 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() - - - Copied: py/dist/py/test/tkinter/testing/test_reportsession.py (from r12233, 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_reportsession.py Fri May 13 16:02:58 2005 @@ -1,8 +1,8 @@ import py -from py.__.test.tkinter import guisession +from py.__.test.tkinter import reportsession from py.__.test.tkinter.util import Status, TestReport, Null -ReportSession = guisession.ReportSession +ReportSession = reportsession.ReportSession class TestReportSession: From hpk at codespeak.net Sat May 14 12:29:18 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 14 May 2005 12:29:18 +0200 (CEST) Subject: [py-svn] r12259 - in py/dist/py/test: . terminal Message-ID: <20050514102918.76CAE27B56@code1.codespeak.net> Author: hpk Date: Sat May 14 12:29:18 2005 New Revision: 12259 Modified: py/dist/py/test/cmdline.py py/dist/py/test/terminal/remote.py Log: raise SystemExit != 0 if there were local or remote failures. Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Sat May 14 12:29:18 2005 @@ -9,7 +9,9 @@ sessionclass = config.getsessionclass() session = sessionclass(config) try: - session.main(args) + failures = session.main(args) + if failures: + raise SystemExit, 1 except KeyboardInterrupt: print print "KeyboardInterrupt" Modified: py/dist/py/test/terminal/remote.py ============================================================================== --- py/dist/py/test/terminal/remote.py (original) +++ py/dist/py/test/terminal/remote.py Sat May 14 12:29:18 2005 @@ -142,4 +142,5 @@ print "Failure at: %r" % (name,) print "# watching py files below %s" % rootdir print "# ", "^" * len(str(rootdir)) + return failures From hpk at codespeak.net Sat May 14 12:41:30 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 14 May 2005 12:41:30 +0200 (CEST) Subject: [py-svn] r12260 - in py/dist/py: documentation/example/pytest test test/terminal Message-ID: <20050514104130.1406E27B82@code1.codespeak.net> Author: hpk Date: Sat May 14 12:41:29 2005 New Revision: 12260 Added: py/dist/py/documentation/example/pytest/test_failures.py Modified: py/dist/py/test/collect.py py/dist/py/test/terminal/terminal.py Log: fix problem with reporting the (looked-ahead) number of testitems for a module. If an exception occurs while determining the number of test items then the reporter will report '?' as the number of testitems. Added: py/dist/py/documentation/example/pytest/test_failures.py ============================================================================== --- (empty file) +++ py/dist/py/documentation/example/pytest/test_failures.py Sat May 14 12:41:29 2005 @@ -0,0 +1,12 @@ + +import py +failure_demo = py.magic.autopath().dirpath('failure_demo.py') + +def test_failure_demo_fails_properly(): + config, args = py.test.Config.parse([]) + session = config.getsessionclass()(config, py.std.sys.stdout) + session.main([failure_demo]) + l = session.getitemoutcomepairs(py.test.Item.Failed) + assert len(l) == 21 + l = session.getitemoutcomepairs(py.test.Item.Passed) + assert not l Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Sat May 14 12:41:29 2005 @@ -183,7 +183,16 @@ def _prepare(self): if not hasattr(self, '_name2items'): - self._name2items = self.buildname2items() + ex = getattr(self, '_name2items_exception', None) + if ex is not None: + raise ex[0], ex[1], ex[2] + try: + self._name2items = self.buildname2items() + except (SystemExit, KeyboardInterrupt): + raise + except: + self._name2items_exception = py.std.sys.exc_info() + raise def buildname2items(self): raise NotImplementedError, "abstract" Modified: py/dist/py/test/terminal/terminal.py ============================================================================== --- py/dist/py/test/terminal/terminal.py (original) +++ py/dist/py/test/terminal/terminal.py Sat May 14 12:41:29 2005 @@ -64,10 +64,16 @@ def startiteration(self, colitem, subitems): if (isinstance(colitem, py.test.collect.Module) and self.config.option.verbose == 0): - sum = 0 - for sub in subitems: - sum += len(list(colitem.join(sub).tryiter())) - self.out.write('[%d] ' % sum) + try: + sum = 0 + for sub in subitems: + sum += len(list(colitem.join(sub).tryiter())) + except (SystemExit, KeyboardInterrupt): + raise + except: + self.out.write('[?]') + else: + self.out.write('[%d] ' % sum) return self.out.line def start_Item(self, colitem): From hpk at codespeak.net Sun May 15 18:31:58 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 15 May 2005 18:31:58 +0200 (CEST) Subject: [py-svn] r12304 - py/dist/py/documentation Message-ID: <20050515163158.DD8C727BBA@code1.codespeak.net> Author: hpk Date: Sun May 15 18:31:58 2005 New Revision: 12304 Added: py/dist/py/documentation/test_conftest.py Modified: py/dist/py/documentation/conftest.py Log: allow to have doctest snippets in '.txt' files in the documentation directory. Additionally you can specify "doctestmodulescope: dotted.name" and the dotted name will get imported and it's dict items merged with the doctest pseudo-module. Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Sun May 15 18:31:58 2005 @@ -59,18 +59,44 @@ def teardown(self): pass def run(self): - return [self.fspath.basename, 'checklinks'] + return [self.fspath.basename, 'checklinks', 'doctest'] def join(self, name): if name == self.fspath.basename: return ReSTSyntaxTest(name, parent=self) elif name == 'checklinks': return LinkCheckerMaker(name, self) + elif name == 'doctest': + return DoctestTextModule(name, self) class ReSTSyntaxTest(py.test.Item): def run(self): mypath = self.fspath restcheck(py.path.svnwc(mypath)) +class DoctestTextModule(py.test.Item): + rex = py.std.re.compile(r'doctestmodulescope: ([\w\.]+)') + + def mergescopes(self, mod, scopes): + """ merge the vars of the imported module from importname + into the given 'mod' + """ + for modname in scopes: + scopemod = __import__(modname, None, None, ['__doc__']) + for name, value in vars(scopemod).items(): + if name[:2] != '__' and name[-2:] != '__': + setattr(mod, name, value) + + def run(self): + s = self.fspath.read() + scopes = self.rex.findall(s) + mod = py.std.types.ModuleType(self.fspath.basename, s) + if s.find('>>>') != -1: + self.mergescopes(mod, scopes) + failed, tot = py.std.doctest.testmod(mod, verbose=1) + if failed: + py.test.fail("doctest %s: %s failed out of %s" %( + self.fspath, failed, tot)) + class LinkCheckerMaker(py.test.collect.Collector): def run(self): l = [] Added: py/dist/py/documentation/test_conftest.py ============================================================================== --- (empty file) +++ py/dist/py/documentation/test_conftest.py Sun May 15 18:31:58 2005 @@ -0,0 +1,36 @@ + +import py + +def setup_module(mod): + mod.tmpdir = py.test.ensuretemp('docdoctest') + +def test_doctest_basic(): + # XXX get rid of the next line: + py.magic.autopath().dirpath('conftest.py').copy(tmpdir.join('conftest.py')) + tmpdir.ensure('__init__.py') + + xtxt = tmpdir.join('x.txt') + xtxt.write(py.code.Source(""" + .. doctestmodulescope: os.path + + hello world + + >>> assert abspath + >>> i=3 + >>> print i + 3 + + yes yes + + >>> i + 3 + + end + """)) + config, args = py.test.Config.parse([str(xtxt)]) + session = config.getsessionclass()(config, py.std.sys.stdout) + session.main([xtxt]) + l = session.getitemoutcomepairs(py.test.Item.Failed) + assert len(l) == 0 + l = session.getitemoutcomepairs(py.test.Item.Passed) + assert len(l) == 2 From hpk at codespeak.net Sun May 15 18:38:58 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 15 May 2005 18:38:58 +0200 (CEST) Subject: [py-svn] r12305 - py/dist/py/documentation Message-ID: <20050515163858.0C5FF27BB6@code1.codespeak.net> Author: hpk Date: Sun May 15 18:38:57 2005 New Revision: 12305 Modified: py/dist/py/documentation/conftest.py Log: ignore too many >>> signs (a hack slightly relating to PyPy but also to the somewhat inflexibile doctest API) Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Sun May 15 18:38:57 2005 @@ -90,7 +90,9 @@ s = self.fspath.read() scopes = self.rex.findall(s) mod = py.std.types.ModuleType(self.fspath.basename, s) - if s.find('>>>') != -1: + if s.find('>>>> ') != -1: + py.test.skip("doctests cannot handle >>>> prompts") + elif s.find('>>> ') != -1: self.mergescopes(mod, scopes) failed, tot = py.std.doctest.testmod(mod, verbose=1) if failed: From hpk at codespeak.net Sun May 15 18:45:12 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 15 May 2005 18:45:12 +0200 (CEST) Subject: [py-svn] r12310 - py/dist/py/documentation Message-ID: <20050515164512.81AE427BC2@code1.codespeak.net> Author: hpk Date: Sun May 15 18:45:12 2005 New Revision: 12310 Modified: py/dist/py/documentation/conftest.py Log: require "doctestmodulescope: dotted.name" in order to turn a .txt file into doctest Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Sun May 15 18:45:12 2005 @@ -90,6 +90,8 @@ s = self.fspath.read() scopes = self.rex.findall(s) mod = py.std.types.ModuleType(self.fspath.basename, s) + if not scopes: + return if s.find('>>>> ') != -1: py.test.skip("doctests cannot handle >>>> prompts") elif s.find('>>> ') != -1: From hpk at codespeak.net Sun May 15 19:15:53 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 15 May 2005 19:15:53 +0200 (CEST) Subject: [py-svn] r12316 - py/dist/py/documentation Message-ID: <20050515171553.0495E27BD9@code1.codespeak.net> Author: hpk Date: Sun May 15 19:15:53 2005 New Revision: 12316 Modified: py/dist/py/documentation/conftest.py Log: shuffle things a bit to make them customizable from other projects (although this will need to be refactored soon but first i'd like to gather some experiences of what issues are there) Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Sun May 15 19:15:53 2005 @@ -46,34 +46,15 @@ return super(DocDirectory, self).join(name) p = self.fspath.join(name) if p.check(file=1): - return ReSTChecker(p, parent=self) + return self.ReSTChecker(p, parent=self) Directory = DocDirectory -class ReSTChecker(py.test.collect.Module): - - def __repr__(self): - return py.test.collect.Collector.__repr__(self) - - def setup(self): - pass - def teardown(self): - pass - def run(self): - return [self.fspath.basename, 'checklinks', 'doctest'] - def join(self, name): - if name == self.fspath.basename: - return ReSTSyntaxTest(name, parent=self) - elif name == 'checklinks': - return LinkCheckerMaker(name, self) - elif name == 'doctest': - return DoctestTextModule(name, self) - class ReSTSyntaxTest(py.test.Item): def run(self): mypath = self.fspath restcheck(py.path.svnwc(mypath)) -class DoctestTextModule(py.test.Item): +class DoctestText(py.test.Item): rex = py.std.re.compile(r'doctestmodulescope: ([\w\.]+)') def mergescopes(self, mod, scopes): @@ -89,17 +70,17 @@ def run(self): s = self.fspath.read() scopes = self.rex.findall(s) - mod = py.std.types.ModuleType(self.fspath.basename, s) if not scopes: return - if s.find('>>>> ') != -1: - py.test.skip("doctests cannot handle >>>> prompts") - elif s.find('>>> ') != -1: - self.mergescopes(mod, scopes) - failed, tot = py.std.doctest.testmod(mod, verbose=1) - if failed: - py.test.fail("doctest %s: %s failed out of %s" %( - self.fspath, failed, tot)) + self.execute(s, scopes) + + def execute(self, docstring, scopes): + mod = py.std.types.ModuleType(self.fspath.basename, docstring) + self.mergescopes(mod, scopes) + failed, tot = py.std.doctest.testmod(mod, verbose=1) + if failed: + py.test.fail("doctest %s: %s failed out of %s" %( + self.fspath, failed, tot)) class LinkCheckerMaker(py.test.collect.Collector): def run(self): @@ -118,6 +99,26 @@ pass def teardown(self): pass + +class ReSTChecker(py.test.collect.Module): + DoctestText = DoctestText + def __repr__(self): + return py.test.collect.Collector.__repr__(self) + + def setup(self): + pass + def teardown(self): + pass + def run(self): + return [self.fspath.basename, 'checklinks', 'doctest'] + def join(self, name): + if name == self.fspath.basename: + return ReSTSyntaxTest(name, parent=self) + elif name == 'checklinks': + return LinkCheckerMaker(name, self) + elif name == 'doctest': + return self.DoctestText(name, self) + # generating functions + args as single tests def genlinkchecks(path): From hpk at codespeak.net Sun May 15 19:37:37 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 15 May 2005 19:37:37 +0200 (CEST) Subject: [py-svn] r12317 - py/dist/py/documentation Message-ID: <20050515173737.2B2C627BB9@code1.codespeak.net> Author: hpk Date: Sun May 15 19:37:37 2005 New Revision: 12317 Modified: py/dist/py/documentation/conftest.py py/dist/py/documentation/test_conftest.py Log: playing a bit with the escape directives to add scoping to doctests from a text file Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Sun May 15 19:37:37 2005 @@ -28,55 +28,28 @@ # 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 self.ReSTChecker(p, parent=self) -Directory = DocDirectory - class ReSTSyntaxTest(py.test.Item): def run(self): mypath = self.fspath restcheck(py.path.svnwc(mypath)) class DoctestText(py.test.Item): - rex = py.std.re.compile(r'doctestmodulescope: ([\w\.]+)') - - def mergescopes(self, mod, scopes): - """ merge the vars of the imported module from importname - into the given 'mod' - """ - for modname in scopes: - scopemod = __import__(modname, None, None, ['__doc__']) - for name, value in vars(scopemod).items(): - if name[:2] != '__' and name[-2:] != '__': - setattr(mod, name, value) - def run(self): s = self.fspath.read() - scopes = self.rex.findall(s) - if not scopes: - return - self.execute(s, scopes) - - def execute(self, docstring, scopes): - mod = py.std.types.ModuleType(self.fspath.basename, docstring) - self.mergescopes(mod, scopes) + l = [] + prefix = '.. >>> ' + mod = py.std.types.ModuleType(self.fspath.purebasename) + for line in s.split('\n'): + if line.startswith(prefix): + exec py.code.Source(line[len(prefix):]).compile() in mod.__dict__ + line = "" + else: + l.append(line) + docstring = "\n".join(l) + self.execute(mod, docstring) + + def execute(self, mod, docstring): + mod.__doc__ = docstring failed, tot = py.std.doctest.testmod(mod, verbose=1) if failed: py.test.fail("doctest %s: %s failed out of %s" %( @@ -118,6 +91,29 @@ return LinkCheckerMaker(name, self) elif name == 'doctest': return self.DoctestText(name, self) +# +# 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): + ReSTChecker = ReSTChecker + + 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 self.ReSTChecker(p, parent=self) +Directory = DocDirectory + # generating functions + args as single tests Modified: py/dist/py/documentation/test_conftest.py ============================================================================== --- py/dist/py/documentation/test_conftest.py (original) +++ py/dist/py/documentation/test_conftest.py Sun May 15 19:37:37 2005 @@ -11,14 +11,15 @@ xtxt = tmpdir.join('x.txt') xtxt.write(py.code.Source(""" - .. doctestmodulescope: os.path + .. + >>> from os.path import abspath hello world - >>> assert abspath - >>> i=3 - >>> print i - 3 + >>> assert abspath + >>> i=3 + >>> print i + 3 yes yes From hpk at codespeak.net Mon May 16 09:20:07 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 16 May 2005 09:20:07 +0200 (CEST) Subject: [py-svn] r12343 - py/dist/py/test Message-ID: <20050516072007.982AA27BDB@code1.codespeak.net> Author: hpk Date: Mon May 16 09:20:07 2005 New Revision: 12343 Modified: py/dist/py/test/cmdline.py Log: keyboard-interrupt will pass through if verbose > 0 Modified: py/dist/py/test/cmdline.py ============================================================================== --- py/dist/py/test/cmdline.py (original) +++ py/dist/py/test/cmdline.py Mon May 16 09:20:07 2005 @@ -13,6 +13,9 @@ if failures: raise SystemExit, 1 except KeyboardInterrupt: - print - print "KeyboardInterrupt" - raise SystemExit, 2 + if not config.option.verbose: + print + print "KeyboardInterrupt" + raise SystemExit, 2 + else: + raise From hpk at codespeak.net Mon May 16 09:47:34 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 16 May 2005 09:47:34 +0200 (CEST) Subject: [py-svn] r12344 - py/dist/py/magic/testing Message-ID: <20050516074734.2E20827BDB@code1.codespeak.net> Author: hpk Date: Mon May 16 09:47:33 2005 New Revision: 12344 Modified: py/dist/py/magic/testing/test_assertion.py Log: added Ian's tests (one of which passes IMO, though) Modified: py/dist/py/magic/testing/test_assertion.py ============================================================================== --- py/dist/py/magic/testing/test_assertion.py (original) +++ py/dist/py/magic/testing/test_assertion.py Mon May 16 09:47:33 2005 @@ -66,3 +66,24 @@ assert 0 == 1, A() except AssertionError, e: assert e.msg == "hello" + + +# These tests should both fail, but should fail nicely... +class WeirdRepr: + def __repr__(self): + return '' + +def bug_test_assert_repr(): + v = WeirdRepr() + try: + assert v == 1 + except AssertionError, e: + assert e.msg.find('WeirdRepr') != -1 + assert e.msg.find('second line') != -1 + assert 0 + +def test_assert_non_string(): + try: + assert 0, ['list'] + except AssertionError, e: + assert e.msg.find("list") != -1 From hpk at codespeak.net Tue May 17 21:46:35 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 17 May 2005 21:46:35 +0200 (CEST) Subject: [py-svn] r12420 - py/dist/py/path/svn Message-ID: <20050517194635.8EE0327C0A@code1.codespeak.net> Author: hpk Date: Tue May 17 21:46:35 2005 New Revision: 12420 Modified: py/dist/py/path/svn/urlcommand.py py/dist/py/path/svn/wccommand.py Log: fixed log() command for svnwc() Modified: py/dist/py/path/svn/urlcommand.py ============================================================================== --- py/dist/py/path/svn/urlcommand.py (original) +++ py/dist/py/path/svn/urlcommand.py Tue May 17 21:46:35 2005 @@ -303,7 +303,5 @@ self.rev, self.author, self.date) -class _Head: - def __str__(self): - return "HEAD" +_Head = "HEAD" Modified: py/dist/py/path/svn/wccommand.py ============================================================================== --- py/dist/py/path/svn/wccommand.py (original) +++ py/dist/py/path/svn/wccommand.py Tue May 17 21:46:35 2005 @@ -378,7 +378,7 @@ return True def log(self, rev_start=None, rev_end=1, verbose=False): - from py.__.path.svn.command import _Head, LogEntry + from py.__.path.svn.urlcommand 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 From arigo at codespeak.net Wed May 18 21:28:53 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Wed, 18 May 2005 21:28:53 +0200 (CEST) Subject: [py-svn] r12481 - py/dist/py/tool/testing Message-ID: <20050518192853.D05EC27BB2@code1.codespeak.net> Author: arigo Date: Wed May 18 21:28:53 2005 New Revision: 12481 Added: py/dist/py/tool/testing/ py/dist/py/tool/testing/__init__.py (contents, props changed) py/dist/py/tool/testing/test_utestconvert.py - copied, changed from r12466, pypy/dist/pypy/tool/test/test_utestconvert.py Log: Moved test_utestconvert from PyPy to here as well, following utestconvert itself. Added: py/dist/py/tool/testing/__init__.py ============================================================================== --- (empty file) +++ py/dist/py/tool/testing/__init__.py Wed May 18 21:28:53 2005 @@ -0,0 +1 @@ +# Copied: py/dist/py/tool/testing/test_utestconvert.py (from r12466, pypy/dist/pypy/tool/test/test_utestconvert.py) ============================================================================== --- pypy/dist/pypy/tool/test/test_utestconvert.py (original) +++ py/dist/py/tool/testing/test_utestconvert.py Wed May 18 21:28:53 2005 @@ -1,9 +1,7 @@ -import autopath -from pypy.tool.utestconvert import rewrite_utest -import unittest +from py.__.tool.utestconvert import rewrite_utest -class NonpyTest(unittest.TestCase): - def test(self): +class Test_UTestConvert: + def testall(self): assert rewrite_utest("badger badger badger") == ( "badger badger badger") @@ -347,11 +345,11 @@ """ ) - self.assertEquals(rewrite_utest( + assert rewrite_utest( """ self.failIfAlmostEqual(first, second, 5, 6, 7, 'Too Many Args') """ - ), + ) == ( """ self.failIfAlmostEqual(first, second, 5, 6, 7, 'Too Many Args') """ From arigo at codespeak.net Wed May 18 22:04:44 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Wed, 18 May 2005 22:04:44 +0200 (CEST) Subject: [py-svn] r12488 - py/dist/py/tool/testing Message-ID: <20050518200444.2FD1427BB9@code1.codespeak.net> Author: arigo Date: Wed May 18 22:04:44 2005 New Revision: 12488 Modified: py/dist/py/tool/testing/ (props changed) Log: fixeol. From arigo at codespeak.net Thu May 19 21:11:44 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Thu, 19 May 2005 21:11:44 +0200 (CEST) Subject: [py-svn] r12572 - py/dist/py/execnet Message-ID: <20050519191144.9279427B9C@code1.codespeak.net> Author: arigo Date: Thu May 19 21:11:44 2005 New Revision: 12572 Modified: py/dist/py/execnet/inputoutput.py Log: Using f.close() instead of os.close(f.fileno()). The file descriptor might be already gone, in some situations, though I don't know if this situation can occur here. The tests are still happy... Modified: py/dist/py/execnet/inputoutput.py ============================================================================== --- py/dist/py/execnet/inputoutput.py (original) +++ py/dist/py/execnet/inputoutput.py Thu May 19 21:11:44 2005 @@ -90,9 +90,9 @@ def close_read(self): if self.readable: - os.close(self.infile.fileno()) + self.infile.close() self.readable = None def close_write(self): if self.writeable: - os.close(self.outfile.fileno()) + self.outfile.close() self.writeable = None From hpk at codespeak.net Fri May 20 10:36:41 2005 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 20 May 2005 10:36:41 +0200 (CEST) Subject: [py-svn] r12604 - py/dist/py/documentation Message-ID: <20050520083641.30EC427B7B@code1.codespeak.net> Author: hpk Date: Fri May 20 10:36:41 2005 New Revision: 12604 Modified: py/dist/py/documentation/conftest.py Log: be more strict about local file references Modified: py/dist/py/documentation/conftest.py ============================================================================== --- py/dist/py/documentation/conftest.py (original) +++ py/dist/py/documentation/conftest.py Fri May 20 10:36:41 2005 @@ -125,7 +125,7 @@ if len(l) != 2: continue tryfn = l[1].strip() - if tryfn.startswith('http:'): + if tryfn.startswith('http:') or tryfn.startswith('https'): if option.checkremote: yield urlcheck, tryfn, path, lineno else: @@ -134,7 +134,7 @@ checkfn = tryfn[:i] else: checkfn = tryfn - if checkfn.endswith('.html'): + if checkfn.strip() and (1 or checkfn.endswith('.html')): yield localrefcheck, tryfn, path, lineno def urlcheck(tryfn, path, lineno): @@ -154,8 +154,9 @@ else: anchor = '' fn = path.dirpath(tryfn) + ishtml = fn.ext == '.html' fn = fn.new(ext='.txt') - if not fn.check(file=1): + if not ishtml or not fn.check(file=1): py.test.fail("reference error %r in %s:%d" %( tryfn, path.basename, lineno+1)) if anchor: From arigo at codespeak.net Fri May 20 19:59:17 2005 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 20 May 2005 19:59:17 +0200 (CEST) Subject: [py-svn] r12654 - in py/dist/py: path/local process Message-ID: <20050520175917.3D4E827B52@code1.codespeak.net> Author: arigo Date: Fri May 20 19:59:17 2005 New Revision: 12654 Modified: py/dist/py/path/local/local.py py/dist/py/process/cmdexec.py Log: Fixes for Windows ME. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Fri May 20 19:59:17 2005 @@ -477,7 +477,13 @@ else: if py.std.sys.platform == 'win32': paths = py.std.os.environ['Path'].split(';') - paths = [re.sub('%SystemRoot%', os.environ['SYSTEMROOT'], path) for path in paths] + try: + systemroot = os.environ['SYSTEMROOT'] + except KeyError: + pass + else: + paths = [re.sub('%SystemRoot%', systemroot, path) + for path in paths] tryadd = '', '.exe', '.com', '.bat' # XXX add more? else: paths = py.std.os.environ['PATH'].split(':') Modified: py/dist/py/process/cmdexec.py ============================================================================== --- py/dist/py/process/cmdexec.py (original) +++ py/dist/py/process/cmdexec.py Fri May 20 19:59:17 2005 @@ -84,9 +84,19 @@ 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 + #print "*****", cmd + + # the following quoting is only valid for CMD.EXE, not COMMAND.COM + cmd_quoting = True + try: + if os.environ['COMSPEC'].upper().endswith('COMMAND.COM'): + cmd_quoting = False + except KeyError: + pass + if cmd_quoting: + if '"' in cmd and not cmd.startswith('""'): + cmd = '"%s"' % cmd + stdin, stdout, stderr = os.popen3(cmd) out = stdout.read() err = stderr.read()