From hpk at codespeak.net Mon Sep 1 10:37:20 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 1 Sep 2008 10:37:20 +0200 (CEST) Subject: [py-svn] r57707 - in py/trunk/py/doc: . future Message-ID: <20080901083720.7A8D9168420@codespeak.net> Author: hpk Date: Mon Sep 1 10:37:17 2008 New Revision: 57707 Removed: py/trunk/py/doc/future/ Modified: py/trunk/py/doc/TODO.txt Log: review todo.txt and future/* items, merge into one TODO.txt file. Modified: py/trunk/py/doc/TODO.txt ============================================================================== --- py/trunk/py/doc/TODO.txt (original) +++ py/trunk/py/doc/TODO.txt Mon Sep 1 10:37:17 2008 @@ -4,26 +4,27 @@ py.test -------------- -- BUG: write test/fix --showlocals (not showing anything) +- simplify collect API + +- get APIGEN back to work + +- get web reporter back to work + +- introduce decorator "shouldfail" or "xfail" + as to mark a test as "expected to fail", + report specially if it surprisingly passes + +- nightly test runs on multiple platforms - review and refactor architecture of py.test with particular respect to: - - allow custom reporting - writing (stacked) extensions / plugins (compared to Nose) - - event naming and processing - porting existing extensions (htmlconftest / buildbot / PyPy's conftest's ...) - fast and stable distributed testing - reliable cross-platform testing -- fix reporting/usage degradation after reporter-merge merge: - - collapse skips with same reason and lineno into one line - -- fix and investigate win32 failures - -- (needs review) adjust py.test documentation to reflect new - collector/session architecture - -- document py.test's conftest.py approach +- improve py.test documentation to reflect new + event architecture - review and optimize skip-handling (it can be quite slow in certain situations because e.g. setup/teardown is fully performed @@ -40,8 +41,11 @@ py.apigen ---------------- -- refactor to produce intermediate data/files capturing - info of test runs +- make it work again + +- py.apigen tool -> separate runtime-data collection and + web page generation. (see M750), provide "py.apigen" tool + - refactor html renderer to work on intermediate data/files rather than on the live data @@ -55,12 +59,6 @@ refactorings ------------------ -- session / collection unification (particularly tryiter and buildname2items) - -- reporting unification, i.e. use dist-testing Reporter class - also for "normal" session, consider introduction of tkinter - session (M978) - - refine doctests usage (particularly skips of doctests if some imports/conditions are not satisfied) @@ -74,22 +72,17 @@ - unification of "gateway"/host setup and teardown, including rsyncing, i.e. cross-platform and dist-testing. -- py.apigen tool -> separate runtime-data collection and - web page generation. (see M750), provide "py.apigen" tool - for generating API documentation - - py.log: unify API, possibly deprecate duplicate ones, base things on a Config object (hte latter almost a feature though) (M988) -- consider setup/teardown for generative tests (M826) - -- fix teardown problems regarding when teardown is done (should be done - after test run, not before the next one) +- see to teardown more eagerly features -------------- +- (Harald Armin Massa): make py.exe work with py lib +- optimize file checking with --looponfailing (harald has code for win32) - have a py.test scan/run database for results and test names etc. (to allow quicker selection of tests and post-run information on failures etc.) (M760) @@ -111,467 +104,10 @@ - review svn-testing (and escape characters), consider svn-bindings (M634) +- py.test.pdb - there is my hack for a while now, which integrates + rlcompleter2 with pdb. First of all it requires some strange changes + to rlcompleter itself, which has no tests. Long-term plan would be + to have pyrepl+rlcompleter2+pdb fixes integrated into pylib and + have it tested. This requires work though. -packaging -------------------------------------- - -* debian and TAR/zip packages for py lib, - particularly look into C module issues (greenlet most importantly) - -* do something about c-extensions both on unix-ish - versus win32 systems - -* ensure compatibility with Python 2.3 - 2.5, - see what is missing for 2.2 - -* optional: support setuptools (eggs?) installs, and instally - from pypi (and register pylib there) - -* (DONE/c-modules don't) see if things work on Win32 (partially done) - -* (partly DONE) refine and implement releasescheme/download - - -APIGEN / source viewer -------------------------------------- - -* (DONE, XXX functions/methods?) integrate rest directive into - py/documentation/conftest.py - with help code from py.__.rest.directive.... - make sure that the txt files in py/documentation/ use it - -testing ------------ - -* these should all work on 1.0 and on the py lib and pypy: - - running "py.test -s" - - running "py.test --pdb" - - running "py.test --looponfailing" - - running "py.test" distributed on some hosts - - (guido tested all on win32, everything works except --dist (requires - os.fork to work)) - -code quality ------------------ - -* no function implementation longer than 30 lines - -* no lines longer than 80 characters - -* review the pylib issue tracker - (cfbolz: done: what has a 1.0.0 tag (or lower) should be looked at again) - -py.test -------- - -* (postponed, likely) py.test fails to parse strangely formatted code after assertion failure - -Missing docstrings ------------------- - -:: - code.Traceback.recursionindex misses a docstring - code.Traceback.filter misses a docstring - code.Traceback.cut misses a docstring - code.Traceback.__getitem__ misses a docstring - code.Traceback.Entry misses a docstring - code.Traceback.Entry.ishidden misses a docstring - code.Traceback.Entry.getfirstlinesource misses a docstring - code.Traceback.Entry.__str__ misses a docstring - code.Traceback.Entry.__repr__ misses a docstring - code.Traceback.Entry.__init__ misses a docstring - code.Source.getblockend misses a docstring - code.Source.__str__ misses a docstring - code.Source.__len__ misses a docstring - code.Source.__init__ misses a docstring - code.Source.__getslice__ misses a docstring - code.Source.__getitem__ misses a docstring - code.Source.__eq__ misses a docstring - code.Frame.repr misses a docstring - code.Frame.is_true misses a docstring - code.Frame.getargs misses a docstring - code.Frame.exec_ misses a docstring - code.Frame.eval misses a docstring - code.Frame.__init__ misses a docstring - code.ExceptionInfo.exconly misses a docstring - code.ExceptionInfo.errisinstance misses a docstring - code.ExceptionInfo.__str__ misses a docstring - code.ExceptionInfo.__init__ misses a docstring - code.Code misses a docstring - code.Code.source misses a docstring - code.Code.getargs misses a docstring - code.Code.__init__ misses a docstring - code.Code.__eq__ misses a docstring - execnet.SshGateway misses a docstring - execnet.SshGateway.join misses a docstring - execnet.SshGateway.exit misses a docstring - execnet.SshGateway.__repr__ misses a docstring - execnet.SshGateway.__init__ misses a docstring - execnet.SshGateway.ThreadOut.write misses a docstring - execnet.SshGateway.ThreadOut.setwritefunc misses a docstring - execnet.SshGateway.ThreadOut.setdefaultwriter misses a docstring - execnet.SshGateway.ThreadOut.resetdefault misses a docstring - execnet.SshGateway.ThreadOut.isatty misses a docstring - execnet.SshGateway.ThreadOut.flush misses a docstring - execnet.SshGateway.ThreadOut.delwritefunc misses a docstring - execnet.SshGateway.ThreadOut.deinstall misses a docstring - execnet.SocketGateway misses a docstring - execnet.SocketGateway.join misses a docstring - execnet.SocketGateway.exit misses a docstring - execnet.SocketGateway.__repr__ misses a docstring - execnet.SocketGateway.__init__ misses a docstring - execnet.PopenGateway misses a docstring - execnet.PopenGateway.remote_bootstrap_gateway misses a docstring - execnet.PopenGateway.join misses a docstring - execnet.PopenGateway.exit misses a docstring - execnet.PopenGateway.__repr__ misses a docstring - execnet.PopenGateway.__init__ misses a docstring - initpkg misses a docstring - log.setconsumer misses a docstring - log.get misses a docstring - log.Syslog misses a docstring - log.STDOUT misses a docstring - log.STDERR misses a docstring - log.Producer.set_consumer misses a docstring - log.Producer.get_consumer misses a docstring - log.Producer.__repr__ misses a docstring - log.Producer.__init__ misses a docstring - log.Producer.__getattr__ misses a docstring - log.Producer.__call__ misses a docstring - log.Producer.Message misses a docstring - log.Producer.Message.prefix misses a docstring - log.Producer.Message.content misses a docstring - log.Producer.Message.__str__ misses a docstring - log.Producer.Message.__init__ misses a docstring - log.Path misses a docstring - log.Path.__init__ misses a docstring - log.Path.__call__ misses a docstring - magic.View.__viewkey__ misses a docstring - magic.View.__repr__ misses a docstring - magic.View.__new__ misses a docstring - magic.View.__matchkey__ misses a docstring - magic.View.__getattr__ misses a docstring - magic.AssertionError misses a docstring - path.svnwc.visit misses a docstring - path.svnwc.mkdir misses a docstring - path.svnwc.dump misses a docstring - path.svnwc.check misses a docstring - path.svnwc.add misses a docstring - path.svnwc.__str__ misses a docstring - path.svnwc.__repr__ misses a docstring - path.svnwc.__new__ misses a docstring - path.svnwc.__iter__ misses a docstring - path.svnwc.__hash__ misses a docstring - path.svnwc.__eq__ misses a docstring - path.svnwc.__div__ misses a docstring - path.svnwc.__contains__ misses a docstring - path.svnwc.__cmp__ misses a docstring - path.svnwc.__add__ misses a docstring - path.svnwc.Checkers misses a docstring - path.svnurl.visit misses a docstring - path.svnurl.check misses a docstring - path.svnurl.__repr__ misses a docstring - path.svnurl.__new__ misses a docstring - path.svnurl.__ne__ misses a docstring - path.svnurl.__iter__ misses a docstring - path.svnurl.__hash__ misses a docstring - path.svnurl.__div__ misses a docstring - path.svnurl.__contains__ misses a docstring - path.svnurl.__cmp__ misses a docstring - path.svnurl.__add__ misses a docstring - path.svnurl.Checkers misses a docstring - path.local.visit misses a docstring - path.local.sysexec has an 'XXX' in its docstring - path.local.check misses a docstring - path.local.__repr__ misses a docstring - path.local.__iter__ misses a docstring - path.local.__hash__ misses a docstring - path.local.__eq__ misses a docstring - path.local.__div__ misses a docstring - path.local.__contains__ misses a docstring - path.local.__cmp__ misses a docstring - path.local.__add__ misses a docstring - path.local.Checkers misses a docstring - test.rest.RestReporter misses a docstring - test.rest.RestReporter.summary misses a docstring - test.rest.RestReporter.skips misses a docstring - test.rest.RestReporter.repr_traceback misses a docstring - test.rest.RestReporter.repr_source misses a docstring - test.rest.RestReporter.repr_signal misses a docstring - test.rest.RestReporter.repr_failure misses a docstring - test.rest.RestReporter.report_unknown misses a docstring - test.rest.RestReporter.report_TestStarted misses a docstring - test.rest.RestReporter.report_TestFinished misses a docstring - test.rest.RestReporter.report_SkippedTryiter misses a docstring - test.rest.RestReporter.report_SendItem misses a docstring - test.rest.RestReporter.report_RsyncFinished misses a docstring - test.rest.RestReporter.report_ReceivedItemOutcome misses a docstring - test.rest.RestReporter.report_Nodes misses a docstring - test.rest.RestReporter.report_ItemStart misses a docstring - test.rest.RestReporter.report_ImmediateFailure misses a docstring - test.rest.RestReporter.report_HostReady misses a docstring - test.rest.RestReporter.report_HostRSyncing misses a docstring - test.rest.RestReporter.report_FailedTryiter misses a docstring - test.rest.RestReporter.report misses a docstring - test.rest.RestReporter.print_summary misses a docstring - test.rest.RestReporter.prepare_source misses a docstring - test.rest.RestReporter.hangs misses a docstring - test.rest.RestReporter.get_rootpath misses a docstring - test.rest.RestReporter.get_path_from_item misses a docstring - test.rest.RestReporter.get_linkwriter misses a docstring - test.rest.RestReporter.get_item_name misses a docstring - test.rest.RestReporter.get_host misses a docstring - test.rest.RestReporter.failures misses a docstring - test.rest.RestReporter.add_rest misses a docstring - test.rest.RestReporter.__init__ misses a docstring - test.rest.RelLinkWriter misses a docstring - test.rest.RelLinkWriter.get_link misses a docstring - test.rest.NoLinkWriter misses a docstring - test.rest.NoLinkWriter.get_link misses a docstring - test.rest.LinkWriter misses a docstring - test.rest.LinkWriter.get_link misses a docstring - test.rest.LinkWriter.__init__ misses a docstring - test.exit misses a docstring - test.deprecated_call misses a docstring - test.collect.Module misses a docstring - test.collect.Module.tryiter has an 'XXX' in its docstring - test.collect.Module.teardown misses a docstring - test.collect.Module.startcapture misses a docstring - test.collect.Module.skipbykeyword misses a docstring - test.collect.Module.setup misses a docstring - test.collect.Module.run misses a docstring - test.collect.Module.makeitem misses a docstring - test.collect.Module.listnames misses a docstring - test.collect.Module.join misses a docstring - test.collect.Module.haskeyword misses a docstring - test.collect.Module.getsortvalue misses a docstring - test.collect.Module.getpathlineno misses a docstring - test.collect.Module.getouterr misses a docstring - test.collect.Module.getitembynames misses a docstring - test.collect.Module.funcnamefilter misses a docstring - test.collect.Module.finishcapture misses a docstring - test.collect.Module.classnamefilter misses a docstring - test.collect.Module.buildname2items misses a docstring - test.collect.Module.__repr__ misses a docstring - test.collect.Module.__ne__ misses a docstring - test.collect.Module.__init__ misses a docstring - test.collect.Module.__hash__ misses a docstring - test.collect.Module.__eq__ misses a docstring - test.collect.Module.__cmp__ misses a docstring - test.collect.Module.Skipped misses a docstring - test.collect.Module.Passed misses a docstring - test.collect.Module.Outcome misses a docstring - test.collect.Module.Failed misses a docstring - test.collect.Module.ExceptionFailure misses a docstring - test.collect.Instance misses a docstring - test.collect.Instance.tryiter has an 'XXX' in its docstring - test.collect.Instance.teardown misses a docstring - test.collect.Instance.startcapture misses a docstring - test.collect.Instance.skipbykeyword misses a docstring - test.collect.Instance.setup misses a docstring - test.collect.Instance.run misses a docstring - test.collect.Instance.makeitem misses a docstring - test.collect.Instance.listnames misses a docstring - test.collect.Instance.join misses a docstring - test.collect.Instance.haskeyword misses a docstring - test.collect.Instance.getsortvalue misses a docstring - test.collect.Instance.getpathlineno misses a docstring - test.collect.Instance.getouterr misses a docstring - test.collect.Instance.getitembynames misses a docstring - test.collect.Instance.funcnamefilter misses a docstring - test.collect.Instance.finishcapture misses a docstring - test.collect.Instance.classnamefilter misses a docstring - test.collect.Instance.buildname2items misses a docstring - test.collect.Instance.__repr__ misses a docstring - test.collect.Instance.__ne__ misses a docstring - test.collect.Instance.__init__ misses a docstring - test.collect.Instance.__hash__ misses a docstring - test.collect.Instance.__eq__ misses a docstring - test.collect.Instance.__cmp__ misses a docstring - test.collect.Generator misses a docstring - test.collect.Generator.tryiter has an 'XXX' in its docstring - test.collect.Generator.teardown misses a docstring - test.collect.Generator.startcapture misses a docstring - test.collect.Generator.skipbykeyword misses a docstring - test.collect.Generator.setup misses a docstring - test.collect.Generator.run misses a docstring - test.collect.Generator.listnames misses a docstring - test.collect.Generator.join misses a docstring - test.collect.Generator.haskeyword misses a docstring - test.collect.Generator.getsortvalue misses a docstring - test.collect.Generator.getpathlineno misses a docstring - test.collect.Generator.getouterr misses a docstring - test.collect.Generator.getitembynames misses a docstring - test.collect.Generator.getcallargs misses a docstring - test.collect.Generator.finishcapture misses a docstring - test.collect.Generator.buildname2items misses a docstring - test.collect.Generator.__repr__ misses a docstring - test.collect.Generator.__ne__ misses a docstring - test.collect.Generator.__init__ misses a docstring - test.collect.Generator.__hash__ misses a docstring - test.collect.Generator.__eq__ misses a docstring - test.collect.Generator.__cmp__ misses a docstring - test.collect.DoctestFile misses a docstring - test.collect.DoctestFile.tryiter has an 'XXX' in its docstring - test.collect.DoctestFile.teardown misses a docstring - test.collect.DoctestFile.startcapture misses a docstring - test.collect.DoctestFile.skipbykeyword misses a docstring - test.collect.DoctestFile.setup misses a docstring - test.collect.DoctestFile.run misses a docstring - test.collect.DoctestFile.makeitem misses a docstring - test.collect.DoctestFile.listnames misses a docstring - test.collect.DoctestFile.join misses a docstring - test.collect.DoctestFile.haskeyword misses a docstring - test.collect.DoctestFile.getsortvalue misses a docstring - test.collect.DoctestFile.getpathlineno misses a docstring - test.collect.DoctestFile.getouterr misses a docstring - test.collect.DoctestFile.getitembynames misses a docstring - test.collect.DoctestFile.funcnamefilter misses a docstring - test.collect.DoctestFile.finishcapture misses a docstring - test.collect.DoctestFile.classnamefilter misses a docstring - test.collect.DoctestFile.buildname2items misses a docstring - test.collect.DoctestFile.__repr__ misses a docstring - test.collect.DoctestFile.__ne__ misses a docstring - test.collect.DoctestFile.__init__ misses a docstring - test.collect.DoctestFile.__hash__ misses a docstring - test.collect.DoctestFile.__eq__ misses a docstring - test.collect.DoctestFile.__cmp__ misses a docstring - test.collect.Directory misses a docstring - test.collect.Directory.tryiter has an 'XXX' in its docstring - test.collect.Directory.teardown misses a docstring - test.collect.Directory.startcapture misses a docstring - test.collect.Directory.skipbykeyword misses a docstring - test.collect.Directory.setup misses a docstring - test.collect.Directory.run misses a docstring - test.collect.Directory.recfilter misses a docstring - test.collect.Directory.makeitem misses a docstring - test.collect.Directory.listnames misses a docstring - test.collect.Directory.join misses a docstring - test.collect.Directory.haskeyword misses a docstring - test.collect.Directory.getsortvalue misses a docstring - test.collect.Directory.getpathlineno misses a docstring - test.collect.Directory.getouterr misses a docstring - test.collect.Directory.getitembynames misses a docstring - test.collect.Directory.finishcapture misses a docstring - test.collect.Directory.filefilter misses a docstring - test.collect.Directory.buildname2items misses a docstring - test.collect.Directory.__repr__ misses a docstring - test.collect.Directory.__ne__ misses a docstring - test.collect.Directory.__init__ misses a docstring - test.collect.Directory.__hash__ misses a docstring - test.collect.Directory.__eq__ misses a docstring - test.collect.Directory.__cmp__ misses a docstring - test.collect.Collector misses a docstring - test.collect.Collector.tryiter has an 'XXX' in its docstring - test.collect.Collector.teardown misses a docstring - test.collect.Collector.startcapture misses a docstring - test.collect.Collector.skipbykeyword misses a docstring - test.collect.Collector.setup misses a docstring - test.collect.Collector.run misses a docstring - test.collect.Collector.listnames misses a docstring - test.collect.Collector.join misses a docstring - test.collect.Collector.haskeyword misses a docstring - test.collect.Collector.getsortvalue misses a docstring - test.collect.Collector.getpathlineno misses a docstring - test.collect.Collector.getouterr misses a docstring - test.collect.Collector.getitembynames misses a docstring - test.collect.Collector.finishcapture misses a docstring - test.collect.Collector.buildname2items misses a docstring - test.collect.Collector.__repr__ misses a docstring - test.collect.Collector.__ne__ misses a docstring - test.collect.Collector.__init__ misses a docstring - test.collect.Collector.__hash__ misses a docstring - test.collect.Collector.__eq__ misses a docstring - test.collect.Collector.__cmp__ misses a docstring - test.collect.Class misses a docstring - test.collect.Class.tryiter has an 'XXX' in its docstring - test.collect.Class.teardown misses a docstring - test.collect.Class.startcapture misses a docstring - test.collect.Class.skipbykeyword misses a docstring - test.collect.Class.setup misses a docstring - test.collect.Class.run misses a docstring - test.collect.Class.makeitem misses a docstring - test.collect.Class.listnames misses a docstring - test.collect.Class.join misses a docstring - test.collect.Class.haskeyword misses a docstring - test.collect.Class.getsortvalue misses a docstring - test.collect.Class.getpathlineno misses a docstring - test.collect.Class.getouterr misses a docstring - test.collect.Class.getitembynames misses a docstring - test.collect.Class.funcnamefilter misses a docstring - test.collect.Class.finishcapture misses a docstring - test.collect.Class.classnamefilter misses a docstring - test.collect.Class.buildname2items misses a docstring - test.collect.Class.__repr__ misses a docstring - test.collect.Class.__ne__ misses a docstring - test.collect.Class.__init__ misses a docstring - test.collect.Class.__hash__ misses a docstring - test.collect.Class.__eq__ misses a docstring - test.collect.Class.__cmp__ misses a docstring - test.cmdline.main misses a docstring - test.Item misses a docstring - test.Item.tryiter has an 'XXX' in its docstring - test.Item.teardown misses a docstring - test.Item.startcapture misses a docstring - test.Item.skipbykeyword misses a docstring - test.Item.setup misses a docstring - test.Item.run misses a docstring - test.Item.listnames misses a docstring - test.Item.join misses a docstring - test.Item.haskeyword misses a docstring - test.Item.getsortvalue misses a docstring - test.Item.getpathlineno misses a docstring - test.Item.getouterr misses a docstring - test.Item.getitembynames misses a docstring - test.Item.finishcapture misses a docstring - test.Item.buildname2items misses a docstring - test.Item.__repr__ misses a docstring - test.Item.__ne__ misses a docstring - test.Item.__init__ misses a docstring - test.Item.__hash__ misses a docstring - test.Item.__eq__ misses a docstring - test.Item.__cmp__ misses a docstring - test.Function.tryiter has an 'XXX' in its docstring - test.Function.teardown misses a docstring - test.Function.startcapture misses a docstring - test.Function.skipbykeyword misses a docstring - test.Function.setup misses a docstring - test.Function.run misses a docstring - test.Function.listnames misses a docstring - test.Function.join misses a docstring - test.Function.haskeyword misses a docstring - test.Function.getsortvalue misses a docstring - test.Function.getpathlineno misses a docstring - test.Function.getouterr misses a docstring - test.Function.getitembynames misses a docstring - test.Function.finishcapture misses a docstring - test.Function.buildname2items misses a docstring - test.Function.__repr__ misses a docstring - test.Function.__ne__ misses a docstring - test.Function.__init__ misses a docstring - test.Function.__hash__ misses a docstring - test.Function.__eq__ misses a docstring - test.Function.__cmp__ misses a docstring - test.Config.__init__ misses a docstring - xml.raw.__init__ misses a docstring - xml.html misses a docstring - xml.html.__tagclass__ misses a docstring - xml.html.__tagclass__.unicode misses a docstring - xml.html.__tagclass__.__unicode__ misses a docstring - xml.html.__tagclass__.__repr__ misses a docstring - xml.html.__tagclass__.__init__ misses a docstring - xml.html.__tagclass__.Attr misses a docstring - xml.html.__tagclass__.Attr.__init__ misses a docstring - xml.html.__metaclass__ misses a docstring - xml.html.__metaclass__.__getattr__ misses a docstring - xml.html.Style misses a docstring - xml.html.Style.__init__ misses a docstring - xml.escape misses a docstring - xml.Tag misses a docstring - xml.Tag.unicode misses a docstring - xml.Tag.__unicode__ misses a docstring - xml.Tag.__repr__ misses a docstring - xml.Tag.__init__ misses a docstring - xml.Namespace misses a docstring From hpk at codespeak.net Tue Sep 2 10:58:16 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 2 Sep 2008 10:58:16 +0200 (CEST) Subject: [py-svn] r57744 - py/trunk/py/test Message-ID: <20080902085816.4328F169F7F@codespeak.net> Author: hpk Date: Tue Sep 2 10:58:14 2008 New Revision: 57744 Added: py/trunk/py/test/ - copied from r57743, user/hpk/branch/py-collect/py/test/ Log: Merging collection-API refactoring branch: * collectors now have a collect() method * items have a runtest() method * deprecated old run/join of collectors and run/execute methods of items From hpk at codespeak.net Tue Sep 2 14:24:18 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 2 Sep 2008 14:24:18 +0200 (CEST) Subject: [py-svn] r57754 - in py/trunk/py: . compat doc doc/example/pytest green test test/testing Message-ID: <20080902122418.59F4216856A@codespeak.net> Author: hpk Date: Tue Sep 2 14:24:15 2008 New Revision: 57754 Modified: py/trunk/py/__init__.py py/trunk/py/compat/conftest.py py/trunk/py/doc/TODO.txt py/trunk/py/doc/conftest.py py/trunk/py/doc/example/pytest/test_failures.py py/trunk/py/doc/test_conftest.py py/trunk/py/green/conftest.py py/trunk/py/test/collect.py py/trunk/py/test/pycollect.py py/trunk/py/test/testing/test_collect.py Log: * introduce py.test.collect.File (File for py or non-py files) * introduce py.test.collect.Collector.collect_by_name and special case it for Directories to allow specifying files that would otherwise be ignored because of filters. * fix py/doc/conftest to work with new API * refactor py/doc/test_conftest.py to use suptest helper * avoid old APIs in some more places. Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Tue Sep 2 14:24:15 2008 @@ -78,12 +78,13 @@ # for customization of collecting/running tests 'test.collect.Collector' : ('./test/collect.py', 'Collector'), 'test.collect.Directory' : ('./test/collect.py', 'Directory'), + 'test.collect.File' : ('./test/collect.py', 'File'), + 'test.collect.Item' : ('./test/collect.py', 'Item'), 'test.collect.Module' : ('./test/pycollect.py', 'Module'), 'test.collect.DoctestFile' : ('./test/pycollect.py', 'DoctestFile'), 'test.collect.Class' : ('./test/pycollect.py', 'Class'), 'test.collect.Instance' : ('./test/pycollect.py', 'Instance'), 'test.collect.Generator' : ('./test/pycollect.py', 'Generator'), - 'test.collect.Item' : ('./test/collect.py', 'Item'), 'test.collect.Function' : ('./test/pycollect.py', 'Function'), # thread related API (still in early design phase) Modified: py/trunk/py/compat/conftest.py ============================================================================== --- py/trunk/py/compat/conftest.py (original) +++ py/trunk/py/compat/conftest.py Tue Sep 2 14:24:15 2008 @@ -1,5 +1,5 @@ import py class Directory(py.test.collect.Directory): - def listdir(self): - py.test.skip("compat tests currently need to be run manually") + def collect(self): + py.test.skip("compat tests need to be run manually") Modified: py/trunk/py/doc/TODO.txt ============================================================================== --- py/trunk/py/doc/TODO.txt (original) +++ py/trunk/py/doc/TODO.txt Tue Sep 2 14:24:15 2008 @@ -43,15 +43,13 @@ - make it work again -- py.apigen tool -> separate runtime-data collection and - web page generation. (see M750), provide "py.apigen" tool - -- refactor html renderer to work on intermediate - data/files rather than on the live data +see apigen_refactorings.txt - check out CodeInvestigator http://codeinvestigator.googlepages.com/main + or other code that collects data from running a program + (in our case running the tests) ld (review and shift to above) ================================= Modified: py/trunk/py/doc/conftest.py ============================================================================== --- py/trunk/py/doc/conftest.py (original) +++ py/trunk/py/doc/conftest.py Tue Sep 2 14:24:15 2008 @@ -110,12 +110,12 @@ #return [] # no need to rebuild class ReSTSyntaxTest(py.test.collect.Item): - def execute(self): + def runtest(self): mypath = self.fspath restcheck(py.path.svnwc(mypath)) class DoctestText(py.test.collect.Item): - def execute(self): + def runtest(self): s = self._normalize_linesep() l = [] prefix = '.. >>> ' @@ -158,20 +158,15 @@ return s class LinkCheckerMaker(py.test.collect.Collector): - def listdir(self): - l = [] + def collect(self): + l = [] for call, tryfn, path, lineno in genlinkchecks(self.fspath): - l.append("%s:%d" %(tryfn, lineno)) + name = "%s:%d" %(tryfn, lineno) + l.append( + CheckLink(name, parent=self, args=(tryfn, path, lineno), callobj=call) + ) return l - def join(self, name): - i = name.rfind(':') - assert i != -1 - jname, jlineno = name[:i], int(name[i+1:]) - for call, tryfn, path, lineno in genlinkchecks(self.fspath): - if tryfn == jname and lineno == jlineno: - return CheckLink(name, parent=self, args=(tryfn, path, lineno), callobj=call) - class CheckLink(py.test.collect.Function): def repr_metainfo(self): return self.ReprMetaInfo(fspath=self.fspath, lineno=self._args[2], @@ -181,26 +176,17 @@ def teardown(self): pass -class ReSTChecker(py.test.collect.Module): +class DocfileTests(py.test.collect.File): DoctestText = DoctestText ReSTSyntaxTest = ReSTSyntaxTest + LinkCheckerMaker = LinkCheckerMaker - def __repr__(self): - return py.test.collect.Collector.__repr__(self) - - def setup(self): - pass - def teardown(self): - pass - def listdir(self): - return [self.fspath.basename, 'checklinks', 'doctest'] - def join(self, name): - if name == self.fspath.basename: - return self.ReSTSyntaxTest(name, parent=self) - elif name == 'checklinks': - return LinkCheckerMaker(name, self) - elif name == 'doctest': - return self.DoctestText(name, self) + def collect(self): + return [ + self.ReSTSyntaxTest(self.fspath.basename, parent=self), + self.LinkCheckerMaker("checklinks", self), + self.DoctestText("doctest", self), + ] # generating functions + args as single tests def genlinkchecks(path): @@ -286,20 +272,13 @@ # hooking into py.test Directory collector's chain ... class DocDirectory(py.test.collect.Directory): - ReSTChecker = ReSTChecker - - def listdir(self): - results = super(DocDirectory, self).listdir() + DocfileTests = DocfileTests + def collect(self): + results = super(DocDirectory, self).collect() for x in self.fspath.listdir('*.txt', sort=True): - results.append(x.basename) + results.append(self.DocfileTests(x, parent=self)) 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 def resolve_linkrole(name, text, check=True): Modified: py/trunk/py/doc/example/pytest/test_failures.py ============================================================================== --- py/trunk/py/doc/example/pytest/test_failures.py (original) +++ py/trunk/py/doc/example/pytest/test_failures.py Tue Sep 2 14:24:15 2008 @@ -1,11 +1,15 @@ import py failure_demo = py.magic.autopath().dirpath('failure_demo.py') -from py.__.doc.test_conftest import countoutcomes + +from py.__.test.testing import suptest +from py.__.test import event def test_failure_demo_fails_properly(): - config = py.test.config._reparse([failure_demo]) - session = config.initsession() - failed, passed, skipped = countoutcomes(session) - assert failed == 21 + sorter = suptest.events_from_cmdline([failure_demo]) + passed, skipped, failed = sorter.countoutcomes() assert passed == 0 + assert failed == 20, failed + colreports = sorter.get(event.CollectionReport) + failed = len([x.failed for x in colreports]) + assert failed == 5 Modified: py/trunk/py/doc/test_conftest.py ============================================================================== --- py/trunk/py/doc/test_conftest.py (original) +++ py/trunk/py/doc/test_conftest.py Tue Sep 2 14:24:15 2008 @@ -1,74 +1,80 @@ import py from py.__.test import event +from py.__.test.testing import suptest +from py.__.doc import conftest as doc_conftest -def setup_module(mod): - mod.tmpdir = py.test.ensuretemp('docdoctest') -def countoutcomes(session): - l = [] - session.bus.subscribe(l.append) - session.main() - session.bus.unsubscribe(l.append) - passed = failed = skipped = 0 - for ev in l: - if isinstance(ev, event.ItemTestReport): - if ev.passed: - passed += 1 - elif ev.skipped: - skipped += 1 - else: - failed += 1 - elif isinstance(ev, event.CollectionReport): - if ev.failed: - failed += 1 - return failed, passed, skipped - -def test_doctest_extra_exec(): - # XXX get rid of the next line: - py.magic.autopath().dirpath('conftest.py').copy(tmpdir.join('conftest.py')) - xtxt = tmpdir.join('y.txt') - xtxt.write(py.code.Source(""" - hello:: - .. >>> raise ValueError - >>> None - """)) - config = py.test.config._reparse([xtxt]) - session = config.initsession() - failed, passed, skipped = countoutcomes(session) - assert failed == 1 - -def test_doctest_basic(): - # XXX get rid of the next line: - py.magic.autopath().dirpath('conftest.py').copy(tmpdir.join('conftest.py')) - - xtxt = tmpdir.join('x.txt') - xtxt.write(py.code.Source(""" - .. - >>> from os.path import abspath - - hello world - - >>> assert abspath - >>> i=3 - >>> print i - 3 - - yes yes - - >>> i - 3 - - end - """)) - config = py.test.config._reparse([xtxt]) - session = config.initsession() - failed, passed, skipped = countoutcomes(session) - assert failed == 0 - assert passed + skipped == 2 +class TestDoctest(suptest.InlineCollection): + def setup_method(self, method): + super(TestDoctest, self).setup_method(method) + p = py.path.local(doc_conftest.__file__) + if p.ext == ".pyc": + p = p.new(ext=".py") + p.copy(self.tmpdir.join("conftest.py")) + + def test_doctest_extra_exec(self): + xtxt = self.maketxtfile(x=""" + hello:: + .. >>> raise ValueError + >>> None + """) + sorter = suptest.events_from_cmdline([xtxt]) + passed, skipped, failed = sorter.countoutcomes() + assert failed == 1 + + def test_doctest_basic(self): + xtxt = self.maketxtfile(x=""" + .. + >>> from os.path import abspath + + hello world + + >>> assert abspath + >>> i=3 + >>> print i + 3 + + yes yes + + >>> i + 3 + + end + """) + sorter = suptest.events_from_cmdline([xtxt]) + passed, skipped, failed = sorter.countoutcomes() + assert failed == 0 + assert passed + skipped == 2 + + def test_doctest_eol(self): + ytxt = self.maketxtfile(y=".. >>> 1 + 1\r\n 2\r\n\r\n") + sorter = suptest.events_from_cmdline([ytxt]) + passed, skipped, failed = sorter.countoutcomes() + assert failed == 0 + assert passed + skipped == 2 + + def test_doctest_indentation(self): + footxt = self.maketxtfile(foo= + '..\n >>> print "foo\\n bar"\n foo\n bar\n') + sorter = suptest.events_from_cmdline([footxt]) + passed, skipped, failed = sorter.countoutcomes() + assert failed == 0 + assert skipped + passed == 2 + + def test_js_ignore(self): + xtxt = self.maketxtfile(xtxt=""" + `blah`_ + + .. _`blah`: javascript:some_function() + """) + sorter = suptest.events_from_cmdline([xtxt]) + passed, skipped, failed = sorter.countoutcomes() + assert failed == 0 + assert skipped + passed == 3 def test_deindent(): - from py.__.doc.conftest import deindent + deindent = doc_conftest.deindent assert deindent('foo') == 'foo' assert deindent('foo\n bar') == 'foo\n bar' assert deindent(' foo\n bar\n') == 'foo\nbar\n' @@ -76,45 +82,6 @@ assert deindent(' foo\n bar\n') == 'foo\n bar\n' assert deindent(' foo\n bar\n') == ' foo\nbar\n' -def test_doctest_eol(): - # XXX get rid of the next line: - py.magic.autopath().dirpath('conftest.py').copy(tmpdir.join('conftest.py')) - - ytxt = tmpdir.join('y.txt') - ytxt.write(py.code.Source(".. >>> 1 + 1\r\n 2\r\n\r\n")) - config = py.test.config._reparse([ytxt]) - session = config.initsession() - failed, passed, skipped = countoutcomes(session) - assert failed == 0 - assert passed + skipped == 2 - -def test_doctest_indentation(): - # XXX get rid of the next line: - py.magic.autopath().dirpath('conftest.py').copy(tmpdir.join('conftest.py')) - - txt = tmpdir.join('foo.txt') - txt.write('..\n >>> print "foo\\n bar"\n foo\n bar\n') - config = py.test.config._reparse([txt]) - session = config.initsession() - failed, passed, skipped = countoutcomes(session) - assert failed == 0 - assert skipped + passed == 2 - -def test_js_ignore(): - 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(""" - `blah`_ - - .. _`blah`: javascript:some_function() - """)) - config = py.test.config._reparse([xtxt]) - session = config.initsession() - - failed, passed, skipped = countoutcomes(session) - assert failed == 0 - assert skipped + passed == 3 def test_resolve_linkrole(): from py.__.doc.conftest import get_apigen_relpath Modified: py/trunk/py/green/conftest.py ============================================================================== --- py/trunk/py/green/conftest.py (original) +++ py/trunk/py/green/conftest.py Tue Sep 2 14:24:15 2008 @@ -1,8 +1,8 @@ import py, os class Directory(py.test.collect.Directory): - def listdir(self): + def collect(self): if os.name == 'nt': py.test.skip("Cannot test green layer on windows") else: - return super(Directory, self).listdir() + return super(Directory, self).run() Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Tue Sep 2 14:24:15 2008 @@ -189,7 +189,7 @@ cur = self for name in namelist: if name: - next = cur.join(name) + next = cur.collect_by_name(name) if next is None: existingnames = [x.name for x in self._memocollect()] msg = ("Collector %r does not have name %r " @@ -297,6 +297,12 @@ """ raise NotImplementedError("abstract") + def collect_by_name(self, name): + """ return a child matching the given name, else None. """ + for colitem in self._memocollect(): + if colitem.name == name: + return colitem + def repr_failure(self, excinfo, outerr): """ represent a failure. """ return self._repr_failure_py(excinfo, outerr) @@ -336,12 +342,10 @@ If the return value is None there is no such child. """ warnoldcollect() - for colitem in self._memocollect(): - if colitem.name == name: - return colitem + return self.collect_by_name(name) def multijoin(self, namelist): - """ DEPRECATED: return a list of colitems for the given namelist. """ + """ DEPRECATED: return a list of child items matching the given namelist. """ warnoldcollect() return [self.join(name) for name in namelist] @@ -380,6 +384,8 @@ name, parent = picklestate self.__init__(parent.fspath.join(name), parent=parent) +class File(FSCollector): + """ base class for collecting tests from a file. """ class Directory(FSCollector): def recfilter(self, path): @@ -398,7 +404,6 @@ return l def consider(self, path, usefilters=True): - print "checking", path if path.check(file=1): return self.consider_file(path, usefilters=usefilters) elif path.check(dir=1): @@ -421,14 +426,13 @@ Directory = self._config.getvalue('Directory', path) return Directory(path, parent=self) - # ********************************************************************** - # DEPRECATED METHODS - # ********************************************************************** - - def join(self, name): - """ get a child collector or item without using filters. """ - p = self.fspath.join(name) - return self.consider(p, usefilters=False) + def collect_by_name(self, name): + """ get a child with the given name. """ + res = super(Directory, self).collect_by_name(name) + if res is None: + p = self.fspath.join(name) + res = self.consider(p, usefilters=False) + return res from py.__.test.runner import basic_run_report, forked_run_report class Item(Node): Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Tue Sep 2 14:24:15 2008 @@ -2,7 +2,7 @@ Python related collection nodes. Here is an example of a tree of collectors and test items that this modules provides:: - Module # FSCollector + Module # File Class Instance Function @@ -12,13 +12,12 @@ Generator Function - DoctestFile # FSCollector + DoctestFile # File DoctestFileContent # acts as Item """ import py -from py.__.test.collect import Collector, FSCollector, Item, configproperty -from py.__.test.collect import warnoldcollect +from py.__.test.collect import configproperty, warnoldcollect class PyobjMixin(object): def obj(): @@ -86,7 +85,7 @@ ) -class PyCollectorMixin(PyobjMixin, Collector): +class PyCollectorMixin(PyobjMixin, py.test.collect.Collector): Class = configproperty('Class') Instance = configproperty('Instance') Function = configproperty('Function') @@ -144,9 +143,7 @@ else: return self.Function(name, parent=self) -class Module(FSCollector, PyCollectorMixin): - _stickyfailure = None - +class Module(py.test.collect.File, PyCollectorMixin): def collect(self): if getattr(self.obj, 'disabled', 0): return [] @@ -169,7 +166,7 @@ #print "*" * 20, "revoke assertion", self py.magic.revoke(assertion=1) -class Class(PyCollectorMixin, Collector): +class Class(PyCollectorMixin, py.test.collect.Collector): def collect(self): if getattr(self.obj, 'disabled', 0): @@ -194,7 +191,7 @@ def _getsortvalue(self): return self.getfslineno() -class Instance(PyCollectorMixin, Collector): +class Instance(PyCollectorMixin, py.test.collect.Collector): def _getobj(self): return self.parent.obj() def Function(self): @@ -259,7 +256,7 @@ shortfailurerepr = "F" -class Generator(FunctionMixin, PyCollectorMixin, Collector): +class Generator(FunctionMixin, PyCollectorMixin, py.test.collect.Collector): def collect(self): # test generators are collectors yet participate in # the test-item setup and teardown protocol. @@ -285,7 +282,7 @@ # Test Items # _dummy = object() -class Function(FunctionMixin, Item): +class Function(FunctionMixin, py.test.collect.Item): """ a Function Item is responsible for setting up and executing a Python callable test object. """ @@ -312,7 +309,7 @@ def __ne__(self, other): return not self == other -class DoctestFile(FSCollector): +class DoctestFile(py.test.collect.File): def collect(self): return [DoctestFileContent(self.fspath.basename, parent=self)] @@ -328,7 +325,7 @@ tw.line(line) self.reprlocation.toterminal(tw) -class DoctestFileContent(Item): +class DoctestFileContent(py.test.collect.Item): def repr_failure(self, excinfo, outerr): if excinfo.errisinstance(py.compat.doctest.DocTestFailure): doctestfailure = excinfo.value Modified: py/trunk/py/test/testing/test_collect.py ============================================================================== --- py/trunk/py/test/testing/test_collect.py (original) +++ py/trunk/py/test/testing/test_collect.py Tue Sep 2 14:24:15 2008 @@ -59,11 +59,31 @@ def test_listnames_and__getitembynames(self): modcol = self.getmodulecol("pass") names = modcol.listnames() - dircol = py.test.collect.Directory(modcol._config.topdir, config=modcol._config) + dircol = modcol._config.getfsnode(modcol._config.topdir) x = dircol._getitembynames(names) assert modcol.name == x.name assert modcol.name == x.name + def test_listnames_getitembynames_custom(self): + hello = self._makefile(".xxx", hello="world") + self.makepyfile(conftest=""" + import py + class CustomFile(py.test.collect.File): + pass + class MyDirectory(py.test.collect.Directory): + def collect(self): + return [CustomFile(self.fspath.join("hello.xxx"), parent=self)] + Directory = MyDirectory + """) + config = self.parseconfig(hello) + node = config.getfsnode(hello) + assert isinstance(node, py.test.collect.File) + assert node.name == "hello.xxx" + names = node.listnames()[1:] + dircol = config.getfsnode(config.topdir) + node = dircol._getitembynames(names) + assert isinstance(node, py.test.collect.File) + def test_found_certain_testfiles(self): p1 = self.makepyfile(test_found = "pass", found_test="pass") col = py.test.collect.Directory(p1.dirpath(), config=dummyconfig) @@ -156,9 +176,9 @@ def test_pass(): pass def test_fail(): assert 0 """) - fn1 = modcol.join("test_pass") + fn1 = modcol.collect_by_name("test_pass") assert isinstance(fn1, py.test.collect.Function) - fn2 = modcol.join("test_pass") + fn2 = modcol.collect_by_name("test_pass") assert isinstance(fn2, py.test.collect.Function) assert fn1 == fn2 @@ -166,7 +186,7 @@ assert cmp(fn1, fn2) == 0 assert hash(fn1) == hash(fn2) - fn3 = modcol.join("test_fail") + fn3 = modcol.collect_by_name("test_fail") assert isinstance(fn3, py.test.collect.Function) assert not (fn1 == fn3) assert fn1 != fn3 @@ -274,7 +294,6 @@ assert item.name == "hello.xxx" assert item.__class__.__name__ == "CustomItem" - def test_module_file_not_found(): fn = tmpdir.join('nada','no') col = py.test.collect.Module(fn, config=dummyconfig) @@ -513,7 +532,7 @@ class TestClass: def test_hello(self): pass """) - classcol = modcol.join("TestClass") + classcol = modcol.collect_by_name("TestClass") info = classcol.repr_metainfo() assert info.fspath == modcol.fspath assert info.lineno == 1 @@ -527,7 +546,7 @@ assert x yield check, 3 """) - gencol = modcol.join("test_gen") + gencol = modcol.collect_by_name("test_gen") info = gencol.repr_metainfo() assert info.fspath == modcol.fspath assert info.lineno == 1 @@ -595,7 +614,7 @@ def test_pickle_func(self): modcol1 = self.getmodulecol("def test_one(): pass") self.unifyconfig(modcol1._config) - item = modcol1.join("test_one") + item = modcol1.collect_by_name("test_one") item2a = self.p1_to_p2(item) assert item is not item2a # of course assert item2a.name == item.name From hpk at codespeak.net Tue Sep 2 14:26:23 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 2 Sep 2008 14:26:23 +0200 (CEST) Subject: [py-svn] r57755 - py/trunk Message-ID: <20080902122623.11033169DB3@codespeak.net> Author: hpk Date: Tue Sep 2 14:26:22 2008 New Revision: 57755 Modified: py/trunk/MANIFEST py/trunk/README.txt py/trunk/setup.py Log: regen setup, readme for trunk. Modified: py/trunk/MANIFEST ============================================================================== --- py/trunk/MANIFEST (original) +++ py/trunk/MANIFEST Tue Sep 2 14:26:22 2008 @@ -60,7 +60,6 @@ py/apigen/tracer/testing/test_model.py py/apigen/tracer/testing/test_package.py py/apigen/tracer/tracer.py -py/bin/_docgen.py py/bin/_findpy.py py/bin/_genscripts.py py/bin/gendoc.py @@ -166,11 +165,6 @@ py/doc/example/pytest/test_setup_flow_example.py py/doc/execnet.txt py/doc/future.txt -py/doc/future/code_template.txt -py/doc/future/planning.txt -py/doc/future/planning2.txt -py/doc/future/pylib_pypy.txt -py/doc/future/rsession_todo.txt py/doc/greenlet.txt py/doc/impl-test.txt py/doc/index.txt @@ -408,6 +402,7 @@ py/test/testing/test_compat.py py/test/testing/test_config.py py/test/testing/test_conftesthandle.py +py/test/testing/test_deprecated_api.py py/test/testing/test_doctest.py py/test/testing/test_event.py py/test/testing/test_outcome.py Modified: py/trunk/README.txt ============================================================================== --- py/trunk/README.txt (original) +++ py/trunk/README.txt Tue Sep 2 14:26:22 2008 @@ -1,7 +1,7 @@ The py lib is a Python development support library featuring the following tools and modules: -* py.test: popular python testing tool +* py.test: automated testing tool * py.execnet: ad-hoc distributed execution * py.magic.greenlet: micro-threads * py.path: uniform local and svn path objects Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Tue Sep 2 14:26:22 2008 @@ -1,7 +1,7 @@ """ setup file for 'py' package based on: - https://codespeak.net/svn/py/trunk, revision=57550 + svn+ssh://codespeak.net/svn/py/trunk, revision=57745 autogenerated by gensetup.py """ @@ -191,11 +191,6 @@ 'doc/example/pytest/test_setup_flow_example.py', 'doc/execnet.txt', 'doc/future.txt', - 'doc/future/code_template.txt', - 'doc/future/planning.txt', - 'doc/future/planning2.txt', - 'doc/future/pylib_pypy.txt', - 'doc/future/rsession_todo.txt', 'doc/greenlet.txt', 'doc/impl-test.txt', 'doc/index.txt', From hpk at codespeak.net Tue Sep 2 14:42:37 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 2 Sep 2008 14:42:37 +0200 (CEST) Subject: [py-svn] r57756 - in py/trunk: contrib contrib/pygreen contrib/pygreen/pipe contrib/pygreen/test py/green Message-ID: <20080902124237.A07D8168581@codespeak.net> Author: hpk Date: Tue Sep 2 14:42:35 2008 New Revision: 57756 Added: py/trunk/contrib/ py/trunk/contrib/pygreen/ - copied from r57745, py/trunk/py/green/ py/trunk/contrib/pygreen/conftest.py - copied, changed from r57754, py/trunk/py/green/conftest.py py/trunk/contrib/readme.txt (contents, props changed) Removed: py/trunk/py/green/ Modified: py/trunk/contrib/pygreen/greenexecnet.py py/trunk/contrib/pygreen/pipe/common.py py/trunk/contrib/pygreen/pipe/fd.py py/trunk/contrib/pygreen/pipe/gsocket.py py/trunk/contrib/pygreen/pipe/mp.py py/trunk/contrib/pygreen/test/test_greenexecnet.py py/trunk/contrib/pygreen/test/test_greensock2.py py/trunk/contrib/pygreen/test/test_pipelayer.py Log: * create new contrib directory * move py/green to contrib/pygreen, fix tests and code to pass Copied: py/trunk/contrib/pygreen/conftest.py (from r57754, py/trunk/py/green/conftest.py) ============================================================================== --- py/trunk/py/green/conftest.py (original) +++ py/trunk/contrib/pygreen/conftest.py Tue Sep 2 14:42:35 2008 @@ -5,4 +5,4 @@ if os.name == 'nt': py.test.skip("Cannot test green layer on windows") else: - return super(Directory, self).run() + return super(Directory, self).collect() Modified: py/trunk/contrib/pygreen/greenexecnet.py ============================================================================== --- py/trunk/py/green/greenexecnet.py (original) +++ py/trunk/contrib/pygreen/greenexecnet.py Tue Sep 2 14:42:35 2008 @@ -13,8 +13,8 @@ """ import sys, os, py, inspect -from py.__.green import greensock2 -from py.__.green.msgstruct import message, decodemessage +from pygreen import greensock2 +from pygreen.msgstruct import message, decodemessage MSG_REMOTE_EXEC = 'r' MSG_OBJECT = 'o' @@ -163,7 +163,7 @@ action = "exec input()" def __init__(self, cmdline): - from py.__.green.pipe.fd import FDInput, FDOutput + from pygreen.pipe.fd import FDInput, FDOutput child_in, child_out = os.popen2(cmdline, 't', 0) fdin = FDInput(child_out.fileno(), child_out.close) fdout = FDOutput(child_in.fileno(), child_in.close) @@ -173,14 +173,14 @@ def get_bootstrap_code(): # XXX assumes that the py lib is installed on the remote side src = [] - src.append('from py.__.green import greenexecnet') + src.append('from pygreen import greenexecnet') src.append('greenexecnet.PopenCmdGateway.run_server()') src.append('') return '%r\n' % ('\n'.join(src),) get_bootstrap_code = staticmethod(get_bootstrap_code) def run_server(): - from py.__.green.pipe.fd import FDInput, FDOutput + from pygreen.pipe.fd import FDInput, FDOutput gw = Gateway(input = FDInput(os.dup(0)), output = FDOutput(os.dup(1)), is_remote = True) Modified: py/trunk/contrib/pygreen/pipe/common.py ============================================================================== --- py/trunk/py/green/pipe/common.py (original) +++ py/trunk/contrib/pygreen/pipe/common.py Tue Sep 2 14:42:35 2008 @@ -1,4 +1,4 @@ -from py.__.green import greensock2 +from pygreen import greensock2 VERBOSE = True Modified: py/trunk/contrib/pygreen/pipe/fd.py ============================================================================== --- py/trunk/py/green/pipe/fd.py (original) +++ py/trunk/contrib/pygreen/pipe/fd.py Tue Sep 2 14:42:35 2008 @@ -1,5 +1,5 @@ import os -from py.__.green import greensock2 +from pygreen import greensock2 class FDInput(object): Modified: py/trunk/contrib/pygreen/pipe/gsocket.py ============================================================================== --- py/trunk/py/green/pipe/gsocket.py (original) +++ py/trunk/contrib/pygreen/pipe/gsocket.py Tue Sep 2 14:42:35 2008 @@ -1,4 +1,4 @@ -from py.__.green import greensock2 +from pygreen import greensock2 import socket, errno, os error = socket.error Modified: py/trunk/contrib/pygreen/pipe/mp.py ============================================================================== --- py/trunk/py/green/pipe/mp.py (original) +++ py/trunk/contrib/pygreen/pipe/mp.py Tue Sep 2 14:42:35 2008 @@ -1,4 +1,4 @@ -from py.__.green.pipe.common import BufferedInput +from pygreen.pipe.common import BufferedInput class MeetingPointInput(BufferedInput): Modified: py/trunk/contrib/pygreen/test/test_greenexecnet.py ============================================================================== --- py/trunk/py/green/test/test_greenexecnet.py (original) +++ py/trunk/contrib/pygreen/test/test_greenexecnet.py Tue Sep 2 14:42:35 2008 @@ -1,7 +1,12 @@ import py -from py.__.green.greenexecnet import * +from pygreen.greenexecnet import * +import pygreen -py.test.skip("Does not work with globally installed pylib") +def setup_module(mod): + os.environ["PYTHONPATH"] = "%s:%s" %( + py.path.local(pygreen.__file__).dirpath().dirpath(), os.environ['PYTHONPATH']) + #py.test.skip("need to fix PYTHONPATH/sys.path handling for sub processes so " + # "that they find the pygreen package.") def test_simple(): gw = PopenGateway() Modified: py/trunk/contrib/pygreen/test/test_greensock2.py ============================================================================== --- py/trunk/py/green/test/test_greensock2.py (original) +++ py/trunk/contrib/pygreen/test/test_greensock2.py Tue Sep 2 14:42:35 2008 @@ -1,6 +1,6 @@ import py from socket import * -from py.__.green.greensock2 import * +from pygreen.greensock2 import * def test_meetingpoint(): giv1, acc1 = meetingpoint() Modified: py/trunk/contrib/pygreen/test/test_pipelayer.py ============================================================================== --- py/trunk/py/green/test/test_pipelayer.py (original) +++ py/trunk/contrib/pygreen/test/test_pipelayer.py Tue Sep 2 14:42:35 2008 @@ -1,5 +1,5 @@ import os, random -from py.__.green.pipelayer import PipeLayer, pipe_over_udp, PipeOverUdp +from pygreen.pipelayer import PipeLayer, pipe_over_udp, PipeOverUdp def test_simple(): data1 = os.urandom(1000) Added: py/trunk/contrib/readme.txt ============================================================================== --- (empty file) +++ py/trunk/contrib/readme.txt Tue Sep 2 14:42:35 2008 @@ -0,0 +1,2 @@ + +pygreen: experimental IO and execnet operations through greenlets From hpk at codespeak.net Tue Sep 2 14:45:53 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 2 Sep 2008 14:45:53 +0200 (CEST) Subject: [py-svn] r57757 - py/trunk/py/code/testing Message-ID: <20080902124553.4A50B169DFB@codespeak.net> Author: hpk Date: Tue Sep 2 14:45:50 2008 New Revision: 57757 Modified: py/trunk/py/code/testing/test_excinfo.py Log: adapt output checking test for slightly different output Modified: py/trunk/py/code/testing/test_excinfo.py ============================================================================== --- py/trunk/py/code/testing/test_excinfo.py (original) +++ py/trunk/py/code/testing/test_excinfo.py Tue Sep 2 14:45:50 2008 @@ -215,7 +215,7 @@ except ValueError: excinfo = py.code.ExceptionInfo() s = str(excinfo.traceback[-1]) - assert s == " File '':1 in \n ???\n" + assert s == " File '':1 in ?\n ???\n" def test_entrysource_Queue_example(): import Queue From hpk at codespeak.net Tue Sep 2 15:22:11 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 2 Sep 2008 15:22:11 +0200 (CEST) Subject: [py-svn] r57758 - py/trunk/py/test/dsession/testing Message-ID: <20080902132211.9E5F9169DFB@codespeak.net> Author: hpk Date: Tue Sep 2 15:22:10 2008 New Revision: 57758 Removed: py/trunk/py/test/dsession/testing/basetest.py Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py py/trunk/py/test/dsession/testing/test_hostmanage.py py/trunk/py/test/dsession/testing/test_masterslave.py Log: unify test support, remove basetest.py Deleted: /py/trunk/py/test/dsession/testing/basetest.py ============================================================================== --- /py/trunk/py/test/dsession/testing/basetest.py Tue Sep 2 15:22:10 2008 +++ (empty file) @@ -1,32 +0,0 @@ - -""" Support module for running tests -""" - -import py -from py.__.test.testing.setupdata import getexamplefile - -class DirSetup(object): - def setup_method(self, method): - name = "%s.%s" %(self.__class__.__name__, method.func_name) - self.tmpdir = py.test.ensuretemp(name) - self.source = self.tmpdir.ensure("source", dir=1) - self.dest = self.tmpdir.join("dest") - - -class BasicRsessionTest(object): - def setup_class(cls): - path = getexamplefile("test_funcexamples.py") - cls.config = py.test.config._reparse([path.dirpath()]) - cls.modulecol = cls.config.getfsnode(path) - - def setup_method(self, method): - self.session = self.config.initsession() - - def getfunc(self, name): - funcname = "test_func" + name - col = self.modulecol.join(funcname) - assert col is not None, funcname - return col - - def getdocexample(self): - return getexamplefile("docexample.txt") Modified: py/trunk/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/trunk/py/test/dsession/testing/test_functional_dsession.py Tue Sep 2 15:22:10 2008 @@ -6,7 +6,6 @@ from py.__.test import event from py.__.test.dsession.dsession import DSession from py.__.test.dsession.hostmanage import HostManager, Host -from basetest import BasicRsessionTest, DirSetup from py.__.test.testing import suptest import os Modified: py/trunk/py/test/dsession/testing/test_hostmanage.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_hostmanage.py (original) +++ py/trunk/py/test/dsession/testing/test_hostmanage.py Tue Sep 2 15:22:10 2008 @@ -3,12 +3,18 @@ """ import py -from basetest import DirSetup +from py.__.test.testing import suptest from py.__.test.dsession.hostmanage import HostRSync, Host, HostManager, gethosts from py.__.test.dsession.hostmanage import sethomedir, gethomedir, getpath_relto_home from py.__.test import event -class TestHost(DirSetup): +class TmpWithSourceDest(suptest.FileCreation): + def setup_method(self, method): + super(TmpWithSourceDest, self).setup_method(method) + self.source = self.tmpdir.mkdir("source") + self.dest = self.tmpdir.mkdir("dest") + +class TestHost(suptest.FileCreation): def _gethostinfo(self, relpath=""): exampledir = self.tmpdir.join("gethostinfo") if relpath: @@ -119,7 +125,7 @@ res = channel.receive() assert res == host.gw_remotepath -class TestSyncing(DirSetup): +class TestSyncing(TmpWithSourceDest): def _gethostinfo(self): hostinfo = Host("localhost:%s" % self.dest) return hostinfo @@ -181,7 +187,7 @@ res2 = rsync.add_target_host(h2) assert not res2 -class TestHostManager(DirSetup): +class TestHostManager(TmpWithSourceDest): def gethostmanager(self, dist_hosts, dist_rsync_roots=None): l = ["dist_hosts = %r" % dist_hosts] if dist_rsync_roots: Modified: py/trunk/py/test/dsession/testing/test_masterslave.py ============================================================================== --- py/trunk/py/test/dsession/testing/test_masterslave.py (original) +++ py/trunk/py/test/dsession/testing/test_masterslave.py Tue Sep 2 15:22:10 2008 @@ -1,11 +1,11 @@ import py from py.__.test.dsession.masterslave import MasterNode -from py.__.test.dsession.hostmanage import Host -from basetest import BasicRsessionTest +from py.__.test.dsession.hostmanage import Host from py.__.test import event +from py.__.test.testing import suptest -class TestMasterSlaveConnection(BasicRsessionTest): +class TestMasterSlaveConnection(suptest.InlineCollection): def getevent(self, eventtype=event.ItemTestReport, timeout=2.0): events = [] while 1: @@ -23,15 +23,30 @@ def setup_method(self, method): super(TestMasterSlaveConnection, self).setup_method(method) + self.makepyfile(__init__="") + self.config = self.parseconfig(self.tmpdir) self.queue = py.std.Queue.Queue() self.host = Host("localhost") self.host.initgateway() - self.node = MasterNode(self.host, self.session.config, - self.queue.put) + self.node = MasterNode(self.host, self.config, self.queue.put) assert not self.node.channel.isclosed() + + def getitem(self, source): + kw = {"test_" + self.tmpdir.basename: py.code.Source(source).strip()} + path = self.makepyfile(**kw) + fscol = self.config.getfsnode(path) + return fscol.collect_by_name("test_func") + + def getitems(self, source): + kw = {"test_" + self.tmpdir.basename: py.code.Source(source).strip()} + path = self.makepyfile(**kw) + fscol = self.config.getfsnode(path) + return fscol.collect() def teardown_method(self, method): print "at teardown:", self.node.channel + #if not self.node.channel.isclosed(): + # self.node.shutdown() self.host.gw.exit() def test_crash_invalid_item(self): @@ -43,7 +58,11 @@ def test_crash_killed(self): if not hasattr(py.std.os, 'kill'): py.test.skip("no os.kill") - item = self.getfunc("kill15") + item = self.getitem(""" + def test_func(): + import os + os.kill(os.getpid(), 15) + """) self.node.send(item) ev = self.getevent(event.HostDown) assert ev.host == self.host @@ -59,15 +78,15 @@ "self.getevent(event.HostDown, timeout=0.01)") def test_send_on_closed_channel(self): - item = self.getfunc("passed") + item = self.getitem("def test_func(): pass") self.node.channel.close() py.test.raises(IOError, "self.node.send(item)") #ev = self.getevent(event.InternalException) #assert ev.excinfo.errisinstance(IOError) def test_send_one(self): - item = self.getfunc("passed") - self.node.send(self.getfunc("passed")) + item = self.getitem("def test_func(): pass") + self.node.send(item) ev = self.getevent() assert ev.passed assert ev.colitem == item @@ -75,16 +94,22 @@ #assert event.item is not item def test_send_some(self): + items = self.getitems(""" + def test_pass(): + pass + def test_fail(): + assert 0 + def test_skip(): + import py + py.test.skip("x") + """) + for item in items: + self.node.send(item) for outcome in "passed failed skipped".split(): - self.node.send(self.getfunc(outcome)) ev = self.getevent() assert getattr(ev, outcome) - def test_send_list(self): - l = [] - for outcome in "passed failed skipped".split(): - l.append(self.getfunc(outcome)) - self.node.sendlist(l) + self.node.sendlist(items) for outcome in "passed failed skipped".split(): ev = self.getevent() assert getattr(ev, outcome) From hpk at codespeak.net Tue Sep 2 16:31:43 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 2 Sep 2008 16:31:43 +0200 (CEST) Subject: [py-svn] r57762 - in py/trunk/py/test: . report report/testing testing Message-ID: <20080902143143.8C3C6169DB5@codespeak.net> Author: hpk Date: Tue Sep 2 16:31:42 2008 New Revision: 57762 Removed: py/trunk/py/test/testing/setupdata.py py/trunk/py/test/testing/test_repevent.py Modified: py/trunk/py/test/pycollect.py py/trunk/py/test/report/base.py py/trunk/py/test/report/terminal.py py/trunk/py/test/report/testing/test_basereporter.py py/trunk/py/test/testing/suptest.py py/trunk/py/test/testing/test_collect.py py/trunk/py/test/testing/test_config.py py/trunk/py/test/testing/test_event.py py/trunk/py/test/testing/test_session.py Log: * settle on using suptest for supporting test runs * fix a bug in getmodpath * remove redundant functions/files Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Tue Sep 2 16:31:42 2008 @@ -43,9 +43,13 @@ for node in chain: if isinstance(node, Instance): continue - if stopatmodule and isinstance(node, Module): - break - parts.append(node.name) + name = node.name + if isinstance(node, Module): + if stopatmodule: + break + assert name.endswith(".py") + name = name[:-3] + parts.append(name) parts.reverse() s = ".".join(parts) return s.replace(".[", "[") Modified: py/trunk/py/test/report/base.py ============================================================================== --- py/trunk/py/test/report/base.py (original) +++ py/trunk/py/test/report/base.py Tue Sep 2 16:31:42 2008 @@ -64,22 +64,6 @@ l.append((len(events),) + key) return l - -def getmodpath(pycolitem): - """ return dotted module path for the given colitem. """ - colitems = pycolitem.listchain() - while colitems: - colitem = colitems.pop(0) - if isinstance(colitem, colitem.Module): - parts = [colitem.obj.__name__] - for colitem in colitems: - if colitem.name[0] in '([': - parts[-1] += colitem.name - else: - parts.append(colitem.name) - return ".".join(parts) - return colitem.name - def repr_pythonversion(v=None): if v is None: v = sys.version_info Modified: py/trunk/py/test/report/terminal.py ============================================================================== --- py/trunk/py/test/report/terminal.py (original) +++ py/trunk/py/test/report/terminal.py Tue Sep 2 16:31:42 2008 @@ -2,7 +2,7 @@ import sys from py.__.test import event from py.__.test.report.base import BaseReporter -from py.__.test.report.base import getrelpath, repr_pythonversion, getmodpath +from py.__.test.report.base import getrelpath, repr_pythonversion class TerminalReporter(BaseReporter): def __init__(self, config, file=None, bus=None): Modified: py/trunk/py/test/report/testing/test_basereporter.py ============================================================================== --- py/trunk/py/test/report/testing/test_basereporter.py (original) +++ py/trunk/py/test/report/testing/test_basereporter.py Tue Sep 2 16:31:42 2008 @@ -3,8 +3,7 @@ from py.__.test.event import EventBus from py.__.test import event from py.__.test.runner import OutcomeRepr -from py.__.test.report.base import getrelpath, getmodpath, repr_pythonversion -from py.__.test.testing import setupdata +from py.__.test.report.base import getrelpath, repr_pythonversion import sys class TestBaseReporter: @@ -73,29 +72,6 @@ assert lineno == longrepr.lineno assert reason == longrepr.message -def test_getmodpath_cases(): - tmpdir = py.test.ensuretemp("test_getmodpath_cases") - pkgdir = tmpdir.join("test_getmodpath") - pkgdir.ensure("__init__.py") - nopkgdir = tmpdir.ensure("nopkg", dir=1) - def checkpkg(names, expected): - fcol = setupdata.getexamplecollector(names, tmpdir=pkgdir) - assert getmodpath(fcol) == pkgdir.basename + "." + expected - def checknopkg(names, expected): - fcol = setupdata.getexamplecollector(names, tmpdir=nopkgdir) - assert getmodpath(fcol) == expected - - for names in ( - 'test_mod.py test_f1 test_mod.test_f1', - 'test_mod.py TestA () test_m1 test_mod.TestA().test_m1', - 'test_mod.py test_g1 test_mod.test_g1', - 'test_mod.py test_g1 [0] test_mod.test_g1[0]', - ): - names = names.split() - expected = names.pop() - yield checkpkg, names, expected - yield checknopkg, names, expected - def test_repr_python_version(): py.magic.patch(sys, 'version_info', (2, 5, 1, 'final', 0)) try: Deleted: /py/trunk/py/test/testing/setupdata.py ============================================================================== --- /py/trunk/py/test/testing/setupdata.py Tue Sep 2 16:31:42 2008 +++ (empty file) @@ -1,173 +0,0 @@ -import py - -#def setup_module(mod): -# mod.datadir = setupdatadir() -# mod.tmpdir = py.test.ensuretemp(mod.__name__) - -#def setupdatadir(): -# datadir = py.test.ensuretemp("datadir") -# for name in namecontent: -# getexamplefile(name) -# return datadir - -def getexamplefile(basename, tmpdir=None): - if tmpdir is None: - tmpdir = py.test.ensuretemp("example") - tmpdir.ensure("__init__.py") - path = tmpdir.join(basename) - if not path.check(): - path.write(py.code.Source(namecontent[basename])) - print "creating testfile", path - return path - -def getexamplecollector(names, tmpdir=None): - fn = getexamplefile(names[0], tmpdir=tmpdir) - config = py.test.config._reparse([fn.dirpath()]) - col = config.getfsnode(fn) - return col._getitembynames(names[1:]) - -namecontent = { - 'syntax_error.py': "this is really not python\n", - - 'disabled_module.py': ''' - disabled = True - - def setup_module(mod): - raise ValueError - - class TestClassOne: - def test_func(self): - raise ValueError - - class TestClassTwo: - def setup_class(cls): - raise ValueError - def test_func(self): - raise ValueError - ''', - - 'brokenrepr.py': ''' - import py - - class BrokenRepr1: - """A broken class with lots of broken methods. Let's try to make the test framework - immune to these.""" - foo=0 - def __repr__(self): - raise Exception("Ha Ha fooled you, I'm a broken repr().") - - class BrokenRepr2: - """A broken class with lots of broken methods. Let's try to make the test framework - immune to these.""" - foo=0 - def __repr__(self): - raise "Ha Ha fooled you, I'm a broken repr()." - - - class TestBrokenClass: - - def test_explicit_bad_repr(self): - t = BrokenRepr1() - py.test.raises(Exception, 'repr(t)') - - def test_implicit_bad_repr1(self): - t = BrokenRepr1() - assert t.foo == 1 - - def test_implicit_bad_repr2(self): - t = BrokenRepr2() - assert t.foo == 1 - ''', - - 'failingimport.py': "import gruetzelmuetzel\n", - - 'test_mod.py': """ - class TestA: - def test_m1(self): - pass - def test_f1(): - pass - def test_g1(): - yield lambda x: None, 42 - """, - - 'file_test.py': """ - def test_one(): - assert 42 == 43 - - class TestClass(object): - def test_method_one(self): - assert 42 == 43 - - """, - - 'test_threepass.py': """ - def test_one(): - assert 1 - - def test_two(): - assert 1 - - def test_three(): - assert 1 - """, - - 'testspecial_importerror.py': """ - - import asdasd - - """, - - 'disabled.py': """ - class TestDisabled: - disabled = True - def test_method(self): - pass - """, - - 'test_funcexamples.py': """ - import py - import time - def test_funcpassed(): - pass - - def test_funcfailed(): - raise AssertionError("hello world") - - def test_funcskipped(): - py.test.skip("skipped") - - def test_funcprint(): - print "samfing" - - def test_funcprinterr(): - print >>py.std.sys.stderr, "samfing" - - def test_funcprintfail(): - print "samfing elz" - asddsa - - def test_funcexplicitfail(): - py.test.fail("3") - - def test_funcraisesfails(): - py.test.raises(ValueError, lambda: 123) - - def test_funcoptioncustom(): - assert py.test.config.getvalue("custom") - - def test_funchang(): - import time - time.sleep(1000) - - def test_funckill15(): - import os - os.kill(os.getpid(), 15) - """, - - 'docexample.txt': """ - Aha!!!!!! - ========= - """, - -} Modified: py/trunk/py/test/testing/suptest.py ============================================================================== --- py/trunk/py/test/testing/suptest.py (original) +++ py/trunk/py/test/testing/suptest.py Tue Sep 2 16:31:42 2008 @@ -6,8 +6,6 @@ for analyzing events an EventSorter instance is returned for both of: * events_from_cmdline(args): inprocess-run of cmdline invocation * events_from_session(session): inprocess-run of given session - * events_run_example(examplename, *args):in-process-run of - given example test file eventappender(config): for getting all events in a list: """ @@ -49,11 +47,6 @@ session.main(getcolitems(session.config)) return sorter -def events_run_example(examplename, *args): - from setupdata import getexamplefile - p = getexamplefile(examplename) - return events_from_cmdline([p] + list(args)) - class EventSorter(object): def __init__(self, config, session=None): self.config = config @@ -217,6 +210,13 @@ runner = self.getrunner() return runner(item, **runnerargs) +class InlineSession(InlineCollection): + def parse_and_run(self, *args): + config = self.parseconfig(*args) + session = config.initsession() + sorter = EventSorter(config, session) + session.main() + return sorter def popvalue(stringio): value = stringio.getvalue().rstrip() Modified: py/trunk/py/test/testing/test_collect.py ============================================================================== --- py/trunk/py/test/testing/test_collect.py (original) +++ py/trunk/py/test/testing/test_collect.py Tue Sep 2 16:31:42 2008 @@ -1,7 +1,7 @@ from __future__ import generators import py from py.__.test import event, outcome -import setupdata, suptest +from py.__.test.testing import suptest from py.__.test.conftesthandle import Conftest from py.__.test.collect import SetupState from test_config import getcolitems @@ -376,7 +376,6 @@ assert f1 == f1_b assert not f1 != f1_b - class Testgenitems: def setup_class(cls): cls.classtemp = py.test.ensuretemp(cls.__name__) @@ -462,6 +461,15 @@ assert items[1].name == 'test_method_one' assert items[2].name == 'test_method_one' + # let's also test getmodpath here + assert items[0].getmodpath() == "test_one" + assert items[1].getmodpath() == "TestX.test_method_one" + assert items[2].getmodpath() == "TestY.test_method_one" + + s = items[0].getmodpath(stopatmodule=False) + assert s == "test_example_items1.test_example.test_one" + print s + def test_collect_doctest_files_with_test_prefix(self): self.tmp.ensure("whatever.txt") checkfile = self.tmp.ensure("test_something.txt") Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Tue Sep 2 16:31:42 2008 @@ -2,7 +2,7 @@ import py from py.__.test.config import gettopdir -import suptest, setupdata +from py.__.test.testing import suptest from py.__.test import event def getcolitems(config): @@ -38,6 +38,8 @@ def test_config_cmdline_options_only_lowercase(): o = py.test.ensuretemp('test_config_cmdline_options_only_lowercase') + o = o.mkdir("onemore") # neccessary because collection of o.dirpath() + # could see our conftest.py o.ensure("conftest.py").write(py.code.Source(""" import py Option = py.test.config.Option @@ -159,12 +161,16 @@ assert config.option.gdest == 11 assert option.gdest == 11 -class TestSessionAndOptions: - def setup_class(cls): - cls.tmproot = py.test.ensuretemp(cls.__name__) - - def setup_method(self, method): - self.tmpdir = self.tmproot.ensure(method.__name__, dir=1) +class TestSessionAndOptions(suptest.FileCreation): + def exampletestfile(self): + return self.makepyfile(file_test=""" + def test_one(): + assert 42 == 43 + + class TestClass(object): + def test_method_one(self): + assert 42 == 43 + """) def test_session_eventlog(self): eventlog = self.tmpdir.join("test_session_eventlog") @@ -312,7 +318,7 @@ def test_conflict_options(self): def check_conflict_option(opts): print "testing if options conflict:", " ".join(opts) - path = setupdata.getexamplefile("file_test.py") + path = self.exampletestfile() config = py.test.config._reparse(opts + [path]) py.test.raises((ValueError, SystemExit), """ config.initsession() @@ -329,7 +335,7 @@ def test_implied_options(self): def check_implied_option(opts, expr): - path = setupdata.getexamplefile("file_test.py") + path = self.exampletestfile() config = py.test.config._reparse(opts + [path]) session = config.initsession() assert eval(expr, session.config.option.__dict__) @@ -348,19 +354,19 @@ passed, skipped, failed = sorter.countoutcomes() assert failed == 2 assert skipped == passed == 0 - path = setupdata.getexamplefile("file_test.py") + path = self.exampletestfile() for opts in ([], ['-l'], ['-s'], ['--tb=no'], ['--tb=short'], ['--tb=long'], ['--fulltrace'], ['--nomagic'], ['--traceconfig'], ['-v'], ['-v', '-v']): yield runfiletest, opts + [path] def test_is_not_boxed_by_default(self): - path = setupdata.getexamplefile("file_test.py") + path = self.exampletestfile() config = py.test.config._reparse([path]) assert not config.option.boxed -class TestConfigColitems: +class TestConfigColitems(suptest.FileCreation): def setup_class(cls): cls.tmproot = py.test.ensuretemp(cls.__name__) Modified: py/trunk/py/test/testing/test_event.py ============================================================================== --- py/trunk/py/test/testing/test_event.py (original) +++ py/trunk/py/test/testing/test_event.py Tue Sep 2 16:31:42 2008 @@ -36,9 +36,12 @@ assert l == [1] -class TestItemTestReport(object): +class TestItemTestReport(suptest.InlineCollection): def test_toterminal(self): - sorter = suptest.events_run_example("file_test.py") + sorter = suptest.events_from_runsource(""" + def test_one(): + assert 42 == 43 + """) reports = sorter.get(event.ItemTestReport) ev = reports[0] assert ev.failed @@ -54,5 +57,5 @@ ##assert ev.repr_run.find("AssertionError") != -1 filepath = ev.colitem.fspath #filepath , modpath = ev.itemrepr_path - assert str(filepath).endswith("file_test.py") + assert str(filepath).endswith(".py") #assert modpath.endswith("file_test.test_one") Deleted: /py/trunk/py/test/testing/test_repevent.py ============================================================================== --- /py/trunk/py/test/testing/test_repevent.py Tue Sep 2 16:31:42 2008 +++ (empty file) @@ -1,30 +0,0 @@ - -from py.__.test import event -import setupdata, suptest -from py.__.code.testing.test_excinfo import TWMock - - - -class TestItemTestReport(object): - - def test_toterminal(self): - sorter = suptest.events_run_example("file_test.py") - reports = sorter.get(event.ItemTestReport) - ev = reports[0] - assert ev.failed - twmock = TWMock() - ev.toterminal(twmock) - assert twmock.lines - twmock = TWMock() - ev.outcome.longrepr = "hello" - ev.toterminal(twmock) - assert twmock.lines[0] == "hello" - assert not twmock.lines[1:] - - ##assert ev.repr_run.find("AssertionError") != -1 - filepath = ev.colitem.fspath - #filepath , modpath = ev.itemrepr_path - assert str(filepath).endswith("file_test.py") - #assert modpath.endswith("file_test.test_one") - - Modified: py/trunk/py/test/testing/test_session.py ============================================================================== --- py/trunk/py/test/testing/test_session.py (original) +++ py/trunk/py/test/testing/test_session.py Tue Sep 2 16:31:42 2008 @@ -1,15 +1,20 @@ import py from py.__.test import event -import suptest, setupdata +from py.__.test.testing import suptest def setup_module(mod): mod.tmpdir = py.test.ensuretemp(mod.__name__) -class TestKeywordSelection: +class TestKeywordSelection(suptest.InlineSession): def test_select_simple(self): + file_test = self.makepyfile(file_test=""" + def test_one(): assert 0 + class TestClass(object): + def test_method_one(self): + assert 42 == 43 + """) def check(keyword, name): - sorter = suptest.events_run_example("file_test.py", - '-s', '-k', keyword) + sorter = self.parse_and_run("-s", "-k", keyword, file_test) passed, skipped, failed = sorter.listoutcomes() assert len(failed) == 1 assert failed[0].colitem.name == name @@ -20,7 +25,7 @@ yield check, 'TestClass.test', 'test_method_one' def test_select_extra_keywords(self): - o = tmpdir.ensure('test_select_extra_keywords', dir=1) + o = self.tmpdir tfile = o.join('test_select.py').write(py.code.Source(""" def test_1(): pass @@ -46,8 +51,12 @@ assert dlist[0].items[0].name == 'test_1' def test_select_starton(self): - sorter = suptest.events_run_example("test_threepass.py", - '-k', "test_two:") + threepass = self.makepyfile(test_threepass=""" + def test_one(): assert 1 + def test_two(): assert 1 + def test_three(): assert 1 + """) + sorter = self.parse_and_run("-k", "test_two:", threepass) passed, skipped, failed = sorter.listoutcomes() assert len(passed) == 2 assert not failed From hpk at codespeak.net Tue Sep 2 21:08:48 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 2 Sep 2008 21:08:48 +0200 (CEST) Subject: [py-svn] r57771 - py/trunk/contrib/py_unittest Message-ID: <20080902190848.549C2169EC1@codespeak.net> Author: hpk Date: Tue Sep 2 21:08:45 2008 New Revision: 57771 Added: py/trunk/contrib/py_unittest/ py/trunk/contrib/py_unittest/conftest.py (contents, props changed) py/trunk/contrib/py_unittest/readme.txt (contents, props changed) py/trunk/contrib/py_unittest/test_conftest.py (contents, props changed) Log: adding Guido's py_unittest unittest TestCase collector, adapting to py/trunk API and simplifing it a bit. Added: py/trunk/contrib/py_unittest/conftest.py ============================================================================== --- (empty file) +++ py/trunk/contrib/py_unittest/conftest.py Tue Sep 2 21:08:45 2008 @@ -0,0 +1,71 @@ +import py +import unittest +import sys +from py.__.test.collect import configproperty as _configproperty +unittest.failureException = AssertionError + +def configproperty(name): + def fget(self): + ret = self._config.getvalue(name, self.fspath) + return ret + return property(fget) + +class Module(py.test.collect.Module): + UnitTestCase = configproperty('UnitTestCase') + def makeitem(self, name, obj, usefilters=True): + # XXX add test_suite() support(?) + if py.std.inspect.isclass(obj) and issubclass(obj, unittest.TestCase): + return self.UnitTestCase(name, parent=self) + elif callable(obj) and getattr(obj, 'func_name', '') == 'test_suite': + return None + return super(Module, self).makeitem(name, obj, usefilters) + +class UnitTestCase(py.test.collect.Class): + TestCaseInstance = configproperty('TestCaseInstance') + def collect(self): + return [self.TestCaseInstance("()", self)] + + def setup(self): + pass + + def teardown(self): + pass + +_dummy = object() +class TestCaseInstance(py.test.collect.Instance): + UnitTestFunction = configproperty('UnitTestFunction') + def collect(self): + loader = unittest.TestLoader() + names = loader.getTestCaseNames(self.obj.__class__) + l = [] + for name in names: + callobj = getattr(self.obj, name) + if callable(callobj): + l.append(self.UnitTestFunction(name, parent=self)) + return l + + def _getobj(self): + x = self.parent.obj + return self.parent.obj(methodName='run') + +class UnitTestFunction(py.test.collect.Function): + def __init__(self, name, parent, args=(), obj=_dummy, sort_value=None): + super(UnitTestFunction, self).__init__(name, parent) + self._args = args + if obj is not _dummy: + self._obj = obj + self._sort_value = sort_value + + def runtest(self): + target = self.obj + args = self._args + target(*args) + + def setup(self): + instance = self.obj.im_self + instance.setUp() + + def teardown(self): + instance = self.obj.im_self + instance.tearDown() + Added: py/trunk/contrib/py_unittest/readme.txt ============================================================================== --- (empty file) +++ py/trunk/contrib/py_unittest/readme.txt Tue Sep 2 21:08:45 2008 @@ -0,0 +1,6 @@ +code for collecting traditional unit tests based on + + http://johnnydebris.net/svn/projects/py_unittest + +from Guido Wesdorp. + Added: py/trunk/contrib/py_unittest/test_conftest.py ============================================================================== --- (empty file) +++ py/trunk/contrib/py_unittest/test_conftest.py Tue Sep 2 21:08:45 2008 @@ -0,0 +1,64 @@ +import py +from py.__.test.outcome import Failed +from py.__.test.testing import suptest +conftestpath = py.magic.autopath().dirpath("conftest.py") + +class TestTestCaseInstance(suptest.InlineSession): + def setup_method(self, method): + super(TestTestCaseInstance, self).setup_method(method) + self.tmpdir.ensure("__init__.py") + conftestpath.copy(self.tmpdir.join(conftestpath.basename)) + + def test_simple_unittest(self): + test_one = self.makepyfile(test_one=""" + import unittest + class MyTestCase(unittest.TestCase): + def test_passing(self): + self.assertEquals('foo', 'foo') + """) + sorter = self.parse_and_run(test_one) + rep = sorter.getreport("test_passing") + assert rep.passed + + def test_simple_failing(self): + test_one = self.makepyfile(test_one=""" + import unittest + class MyTestCase(unittest.TestCase): + def test_failing(self): + self.assertEquals('foo', 'bar') + """) + sorter = self.parse_and_run(test_one) + rep = sorter.getreport("test_failing") + assert rep.failed + + def test_setup(self): + test_one = self.makepyfile(test_one=""" + import unittest + class MyTestCase(unittest.TestCase): + def setUp(self): + self.foo = 1 + def test_setUp(self): + self.assertEquals(1, self.foo) + """) + sorter = self.parse_and_run(test_one) + rep = sorter.getreport("test_setUp") + assert rep.passed + + def test_teardown(self): + test_one = self.makepyfile(test_one=""" + import unittest + class MyTestCase(unittest.TestCase): + l = [] + def test_one(self): + pass + def tearDown(self): + self.l.append(None) + class Second(unittest.TestCase): + def test_check(self): + self.assertEquals(MyTestCase.l, [None]) + """) + sorter = self.parse_and_run(test_one) + passed, skipped, failed = sorter.countoutcomes() + assert passed + skipped + failed == 2 + assert failed == 0, failed + assert passed == 2 From hpk at codespeak.net Tue Sep 2 21:38:48 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 2 Sep 2008 21:38:48 +0200 (CEST) Subject: [py-svn] r57772 - py/trunk/py/test/testing Message-ID: <20080902193848.E37B5169E54@codespeak.net> Author: hpk Date: Tue Sep 2 21:38:46 2008 New Revision: 57772 Modified: py/trunk/py/test/testing/suptest.py Log: tweak Modified: py/trunk/py/test/testing/suptest.py ============================================================================== --- py/trunk/py/test/testing/suptest.py (original) +++ py/trunk/py/test/testing/suptest.py Tue Sep 2 21:38:46 2008 @@ -179,6 +179,9 @@ if ret is None: ret = p return ret + + def parseconfig(self, *args): + return py.test.config._reparse(list(args)) class InlineCollection(FileCreation): """ helps to collect and run test functions inlining other test functions. """ @@ -192,9 +195,6 @@ self.session = self.config.initsession() return self.config.getfsnode(path) - def parseconfig(self, *args): - return py.test.config._reparse(list(args)) - def getitems(self, source): modulecol = self.getmodulecol(source) return modulecol.collect() From hpk at codespeak.net Tue Sep 2 21:40:23 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 2 Sep 2008 21:40:23 +0200 (CEST) Subject: [py-svn] r57773 - in py/branch/py-compat-2.5.2: . contrib contrib/py_unittest py py/apigen/tracer py/code/testing py/compat py/compat/testing py/green py/test py/test/dsession/testing py/test/report py/test/report/testing py/test/testing Message-ID: <20080902194023.4B9E9169E9F@codespeak.net> Author: hpk Date: Tue Sep 2 21:40:21 2008 New Revision: 57773 Added: py/branch/py-compat-2.5.2/ - copied from r57745, py/trunk/ py/branch/py-compat-2.5.2/MANIFEST - copied unchanged from r57755, py/trunk/MANIFEST py/branch/py-compat-2.5.2/README.txt - copied unchanged from r57755, py/trunk/README.txt py/branch/py-compat-2.5.2/contrib/ - copied from r57756, py/trunk/contrib/ py/branch/py-compat-2.5.2/contrib/py_unittest/ - copied from r57771, py/trunk/contrib/py_unittest/ py/branch/py-compat-2.5.2/py/ - copied from r57755, py/trunk/py/ py/branch/py-compat-2.5.2/py/code/testing/test_excinfo.py - copied unchanged from r57757, py/trunk/py/code/testing/test_excinfo.py py/branch/py-compat-2.5.2/py/compat/conftest.pyc (contents, props changed) py/branch/py-compat-2.5.2/py/compat/linecache.py (contents, props changed) py/branch/py-compat-2.5.2/py/compat/pdb.py (contents, props changed) py/branch/py-compat-2.5.2/py/test/dsession/testing/test_functional_dsession.py - copied unchanged from r57758, py/trunk/py/test/dsession/testing/test_functional_dsession.py py/branch/py-compat-2.5.2/py/test/dsession/testing/test_hostmanage.py - copied unchanged from r57758, py/trunk/py/test/dsession/testing/test_hostmanage.py py/branch/py-compat-2.5.2/py/test/dsession/testing/test_masterslave.py - copied unchanged from r57758, py/trunk/py/test/dsession/testing/test_masterslave.py py/branch/py-compat-2.5.2/py/test/pycollect.py - copied unchanged from r57762, py/trunk/py/test/pycollect.py py/branch/py-compat-2.5.2/py/test/report/base.py - copied unchanged from r57762, py/trunk/py/test/report/base.py py/branch/py-compat-2.5.2/py/test/report/terminal.py - copied unchanged from r57762, py/trunk/py/test/report/terminal.py py/branch/py-compat-2.5.2/py/test/report/testing/test_basereporter.py - copied unchanged from r57762, py/trunk/py/test/report/testing/test_basereporter.py py/branch/py-compat-2.5.2/py/test/testing/suptest.py - copied unchanged from r57772, py/trunk/py/test/testing/suptest.py py/branch/py-compat-2.5.2/py/test/testing/test_collect.py - copied unchanged from r57762, py/trunk/py/test/testing/test_collect.py py/branch/py-compat-2.5.2/py/test/testing/test_config.py - copied unchanged from r57762, py/trunk/py/test/testing/test_config.py py/branch/py-compat-2.5.2/py/test/testing/test_event.py - copied unchanged from r57762, py/trunk/py/test/testing/test_event.py py/branch/py-compat-2.5.2/py/test/testing/test_session.py - copied unchanged from r57762, py/trunk/py/test/testing/test_session.py py/branch/py-compat-2.5.2/setup.py - copied unchanged from r57755, py/trunk/setup.py Removed: py/branch/py-compat-2.5.2/py/compat/conftest.py py/branch/py-compat-2.5.2/py/green/ py/branch/py-compat-2.5.2/py/test/dsession/testing/basetest.py py/branch/py-compat-2.5.2/py/test/testing/setupdata.py py/branch/py-compat-2.5.2/py/test/testing/test_repevent.py Modified: py/branch/py-compat-2.5.2/py/__init__.py py/branch/py-compat-2.5.2/py/apigen/tracer/description.py py/branch/py-compat-2.5.2/py/compat/doctest.py py/branch/py-compat-2.5.2/py/compat/optparse.py py/branch/py-compat-2.5.2/py/compat/subprocess.py py/branch/py-compat-2.5.2/py/compat/testing/test_doctest.py py/branch/py-compat-2.5.2/py/compat/testing/test_doctest2.py py/branch/py-compat-2.5.2/py/compat/testing/test_optparse.py py/branch/py-compat-2.5.2/py/compat/testing/test_textwrap.py py/branch/py-compat-2.5.2/py/compat/textwrap.py Log: branch to use 2.5.2 compat modules, needs more work and fails on py/doc/apigen.txt text which probably is an apigen bug. Modified: py/branch/py-compat-2.5.2/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/branch/py-compat-2.5.2/py/__init__.py Tue Sep 2 21:40:21 2008 @@ -176,11 +176,12 @@ 'log.Syslog' : ('./log/consumer.py', 'Syslog'), 'log.get' : ('./log/logger.py', 'get'), - # compatibility modules (taken from 2.4.4) + # compatibility modules (taken from 2.5.2) 'compat.__doc__' : ('./compat/__init__.py', '__doc__'), 'compat.doctest' : ('./compat/doctest.py', '*'), 'compat.optparse' : ('./compat/optparse.py', '*'), 'compat.textwrap' : ('./compat/textwrap.py', '*'), - 'compat.subprocess' : ('./compat/subprocess.py', '*'), + 'compat.pdb' : ('./compat/pdb.py', '*'), + #'compat.subprocess' : ('./compat/subprocess.py', '*'), }) Modified: py/branch/py-compat-2.5.2/py/apigen/tracer/description.py ============================================================================== --- py/trunk/py/apigen/tracer/description.py (original) +++ py/branch/py-compat-2.5.2/py/apigen/tracer/description.py Tue Sep 2 21:40:21 2008 @@ -78,6 +78,8 @@ if upward_frame: if hasattr(upward_frame, 'raw'): upward_frame = upward_frame.raw + assert upward_frame in stack, (upward_frame, stack) + assert frame in stack, (frame, stack) lst = [py.code.Frame(i) for i in stack[stack.index(frame):\ stack.index(upward_frame)+1]] if len(lst) > 1: Deleted: /py/trunk/py/compat/conftest.py ============================================================================== --- /py/trunk/py/compat/conftest.py Tue Sep 2 21:40:21 2008 +++ (empty file) @@ -1,5 +0,0 @@ -import py - -class Directory(py.test.collect.Directory): - def collect(self): - py.test.skip("compat tests need to be run manually") Added: py/branch/py-compat-2.5.2/py/compat/conftest.pyc ============================================================================== Binary file. No diff available. Modified: py/branch/py-compat-2.5.2/py/compat/doctest.py ============================================================================== --- py/trunk/py/compat/doctest.py (original) +++ py/branch/py-compat-2.5.2/py/compat/doctest.py Tue Sep 2 21:40:21 2008 @@ -54,6 +54,7 @@ 'DONT_ACCEPT_BLANKLINE', 'NORMALIZE_WHITESPACE', 'ELLIPSIS', + 'SKIP', 'IGNORE_EXCEPTION_DETAIL', 'COMPARISON_FLAGS', 'REPORT_UDIFF', @@ -62,7 +63,6 @@ 'REPORT_ONLY_FIRST_FAILURE', 'REPORTING_FLAGS', # 1. Utility Functions - 'is_private', # 2. Example & DocTest 'Example', 'DocTest', @@ -95,16 +95,11 @@ import __future__ -import sys, traceback, inspect, linecache, os, re, types +import sys, traceback, inspect, linecache, os, re import unittest, difflib, pdb, tempfile import warnings from StringIO import StringIO -# Don't whine about the deprecated is_private function in this -# module's tests. -warnings.filterwarnings("ignore", "is_private", DeprecationWarning, - __name__, 0) - # There are 4 basic classes: # - Example: a pair, plus an intra-docstring line number. # - DocTest: a collection of examples, parsed from a docstring, plus @@ -135,12 +130,14 @@ DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE') NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE') ELLIPSIS = register_optionflag('ELLIPSIS') +SKIP = register_optionflag('SKIP') IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL') COMPARISON_FLAGS = (DONT_ACCEPT_TRUE_FOR_1 | DONT_ACCEPT_BLANKLINE | NORMALIZE_WHITESPACE | ELLIPSIS | + SKIP | IGNORE_EXCEPTION_DETAIL) REPORT_UDIFF = register_optionflag('REPORT_UDIFF') @@ -175,35 +172,6 @@ ## 1. Utility Functions ###################################################################### -def is_private(prefix, base): - """prefix, base -> true iff name prefix + "." + base is "private". - - Prefix may be an empty string, and base does not contain a period. - Prefix is ignored (although functions you write conforming to this - protocol may make use of it). - Return true iff base begins with an (at least one) underscore, but - does not both begin and end with (at least) two underscores. - - >>> is_private("a.b", "my_func") - False - >>> is_private("____", "_my_func") - True - >>> is_private("someclass", "__init__") - False - >>> is_private("sometypo", "__init_") - True - >>> is_private("x.y.z", "_") - True - >>> is_private("_x.y.z", "__") - False - >>> is_private("", "") # senseless but consistent - False - """ - warnings.warn("is_private is deprecated; it wasn't useful; " - "examine DocTestFinder.find() lists instead", - DeprecationWarning, stacklevel=2) - return base[:1] == "_" and not base[:2] == "__" == base[-2:] - def _extract_future_flags(globs): """ Return the compiler-flags associated with the future features that @@ -235,6 +203,18 @@ else: raise TypeError("Expected a module, string, or None") +def _load_testfile(filename, package, module_relative): + if module_relative: + package = _normalize_module(package, 3) + filename = _module_relative_path(package, filename) + if hasattr(package, '__loader__'): + if hasattr(package.__loader__, 'get_data'): + file_contents = package.__loader__.get_data(filename) + # get_data() opens files as 'rb', so one must do the equivalent + # conversion as universal newlines would do. + return file_contents.replace(os.linesep, '\n'), filename + return open(filename).read(), filename + def _indent(s, indent=4): """ Add the given number of space characters to the beginning every @@ -340,7 +320,20 @@ """ def __init__(self, out): self.__out = out - pdb.Pdb.__init__(self) + self.__debugger_used = False + pdb.Pdb.__init__(self, stdout=out) + + def set_trace(self, frame=None): + self.__debugger_used = True + if frame is None: + frame = sys._getframe().f_back + pdb.Pdb.set_trace(self, frame) + + def set_continue(self): + # Calling set_continue unconditionally would break unit test + # coverage reporting, as Bdb.set_continue calls sys.settrace(None). + if self.__debugger_used: + pdb.Pdb.set_continue(self) def trace_dispatch(self, *args): # Redirect stdout to the given stream. @@ -747,7 +740,7 @@ """ def __init__(self, verbose=False, parser=DocTestParser(), - recurse=True, _namefilter=None, exclude_empty=True): + recurse=True, exclude_empty=True): """ Create a new doctest finder. @@ -767,12 +760,8 @@ self._verbose = verbose self._recurse = recurse self._exclude_empty = exclude_empty - # _namefilter is undocumented, and exists only for temporary backward- - # compatibility support of testmod's deprecated isprivate mess. - self._namefilter = _namefilter - def find(self, obj, name=None, module=None, globs=None, - extraglobs=None): + def find(self, obj, name=None, module=None, globs=None, extraglobs=None): """ Return a list of the DocTests that are defined by the given object's docstring, or by any of its contained objects' @@ -855,13 +844,6 @@ tests.sort() return tests - def _filter(self, obj, prefix, base): - """ - Return true if the given object should not be examined. - """ - return (self._namefilter is not None and - self._namefilter(prefix, base)) - def _from_module(self, module, object): """ Return true if the given object is defined in the given @@ -903,9 +885,6 @@ # Look for tests in a module's contained objects. if inspect.ismodule(obj) and self._recurse: for valname, val in obj.__dict__.items(): - # Check if this contained object should be ignored. - if self._filter(val, name, valname): - continue valname = '%s.%s' % (name, valname) # Recurse to functions & classes. if ((inspect.isfunction(val) or inspect.isclass(val)) and @@ -934,9 +913,6 @@ # Look for tests in a class's contained objects. if inspect.isclass(obj) and self._recurse: for valname, val in obj.__dict__.items(): - # Check if this contained object should be ignored. - if self._filter(val, name, valname): - continue # Special handling for staticmethod/classmethod. if isinstance(val, staticmethod): val = getattr(obj, valname) @@ -1229,6 +1205,10 @@ else: self.optionflags &= ~optionflag + # If 'SKIP' is set, then skip this example. + if self.optionflags & SKIP: + continue + # Record that we started this example. tries += 1 if not quiet: @@ -1324,13 +1304,13 @@ __LINECACHE_FILENAME_RE = re.compile(r'[\w\.]+)' r'\[(?P\d+)\]>$') - def __patched_linecache_getlines(self, filename, additional_arg=None): + def __patched_linecache_getlines(self, filename, module_globals=None): m = self.__LINECACHE_FILENAME_RE.match(filename) if m and m.group('name') == self.test.name: example = self.test.examples[int(m.group('examplenum'))] return example.source.splitlines(True) else: - return self.save_linecache_getlines(filename) + return self.save_linecache_getlines(filename, module_globals) def run(self, test, compileflags=None, out=None, clear_globs=True): """ @@ -1597,7 +1577,7 @@ - test: the DocTest object being run - - excample: the Example object that failed + - example: the Example object that failed - got: the actual output """ @@ -1616,7 +1596,7 @@ - test: the DocTest object being run - - excample: the Example object that failed + - example: the Example object that failed - exc_info: the exception info """ @@ -1740,17 +1720,16 @@ # class, updated by testmod. master = None -def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None, +def testmod(m=None, name=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, exclude_empty=False): - """m=None, name=None, globs=None, verbose=None, isprivate=None, - report=True, optionflags=0, extraglobs=None, raise_on_error=False, + """m=None, name=None, globs=None, verbose=None, report=True, + optionflags=0, extraglobs=None, raise_on_error=False, exclude_empty=False Test examples in docstrings in functions and classes reachable from module m (or the current module if m is not supplied), starting - with m.__doc__. Unless isprivate is specified, private names - are not skipped. + with m.__doc__. Also test examples reachable from dict m.__test__ if it exists and is not None. m.__test__ maps names to functions, classes and strings; @@ -1788,6 +1767,7 @@ DONT_ACCEPT_BLANKLINE NORMALIZE_WHITESPACE ELLIPSIS + SKIP IGNORE_EXCEPTION_DETAIL REPORT_UDIFF REPORT_CDIFF @@ -1798,13 +1778,6 @@ first unexpected exception or failure. This allows failures to be post-mortem debugged. - Deprecated in Python 2.4: - Optional keyword arg "isprivate" specifies a function used to - determine whether a name is private. The default function is - treat all functions as public. Optionally, "isprivate" can be - set to doctest.is_private to skip over functions marked as private - using the underscore naming convention; see its docs for details. - Advanced tomfoolery: testmod runs methods of a local instance of class doctest.Tester, then merges the results into (or creates) global Tester instance doctest.master. Methods of doctest.master @@ -1815,11 +1788,6 @@ """ global master - if isprivate is not None: - warnings.warn("the isprivate argument is deprecated; " - "examine DocTestFinder.find() lists instead", - DeprecationWarning) - # If no module was given, then use __main__. if m is None: # DWA - m will still be None if this wasn't invoked from the command @@ -1836,7 +1804,7 @@ name = m.__name__ # Find, parse, and run all tests in the given module. - finder = DocTestFinder(_namefilter=isprivate, exclude_empty=exclude_empty) + finder = DocTestFinder(exclude_empty=exclude_empty) if raise_on_error: runner = DebugRunner(verbose=verbose, optionflags=optionflags) @@ -1858,7 +1826,8 @@ def testfile(filename, module_relative=True, name=None, package=None, globs=None, verbose=None, report=True, optionflags=0, - extraglobs=None, raise_on_error=False, parser=DocTestParser()): + extraglobs=None, raise_on_error=False, parser=DocTestParser(), + encoding=None): """ Test examples in the given file. Return (#failures, #tests). @@ -1910,6 +1879,7 @@ DONT_ACCEPT_BLANKLINE NORMALIZE_WHITESPACE ELLIPSIS + SKIP IGNORE_EXCEPTION_DETAIL REPORT_UDIFF REPORT_CDIFF @@ -1923,6 +1893,9 @@ Optional keyword arg "parser" specifies a DocTestParser (or subclass) that should be used to extract tests from the files. + Optional keyword arg "encoding" specifies an encoding that should + be used to convert the file to unicode. + Advanced tomfoolery: testmod runs methods of a local instance of class doctest.Tester, then merges the results into (or creates) global Tester instance doctest.master. Methods of doctest.master @@ -1938,9 +1911,7 @@ "relative paths.") # Relativize the path - if module_relative: - package = _normalize_module(package) - filename = _module_relative_path(package, filename) + text, filename = _load_testfile(filename, package, module_relative) # If no name was given, then use the file's name. if name is None: @@ -1959,9 +1930,11 @@ else: runner = DocTestRunner(verbose=verbose, optionflags=optionflags) + if encoding is not None: + text = text.decode(encoding) + # Read the file, convert it to a test, and run it. - s = open(filename).read() - test = parser.get_doctest(s, globs, name, filename, 0) + test = parser.get_doctest(text, globs, name, filename, 0) runner.run(test) if report: @@ -2004,8 +1977,7 @@ # actually used in any way. class Tester: - def __init__(self, mod=None, globs=None, verbose=None, - isprivate=None, optionflags=0): + def __init__(self, mod=None, globs=None, verbose=None, optionflags=0): warnings.warn("class Tester is deprecated; " "use class doctest.DocTestRunner instead", @@ -2020,9 +1992,8 @@ self.globs = globs self.verbose = verbose - self.isprivate = isprivate self.optionflags = optionflags - self.testfinder = DocTestFinder(_namefilter=isprivate) + self.testfinder = DocTestFinder() self.testrunner = DocTestRunner(verbose=verbose, optionflags=optionflags) @@ -2330,22 +2301,29 @@ ) def DocFileTest(path, module_relative=True, package=None, - globs=None, parser=DocTestParser(), **options): + globs=None, parser=DocTestParser(), + encoding=None, **options): if globs is None: globs = {} + else: + globs = globs.copy() if package and not module_relative: raise ValueError("Package may only be specified for module-" "relative paths.") # Relativize the path. - if module_relative: - package = _normalize_module(package) - path = _module_relative_path(package, path) + doc, path = _load_testfile(path, package, module_relative) + + if "__file__" not in globs: + globs["__file__"] = path # Find the file and read it. name = os.path.basename(path) - doc = open(path).read() + + # If an encoding is specified, use it to convert the file to unicode + if encoding is not None: + doc = doc.decode(encoding) # Convert it to a test, and wrap it in a DocFileCase. test = parser.get_doctest(doc, globs, name, path, 0) @@ -2403,6 +2381,9 @@ parser A DocTestParser (or subclass) that should be used to extract tests from the files. + + encoding + An encoding that will be used to convert the files to unicode. """ suite = unittest.TestSuite() Added: py/branch/py-compat-2.5.2/py/compat/linecache.py ============================================================================== --- (empty file) +++ py/branch/py-compat-2.5.2/py/compat/linecache.py Tue Sep 2 21:40:21 2008 @@ -0,0 +1,136 @@ +"""Cache lines from files. + +This is intended to read lines from modules imported -- hence if a filename +is not found, it will look down the module search path for a file by +that name. +""" + +import sys +import os + +__all__ = ["getline", "clearcache", "checkcache"] + +def getline(filename, lineno, module_globals=None): + lines = getlines(filename, module_globals) + if 1 <= lineno <= len(lines): + return lines[lineno-1] + else: + return '' + + +# The cache + +cache = {} # The cache + + +def clearcache(): + """Clear the cache entirely.""" + + global cache + cache = {} + + +def getlines(filename, module_globals=None): + """Get the lines for a file from the cache. + Update the cache if it doesn't contain an entry for this file already.""" + + if filename in cache: + return cache[filename][2] + else: + return updatecache(filename, module_globals) + + +def checkcache(filename=None): + """Discard cache entries that are out of date. + (This is not checked upon each call!)""" + + if filename is None: + filenames = cache.keys() + else: + if filename in cache: + filenames = [filename] + else: + return + + for filename in filenames: + size, mtime, lines, fullname = cache[filename] + if mtime is None: + continue # no-op for files loaded via a __loader__ + try: + stat = os.stat(fullname) + except os.error: + del cache[filename] + continue + if size != stat.st_size or mtime != stat.st_mtime: + del cache[filename] + + +def updatecache(filename, module_globals=None): + """Update a cache entry and return its list of lines. + If something's wrong, print a message, discard the cache entry, + and return an empty list.""" + + if filename in cache: + del cache[filename] + if not filename or filename[0] + filename[-1] == '<>': + return [] + + fullname = filename + try: + stat = os.stat(fullname) + except os.error, msg: + basename = os.path.split(filename)[1] + + # Try for a __loader__, if available + if module_globals and '__loader__' in module_globals: + name = module_globals.get('__name__') + loader = module_globals['__loader__'] + get_source = getattr(loader, 'get_source', None) + + if name and get_source: + if basename.startswith(name.split('.')[-1]+'.'): + try: + data = get_source(name) + except (ImportError, IOError): + pass + else: + if data is None: + # No luck, the PEP302 loader cannot find the source + # for this module. + return [] + cache[filename] = ( + len(data), None, + [line+'\n' for line in data.splitlines()], fullname + ) + return cache[filename][2] + + # Try looking through the module search path. + + for dirname in sys.path: + # When using imputil, sys.path may contain things other than + # strings; ignore them when it happens. + try: + fullname = os.path.join(dirname, basename) + except (TypeError, AttributeError): + # Not sufficiently string-like to do anything useful with. + pass + else: + try: + stat = os.stat(fullname) + break + except os.error: + pass + else: + # No luck +## print '*** Cannot stat', filename, ':', msg + return [] + try: + fp = open(fullname, 'rU') + lines = fp.readlines() + fp.close() + except IOError, msg: +## print '*** Cannot open', fullname, ':', msg + return [] + size, mtime = stat.st_size, stat.st_mtime + cache[filename] = size, mtime, lines, fullname + return lines Modified: py/branch/py-compat-2.5.2/py/compat/optparse.py ============================================================================== --- py/trunk/py/compat/optparse.py (original) +++ py/branch/py-compat-2.5.2/py/compat/optparse.py Tue Sep 2 21:40:21 2008 @@ -16,7 +16,7 @@ # Python developers: please do not make changes to this file, since # it is automatically generated from the Optik source code. -__version__ = "1.5a2" +__version__ = "1.5.3" __all__ = ['Option', 'SUPPRESS_HELP', @@ -35,8 +35,8 @@ 'BadOptionError'] __copyright__ = """ -Copyright (c) 2001-2004 Gregory P. Ward. All rights reserved. -Copyright (c) 2002-2004 Python Software Foundation. All rights reserved. +Copyright (c) 2001-2006 Gregory P. Ward. All rights reserved. +Copyright (c) 2002-2006 Python Software Foundation. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -69,17 +69,24 @@ import sys, os import types import textwrap -from gettext import gettext as _ def _repr(self): return "<%s at 0x%x: %s>" % (self.__class__.__name__, id(self), self) # This file was generated from: -# Id: option_parser.py 421 2004-10-26 00:45:16Z greg -# Id: option.py 422 2004-10-26 00:53:47Z greg -# Id: help.py 367 2004-07-24 23:21:21Z gward -# Id: errors.py 367 2004-07-24 23:21:21Z gward +# Id: option_parser.py 527 2006-07-23 15:21:30Z greg +# Id: option.py 522 2006-06-11 16:22:03Z gward +# Id: help.py 527 2006-07-23 15:21:30Z greg +# Id: errors.py 509 2006-04-20 00:58:24Z gward + +try: + from gettext import gettext +except ImportError: + def gettext(message): + return message +_ = gettext + class OptParseError (Exception): def __init__(self, msg): @@ -118,8 +125,25 @@ class BadOptionError (OptParseError): """ - Raised if an invalid or ambiguous option is seen on the command-line. + Raised if an invalid option is seen on the command line. + """ + def __init__(self, opt_str): + self.opt_str = opt_str + + def __str__(self): + return _("no such option: %s") % self.opt_str + +class AmbiguousOptionError (BadOptionError): + """ + Raised if an ambiguous option is seen on the command line. """ + def __init__(self, opt_str, possibilities): + BadOptionError.__init__(self, opt_str) + self.possibilities = possibilities + + def __str__(self): + return (_("ambiguous option: %s (%s?)") + % (self.opt_str, ", ".join(self.possibilities))) class HelpFormatter: @@ -221,15 +245,30 @@ def format_heading(self, heading): raise NotImplementedError, "subclasses must implement" - def format_description(self, description): - if not description: - return "" - desc_width = self.width - self.current_indent + def _format_text(self, text): + """ + Format a paragraph of free-form text for inclusion in the + help output at the current indentation level. + """ + text_width = self.width - self.current_indent indent = " "*self.current_indent - return textwrap.fill(description, - desc_width, + return textwrap.fill(text, + text_width, initial_indent=indent, - subsequent_indent=indent) + "\n" + subsequent_indent=indent) + + def format_description(self, description): + if description: + return self._format_text(description) + "\n" + else: + return "" + + def format_epilog(self, epilog): + if epilog: + return "\n" + self._format_text(epilog) + "\n" + else: + return "" + def expand_default(self, option): if self.parser is None or not self.default_tag: @@ -326,7 +365,7 @@ self, indent_increment, max_help_position, width, short_first) def format_usage(self, usage): - return _("usage: %s\n") % usage + return _("Usage: %s\n") % usage def format_heading(self, heading): return "%*s%s:\n" % (self.current_indent, "", heading) @@ -351,8 +390,27 @@ return "%s\n%s\n" % (heading, "=-"[self.level] * len(heading)) -_builtin_cvt = { "int" : (int, _("integer")), - "long" : (long, _("long integer")), +def _parse_num(val, type): + if val[:2].lower() == "0x": # hexadecimal + radix = 16 + elif val[:2].lower() == "0b": # binary + radix = 2 + val = val[2:] or "0" # have to remove "0b" prefix + elif val[:1] == "0": # octal + radix = 8 + else: # decimal + radix = 10 + + return type(val, radix) + +def _parse_int(val): + return _parse_num(val, int) + +def _parse_long(val): + return _parse_num(val, long) + +_builtin_cvt = { "int" : (_parse_int, _("integer")), + "long" : (_parse_long, _("long integer")), "float" : (float, _("floating-point")), "complex" : (complex, _("complex")) } @@ -420,6 +478,7 @@ "store_true", "store_false", "append", + "append_const", "count", "callback", "help", @@ -433,6 +492,7 @@ "store_true", "store_false", "append", + "append_const", "count") # The set of actions for which it makes sense to supply a value @@ -446,6 +506,10 @@ ALWAYS_TYPED_ACTIONS = ("store", "append") + # The set of actions which take a 'const' attribute. + CONST_ACTIONS = ("store_const", + "append_const") + # The set of known types for option parsers. Again, listed here for # constructor argument validation. TYPES = ("string", "int", "long", "float", "complex", "choice") @@ -572,9 +636,17 @@ # No type given? "string" is the most sensible default. self.type = "string" else: - # Allow type objects as an alternative to their names. - if type(self.type) is type: + # Allow type objects or builtin type conversion functions + # (int, str, etc.) as an alternative to their names. (The + # complicated check of __builtin__ is only necessary for + # Python 2.1 and earlier, and is short-circuited by the + # first check on modern Pythons.) + import __builtin__ + if ( type(self.type) is types.TypeType or + (hasattr(self.type, "__name__") and + getattr(__builtin__, self.type.__name__, None) is self.type) ): self.type = self.type.__name__ + if self.type == "str": self.type = "string" @@ -613,7 +685,7 @@ self.dest = self._short_opts[0][1] def _check_const(self): - if self.action != "store_const" and self.const is not None: + if self.action not in self.CONST_ACTIONS and self.const is not None: raise OptionError( "'const' must not be supplied for action %r" % self.action, self) @@ -720,6 +792,8 @@ setattr(values, dest, False) elif action == "append": values.ensure_value(dest, []).append(value) + elif action == "append_const": + values.ensure_value(dest, []).append(self.const) elif action == "count": setattr(values, dest, values.ensure_value(dest, 0) + 1) elif action == "callback": @@ -748,10 +822,15 @@ True, False except NameError: (True, False) = (1, 0) + try: basestring except NameError: - basestring = (str, unicode) + def isbasestring(x): + return isinstance(x, (types.StringType, types.UnicodeType)) +else: + def isbasestring(x): + return isinstance(x, basestring) class Values: @@ -766,16 +845,13 @@ __repr__ = _repr - def __eq__(self, other): + def __cmp__(self, other): if isinstance(other, Values): - return self.__dict__ == other.__dict__ - elif isinstance(other, dict): - return self.__dict__ == other + return cmp(self.__dict__, other.__dict__) + elif isinstance(other, types.DictType): + return cmp(self.__dict__, other) else: - return False - - def __ne__(self, other): - return not (self == other) + return -1 def _update_careful(self, dict): """ @@ -893,6 +969,13 @@ return self.description + def destroy(self): + """see OptionParser.destroy().""" + del self._short_opt + del self._long_opt + del self.defaults + + # -- Option-adding methods ----------------------------------------- def _check_conflict(self, option): @@ -1018,6 +1101,11 @@ def set_title(self, title): self.title = title + def destroy(self): + """see OptionParser.destroy().""" + OptionContainer.destroy(self) + del self.option_list + # -- Help-formatting methods --------------------------------------- def format_help(self, formatter): @@ -1044,6 +1132,8 @@ prog : string the name of the current program (to override os.path.basename(sys.argv[0])). + epilog : string + paragraph of help text to print after option help option_groups : [OptionGroup] list of option groups in this parser (option groups are @@ -1102,7 +1192,8 @@ description=None, formatter=None, add_help_option=True, - prog=None): + prog=None, + epilog=None): OptionContainer.__init__( self, option_class, conflict_handler, description) self.set_usage(usage) @@ -1114,6 +1205,7 @@ formatter = IndentedHelpFormatter() self.formatter = formatter self.formatter.set_parser(self) + self.epilog = epilog # Populate the option list; initial sources are the # standard_option_list class attribute, the 'option_list' @@ -1124,6 +1216,22 @@ self._init_parsing_state() + + def destroy(self): + """ + Declare that you are done with this OptionParser. This cleans up + reference cycles so the OptionParser (and all objects referenced by + it) can be garbage-collected promptly. After calling destroy(), the + OptionParser is unusable. + """ + OptionContainer.destroy(self) + for group in self.option_groups: + group.destroy() + del self.option_list + del self.option_groups + del self.formatter + + # -- Private methods ----------------------------------------------- # (used by our or OptionContainer's constructor) @@ -1167,7 +1275,7 @@ elif usage is SUPPRESS_USAGE: self.usage = None # For backwards compatibility with Optik 1.3 and earlier. - elif usage.startswith("usage:" + " "): + elif usage.lower().startswith("usage: "): self.usage = usage[7:] else: self.usage = usage @@ -1201,7 +1309,7 @@ defaults = self.defaults.copy() for option in self._get_all_options(): default = defaults.get(option.dest) - if isinstance(default, basestring): + if isbasestring(default): opt_str = option.get_opt_string() defaults[option.dest] = option.check_value(opt_str, default) @@ -1276,7 +1384,7 @@ try: stop = self._process_args(largs, rargs, values) except (BadOptionError, OptionValueError), err: - self.error(err.msg) + self.error(str(err)) args = largs + rargs return self.check_values(values, args) @@ -1401,7 +1509,7 @@ i += 1 # we have consumed a character if not option: - self.error(_("no such option: %s") % opt) + raise BadOptionError(opt) if option.takes_value(): # Any characters left in arg? Pretend they're the # next arg, and stop consuming characters of arg. @@ -1501,7 +1609,7 @@ formatter = self.formatter formatter.store_option_strings(self) result = [] - result.append(formatter.format_heading(_("options"))) + result.append(formatter.format_heading(_("Options"))) formatter.indent() if self.option_list: result.append(OptionContainer.format_option_help(self, formatter)) @@ -1513,6 +1621,9 @@ # Drop the last "\n", or the header if no options or option groups: return "".join(result[:-1]) + def format_epilog(self, formatter): + return formatter.format_epilog(self.epilog) + def format_help(self, formatter=None): if formatter is None: formatter = self.formatter @@ -1522,8 +1633,16 @@ if self.description: result.append(self.format_description(formatter) + "\n") result.append(self.format_option_help(formatter)) + result.append(self.format_epilog(formatter)) return "".join(result) + # used by test suite + def _get_encoding(self, file): + encoding = getattr(file, "encoding", None) + if not encoding: + encoding = sys.getdefaultencoding() + return encoding + def print_help(self, file=None): """print_help(file : file = stdout) @@ -1532,7 +1651,8 @@ """ if file is None: file = sys.stdout - file.write(self.format_help()) + encoding = self._get_encoding(file) + file.write(self.format_help().encode(encoding, "replace")) # class OptionParser @@ -1555,12 +1675,11 @@ if len(possibilities) == 1: return possibilities[0] elif not possibilities: - raise BadOptionError(_("no such option: %s") % s) + raise BadOptionError(s) else: # More than one possible completion: ambiguous prefix. possibilities.sort() - raise BadOptionError(_("ambiguous option: %s (%s?)") - % (s, ", ".join(possibilities))) + raise AmbiguousOptionError(s, possibilities) # Some day, there might be many Option classes. As of Optik 1.3, the Added: py/branch/py-compat-2.5.2/py/compat/pdb.py ============================================================================== --- (empty file) +++ py/branch/py-compat-2.5.2/py/compat/pdb.py Tue Sep 2 21:40:21 2008 @@ -0,0 +1,1234 @@ +#! /usr/bin/env python + +"""A Python debugger.""" + +# (See pdb.doc for documentation.) + +import sys +import linecache +import cmd +import bdb +from repr import Repr +import os +import re +import pprint +import traceback +# Create a custom safe Repr instance and increase its maxstring. +# The default of 30 truncates error messages too easily. +_repr = Repr() +_repr.maxstring = 200 +_saferepr = _repr.repr + +__all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace", + "post_mortem", "help"] + +def find_function(funcname, filename): + cre = re.compile(r'def\s+%s\s*[(]' % funcname) + try: + fp = open(filename) + except IOError: + return None + # consumer of this info expects the first line to be 1 + lineno = 1 + answer = None + while 1: + line = fp.readline() + if line == '': + break + if cre.match(line): + answer = funcname, filename, lineno + break + lineno = lineno + 1 + fp.close() + return answer + + +# Interaction prompt line will separate file and call info from code +# text using value of line_prefix string. A newline and arrow may +# be to your liking. You can set it once pdb is imported using the +# command "pdb.line_prefix = '\n% '". +# line_prefix = ': ' # Use this to get the old situation back +line_prefix = '\n-> ' # Probably a better default + +class Pdb(bdb.Bdb, cmd.Cmd): + + def __init__(self, completekey='tab', stdin=None, stdout=None): + bdb.Bdb.__init__(self) + cmd.Cmd.__init__(self, completekey, stdin, stdout) + if stdout: + self.use_rawinput = 0 + self.prompt = '(Pdb) ' + self.aliases = {} + self.mainpyfile = '' + self._wait_for_mainpyfile = 0 + # Try to load readline if it exists + try: + import readline + except ImportError: + pass + + # Read $HOME/.pdbrc and ./.pdbrc + self.rcLines = [] + if 'HOME' in os.environ: + envHome = os.environ['HOME'] + try: + rcFile = open(os.path.join(envHome, ".pdbrc")) + except IOError: + pass + else: + for line in rcFile.readlines(): + self.rcLines.append(line) + rcFile.close() + try: + rcFile = open(".pdbrc") + except IOError: + pass + else: + for line in rcFile.readlines(): + self.rcLines.append(line) + rcFile.close() + + self.commands = {} # associates a command list to breakpoint numbers + self.commands_doprompt = {} # for each bp num, tells if the prompt must be disp. after execing the cmd list + self.commands_silent = {} # for each bp num, tells if the stack trace must be disp. after execing the cmd list + self.commands_defining = False # True while in the process of defining a command list + self.commands_bnum = None # The breakpoint number for which we are defining a list + + def reset(self): + bdb.Bdb.reset(self) + self.forget() + + def forget(self): + self.lineno = None + self.stack = [] + self.curindex = 0 + self.curframe = None + + def setup(self, f, t): + self.forget() + self.stack, self.curindex = self.get_stack(f, t) + self.curframe = self.stack[self.curindex][0] + self.execRcLines() + + # Can be executed earlier than 'setup' if desired + def execRcLines(self): + if self.rcLines: + # Make local copy because of recursion + rcLines = self.rcLines + # executed only once + self.rcLines = [] + for line in rcLines: + line = line[:-1] + if len(line) > 0 and line[0] != '#': + self.onecmd(line) + + # Override Bdb methods + + def user_call(self, frame, argument_list): + """This method is called when there is the remote possibility + that we ever need to stop in this function.""" + if self._wait_for_mainpyfile: + return + if self.stop_here(frame): + print >>self.stdout, '--Call--' + self.interaction(frame, None) + + def user_line(self, frame): + """This function is called when we stop or break at this line.""" + if self._wait_for_mainpyfile: + if (self.mainpyfile != self.canonic(frame.f_code.co_filename) + or frame.f_lineno<= 0): + return + self._wait_for_mainpyfile = 0 + if self.bp_commands(frame): + self.interaction(frame, None) + + def bp_commands(self,frame): + """ Call every command that was set for the current active breakpoint (if there is one) + Returns True if the normal interaction function must be called, False otherwise """ + #self.currentbp is set in bdb.py in bdb.break_here if a breakpoint was hit + if getattr(self,"currentbp",False) and self.currentbp in self.commands: + currentbp = self.currentbp + self.currentbp = 0 + lastcmd_back = self.lastcmd + self.setup(frame, None) + for line in self.commands[currentbp]: + self.onecmd(line) + self.lastcmd = lastcmd_back + if not self.commands_silent[currentbp]: + self.print_stack_entry(self.stack[self.curindex]) + if self.commands_doprompt[currentbp]: + self.cmdloop() + self.forget() + return + return 1 + + def user_return(self, frame, return_value): + """This function is called when a return trap is set here.""" + frame.f_locals['__return__'] = return_value + print >>self.stdout, '--Return--' + self.interaction(frame, None) + + def user_exception(self, frame, (exc_type, exc_value, exc_traceback)): + """This function is called if an exception occurs, + but only if we are to stop at or just below this level.""" + frame.f_locals['__exception__'] = exc_type, exc_value + if type(exc_type) == type(''): + exc_type_name = exc_type + else: exc_type_name = exc_type.__name__ + print >>self.stdout, exc_type_name + ':', _saferepr(exc_value) + self.interaction(frame, exc_traceback) + + # General interaction function + + def interaction(self, frame, traceback): + self.setup(frame, traceback) + self.print_stack_entry(self.stack[self.curindex]) + self.cmdloop() + self.forget() + + def default(self, line): + if line[:1] == '!': line = line[1:] + locals = self.curframe.f_locals + globals = self.curframe.f_globals + try: + code = compile(line + '\n', '', 'single') + exec code in globals, locals + except: + t, v = sys.exc_info()[:2] + if type(t) == type(''): + exc_type_name = t + else: exc_type_name = t.__name__ + print >>self.stdout, '***', exc_type_name + ':', v + + def precmd(self, line): + """Handle alias expansion and ';;' separator.""" + if not line.strip(): + return line + args = line.split() + while args[0] in self.aliases: + line = self.aliases[args[0]] + ii = 1 + for tmpArg in args[1:]: + line = line.replace("%" + str(ii), + tmpArg) + ii = ii + 1 + line = line.replace("%*", ' '.join(args[1:])) + args = line.split() + # split into ';;' separated commands + # unless it's an alias command + if args[0] != 'alias': + marker = line.find(';;') + if marker >= 0: + # queue up everything after marker + next = line[marker+2:].lstrip() + self.cmdqueue.append(next) + line = line[:marker].rstrip() + return line + + def onecmd(self, line): + """Interpret the argument as though it had been typed in response + to the prompt. + + Checks whether this line is typed at the normal prompt or in + a breakpoint command list definition. + """ + if not self.commands_defining: + return cmd.Cmd.onecmd(self, line) + else: + return self.handle_command_def(line) + + def handle_command_def(self,line): + """ Handles one command line during command list definition. """ + cmd, arg, line = self.parseline(line) + if cmd == 'silent': + self.commands_silent[self.commands_bnum] = True + return # continue to handle other cmd def in the cmd list + elif cmd == 'end': + self.cmdqueue = [] + return 1 # end of cmd list + cmdlist = self.commands[self.commands_bnum] + if (arg): + cmdlist.append(cmd+' '+arg) + else: + cmdlist.append(cmd) + # Determine if we must stop + try: + func = getattr(self, 'do_' + cmd) + except AttributeError: + func = self.default + if func.func_name in self.commands_resuming : # one of the resuming commands. + self.commands_doprompt[self.commands_bnum] = False + self.cmdqueue = [] + return 1 + return + + # Command definitions, called by cmdloop() + # The argument is the remaining string on the command line + # Return true to exit from the command loop + + do_h = cmd.Cmd.do_help + + def do_commands(self, arg): + """Defines a list of commands associated to a breakpoint + Those commands will be executed whenever the breakpoint causes the program to stop execution.""" + if not arg: + bnum = len(bdb.Breakpoint.bpbynumber)-1 + else: + try: + bnum = int(arg) + except: + print >>self.stdout, "Usage : commands [bnum]\n ...\n end" + return + self.commands_bnum = bnum + self.commands[bnum] = [] + self.commands_doprompt[bnum] = True + self.commands_silent[bnum] = False + prompt_back = self.prompt + self.prompt = '(com) ' + self.commands_defining = True + self.cmdloop() + self.commands_defining = False + self.prompt = prompt_back + + def do_break(self, arg, temporary = 0): + # break [ ([filename:]lineno | function) [, "condition"] ] + if not arg: + if self.breaks: # There's at least one + print >>self.stdout, "Num Type Disp Enb Where" + for bp in bdb.Breakpoint.bpbynumber: + if bp: + bp.bpprint(self.stdout) + return + # parse arguments; comma has lowest precedence + # and cannot occur in filename + filename = None + lineno = None + cond = None + comma = arg.find(',') + if comma > 0: + # parse stuff after comma: "condition" + cond = arg[comma+1:].lstrip() + arg = arg[:comma].rstrip() + # parse stuff before comma: [filename:]lineno | function + colon = arg.rfind(':') + funcname = None + if colon >= 0: + filename = arg[:colon].rstrip() + f = self.lookupmodule(filename) + if not f: + print >>self.stdout, '*** ', repr(filename), + print >>self.stdout, 'not found from sys.path' + return + else: + filename = f + arg = arg[colon+1:].lstrip() + try: + lineno = int(arg) + except ValueError, msg: + print >>self.stdout, '*** Bad lineno:', arg + return + else: + # no colon; can be lineno or function + try: + lineno = int(arg) + except ValueError: + try: + func = eval(arg, + self.curframe.f_globals, + self.curframe.f_locals) + except: + func = arg + try: + if hasattr(func, 'im_func'): + func = func.im_func + code = func.func_code + #use co_name to identify the bkpt (function names + #could be aliased, but co_name is invariant) + funcname = code.co_name + lineno = code.co_firstlineno + filename = code.co_filename + except: + # last thing to try + (ok, filename, ln) = self.lineinfo(arg) + if not ok: + print >>self.stdout, '*** The specified object', + print >>self.stdout, repr(arg), + print >>self.stdout, 'is not a function' + print >>self.stdout, 'or was not found along sys.path.' + return + funcname = ok # ok contains a function name + lineno = int(ln) + if not filename: + filename = self.defaultFile() + # Check for reasonable breakpoint + line = self.checkline(filename, lineno) + if line: + # now set the break point + err = self.set_break(filename, line, temporary, cond, funcname) + if err: print >>self.stdout, '***', err + else: + bp = self.get_breaks(filename, line)[-1] + print >>self.stdout, "Breakpoint %d at %s:%d" % (bp.number, + bp.file, + bp.line) + + # To be overridden in derived debuggers + def defaultFile(self): + """Produce a reasonable default.""" + filename = self.curframe.f_code.co_filename + if filename == '' and self.mainpyfile: + filename = self.mainpyfile + return filename + + do_b = do_break + + def do_tbreak(self, arg): + self.do_break(arg, 1) + + def lineinfo(self, identifier): + failed = (None, None, None) + # Input is identifier, may be in single quotes + idstring = identifier.split("'") + if len(idstring) == 1: + # not in single quotes + id = idstring[0].strip() + elif len(idstring) == 3: + # quoted + id = idstring[1].strip() + else: + return failed + if id == '': return failed + parts = id.split('.') + # Protection for derived debuggers + if parts[0] == 'self': + del parts[0] + if len(parts) == 0: + return failed + # Best first guess at file to look at + fname = self.defaultFile() + if len(parts) == 1: + item = parts[0] + else: + # More than one part. + # First is module, second is method/class + f = self.lookupmodule(parts[0]) + if f: + fname = f + item = parts[1] + answer = find_function(item, fname) + return answer or failed + + def checkline(self, filename, lineno): + """Check whether specified line seems to be executable. + + Return `lineno` if it is, 0 if not (e.g. a docstring, comment, blank + line or EOF). Warning: testing is not comprehensive. + """ + line = linecache.getline(filename, lineno) + if not line: + print >>self.stdout, 'End of file' + return 0 + line = line.strip() + # Don't allow setting breakpoint at a blank line + if (not line or (line[0] == '#') or + (line[:3] == '"""') or line[:3] == "'''"): + print >>self.stdout, '*** Blank or comment' + return 0 + return lineno + + def do_enable(self, arg): + args = arg.split() + for i in args: + try: + i = int(i) + except ValueError: + print >>self.stdout, 'Breakpoint index %r is not a number' % i + continue + + if not (0 <= i < len(bdb.Breakpoint.bpbynumber)): + print >>self.stdout, 'No breakpoint numbered', i + continue + + bp = bdb.Breakpoint.bpbynumber[i] + if bp: + bp.enable() + + def do_disable(self, arg): + args = arg.split() + for i in args: + try: + i = int(i) + except ValueError: + print >>self.stdout, 'Breakpoint index %r is not a number' % i + continue + + if not (0 <= i < len(bdb.Breakpoint.bpbynumber)): + print >>self.stdout, 'No breakpoint numbered', i + continue + + bp = bdb.Breakpoint.bpbynumber[i] + if bp: + bp.disable() + + def do_condition(self, arg): + # arg is breakpoint number and condition + args = arg.split(' ', 1) + try: + bpnum = int(args[0].strip()) + except ValueError: + # something went wrong + print >>self.stdout, \ + 'Breakpoint index %r is not a number' % args[0] + return + try: + cond = args[1] + except: + cond = None + try: + bp = bdb.Breakpoint.bpbynumber[bpnum] + except IndexError: + print >>self.stdout, 'Breakpoint index %r is not valid' % args[0] + return + if bp: + bp.cond = cond + if not cond: + print >>self.stdout, 'Breakpoint', bpnum, + print >>self.stdout, 'is now unconditional.' + + def do_ignore(self,arg): + """arg is bp number followed by ignore count.""" + args = arg.split() + try: + bpnum = int(args[0].strip()) + except ValueError: + # something went wrong + print >>self.stdout, \ + 'Breakpoint index %r is not a number' % args[0] + return + try: + count = int(args[1].strip()) + except: + count = 0 + try: + bp = bdb.Breakpoint.bpbynumber[bpnum] + except IndexError: + print >>self.stdout, 'Breakpoint index %r is not valid' % args[0] + return + if bp: + bp.ignore = count + if count > 0: + reply = 'Will ignore next ' + if count > 1: + reply = reply + '%d crossings' % count + else: + reply = reply + '1 crossing' + print >>self.stdout, reply + ' of breakpoint %d.' % bpnum + else: + print >>self.stdout, 'Will stop next time breakpoint', + print >>self.stdout, bpnum, 'is reached.' + + def do_clear(self, arg): + """Three possibilities, tried in this order: + clear -> clear all breaks, ask for confirmation + clear file:lineno -> clear all breaks at file:lineno + clear bpno bpno ... -> clear breakpoints by number""" + if not arg: + try: + reply = raw_input('Clear all breaks? ') + except EOFError: + reply = 'no' + reply = reply.strip().lower() + if reply in ('y', 'yes'): + self.clear_all_breaks() + return + if ':' in arg: + # Make sure it works for "clear C:\foo\bar.py:12" + i = arg.rfind(':') + filename = arg[:i] + arg = arg[i+1:] + try: + lineno = int(arg) + except ValueError: + err = "Invalid line number (%s)" % arg + else: + err = self.clear_break(filename, lineno) + if err: print >>self.stdout, '***', err + return + numberlist = arg.split() + for i in numberlist: + try: + i = int(i) + except ValueError: + print >>self.stdout, 'Breakpoint index %r is not a number' % i + continue + + if not (0 <= i < len(bdb.Breakpoint.bpbynumber)): + print >>self.stdout, 'No breakpoint numbered', i + continue + err = self.clear_bpbynumber(i) + if err: + print >>self.stdout, '***', err + else: + print >>self.stdout, 'Deleted breakpoint', i + do_cl = do_clear # 'c' is already an abbreviation for 'continue' + + def do_where(self, arg): + self.print_stack_trace() + do_w = do_where + do_bt = do_where + + def do_up(self, arg): + if self.curindex == 0: + print >>self.stdout, '*** Oldest frame' + else: + self.curindex = self.curindex - 1 + self.curframe = self.stack[self.curindex][0] + self.print_stack_entry(self.stack[self.curindex]) + self.lineno = None + do_u = do_up + + def do_down(self, arg): + if self.curindex + 1 == len(self.stack): + print >>self.stdout, '*** Newest frame' + else: + self.curindex = self.curindex + 1 + self.curframe = self.stack[self.curindex][0] + self.print_stack_entry(self.stack[self.curindex]) + self.lineno = None + do_d = do_down + + def do_step(self, arg): + self.set_step() + return 1 + do_s = do_step + + def do_next(self, arg): + self.set_next(self.curframe) + return 1 + do_n = do_next + + def do_return(self, arg): + self.set_return(self.curframe) + return 1 + do_r = do_return + + def do_continue(self, arg): + self.set_continue() + return 1 + do_c = do_cont = do_continue + + def do_jump(self, arg): + if self.curindex + 1 != len(self.stack): + print >>self.stdout, "*** You can only jump within the bottom frame" + return + try: + arg = int(arg) + except ValueError: + print >>self.stdout, "*** The 'jump' command requires a line number." + else: + try: + # Do the jump, fix up our copy of the stack, and display the + # new position + self.curframe.f_lineno = arg + self.stack[self.curindex] = self.stack[self.curindex][0], arg + self.print_stack_entry(self.stack[self.curindex]) + except ValueError, e: + print >>self.stdout, '*** Jump failed:', e + do_j = do_jump + + def do_debug(self, arg): + sys.settrace(None) + globals = self.curframe.f_globals + locals = self.curframe.f_locals + p = Pdb(self.completekey, self.stdin, self.stdout) + p.prompt = "(%s) " % self.prompt.strip() + print >>self.stdout, "ENTERING RECURSIVE DEBUGGER" + sys.call_tracing(p.run, (arg, globals, locals)) + print >>self.stdout, "LEAVING RECURSIVE DEBUGGER" + sys.settrace(self.trace_dispatch) + self.lastcmd = p.lastcmd + + def do_quit(self, arg): + self._user_requested_quit = 1 + self.set_quit() + return 1 + + do_q = do_quit + do_exit = do_quit + + def do_EOF(self, arg): + print >>self.stdout + self._user_requested_quit = 1 + self.set_quit() + return 1 + + def do_args(self, arg): + f = self.curframe + co = f.f_code + dict = f.f_locals + n = co.co_argcount + if co.co_flags & 4: n = n+1 + if co.co_flags & 8: n = n+1 + for i in range(n): + name = co.co_varnames[i] + print >>self.stdout, name, '=', + if name in dict: print >>self.stdout, dict[name] + else: print >>self.stdout, "*** undefined ***" + do_a = do_args + + def do_retval(self, arg): + if '__return__' in self.curframe.f_locals: + print >>self.stdout, self.curframe.f_locals['__return__'] + else: + print >>self.stdout, '*** Not yet returned!' + do_rv = do_retval + + def _getval(self, arg): + try: + return eval(arg, self.curframe.f_globals, + self.curframe.f_locals) + except: + t, v = sys.exc_info()[:2] + if isinstance(t, str): + exc_type_name = t + else: exc_type_name = t.__name__ + print >>self.stdout, '***', exc_type_name + ':', repr(v) + raise + + def do_p(self, arg): + try: + print >>self.stdout, repr(self._getval(arg)) + except: + pass + + def do_pp(self, arg): + try: + pprint.pprint(self._getval(arg), self.stdout) + except: + pass + + def do_list(self, arg): + self.lastcmd = 'list' + last = None + if arg: + try: + x = eval(arg, {}, {}) + if type(x) == type(()): + first, last = x + first = int(first) + last = int(last) + if last < first: + # Assume it's a count + last = first + last + else: + first = max(1, int(x) - 5) + except: + print >>self.stdout, '*** Error in argument:', repr(arg) + return + elif self.lineno is None: + first = max(1, self.curframe.f_lineno - 5) + else: + first = self.lineno + 1 + if last is None: + last = first + 10 + filename = self.curframe.f_code.co_filename + breaklist = self.get_file_breaks(filename) + try: + for lineno in range(first, last+1): + line = linecache.getline(filename, lineno) + if not line: + print >>self.stdout, '[EOF]' + break + else: + s = repr(lineno).rjust(3) + if len(s) < 4: s = s + ' ' + if lineno in breaklist: s = s + 'B' + else: s = s + ' ' + if lineno == self.curframe.f_lineno: + s = s + '->' + print >>self.stdout, s + '\t' + line, + self.lineno = lineno + except KeyboardInterrupt: + pass + do_l = do_list + + def do_whatis(self, arg): + try: + value = eval(arg, self.curframe.f_globals, + self.curframe.f_locals) + except: + t, v = sys.exc_info()[:2] + if type(t) == type(''): + exc_type_name = t + else: exc_type_name = t.__name__ + print >>self.stdout, '***', exc_type_name + ':', repr(v) + return + code = None + # Is it a function? + try: code = value.func_code + except: pass + if code: + print >>self.stdout, 'Function', code.co_name + return + # Is it an instance method? + try: code = value.im_func.func_code + except: pass + if code: + print >>self.stdout, 'Method', code.co_name + return + # None of the above... + print >>self.stdout, type(value) + + def do_alias(self, arg): + args = arg.split() + if len(args) == 0: + keys = self.aliases.keys() + keys.sort() + for alias in keys: + print >>self.stdout, "%s = %s" % (alias, self.aliases[alias]) + return + if args[0] in self.aliases and len(args) == 1: + print >>self.stdout, "%s = %s" % (args[0], self.aliases[args[0]]) + else: + self.aliases[args[0]] = ' '.join(args[1:]) + + def do_unalias(self, arg): + args = arg.split() + if len(args) == 0: return + if args[0] in self.aliases: + del self.aliases[args[0]] + + #list of all the commands making the program resume execution. + commands_resuming = ['do_continue', 'do_step', 'do_next', 'do_return', + 'do_quit', 'do_jump'] + + # Print a traceback starting at the top stack frame. + # The most recently entered frame is printed last; + # this is different from dbx and gdb, but consistent with + # the Python interpreter's stack trace. + # It is also consistent with the up/down commands (which are + # compatible with dbx and gdb: up moves towards 'main()' + # and down moves towards the most recent stack frame). + + def print_stack_trace(self): + try: + for frame_lineno in self.stack: + self.print_stack_entry(frame_lineno) + except KeyboardInterrupt: + pass + + def print_stack_entry(self, frame_lineno, prompt_prefix=line_prefix): + frame, lineno = frame_lineno + if frame is self.curframe: + print >>self.stdout, '>', + else: + print >>self.stdout, ' ', + print >>self.stdout, self.format_stack_entry(frame_lineno, + prompt_prefix) + + + # Help methods (derived from pdb.doc) + + def help_help(self): + self.help_h() + + def help_h(self): + print >>self.stdout, """h(elp) +Without argument, print the list of available commands. +With a command name as argument, print help about that command +"help pdb" pipes the full documentation file to the $PAGER +"help exec" gives help on the ! command""" + + def help_where(self): + self.help_w() + + def help_w(self): + print >>self.stdout, """w(here) +Print a stack trace, with the most recent frame at the bottom. +An arrow indicates the "current frame", which determines the +context of most commands. 'bt' is an alias for this command.""" + + help_bt = help_w + + def help_down(self): + self.help_d() + + def help_d(self): + print >>self.stdout, """d(own) +Move the current frame one level down in the stack trace +(to a newer frame).""" + + def help_up(self): + self.help_u() + + def help_u(self): + print >>self.stdout, """u(p) +Move the current frame one level up in the stack trace +(to an older frame).""" + + def help_break(self): + self.help_b() + + def help_b(self): + print >>self.stdout, """b(reak) ([file:]lineno | function) [, condition] +With a line number argument, set a break there in the current +file. With a function name, set a break at first executable line +of that function. Without argument, list all breaks. If a second +argument is present, it is a string specifying an expression +which must evaluate to true before the breakpoint is honored. + +The line number may be prefixed with a filename and a colon, +to specify a breakpoint in another file (probably one that +hasn't been loaded yet). The file is searched for on sys.path; +the .py suffix may be omitted.""" + + def help_clear(self): + self.help_cl() + + def help_cl(self): + print >>self.stdout, "cl(ear) filename:lineno" + print >>self.stdout, """cl(ear) [bpnumber [bpnumber...]] +With a space separated list of breakpoint numbers, clear +those breakpoints. Without argument, clear all breaks (but +first ask confirmation). With a filename:lineno argument, +clear all breaks at that line in that file. + +Note that the argument is different from previous versions of +the debugger (in python distributions 1.5.1 and before) where +a linenumber was used instead of either filename:lineno or +breakpoint numbers.""" + + def help_tbreak(self): + print >>self.stdout, """tbreak same arguments as break, but breakpoint is +removed when first hit.""" + + def help_enable(self): + print >>self.stdout, """enable bpnumber [bpnumber ...] +Enables the breakpoints given as a space separated list of +bp numbers.""" + + def help_disable(self): + print >>self.stdout, """disable bpnumber [bpnumber ...] +Disables the breakpoints given as a space separated list of +bp numbers.""" + + def help_ignore(self): + print >>self.stdout, """ignore bpnumber count +Sets the ignore count for the given breakpoint number. A breakpoint +becomes active when the ignore count is zero. When non-zero, the +count is decremented each time the breakpoint is reached and the +breakpoint is not disabled and any associated condition evaluates +to true.""" + + def help_condition(self): + print >>self.stdout, """condition bpnumber str_condition +str_condition is a string specifying an expression which +must evaluate to true before the breakpoint is honored. +If str_condition is absent, any existing condition is removed; +i.e., the breakpoint is made unconditional.""" + + def help_step(self): + self.help_s() + + def help_s(self): + print >>self.stdout, """s(tep) +Execute the current line, stop at the first possible occasion +(either in a function that is called or in the current function).""" + + def help_next(self): + self.help_n() + + def help_n(self): + print >>self.stdout, """n(ext) +Continue execution until the next line in the current function +is reached or it returns.""" + + def help_return(self): + self.help_r() + + def help_r(self): + print >>self.stdout, """r(eturn) +Continue execution until the current function returns.""" + + def help_continue(self): + self.help_c() + + def help_cont(self): + self.help_c() + + def help_c(self): + print >>self.stdout, """c(ont(inue)) +Continue execution, only stop when a breakpoint is encountered.""" + + def help_jump(self): + self.help_j() + + def help_j(self): + print >>self.stdout, """j(ump) lineno +Set the next line that will be executed.""" + + def help_debug(self): + print >>self.stdout, """debug code +Enter a recursive debugger that steps through the code argument +(which is an arbitrary expression or statement to be executed +in the current environment).""" + + def help_list(self): + self.help_l() + + def help_l(self): + print >>self.stdout, """l(ist) [first [,last]] +List source code for the current file. +Without arguments, list 11 lines around the current line +or continue the previous listing. +With one argument, list 11 lines starting at that line. +With two arguments, list the given range; +if the second argument is less than the first, it is a count.""" + + def help_args(self): + self.help_a() + + def help_a(self): + print >>self.stdout, """a(rgs) +Print the arguments of the current function.""" + + def help_p(self): + print >>self.stdout, """p expression +Print the value of the expression.""" + + def help_pp(self): + print >>self.stdout, """pp expression +Pretty-print the value of the expression.""" + + def help_exec(self): + print >>self.stdout, """(!) statement +Execute the (one-line) statement in the context of +the current stack frame. +The exclamation point can be omitted unless the first word +of the statement resembles a debugger command. +To assign to a global variable you must always prefix the +command with a 'global' command, e.g.: +(Pdb) global list_options; list_options = ['-l'] +(Pdb)""" + + def help_quit(self): + self.help_q() + + def help_q(self): + print >>self.stdout, """q(uit) or exit - Quit from the debugger. +The program being executed is aborted.""" + + help_exit = help_q + + def help_whatis(self): + print >>self.stdout, """whatis arg +Prints the type of the argument.""" + + def help_EOF(self): + print >>self.stdout, """EOF +Handles the receipt of EOF as a command.""" + + def help_alias(self): + print >>self.stdout, """alias [name [command [parameter parameter ...] ]] +Creates an alias called 'name' the executes 'command'. The command +must *not* be enclosed in quotes. Replaceable parameters are +indicated by %1, %2, and so on, while %* is replaced by all the +parameters. If no command is given, the current alias for name +is shown. If no name is given, all aliases are listed. + +Aliases may be nested and can contain anything that can be +legally typed at the pdb prompt. Note! You *can* override +internal pdb commands with aliases! Those internal commands +are then hidden until the alias is removed. Aliasing is recursively +applied to the first word of the command line; all other words +in the line are left alone. + +Some useful aliases (especially when placed in the .pdbrc file) are: + +#Print instance variables (usage "pi classInst") +alias pi for k in %1.__dict__.keys(): print "%1.",k,"=",%1.__dict__[k] + +#Print instance variables in self +alias ps pi self +""" + + def help_unalias(self): + print >>self.stdout, """unalias name +Deletes the specified alias.""" + + def help_commands(self): + print >>self.stdout, """commands [bpnumber] +(com) ... +(com) end +(Pdb) + +Specify a list of commands for breakpoint number bpnumber. The +commands themselves appear on the following lines. Type a line +containing just 'end' to terminate the commands. + +To remove all commands from a breakpoint, type commands and +follow it immediately with end; that is, give no commands. + +With no bpnumber argument, commands refers to the last +breakpoint set. + +You can use breakpoint commands to start your program up again. +Simply use the continue command, or step, or any other +command that resumes execution. + +Specifying any command resuming execution (currently continue, +step, next, return, jump, quit and their abbreviations) terminates +the command list (as if that command was immediately followed by end). +This is because any time you resume execution +(even with a simple next or step), you may encounter +another breakpoint--which could have its own command list, leading to +ambiguities about which list to execute. + + If you use the 'silent' command in the command list, the +usual message about stopping at a breakpoint is not printed. This may +be desirable for breakpoints that are to print a specific message and +then continue. If none of the other commands print anything, you +see no sign that the breakpoint was reached. +""" + + def help_pdb(self): + help() + + def lookupmodule(self, filename): + """Helper function for break/clear parsing -- may be overridden. + + lookupmodule() translates (possibly incomplete) file or module name + into an absolute file name. + """ + if os.path.isabs(filename) and os.path.exists(filename): + return filename + f = os.path.join(sys.path[0], filename) + if os.path.exists(f) and self.canonic(f) == self.mainpyfile: + return f + root, ext = os.path.splitext(filename) + if ext == '': + filename = filename + '.py' + if os.path.isabs(filename): + return filename + for dirname in sys.path: + while os.path.islink(dirname): + dirname = os.readlink(dirname) + fullname = os.path.join(dirname, filename) + if os.path.exists(fullname): + return fullname + return None + + def _runscript(self, filename): + # Start with fresh empty copy of globals and locals and tell the script + # that it's being run as __main__ to avoid scripts being able to access + # the pdb.py namespace. + globals_ = {"__name__" : "__main__", "__file__" : filename} + locals_ = globals_ + + # When bdb sets tracing, a number of call and line events happens + # BEFORE debugger even reaches user's code (and the exact sequence of + # events depends on python version). So we take special measures to + # avoid stopping before we reach the main script (see user_line and + # user_call for details). + self._wait_for_mainpyfile = 1 + self.mainpyfile = self.canonic(filename) + self._user_requested_quit = 0 + statement = 'execfile( "%s")' % filename + self.run(statement, globals=globals_, locals=locals_) + +# Simplified interface + +def run(statement, globals=None, locals=None): + Pdb().run(statement, globals, locals) + +def runeval(expression, globals=None, locals=None): + return Pdb().runeval(expression, globals, locals) + +def runctx(statement, globals, locals): + # B/W compatibility + run(statement, globals, locals) + +def runcall(*args, **kwds): + return Pdb().runcall(*args, **kwds) + +def set_trace(): + Pdb().set_trace(sys._getframe().f_back) + +# Post-Mortem interface + +def post_mortem(t): + p = Pdb() + p.reset() + while t.tb_next is not None: + t = t.tb_next + p.interaction(t.tb_frame, t) + +def pm(): + post_mortem(sys.last_traceback) + + +# Main program for testing + +TESTCMD = 'import x; x.main()' + +def test(): + run(TESTCMD) + +# print help +def help(): + for dirname in sys.path: + fullname = os.path.join(dirname, 'pdb.doc') + if os.path.exists(fullname): + sts = os.system('${PAGER-more} '+fullname) + if sts: print '*** Pager exit status:', sts + break + else: + print 'Sorry, can\'t find the help file "pdb.doc"', + print 'along the Python search path' + +def main(): + if not sys.argv[1:]: + print "usage: pdb.py scriptfile [arg] ..." + sys.exit(2) + + mainpyfile = sys.argv[1] # Get script filename + if not os.path.exists(mainpyfile): + print 'Error:', mainpyfile, 'does not exist' + sys.exit(1) + + del sys.argv[0] # Hide "pdb.py" from argument list + + # Replace pdb's dir with script's dir in front of module search path. + sys.path[0] = os.path.dirname(mainpyfile) + + # Note on saving/restoring sys.argv: it's a good idea when sys.argv was + # modified by the script being debugged. It's a bad idea when it was + # changed by the user from the command line. The best approach would be to + # have a "restart" command which would allow explicit specification of + # command line arguments. + pdb = Pdb() + while 1: + try: + pdb._runscript(mainpyfile) + if pdb._user_requested_quit: + break + print "The program finished and will be restarted" + except SystemExit: + # In most cases SystemExit does not warrant a post-mortem session. + print "The program exited via sys.exit(). Exit status: ", + print sys.exc_info()[1] + except: + traceback.print_exc() + print "Uncaught exception. Entering post mortem debugging" + print "Running 'cont' or 'step' will restart the program" + t = sys.exc_info()[2] + while t.tb_next is not None: + t = t.tb_next + pdb.interaction(t.tb_frame,t) + print "Post mortem debugger finished. The "+mainpyfile+" will be restarted" + + +# When invoked as main program, invoke the debugger on a script +if __name__=='__main__': + main() Modified: py/branch/py-compat-2.5.2/py/compat/subprocess.py ============================================================================== --- py/trunk/py/compat/subprocess.py (original) +++ py/branch/py-compat-2.5.2/py/compat/subprocess.py Tue Sep 2 21:40:21 2008 @@ -109,7 +109,7 @@ This module also defines two shortcut functions: -call(*args, **kwargs): +call(*popenargs, **kwargs): Run command with arguments. Wait for command to complete, then return the returncode attribute. @@ -117,6 +117,15 @@ retcode = call(["ls", "-l"]) +check_call(*popenargs, **kwargs): + Run command with arguments. Wait for command to complete. If the + exit code was zero then return, otherwise raise + CalledProcessError. The CalledProcessError object will have the + return code in the returncode attribute. + + The arguments are the same as for the Popen constructor. Example: + + check_call(["ls", "-l"]) Exceptions ---------- @@ -132,6 +141,9 @@ A ValueError will be raised if Popen is called with invalid arguments. +check_call() will raise CalledProcessError, if the called process +returns a non-zero return code. + Security -------- @@ -154,7 +166,7 @@ communicate(input=None) Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to - terminate. The optional stdin argument should be a string to be + terminate. The optional input argument should be a string to be sent to the child process, or None, if no data should be sent to the child. @@ -222,7 +234,7 @@ sts = os.system("mycmd" + " myarg") ==> p = Popen("mycmd" + " myarg", shell=True) -sts = os.waitpid(p.pid, 0) +pid, sts = os.waitpid(p.pid, 0) Note: @@ -328,7 +340,7 @@ stdin=PIPE, stdout=PIPE, close_fds=True) (child_stdout, child_stdin) = (p.stdout, p.stdin) -The popen2.Popen3 and popen3.Popen4 basically works as subprocess.Popen, +The popen2.Popen3 and popen2.Popen4 basically works as subprocess.Popen, except that: * subprocess.Popen raises an exception if the execution fails @@ -346,6 +358,19 @@ import os import types import traceback +import gc + +# Exception classes used by this module. +class CalledProcessError(Exception): + """This exception is raised when a process run by check_call() returns + a non-zero exit status. The exit status will be stored in the + returncode attribute.""" + def __init__(self, returncode, cmd): + self.returncode = returncode + self.cmd = cmd + def __str__(self): + return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) + if mswindows: import threading @@ -378,7 +403,7 @@ import fcntl import pickle -__all__ = ["Popen", "PIPE", "STDOUT", "call"] +__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "CalledProcessError"] try: MAXFD = os.sysconf("SC_OPEN_MAX") @@ -396,13 +421,19 @@ def _cleanup(): for inst in _active[:]: - inst.poll() + if inst.poll(_deadstate=sys.maxint) >= 0: + try: + _active.remove(inst) + except ValueError: + # This can happen if two threads create a new Popen instance. + # It's harmless that it was already removed, so ignore. + pass PIPE = -1 STDOUT = -2 -def call(*args, **kwargs): +def call(*popenargs, **kwargs): """Run command with arguments. Wait for command to complete, then return the returncode attribute. @@ -410,7 +441,26 @@ retcode = call(["ls", "-l"]) """ - return Popen(*args, **kwargs).wait() + return Popen(*popenargs, **kwargs).wait() + + +def check_call(*popenargs, **kwargs): + """Run command with arguments. Wait for command to complete. If + the exit code was zero then return, otherwise raise + CalledProcessError. The CalledProcessError object will have the + return code in the returncode attribute. + + The arguments are the same as for the Popen constructor. Example: + + check_call(["ls", "-l"]) + """ + retcode = call(*popenargs, **kwargs) + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + if retcode: + raise CalledProcessError(retcode, cmd) + return retcode def list2cmdline(seq): @@ -450,7 +500,7 @@ if result: result.append(' ') - needquote = (" " in arg) or ("\t" in arg) + needquote = (" " in arg) or ("\t" in arg) or arg == "" if needquote: result.append('"') @@ -490,6 +540,7 @@ """Create new Popen instance.""" _cleanup() + self._child_created = False if not isinstance(bufsize, (int, long)): raise TypeError("bufsize must be an integer") @@ -542,6 +593,22 @@ c2pread, c2pwrite, errread, errwrite) + # On Windows, you cannot just redirect one or two handles: You + # either have to redirect all three or none. If the subprocess + # user has only redirected one or two handles, we are + # automatically creating PIPEs for the rest. We should close + # these after the process is started. See bug #1124861. + if mswindows: + if stdin is None and p2cwrite is not None: + os.close(p2cwrite) + p2cwrite = None + if stdout is None and c2pread is not None: + os.close(c2pread) + c2pread = None + if stderr is None and errread is not None: + os.close(errread) + errread = None + if p2cwrite: self.stdin = os.fdopen(p2cwrite, 'wb', bufsize) if c2pread: @@ -555,8 +622,6 @@ else: self.stderr = os.fdopen(errread, 'rb', bufsize) - _active.append(self) - def _translate_newlines(self, data): data = data.replace("\r\n", "\n") @@ -564,6 +629,45 @@ return data + def __del__(self, sys=sys): + if not self._child_created: + # We didn't get to successfully create a child process. + return + # In case the child hasn't been waited on, check if it's done. + self.poll(_deadstate=sys.maxint) + if self.returncode is None and _active is not None: + # Child is still running, keep us alive until we can wait on it. + _active.append(self) + + + def communicate(self, input=None): + """Interact with process: Send data to stdin. Read data from + stdout and stderr, until end-of-file is reached. Wait for + process to terminate. The optional input argument should be a + string to be sent to the child process, or None, if no data + should be sent to the child. + + communicate() returns a tuple (stdout, stderr).""" + + # Optimization: If we are only using one pipe, or no pipe at + # all, using select() or threads is unnecessary. + if [self.stdin, self.stdout, self.stderr].count(None) >= 2: + stdout = None + stderr = None + if self.stdin: + if input: + self.stdin.write(input) + self.stdin.close() + elif self.stdout: + stdout = self.stdout.read() + elif self.stderr: + stderr = self.stderr.read() + self.wait() + return (stdout, stderr) + + return self._communicate(input) + + if mswindows: # # Windows methods @@ -572,51 +676,57 @@ """Construct and return tupel with IO objects: p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite """ - if stdin == None and stdout == None and stderr == None: + if stdin is None and stdout is None and stderr is None: return (None, None, None, None, None, None) p2cread, p2cwrite = None, None c2pread, c2pwrite = None, None errread, errwrite = None, None - if stdin == None: + if stdin is None: p2cread = GetStdHandle(STD_INPUT_HANDLE) - elif stdin == PIPE: + if p2cread is not None: + pass + elif stdin is None or stdin == PIPE: p2cread, p2cwrite = CreatePipe(None, 0) # Detach and turn into fd p2cwrite = p2cwrite.Detach() p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0) - elif type(stdin) == types.IntType: + elif isinstance(stdin, int): p2cread = msvcrt.get_osfhandle(stdin) else: # Assuming file-like object p2cread = msvcrt.get_osfhandle(stdin.fileno()) p2cread = self._make_inheritable(p2cread) - if stdout == None: + if stdout is None: c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE) - elif stdout == PIPE: + if c2pwrite is not None: + pass + elif stdout is None or stdout == PIPE: c2pread, c2pwrite = CreatePipe(None, 0) # Detach and turn into fd c2pread = c2pread.Detach() c2pread = msvcrt.open_osfhandle(c2pread, 0) - elif type(stdout) == types.IntType: + elif isinstance(stdout, int): c2pwrite = msvcrt.get_osfhandle(stdout) else: # Assuming file-like object c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) c2pwrite = self._make_inheritable(c2pwrite) - if stderr == None: + if stderr is None: errwrite = GetStdHandle(STD_ERROR_HANDLE) - elif stderr == PIPE: + if errwrite is not None: + pass + elif stderr is None or stderr == PIPE: errread, errwrite = CreatePipe(None, 0) # Detach and turn into fd errread = errread.Detach() errread = msvcrt.open_osfhandle(errread, 0) elif stderr == STDOUT: errwrite = c2pwrite - elif type(stderr) == types.IntType: + elif isinstance(stderr, int): errwrite = msvcrt.get_osfhandle(stderr) else: # Assuming file-like object @@ -663,7 +773,7 @@ args = list2cmdline(args) # Process startup details - if startupinfo == None: + if startupinfo is None: startupinfo = STARTUPINFO() if None not in (p2cread, c2pwrite, errwrite): startupinfo.dwFlags |= STARTF_USESTDHANDLES @@ -712,6 +822,7 @@ raise WindowsError(*e.args) # Retain the process handle, but close the thread handle + self._child_created = True self._handle = hp self.pid = pid ht.Close() @@ -722,31 +833,29 @@ # output pipe are maintained in this process or else the # pipe will not close when the child process exits and the # ReadFile will hang. - if p2cread != None: + if p2cread is not None: p2cread.Close() - if c2pwrite != None: + if c2pwrite is not None: c2pwrite.Close() - if errwrite != None: + if errwrite is not None: errwrite.Close() - def poll(self): + def poll(self, _deadstate=None): """Check if child process has terminated. Returns returncode attribute.""" - if self.returncode == None: + if self.returncode is None: if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0: self.returncode = GetExitCodeProcess(self._handle) - _active.remove(self) return self.returncode def wait(self): """Wait for child process to terminate. Returns returncode attribute.""" - if self.returncode == None: + if self.returncode is None: obj = WaitForSingleObject(self._handle, INFINITE) self.returncode = GetExitCodeProcess(self._handle) - _active.remove(self) return self.returncode @@ -754,14 +863,7 @@ buffer.append(fh.read()) - def communicate(self, input=None): - """Interact with process: Send data to stdin. Read data from - stdout and stderr, until end-of-file is reached. Wait for - process to terminate. The optional input argument should be a - string to be sent to the child process, or None, if no data - should be sent to the child. - - communicate() returns a tuple (stdout, stderr).""" + def _communicate(self, input): stdout = None # Return stderr = None # Return @@ -779,7 +881,7 @@ stderr_thread.start() if self.stdin: - if input != None: + if input is not None: self.stdin.write(input) self.stdin.close() @@ -789,16 +891,16 @@ stderr_thread.join() # All data exchanged. Translate lists into strings. - if stdout != None: + if stdout is not None: stdout = stdout[0] - if stderr != None: + if stderr is not None: stderr = stderr[0] # Translate newlines, if requested. We cannot let the file # object do the translation: It is based on stdio, which is # impossible to combine with select (unless forcing no # buffering). - if self.universal_newlines and hasattr(open, 'newlines'): + if self.universal_newlines and hasattr(file, 'newlines'): if stdout: stdout = self._translate_newlines(stdout) if stderr: @@ -819,33 +921,33 @@ c2pread, c2pwrite = None, None errread, errwrite = None, None - if stdin == None: + if stdin is None: pass elif stdin == PIPE: p2cread, p2cwrite = os.pipe() - elif type(stdin) == types.IntType: + elif isinstance(stdin, int): p2cread = stdin else: # Assuming file-like object p2cread = stdin.fileno() - if stdout == None: + if stdout is None: pass elif stdout == PIPE: c2pread, c2pwrite = os.pipe() - elif type(stdout) == types.IntType: + elif isinstance(stdout, int): c2pwrite = stdout else: # Assuming file-like object c2pwrite = stdout.fileno() - if stderr == None: + if stderr is None: pass elif stderr == PIPE: errread, errwrite = os.pipe() elif stderr == STDOUT: errwrite = c2pwrite - elif type(stderr) == types.IntType: + elif isinstance(stderr, int): errwrite = stderr else: # Assuming file-like object @@ -886,11 +988,13 @@ if isinstance(args, types.StringTypes): args = [args] + else: + args = list(args) if shell: args = ["/bin/sh", "-c"] + args - if executable == None: + if executable is None: executable = args[0] # For transferring possible exec failure from child to parent @@ -899,7 +1003,17 @@ errpipe_read, errpipe_write = os.pipe() self._set_cloexec_flag(errpipe_write) - self.pid = os.fork() + gc_was_enabled = gc.isenabled() + # Disable gc to avoid bug where gc -> file_dealloc -> + # write to stderr -> hang. http://bugs.python.org/issue1336 + gc.disable() + try: + self.pid = os.fork() + except: + if gc_was_enabled: + gc.enable() + raise + self._child_created = True if self.pid == 0: # Child try: @@ -920,26 +1034,26 @@ if errwrite: os.dup2(errwrite, 2) - # Close pipe fds. Make sure we doesn't close the same - # fd more than once. - if p2cread: + # Close pipe fds. Make sure we don't close the same + # fd more than once, or standard fds. + if p2cread and p2cread not in (0,): os.close(p2cread) - if c2pwrite and c2pwrite not in (p2cread,): + if c2pwrite and c2pwrite not in (p2cread, 1): os.close(c2pwrite) - if errwrite and errwrite not in (p2cread, c2pwrite): + if errwrite and errwrite not in (p2cread, c2pwrite, 2): os.close(errwrite) # Close all other fds, if asked for if close_fds: self._close_fds(but=errpipe_write) - if cwd != None: + if cwd is not None: os.chdir(cwd) if preexec_fn: apply(preexec_fn) - if env == None: + if env is None: os.execvp(executable, args) else: os.execvpe(executable, args, env) @@ -958,6 +1072,8 @@ os._exit(255) # Parent + if gc_was_enabled: + gc.enable() os.close(errpipe_write) if p2cread and p2cwrite: os.close(p2cread) @@ -984,39 +1100,31 @@ # Should never happen raise RuntimeError("Unknown child exit status!") - _active.remove(self) - - def poll(self): + def poll(self, _deadstate=None): """Check if child process has terminated. Returns returncode attribute.""" - if self.returncode == None: + if self.returncode is None: try: pid, sts = os.waitpid(self.pid, os.WNOHANG) if pid == self.pid: self._handle_exitstatus(sts) except os.error: - pass + if _deadstate is not None: + self.returncode = _deadstate return self.returncode def wait(self): """Wait for child process to terminate. Returns returncode attribute.""" - if self.returncode == None: + if self.returncode is None: pid, sts = os.waitpid(self.pid, 0) self._handle_exitstatus(sts) return self.returncode - def communicate(self, input=None): - """Interact with process: Send data to stdin. Read data from - stdout and stderr, until end-of-file is reached. Wait for - process to terminate. The optional input argument should be a - string to be sent to the child process, or None, if no data - should be sent to the child. - - communicate() returns a tuple (stdout, stderr).""" + def _communicate(self, input): read_set = [] write_set = [] stdout = None # Return @@ -1037,6 +1145,7 @@ read_set.append(self.stderr) stderr = [] + input_offset = 0 while read_set or write_set: rlist, wlist, xlist = select.select(read_set, write_set, []) @@ -1044,9 +1153,9 @@ # When select has indicated that the file is writable, # we can write up to PIPE_BUF bytes without risk # blocking. POSIX defines PIPE_BUF >= 512 - bytes_written = os.write(self.stdin.fileno(), input[:512]) - input = input[bytes_written:] - if not input: + bytes_written = os.write(self.stdin.fileno(), buffer(input, input_offset, 512)) + input_offset += bytes_written + if input_offset >= len(input): self.stdin.close() write_set.remove(self.stdin) @@ -1065,16 +1174,16 @@ stderr.append(data) # All data exchanged. Translate lists into strings. - if stdout != None: + if stdout is not None: stdout = ''.join(stdout) - if stderr != None: + if stderr is not None: stderr = ''.join(stderr) # Translate newlines, if requested. We cannot let the file # object do the translation: It is based on stdio, which is # impossible to combine with select (unless forcing no # buffering). - if self.universal_newlines and hasattr(open, 'newlines'): + if self.universal_newlines and hasattr(file, 'newlines'): if stdout: stdout = self._translate_newlines(stdout) if stderr: Modified: py/branch/py-compat-2.5.2/py/compat/testing/test_doctest.py ============================================================================== --- py/trunk/py/compat/testing/test_doctest.py (original) +++ py/branch/py-compat-2.5.2/py/compat/testing/test_doctest.py Tue Sep 2 21:40:21 2008 @@ -3,15 +3,9 @@ """ from test import test_support +import doctest import warnings -import py -doctest = py.compat.doctest - -import sys -sys.modules['doctest'] = py.compat.doctest - - ###################################################################### ## Sample Objects (used by test cases) ###################################################################### @@ -514,21 +508,20 @@ >>> tests[1].name.split('.')[-1] in ['f', 'g'] True -Filter Functions -~~~~~~~~~~~~~~~~ -A filter function can be used to restrict which objects get examined, -but this is temporary, undocumented internal support for testmod's -deprecated isprivate gimmick. - - >>> def namefilter(prefix, base): - ... return base.startswith('a_') - >>> tests = doctest.DocTestFinder(_namefilter=namefilter).find(SampleClass) +Empty Tests +~~~~~~~~~~~ +By default, an object with no doctests doesn't create any tests: + + >>> tests = doctest.DocTestFinder().find(SampleClass) >>> for t in tests: ... print '%2s %s' % (len(t.examples), t.name) 3 SampleClass 3 SampleClass.NestedClass 1 SampleClass.NestedClass.__init__ 1 SampleClass.__init__ + 2 SampleClass.a_classmethod + 1 SampleClass.a_property + 1 SampleClass.a_staticmethod 1 SampleClass.double 1 SampleClass.get @@ -537,8 +530,7 @@ is really to support backward compatibility in what doctest.master.summarize() displays. - >>> tests = doctest.DocTestFinder(_namefilter=namefilter, - ... exclude_empty=False).find(SampleClass) + >>> tests = doctest.DocTestFinder(exclude_empty=False).find(SampleClass) >>> for t in tests: ... print '%2s %s' % (len(t.examples), t.name) 3 SampleClass @@ -547,35 +539,12 @@ 0 SampleClass.NestedClass.get 0 SampleClass.NestedClass.square 1 SampleClass.__init__ - 1 SampleClass.double - 1 SampleClass.get - -If a given object is filtered out, then none of the objects that it -contains will be added either: - - >>> def namefilter(prefix, base): - ... return base == 'NestedClass' - >>> tests = doctest.DocTestFinder(_namefilter=namefilter).find(SampleClass) - >>> tests.sort() - >>> for t in tests: - ... print '%2s %s' % (len(t.examples), t.name) - 3 SampleClass - 1 SampleClass.__init__ 2 SampleClass.a_classmethod 1 SampleClass.a_property 1 SampleClass.a_staticmethod 1 SampleClass.double 1 SampleClass.get -The filter function apply to contained objects, and *not* to the -object explicitly passed to DocTestFinder: - - >>> def namefilter(prefix, base): - ... return base == 'SampleClass' - >>> tests = doctest.DocTestFinder(_namefilter=namefilter).find(SampleClass) - >>> len(tests) - 9 - Turning off Recursion ~~~~~~~~~~~~~~~~~~~~~ DocTestFinder can be told not to look for tests in contained objects @@ -603,7 +572,7 @@ ... >>> for x in range(10): ... ... print x, ... 0 1 2 3 4 5 6 7 8 9 - ... >>> x/2 + ... >>> x//2 ... 6 ... ''' >>> test = doctest.DocTestFinder().find(f)[0] @@ -678,7 +647,7 @@ ... >>> x = 12 ... >>> print x ... 12 - ... >>> x/2 + ... >>> x//2 ... 6 ... ''' >>> test = doctest.DocTestFinder().find(f)[0] @@ -699,7 +668,7 @@ ... >>> x = 12 ... >>> print x ... 14 - ... >>> x/2 + ... >>> x//2 ... 6 ... ''' >>> test = doctest.DocTestFinder().find(f)[0] @@ -722,7 +691,7 @@ Got: 12 Trying: - x/2 + x//2 Expecting: 6 ok @@ -737,7 +706,7 @@ ... >>> x = 12 ... >>> print x ... 12 - ... >>> x/2 + ... >>> x//2 ... 6 ... ''' >>> test = doctest.DocTestFinder().find(f)[0] @@ -753,7 +722,7 @@ 12 ok Trying: - x/2 + x//2 Expecting: 6 ok @@ -783,7 +752,7 @@ 12 ok Trying: - x/2 + x//2 Expecting: 6 ok @@ -805,7 +774,7 @@ >>> def f(x): ... ''' ... >>> x = 12 - ... >>> print x/0 + ... >>> print x//0 ... Traceback (most recent call last): ... ZeroDivisionError: integer division or modulo by zero ... ''' @@ -821,7 +790,7 @@ >>> def f(x): ... ''' ... >>> x = 12 - ... >>> print 'pre-exception output', x/0 + ... >>> print 'pre-exception output', x//0 ... pre-exception output ... Traceback (most recent call last): ... ZeroDivisionError: integer division or modulo by zero @@ -832,7 +801,7 @@ ********************************************************************** File ..., line 4, in f Failed example: - print 'pre-exception output', x/0 + print 'pre-exception output', x//0 Exception raised: ... ZeroDivisionError: integer division or modulo by zero @@ -919,7 +888,7 @@ >>> def f(x): ... r''' - ... >>> 1/0 + ... >>> 1//0 ... 0 ... ''' >>> test = doctest.DocTestFinder().find(f)[0] @@ -928,7 +897,7 @@ ********************************************************************** File ..., line 3, in f Failed example: - 1/0 + 1//0 Exception raised: Traceback (most recent call last): ... @@ -1078,6 +1047,25 @@ ... # doctest: +NORMALIZE_WHITESPACE [0, 1, ..., 18, 19] +The SKIP flag causes an example to be skipped entirely. I.e., the +example is not run. It can be useful in contexts where doctest +examples serve as both documentation and test cases, and an example +should be included for documentation purposes, but should not be +checked (e.g., because its output is random, or depends on resources +which would be unavailable.) The SKIP flag can also be used for +'commenting out' broken examples. + + >>> import unavailable_resource # doctest: +SKIP + >>> unavailable_resource.do_something() # doctest: +SKIP + >>> unavailable_resource.blow_up() # doctest: +SKIP + Traceback (most recent call last): + ... + UncheckedBlowUpError: Nobody checks me. + + >>> import random + >>> print random.random() # doctest: +SKIP + 0.721216923889 + The REPORT_UDIFF flag causes failures that involve multi-line expected and actual outputs to be displayed using a unified diff: @@ -1578,11 +1566,11 @@ >>> try: doctest.debug_src(s) ... finally: sys.stdin = real_stdin - > (1)?() + > (1)() (Pdb) next 12 --Return-- - > (1)?()->None + > (1)()->None (Pdb) print x 12 (Pdb) continue @@ -1620,7 +1608,7 @@ >>> try: runner.run(test) ... finally: sys.stdin = real_stdin --Return-- - > (1)?()->None + > (1)()->None -> import pdb; pdb.set_trace() (Pdb) print x 42 @@ -1656,7 +1644,7 @@ (Pdb) print y 2 (Pdb) up - > (1)?() + > (1)() -> calls_set_trace() (Pdb) print x 1 @@ -1705,7 +1693,7 @@ [EOF] (Pdb) next --Return-- - > (1)?()->None + > (1)()->None -> f(3) (Pdb) list 1 -> f(3) @@ -1798,7 +1786,7 @@ (Pdb) print y 1 (Pdb) up - > (1)?() + > (1)() -> calls_set_trace() (Pdb) print foo *** NameError: name 'foo' is not defined @@ -1893,20 +1881,6 @@ modified the test globals, which are a copy of the sample_doctest module dictionary. The test globals are automatically cleared for us after a test. - - Finally, you can provide an alternate test finder. Here we'll - use a custom test_finder to to run just the test named bar. - However, the test in the module docstring, and the two tests - in the module __test__ dict, aren't filtered, so we actually - run three tests besides bar's. The filtering mechanisms are - poorly conceived, and will go away someday. - - >>> finder = doctest.DocTestFinder( - ... _namefilter=lambda prefix, base: base!='bar') - >>> suite = doctest.DocTestSuite('test.sample_doctest', - ... test_finder=finder) - >>> suite.run(unittest.TestResult()) - """ def test_DocFileSuite(): @@ -1917,9 +1891,10 @@ >>> import unittest >>> suite = doctest.DocFileSuite('test_doctest.txt', - ... 'test_doctest2.txt') + ... 'test_doctest2.txt', + ... 'test_doctest4.txt') >>> suite.run(unittest.TestResult()) - + The test files are looked for in the directory containing the calling module. A package keyword argument can be provided to @@ -1928,9 +1903,29 @@ >>> import unittest >>> suite = doctest.DocFileSuite('test_doctest.txt', ... 'test_doctest2.txt', + ... 'test_doctest4.txt', ... package='test') >>> suite.run(unittest.TestResult()) - + + + Support for using a package's __loader__.get_data() is also + provided. + + >>> import unittest, pkgutil, test + >>> added_loader = False + >>> if not hasattr(test, '__loader__'): + ... test.__loader__ = pkgutil.get_loader(test) + ... added_loader = True + >>> try: + ... suite = doctest.DocFileSuite('test_doctest.txt', + ... 'test_doctest2.txt', + ... 'test_doctest4.txt', + ... package='test') + ... suite.run(unittest.TestResult()) + ... finally: + ... if added_loader: + ... del test.__loader__ + '/' should be used as a path separator. It will be converted to a native separator at run time: @@ -1975,19 +1970,21 @@ >>> suite = doctest.DocFileSuite('test_doctest.txt', ... 'test_doctest2.txt', + ... 'test_doctest4.txt', ... globs={'favorite_color': 'blue'}) >>> suite.run(unittest.TestResult()) - + In this case, we supplied a missing favorite color. You can provide doctest options: >>> suite = doctest.DocFileSuite('test_doctest.txt', ... 'test_doctest2.txt', + ... 'test_doctest4.txt', ... optionflags=doctest.DONT_ACCEPT_BLANKLINE, ... globs={'favorite_color': 'blue'}) >>> suite.run(unittest.TestResult()) - + And, you can provide setUp and tearDown functions: @@ -2005,9 +2002,10 @@ >>> suite = doctest.DocFileSuite('test_doctest.txt', ... 'test_doctest2.txt', + ... 'test_doctest4.txt', ... setUp=setUp, tearDown=tearDown) >>> suite.run(unittest.TestResult()) - + But the tearDown restores sanity: @@ -2032,6 +2030,25 @@ modified the test globals. The test globals are automatically cleared for us after a test. + Tests in a file run using `DocFileSuite` can also access the + `__file__` global, which is set to the name of the file + containing the tests: + + >>> suite = doctest.DocFileSuite('test_doctest3.txt') + >>> suite.run(unittest.TestResult()) + + + If the tests contain non-ASCII characters, we have to specify which + encoding the file is encoded with. We do so by using the `encoding` + parameter: + + >>> suite = doctest.DocFileSuite('test_doctest.txt', + ... 'test_doctest2.txt', + ... 'test_doctest4.txt', + ... encoding='utf-8') + >>> suite.run(unittest.TestResult()) + + """ def test_trailing_space_in_test(): @@ -2238,6 +2255,32 @@ Traceback (most recent call last): UnexpectedException: ... >>> doctest.master = None # Reset master. + +If the tests contain non-ASCII characters, the tests might fail, since +it's unknown which encoding is used. The encoding can be specified +using the optional keyword argument `encoding`: + + >>> doctest.testfile('test_doctest4.txt') # doctest: +ELLIPSIS + ********************************************************************** + File "...", line 7, in test_doctest4.txt + Failed example: + u'...' + Expected: + u'f\xf6\xf6' + Got: + u'f\xc3\xb6\xc3\xb6' + ********************************************************************** + ... + ********************************************************************** + 1 items had failures: + 2 of 4 in test_doctest4.txt + ***Test Failed*** 2 failures. + (2, 4) + >>> doctest.master = None # Reset master. + + >>> doctest.testfile('test_doctest4.txt', encoding='utf-8') + (0, 4) + >>> doctest.master = None # Reset master. """ # old_test1, ... used to live in doctest.py, but cluttered it. Note Modified: py/branch/py-compat-2.5.2/py/compat/testing/test_doctest2.py ============================================================================== --- py/trunk/py/compat/testing/test_doctest2.py (original) +++ py/branch/py-compat-2.5.2/py/compat/testing/test_doctest2.py Tue Sep 2 21:40:21 2008 @@ -80,6 +80,7 @@ -12 """) + @staticmethod def statm(): """ A static method. @@ -91,8 +92,7 @@ """ return 666 - statm = staticmethod(statm) - + @classmethod def clsm(cls, val): """ A class method. @@ -104,10 +104,8 @@ """ return val - clsm = classmethod(clsm) - def test_main(): - from py.__.compat.testing import test_doctest2 + from test import test_doctest2 EXPECTED = 19 f, t = test_support.run_doctest(test_doctest2) if t != EXPECTED: @@ -116,7 +114,7 @@ # Pollute the namespace with a bunch of imported functions and classes, # to make sure they don't get tested. -from py.compat.doctest import * +from doctest import * if __name__ == '__main__': test_main() Modified: py/branch/py-compat-2.5.2/py/compat/testing/test_optparse.py ============================================================================== --- py/trunk/py/compat/testing/test_optparse.py (original) +++ py/branch/py-compat-2.5.2/py/compat/testing/test_optparse.py Tue Sep 2 21:40:21 2008 @@ -1,31 +1,31 @@ -#!/usr/bin/env python +#!/usr/bin/python # # Test suite for Optik. Supplied by Johannes Gijsbers # (taradino at softhome.net) -- translated from the original Optik # test suite to this PyUnit-based version. # -# $Id: test_optparse.py 46506 2006-05-28 18:15:43Z armin.rigo $ +# $Id: test_optparse.py 50791 2006-07-23 16:05:51Z greg.ward $ # import sys import os +import re import copy +import types import unittest -from cStringIO import StringIO +from StringIO import StringIO from pprint import pprint from test import test_support -import py -optparse = py.compat.optparse -import sys -sys.modules['optparse'] = optparse from optparse import make_option, Option, IndentedHelpFormatter, \ TitledHelpFormatter, OptionParser, OptionContainer, OptionGroup, \ SUPPRESS_HELP, SUPPRESS_USAGE, OptionError, OptionConflictError, \ - BadOptionError, OptionValueError, Values, _match_abbrev + BadOptionError, OptionValueError, Values +from optparse import _match_abbrev +from optparse import _parse_num # Do the right thing with boolean values for all known Python versions. try: @@ -33,6 +33,7 @@ except NameError: (True, False) = (1, 0) +retype = type(re.compile('')) class InterceptedError(Exception): def __init__(self, @@ -101,7 +102,8 @@ args -- positional arguments to `func` kwargs -- keyword arguments to `func` expected_exception -- exception that should be raised - expected_output -- output we expect to see + expected_message -- expected exception message (or pattern + if a compiled regex object) Returns the exception raised for further testing. """ @@ -114,14 +116,23 @@ func(*args, **kwargs) except expected_exception, err: actual_message = str(err) - self.assertEqual(actual_message, - expected_message, + if isinstance(expected_message, retype): + self.assert_(expected_message.search(actual_message), """\ +expected exception message pattern: +/%s/ +actual exception message: +'''%s''' +""" % (expected_message.pattern, actual_message)) + else: + self.assertEqual(actual_message, + expected_message, + """\ expected exception message: -'''%(expected_message)s''' +'''%s''' actual exception message: -'''%(actual_message)s''' -""" % locals()) +'''%s''' +""" % (expected_message, actual_message)) return err else: @@ -153,16 +164,26 @@ expected_error=None): """Assert the parser prints the expected output on stdout.""" save_stdout = sys.stdout + encoding = getattr(save_stdout, 'encoding', None) try: try: sys.stdout = StringIO() + if encoding: + sys.stdout.encoding = encoding self.parser.parse_args(cmdline_args) finally: output = sys.stdout.getvalue() sys.stdout = save_stdout except InterceptedError, err: - self.assertEqual(output, expected_output) + self.assert_( + type(output) is types.StringType, + "expected output to be an ordinary string, not %r" + % type(output)) + + if output != expected_output: + self.fail("expected: \n'''\n" + expected_output + + "'''\nbut got \n'''\n" + output + "'''") self.assertEqual(err.exit_status, expected_status) self.assertEqual(err.exit_message, expected_error) else: @@ -371,6 +392,23 @@ self.assertRaises(self.parser.remove_option, ('foo',), None, ValueError, "no such option 'foo'") + def test_refleak(self): + # If an OptionParser is carrying around a reference to a large + # object, various cycles can prevent it from being GC'd in + # a timely fashion. destroy() breaks the cycles to ensure stuff + # can be cleaned up. + big_thing = [42] + refcount = sys.getrefcount(big_thing) + parser = OptionParser() + parser.add_option("-a", "--aaarggh") + parser.big_thing = big_thing + + parser.destroy() + #self.assertEqual(refcount, sys.getrefcount(big_thing)) + del parser + self.assertEqual(refcount, sys.getrefcount(big_thing)) + + class TestOptionValues(BaseTest): def setUp(self): pass @@ -396,13 +434,21 @@ def setUp(self): self.parser = OptionParser() - def test_type_aliases(self): - self.parser.add_option("-x", type=int) + def test_str_aliases_string(self): + self.parser.add_option("-s", type="str") + self.assertEquals(self.parser.get_option("-s").type, "string") + + def test_new_type_object(self): self.parser.add_option("-s", type=str) - self.parser.add_option("-t", type="str") + self.assertEquals(self.parser.get_option("-s").type, "string") + self.parser.add_option("-x", type=int) self.assertEquals(self.parser.get_option("-x").type, "int") + + def test_old_type_object(self): + self.parser.add_option("-s", type=types.StringType) self.assertEquals(self.parser.get_option("-s").type, "string") - self.assertEquals(self.parser.get_option("-t").type, "string") + self.parser.add_option("-x", type=types.IntType) + self.assertEquals(self.parser.get_option("-x").type, "int") # Custom type for testing processing of default values. @@ -492,13 +538,13 @@ save_argv = sys.argv[:] try: sys.argv[0] = os.path.join("foo", "bar", "baz.py") - parser = OptionParser("usage: %prog ...", version="%prog 1.2") - expected_usage = "usage: baz.py ...\n" + parser = OptionParser("%prog ...", version="%prog 1.2") + expected_usage = "Usage: baz.py ...\n" self.assertUsage(parser, expected_usage) self.assertVersion(parser, "baz.py 1.2") self.assertHelp(parser, expected_usage + "\n" + - "options:\n" + "Options:\n" " --version show program's version number and exit\n" " -h, --help show this help message and exit\n") finally: @@ -510,7 +556,7 @@ usage="%prog arg arg") parser.remove_option("-h") parser.remove_option("--version") - expected_usage = "usage: thingy arg arg\n" + expected_usage = "Usage: thingy arg arg\n" self.assertUsage(parser, expected_usage) self.assertVersion(parser, "thingy 0.1") self.assertHelp(parser, expected_usage + "\n") @@ -520,9 +566,9 @@ def setUp(self): self.parser = OptionParser(prog="test") self.help_prefix = """\ -usage: test [options] +Usage: test [options] -options: +Options: -h, --help show this help message and exit """ self.file_help = "read from FILE [default: %default]" @@ -703,13 +749,16 @@ self.assertParseOK(["-a", "--", "foo", "bar"], {'a': "--", 'boo': None, 'foo': None}, ["foo", "bar"]), + self.assertParseOK(["-a", "--", "--foo", "bar"], + {'a': "--", 'boo': None, 'foo': ["bar"]}, + []), def test_short_option_joined_and_separator(self): self.assertParseOK(["-ab", "--", "--foo", "bar"], {'a': "b", 'boo': None, 'foo': None}, ["--foo", "bar"]), - def test_invalid_option_becomes_positional_arg(self): + def test_hyphen_becomes_positional_arg(self): self.assertParseOK(["-ab", "-", "--foo", "bar"], {'a': "b", 'boo': None, 'foo': ["bar"]}, ["-"]) @@ -874,6 +923,8 @@ type="float", dest="point") self.parser.add_option("-f", "--foo", action="append", nargs=2, type="int", dest="foo") + self.parser.add_option("-z", "--zero", action="append_const", + dest="foo", const=(0, 0)) def test_nargs_append(self): self.assertParseOK(["-f", "4", "-3", "blah", "--foo", "1", "666"], @@ -889,6 +940,11 @@ {'point': None, 'foo':[(3, 4)]}, []) + def test_nargs_append_const(self): + self.assertParseOK(["--zero", "--foo", "3", "4", "-z"], + {'point': None, 'foo':[(0, 0), (3, 4), (0, 0)]}, + []) + class TestVersion(BaseTest): def test_version(self): self.parser = InterceptingOptionParser(usage=SUPPRESS_USAGE, @@ -964,8 +1020,14 @@ self.parser.add_option("-a", None, type="string", dest="a") self.parser.add_option("-f", "--file", type="file", dest="file") + def tearDown(self): + if os.path.isdir(test_support.TESTFN): + os.rmdir(test_support.TESTFN) + elif os.path.isfile(test_support.TESTFN): + os.unlink(test_support.TESTFN) + class MyOption (Option): - def check_file (option, opt, value): + def check_file(option, opt, value): if not os.path.exists(value): raise OptionValueError("%s: file does not exist" % value) elif not os.path.isfile(value): @@ -976,25 +1038,23 @@ TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER) TYPE_CHECKER["file"] = check_file - def test_extend_file(self): + def test_filetype_ok(self): open(test_support.TESTFN, "w").close() self.assertParseOK(["--file", test_support.TESTFN, "-afoo"], {'file': test_support.TESTFN, 'a': 'foo'}, []) - os.unlink(test_support.TESTFN) - - def test_extend_file_nonexistent(self): + def test_filetype_noexist(self): self.assertParseFail(["--file", test_support.TESTFN, "-afoo"], "%s: file does not exist" % test_support.TESTFN) - def test_file_irregular(self): + def test_filetype_notfile(self): os.mkdir(test_support.TESTFN) self.assertParseFail(["--file", test_support.TESTFN, "-afoo"], "%s: not a regular file" % test_support.TESTFN) - os.rmdir(test_support.TESTFN) + class TestExtendAddActions(BaseTest): def setUp(self): @@ -1007,7 +1067,7 @@ STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",) TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",) - def take_action (self, action, dest, opt, value, values, parser): + def take_action(self, action, dest, opt, value, values, parser): if action == "extend": lvalue = value.split(",") values.ensure_value(dest, []).extend(lvalue) @@ -1076,7 +1136,7 @@ callback=lambda: None, type="string", help="foo") - expected_help = ("options:\n" + expected_help = ("Options:\n" " -t TEST, --test=TEST foo\n") self.assertHelp(parser, expected_help) @@ -1089,7 +1149,7 @@ dest="points", default=[])] self.parser = OptionParser(option_list=options) - def process_tuple (self, option, opt, value, parser_, len, type): + def process_tuple(self, option, opt, value, parser_, len, type): self.assertEqual(len, 3) self.assert_(type is int) @@ -1114,7 +1174,7 @@ self.parser = OptionParser(option_list=options) # Callback that meddles in rargs, largs - def process_n (self, option, opt, value, parser_): + def process_n(self, option, opt, value, parser_): # option is -3, -5, etc. nargs = int(opt[1:]) rargs = parser_.rargs @@ -1143,7 +1203,7 @@ callback=self.process_many, type="int")] self.parser = OptionParser(option_list=options) - def process_many (self, option, opt, value, parser_): + def process_many(self, option, opt, value, parser_): if opt == "-a": self.assertEqual(value, ("foo", "bar")) elif opt == "--apple": @@ -1166,7 +1226,7 @@ self.parser.add_option("--foo-bar", action="callback", callback=self.check_abbrev) - def check_abbrev (self, option, opt, value, parser): + def check_abbrev(self, option, opt, value, parser): self.assertEqual(opt, "--foo-bar") def test_abbrev_callback_expansion(self): @@ -1181,7 +1241,7 @@ self.parser = InterceptingOptionParser(usage=SUPPRESS_USAGE, option_list=options) - def variable_args (self, option, opt, value, parser): + def variable_args(self, option, opt, value, parser): self.assert_(value is None) done = 0 value = [] @@ -1233,7 +1293,7 @@ self.parser = InterceptingOptionParser(usage=SUPPRESS_USAGE, option_list=options) - def show_version (self, option, opt, value, parser): + def show_version(self, option, opt, value, parser): parser.values.show_version = 1 class TestConflict(ConflictBase): @@ -1284,7 +1344,7 @@ def test_conflict_resolve_help(self): self.assertOutput(["-h"], """\ -options: +Options: --verbose increment verbosity -h, --help show this help message and exit -v, --version show version @@ -1323,7 +1383,7 @@ def test_conflict_override_help(self): self.assertOutput(["-h"], """\ -options: +Options: -h, --help show this help message and exit -n, --dry-run dry run mode """) @@ -1336,9 +1396,9 @@ # -- Other testing. ---------------------------------------------------- _expected_help_basic = """\ -usage: bar.py [options] +Usage: bar.py [options] -options: +Options: -a APPLE throw APPLEs at basket -b NUM, --boo=NUM shout "boo!" NUM times (in order to frighten away all the evil spirits that cause trouble and mayhem) @@ -1347,9 +1407,9 @@ """ _expected_help_long_opts_first = """\ -usage: bar.py [options] +Usage: bar.py [options] -options: +Options: -a APPLE throw APPLEs at basket --boo=NUM, -b NUM shout "boo!" NUM times (in order to frighten away all the evil spirits that cause trouble and mayhem) @@ -1362,7 +1422,7 @@ ===== bar.py [options] -options +Options ======= -a APPLE throw APPLEs at basket --boo=NUM, -b NUM shout "boo!" NUM times (in order to frighten away all the @@ -1372,9 +1432,9 @@ """ _expected_help_short_lines = """\ -usage: bar.py [options] +Usage: bar.py [options] -options: +Options: -a APPLE throw APPLEs at basket -b NUM, --boo=NUM shout "boo!" NUM times (in order to frighten away all the evil spirits @@ -1400,10 +1460,26 @@ make_option("--foo", action="append", type="string", dest='foo', help="store FOO in the foo list for later fooing"), ] + + # We need to set COLUMNS for the OptionParser constructor, but + # we must restore its original value -- otherwise, this test + # screws things up for other tests when it's part of the Python + # test suite. + orig_columns = os.environ.get('COLUMNS') os.environ['COLUMNS'] = str(columns) - return InterceptingOptionParser(option_list=options) + try: + return InterceptingOptionParser(option_list=options) + finally: + if orig_columns is None: + del os.environ['COLUMNS'] + else: + os.environ['COLUMNS'] = orig_columns def assertHelpEquals(self, expected_output): + if type(expected_output) is types.UnicodeType: + encoding = self.parser._get_encoding(sys.stdout) + expected_output = expected_output.encode(encoding, "replace") + save_argv = sys.argv[:] try: # Make optparse believe bar.py is being executed. @@ -1416,7 +1492,7 @@ self.assertHelpEquals(_expected_help_basic) def test_help_old_usage(self): - self.parser.set_usage("usage: %prog [options]") + self.parser.set_usage("Usage: %prog [options]") self.assertHelpEquals(_expected_help_basic) def test_help_long_opts_first(self): @@ -1434,6 +1510,27 @@ self.parser = self.make_parser(60) self.assertHelpEquals(_expected_help_short_lines) + def test_help_unicode(self): + self.parser = InterceptingOptionParser(usage=SUPPRESS_USAGE) + self.parser.add_option("-a", action="store_true", help=u"ol\u00E9!") + expect = u"""\ +Options: + -h, --help show this help message and exit + -a ol\u00E9! +""" + self.assertHelpEquals(expect) + + def test_help_unicode_description(self): + self.parser = InterceptingOptionParser(usage=SUPPRESS_USAGE, + description=u"ol\u00E9!") + expect = u"""\ +ol\u00E9! + +Options: + -h, --help show this help message and exit +""" + self.assertHelpEquals(expect) + def test_help_description_groups(self): self.parser.set_description( "This is the program description for %prog. %prog has " @@ -1446,13 +1543,13 @@ group.add_option("-g", action="store_true", help="Group option.") self.parser.add_option_group(group) - self.assertHelpEquals("""\ -usage: bar.py [options] + expect = """\ +Usage: bar.py [options] This is the program description for bar.py. bar.py has an option group as well as single options. -options: +Options: -a APPLE throw APPLEs at basket -b NUM, --boo=NUM shout "boo!" NUM times (in order to frighten away all the evil spirits that cause trouble and mayhem) @@ -1464,9 +1561,12 @@ that some of them bite. -g Group option. -""") +""" + self.assertHelpEquals(expect) + self.parser.epilog = "Please report bugs to /dev/null." + self.assertHelpEquals(expect + "\nPlease report bugs to /dev/null.\n") class TestMatchAbbrev(BaseTest): @@ -1486,6 +1586,43 @@ BadOptionError, "ambiguous option: --f (--fie, --foo, --foz?)") +class TestParseNumber(BaseTest): + def setUp(self): + self.parser = InterceptingOptionParser() + self.parser.add_option("-n", type=int) + self.parser.add_option("-l", type=long) + + def test_parse_num_fail(self): + self.assertRaises( + _parse_num, ("", int), {}, + ValueError, + re.compile(r"invalid literal for int().*: '?'?")) + self.assertRaises( + _parse_num, ("0xOoops", long), {}, + ValueError, + re.compile(r"invalid literal for long().*: '?0xOoops'?")) + + def test_parse_num_ok(self): + self.assertEqual(_parse_num("0", int), 0) + self.assertEqual(_parse_num("0x10", int), 16) + self.assertEqual(_parse_num("0XA", long), 10L) + self.assertEqual(_parse_num("010", long), 8L) + self.assertEqual(_parse_num("0b11", int), 3) + self.assertEqual(_parse_num("0b", long), 0L) + + def test_numeric_options(self): + self.assertParseOK(["-n", "42", "-l", "0x20"], + { "n": 42, "l": 0x20 }, []) + self.assertParseOK(["-n", "0b0101", "-l010"], + { "n": 5, "l": 8 }, []) + self.assertParseFail(["-n008"], + "option -n: invalid integer value: '008'") + self.assertParseFail(["-l0b0123"], + "option -l: invalid long integer value: '0b0123'") + self.assertParseFail(["-l", "0x12x"], + "option -l: invalid long integer value: '0x12x'") + + def _testclasses(): mod = sys.modules[__name__] return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')] Modified: py/branch/py-compat-2.5.2/py/compat/testing/test_textwrap.py ============================================================================== --- py/trunk/py/compat/testing/test_textwrap.py (original) +++ py/branch/py-compat-2.5.2/py/compat/testing/test_textwrap.py Tue Sep 2 21:40:21 2008 @@ -5,18 +5,14 @@ # Converted to PyUnit by Peter Hansen . # Currently maintained by Greg Ward. # -# $Id: test_textwrap.py 38573 2005-03-05 02:38:33Z gward $ +# $Id: test_textwrap.py 46863 2006-06-11 19:42:51Z tim.peters $ # import unittest from test import test_support -import py -textwrap = py.compat.textwrap -TextWrapper = textwrap.TextWrapper -wrap = textwrap.wrap -fill = textwrap.fill -dedent = textwrap.dedent +from textwrap import TextWrapper, wrap, fill, dedent + class BaseTestCase(unittest.TestCase): '''Parent class with utility methods for textwrap tests.''' @@ -332,17 +328,18 @@ self.check_wrap(text, 30, [" This is a sentence with", "leading whitespace."]) - def test_unicode(self): - # *Very* simple test of wrapping Unicode strings. I'm sure - # there's more to it than this, but let's at least make - # sure textwrap doesn't crash on Unicode input! - text = u"Hello there, how are you today?" - self.check_wrap(text, 50, [u"Hello there, how are you today?"]) - self.check_wrap(text, 20, [u"Hello there, how are", "you today?"]) - olines = self.wrapper.wrap(text) - assert isinstance(olines, list) and isinstance(olines[0], unicode) - otext = self.wrapper.fill(text) - assert isinstance(otext, unicode) + if test_support.have_unicode: + def test_unicode(self): + # *Very* simple test of wrapping Unicode strings. I'm sure + # there's more to it than this, but let's at least make + # sure textwrap doesn't crash on Unicode input! + text = u"Hello there, how are you today?" + self.check_wrap(text, 50, [u"Hello there, how are you today?"]) + self.check_wrap(text, 20, [u"Hello there, how are", "you today?"]) + olines = self.wrapper.wrap(text) + assert isinstance(olines, list) and isinstance(olines[0], unicode) + otext = self.wrapper.fill(text) + assert isinstance(otext, unicode) def test_split(self): # Ensure that the standard _split() method works as advertised @@ -463,38 +460,42 @@ # of IndentTestCase! class DedentTestCase(unittest.TestCase): + def assertUnchanged(self, text): + """assert that dedent() has no effect on 'text'""" + self.assertEquals(text, dedent(text)) + def test_dedent_nomargin(self): # No lines indented. text = "Hello there.\nHow are you?\nOh good, I'm glad." - self.assertEquals(dedent(text), text) + self.assertUnchanged(text) # Similar, with a blank line. text = "Hello there.\n\nBoo!" - self.assertEquals(dedent(text), text) + self.assertUnchanged(text) # Some lines indented, but overall margin is still zero. text = "Hello there.\n This is indented." - self.assertEquals(dedent(text), text) + self.assertUnchanged(text) # Again, add a blank line. text = "Hello there.\n\n Boo!\n" - self.assertEquals(dedent(text), text) + self.assertUnchanged(text) def test_dedent_even(self): # All lines indented by two spaces. text = " Hello there.\n How are ya?\n Oh good." expect = "Hello there.\nHow are ya?\nOh good." - self.assertEquals(dedent(text), expect) + self.assertEquals(expect, dedent(text)) # Same, with blank lines. text = " Hello there.\n\n How are ya?\n Oh good.\n" expect = "Hello there.\n\nHow are ya?\nOh good.\n" - self.assertEquals(dedent(text), expect) + self.assertEquals(expect, dedent(text)) # Now indent one of the blank lines. text = " Hello there.\n \n How are ya?\n Oh good.\n" expect = "Hello there.\n\nHow are ya?\nOh good.\n" - self.assertEquals(dedent(text), expect) + self.assertEquals(expect, dedent(text)) def test_dedent_uneven(self): # Lines indented unevenly. @@ -508,18 +509,53 @@ while 1: return foo ''' - self.assertEquals(dedent(text), expect) + self.assertEquals(expect, dedent(text)) # Uneven indentation with a blank line. text = " Foo\n Bar\n\n Baz\n" expect = "Foo\n Bar\n\n Baz\n" - self.assertEquals(dedent(text), expect) + self.assertEquals(expect, dedent(text)) # Uneven indentation with a whitespace-only line. text = " Foo\n Bar\n \n Baz\n" expect = "Foo\n Bar\n\n Baz\n" - self.assertEquals(dedent(text), expect) + self.assertEquals(expect, dedent(text)) + # dedent() should not mangle internal tabs + def test_dedent_preserve_internal_tabs(self): + text = " hello\tthere\n how are\tyou?" + expect = "hello\tthere\nhow are\tyou?" + self.assertEquals(expect, dedent(text)) + + # make sure that it preserves tabs when it's not making any + # changes at all + self.assertEquals(expect, dedent(expect)) + + # dedent() should not mangle tabs in the margin (i.e. + # tabs and spaces both count as margin, but are *not* + # considered equivalent) + def test_dedent_preserve_margin_tabs(self): + text = " hello there\n\thow are you?" + self.assertUnchanged(text) + + # same effect even if we have 8 spaces + text = " hello there\n\thow are you?" + self.assertUnchanged(text) + + # dedent() only removes whitespace that can be uniformly removed! + text = "\thello there\n\thow are you?" + expect = "hello there\nhow are you?" + self.assertEquals(expect, dedent(text)) + + text = " \thello there\n \thow are you?" + self.assertEquals(expect, dedent(text)) + + text = " \t hello there\n \t how are you?" + self.assertEquals(expect, dedent(text)) + + text = " \thello there\n \t how are you?" + expect = "hello there\n how are you?" + self.assertEquals(expect, dedent(text)) def test_main(): Modified: py/branch/py-compat-2.5.2/py/compat/textwrap.py ============================================================================== --- py/trunk/py/compat/textwrap.py (original) +++ py/branch/py-compat-2.5.2/py/compat/textwrap.py Tue Sep 2 21:40:21 2008 @@ -317,41 +317,58 @@ # -- Loosely related functionality ------------------------------------- -def dedent(text): - """dedent(text : string) -> string +_whitespace_only_re = re.compile('^[ \t]+$', re.MULTILINE) +_leading_whitespace_re = re.compile('(^[ \t]*)(?:[^ \t\n])', re.MULTILINE) - Remove any whitespace than can be uniformly removed from the left - of every line in `text`. +def dedent(text): + """Remove any common leading whitespace from every line in `text`. - This can be used e.g. to make triple-quoted strings line up with - the left edge of screen/whatever, while still presenting it in the - source code in indented form. - - For example: - - def test(): - # end first line with \ to avoid the empty line! - s = '''\ - hello - world - ''' - print repr(s) # prints ' hello\n world\n ' - print repr(dedent(s)) # prints 'hello\n world\n' + This can be used to make triple-quoted strings line up with the left + edge of the display, while still presenting them in the source code + in indented form. + + Note that tabs and spaces are both treated as whitespace, but they + are not equal: the lines " hello" and "\thello" are + considered to have no common leading whitespace. (This behaviour is + new in Python 2.5; older versions of this module incorrectly + expanded tabs before searching for common leading whitespace.) """ - lines = text.expandtabs().split('\n') + # Look for the longest leading string of spaces and tabs common to + # all lines. margin = None - for line in lines: - content = line.lstrip() - if not content: - continue - indent = len(line) - len(content) + text = _whitespace_only_re.sub('', text) + indents = _leading_whitespace_re.findall(text) + for indent in indents: if margin is None: margin = indent - else: - margin = min(margin, indent) - if margin is not None and margin > 0: - for i in range(len(lines)): - lines[i] = lines[i][margin:] + # Current line more deeply indented than previous winner: + # no change (previous winner is still on top). + elif indent.startswith(margin): + pass + + # Current line consistent with and no deeper than previous winner: + # it's the new winner. + elif margin.startswith(indent): + margin = indent + + # Current line and previous winner have no common whitespace: + # there is no margin. + else: + margin = "" + break - return '\n'.join(lines) + # sanity check (testing/debugging only) + if 0 and margin: + for line in text.split("\n"): + assert not line or line.startswith(margin), \ + "line = %r, margin = %r" % (line, margin) + + if margin: + text = re.sub(r'(?m)^' + margin, '', text) + return text + +if __name__ == "__main__": + #print dedent("\tfoo\n\tbar") + #print dedent(" \thello there\n \t how are you?") + print dedent("Hello there.\n This is indented.") Deleted: /py/trunk/py/test/dsession/testing/basetest.py ============================================================================== --- /py/trunk/py/test/dsession/testing/basetest.py Tue Sep 2 21:40:21 2008 +++ (empty file) @@ -1,32 +0,0 @@ - -""" Support module for running tests -""" - -import py -from py.__.test.testing.setupdata import getexamplefile - -class DirSetup(object): - def setup_method(self, method): - name = "%s.%s" %(self.__class__.__name__, method.func_name) - self.tmpdir = py.test.ensuretemp(name) - self.source = self.tmpdir.ensure("source", dir=1) - self.dest = self.tmpdir.join("dest") - - -class BasicRsessionTest(object): - def setup_class(cls): - path = getexamplefile("test_funcexamples.py") - cls.config = py.test.config._reparse([path.dirpath()]) - cls.modulecol = cls.config.getfsnode(path) - - def setup_method(self, method): - self.session = self.config.initsession() - - def getfunc(self, name): - funcname = "test_func" + name - col = self.modulecol.join(funcname) - assert col is not None, funcname - return col - - def getdocexample(self): - return getexamplefile("docexample.txt") Deleted: /py/trunk/py/test/testing/setupdata.py ============================================================================== --- /py/trunk/py/test/testing/setupdata.py Tue Sep 2 21:40:21 2008 +++ (empty file) @@ -1,173 +0,0 @@ -import py - -#def setup_module(mod): -# mod.datadir = setupdatadir() -# mod.tmpdir = py.test.ensuretemp(mod.__name__) - -#def setupdatadir(): -# datadir = py.test.ensuretemp("datadir") -# for name in namecontent: -# getexamplefile(name) -# return datadir - -def getexamplefile(basename, tmpdir=None): - if tmpdir is None: - tmpdir = py.test.ensuretemp("example") - tmpdir.ensure("__init__.py") - path = tmpdir.join(basename) - if not path.check(): - path.write(py.code.Source(namecontent[basename])) - print "creating testfile", path - return path - -def getexamplecollector(names, tmpdir=None): - fn = getexamplefile(names[0], tmpdir=tmpdir) - config = py.test.config._reparse([fn.dirpath()]) - col = config.getfsnode(fn) - return col._getitembynames(names[1:]) - -namecontent = { - 'syntax_error.py': "this is really not python\n", - - 'disabled_module.py': ''' - disabled = True - - def setup_module(mod): - raise ValueError - - class TestClassOne: - def test_func(self): - raise ValueError - - class TestClassTwo: - def setup_class(cls): - raise ValueError - def test_func(self): - raise ValueError - ''', - - 'brokenrepr.py': ''' - import py - - class BrokenRepr1: - """A broken class with lots of broken methods. Let's try to make the test framework - immune to these.""" - foo=0 - def __repr__(self): - raise Exception("Ha Ha fooled you, I'm a broken repr().") - - class BrokenRepr2: - """A broken class with lots of broken methods. Let's try to make the test framework - immune to these.""" - foo=0 - def __repr__(self): - raise "Ha Ha fooled you, I'm a broken repr()." - - - class TestBrokenClass: - - def test_explicit_bad_repr(self): - t = BrokenRepr1() - py.test.raises(Exception, 'repr(t)') - - def test_implicit_bad_repr1(self): - t = BrokenRepr1() - assert t.foo == 1 - - def test_implicit_bad_repr2(self): - t = BrokenRepr2() - assert t.foo == 1 - ''', - - 'failingimport.py': "import gruetzelmuetzel\n", - - 'test_mod.py': """ - class TestA: - def test_m1(self): - pass - def test_f1(): - pass - def test_g1(): - yield lambda x: None, 42 - """, - - 'file_test.py': """ - def test_one(): - assert 42 == 43 - - class TestClass(object): - def test_method_one(self): - assert 42 == 43 - - """, - - 'test_threepass.py': """ - def test_one(): - assert 1 - - def test_two(): - assert 1 - - def test_three(): - assert 1 - """, - - 'testspecial_importerror.py': """ - - import asdasd - - """, - - 'disabled.py': """ - class TestDisabled: - disabled = True - def test_method(self): - pass - """, - - 'test_funcexamples.py': """ - import py - import time - def test_funcpassed(): - pass - - def test_funcfailed(): - raise AssertionError("hello world") - - def test_funcskipped(): - py.test.skip("skipped") - - def test_funcprint(): - print "samfing" - - def test_funcprinterr(): - print >>py.std.sys.stderr, "samfing" - - def test_funcprintfail(): - print "samfing elz" - asddsa - - def test_funcexplicitfail(): - py.test.fail("3") - - def test_funcraisesfails(): - py.test.raises(ValueError, lambda: 123) - - def test_funcoptioncustom(): - assert py.test.config.getvalue("custom") - - def test_funchang(): - import time - time.sleep(1000) - - def test_funckill15(): - import os - os.kill(os.getpid(), 15) - """, - - 'docexample.txt': """ - Aha!!!!!! - ========= - """, - -} Deleted: /py/trunk/py/test/testing/test_repevent.py ============================================================================== --- /py/trunk/py/test/testing/test_repevent.py Tue Sep 2 21:40:21 2008 +++ (empty file) @@ -1,30 +0,0 @@ - -from py.__.test import event -import setupdata, suptest -from py.__.code.testing.test_excinfo import TWMock - - - -class TestItemTestReport(object): - - def test_toterminal(self): - sorter = suptest.events_run_example("file_test.py") - reports = sorter.get(event.ItemTestReport) - ev = reports[0] - assert ev.failed - twmock = TWMock() - ev.toterminal(twmock) - assert twmock.lines - twmock = TWMock() - ev.outcome.longrepr = "hello" - ev.toterminal(twmock) - assert twmock.lines[0] == "hello" - assert not twmock.lines[1:] - - ##assert ev.repr_run.find("AssertionError") != -1 - filepath = ev.colitem.fspath - #filepath , modpath = ev.itemrepr_path - assert str(filepath).endswith("file_test.py") - #assert modpath.endswith("file_test.test_one") - - From arigo at codespeak.net Wed Sep 3 10:15:46 2008 From: arigo at codespeak.net (arigo at codespeak.net) Date: Wed, 3 Sep 2008 10:15:46 +0200 (CEST) Subject: [py-svn] r57790 - in py/trunk/py: code test test/report test/report/testing Message-ID: <20080903081546.416E8169F75@codespeak.net> Author: arigo Date: Wed Sep 3 10:15:44 2008 New Revision: 57790 Modified: py/trunk/py/code/excinfo.py py/trunk/py/test/collect.py py/trunk/py/test/report/terminal.py py/trunk/py/test/report/testing/test_terminal.py Log: Reintroduce the --tb option. Add test. Modified: py/trunk/py/code/excinfo.py ============================================================================== --- py/trunk/py/code/excinfo.py (original) +++ py/trunk/py/code/excinfo.py Wed Sep 3 10:15:44 2008 @@ -243,9 +243,10 @@ def toterminal(self, tw): sepok = False for entry in self.reprentries: - if sepok and self.style == "long": - tw.sep(self.entrysep) - tw.line("") + if self.style == "long": + if sepok: + tw.sep(self.entrysep) + tw.line("") sepok = True entry.toterminal(tw) if self.extraline: Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Wed Sep 3 10:15:44 2008 @@ -268,7 +268,8 @@ def _repr_failure_py(self, excinfo, outerr): excinfo.traceback = self._prunetraceback(excinfo.traceback) repr = excinfo.getrepr(funcargs=True, - showlocals=self._config.option.showlocals) + showlocals=self._config.option.showlocals, + style=self._config.option.tbstyle) for secname, content in zip(["out", "err"], outerr): if content: repr.addsection("Captured std%s" % secname, content.rstrip()) Modified: py/trunk/py/test/report/terminal.py ============================================================================== --- py/trunk/py/test/report/terminal.py (original) +++ py/trunk/py/test/report/terminal.py Wed Sep 3 10:15:44 2008 @@ -156,7 +156,7 @@ # def summary_failures(self): - if self._failed: + if self._failed and self.config.option.tbstyle != "no": self.write_sep("=", "FAILURES") for ev in self._failed: self.write_sep("_") Modified: py/trunk/py/test/report/testing/test_terminal.py ============================================================================== --- py/trunk/py/test/report/testing/test_terminal.py (original) +++ py/trunk/py/test/report/testing/test_terminal.py Wed Sep 3 10:15:44 2008 @@ -161,3 +161,34 @@ "*waiting*", "*%s*" % (modcol._config.topdir), ]) + + def test_tb_option(self): + for tbopt in ["no", "short", "long"]: + print 'testing --tb=%s...' % tbopt + modcol = self.getmodulecol(""" + import py + def g(): + raise IndexError + def test_func(): + print 6*7 + g() # --calling-- + """, configargs=("--tb=%s" % tbopt,), withsession=True) + stringio = py.std.cStringIO.StringIO() + rep = TerminalReporter(modcol._config, file=stringio) + rep.processevent(event.TestrunStart()) + for item in self.session.genitems([modcol]): + ev = basic_run_report(item) + rep.processevent(ev) + rep.processevent(event.TestrunFinish()) + s = popvalue(stringio) + if tbopt == "long": + assert 'print 6*7' in s + else: + assert 'print 6*7' not in s + if tbopt != "no": + assert '--calling--' in s + assert 'IndexError' in s + else: + assert 'FAILURES' not in s + assert '--calling--' not in s + assert 'IndexError' not in s From hpk at codespeak.net Fri Sep 5 11:03:02 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 5 Sep 2008 11:03:02 +0200 (CEST) Subject: [py-svn] r57831 - in py/trunk/py/path: local/testing svn/testing Message-ID: <20080905090302.224DA169F23@codespeak.net> Author: hpk Date: Fri Sep 5 11:02:58 2008 New Revision: 57831 Modified: py/trunk/py/path/local/testing/test_local.py py/trunk/py/path/local/testing/test_posix.py py/trunk/py/path/svn/testing/test_wccommand.py Log: small test cleanups while prepping for pycon uk slides Modified: py/trunk/py/path/local/testing/test_local.py ============================================================================== --- py/trunk/py/path/local/testing/test_local.py (original) +++ py/trunk/py/path/local/testing/test_local.py Fri Sep 5 11:02:58 2008 @@ -1,7 +1,6 @@ import py import sys from py.path import local -from py.__.path.common import checker from py.__.path.testing.fscommon import CommonFSTests, setuptestfs class LocalSetup: @@ -343,7 +342,6 @@ def test_pypkgdir(): datadir = py.test.ensuretemp("pypkgdir") - pkg = datadir.ensure('pkg1', dir=1) pkg.ensure("__init__.py") pkg.ensure("subdir/__init__.py") @@ -354,12 +352,3 @@ homedir = py.path.local._gethomedir() assert homedir.check(dir=1) -#class XTestLocalPath(TestLocalPath): -# def __init__(self): -# TestLocalPath.__init__(self) -# self.root = local(self.root) -# -#class XXTestLocalPath(TestLocalPath): -# def __init__(self): -# TestLocalPath.__init__(self) -# self.root = local(self.root) Modified: py/trunk/py/path/local/testing/test_posix.py ============================================================================== --- py/trunk/py/path/local/testing/test_posix.py (original) +++ py/trunk/py/path/local/testing/test_posix.py Fri Sep 5 11:02:58 2008 @@ -1,7 +1,6 @@ import py class TestPOSIXLocalPath: - #root = local(TestLocalPath.root) disabled = py.std.sys.platform == 'win32' def setup_class(cls): @@ -103,16 +102,17 @@ assert gid == stat.gid assert group == stat.group - def XXXtest_atime(self): - # XXX disabled. this test is just not platform independent enough - # because acesstime resolution is very different through - # filesystems even on one platform. + def test_atime(self): import time - path = self.root.join('samplefile') - atime = path.atime() - time.sleep(1) - path.read(1) - assert path.atime() != atime + path = self.root.ensure('samplefile') + now = time.time() + atime1 = path.atime() + # we could wait here but timer resolution is very + # system dependent + path.read() + atime2 = path.atime() + duration = time.time() - now + assert (atime2-atime1) <= duration def test_commondir(self): # XXX This is here in local until we find a way to implement this Modified: py/trunk/py/path/svn/testing/test_wccommand.py ============================================================================== --- py/trunk/py/path/svn/testing/test_wccommand.py (original) +++ py/trunk/py/path/svn/testing/test_wccommand.py Fri Sep 5 11:02:58 2008 @@ -6,9 +6,6 @@ from py.__.path.svn import svncommon from py.__.conftest import option -if py.path.local.sysfind('svn') is None: - py.test.skip("cannot test py.path.svn, 'svn' binary not found") - if sys.platform != 'win32': def normpath(p): return p @@ -24,8 +21,11 @@ p = win32api.GetShortPathName(p) return os.path.normpath(os.path.normcase(p)) -class TestWCSvnCommandPath(CommonSvnTests): +def setup_module(mod): + if py.path.local.sysfind('svn') is None: + py.test.skip("cannot test py.path.svn, 'svn' binary not found") +class TestWCSvnCommandPath(CommonSvnTests): def setup_class(cls): repo, cls.root = getrepowc() From hpk at codespeak.net Fri Sep 5 11:28:26 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 5 Sep 2008 11:28:26 +0200 (CEST) Subject: [py-svn] r57833 - py/trunk/py/path/local/testing Message-ID: <20080905092826.504691684C1@codespeak.net> Author: hpk Date: Fri Sep 5 11:28:25 2008 New Revision: 57833 Modified: py/trunk/py/path/local/testing/test_local.py Log: simplify test setup Modified: py/trunk/py/path/local/testing/test_local.py ============================================================================== --- py/trunk/py/path/local/testing/test_local.py (original) +++ py/trunk/py/path/local/testing/test_local.py Fri Sep 5 11:28:25 2008 @@ -10,10 +10,7 @@ setuptestfs(cls.root) def setup_method(self, method): - self.tmpdir = self.root.ensure('tmpdir', dir=1) - - def teardown_method(self, method): - self.tmpdir.remove(rec=1) + self.tmpdir = self.root.mkdir(method.__name__) class TestLocalPath(LocalSetup, CommonFSTests): From hpk at codespeak.net Fri Sep 5 12:07:39 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 5 Sep 2008 12:07:39 +0200 (CEST) Subject: [py-svn] r57834 - in py/trunk/py/test: . testing Message-ID: <20080905100739.1CBE6169E1F@codespeak.net> Author: hpk Date: Fri Sep 5 12:07:36 2008 New Revision: 57834 Modified: py/trunk/py/test/pycollect.py py/trunk/py/test/testing/test_deprecated_api.py Log: try to accomodate the fact that some conftests like the Prolog-test ones subclass Module but do not actually have an underlying python file. Nowadays, they should subclass "py.test.collect.File". Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Fri Sep 5 12:07:36 2008 @@ -149,7 +149,7 @@ class Module(py.test.collect.File, PyCollectorMixin): def collect(self): - if getattr(self.obj, 'disabled', 0): + if self.fspath.ext == ".py" and getattr(self.obj, 'disabled', 0): return [] return super(Module, self).collect() Modified: py/trunk/py/test/testing/test_deprecated_api.py ============================================================================== --- py/trunk/py/test/testing/test_deprecated_api.py (original) +++ py/trunk/py/test/testing/test_deprecated_api.py Fri Sep 5 12:07:36 2008 @@ -135,3 +135,20 @@ modcol = self.getmodulecol("def test_some(): pass") colitems = py.test.deprecated_call(modcol.collect) funcitem = colitems[0] + + def test_conftest_subclasses_Module_with_non_pyfile(self): + self.makepyfile(conftest=""" + import py + class Module(py.test.collect.Module): + def run(self): + return [] + class Directory(py.test.collect.Directory): + def consider_file(self, path, usefilters=True): + if path.basename == "testme.xxx": + return Module(path, parent=self) + return super(Directory, self).consider_file(path, usefilters=usefilters) + """) + testme = self._makefile('xxx', testme="hello") + config = self.parseconfig(testme) + col = config.getfsnode(testme) + assert col.collect() == [] From hpk at codespeak.net Fri Sep 5 12:08:48 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 5 Sep 2008 12:08:48 +0200 (CEST) Subject: [py-svn] r57835 - in py/extradoc/talk/pycon-uk-2008: . example ui Message-ID: <20080905100848.CD728169FA4@codespeak.net> Author: hpk Date: Fri Sep 5 12:08:46 2008 New Revision: 57835 Added: py/extradoc/talk/pycon-uk-2008/example/ py/extradoc/talk/pycon-uk-2008/example/readme.txt (contents, props changed) py/extradoc/talk/pycon-uk-2008/example/test_doc.txt (contents, props changed) py/extradoc/talk/pycon-uk-2008/proposal-pytest.txt - copied unchanged from r57702, py/extradoc/talk/pycon-uk-2008/pytest.txt py/extradoc/talk/pycon-uk-2008/pytest.txt - copied, changed from r57702, py/extradoc/talk/ep2008/pytest.txt py/extradoc/talk/pycon-uk-2008/ui/ - copied from r57702, py/extradoc/talk/ep2008/ui/ Log: snapshot pycon slides Added: py/extradoc/talk/pycon-uk-2008/example/readme.txt ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-uk-2008/example/readme.txt Fri Sep 5 12:08:46 2008 @@ -0,0 +1,6 @@ + +the examples here only work with the 0.9.2 release +for py/trunk and the upcoming 1.0 release please +see http://codespeak.net/svn/py/trunk/contrib +and subscribe to http://codespeak.net/mailman/listinfo/py-dev +for more info. Added: py/extradoc/talk/pycon-uk-2008/example/test_doc.txt ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-uk-2008/example/test_doc.txt Fri Sep 5 12:08:46 2008 @@ -0,0 +1,6 @@ + +this is an example doc test for the py.test tutorial + +>>> x = 3 +>>> print x +3 Copied: py/extradoc/talk/pycon-uk-2008/pytest.txt (from r57702, py/extradoc/talk/ep2008/pytest.txt) ============================================================================== --- py/extradoc/talk/ep2008/pytest.txt (original) +++ py/extradoc/talk/pycon-uk-2008/pytest.txt Fri Sep 5 12:08:46 2008 @@ -5,16 +5,27 @@ ================================================================= :author: Holger Krekel, merlinux GmbH -:event: 7.7.2008, EuroPython 2008, Vilnius +:event: 13.9.2008, Pycon UK 2008, Birmingham + +my background +=============== + +- programming since 20 years +- Python since around 2000 +- released projects: pypy, py lib, rlcompleter2 +- other: mailwitness, shpy, zope work, vadm, codespeak, ... +- merlinux GmbH since 2004, EU-project 2004-2007 + What is py.test? =================== -- **external** testing tool +- **cross-project** testing tool - automatically collects and executes tests - **minimal boilerplate** approach - **per-project customization** - many mature features +- works on linux, windows, osx, python2.3-2.5 Structure of Tutorial (I) ========================== @@ -35,51 +46,43 @@ - conftest.py mechanism - test collection hooks - test running hooks - - existing extensions + - existing extensions: control collection, ReST tests, HTML + generation, run prolog tests, run javascript tests First: Install py.test ============================ install "py.test" (see http://pylib.org): - - via "easy_install py" + - via "easy_install -U py" - via download and "python setup.py install" - via svn and adding py/bin to system $PATH + - works on python 2.3-2.6, windows, linux, osx - -Write and run a first test +Write and run basic tests =================================== -let's write a "test_ospath1.py":: - - import os.path, py - - def test_abspath(): - assert os.path.abspath("/a/b/c") == "/a/b/c" - p = "a/b" - cur = os.path.join(os.getcwd(), "a/b") - assert os.path.abspath("a/b/c") == "/a/b/c" - - def test_typeerror(): - assert py.test.raises(TypeError, "os.path.abspath()") - XXX check why "os.path.abspath(3)" cannot display source lines correctly - -then issue on the command line: ``py.test test_ospath1.py`` +example from ``py/path/local/testing/test_local.py``:: + def test_pypkgdir(): + datadir = py.test.ensuretemp("pypkgdir") + pkg = datadir.ensure('pkg1', dir=1) + pkg.ensure("__init__.py") + pkg.ensure("subdir/__init__.py") + assert pkg.pypkgpath() == pkg + assert pkg.join('subdir', '__init__.py').pypkgpath() == pkg Putting tests in Test Classes ================================== -test_ospath2.py (one test failing):: +example from ``py/path/local/testing/test_local.py``:: - import os.path - class TestOSPath: - def test_absolute(self): - assert os.path.abspath("/a/b/c") == "/a/b/c" - def test_relative(self): - p = "a/b" - cur = os.path.join(os.getcwd(), "a/b") - assert os.path.abspath("a/b/c") == cur - + class TestExecutionOnWindows(LocalSetup): + disabled = py.std.sys.platform != 'win32' + + def test_sysfind(self): + x = py.path.local.sysfind('cmd') + assert x.check(file=1) + assert py.path.local.sysfind('jaksdkasldqwe') is None observations and notes ======================== @@ -87,27 +90,21 @@ - py.test automatically collects ``test_*`` functions - use ``py.test --collectonly`` to inspect collection - you can write tests at module global level -- no need to subclass from py.test +- no need to subclass "TestCases" like in unittest - assertion failures provide detailed info + Generative / Parametrized tests ================================= -test_ospath3.py:: +creating three tests with "yield":: - class TestOSPath: - def test_gen_absolute(self): - def checkabssame(p1): - print "checking on", repr(p1) - assert os.path.abspath(p1) == p1 - for s in "/ /a /a/b c".split(): - yield checkabssame, s - - def test_gen_relative(self): - for p in "a a/b".split(): - p = "a/b" - expected = os.path.join(os.getcwd(), p) - yield lambda x,y: x==y, p, expected + def check(x): + assert x >= 0 + + def test_gen(): + for x in 1,2,3: + yield check, x Useful Features @@ -135,34 +132,24 @@ Setup and Teardown of test state ================================== -hooks at module, class, instance level, example:: - - import py, os.path +let's revisit the above ``test_local.py``: - def setup_module(mod): - mod.tmpdir = py.test.ensuretemp(__name__) + class LocalSetup: + def setup_class(cls): + cls.root = py.test.ensuretemp(cls.__name__) + cls.root.ensure(dir=1) + setuptestfs(cls.root) - class TestOS: def setup_method(self, method): - self.tmpdir = tmpdir.mkdir(method.__name__) - print "chdir() to ", self.tmpdir - self.oldcwd = self.tmpdir.chdir() - def teardown_method(self, method): - self.oldcwd.chdir() - - def test_relative(self): - p = "a/b" - expected = os.path.join(str(self.tmpdir), p) - assert os.path.abspath(p) == expected + "xxx" + self.tmpdir = self.root.mkdir(method.__name__) Skipping tests =================== -the above tests do not make sense for win32, -you can insert e.g.:: +Sometimes you need to skip test, use ``py.test.skip``:: - class TestOSPosix: + class TestSomeClass: def setup_class(cls): if sys.platform == "win32": py.test.skip("posix platform required") @@ -173,7 +160,7 @@ Doctests ================= -py.test automatically collects test_doc.txt files:: +py.test automatically collects ``test_doc.txt`` files:: this is an example doc test for the py.test tutorial @@ -190,8 +177,7 @@ - integrate new test "items" or "collectors" - add command line options - influence the collection process -- conftest.py's are picked up "upwards" -- to aid debugging use:: +- for debugging:: py.test --collectonly py.test --traceconfig @@ -229,7 +215,7 @@ **dynamic lookup of Collector/Item class from conftest.py files** -Example conftest.py: control traversal +Example conftest.py: control directory traversal =================================================== a conftest.py to influence collection:: @@ -237,16 +223,15 @@ import py mydir = py.magic.autopath().dirpath() class Directory(py.test.collect.Directory): - def run(self): - if self.fspath == mydir: - return ["x", "y", "z"] - return super(Directory, self).run() - + def recfilter(self, path): + if path.dirpath() == mydir: + return path.basename in ('x', 'y', 'z') + return super(Directory, self).recfilter(path) Example conftest.py: add ReST check support ============================================== -look into py/doc/conftest.py: +look into ``py/doc/conftest.py``: - this produces non-python test items - ``py.test --collectonly`` shows the custom tree @@ -317,39 +302,39 @@ - you can extend and modify the collection process - you can add new (non-python) test items - conftest.py files are usually "drop in" plugins +- often project independent +- **orthogonality** of conftest features -note, however: - -- no easy customization of reporting (yet) -- details will change for py lib 1.0 release, thus - if you use special conftest's be sure to subscribe - http://codespeak.net/mailman/listinfo/py-dev - - -XXX refactor / do extra slides see what could stay / below - - - - - - - - - - - - +but note: +- customization gets easier with the upcoming 1.0 release, + please subscribe to http://codespeak.net/mailman/listinfo/py-dev recap: main features ====================== -- assertions by assert statement, nice debugging +- assertions by assert statement +- informative concise debugging messages - unittests, generative tests, doctests, rest-tests - automatic customize collection of tests - select tests by keyword - capture stdout/stderr per-test +Summary / questions +========================== + +- minimal boilerplate +- re-use assert statement +- easy to customize for your test suites +- use existing and evolving features for your project! + +links: + +http://pylib.org http://pytest.org +http://merlinux.eu - holger at merlinux.de +https://codespeak.net/svn/py/extradoc/talk/ep2008/pytest.txt + + ad-hoc distribution of tests =================================== @@ -373,10 +358,6 @@ - expects plain Python executable on remote side - no need to pre-install other software remotely -- generated AJAX application async displays test results - -- demo - cross-platform testing ============================= From hpk at codespeak.net Fri Sep 5 13:58:55 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 5 Sep 2008 13:58:55 +0200 (CEST) Subject: [py-svn] r57840 - py/extradoc/talk/pycon-uk-2008 Message-ID: <20080905115855.691AC16A086@codespeak.net> Author: hpk Date: Fri Sep 5 13:58:53 2008 New Revision: 57840 Added: py/extradoc/talk/pycon-uk-2008/author.latex py/extradoc/talk/pycon-uk-2008/beamerdefs.txt (contents, props changed) py/extradoc/talk/pycon-uk-2008/makepdf (contents, props changed) py/extradoc/talk/pycon-uk-2008/rst2beamer.py (contents, props changed) py/extradoc/talk/pycon-uk-2008/stylesheet.latex Modified: py/extradoc/talk/pycon-uk-2008/pytest.txt Log: preparing pdf, adjustments to formatting Added: py/extradoc/talk/pycon-uk-2008/author.latex ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-uk-2008/author.latex Fri Sep 5 13:58:53 2008 @@ -0,0 +1,7 @@ +\definecolor{rrblitbackground}{rgb}{0.0, 0.0, 0.0} + +\title[pytest - rapid testing with minimal effort]{rapid testing with minimal effort} +\author[H. Krekel]{Holger Krekel \\Merlinux GmbH} + +\institute[PyCon UK 2008]{PyCon UK 2008 - Birmingham} +\date{September 13 2008} Added: py/extradoc/talk/pycon-uk-2008/beamerdefs.txt ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-uk-2008/beamerdefs.txt Fri Sep 5 13:58:53 2008 @@ -0,0 +1,77 @@ +.. colors +.. =========================== + +.. role:: green +.. role:: red + + +.. general useful commands +.. =========================== + +.. |pause| raw:: latex + + \pause + +.. |small| raw:: latex + + {\small + +.. |end_small| raw:: latex + + } + + +.. closed bracket +.. =========================== + +.. |>| raw:: latex + + } + + +.. example block +.. =========================== + +.. |example<| raw:: latex + + \begin{exampleblock}{ + + +.. |end_example| raw:: latex + + \end{exampleblock} + + + +.. alert block +.. =========================== + +.. |alert<| raw:: latex + + \begin{alertblock}{ + + +.. |end_alert| raw:: latex + + \end{alertblock} + + + +.. columns +.. =========================== + +.. |column1| raw:: latex + + \begin{columns} + \begin{column}{0.45\textwidth} + +.. |column2| raw:: latex + + \end{column} + \begin{column}{0.45\textwidth} + + +.. |end_columns| raw:: latex + + \end{column} + \end{columns} Added: py/extradoc/talk/pycon-uk-2008/makepdf ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-uk-2008/makepdf Fri Sep 5 13:58:53 2008 @@ -0,0 +1,12 @@ +#!/bin/bash + +# you can find rst2beamer.py here: +# http://codespeak.net/svn/user/antocuni/bin/rst2beamer.py + +# WARNING: to work, it needs this patch for docutils +# https://sourceforge.net/tracker/?func=detail&atid=422032&aid=1459707&group_id=38414 + +python rst2beamer.py --stylesheet=stylesheet.latex --documentoptions=14pt pytest.txt pytest.latex +sed 's/\\date{}/\\input{author.latex}/' -i pytest.latex +pdflatex pytest.latex + Modified: py/extradoc/talk/pycon-uk-2008/pytest.txt ============================================================================== --- py/extradoc/talk/pycon-uk-2008/pytest.txt (original) +++ py/extradoc/talk/pycon-uk-2008/pytest.txt Fri Sep 5 13:58:53 2008 @@ -1,3 +1,4 @@ +.. include:: beamerdefs.txt .. include:: ================================================================= @@ -7,7 +8,6 @@ :author: Holger Krekel, merlinux GmbH :event: 13.9.2008, Pycon UK 2008, Birmingham - my background =============== @@ -31,58 +31,65 @@ ========================== writing and running tests: - - test functions - - test classes - - generative tests - - test driven development - - setup and teardown test state - - skipping tests - - doctests + +- test functions +- test classes +- generative tests +- test driven development +- setup and teardown test state +- skipping tests +- doctests Structure of Tutorial (II) ========================== writing and using extensions: - - conftest.py mechanism - - test collection hooks - - test running hooks - - existing extensions: control collection, ReST tests, HTML - generation, run prolog tests, run javascript tests + +- conftest.py mechanism +- test collection hooks +- test running hooks +- existing extensions: control collection, ReST tests, HTML + generation, run prolog tests, run javascript tests First: Install py.test ============================ install "py.test" (see http://pylib.org): - - via "easy_install -U py" - - via download and "python setup.py install" - - via svn and adding py/bin to system $PATH - - works on python 2.3-2.6, windows, linux, osx + +- via "easy_install -U py" +- via download and "python setup.py install" +- via svn and adding py/bin to system $PATH +- works on python 2.3-2.6, windows, linux, osx Write and run basic tests =================================== example from ``py/path/local/testing/test_local.py``:: - def test_pypkgdir(): - datadir = py.test.ensuretemp("pypkgdir") - pkg = datadir.ensure('pkg1', dir=1) - pkg.ensure("__init__.py") - pkg.ensure("subdir/__init__.py") - assert pkg.pypkgpath() == pkg - assert pkg.join('subdir', '__init__.py').pypkgpath() == pkg + def test_pypkgdir(): + datadir = py.test.ensuretemp("pypkgdir") + pkg = datadir.ensure('pkg1', dir=1) + pkg.ensure("__init__.py") + pkg.ensure("subdir/__init__.py") + assert pkg.pypkgpath() == pkg + subinit = pkg.join('subdir', '__init__.py') + assert subinit.pypkgpath() == pkg + Putting tests in Test Classes ================================== example from ``py/path/local/testing/test_local.py``:: - class TestExecutionOnWindows(LocalSetup): - disabled = py.std.sys.platform != 'win32' + class TestExecutionOnWindows(LocalSetup): + disabled = py.std.sys.platform != 'win32' + + def test_sysfind(self): + x = py.path.local.sysfind('cmd') + assert x.check(file=1) + x = py.path.local.sysfind('jaksdkasldqwe') + assert x is None - def test_sysfind(self): - x = py.path.local.sysfind('cmd') - assert x.check(file=1) - assert py.path.local.sysfind('jaksdkasldqwe') is None observations and notes ======================== @@ -134,14 +141,14 @@ let's revisit the above ``test_local.py``: - class LocalSetup: - def setup_class(cls): - cls.root = py.test.ensuretemp(cls.__name__) - cls.root.ensure(dir=1) - setuptestfs(cls.root) + class LocalSetup: + def setup_class(cls): + cls.root = py.test.ensuretemp(cls.__name__) + cls.root.ensure(dir=1) + setuptestfs(cls.root) - def setup_method(self, method): - self.tmpdir = self.root.mkdir(method.__name__) + def setup_method(self, method): + self.tmpdir = self.root.mkdir(method.__name__) Skipping tests @@ -149,10 +156,10 @@ Sometimes you need to skip test, use ``py.test.skip``:: - class TestSomeClass: - def setup_class(cls): - if sys.platform == "win32": - py.test.skip("posix platform required") + class TestSomeClass: + def setup_class(cls): + if sys.platform == "win32": + py.test.skip("win32 required") you can also place such calls to "py.test.skip()" inside test functions or in other test setup functions. @@ -178,7 +185,6 @@ - add command line options - influence the collection process - for debugging:: - py.test --collectonly py.test --traceconfig @@ -186,34 +192,23 @@ Important Internal Objects ================================== -**Session**: iterates over (custom) collection tree, executes +- **Session**: iterates over (custom) collection tree, executes tests and reports outcomes. +- **Directory** collect files in directory +- **Module** collect python Classes and Functions +- **Class**/**Instance** collect test methods +- **Generator** collect generative tests +- **Function** sets up and executes a python test function +- **DoctestFile** collect doctests in a .txt file -Collection Nodes ("py.test.collect.*"): - -**Directory**: collect files - -**Module**: collect test Classes and Functions - -**Class**/**Instance**: collect test methods - -**DoctestFile**: collect doctest of a .txt file - -**Generator**: collect generative tests - -***Function***: sets up and executes a python test function - -The collection process +The collection tree =========================== -collection process forms a tree: - - leaves: so called "items", e.g. ``py.test.collect.Function`` - - nodes: so called "collectors" contain further collectors or items - - collection tree is built iterativels - -test collection usually starts from directories or files - -**dynamic lookup of Collector/Item class from conftest.py files** +- leaves: so called "items", e.g. ``py.test.collect.Function`` +- nodes: so called "collectors" contain further collectors or items +- collection tree is built iterativels +- test collection usually starts from directories or files +- **dynamic lookup in conftest.py files** Example conftest.py: control directory traversal =================================================== @@ -333,129 +328,3 @@ http://pylib.org http://pytest.org http://merlinux.eu - holger at merlinux.de https://codespeak.net/svn/py/extradoc/talk/ep2008/pytest.txt - - -ad-hoc distribution of tests -=================================== - -``py.test --dist`` sends tests to remote places -or multiple processors - -- for each host: - - - setup (ssh) connection - - sync local source code to remote place - - trigger running of (isolated) tests - -ad-hoc distribution of tests (2) -===================================== - -- synchronise source code "1 -> N" simultaneously - -- uses py.execnet: - - - can connect e.g. via SSH - - expects plain Python executable on remote side - - no need to pre-install other software remotely - -cross-platform testing -============================= - -- test run on linux, test execution on windows -- uses py.execnet (same technique as "--dist") -- works well with "--looponfailing" - -status cross-platform/distributed testing -============================================== - -- should basically work for any project -- gives full tracebacks, most options work - -todo: - -- make introspection/pdb work -- use ``screen`` for access to failed tests -- accellerate py.execnet setup -- more unification among testing modes - -collect info about functions (apigen) -============================================= - -- collect function signatures (via settrace) -- track input values/types, stacktraces, return values -- web page with rich automatically produced information -- TODO: decoupling of collecting info and generating html - -doctests -============== - -- basically work (automatically collected from text files) -- uses 2.5's doctest module -- needs more fine-grained integration - -Unifying Test Reporting -================================= - -py.test contains two approaches for processing test results: - -- "old style": methods invoked on session object -- "new style": reporting events are sent to Reporter object - -next: - -- use reporting event architecture pervasively -- refactor existing reporting extensions - -py.test extensions (conftest) -================================= - -- all conftest.py files are considered in traversed directories -- can extend/modify testing process in arbitrary ways -- however: - - - often requires too much knowledge of internal architecture - - no convention for organising shared test support code - -- ergo: introduce mechanism to share test support code / plugins - - -Platform support -====================== - -- py lib works on linux, freebsd, windows and OSX, ... -- works on python 2.3, 2.4, 2.5 -- py lib provides doctest/optparse/... unified compat modules -- todo: improve and automate packaging/installation - -test networks -================== - -- currently model for 1:1 process-to-process -- "A <-> B and B <-> C" connections do not imply "A <-> C" -- fix that! -- then: manage (large) network of test hosts -- select set of test deployment hosts by platform, load -- have modes for testing on several platforms at once - -difference to "buildbot" ... -================================== - -- py.test generally works from developers WC -- no need to first commit and wait for later (nightly) test run -- developer's choice of test setting -- more interactivity - -summary development topics -============================= - -- refactor/unify reporting mechanisms -- introduce shared test support code (plugins) -- improve py.execnet (towards networks) -- interactive debugging / introspection everywhere -- systematically persist test results - -Goal -============= - -Maximize fun and efficiency of (test-driven) development - Added: py/extradoc/talk/pycon-uk-2008/rst2beamer.py ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-uk-2008/rst2beamer.py Fri Sep 5 13:58:53 2008 @@ -0,0 +1,170 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +A docutils script converting restructured text into Beamer-flavoured LaTeX. + +Beamer is a LaTeX document class for presentations. Via this script, ReST can +be used to prepare slides. It can be called:: + + rst2beamer.py infile.txt > outfile.tex + +where ``infile.tex`` contains the produced Beamer LaTeX. + +See for more details. + +""" +# TODO: modifications for handout sections? +# TOOD: sections and subsections? +# TODO: enable beamer themes? +# TODO: convert document metadata to front page fields? +# TODO: toc-conversion? +# TODO: fix descriptions + +# Unless otherwise stated, created by P-M Agapow on 2007-08-21 +# and open for academic & non-commercial use and modification . + +__docformat__ = 'restructuredtext en' +__author__ = "Paul-Michael Agapow " +__version__ = "0.2" + + +### IMPORTS ### + +import locale +from docutils.core import publish_cmdline, default_description +from docutils.writers.latex2e import Writer as Latex2eWriter +from docutils.writers.latex2e import LaTeXTranslator, DocumentClass +from docutils import nodes + +## CONSTANTS & DEFINES: ### + +BEAMER_SPEC = ( + 'Beamer options', + 'These are derived almost entirely from the LaTeX2e options', + tuple ( + [ + ( + 'Specify theme.', + ['--theme'], + {'default': '', } + ), + ( + 'Specify document options. Multiple options can be given, ' + 'separated by commas. Default is "10pt,a4paper".', + ['--documentoptions'], + {'default': '', } + ), + ] + list (Latex2eWriter.settings_spec[2][2:]) + ), +) + +BEAMER_DEFAULTS = { + 'output_encoding': 'latin-1', + 'documentclass': 'beamer', +} + + +### IMPLEMENTATION ### + +try: + locale.setlocale (locale.LC_ALL, '') +except: + pass + +class BeamerTranslator (LaTeXTranslator): + """ + A converter for docutils elements to beamer-flavoured latex. + """ + + def __init__ (self, document): + LaTeXTranslator.__init__ (self, document) + self.head_prefix = [x for x in self.head_prefix if ('{typearea}' not in x)] + hyperref_posn = [i for i in range (len (self.head_prefix)) if ('{hyperref}' in self.head_prefix[i])] + self.head_prefix[hyperref_posn[0]] = '\\usepackage{hyperref}\n' + self.head_prefix.extend ([ + '\\definecolor{rrblitbackground}{rgb}{0.55, 0.3, 0.1}\n', + '\\newenvironment{rtbliteral}{\n', + '\\begin{ttfamily}\n', + '\\color{rrblitbackground}\n', + '}{\n', + '\\end{ttfamily}\n', + '}\n', + ]) + # this fixes the hardcoded section titles in docutils 0.4 + self.d_class = DocumentClass ('article') + + def begin_frametag (self): + return '\\begin{frame}\n' + + def end_frametag (self): + return '\\end{frame}\n' + + def visit_section (self, node): + if (self.section_level == 0): + self.body.append (self.begin_frametag()) + LaTeXTranslator.visit_section (self, node) + + def depart_section (self, node): + # Remove counter for potential subsections: + LaTeXTranslator.depart_section (self, node) + if (self.section_level == 0): + self.body.append (self.end_frametag()) + + def visit_title (self, node): + if (self.section_level == 1): + self.body.append ('\\frametitle{%s}\n\n' % self.encode(node.astext())) + raise nodes.SkipNode + else: + LaTeXTranslator.visit_title (self, node) + + def depart_title (self, node): + if (self.section_level != 1): + LaTeXTranslator.depart_title (self, node) + + def visit_literal_block(self, node): + if not self.active_table.is_open(): + self.body.append('\n\n\\smallskip\n\\begin{rtbliteral}\n') + self.context.append('\\end{rtbliteral}\n\\smallskip\n\n') + else: + self.body.append('\n') + self.context.append('\n') + if (self.settings.use_verbatim_when_possible and (len(node) == 1) + # in case of a parsed-literal containing just a "**bold**" word: + and isinstance(node[0], nodes.Text)): + self.verbatim = 1 + self.body.append('\\begin{verbatim}\n') + else: + self.literal_block = 1 + self.insert_none_breaking_blanks = 1 + + def depart_literal_block(self, node): + if self.verbatim: + self.body.append('\n\\end{verbatim}\n') + self.verbatim = 0 + else: + self.body.append('\n') + self.insert_none_breaking_blanks = 0 + self.literal_block = 0 + self.body.append(self.context.pop()) + + +class BeamerWriter (Latex2eWriter): + """ + A docutils writer that modifies the translator and settings for beamer. + """ + settings_spec = BEAMER_SPEC + settings_defaults = BEAMER_DEFAULTS + + def __init__(self): + Latex2eWriter.__init__(self) + self.translator_class = BeamerTranslator + + +if __name__ == '__main__': + description = ( + "Generates Beamer-flavoured LaTeX for PDF-based presentations." + default_description) + publish_cmdline (writer=BeamerWriter(), description=description) + + +### END ###################################################################### + Added: py/extradoc/talk/pycon-uk-2008/stylesheet.latex ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-uk-2008/stylesheet.latex Fri Sep 5 13:58:53 2008 @@ -0,0 +1,10 @@ +\usetheme{Boadilla} +\setbeamercovered{transparent} +\setbeamertemplate{navigation symbols}{} + +\definecolor{darkgreen}{rgb}{0, 0.5, 0.0} +\newcommand{\docutilsrolegreen}[1]{\color{darkgreen}#1\normalcolor} +\newcommand{\docutilsrolered}[1]{\color{red}#1\normalcolor} + +\newcommand{\green}[1]{\color{darkgreen}#1\normalcolor} +\newcommand{\red}[1]{\color{red}#1\normalcolor} From hpk at codespeak.net Fri Sep 5 14:05:00 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 5 Sep 2008 14:05:00 +0200 (CEST) Subject: [py-svn] r57841 - py/extradoc/talk/pycon-uk-2008 Message-ID: <20080905120500.5C0EC169F2F@codespeak.net> Author: hpk Date: Fri Sep 5 14:04:57 2008 New Revision: 57841 Added: py/extradoc/talk/pycon-uk-2008/pytest.pdf (contents, props changed) Log: add PDF as well Added: py/extradoc/talk/pycon-uk-2008/pytest.pdf ============================================================================== Binary file. No diff available. From hpk at codespeak.net Fri Sep 5 14:17:40 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 5 Sep 2008 14:17:40 +0200 (CEST) Subject: [py-svn] r57844 - py/extradoc/talk/pycon-uk-2008 Message-ID: <20080905121740.E5DEE16A060@codespeak.net> Author: hpk Date: Fri Sep 5 14:17:39 2008 New Revision: 57844 Modified: py/extradoc/talk/pycon-uk-2008/pytest.pdf py/extradoc/talk/pycon-uk-2008/pytest.txt Log: removing event/author line as suggested, regen pdf Modified: py/extradoc/talk/pycon-uk-2008/pytest.pdf ============================================================================== Binary files. No diff available. Modified: py/extradoc/talk/pycon-uk-2008/pytest.txt ============================================================================== --- py/extradoc/talk/pycon-uk-2008/pytest.txt (original) +++ py/extradoc/talk/pycon-uk-2008/pytest.txt Fri Sep 5 14:17:39 2008 @@ -5,9 +5,6 @@ py.test: rapid testing with minimal effort ================================================================= -:author: Holger Krekel, merlinux GmbH -:event: 13.9.2008, Pycon UK 2008, Birmingham - my background =============== From hpk at codespeak.net Fri Sep 5 15:04:41 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 5 Sep 2008 15:04:41 +0200 (CEST) Subject: [py-svn] r57856 - py/extradoc/talk/pycon-uk-2008 Message-ID: <20080905130441.6891C16A086@codespeak.net> Author: hpk Date: Fri Sep 5 15:04:37 2008 New Revision: 57856 Modified: py/extradoc/talk/pycon-uk-2008/pytest.pdf py/extradoc/talk/pycon-uk-2008/pytest.txt Log: fix ReST, regen pdf from different computer Modified: py/extradoc/talk/pycon-uk-2008/pytest.pdf ============================================================================== Binary files. No diff available. Modified: py/extradoc/talk/pycon-uk-2008/pytest.txt ============================================================================== --- py/extradoc/talk/pycon-uk-2008/pytest.txt (original) +++ py/extradoc/talk/pycon-uk-2008/pytest.txt Fri Sep 5 15:04:37 2008 @@ -189,8 +189,7 @@ Important Internal Objects ================================== -- **Session**: iterates over (custom) collection tree, executes -tests and reports outcomes. +- **Session**: iterates over (custom) collection tree, executes tests and reports outcomes. - **Directory** collect files in directory - **Module** collect python Classes and Functions - **Class**/**Instance** collect test methods From hpk at codespeak.net Mon Sep 8 12:51:33 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 8 Sep 2008 12:51:33 +0200 (CEST) Subject: [py-svn] r57964 - py/extradoc/talk/pycon-uk-2008 Message-ID: <20080908105133.5248816A239@codespeak.net> Author: hpk Date: Mon Sep 8 12:51:31 2008 New Revision: 57964 Modified: py/extradoc/talk/pycon-uk-2008/pytest.txt Log: finalizing pytest talk for pycon uk Modified: py/extradoc/talk/pycon-uk-2008/pytest.txt ============================================================================== --- py/extradoc/talk/pycon-uk-2008/pytest.txt (original) +++ py/extradoc/talk/pycon-uk-2008/pytest.txt Mon Sep 8 12:51:31 2008 @@ -94,7 +94,6 @@ - py.test automatically collects ``test_*`` functions - use ``py.test --collectonly`` to inspect collection - you can write tests at module global level -- no need to subclass "TestCases" like in unittest - assertion failures provide detailed info @@ -111,7 +110,7 @@ yield check, x -Useful Features +Other useful features ============================ - stdout/stderr is captured per-test @@ -136,7 +135,7 @@ Setup and Teardown of test state ================================== -let's revisit the above ``test_local.py``: +look again at the above ``test_local.py``: class LocalSetup: def setup_class(cls): From hpk at codespeak.net Mon Sep 8 13:29:56 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 8 Sep 2008 13:29:56 +0200 (CEST) Subject: [py-svn] r57965 - py/extradoc/talk/pycon-uk-2008 Message-ID: <20080908112956.C474A169F65@codespeak.net> Author: hpk Date: Mon Sep 8 13:29:54 2008 New Revision: 57965 Modified: py/extradoc/talk/pycon-uk-2008/author.latex py/extradoc/talk/pycon-uk-2008/pytest.pdf py/extradoc/talk/pycon-uk-2008/pytest.txt Log: fixed formatting glitches Modified: py/extradoc/talk/pycon-uk-2008/author.latex ============================================================================== --- py/extradoc/talk/pycon-uk-2008/author.latex (original) +++ py/extradoc/talk/pycon-uk-2008/author.latex Mon Sep 8 13:29:54 2008 @@ -1,6 +1,6 @@ \definecolor{rrblitbackground}{rgb}{0.0, 0.0, 0.0} -\title[pytest - rapid testing with minimal effort]{rapid testing with minimal effort} +\title[py.test - rapid testing with minimal effort]{py.test: rapid testing with minimal effort} \author[H. Krekel]{Holger Krekel \\Merlinux GmbH} \institute[PyCon UK 2008]{PyCon UK 2008 - Birmingham} Modified: py/extradoc/talk/pycon-uk-2008/pytest.pdf ============================================================================== Binary files. No diff available. Modified: py/extradoc/talk/pycon-uk-2008/pytest.txt ============================================================================== --- py/extradoc/talk/pycon-uk-2008/pytest.txt (original) +++ py/extradoc/talk/pycon-uk-2008/pytest.txt Mon Sep 8 13:29:54 2008 @@ -135,7 +135,7 @@ Setup and Teardown of test state ================================== -look again at the above ``test_local.py``: +look again at the above ``test_local.py``:: class LocalSetup: def setup_class(cls): @@ -181,6 +181,7 @@ - add command line options - influence the collection process - for debugging:: + py.test --collectonly py.test --traceconfig @@ -205,18 +206,6 @@ - test collection usually starts from directories or files - **dynamic lookup in conftest.py files** -Example conftest.py: control directory traversal -=================================================== - -a conftest.py to influence collection:: - - import py - mydir = py.magic.autopath().dirpath() - class Directory(py.test.collect.Directory): - def recfilter(self, path): - if path.dirpath() == mydir: - return path.basename in ('x', 'y', 'z') - return super(Directory, self).recfilter(path) Example conftest.py: add ReST check support ============================================== @@ -321,5 +310,6 @@ links: http://pylib.org http://pytest.org + http://merlinux.eu - holger at merlinux.de -https://codespeak.net/svn/py/extradoc/talk/ep2008/pytest.txt + From pedronis at codespeak.net Mon Sep 8 15:02:38 2008 From: pedronis at codespeak.net (pedronis at codespeak.net) Date: Mon, 8 Sep 2008 15:02:38 +0200 (CEST) Subject: [py-svn] r57971 - in py/trunk/py/test: . testing Message-ID: <20080908130238.D594416A23F@codespeak.net> Author: pedronis Date: Mon Sep 8 15:02:35 2008 New Revision: 57971 Modified: py/trunk/py/test/collect.py py/trunk/py/test/testing/test_collect.py Log: (iko, pedronis) make the Directory collection return files sorted by name again, as it seemed expected by the tests Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Mon Sep 8 15:02:35 2008 @@ -398,7 +398,7 @@ if l is not None: return l l = [] - for path in self.fspath.listdir(): # listdir() returns sorted order + for path in self.fspath.listdir(sort=True): res = self.consider(path, usefilters=True) if res is not None: l.append(res) Modified: py/trunk/py/test/testing/test_collect.py ============================================================================== --- py/trunk/py/test/testing/test_collect.py (original) +++ py/trunk/py/test/testing/test_collect.py Mon Sep 8 15:02:35 2008 @@ -87,7 +87,7 @@ def test_found_certain_testfiles(self): p1 = self.makepyfile(test_found = "pass", found_test="pass") col = py.test.collect.Directory(p1.dirpath(), config=dummyconfig) - items = col.collect() + items = col.collect() # Directory collect returns files sorted by name assert len(items) == 2 assert items[1].name == 'test_found.py' assert items[0].name == 'found_test.py' From hpk at codespeak.net Mon Sep 8 15:07:20 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 8 Sep 2008 15:07:20 +0200 (CEST) Subject: [py-svn] r57972 - py/extradoc/talk/pycon-uk-2008 Message-ID: <20080908130720.D62E216850A@codespeak.net> Author: hpk Date: Mon Sep 8 15:07:18 2008 New Revision: 57972 Added: py/extradoc/talk/pycon-uk-2008/merlinux-logo.jpg (contents, props changed) py/extradoc/talk/pycon-uk-2008/title.latex Modified: py/extradoc/talk/pycon-uk-2008/author.latex py/extradoc/talk/pycon-uk-2008/makepdf py/extradoc/talk/pycon-uk-2008/pytest.pdf py/extradoc/talk/pycon-uk-2008/pytest.txt Log: finalizing slides PDF Modified: py/extradoc/talk/pycon-uk-2008/author.latex ============================================================================== --- py/extradoc/talk/pycon-uk-2008/author.latex (original) +++ py/extradoc/talk/pycon-uk-2008/author.latex Mon Sep 8 15:07:18 2008 @@ -1,6 +1,6 @@ \definecolor{rrblitbackground}{rgb}{0.0, 0.0, 0.0} -\title[py.test - rapid testing with minimal effort]{py.test: rapid testing with minimal effort} +\title[py.test - rapid testing with minimal effort]{py.test: testing with minimal effort} \author[H. Krekel]{Holger Krekel \\Merlinux GmbH} \institute[PyCon UK 2008]{PyCon UK 2008 - Birmingham} Modified: py/extradoc/talk/pycon-uk-2008/makepdf ============================================================================== --- py/extradoc/talk/pycon-uk-2008/makepdf (original) +++ py/extradoc/talk/pycon-uk-2008/makepdf Mon Sep 8 15:07:18 2008 @@ -8,5 +8,6 @@ python rst2beamer.py --stylesheet=stylesheet.latex --documentoptions=14pt pytest.txt pytest.latex sed 's/\\date{}/\\input{author.latex}/' -i pytest.latex +sed 's/\\maketitle/\\input{title.latex}/' -i pytest.latex pdflatex pytest.latex Added: py/extradoc/talk/pycon-uk-2008/merlinux-logo.jpg ============================================================================== Binary file. No diff available. Modified: py/extradoc/talk/pycon-uk-2008/pytest.pdf ============================================================================== Binary files. No diff available. Modified: py/extradoc/talk/pycon-uk-2008/pytest.txt ============================================================================== --- py/extradoc/talk/pycon-uk-2008/pytest.txt (original) +++ py/extradoc/talk/pycon-uk-2008/pytest.txt Mon Sep 8 15:07:18 2008 @@ -2,7 +2,7 @@ .. include:: ================================================================= -py.test: rapid testing with minimal effort +rapid testing with minimal effort ================================================================= my background @@ -311,5 +311,5 @@ http://pylib.org http://pytest.org -http://merlinux.eu - holger at merlinux.de +http://merlinux.eu - holger at merlinux.eu Added: py/extradoc/talk/pycon-uk-2008/title.latex ============================================================================== --- (empty file) +++ py/extradoc/talk/pycon-uk-2008/title.latex Mon Sep 8 15:07:18 2008 @@ -0,0 +1,6 @@ +\begin{titlepage} +\begin{figure}[h] +\includegraphics[width=64px,height=64px]{merlinux-logo.jpg} +\qquad +\end{figure} +\end{titlepage} From pedronis at codespeak.net Mon Sep 8 17:51:39 2008 From: pedronis at codespeak.net (pedronis at codespeak.net) Date: Mon, 8 Sep 2008 17:51:39 +0200 (CEST) Subject: [py-svn] r57977 - in py/trunk/py/code: . testing Message-ID: <20080908155139.3132E16A11D@codespeak.net> Author: pedronis Date: Mon Sep 8 17:51:38 2008 New Revision: 57977 Modified: py/trunk/py/code/excinfo.py py/trunk/py/code/testing/test_excinfo.py py/trunk/py/code/traceback2.py Log: (iko, pedronis) - fixing a bug with test about the case when a multi line source cannot be retrieved ( test_repr_many_line_source_not_existing in test_excinfo.py ) - trying to make py test behave more like in the past in the face of fullsource errors, needed by pypy app test infrastructure ( test_repr_source_failing_fullsource in test_excinfo.py ) Modified: py/trunk/py/code/excinfo.py ============================================================================== --- py/trunk/py/code/excinfo.py (original) +++ py/trunk/py/code/excinfo.py Mon Sep 8 17:51:38 2008 @@ -96,10 +96,10 @@ def _getentrysource(self, entry): source = entry.getsource() - if source is None: - source = py.code.Source("???") - return source.deindent() - + if source is not None: + source = source.deindent() + return source + def _saferepr(self, obj): return safe_repr._repr(obj) @@ -166,7 +166,11 @@ def repr_traceback_entry(self, entry, excinfo=None): # excinfo is not None if this is the last tb entry source = self._getentrysource(entry) - line_index = entry.lineno - entry.getfirstlinesource() + if source is None: + source = py.code.Source("???") + line_index = 0 + else: + line_index = entry.lineno - entry.getfirstlinesource() lines = [] if self.style == "long": Modified: py/trunk/py/code/testing/test_excinfo.py ============================================================================== --- py/trunk/py/code/testing/test_excinfo.py (original) +++ py/trunk/py/code/testing/test_excinfo.py Mon Sep 8 17:51:38 2008 @@ -305,6 +305,65 @@ repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" + def test_repr_many_line_source_not_existing(self): + pr = FormattedExcinfo() + co = compile(""" +a = 1 +raise ValueError() +""", "", "exec") + try: + exec co + except ValueError: + excinfo = py.code.ExceptionInfo() + repr = pr.repr_excinfo(excinfo) + assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" + + def test_repr_source_failing_fullsource(self): + pr = FormattedExcinfo() + + class FakeCode(object): + path = '?' + firstlineno = 5 + + @property + def fullsource(self): + raise fail + + class FakeFrame(object): + code = FakeCode() + f_locals = {} + + class FakeTracebackEntry(py.code.Traceback.Entry): + def __init__(self, tb): + self.frame = FakeFrame() + self.lineno = 5+3 + + class Traceback(py.code.Traceback): + Entry = FakeTracebackEntry + + class FakeExcinfo(py.code.ExceptionInfo): + typename = "Foo" + def __init__(self): + pass + + def exconly(self, tryshort): + return "EXC" + + excinfo = FakeExcinfo() + class FakeRawTB(object): + tb_next = None + tb = FakeRawTB() + excinfo.traceback = Traceback(tb) + + fail = IOError() + repr = pr.repr_excinfo(excinfo) + assert repr.reprtraceback.reprentries[0].lines[0] == "> ???" + + fail = py.error.ENOENT + repr = pr.repr_excinfo(excinfo) + assert repr.reprtraceback.reprentries[0].lines[0] == "> ???" + + def test_repr_local(self): p = FormattedExcinfo(showlocals=True) loc = {'y': 5, 'z': 7, 'x': 3, '__builtins__': __builtins__} @@ -581,4 +640,3 @@ 'tbfilter': tbfilter } yield kw - Modified: py/trunk/py/code/traceback2.py ============================================================================== --- py/trunk/py/code/traceback2.py (original) +++ py/trunk/py/code/traceback2.py Mon Sep 8 17:51:38 2008 @@ -50,8 +50,11 @@ return self.frame.code.firstlineno def getsource(self): - """ return failing source code. """ - source = self.frame.code.fullsource + """ return failing source code. """ + try: + source = self.frame.code.fullsource + except (IOError, py.error.ENOENT): + return None if source is None: try: sourcelines, lineno = py.std.inspect.findsource(self.frame.code.raw) From hpk at codespeak.net Mon Sep 8 18:01:13 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 8 Sep 2008 18:01:13 +0200 (CEST) Subject: [py-svn] r57979 - py/trunk/py/code/testing Message-ID: <20080908160113.A4A2716A156@codespeak.net> Author: hpk Date: Mon Sep 8 18:01:11 2008 New Revision: 57979 Modified: py/trunk/py/code/testing/test_excinfo.py Log: fix 2.3 compat Modified: py/trunk/py/code/testing/test_excinfo.py ============================================================================== --- py/trunk/py/code/testing/test_excinfo.py (original) +++ py/trunk/py/code/testing/test_excinfo.py Mon Sep 8 18:01:11 2008 @@ -325,9 +325,9 @@ path = '?' firstlineno = 5 - @property def fullsource(self): raise fail + fullsource = property(fullsource) class FakeFrame(object): code = FakeCode() From hpk at codespeak.net Tue Sep 9 18:51:12 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 9 Sep 2008 18:51:12 +0200 (CEST) Subject: [py-svn] r58009 - in py/trunk: contrib/webcheck py/test/web Message-ID: <20080909165112.E609E169EEA@codespeak.net> Author: hpk Date: Tue Sep 9 18:51:07 2008 New Revision: 58009 Added: py/trunk/contrib/webcheck/ - copied from r58005, py/trunk/py/test/web/ py/trunk/contrib/webcheck/readme.txt (contents, props changed) Removed: py/trunk/py/test/web/ Log: move old webcheck things into contrib Added: py/trunk/contrib/webcheck/readme.txt ============================================================================== --- (empty file) +++ py/trunk/contrib/webcheck/readme.txt Tue Sep 9 18:51:07 2008 @@ -0,0 +1,4 @@ +this directory contains some code to perform CSS and w3c +validation checks for html strings. + +see webcheck.py From hpk at codespeak.net Tue Sep 9 18:51:51 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 9 Sep 2008 18:51:51 +0200 (CEST) Subject: [py-svn] r58010 - py/trunk/py/doc Message-ID: <20080909165151.96210169EF0@codespeak.net> Author: hpk Date: Tue Sep 9 18:51:49 2008 New Revision: 58010 Modified: py/trunk/py/doc/conftest.py Log: some support for old naming because many sites import ReST checker from this conftest. Modified: py/trunk/py/doc/conftest.py ============================================================================== --- py/trunk/py/doc/conftest.py (original) +++ py/trunk/py/doc/conftest.py Tue Sep 9 18:51:49 2008 @@ -321,3 +321,5 @@ relpath += '.html' return (text, apigen_relpath + 'source/%s' % (relpath,)) +# legacy +ReSTChecker = DocfileTests From hpk at codespeak.net Tue Sep 9 19:02:29 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 9 Sep 2008 19:02:29 +0200 (CEST) Subject: [py-svn] r58012 - in py/trunk/py/test/report: . testing Message-ID: <20080909170229.B972A169F8C@codespeak.net> Author: hpk Date: Tue Sep 9 19:02:28 2008 New Revision: 58012 Modified: py/trunk/py/test/report/testing/test_web.py py/trunk/py/test/report/testing/test_webjs.py py/trunk/py/test/report/web.py py/trunk/py/test/report/webjs.py Log: fix imports so that tests pass against pypy-dist Modified: py/trunk/py/test/report/testing/test_web.py ============================================================================== --- py/trunk/py/test/report/testing/test_web.py (original) +++ py/trunk/py/test/report/testing/test_web.py Tue Sep 9 19:02:28 2008 @@ -15,14 +15,14 @@ mod.commproxy.USE_MOCHIKIT = False mod.rpython2javascript = rpython2javascript mod.commproxy = mod.commproxy - from py.__.test.dsession.web import TestHandler as _TestHandler - from py.__.test.dsession.web import MultiQueue + from py.__.test.report.web import TestHandler as _TestHandler + from py.__.test.report.web import MultiQueue mod._TestHandler = _TestHandler mod.MultiQueue = MultiQueue def test_js_generate(): - from py.__.test.dsession import webjs - from py.__.test.dsession.web import FUNCTION_LIST, IMPORTED_PYPY + from py.__.test.report import webjs + from py.__.test.report.web import FUNCTION_LIST, IMPORTED_PYPY source = rpython2javascript(webjs, FUNCTION_LIST, use_pdb=False) assert source Modified: py/trunk/py/test/report/testing/test_webjs.py ============================================================================== --- py/trunk/py/test/report/testing/test_webjs.py (original) +++ py/trunk/py/test/report/testing/test_webjs.py Tue Sep 9 19:02:28 2008 @@ -12,8 +12,8 @@ mod.dom = dom mod.schedule_callbacks = schedule_callbacks - from py.__.test.dsession import webjs - from py.__.test.dsession.web import exported_methods + from py.__.test.report import webjs + from py.__.test.report.web import exported_methods mod.webjs = webjs mod.exported_methods = exported_methods mod.here = py.magic.autopath().dirpath() @@ -28,8 +28,8 @@ mod.dom = dom dom.window = dom.Window(html) dom.document = dom.window.document - from py.__.test.dsession import webjs - from py.__.test.dsession.web import exported_methods + from py.__.test.report import webjs + from py.__.test.report.web import exported_methods mod.webjs = webjs mod.exported_methods = exported_methods Modified: py/trunk/py/test/report/web.py ============================================================================== --- py/trunk/py/test/report/web.py (original) +++ py/trunk/py/test/report/web.py Tue Sep 9 19:02:28 2008 @@ -14,10 +14,8 @@ import socket import py -from py.__.test.dsession.dsession import RSession -from py.__.test import event -from py.__.test import collect -from py.__.test.dsession.webdata import json +from py.test import collect +from py.__.test.report.webdata import json DATADIR = py.path.local(__file__).dirpath("webdata") FUNCTION_LIST = ["main", "show_skip", "show_traceback", "show_info", "hide_info", Modified: py/trunk/py/test/report/webjs.py ============================================================================== --- py/trunk/py/test/report/webjs.py (original) +++ py/trunk/py/test/report/webjs.py Tue Sep 9 19:02:28 2008 @@ -3,7 +3,7 @@ """ import py -from py.__.test.dsession.web import exported_methods +from py.__.test.report.web import exported_methods try: from pypy.translator.js.modules import dom from pypy.translator.js.helper import __show_traceback From hpk at codespeak.net Tue Sep 9 19:15:39 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 9 Sep 2008 19:15:39 +0200 (CEST) Subject: [py-svn] r58013 - py/trunk/py/test/testing Message-ID: <20080909171539.999D5169F9D@codespeak.net> Author: hpk Date: Tue Sep 9 19:15:37 2008 New Revision: 58013 Modified: py/trunk/py/test/testing/acceptance_test.py Log: bah, forgot that helper uses fnmatch-style matching, not regular expression Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Tue Sep 9 19:15:37 2008 @@ -211,7 +211,7 @@ """) result = self.runpytest(p1) assert_lines_contain_lines(result.outlines, [ - ".*test_fail.py F", + "*test_fail.py F", "====* FAILURES *====", "____*____", "", @@ -242,7 +242,7 @@ """) result = self.runpytest(p1) assert_lines_contain_lines(result.outlines, [ - ".*test_one.py .F", + "*test_one.py .F", "====* FAILURES *====", "____*____", "*test_one.py:8: ValueError", From hpk at codespeak.net Tue Sep 9 19:47:43 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 9 Sep 2008 19:47:43 +0200 (CEST) Subject: [py-svn] r58017 - in py/trunk/py: cmdline/testing rest/testing Message-ID: <20080909174743.3E53D169FBC@codespeak.net> Author: hpk Date: Tue Sep 9 19:47:39 2008 New Revision: 58017 Modified: py/trunk/py/cmdline/testing/test_cmdline.py py/trunk/py/rest/testing/test_directive.py Log: fixing two more glitches Modified: py/trunk/py/cmdline/testing/test_cmdline.py ============================================================================== --- py/trunk/py/cmdline/testing/test_cmdline.py (original) +++ py/trunk/py/cmdline/testing/test_cmdline.py Tue Sep 9 19:47:39 2008 @@ -11,7 +11,7 @@ def test_search_in_filename(self): p = self.makepyfile(hello="def x(): pass") - result = self.run("py.lookup", "hello") + result = self.runpybin("py.lookup", "hello") suptest.assert_lines_contain_lines(result.outlines, ['*%s:*' %(p.basename)] ) Modified: py/trunk/py/rest/testing/test_directive.py ============================================================================== --- py/trunk/py/rest/testing/test_directive.py (original) +++ py/trunk/py/rest/testing/test_directive.py Tue Sep 9 19:47:39 2008 @@ -32,7 +32,7 @@ png.remove() def _graphviz_pdf(self): - for exe in 'dot latex epstopdf'.split(): + for exe in 'dot latex epstopdf ps2eps'.split(): if not py.path.local.sysfind(exe): py.test.skip("%r needed" %(exe,)) From hpk at codespeak.net Tue Sep 9 20:18:24 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 9 Sep 2008 20:18:24 +0200 (CEST) Subject: [py-svn] r58020 - py/trunk/py/test/testing Message-ID: <20080909181824.D43E4169EEA@codespeak.net> Author: hpk Date: Tue Sep 9 20:18:22 2008 New Revision: 58020 Modified: py/trunk/py/test/testing/acceptance_test.py Log: streamline pdb pexpect test Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Tue Sep 9 20:18:22 2008 @@ -404,13 +404,14 @@ child = spawn("%s %s --pdb test_one.py" % (py.std.sys.executable, pytestpath)) - child.expect("(Pdb)", timeout=EXPECTTIMEOUT) + child.timeout = EXPECTTIMEOUT + child.expect("(Pdb)") child.sendline("l") - child.expect(".*def test_1.*", timeout=EXPECTTIMEOUT) + child.expect(".*def test_1.*") child.sendeof() - child.expect("failures: 1", timeout=EXPECTTIMEOUT) - child.wait() - + child.expect("failures: 1") + if child.isalive(): + child.wait() def test_simple_looponfailing_interaction(self): spawn = self.getspawn() From hpk at codespeak.net Tue Sep 9 20:56:20 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 9 Sep 2008 20:56:20 +0200 (CEST) Subject: [py-svn] r58021 - py/trunk Message-ID: <20080909185620.5C59216A1CE@codespeak.net> Author: hpk Date: Tue Sep 9 20:56:18 2008 New Revision: 58021 Modified: py/trunk/MANIFEST py/trunk/setup.py Log: regen setup Modified: py/trunk/MANIFEST ============================================================================== --- py/trunk/MANIFEST (original) +++ py/trunk/MANIFEST Tue Sep 9 20:56:18 2008 @@ -202,23 +202,6 @@ py/execnet/testing/test_gateway.py py/execnet/testing/test_pickle.py py/execnet/testing/test_rsync.py -py/green/__init__.py -py/green/conftest.py -py/green/greenexecnet.py -py/green/greensock2.py -py/green/msgstruct.py -py/green/pipe/__init__.py -py/green/pipe/common.py -py/green/pipe/fd.py -py/green/pipe/gsocket.py -py/green/pipe/mp.py -py/green/pipelayer.py -py/green/server/__init__.py -py/green/server/httpserver.py -py/green/test/__init__.py -py/green/test/test_greenexecnet.py -py/green/test/test_greensock2.py -py/green/test/test_pipelayer.py py/initpkg.py py/io/__init__.py py/io/dupfile.py @@ -354,7 +337,6 @@ py/test/dsession/masterslave.py py/test/dsession/mypickle.py py/test/dsession/testing/__init__.py -py/test/dsession/testing/basetest.py py/test/dsession/testing/test_dsession.py py/test/dsession/testing/test_functional_dsession.py py/test/dsession/testing/test_hostmanage.py @@ -396,7 +378,6 @@ py/test/testing/import_test/package/module_that_imports_shared_lib.py py/test/testing/import_test/package/shared_lib.py py/test/testing/import_test/package/test_import.py -py/test/testing/setupdata.py py/test/testing/suptest.py py/test/testing/test_collect.py py/test/testing/test_compat.py @@ -406,14 +387,9 @@ py/test/testing/test_doctest.py py/test/testing/test_event.py py/test/testing/test_outcome.py -py/test/testing/test_repevent.py py/test/testing/test_runner_functional.py py/test/testing/test_session.py py/test/testing/test_setup_nested.py -py/test/web/__init__.py -py/test/web/exception.py -py/test/web/post_multipart.py -py/test/web/webcheck.py py/thread/__init__.py py/thread/io.py py/thread/pool.py Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Tue Sep 9 20:56:18 2008 @@ -90,10 +90,6 @@ 'py.execnet', 'py.execnet.script', 'py.execnet.testing', - 'py.green', - 'py.green.pipe', - 'py.green.server', - 'py.green.test', 'py.io', 'py.io.testing', 'py.log', @@ -124,7 +120,6 @@ 'py.test.report.webdata', 'py.test.testing', 'py.test.testing.import_test.package', - 'py.test.web', 'py.thread', 'py.thread.testing', 'py.tool', From hpk at codespeak.net Tue Sep 9 20:56:34 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 9 Sep 2008 20:56:34 +0200 (CEST) Subject: [py-svn] r58022 - py/build Message-ID: <20080909185634.386D216A12D@codespeak.net> Author: hpk Date: Tue Sep 9 20:56:32 2008 New Revision: 58022 Modified: py/build/gensetup.py Log: exclude contrib from packaging Modified: py/build/gensetup.py ============================================================================== --- py/build/gensetup.py (original) +++ py/build/gensetup.py Tue Sep 9 20:56:32 2008 @@ -26,8 +26,10 @@ self.lines = [] self.wcinfo = self.wcbasedir.info() self.wcstatus = self.wcbasedir.status(rec=True) + contrib = self.wcbasedir.join("contrib") self.allpaths = [x for x in self.wcstatus.allpath() - if x not in self.wcstatus.unknown and + if not x.relto(contrib) and x != contrib and + x not in self.wcstatus.unknown and x not in self.wcstatus.external and x not in self.wcstatus.ignored and not self.isexcluded(x)] From hpk at codespeak.net Tue Sep 9 23:36:32 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 9 Sep 2008 23:36:32 +0200 (CEST) Subject: [py-svn] r58026 - py/trunk/py/test/testing Message-ID: <20080909213632.7C96A16A272@codespeak.net> Author: hpk Date: Tue Sep 9 23:36:30 2008 New Revision: 58026 Modified: py/trunk/py/test/testing/acceptance_test.py Log: avoiding an issue with older pexpect versions Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Tue Sep 9 23:36:30 2008 @@ -389,8 +389,12 @@ def getspawn(self): try: import pexpect - except ImportError: - py.test.skip("need pexpect") + ver = tuple(map(int, pexpect.__version__.split("."))) + if ver < (2,3): # because sendeof() fails on sys.stdin.fileno() otherwise + # because we are running captured + raise AttributeError + except (ImportError, AttributeError): + py.test.skip("need pexpect version >= 2.3") def spawn(cmd): return pexpect.spawn(cmd, logfile=self.tmpdir.join("spawn.out").open("w")) return spawn From hpk at codespeak.net Tue Sep 9 23:40:21 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 9 Sep 2008 23:40:21 +0200 (CEST) Subject: [py-svn] r58027 - py/trunk/py/test/testing Message-ID: <20080909214021.1013416A05C@codespeak.net> Author: hpk Date: Tue Sep 9 23:40:21 2008 New Revision: 58027 Modified: py/trunk/py/test/testing/acceptance_test.py Log: skip specific test instead of all pexpect ones. Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Tue Sep 9 23:40:21 2008 @@ -389,17 +389,20 @@ def getspawn(self): try: import pexpect - ver = tuple(map(int, pexpect.__version__.split("."))) - if ver < (2,3): # because sendeof() fails on sys.stdin.fileno() otherwise - # because we are running captured - raise AttributeError - except (ImportError, AttributeError): - py.test.skip("need pexpect version >= 2.3") + except ImportError: + py.test.skip("cannot import pexpect") def spawn(cmd): return pexpect.spawn(cmd, logfile=self.tmpdir.join("spawn.out").open("w")) return spawn + + def requirespexpect(self, version_needed): + import pexpect + ver = tuple(map(int, pexpect.__version__.split("."))) + if ver < version_needed: + py.test.skip("pexpect version %s needed" %(".".join(map(str, version_needed)))) def test_pdb_interaction(self): + self.requirespexpect((2,3)) spawn = self.getspawn() self.makepyfile(test_one=""" def test_1(): From hpk at codespeak.net Tue Sep 9 23:53:40 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 9 Sep 2008 23:53:40 +0200 (CEST) Subject: [py-svn] r58028 - in py/trunk: contrib/webcheck py/test/web Message-ID: <20080909215340.549C6168020@codespeak.net> Author: hpk Date: Tue Sep 9 23:53:38 2008 New Revision: 58028 Added: py/trunk/py/test/web/ (props changed) - copied from r58008, py/trunk/py/test/web/ Removed: py/trunk/contrib/webcheck/ Log: reverting 58009 - webcheck is actually used although not directly tested. From hpk at codespeak.net Wed Sep 10 00:08:24 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 10 Sep 2008 00:08:24 +0200 (CEST) Subject: [py-svn] r58030 - py/trunk/py/code/testing Message-ID: <20080909220824.5ED3C16A24C@codespeak.net> Author: hpk Date: Wed Sep 10 00:08:21 2008 New Revision: 58030 Modified: py/trunk/py/code/testing/test_excinfo.py Log: check for different output according to cpython version Modified: py/trunk/py/code/testing/test_excinfo.py ============================================================================== --- py/trunk/py/code/testing/test_excinfo.py (original) +++ py/trunk/py/code/testing/test_excinfo.py Wed Sep 10 00:08:21 2008 @@ -215,8 +215,11 @@ except ValueError: excinfo = py.code.ExceptionInfo() s = str(excinfo.traceback[-1]) - assert s == " File '':1 in ?\n ???\n" - + if py.std.sys.version_info < (2,5): + assert s == " File '':1 in ?\n ???\n" + else: + assert s == " File '':1 in \n ???\n" + def test_entrysource_Queue_example(): import Queue try: From hpk at codespeak.net Wed Sep 10 11:47:40 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 10 Sep 2008 11:47:40 +0200 (CEST) Subject: [py-svn] r58034 - in py/trunk/py: misc misc/testing path test Message-ID: <20080910094740.07F6D16A296@codespeak.net> Author: hpk Date: Wed Sep 10 11:47:37 2008 New Revision: 58034 Added: py/trunk/py/misc/testing/test_warn.py (contents, props changed) py/trunk/py/misc/warn.py (contents, props changed) Modified: py/trunk/py/path/common.py py/trunk/py/test/collect.py Log: introduce APIWARN helper allow for subscriptions to produced warnings use it from some places Added: py/trunk/py/misc/testing/test_warn.py ============================================================================== --- (empty file) +++ py/trunk/py/misc/testing/test_warn.py Wed Sep 10 11:47:37 2008 @@ -0,0 +1,53 @@ +import py +from py.__.misc.warn import WarningBus +mypath = py.magic.autopath() + +class TestWarningBus: + def setup_method(self, method): + self.wb = WarningBus() + self.warnings = [] + self.wb.subscribe(self.warnings.append) + + def test_basic(self): + self.wb.warn("hello") + assert len(self.warnings) == 1 + self.wb.unsubscribe(self.warnings.append) + self.wb.warn("this") + assert len(self.warnings) == 1 + w = self.warnings[0] + + def test_location(self): + self.wb.warn("again") + warning = self.warnings[0] + lno = self.test_location.im_func.func_code.co_firstlineno + 1 + assert warning.lineno == lno + assert warning.path == mypath + locstr = "%s:%d: " %(mypath, lno+1,) + assert repr(warning).startswith(locstr) + assert str(warning) == warning.msg + + def test_stacklevel(self): + l = [] + self.wb.subscribe(l.append) + def f(): + self.wb.warn("x", stacklevel=2) + # 5 + # 6 + f() + lno = self.test_stacklevel.im_func.func_code.co_firstlineno + 7 + warning = l[0] + assert warning.lineno == lno + + def test_forwarding_to_warnings_module(self): + self.wb._setforwarding() + py.test.deprecated_call(self.wb.warn, "x") + + def test_apiwarn(self): + self.wb.apiwarn("3.0", "xxx") + warning = self.warnings[0] + assert warning.msg == "xxx (since version 3.0)" + +def test_APIWARN(): + from py.__.misc.warn import APIWARN + wb = APIWARN.im_self + assert wb._forward in wb._eventbus._subscribers Added: py/trunk/py/misc/warn.py ============================================================================== --- (empty file) +++ py/trunk/py/misc/warn.py Wed Sep 10 11:47:37 2008 @@ -0,0 +1,74 @@ +import py, sys +from py.__.test.event import EventBus + +class Warning(py.std.exceptions.DeprecationWarning): + def __init__(self, msg, path, lineno): + self.msg = msg + self.path = path + self.lineno = lineno + def __repr__(self): + return "%s:%d: %s" %(self.path, self.lineno+1, self.msg) + def __str__(self): + return self.msg + +class WarningBus(object): + def __init__(self): + self._eventbus = EventBus() + + def subscribe(self, callable): + self._eventbus.subscribe(callable) + + def unsubscribe(self, callable): + self._eventbus.unsubscribe(callable) + + def _setforwarding(self): + self._eventbus.subscribe(self._forward) + def _forward(self, warning): + py.std.warnings.warn_explicit(warning, category=Warning, + filename=str(warning.path), + lineno=warning.lineno, + registry=py.std.warnings.__dict__.setdefault( + "__warningsregistry__", {}) + ) + + def apiwarn(self, startversion, msg, stacklevel=1): + # below is mostly COPIED from python2.4/warnings.py's def warn() + # Get context information + msg = "%s (since version %s)" %(msg, startversion) + self.warn(msg, stacklevel=stacklevel+1) + + def warn(self, msg, stacklevel=1): + try: + caller = sys._getframe(stacklevel) + except ValueError: + globals = sys.__dict__ + lineno = 1 + else: + globals = caller.f_globals + lineno = caller.f_lineno + if '__name__' in globals: + module = globals['__name__'] + else: + module = "" + filename = globals.get('__file__') + if filename: + fnl = filename.lower() + if fnl.endswith(".pyc") or fnl.endswith(".pyo"): + filename = filename[:-1] + else: + if module == "__main__": + try: + filename = sys.argv[0] + except AttributeError: + # embedded interpreters don't have sys.argv, see bug #839151 + filename = '__main__' + if not filename: + filename = module + path = py.path.local(filename) + warning = Warning(msg, path, lineno) + self._eventbus.notify(warning) + +# singleton api warner for py lib +apiwarner = WarningBus() +apiwarner._setforwarding() +APIWARN = apiwarner.apiwarn Modified: py/trunk/py/path/common.py ============================================================================== --- py/trunk/py/path/common.py (original) +++ py/trunk/py/path/common.py Wed Sep 10 11:47:37 2008 @@ -5,6 +5,7 @@ from __future__ import generators import os, sys import py +from py.__.misc.warn import APIWARN def checktype(pathinstance, kw): names = ('local', 'svnwc', 'svnurl', 'py', ) @@ -21,9 +22,10 @@ kwargs-specified specification. """ def __init__(self, **kwargs): - py.std.warnings.warn("py.path.checker is deprecated, construct " - "calls to pathobj.check() instead", - DeprecationWarning, stacklevel=2) + APIWARN("0.9.0", + "py.path.checker is deprecated, construct " + "calls to pathobj.check() instead", + ) self.kwargs = kwargs def __call__(self, p): return p.check(**self.kwargs) Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Wed Sep 10 11:47:37 2008 @@ -19,6 +19,7 @@ """ import py +from py.__.misc.warn import APIWARN def configproperty(name): def fget(self): @@ -493,10 +494,16 @@ def depwarn(msg): - py.std.warnings.warn(msg, DeprecationWarning) + APIWARN("1.0", msg, stacklevel=2) def warnoldcollect(): - return depwarn("implement collector.collect() instead of collector.run() and collector.join()") + APIWARN("1.0", + "implement collector.collect() instead of " + "collector.run() and collector.join()", + stacklevel=2) def warnoldtestrun(): - return depwarn("implement item.runtest() instead of item.run() and item.execute()") + APIWARN("1.0", + "implement item.runtest() instead of " + "item.run() and item.execute()", + stacklevel=2) From hpk at codespeak.net Wed Sep 10 11:48:46 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 10 Sep 2008 11:48:46 +0200 (CEST) Subject: [py-svn] r58035 - in py/trunk/py/execnet: . testing Message-ID: <20080910094846.69CAE16A290@codespeak.net> Author: hpk Date: Wed Sep 10 11:48:44 2008 New Revision: 58035 Modified: py/trunk/py/execnet/register.py py/trunk/py/execnet/testing/test_gateway.py Log: allow for passing in ssh_config files, deprecat "identity" argument Modified: py/trunk/py/execnet/register.py ============================================================================== --- py/trunk/py/execnet/register.py (original) +++ py/trunk/py/execnet/register.py Wed Sep 10 11:48:44 2008 @@ -2,6 +2,8 @@ import os, inspect, socket import sys from py.magic import autopath ; mypath = autopath() +from py.__.misc.warn import APIWARN + import py if sys.platform == "win32": win32 = True @@ -55,6 +57,7 @@ class PopenCmdGateway(InstallableGateway): def __init__(self, cmd): infile, outfile = os.popen2(cmd) + self._cmd = cmd io = inputoutput.Popen2IO(infile, outfile) super(PopenCmdGateway, self).__init__(io=io) @@ -130,14 +133,16 @@ class SshGateway(PopenCmdGateway): - """ This Gateway provides interaction with a remote process, + """ This Gateway provides interaction with a remote Python process, established via the 'ssh' command line binary. The remote side needs to have a Python interpreter executable. """ - def __init__(self, sshaddress, remotepython='python', identity=None): + def __init__(self, sshaddress, remotepython='python', + identity=None, ssh_config=None): """ instantiate a remote ssh process with the given 'sshaddress' and remotepython version. - you may specify an 'identity' filepath. + you may specify an ssh_config file. + DEPRECATED: you may specify an 'identity' filepath. """ self.remoteaddress = sshaddress remotecmd = '%s -u -c "exec input()"' % (remotepython,) @@ -147,9 +152,13 @@ cmdline[i] = "'" + cmdline[i].replace("'", "'\\''") + "'" cmd = 'ssh -C' if identity is not None: + APIWARN("1.0", "pass in 'ssh_config' file instead of identity") cmd += ' -i %s' % (identity,) + if ssh_config is not None: + cmd += ' -F %s' % (ssh_config) cmdline.insert(0, cmd) - super(SshGateway, self).__init__(' '.join(cmdline)) + cmd = ' '.join(cmdline) + super(SshGateway, self).__init__(cmd) def _remote_bootstrap_gateway(self, io, s=""): extra = "\n".join([ Modified: py/trunk/py/execnet/testing/test_gateway.py ============================================================================== --- py/trunk/py/execnet/testing/test_gateway.py (original) +++ py/trunk/py/execnet/testing/test_gateway.py Wed Sep 10 11:48:44 2008 @@ -439,6 +439,9 @@ text = c1.receive() assert text.find("execution disallowed") != -1 +class BasicCmdbasedRemoteExecution(BasicRemoteExecution): + def test_cmdattr(self): + assert hasattr(self.gw, '_cmd') def test_channel_endmarker_remote_killterm(): gw = py.execnet.PopenGateway() @@ -571,20 +574,31 @@ py.test.skip("no known ssh target, use -S to set one") cls.gw = py.execnet.SshGateway(option.sshtarget) + def test_sshconfig_functional(self): + tmpdir = py.test.ensuretemp("test_sshconfig") + ssh_config = tmpdir.join("ssh_config") + ssh_config.write( + "Host alias123\n" + " HostName %s\n" % (option.sshtarget,)) + gw = py.execnet.SshGateway("alias123", ssh_config=ssh_config) + assert gw._cmd.find("-F") != -1 + assert gw._cmd.find(str(ssh_config)) != -1 + pid = gw.remote_exec("import os ; channel.send(os.getpid())").receive() + gw.exit() + def test_sshaddress(self): assert self.gw.remoteaddress == option.sshtarget - def test_failed_connexion(self): - gw = py.execnet.SshGateway('nowhere.codespeak.net') - try: - channel = gw.remote_exec("...") - except IOError: - pass # connexion failed already - else: - # connexion did not fail yet - py.test.raises(EOFError, channel.receive) - # now it did - py.test.raises(IOError, gw.remote_exec, "...") + def test_connexion_failes_on_non_existing_hosts(self): + py.test.raises(IOError, + "py.execnet.SshGateway('nowhere.codespeak.net')") + + def test_deprecated_identity(self): + py.test.deprecated_call( + py.test.raises, IOError, + py.execnet.SshGateway, + 'nowhere.codespeak.net', identity='qwe') + def test_threads(): gw = py.execnet.PopenGateway() From hpk at codespeak.net Wed Sep 10 13:28:44 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 10 Sep 2008 13:28:44 +0200 (CEST) Subject: [py-svn] r58037 - py/trunk/contrib Message-ID: <20080910112844.288D316A2CC@codespeak.net> Author: hpk Date: Wed Sep 10 13:28:42 2008 New Revision: 58037 Added: py/trunk/contrib/sysinfo.py - copied, changed from r58036, pypy/build/doc/tool/sysinfo.py Log: adding small sysinfo script for retrieving remote host information through execnet. Copied: py/trunk/contrib/sysinfo.py (from r58036, pypy/build/doc/tool/sysinfo.py) ============================================================================== --- pypy/build/doc/tool/sysinfo.py (original) +++ py/trunk/contrib/sysinfo.py Wed Sep 10 13:28:42 2008 @@ -1,25 +1,30 @@ +""" +sysinfo.py [host1] [host2] [options] + +obtain system info from remote machine. +""" import py import sys -mydir = py.magic.autopath().dirpath() -ssh_config = mydir.dirpath("ssh_config") -class SshConfig(object): - def __init__(self, path): - self.path = path - - def parsehosts(self, ignores): - if isinstance(ignores, str): - ignores = ignores.split(",") - l = [] - rex = py.std.re.compile(r'Host\s*(\S+)') - for line in self.path.readlines(): - m = rex.match(line) - if m is not None: - sshname, = m.groups() - if sshname not in ignores: - l.append(sshname) - return l +optparse = py.compat.optparse + +parser = optparse.OptionParser(usage=__doc__) +parser.add_option("-f", "--sshconfig", action="store", dest="ssh_config", default=None, + help="use given ssh config file, and add info all contained hosts for getting info") +parser.add_option("-i", "--ignore", action="store", dest="ignores", default=None, + help="ignore hosts (useful if the list of hostnames come from a file list)") + +def parsehosts(path): + path = py.path.local(path) + l = [] + rex = py.std.re.compile(r'Host\s*(\S+)') + for line in path.readlines(): + m = rex.match(line) + if m is not None: + sshname, = m.groups() + l.append(sshname) + return l class RemoteInfo: def __init__(self, gateway): @@ -42,6 +47,12 @@ def islinux(self): return self.getmodattr('sys.platform').find("linux") != -1 + def getfqdn(self): + return self.exreceive(""" + import socket + channel.send(socket.getfqdn()) + """) + def getmemswap(self): if self.islinux(): return self.exreceive(""" @@ -74,7 +85,7 @@ def error(*args): debug("ERROR", args[0] + ":", *args[1:]) -def getinfo(sshname, loginfo=sys.stdout): +def getinfo(sshname, ssh_config=None, loginfo=sys.stdout): debug("connecting to", sshname) try: gw = py.execnet.SshGateway(sshname, ssh_config=ssh_config) @@ -84,6 +95,7 @@ ri = RemoteInfo(gw) #print "%s info:" % sshname prefix = sshname.upper() + " " + print >>loginfo, prefix, "fqdn:", ri.getfqdn() for attr in ( "sys.platform", "sys.version_info", @@ -106,11 +118,15 @@ return ri if __name__ == '__main__': - if len(sys.argv) < 2: - sc = SshConfig(ssh_config) - hosts = sc.parsehosts(ignores="t20,snake") - else: - hosts = sys.argv[1:] + options, args = parser.parse_args() + hosts = list(args) + ssh_config = options.ssh_config + if ssh_config: + hosts.extend(parsehosts(ssh_config)) + ignores = options.ignores or () + if ignores: + ignores = ignores.split(",") for host in hosts: - getinfo(host) + if host not in ignores: + getinfo(host, ssh_config=ssh_config) From arigo at codespeak.net Fri Sep 12 21:16:38 2008 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 12 Sep 2008 21:16:38 +0200 (CEST) Subject: [py-svn] r58090 - py/trunk/py/test Message-ID: <20080912191638.9CF4016A49E@codespeak.net> Author: arigo Date: Fri Sep 12 21:16:34 2008 New Revision: 58090 Modified: py/trunk/py/test/collect.py Log: Add a hack with a comment that explains why I think it is a hack. I fear I don't care enough about --tb=short to do the "proper" fix as documented in the comment. Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Fri Sep 12 21:16:34 2008 @@ -268,9 +268,16 @@ def _repr_failure_py(self, excinfo, outerr): excinfo.traceback = self._prunetraceback(excinfo.traceback) + # XXX temporary hack: getrepr() should not take a 'style' argument + # at all; it should record all data in all cases, and the style + # should be parametrized in toterminal(). + if self._config.option.tbstyle == "short": + style = "short" + else: + style = "long" repr = excinfo.getrepr(funcargs=True, showlocals=self._config.option.showlocals, - style=self._config.option.tbstyle) + style=style) for secname, content in zip(["out", "err"], outerr): if content: repr.addsection("Captured std%s" % secname, content.rstrip()) From arigo at codespeak.net Fri Sep 12 21:31:31 2008 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 12 Sep 2008 21:31:31 +0200 (CEST) Subject: [py-svn] r58091 - py/trunk/py/test/report Message-ID: <20080912193131.02F7216A4D8@codespeak.net> Author: arigo Date: Fri Sep 12 21:31:31 2008 New Revision: 58091 Modified: py/trunk/py/test/report/terminal.py Log: Minor refactoring of a few methods. The point is to add a couple of hooks for my conftest. This is a bit hard to test, and I don't even know if it's worth a comment, as I'm ready to fix my conftest when needed. Modified: py/trunk/py/test/report/terminal.py ============================================================================== --- py/trunk/py/test/report/terminal.py (original) +++ py/trunk/py/test/report/terminal.py Fri Sep 12 21:31:31 2008 @@ -48,12 +48,21 @@ self.ensure_newline() self._tw.sep(sep, title, **markup) + def getoutcomeletter(self, item): + return item.outcome.shortrepr + def getoutcomeword(self, item): if item.passed: return self._tw.markup("PASS", green=True) elif item.failed: return self._tw.markup("FAIL", red=True) elif item.skipped: return "SKIP" else: return self._tw.markup("???", red=True) + def getcollectoutcome(self, item): + if item.skipped: + return str(item.outcome.longrepr.message) + else: + return str(item.outcome.longrepr.reprcrash.message) + def rep_InternalException(self, ev): for line in str(ev.repr).split("\n"): self.write_line("InternalException: " + line) @@ -93,7 +102,7 @@ super(TerminalReporter, self).rep_ItemTestReport(ev) fspath = ev.colitem.fspath if not self.config.option.verbose: - self.write_fspath_result(fspath, ev.outcome.shortrepr) + self.write_fspath_result(fspath, self.getoutcomeletter(ev)) else: info = ev.colitem.repr_metainfo() line = info.verboseline(basedir=self.curdir) + " " @@ -104,11 +113,8 @@ super(TerminalReporter, self).rep_CollectionReport(ev) fspath = ev.colitem.fspath if ev.failed or ev.skipped: - if ev.skipped: - msg = ev.outcome.longrepr.message - else: - msg = ev.outcome.longrepr.reprcrash.message - self.write_fspath_result(fspath, "- " + str(msg)) + msg = self.getcollectoutcome(ev) + self.write_fspath_result(fspath, "- " + msg) def rep_TestrunStart(self, ev): super(TerminalReporter, self).rep_TestrunStart(ev) From arigo at codespeak.net Fri Sep 12 21:41:32 2008 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 12 Sep 2008 21:41:32 +0200 (CEST) Subject: [py-svn] r58092 - in py/trunk/py/test/report: . testing Message-ID: <20080912194132.542D416A1F4@codespeak.net> Author: arigo Date: Fri Sep 12 21:41:31 2008 New Revision: 58092 Modified: py/trunk/py/test/report/terminal.py py/trunk/py/test/report/testing/test_terminal.py Log: Ensure that the path is printed before the 1st test of a module starts running (even when -v is not specified). Modified: py/trunk/py/test/report/terminal.py ============================================================================== --- py/trunk/py/test/report/terminal.py (original) +++ py/trunk/py/test/report/terminal.py Fri Sep 12 21:41:31 2008 @@ -93,6 +93,11 @@ if ev.host: extra = "-> " + ev.host.hostid self.write_ensure_prefix(line, extra) + else: + # ensure that the path is printed before the 1st test of + # a module starts running + fspath = ev.item.fspath + self.write_fspath_result(fspath, "") def rep_RescheduleItems(self, ev): if self.config.option.debug: Modified: py/trunk/py/test/report/testing/test_terminal.py ============================================================================== --- py/trunk/py/test/report/testing/test_terminal.py (original) +++ py/trunk/py/test/report/testing/test_terminal.py Fri Sep 12 21:41:31 2008 @@ -192,3 +192,17 @@ assert 'FAILURES' not in s assert '--calling--' not in s assert 'IndexError' not in s + + def test_show_path_before_running_test(self): + modcol = self.getmodulecol(""" + def test_foobar(): + pass + """, withsession=True) + stringio = py.std.cStringIO.StringIO() + rep = TerminalReporter(modcol._config, bus=self.session.bus, file=stringio) + l = list(self.session.genitems([modcol])) + assert len(l) == 1 + rep.processevent(event.ItemStart(l[0])) + s = popvalue(stringio) + print s + assert s.find("test_show_path_before_running_test.py") != -1 From arigo at codespeak.net Fri Sep 12 21:54:46 2008 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 12 Sep 2008 21:54:46 +0200 (CEST) Subject: [py-svn] r58093 - in py/trunk/py/test/report: . testing Message-ID: <20080912195446.576D516A3B0@codespeak.net> Author: arigo Date: Fri Sep 12 21:54:41 2008 New Revision: 58093 Modified: py/trunk/py/test/report/terminal.py py/trunk/py/test/report/testing/test_terminal.py Log: (pedronis, arigo) Show tracebacks for the failures even when py.test is interrupted by Ctrl-C. Modified: py/trunk/py/test/report/terminal.py ============================================================================== --- py/trunk/py/test/report/terminal.py (original) +++ py/trunk/py/test/report/terminal.py Fri Sep 12 21:54:41 2008 @@ -129,10 +129,9 @@ def rep_TestrunFinish(self, ev): self._tw.line("") - if ev.exitstatus == 0 or ev.exitstatus == 1: - self.summary_failures() - self.summary_skips() - elif ev.exitstatus == 2: + self.summary_failures() + self.summary_skips() + if ev.exitstatus == 2: self.write_sep("!", "KEYBOARD INTERRUPT") self.summary_deselected() self.summary_stats() Modified: py/trunk/py/test/report/testing/test_terminal.py ============================================================================== --- py/trunk/py/test/report/testing/test_terminal.py (original) +++ py/trunk/py/test/report/testing/test_terminal.py Fri Sep 12 21:54:41 2008 @@ -206,3 +206,26 @@ s = popvalue(stringio) print s assert s.find("test_show_path_before_running_test.py") != -1 + + def test_keyboard_interrupt(self): + modcol = self.getmodulecol(""" + def test_foobar(): + assert 0 + def test_spamegg(): + import py; py.test.skip('skip me please!') + """, configargs=("--showskipsummary",), withsession=True) + stringio = py.std.cStringIO.StringIO() + rep = TerminalReporter(modcol._config, bus=self.session.bus, file=stringio) + rep.processevent(event.TestrunStart()) + for item in self.session.genitems([modcol]): + ev = basic_run_report(item) + rep.processevent(ev) + s = popvalue(stringio) + assert s.find("test_keyboard_interrupt.py Fs") != -1 + rep.processevent(event.TestrunFinish(exitstatus=2)) + assert_stringio_contains_lines(stringio, [ + " def test_foobar():", + "> assert 0", + "E assert 0", + ]) + assert "Skipped: 'skip me please!'" in stringio.getvalue() From arigo at codespeak.net Fri Sep 12 22:05:04 2008 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 12 Sep 2008 22:05:04 +0200 (CEST) Subject: [py-svn] r58094 - py/trunk/py/test/report Message-ID: <20080912200504.86C8716A3E3@codespeak.net> Author: arigo Date: Fri Sep 12 22:04:59 2008 New Revision: 58094 Modified: py/trunk/py/test/report/terminal.py Log: (pedronis, arigo) Partial revert: don't print traceback failures after InternalExceptions. Modified: py/trunk/py/test/report/terminal.py ============================================================================== --- py/trunk/py/test/report/terminal.py (original) +++ py/trunk/py/test/report/terminal.py Fri Sep 12 22:04:59 2008 @@ -129,8 +129,9 @@ def rep_TestrunFinish(self, ev): self._tw.line("") - self.summary_failures() - self.summary_skips() + if ev.exitstatus in (0, 1, 2): + self.summary_failures() + self.summary_skips() if ev.exitstatus == 2: self.write_sep("!", "KEYBOARD INTERRUPT") self.summary_deselected() From arigo at codespeak.net Fri Sep 12 22:35:19 2008 From: arigo at codespeak.net (arigo at codespeak.net) Date: Fri, 12 Sep 2008 22:35:19 +0200 (CEST) Subject: [py-svn] r58095 - in py/trunk/py/test: . report report/testing Message-ID: <20080912203519.D577B16A350@codespeak.net> Author: arigo Date: Fri Sep 12 22:35:18 2008 New Revision: 58095 Modified: py/trunk/py/test/event.py py/trunk/py/test/report/terminal.py py/trunk/py/test/report/testing/test_terminal.py py/trunk/py/test/session.py Log: (pedronis, arigo) KeyboardInterrupt handling: * in --verbose mode, print a detailed traceback at the end of the report. * in non-verbose mode, only print the file name and line number where the KeyboardInterrupt occurred. That's the minimal amount of information that is of any help at all to locate an infinite loop somewhere. Modified: py/trunk/py/test/event.py ============================================================================== --- py/trunk/py/test/event.py (original) +++ py/trunk/py/test/event.py Fri Sep 12 22:35:18 2008 @@ -45,8 +45,12 @@ self.timestart = time.time() class TestrunFinish(BaseEvent): - def __init__(self, exitstatus=0): + def __init__(self, exitstatus=0, excinfo=None): self.exitstatus = exitstatus + if excinfo is None: + self.excrepr = None + else: + self.excrepr = excinfo.getrepr() self.timeend = time.time() class InternalException(BaseEvent): Modified: py/trunk/py/test/report/terminal.py ============================================================================== --- py/trunk/py/test/report/terminal.py (original) +++ py/trunk/py/test/report/terminal.py Fri Sep 12 22:35:18 2008 @@ -132,6 +132,8 @@ if ev.exitstatus in (0, 1, 2): self.summary_failures() self.summary_skips() + if ev.excrepr is not None: + self.summary_final_exc(ev.excrepr) if ev.exitstatus == 2: self.write_sep("!", "KEYBOARD INTERRUPT") self.summary_deselected() @@ -201,6 +203,13 @@ for num, fspath, lineno, reason in folded_skips: self._tw.line("%s:%d: [%d] %s" %(fspath, lineno, num, reason)) + def summary_final_exc(self, excrepr): + self.write_sep("!") + if self.config.option.verbose: + excrepr.toterminal(self._tw) + else: + excrepr.reprcrash.toterminal(self._tw) + def out_hostinfo(self): self._tw.line("host 0: %s %s - Python %s" % (py.std.sys.platform, Modified: py/trunk/py/test/report/testing/test_terminal.py ============================================================================== --- py/trunk/py/test/report/testing/test_terminal.py (original) +++ py/trunk/py/test/report/testing/test_terminal.py Fri Sep 12 22:35:18 2008 @@ -207,25 +207,41 @@ print s assert s.find("test_show_path_before_running_test.py") != -1 - def test_keyboard_interrupt(self): + def test_keyboard_interrupt(self, verbose=False): modcol = self.getmodulecol(""" def test_foobar(): assert 0 def test_spamegg(): import py; py.test.skip('skip me please!') - """, configargs=("--showskipsummary",), withsession=True) + def test_interrupt_me(): + raise KeyboardInterrupt # simulating the user + """, configargs=("--showskipsummary",) + ("-v",)*verbose, + withsession=True) stringio = py.std.cStringIO.StringIO() rep = TerminalReporter(modcol._config, bus=self.session.bus, file=stringio) rep.processevent(event.TestrunStart()) - for item in self.session.genitems([modcol]): - ev = basic_run_report(item) - rep.processevent(ev) + try: + for item in self.session.genitems([modcol]): + ev = basic_run_report(item) + rep.processevent(ev) + except KeyboardInterrupt: + excinfo = py.code.ExceptionInfo() + else: + py.test.fail("no KeyboardInterrupt??") s = popvalue(stringio) - assert s.find("test_keyboard_interrupt.py Fs") != -1 - rep.processevent(event.TestrunFinish(exitstatus=2)) + if not verbose: + assert s.find("_keyboard_interrupt.py Fs") != -1 + rep.processevent(event.TestrunFinish(exitstatus=2, excinfo=excinfo)) assert_stringio_contains_lines(stringio, [ " def test_foobar():", "> assert 0", "E assert 0", ]) - assert "Skipped: 'skip me please!'" in stringio.getvalue() + text = stringio.getvalue() + assert "Skipped: 'skip me please!'" in text + assert "_keyboard_interrupt.py:6: KeyboardInterrupt" in text + see_details = "raise KeyboardInterrupt # simulating the user" in text + assert see_details == verbose + + def test_verbose_keyboard_interrupt(self): + self.test_keyboard_interrupt(verbose=True) Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Fri Sep 12 22:35:18 2008 @@ -103,9 +103,10 @@ if self.config.option.exitfirst: self.shouldstop = True - def sessionfinishes(self, exitstatus=0): + def sessionfinishes(self, exitstatus=0, excinfo=None): """ teardown any resources after a test run. """ - self.bus.notify(event.TestrunFinish(exitstatus=exitstatus)) + self.bus.notify(event.TestrunFinish(exitstatus=exitstatus, + excinfo=excinfo)) self.bus.unsubscribe(self._processfailures) #self.reporter.deactivate() return self._failurelist @@ -123,6 +124,7 @@ self.sessionstarts() self.bus.notify(makehostup()) exitstatus = outcome.EXIT_OK + captured_excinfo = None try: for item in self.collect(colitems): if self.shouldstop: @@ -131,13 +133,14 @@ self.runtest(item) py.test.collect.Item._setupstate.teardown_all() except KeyboardInterrupt: + captured_excinfo = py.code.ExceptionInfo() exitstatus = outcome.EXIT_INTERRUPTED except: self.bus.notify(event.InternalException()) exitstatus = outcome.EXIT_INTERNALERROR if self._failurelist and exitstatus == 0: exitstatus = outcome.EXIT_TESTSFAILED - self.sessionfinishes(exitstatus=exitstatus) + self.sessionfinishes(exitstatus=exitstatus, excinfo=captured_excinfo) return exitstatus def runpdb(self, excinfo): From hpk at codespeak.net Sat Sep 13 14:42:53 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 13 Sep 2008 14:42:53 +0200 (CEST) Subject: [py-svn] r58104 - py/extradoc/talk/pycon-uk-2008 Message-ID: <20080913124253.5831D16A38F@codespeak.net> Author: hpk Date: Sat Sep 13 14:42:52 2008 New Revision: 58104 Modified: py/extradoc/talk/pycon-uk-2008/pytest.txt Log: some streamlining, updates jit before the talk Modified: py/extradoc/talk/pycon-uk-2008/pytest.txt ============================================================================== --- py/extradoc/talk/pycon-uk-2008/pytest.txt (original) +++ py/extradoc/talk/pycon-uk-2008/pytest.txt Sat Sep 13 14:42:52 2008 @@ -61,9 +61,9 @@ Write and run basic tests =================================== -example from ``py/path/local/testing/test_local.py``:: +real life example: - def test_pypkgdir(): + def test_pypkpath(): datadir = py.test.ensuretemp("pypkgdir") pkg = datadir.ensure('pkg1', dir=1) pkg.ensure("__init__.py") @@ -76,7 +76,7 @@ Putting tests in Test Classes ================================== -example from ``py/path/local/testing/test_local.py``:: +real life example: class TestExecutionOnWindows(LocalSetup): disabled = py.std.sys.platform != 'win32' @@ -92,7 +92,6 @@ ======================== - py.test automatically collects ``test_*`` functions -- use ``py.test --collectonly`` to inspect collection - you can write tests at module global level - assertion failures provide detailed info @@ -124,18 +123,18 @@ ======================================= meet ``py.test --looponfailing``: - - does a full test run - - keeps a set of failing tests - - checks for file changes and re-runs failing tests - - if all failures are fixed, re-run the whole -great for refactoring! +- does a full test run +- keeps a set of failing tests +- checks for file changes and re-runs failing tests +- if all failures fixed, reruns whole test suite +great for refactoring! Setup and Teardown of test state ================================== -look again at the above ``test_local.py``:: +again example from ``test_local.py``:: class LocalSetup: def setup_class(cls): @@ -150,15 +149,14 @@ Skipping tests =================== -Sometimes you need to skip test, use ``py.test.skip``:: +Sometimes you need to skip a test, use ``py.test.skip``:: - class TestSomeClass: - def setup_class(cls): + def test_something_on_windows(): if sys.platform == "win32": py.test.skip("win32 required") + ... -you can also place such calls to "py.test.skip()" inside test functions -or in other test setup functions. +you can also place such calls to "py.test.skip()" in setup functions. Doctests ================= @@ -175,37 +173,43 @@ Customizing py.test =========================== -write "confest.py" files at project or global level: - -- integrate new test "items" or "collectors" +- modify/extend the test collection process - add command line options -- influence the collection process +- register to reporting events (trunk/1.0) +- write conftest.py files - for debugging:: py.test --collectonly py.test --traceconfig +The collection tree +=========================== -Important Internal Objects +- items: runnable tests - ``item.runtest()`` +- collectors: return collectors or items - ``collector.collect()`` +- collection tree is built iteratively +- test collection starts from directories or files (via cmdline) +- test collection not limited to Python files! +- use ``py.test --collectonly`` for debugging + +Important Collection Objects ================================== -- **Session**: iterates over (custom) collection tree, executes tests and reports outcomes. - **Directory** collect files in directory +- **FSCollector** a Collector for a File - **Module** collect python Classes and Functions - **Class**/**Instance** collect test methods - **Generator** collect generative tests - **Function** sets up and executes a python test function - **DoctestFile** collect doctests in a .txt file -The collection tree -=========================== - -- leaves: so called "items", e.g. ``py.test.collect.Function`` -- nodes: so called "collectors" contain further collectors or items -- collection tree is built iterativels -- test collection usually starts from directories or files -- **dynamic lookup in conftest.py files** +Conftest.py files +========================== +- place ``conftest.py`` files in a directory +- project root, subdir or home directory +- collectors/items are looked up in **nearest conftest.py** file. +- this means: **project specific or global extensions** Example conftest.py: add ReST check support ============================================== @@ -213,9 +217,8 @@ look into ``py/doc/conftest.py``: - this produces non-python test items -- ``py.test --collectonly`` shows the custom tree - produces syntax and link checks -- drop the conftest.py file into your own project/doc directory +- drop the conftest.py file into your own project/doc directory! Example conftest.py: html page generation =========================================== @@ -254,7 +257,7 @@ [courtesy Holger Krekel] -- goal: remotely run tests on windows +- goal: remotely run tests on windows from linux - check out py/misc/conftest-socketgatewayrun.py - requires a small server script on the windows side - transfers your source code to windows, runs tests, @@ -280,14 +283,12 @@ - you can extend and modify the collection process - you can add new (non-python) test items -- conftest.py files are usually "drop in" plugins -- often project independent -- **orthogonality** of conftest features +- conftest.py files are meant to be "drop in" plugins -but note: +note: -- customization gets easier with the upcoming 1.0 release, - please subscribe to http://codespeak.net/mailman/listinfo/py-dev +- customization easier with upcoming 1.0 release, + use py/trunk instead of 0.9.2 release recap: main features ====================== @@ -311,5 +312,6 @@ http://pylib.org http://pytest.org -http://merlinux.eu - holger at merlinux.eu +http://merlinux.eu - holger at merlinux.de +https://codespeak.net/svn/py/extradoc/talk/ep2008/pytest.txt From hpk at codespeak.net Sat Sep 13 14:45:48 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 13 Sep 2008 14:45:48 +0200 (CEST) Subject: [py-svn] r58105 - py/extradoc/talk/pycon-uk-2008 Message-ID: <20080913124548.A23A316A3C9@codespeak.net> Author: hpk Date: Sat Sep 13 14:45:47 2008 New Revision: 58105 Modified: py/extradoc/talk/pycon-uk-2008/pytest.pdf Log: updating PDF Modified: py/extradoc/talk/pycon-uk-2008/pytest.pdf ============================================================================== Binary files. No diff available. From pedronis at codespeak.net Sat Sep 13 15:39:39 2008 From: pedronis at codespeak.net (pedronis at codespeak.net) Date: Sat, 13 Sep 2008 15:39:39 +0200 (CEST) Subject: [py-svn] r58107 - in py/trunk/contrib: filelog filelog/test py_unittest Message-ID: <20080913133939.F02DD16A477@codespeak.net> Author: pedronis Date: Sat Sep 13 15:39:36 2008 New Revision: 58107 Added: py/trunk/contrib/filelog/ (props changed) py/trunk/contrib/filelog/__init__.py py/trunk/contrib/filelog/session.py - copied unchanged from r58084, pypy/branch/pypy-pytrunk/pypy/tool/pytest/filelog.py py/trunk/contrib/filelog/test/ (props changed) py/trunk/contrib/filelog/test/__init__.py py/trunk/contrib/filelog/test/test_filelog.py - copied, changed from r58084, pypy/branch/pypy-pytrunk/pypy/tool/pytest/test/test_filelog.py Modified: py/trunk/contrib/py_unittest/ (props changed) Log: first step in moving experimental filelog session into contrib Added: py/trunk/contrib/filelog/__init__.py ============================================================================== Added: py/trunk/contrib/filelog/test/__init__.py ============================================================================== Copied: py/trunk/contrib/filelog/test/test_filelog.py (from r58084, pypy/branch/pypy-pytrunk/pypy/tool/pytest/test/test_filelog.py) ============================================================================== --- pypy/branch/pypy-pytrunk/pypy/tool/pytest/test/test_filelog.py (original) +++ py/trunk/contrib/filelog/test/test_filelog.py Sat Sep 13 15:39:36 2008 @@ -1,6 +1,7 @@ import py -from pypy.tool.pytest import filelog +from filelog import session + import os, StringIO from py.__.test.collect import Node, Item, FSCollector @@ -21,7 +22,7 @@ p3 = Node('()', parent = p2) item = Item('c', parent = p3) - res = filelog.generic_path(item) + res = session.generic_path(item) assert res == 'a.B().c' p0 = FSCollector('proj/test', config='dummy') @@ -31,7 +32,7 @@ p4 = Node('c', parent=p3) item = Item('[1]', parent = p4) - res = filelog.generic_path(item) + res = session.generic_path(item) assert res == 'test/a:B().c[1]' @@ -54,7 +55,7 @@ option = Fake(eventlog=None) config = Fake(option=option) - filelog.FileLogSession(config) + session.FileLogSession(config) def test_open_logfile(self): logfname = os.tempnam() @@ -62,7 +63,7 @@ option = Fake(eventlog=None, filelog=logfname) config = Fake(option=option) - sess = filelog.FileLogSession(config) + sess = session.FileLogSession(config) assert len(sess.bus._subscribers) == 1 @@ -75,7 +76,7 @@ def test_write_log_entry(self): option = Fake(eventlog=None) config = Fake(option=option) - sess = filelog.FileLogSession(config) + sess = session.FileLogSession(config) sess.logfile = StringIO.StringIO() sess.write_log_entry('.', 'name', '') @@ -116,7 +117,7 @@ def test_log_outcome(self): option = Fake(eventlog=None) config = Fake(option=option) - sess = filelog.FileLogSession(config) + sess = session.FileLogSession(config) sess.logfile = StringIO.StringIO() colitem = make_item('some', 'path', 'a', 'b') @@ -141,7 +142,7 @@ def test_item_test_passed(self): option = Fake(eventlog=None) config = Fake(option=option) - sess = filelog.FileLogSession(config) + sess = session.FileLogSession(config) sess.logfile = StringIO.StringIO() colitem = make_item('proj/test', 'proj/test/mod', 'a', 'b') @@ -160,7 +161,7 @@ def test_collection_report(self): option = Fake(eventlog=None) config = Fake(option=option) - sess = filelog.FileLogSession(config) + sess = session.FileLogSession(config) sess.logfile = StringIO.StringIO() colitem = make_item('proj/test', 'proj/test/mod', 'A', None) @@ -188,7 +189,7 @@ # at the end of the run option = Fake(eventlog=None) config = Fake(option=option) - sess = filelog.FileLogSession(config) + sess = session.FileLogSession(config) sess.logfile = StringIO.StringIO() try: From arigo at codespeak.net Sat Sep 13 16:23:32 2008 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sat, 13 Sep 2008 16:23:32 +0200 (CEST) Subject: [py-svn] r58108 - py/trunk/contrib Message-ID: <20080913142332.91A0E16A49E@codespeak.net> Author: arigo Date: Sat Sep 13 16:23:30 2008 New Revision: 58108 Modified: py/trunk/contrib/sysinfo.py Log: A hyperthreaded cpu core should only count as 1, although it is present as 2 entries in /proc/cpuinfo. Counting it as 2 is misleading because it is *by far* not as efficient as two independent cores. Modified: py/trunk/contrib/sysinfo.py ============================================================================== --- py/trunk/contrib/sysinfo.py (original) +++ py/trunk/contrib/sysinfo.py Sat Sep 13 16:23:30 2008 @@ -65,18 +65,26 @@ def getcpuinfo(self): if self.islinux(): - return self.exreceive(""" - numcpus = 0 - model = None - for line in open("/proc/cpuinfo"): - if not line.strip(): - continue - key, value = line.split(":") - key = key.strip() - if key == "processor": - numcpus += 1 - elif key == "model name": - model = value.strip() + return self.exreceive(""" + # a hyperthreaded cpu core only counts as 1, although it + # is present as 2 in /proc/cpuinfo. Counting it as 2 is + # misleading because it is *by far* not as efficient as + # two independent cores. + cpus = {} + cpuinfo = {} + f = open("/proc/cpuinfo") + lines = f.readlines() + f.close() + for line in lines + ['']: + if line.strip(): + key, value = line.split(":", 1) + cpuinfo[key.strip()] = value.strip() + else: + corekey = (cpuinfo.get("physical id"), + cpuinfo.get("core id")) + cpus[corekey] = 1 + numcpus = len(cpus) + model = cpuinfo.get("model name") channel.send((numcpus, model)) """) From pedronis at codespeak.net Sat Sep 13 16:38:06 2008 From: pedronis at codespeak.net (pedronis at codespeak.net) Date: Sat, 13 Sep 2008 16:38:06 +0200 (CEST) Subject: [py-svn] r58111 - py/trunk/contrib/filelog/test Message-ID: <20080913143806.D647916A438@codespeak.net> Author: pedronis Date: Sat Sep 13 16:38:06 2008 New Revision: 58111 Modified: py/trunk/contrib/filelog/test/test_filelog.py Log: make the tests runnable also under pypy Modified: py/trunk/contrib/filelog/test/test_filelog.py ============================================================================== --- py/trunk/contrib/filelog/test/test_filelog.py (original) +++ py/trunk/contrib/filelog/test/test_filelog.py Sat Sep 13 16:38:06 2008 @@ -1,7 +1,11 @@ import py -from filelog import session - +try: + from filelog import session +except ImportError: + # convenience, make these tests runnable where fielog lives under pypy + from pypy.tool.pytest.filelog import session + import os, StringIO from py.__.test.collect import Node, Item, FSCollector From pedronis at codespeak.net Sat Sep 13 16:46:16 2008 From: pedronis at codespeak.net (pedronis at codespeak.net) Date: Sat, 13 Sep 2008 16:46:16 +0200 (CEST) Subject: [py-svn] r58112 - py/trunk/contrib/filelog Message-ID: <20080913144616.1B31016A4D7@codespeak.net> Author: pedronis Date: Sat Sep 13 16:46:15 2008 New Revision: 58112 Modified: py/trunk/contrib/filelog/session.py Log: helper to add the required option Modified: py/trunk/contrib/filelog/session.py ============================================================================== --- py/trunk/contrib/filelog/session.py (original) +++ py/trunk/contrib/filelog/session.py Sat Sep 13 16:46:15 2008 @@ -1,6 +1,14 @@ +import py from py.__.test.session import Session from py.__.test import event +def add_filelog_option(): + py.test.config.addoptions("filelog session options " + "(use --session=FileLogSession)", + py.test.config.Option('--filelog', action="store", + default=None, dest="filelog", + help="path for filelog session logging")) + def generic_path(item): chain = item.listchain() From pedronis at codespeak.net Sat Sep 13 16:48:57 2008 From: pedronis at codespeak.net (pedronis at codespeak.net) Date: Sat, 13 Sep 2008 16:48:57 +0200 (CEST) Subject: [py-svn] r58113 - py/trunk/contrib/filelog/test Message-ID: <20080913144857.8291916A4A7@codespeak.net> Author: pedronis Date: Sat Sep 13 16:48:56 2008 New Revision: 58113 Modified: py/trunk/contrib/filelog/test/test_filelog.py Log: typo Modified: py/trunk/contrib/filelog/test/test_filelog.py ============================================================================== --- py/trunk/contrib/filelog/test/test_filelog.py (original) +++ py/trunk/contrib/filelog/test/test_filelog.py Sat Sep 13 16:48:56 2008 @@ -3,7 +3,7 @@ try: from filelog import session except ImportError: - # convenience, make these tests runnable where fielog lives under pypy + # convenience, make these tests runnable where filelog lives under pypy from pypy.tool.pytest.filelog import session import os, StringIO From pedronis at codespeak.net Wed Sep 17 10:50:06 2008 From: pedronis at codespeak.net (pedronis at codespeak.net) Date: Wed, 17 Sep 2008 10:50:06 +0200 (CEST) Subject: [py-svn] r58190 - in py/trunk/py: . bin bin/win32 cmdline Message-ID: <20080917085006.E68B3169FC7@codespeak.net> Author: pedronis Date: Wed Sep 17 10:50:04 2008 New Revision: 58190 Added: py/trunk/py/bin/py.svnwcrevert (contents, props changed) py/trunk/py/bin/win32/py.svnwcrevert.cmd py/trunk/py/cmdline/pysvnwcrevert.py - copied, changed from r58188, user/arigo/hack/svnutil/svnwcrevert.py Modified: py/trunk/py/__init__.py Log: adding arigo/hacks/svnwcrevert as a py.svnwcrevert script on py lib trunk Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Wed Sep 17 10:50:04 2008 @@ -60,6 +60,7 @@ 'cmdline.pycountloc' : ('./cmdline/pycountloc.py', 'main',), 'cmdline.pycleanup' : ('./cmdline/pycleanup.py', 'main',), 'cmdline.pywhich' : ('./cmdline/pywhich.py', 'main',), + 'cmdline.pysvnwcrevert' : ('./cmdline/pysvnwcrevert.py', 'main',), # helpers for use from test functions or collectors 'test.__doc__' : ('./test/__init__.py', '__doc__'), Added: py/trunk/py/bin/py.svnwcrevert ============================================================================== --- (empty file) +++ py/trunk/py/bin/py.svnwcrevert Wed Sep 17 10:50:04 2008 @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from _findpy import py +py.cmdline.pysvnwcrevert() \ No newline at end of file Added: py/trunk/py/bin/win32/py.svnwcrevert.cmd ============================================================================== --- (empty file) +++ py/trunk/py/bin/win32/py.svnwcrevert.cmd Wed Sep 17 10:50:04 2008 @@ -0,0 +1,2 @@ + at echo off +python "%~dp0\..\py.svnwcrevert" %* \ No newline at end of file Copied: py/trunk/py/cmdline/pysvnwcrevert.py (from r58188, user/arigo/hack/svnutil/svnwcrevert.py) ============================================================================== --- user/arigo/hack/svnutil/svnwcrevert.py (original) +++ py/trunk/py/cmdline/pysvnwcrevert.py Wed Sep 17 10:50:04 2008 @@ -1,11 +1,13 @@ #! /usr/bin/env python -""" -Running this script and then 'svn up' puts a the working copy in a state +"""\ +py.svnwcrevert WCPATH + +Running this script and then 'svn up' puts the working copy WCPATH in a state as clean as a fresh check-out. WARNING: you'll loose all local changes, obviously! -This is a pylib-based hack that deletes all files that have been modified +This script deletes all files that have been modified or that svn doesn't explicitly know about, including svn:ignored files (like .pyc files, hint hint). @@ -39,7 +41,7 @@ svnwcrevert(p, root) -if __name__ == '__main__': +def main(): import sys if len(sys.argv) != 2: print __doc__ From pedronis at codespeak.net Wed Sep 17 22:29:36 2008 From: pedronis at codespeak.net (pedronis at codespeak.net) Date: Wed, 17 Sep 2008 22:29:36 +0200 (CEST) Subject: [py-svn] r58211 - py/trunk/py/cmdline Message-ID: <20080917202936.65AAF169FD2@codespeak.net> Author: pedronis Date: Wed Sep 17 22:29:35 2008 New Revision: 58211 Modified: py/trunk/py/cmdline/pysvnwcrevert.py Log: allow to specify filenames that need to be preserved Modified: py/trunk/py/cmdline/pysvnwcrevert.py ============================================================================== --- py/trunk/py/cmdline/pysvnwcrevert.py (original) +++ py/trunk/py/cmdline/pysvnwcrevert.py Wed Sep 17 22:29:35 2008 @@ -1,6 +1,6 @@ #! /usr/bin/env python """\ -py.svnwcrevert WCPATH +py.svnwcrevert WCPATH [precious...] Running this script and then 'svn up' puts the working copy WCPATH in a state as clean as a fresh check-out. @@ -14,6 +14,9 @@ The goal of this script is to leave the working copy with some files and directories possibly missing, but - most importantly - in a state where the following 'svn up' won't just crash. + +Optionally filenames that should be left untouched can be passed as arguments +too. """ import py @@ -22,7 +25,7 @@ print '< %s' % (p.relto(root),) p.remove(rec=1) -def svnwcrevert(path, root=None): +def svnwcrevert(path, root=None, precious=[]): if root is None: root = path wcpath = py.path.svnwc(path) @@ -32,7 +35,7 @@ kill(path, root) return for p in path.listdir(): - if p.basename == '.svn': + if p.basename == '.svn' or p.basename in precious: continue wcp = py.path.svnwc(p) if wcp not in st.unchanged and wcp not in st.external: @@ -43,7 +46,7 @@ def main(): import sys - if len(sys.argv) != 2: + if len(sys.argv) < 2: print __doc__ sys.exit(2) - svnwcrevert(py.path.local(sys.argv[1])) + svnwcrevert(py.path.local(sys.argv[1]), precious=sys.argv[1:]) From hpk at codespeak.net Thu Sep 18 17:41:23 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 18 Sep 2008 17:41:23 +0200 (CEST) Subject: [py-svn] r58224 - py/trunk/py/test/testing Message-ID: <20080918154123.A5951169E7C@codespeak.net> Author: hpk Date: Thu Sep 18 17:41:19 2008 New Revision: 58224 Modified: py/trunk/py/test/testing/test_outcome.py Log: remove redundant py import, thanks getxsick Modified: py/trunk/py/test/testing/test_outcome.py ============================================================================== --- py/trunk/py/test/testing/test_outcome.py (original) +++ py/trunk/py/test/testing/test_outcome.py Thu Sep 18 17:41:19 2008 @@ -1,7 +1,6 @@ import py import marshal -import py class TestRaises: def test_raises(self): From hpk at codespeak.net Thu Sep 18 18:12:22 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 18 Sep 2008 18:12:22 +0200 (CEST) Subject: [py-svn] r58225 - py/trunk/py/code Message-ID: <20080918161222.517EB169EED@codespeak.net> Author: hpk Date: Thu Sep 18 18:12:18 2008 New Revision: 58225 Modified: py/trunk/py/code/source.py Log: remove outdated comment. Modified: py/trunk/py/code/source.py ============================================================================== --- py/trunk/py/code/source.py (original) +++ py/trunk/py/code/source.py Thu Sep 18 18:12:18 2008 @@ -4,8 +4,6 @@ import py cpy_compile = compile -# DON'T IMPORT PY HERE - class Source(object): """ a mutable object holding a source code fragment, possibly deindenting it. From dlpxf at codespeak.net Fri Sep 19 17:22:29 2008 From: dlpxf at codespeak.net (Jewel) Date: Fri, 19 Sep 2008 16:22:29 +0100 Subject: [py-svn] Rates are near 40 year lows Message-ID: <48D3B5A5.50404@codespeak.net> Rates as low as this might not last much longer! As the Fed combats inflation, rates could rise once again. - Get your mortgage Refinanced - Get a Home Equity Loan - Use your equity to consolidate your debt - Get up to 4 quotes for FREE! http://cgvxcksdgfdi.cn Match me with Lenders! http://cgvxcksdgfdi.cn ----------------------- le pr?sent message (ainsi que ses ?ventuelles pi?ces jointes) peut contenir des informations confidentielles. Etant ?tabli ? l'intention de ses destinataires, son utilisation ou diffusion non autoris?e est interdite. Tout message ?lectronique ?tant susceptible d'alt?ration, Prisma Presse d?cline toute responsabilit? au titre dudit message en cas de falsification. Ce message a ?t? trait? par un anti virus et aucun virus connu n'a ?t? d?tect?. From hpk at codespeak.net Sun Sep 21 08:40:57 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 21 Sep 2008 08:40:57 +0200 (CEST) Subject: [py-svn] r58284 - py/trunk/contrib/py_unittest Message-ID: <20080921064057.09FB4169E8B@codespeak.net> Author: hpk Date: Sun Sep 21 08:40:54 2008 New Revision: 58284 Modified: py/trunk/contrib/py_unittest/conftest.py py/trunk/contrib/py_unittest/readme.txt py/trunk/contrib/py_unittest/test_conftest.py Log: make sure that "test" prefix is enough. Modified: py/trunk/contrib/py_unittest/conftest.py ============================================================================== --- py/trunk/contrib/py_unittest/conftest.py (original) +++ py/trunk/contrib/py_unittest/conftest.py Sun Sep 21 08:40:54 2008 @@ -1,7 +1,6 @@ import py import unittest import sys -from py.__.test.collect import configproperty as _configproperty unittest.failureException = AssertionError def configproperty(name): @@ -13,7 +12,7 @@ class Module(py.test.collect.Module): UnitTestCase = configproperty('UnitTestCase') def makeitem(self, name, obj, usefilters=True): - # XXX add test_suite() support(?) + # XXX add generic test_suite() support(?) if py.std.inspect.isclass(obj) and issubclass(obj, unittest.TestCase): return self.UnitTestCase(name, parent=self) elif callable(obj) and getattr(obj, 'func_name', '') == 'test_suite': Modified: py/trunk/contrib/py_unittest/readme.txt ============================================================================== --- py/trunk/contrib/py_unittest/readme.txt (original) +++ py/trunk/contrib/py_unittest/readme.txt Sun Sep 21 08:40:54 2008 @@ -1,4 +1,5 @@ -code for collecting traditional unit tests based on +code for collecting traditional unit tests. +This conftest is based on http://johnnydebris.net/svn/projects/py_unittest Modified: py/trunk/contrib/py_unittest/test_conftest.py ============================================================================== --- py/trunk/contrib/py_unittest/test_conftest.py (original) +++ py/trunk/contrib/py_unittest/test_conftest.py Sun Sep 21 08:40:54 2008 @@ -13,11 +13,11 @@ test_one = self.makepyfile(test_one=""" import unittest class MyTestCase(unittest.TestCase): - def test_passing(self): + def testpassing(self): self.assertEquals('foo', 'foo') """) sorter = self.parse_and_run(test_one) - rep = sorter.getreport("test_passing") + rep = sorter.getreport("testpassing") assert rep.passed def test_simple_failing(self): From hpk at codespeak.net Sun Sep 21 09:17:00 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 21 Sep 2008 09:17:00 +0200 (CEST) Subject: [py-svn] r58285 - py/trunk/contrib/py_unittest Message-ID: <20080921071700.BC53A16A071@codespeak.net> Author: hpk Date: Sun Sep 21 09:17:00 2008 New Revision: 58285 Modified: py/trunk/contrib/py_unittest/conftest.py (contents, props changed) Log: add docstring and version info Modified: py/trunk/contrib/py_unittest/conftest.py ============================================================================== --- py/trunk/contrib/py_unittest/conftest.py (original) +++ py/trunk/contrib/py_unittest/conftest.py Sun Sep 21 09:17:00 2008 @@ -1,7 +1,23 @@ +""" + + collect and run traditional "unittest.py" style tests. + + drop this conftest.py into your project directory so that + all testing directories are below it. + + you can mix unittest TestCase subclasses and + py.test style tests (discovery based on name). + + user-extensions such as a custom test_suite() + will not be considered (see XXX). + +$Id$ +""" import py import unittest import sys -unittest.failureException = AssertionError + +__version__ = "$Rev$".split()[1] def configproperty(name): def fget(self): From hpk at codespeak.net Sun Sep 21 09:18:43 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 21 Sep 2008 09:18:43 +0200 (CEST) Subject: [py-svn] r58286 - py/trunk/contrib/py_unittest Message-ID: <20080921071843.664BE16A071@codespeak.net> Author: hpk Date: Sun Sep 21 09:18:39 2008 New Revision: 58286 Modified: py/trunk/contrib/py_unittest/conftest.py (contents, props changed) Log: also contain URL Modified: py/trunk/contrib/py_unittest/conftest.py ============================================================================== --- py/trunk/contrib/py_unittest/conftest.py (original) +++ py/trunk/contrib/py_unittest/conftest.py Sun Sep 21 09:18:39 2008 @@ -1,16 +1,16 @@ """ +collect and run traditional "unittest.py" style tests. - collect and run traditional "unittest.py" style tests. +drop this conftest.py into your project directory so that +all testing directories are below it. - drop this conftest.py into your project directory so that - all testing directories are below it. +you can mix unittest TestCase subclasses and +py.test style tests (discovery based on name). - you can mix unittest TestCase subclasses and - py.test style tests (discovery based on name). - - user-extensions such as a custom test_suite() - will not be considered (see XXX). +user-extensions such as a custom test_suite() +will not be considered (see XXX). +$HeadURL$ $Id$ """ import py From hpk at codespeak.net Sun Sep 21 09:19:30 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 21 Sep 2008 09:19:30 +0200 (CEST) Subject: [py-svn] r58287 - py/trunk/contrib/py_unittest Message-ID: <20080921071930.D41C716A167@codespeak.net> Author: hpk Date: Sun Sep 21 09:19:30 2008 New Revision: 58287 Modified: py/trunk/contrib/py_unittest/test_conftest.py Log: add a test for __version__ attr Modified: py/trunk/contrib/py_unittest/test_conftest.py ============================================================================== --- py/trunk/contrib/py_unittest/test_conftest.py (original) +++ py/trunk/contrib/py_unittest/test_conftest.py Sun Sep 21 09:19:30 2008 @@ -3,6 +3,10 @@ from py.__.test.testing import suptest conftestpath = py.magic.autopath().dirpath("conftest.py") +def test_version(): + mod = conftestpath.pyimport() + assert hasattr(mod, "__version__") + class TestTestCaseInstance(suptest.InlineSession): def setup_method(self, method): super(TestTestCaseInstance, self).setup_method(method) From hpk at codespeak.net Sun Sep 21 10:17:13 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 21 Sep 2008 10:17:13 +0200 (CEST) Subject: [py-svn] r58288 - in py/trunk: contrib/py_unittest py/doc Message-ID: <20080921081713.F1E40169F6F@codespeak.net> Author: hpk Date: Sun Sep 21 10:17:11 2008 New Revision: 58288 Modified: py/trunk/contrib/py_unittest/conftest.py py/trunk/py/doc/TODO.txt Log: update TODOs, docstring Modified: py/trunk/contrib/py_unittest/conftest.py ============================================================================== --- py/trunk/contrib/py_unittest/conftest.py (original) +++ py/trunk/contrib/py_unittest/conftest.py Sun Sep 21 10:17:11 2008 @@ -1,5 +1,5 @@ """ -collect and run traditional "unittest.py" style tests. +automatically collect and run traditional "unittest.py" style tests. drop this conftest.py into your project directory so that all testing directories are below it. Modified: py/trunk/py/doc/TODO.txt ============================================================================== --- py/trunk/py/doc/TODO.txt (original) +++ py/trunk/py/doc/TODO.txt Sun Sep 21 10:17:11 2008 @@ -4,8 +4,6 @@ py.test -------------- -- simplify collect API - - get APIGEN back to work - get web reporter back to work @@ -14,6 +12,18 @@ as to mark a test as "expected to fail", report specially if it surprisingly passes +- introduce extended skipping, for example: + + py.test.skip(ifexecerror=''' + import docutils + assert docutils.__version__.startswith("0.4") + ''') + +- introduce setuptools-style version checking, at least + for py lib itself, maybe also for other packages: + + py.checkversion("py>=1.0") + - nightly test runs on multiple platforms - review and refactor architecture of py.test with particular From hpk at codespeak.net Sun Sep 21 14:50:58 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 21 Sep 2008 14:50:58 +0200 (CEST) Subject: [py-svn] r58297 - in py/trunk: . py/doc py/test py/test/testing Message-ID: <20080921125058.7356116A154@codespeak.net> Author: hpk Date: Sun Sep 21 14:50:56 2008 New Revision: 58297 Modified: py/trunk/CHANGELOG py/trunk/py/doc/test.txt py/trunk/py/test/outcome.py py/trunk/py/test/testing/test_collect.py py/trunk/py/test/testing/test_outcome.py Log: add a new way of conditionally skipping a test: py.test.skip(ifraises="...") see more info in the added doc. also remove a redundant raises test and cleanup raises code a bit. Modified: py/trunk/CHANGELOG ============================================================================== --- py/trunk/CHANGELOG (original) +++ py/trunk/CHANGELOG Sun Sep 21 14:50:56 2008 @@ -3,7 +3,12 @@ Changes between 0.9.2 and 1.0 (UNRELEASED) ============================================= +* py.test.skip(ifraises=execstring) allows to + conditionally skip, e.g. ifraises="import docutils" + will skip if there is no docutils installed. + * revised internal py.test architecture + * new py.process.ForkedFunc object allowing to fork execution of a function to a sub process and getting a result back. Modified: py/trunk/py/doc/test.txt ============================================================================== --- py/trunk/py/doc/test.txt (original) +++ py/trunk/py/doc/test.txt Sun Sep 21 14:50:56 2008 @@ -86,6 +86,24 @@ provide you with helpful output in case of failures such as *no exception* or *wrong exception*. +Skipping tests +---------------------------------------- + +If you want to skip tests you can use ``py.test.skip`` within +test or setup functions. Examples:: + + py.test.skip("message") + py.test.skip(ifraises="import docutils") + py.test.skip(ifraises=""" + import somepkg + assert somepkg.__version__.startswith("2") + """) + +The first skip will cause unconditional skipping +The second skip will only cause skipping if +``docutils`` is not importable. +The third form will cause skipping if ``somepkg`` +is not importable or is not at least version "2". automatic collection of tests on all levels ------------------------------------------- Modified: py/trunk/py/test/outcome.py ============================================================================== --- py/trunk/py/test/outcome.py (original) +++ py/trunk/py/test/outcome.py Sun Sep 21 14:50:56 2008 @@ -49,9 +49,26 @@ __tracebackhide__ = True raise Exit(msg) -def skip(msg=""): - """ skip with the given Message. """ +def skip(msg="", ifraises=None): + """ (conditionally) skip this test/module/conftest. + + ifraises: + if "exec ifraises in {'py': py}" raises an exception + skip this test. + msg: use this message when skipping. + """ __tracebackhide__ = True + if ifraises is not None: + ifraises = py.code.Source(ifraises).compile() + try: + exec ifraises in {'py': py} + except (KeyboardInterrupt, SystemExit): + raise + except Exception, e: + if not msg: + msg = repr(e) + else: + return raise Skipped(msg=msg) def fail(msg="unknown failure"): @@ -66,16 +83,15 @@ __tracebackhide__ = True assert args if isinstance(args[0], str): - expr, = args - assert isinstance(expr, str) + code, = args + assert isinstance(code, str) frame = sys._getframe(1) loc = frame.f_locals.copy() loc.update(kwargs) #print "raises frame scope: %r" % frame.f_locals - source = py.code.Source(expr) try: - exec source.compile() in frame.f_globals, loc - #del __traceback__ + code = py.code.Source(code).compile() + exec code in frame.f_globals, loc # XXX didn'T mean f_globals == f_locals something special? # this is destroyed here ... except ExpectedException: @@ -85,7 +101,6 @@ assert callable try: func(*args[1:], **kwargs) - #del __traceback__ except ExpectedException: return py.code.ExceptionInfo() k = ", ".join(["%s=%r" % x for x in kwargs.items()]) Modified: py/trunk/py/test/testing/test_collect.py ============================================================================== --- py/trunk/py/test/testing/test_collect.py (original) +++ py/trunk/py/test/testing/test_collect.py Sun Sep 21 14:50:56 2008 @@ -427,29 +427,13 @@ ev = failures[0] assert ev.outcome.longrepr.reprcrash.message.startswith("SyntaxError") - def test_skip_at_module_level(self): - self.tmp.ensure("test_module.py").write(py.code.Source(""" - import py - py.test.skip('xxx') - """)) - items, events = self._genitems() - funcs = [x for x in items if isinstance(x, event.ItemStart)] - assert not funcs - assert not items - l = [x for x in events - if isinstance(x, event.CollectionReport) - and x.colitem.name == 'test_module.py'] - assert len(l) == 1 - ev = l[0] - assert ev.skipped - def test_example_items1(self): self.tmp.ensure("test_example.py").write(py.code.Source(''' - def test_one(): + def testone(): pass class TestX: - def test_method_one(self): + def testmethod_one(self): pass class TestY(TestX): @@ -457,17 +441,17 @@ ''')) items, events = self._genitems() assert len(items) == 3 - assert items[0].name == 'test_one' - assert items[1].name == 'test_method_one' - assert items[2].name == 'test_method_one' + assert items[0].name == 'testone' + assert items[1].name == 'testmethod_one' + assert items[2].name == 'testmethod_one' # let's also test getmodpath here - assert items[0].getmodpath() == "test_one" - assert items[1].getmodpath() == "TestX.test_method_one" - assert items[2].getmodpath() == "TestY.test_method_one" + assert items[0].getmodpath() == "testone" + assert items[1].getmodpath() == "TestX.testmethod_one" + assert items[2].getmodpath() == "TestY.testmethod_one" s = items[0].getmodpath(stopatmodule=False) - assert s == "test_example_items1.test_example.test_one" + assert s == "test_example_items1.test_example.testone" print s def test_collect_doctest_files_with_test_prefix(self): Modified: py/trunk/py/test/testing/test_outcome.py ============================================================================== --- py/trunk/py/test/testing/test_outcome.py (original) +++ py/trunk/py/test/testing/test_outcome.py Sun Sep 21 14:50:56 2008 @@ -1,6 +1,7 @@ import py import marshal +from py.__.test.outcome import Skipped class TestRaises: def test_raises(self): @@ -58,3 +59,28 @@ py.test.deprecated_call(dep_explicit, 0) py.test.deprecated_call(dep_explicit, 0) + + +def test_skip_simple(): + excinfo = py.test.raises(Skipped, 'py.test.skip("xxx")') + assert excinfo.traceback[-1].frame.code.name == "skip" + assert excinfo.traceback[-1].ishidden() + +def test_skip_ifraises(): + excinfo = py.test.raises(Skipped, ''' + py.test.skip(ifraises=""" + import lky + """) + ''') + assert excinfo.traceback[-1].frame.code.name == "skip" + assert excinfo.traceback[-1].ishidden() + assert excinfo.value.msg.startswith("ImportError") + +def test_skip_ifraises_syntaxerror(): + try: + excinfo = py.test.raises(SyntaxError, ''' + py.test.skip(ifraises="x y z")''') + except Skipped: + py.test.fail("should not skip") + assert not excinfo.traceback[-1].ishidden() + From hpk at codespeak.net Sun Sep 21 14:51:33 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 21 Sep 2008 14:51:33 +0200 (CEST) Subject: [py-svn] r58298 - in py/trunk: . py/doc Message-ID: <20080921125133.7C9AE16A154@codespeak.net> Author: hpk Date: Sun Sep 21 14:51:32 2008 New Revision: 58298 Added: py/trunk/TODO.txt - copied, changed from r58288, py/trunk/py/doc/TODO.txt Removed: py/trunk/py/doc/TODO.txt Log: move and update TODO file Copied: py/trunk/TODO.txt (from r58288, py/trunk/py/doc/TODO.txt) ============================================================================== --- py/trunk/py/doc/TODO.txt (original) +++ py/trunk/TODO.txt Sun Sep 21 14:51:32 2008 @@ -12,13 +12,6 @@ as to mark a test as "expected to fail", report specially if it surprisingly passes -- introduce extended skipping, for example: - - py.test.skip(ifexecerror=''' - import docutils - assert docutils.__version__.startswith("0.4") - ''') - - introduce setuptools-style version checking, at least for py lib itself, maybe also for other packages: @@ -89,12 +82,14 @@ features -------------- -- (Harald Armin Massa): make py.exe work with py lib +- (Harald Armin Massa): make py2exe work with py lib - optimize file checking with --looponfailing (harald has code for win32) - have a py.test scan/run database for results and test names etc. (to allow quicker selection of tests and post-run information on failures etc.) (M760) +- have config options from environment, command line or conftest's + - consider features of py.apigen (recheck closed "M1016") - integrate rlcompleter2 (make it remotely workable) @@ -118,4 +113,105 @@ to have pyrepl+rlcompleter2+pdb fixes integrated into pylib and have it tested. This requires work though. +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +--- below neeeds more review --- +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +More random notes, goals +-------------------------------- +- REDUCE "MAGICALNESS", from an IRC discussion with ronny: + - integration into IDEs + - python2.6/python3 compat? + - initpkg exports + - assert reinterpretation + - greenlet compiles at runtime only in "dev-mode" + - conftest's are "scary", hum, what about + +nicefications: +looponfailing shoudl nicely signal tests that failed but now PASS tests +rename Node to itemtestloop +refactor config and session tests to go into test_distsession.py +raises DID NOT RAISE: report the return value +have node.shutdown perform out-of-band so that shutdowns happens more quickly +tracebacks of importerrors of test modules should start with the test module file + +- fix hostmanage to care for setting PYTHONPATH properly + +- BRANCH: + adding of options + +- BRANCH: + TEMPDIR handling + syspath handling (notify on changes, restore for each test?) + +- COMPLETE REPORTING FOR MERGE! + - remove ItemStart and CollectionStart + which are only needed for collectonly. + implement it some other way. + + - pre-counting of test items + + - move assert reinterp back to session? + + - merge "--tb" and "--fulltrace" option, --tb=full + - implement --showouterr, don't show outerr by default + + - domainpath? + + - translate remote filenames to local filenames so that, + probably based on option + + - review safe_repr + + - move OutcomeRepr.where/exconly attr to ReprExceptionInfo + or substitute as LocationRepr? + +- reprcrash rename message to exconly + +- test terminal reporter + ACCEPTANCE test for eventlog writing + ACCEPTANCE test for acceptance custom reporting :) + + ACCEPTANCE showing nice Collection Errors + + ACCEPTANCE test for "py.test2" exit signals + ACCEPTANCE test for "py.test2 --traceconfig" + ACCEPTANCE test for nice reprsentation of failures during Collection + ACCEPTANCE test for nice reprsentation of failures during Generator Collection + ACCEPTANCE test for "py.test2" conftest containing syntax errors + + ACCEPTANCE test for "py.test2" honouring conftest specifying "extrainfo" + ACCEPTANCE test for "py.test2" on a simple example project + +- merge CollectionFinish and ItemTestReport + maybe with base class: BaseReport and some common attrs/methods + +- merge terminal/remote and dist-testing + and make allocation of tests to hosts more dynamic + +- time setup/teardown and the actual test runs separately + ACCEPTANCE: + py.test2 -- + py.test2 --repeat=10 + +- expect failing tests + py.test2.expectfail(feature=138) + + +while killing a process: +Exception in thread receiver: +Traceback (most recent call last): + File "threading.py", line 460, in __bootstrap + self.run() + File "threading.py", line 440, in run + self.__target(*self.__args, **self.__kwargs) + File "/home/hpk/py/branch/event/py/execnet/gateway.py", line 140, in _thread_receiver + self._stopsend() + File "/home/hpk/py/branch/event/py/execnet/gateway.py", line 329, in _stopsend + self._send(None) + File "/home/hpk/py/branch/event/py/execnet/gateway.py", line 147, in _send + self._io.close_write() + File "/home/hpk/py/branch/event/py/execnet/inputoutput.py", line 106, in close_write + self.outfile.close() +IOError: [Errno 32] Broken pipe Deleted: /py/trunk/py/doc/TODO.txt ============================================================================== --- /py/trunk/py/doc/TODO.txt Sun Sep 21 14:51:32 2008 +++ (empty file) @@ -1,121 +0,0 @@ -Things to do for 1.0.0 -========================= - -py.test --------------- - -- get APIGEN back to work - -- get web reporter back to work - -- introduce decorator "shouldfail" or "xfail" - as to mark a test as "expected to fail", - report specially if it surprisingly passes - -- introduce extended skipping, for example: - - py.test.skip(ifexecerror=''' - import docutils - assert docutils.__version__.startswith("0.4") - ''') - -- introduce setuptools-style version checking, at least - for py lib itself, maybe also for other packages: - - py.checkversion("py>=1.0") - -- nightly test runs on multiple platforms - -- review and refactor architecture of py.test with particular - respect to: - - writing (stacked) extensions / plugins (compared to Nose) - - porting existing extensions (htmlconftest / buildbot / PyPy's conftest's ...) - - fast and stable distributed testing - - reliable cross-platform testing - -- improve py.test documentation to reflect new - event architecture - -- review and optimize skip-handling (it can be quite slow in - certain situations because e.g. setup/teardown is fully performed - although we have "skip by keyword" and could detect this early) - -py.execnet --------------- - -- cross-python version (2.2/2.3-2.5/6) and cross-platform testing of - setup/teardown semantics - -- optimize general setup and rsync timing? - -py.apigen ----------------- - -- make it work again - -see apigen_refactorings.txt - -- check out CodeInvestigator - http://codeinvestigator.googlepages.com/main - - or other code that collects data from running a program - (in our case running the tests) - -ld (review and shift to above) -================================= - -refactorings ------------------- - -- refine doctests usage (particularly skips of doctests if - some imports/conditions are not satisfied) - -- generalization of "host specifications" for execnet and - py.test --dist usages in particular (see also revision 37500 which - contained a draft for that). The goal is to have cross-platform - testing and dist-testing and other usages of py.execnet all - use a common syntax for specifiying connection methods and - be able to instantiate gateways/connections through it. - -- unification of "gateway"/host setup and teardown, including - rsyncing, i.e. cross-platform and dist-testing. - -- py.log: unify API, possibly deprecate duplicate ones, - base things on a Config object (hte latter almost a feature though) - (M988) - -- see to teardown more eagerly - -features --------------- - -- (Harald Armin Massa): make py.exe work with py lib -- optimize file checking with --looponfailing (harald has code for win32) -- have a py.test scan/run database for results and test names - etc. (to allow quicker selection of tests and post-run - information on failures etc.) (M760) - -- consider features of py.apigen (recheck closed "M1016") - -- integrate rlcompleter2 (make it remotely workable) - and maybe integrate with "pdb" / pdbplus (M975) - -- integrate native collecting of unittest.py tests from py.test - (along the PyPy lib-python tests) (M987) - -- provide an automated conversion script helper for converting - unittest.py based tests to py.test ones. (M987) - -- references from ReST docs to modules, functions and classes - of apigen generated html docs (M960) - -- review svn-testing (and escape characters), consider - svn-bindings (M634) - -- py.test.pdb - there is my hack for a while now, which integrates - rlcompleter2 with pdb. First of all it requires some strange changes - to rlcompleter itself, which has no tests. Long-term plan would be - to have pyrepl+rlcompleter2+pdb fixes integrated into pylib and - have it tested. This requires work though. - - From hpk at codespeak.net Sun Sep 21 15:43:03 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 21 Sep 2008 15:43:03 +0200 (CEST) Subject: [py-svn] r58300 - in py/trunk/py/test: . testing Message-ID: <20080921134303.0BBCE16A123@codespeak.net> Author: hpk Date: Sun Sep 21 15:43:01 2008 New Revision: 58300 Modified: py/trunk/py/test/outcome.py py/trunk/py/test/testing/acceptance_test.py py/trunk/py/test/testing/test_outcome.py Log: adding option to specify namespace for ifraises execution. Modified: py/trunk/py/test/outcome.py ============================================================================== --- py/trunk/py/test/outcome.py (original) +++ py/trunk/py/test/outcome.py Sun Sep 21 15:43:01 2008 @@ -49,19 +49,22 @@ __tracebackhide__ = True raise Exit(msg) -def skip(msg="", ifraises=None): +def skip(msg="", ifraises=None, ns=None): """ (conditionally) skip this test/module/conftest. + msg: use this message when skipping. ifraises: if "exec ifraises in {'py': py}" raises an exception skip this test. - msg: use this message when skipping. + ns: use this namespace when executing ifraises """ __tracebackhide__ = True if ifraises is not None: ifraises = py.code.Source(ifraises).compile() + if ns is None: + ns = {} try: - exec ifraises in {'py': py} + exec ifraises in ns except (KeyboardInterrupt, SystemExit): raise except Exception, e: Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Sun Sep 21 15:43:01 2008 @@ -387,10 +387,7 @@ class TestInteractive(AcceptBase): def getspawn(self): - try: - import pexpect - except ImportError: - py.test.skip("cannot import pexpect") + py.test.skip(ifraises="import pexpect", ns=globals()) def spawn(cmd): return pexpect.spawn(cmd, logfile=self.tmpdir.join("spawn.out").open("w")) return spawn Modified: py/trunk/py/test/testing/test_outcome.py ============================================================================== --- py/trunk/py/test/testing/test_outcome.py (original) +++ py/trunk/py/test/testing/test_outcome.py Sun Sep 21 15:43:01 2008 @@ -76,6 +76,11 @@ assert excinfo.traceback[-1].ishidden() assert excinfo.value.msg.startswith("ImportError") +def test_skip_ifraises_ns(): + d = {} + py.test.skip(ns=d, ifraises="import py") + assert d['py'] == py + def test_skip_ifraises_syntaxerror(): try: excinfo = py.test.raises(SyntaxError, ''' From hpk at codespeak.net Sun Sep 21 17:15:29 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 21 Sep 2008 17:15:29 +0200 (CEST) Subject: [py-svn] r58308 - in py/trunk: . py py/doc py/path/local py/path/svn/testing py/rest/testing py/test py/test/testing Message-ID: <20080921151529.014FD16A184@codespeak.net> Author: hpk Date: Sun Sep 21 17:15:28 2008 New Revision: 58308 Modified: py/trunk/CHANGELOG py/trunk/py/__init__.py py/trunk/py/doc/conftest.py py/trunk/py/doc/test.txt py/trunk/py/path/local/local.py py/trunk/py/path/svn/testing/svntestbase.py py/trunk/py/path/svn/testing/test_auth.py py/trunk/py/path/svn/testing/test_test_repo.py py/trunk/py/path/svn/testing/test_urlcommand.py py/trunk/py/path/svn/testing/test_wccommand.py py/trunk/py/rest/testing/setup.py py/trunk/py/rest/testing/test_directive.py py/trunk/py/rest/testing/test_htmlrest.py py/trunk/py/rest/testing/test_rst2pdf.py py/trunk/py/test/outcome.py py/trunk/py/test/testing/acceptance_test.py py/trunk/py/test/testing/test_outcome.py Log: * de-generalize conditional skips and only care nicely for common tedious causes of skipping: import a module and checking it has a certain version. usage example: docutils = py.test.importorskip(docutils, minversion="0.4") * used new helper and cleanup skipping logic in py lib Modified: py/trunk/CHANGELOG ============================================================================== --- py/trunk/CHANGELOG (original) +++ py/trunk/CHANGELOG Sun Sep 21 17:15:28 2008 @@ -3,9 +3,8 @@ Changes between 0.9.2 and 1.0 (UNRELEASED) ============================================= -* py.test.skip(ifraises=execstring) allows to - conditionally skip, e.g. ifraises="import docutils" - will skip if there is no docutils installed. +* new method: py.test.importorskip(mod,minversion) + will either import or call py.test.skip() * revised internal py.test architecture Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Sun Sep 21 17:15:28 2008 @@ -67,6 +67,7 @@ 'test.raises' : ('./test/outcome.py', 'raises'), 'test.deprecated_call' : ('./test/outcome.py', 'deprecated_call'), 'test.skip' : ('./test/outcome.py', 'skip'), + 'test.importorskip' : ('./test/outcome.py', 'importorskip'), 'test.fail' : ('./test/outcome.py', 'fail'), 'test.exit' : ('./test/outcome.py', 'exit'), 'test.pdb' : ('./test/custompdb.py', 'set_trace'), Modified: py/trunk/py/doc/conftest.py ============================================================================== --- py/trunk/py/doc/conftest.py (original) +++ py/trunk/py/doc/conftest.py Sun Sep 21 17:15:28 2008 @@ -61,10 +61,7 @@ _initialized = False def checkdocutils(): global _initialized - try: - import docutils - except ImportError: - py.test.skip("docutils not importable") + py.test.importorskip("docutils") if not _initialized: from py.__.rest import directive directive.register_linkrole('api', resolve_linkrole) Modified: py/trunk/py/doc/test.txt ============================================================================== --- py/trunk/py/doc/test.txt (original) +++ py/trunk/py/doc/test.txt Sun Sep 21 17:15:28 2008 @@ -90,20 +90,17 @@ ---------------------------------------- If you want to skip tests you can use ``py.test.skip`` within -test or setup functions. Examples:: +test or setup functions. Example:: py.test.skip("message") - py.test.skip(ifraises="import docutils") - py.test.skip(ifraises=""" - import somepkg - assert somepkg.__version__.startswith("2") - """) - -The first skip will cause unconditional skipping -The second skip will only cause skipping if -``docutils`` is not importable. -The third form will cause skipping if ``somepkg`` -is not importable or is not at least version "2". + +You can also use a helper to skip on a failing import:: + + docutils = py.test.importorskip("docutils") + +or to skip if the library does not have the right version:: + + docutils = py.test.importorskip("docutils", minversion="0.3") automatic collection of tests on all levels ------------------------------------------- Modified: py/trunk/py/path/local/local.py ============================================================================== --- py/trunk/py/path/local/local.py (original) +++ py/trunk/py/path/local/local.py Sun Sep 21 17:15:28 2008 @@ -181,7 +181,7 @@ break for arg in strargs: arg = arg.strip(sep) - if py.std.sys.platform == 'win32': + if iswin32: # allow unix style paths even on windows. arg = arg.strip('/') arg = arg.replace('/', sep) @@ -512,7 +512,7 @@ if p.check(file=1): return p else: - if py.std.sys.platform == 'win32': + if iswin32: paths = py.std.os.environ['Path'].split(';') try: systemroot = os.environ['SYSTEMROOT'] Modified: py/trunk/py/path/svn/testing/svntestbase.py ============================================================================== --- py/trunk/py/path/svn/testing/svntestbase.py (original) +++ py/trunk/py/path/svn/testing/svntestbase.py Sun Sep 21 17:15:28 2008 @@ -7,6 +7,13 @@ mypath = py.magic.autopath() repodump = mypath.dirpath('repotest.dump') +def getsvnbin(): + svnbin = py.path.local.sysfind('svn') + if svnbin is None: + py.test.skip("svn binary not found") + return svnbin + + # make a wc directory out of a given root url # cache previously obtained wcs! # Modified: py/trunk/py/path/svn/testing/test_auth.py ============================================================================== --- py/trunk/py/path/svn/testing/test_auth.py (original) +++ py/trunk/py/path/svn/testing/test_auth.py Sun Sep 21 17:15:28 2008 @@ -95,6 +95,7 @@ class TestSvnWCAuth(object): def setup_method(self, meth): self.auth = SvnAuth('user', 'pass', cache_auth=False) + svntestbase.getsvnbin() def test_checkout(self): wc = svnwc_no_svn('foo', auth=self.auth) @@ -250,6 +251,7 @@ class SvnAuthFunctionalTestBase(object): def setup_class(cls): + svntestbase.getsvnbin() if not option.runslowtests: py.test.skip('skipping slow functional tests - use --runslowtests ' 'to override') Modified: py/trunk/py/path/svn/testing/test_test_repo.py ============================================================================== --- py/trunk/py/path/svn/testing/test_test_repo.py (original) +++ py/trunk/py/path/svn/testing/test_test_repo.py Sun Sep 21 17:15:28 2008 @@ -1,12 +1,10 @@ import py -from py.__.path.svn.testing.svntestbase import make_test_repo +from py.__.path.svn.testing.svntestbase import make_test_repo, getsvnbin -if py.path.local.sysfind('svn') is None: - py.test.skip("cannot test py.path.svn, 'svn' binary not found") - class TestMakeRepo(object): def setup_class(cls): + getsvnbin() cls.repo = make_test_repo() cls.wc = py.path.svnwc(py.test.ensuretemp("test-wc").join("wc")) Modified: py/trunk/py/path/svn/testing/test_urlcommand.py ============================================================================== --- py/trunk/py/path/svn/testing/test_urlcommand.py (original) +++ py/trunk/py/path/svn/testing/test_urlcommand.py Sun Sep 21 17:15:28 2008 @@ -1,13 +1,12 @@ import py from py.__.path.svn.urlcommand import InfoSvnCommand from py.__.path.svn.testing.svntestbase import CommonCommandAndBindingTests, \ - getrepowc + getrepowc, getsvnbin import datetime import time def setup_module(mod): - if py.path.local.sysfind('svn') is None: - py.test.skip("cannot test py.path.svn, 'svn' binary not found") + getsvnbin() class TestSvnURLCommandPath(CommonCommandAndBindingTests): def setup_class(cls): Modified: py/trunk/py/path/svn/testing/test_wccommand.py ============================================================================== --- py/trunk/py/path/svn/testing/test_wccommand.py (original) +++ py/trunk/py/path/svn/testing/test_wccommand.py Sun Sep 21 17:15:28 2008 @@ -1,6 +1,6 @@ import py import sys -from py.__.path.svn.testing.svntestbase import CommonSvnTests, getrepowc +from py.__.path.svn.testing.svntestbase import CommonSvnTests, getrepowc, getsvnbin from py.__.path.svn.wccommand import InfoSvnWCCommand, XMLWCStatus from py.__.path.svn.wccommand import parse_wcinfotime from py.__.path.svn import svncommon @@ -22,8 +22,7 @@ return os.path.normpath(os.path.normcase(p)) def setup_module(mod): - if py.path.local.sysfind('svn') is None: - py.test.skip("cannot test py.path.svn, 'svn' binary not found") + getsvnbin() class TestWCSvnCommandPath(CommonSvnTests): def setup_class(cls): Modified: py/trunk/py/rest/testing/setup.py ============================================================================== --- py/trunk/py/rest/testing/setup.py (original) +++ py/trunk/py/rest/testing/setup.py Sun Sep 21 17:15:28 2008 @@ -8,3 +8,4 @@ tmpdir = py.test.ensuretemp(rel.replace(pydir.sep, '_')) mydatadir.copy(tmpdir) return tmpdir + Modified: py/trunk/py/rest/testing/test_directive.py ============================================================================== --- py/trunk/py/rest/testing/test_directive.py (original) +++ py/trunk/py/rest/testing/test_directive.py Sun Sep 21 17:15:28 2008 @@ -1,13 +1,10 @@ import py -try: - import docutils -except ImportError: - py.test.skip("docutils not present") +from py.__.rest.testing.setup import getdata +docutils = py.test.importorskip("docutils") from py.__.rest import directive from py.__.misc import rest from py.__.rest.latex import process_rest_file -from py.__.rest.testing.setup import getdata def setup_module(mod): mod.datadir = getdata() Modified: py/trunk/py/rest/testing/test_htmlrest.py ============================================================================== --- py/trunk/py/rest/testing/test_htmlrest.py (original) +++ py/trunk/py/rest/testing/test_htmlrest.py Sun Sep 21 17:15:28 2008 @@ -4,16 +4,13 @@ from py.__.misc import rest from py.__.rest.testing.setup import getdata + def setup_module(mod): + py.test.importorskip("docutils") if not py.path.local.sysfind("gs") or \ not py.path.local.sysfind("dot") or \ not py.path.local.sysfind("latex"): py.test.skip("ghostscript, graphviz and latex needed") - try: - import docutils - except ImportError: - py.test.skip("docutils not present") - mod.datadir = getdata() def test_process_simple(): Modified: py/trunk/py/rest/testing/test_rst2pdf.py ============================================================================== --- py/trunk/py/rest/testing/test_rst2pdf.py (original) +++ py/trunk/py/rest/testing/test_rst2pdf.py Sun Sep 21 17:15:28 2008 @@ -4,11 +4,7 @@ from py.__.rest.latex import process_configfile, process_rest_file from py.__.rest.testing.setup import getdata -try: - import docutils -except ImportError: - py.test.skip("docutils not present") - +docutils = py.test.importorskip("docutils") def setup_module(mod): if not py.path.local.sysfind("gs") or \ Modified: py/trunk/py/test/outcome.py ============================================================================== --- py/trunk/py/test/outcome.py (original) +++ py/trunk/py/test/outcome.py Sun Sep 21 17:15:28 2008 @@ -49,31 +49,30 @@ __tracebackhide__ = True raise Exit(msg) -def skip(msg="", ifraises=None, ns=None): - """ (conditionally) skip this test/module/conftest. - - msg: use this message when skipping. - ifraises: - if "exec ifraises in {'py': py}" raises an exception - skip this test. - ns: use this namespace when executing ifraises - """ +def skip(msg=""): + """ skip with the given message. """ __tracebackhide__ = True - if ifraises is not None: - ifraises = py.code.Source(ifraises).compile() - if ns is None: - ns = {} - try: - exec ifraises in ns - except (KeyboardInterrupt, SystemExit): - raise - except Exception, e: - if not msg: - msg = repr(e) - else: - return raise Skipped(msg=msg) +def importorskip(modname, minversion=None): + """ return imported module or skip() """ + compile(modname, '', 'eval') # to catch syntaxerrors + try: + mod = __import__(modname) + except ImportError: + py.test.skip("could not import %r" %(modname,)) + if minversion is None: + return mod + verattr = getattr(mod, '__version__', None) + if isinstance(minversion, str): + minver = minversion.split(".") + else: + minver = list(minversion) + if verattr is None or verattr.split(".") < minver: + py.test.skip("module %r has __version__ %r, required is: %r" %( + modname, verattr, minversion)) + return mod + def fail(msg="unknown failure"): """ fail with the given Message. """ __tracebackhide__ = True Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Sun Sep 21 17:15:28 2008 @@ -387,7 +387,7 @@ class TestInteractive(AcceptBase): def getspawn(self): - py.test.skip(ifraises="import pexpect", ns=globals()) + pexpect = py.test.importorskip("pexpect") def spawn(cmd): return pexpect.spawn(cmd, logfile=self.tmpdir.join("spawn.out").open("w")) return spawn Modified: py/trunk/py/test/testing/test_outcome.py ============================================================================== --- py/trunk/py/test/testing/test_outcome.py (original) +++ py/trunk/py/test/testing/test_outcome.py Sun Sep 21 17:15:28 2008 @@ -59,33 +59,25 @@ py.test.deprecated_call(dep_explicit, 0) py.test.deprecated_call(dep_explicit, 0) - - def test_skip_simple(): excinfo = py.test.raises(Skipped, 'py.test.skip("xxx")') assert excinfo.traceback[-1].frame.code.name == "skip" assert excinfo.traceback[-1].ishidden() -def test_skip_ifraises(): - excinfo = py.test.raises(Skipped, ''' - py.test.skip(ifraises=""" - import lky - """) - ''') - assert excinfo.traceback[-1].frame.code.name == "skip" - assert excinfo.traceback[-1].ishidden() - assert excinfo.value.msg.startswith("ImportError") - -def test_skip_ifraises_ns(): - d = {} - py.test.skip(ns=d, ifraises="import py") - assert d['py'] == py - -def test_skip_ifraises_syntaxerror(): +def test_importorskip(): try: - excinfo = py.test.raises(SyntaxError, ''' - py.test.skip(ifraises="x y z")''') + sys = py.test.importorskip("sys") + assert sys == py.std.sys + #path = py.test.importorskip("os.path") + #assert path == py.std.os.path + py.test.raises(Skipped, "py.test.importorskip('alskdj')") + py.test.raises(SyntaxError, "py.test.importorskip('x y z')") + py.test.raises(SyntaxError, "py.test.importorskip('x=y')") + path = py.test.importorskip("py", minversion=".".join(py.__version__)) + py.test.raises(Skipped, """ + py.test.importorskip("py", minversion="5.0") + """) except Skipped: - py.test.fail("should not skip") - assert not excinfo.traceback[-1].ishidden() + print py.code.ExceptionInfo() + py.test.fail("spurious skip") From hpk at codespeak.net Sun Sep 21 17:24:49 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 21 Sep 2008 17:24:49 +0200 (CEST) Subject: [py-svn] r58309 - in py/trunk/py/cmdline: . testing Message-ID: <20080921152449.A977416A154@codespeak.net> Author: hpk Date: Sun Sep 21 17:24:47 2008 New Revision: 58309 Modified: py/trunk/py/cmdline/pysvnwcrevert.py py/trunk/py/cmdline/testing/test_generic.py Log: fix a test, add a comment (pysvnwcrevert cannot be invoked generically) Modified: py/trunk/py/cmdline/pysvnwcrevert.py ============================================================================== --- py/trunk/py/cmdline/pysvnwcrevert.py (original) +++ py/trunk/py/cmdline/pysvnwcrevert.py Sun Sep 21 17:24:47 2008 @@ -44,6 +44,8 @@ svnwcrevert(p, root) +# XXX use optparse, and add a functional test + def main(): import sys if len(sys.argv) < 2: Modified: py/trunk/py/cmdline/testing/test_generic.py ============================================================================== --- py/trunk/py/cmdline/testing/test_generic.py (original) +++ py/trunk/py/cmdline/testing/test_generic.py Sun Sep 21 17:24:47 2008 @@ -47,10 +47,10 @@ def test_script_invocation(): if iswin32: - for script in binwinpath.listdir("py.*"): - assert script.ext == ".cmd" - yield checkprocess, script + scripts = binwinpath.listdir("py.*") else: - for script in binpath.listdir("py.*"): - yield checkprocess, script - + scripts = binpath.listdir("py.*") + scripts = [x for x in scripts + if not x.basename.startswith("py.svnwcrevert")] + for script in scripts: + yield checkprocess, script From hpk at codespeak.net Sun Sep 21 19:51:46 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 21 Sep 2008 19:51:46 +0200 (CEST) Subject: [py-svn] r58311 - py/trunk/py/test Message-ID: <20080921175146.82019169F01@codespeak.net> Author: hpk Date: Sun Sep 21 19:51:44 2008 New Revision: 58311 Modified: py/trunk/py/test/collect.py Log: remove dead code. Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Sun Sep 21 19:51:44 2008 @@ -500,9 +500,6 @@ return dest -def depwarn(msg): - APIWARN("1.0", msg, stacklevel=2) - def warnoldcollect(): APIWARN("1.0", "implement collector.collect() instead of " From pedronis at codespeak.net Mon Sep 22 14:34:56 2008 From: pedronis at codespeak.net (pedronis at codespeak.net) Date: Mon, 22 Sep 2008 14:34:56 +0200 (CEST) Subject: [py-svn] r58316 - in py/trunk/py/test: . testing Message-ID: <20080922123456.3A618169EB1@codespeak.net> Author: pedronis Date: Mon Sep 22 14:34:54 2008 New Revision: 58316 Added: py/trunk/py/test/resultlog.py - copied, changed from r58238, py/trunk/contrib/filelog/session.py py/trunk/py/test/testing/test_resultlog.py - copied, changed from r58238, py/trunk/contrib/filelog/test/test_filelog.py Modified: py/trunk/py/test/defaultconftest.py py/trunk/py/test/session.py py/trunk/py/test/testing/test_config.py Log: (iko, pedronis) move the FileLogSession into the py.lib proper, activated with the option --resultlog (suggestions for a better name are welcome) - added its tests - plus a functional/integration test in test_config in the style of the one for eventlog Modified: py/trunk/py/test/defaultconftest.py ============================================================================== --- py/trunk/py/test/defaultconftest.py (original) +++ py/trunk/py/test/defaultconftest.py Mon Sep 22 14:34:54 2008 @@ -112,5 +112,8 @@ "argument pointing to a script)."), Option('', '--session', action="store", dest="session", default=None, - help="lookup given sessioname in conftest.py files and use it."), + help="lookup given sessioname in conftest.py files and use it."), + Option('--resultlog', action="store", + default=None, dest="resultlog", + help="path for machine-readable result log") ) Copied: py/trunk/py/test/resultlog.py (from r58238, py/trunk/contrib/filelog/session.py) ============================================================================== --- py/trunk/contrib/filelog/session.py (original) +++ py/trunk/py/test/resultlog.py Mon Sep 22 14:34:54 2008 @@ -1,15 +1,6 @@ import py -from py.__.test.session import Session from py.__.test import event -def add_filelog_option(): - py.test.config.addoptions("filelog session options " - "(use --session=FileLogSession)", - py.test.config.Option('--filelog', action="store", - default=None, dest="filelog", - help="path for filelog session logging")) - - def generic_path(item): chain = item.listchain() gpath = [chain[0].name] @@ -33,14 +24,11 @@ fspath = newfspath return ''.join(gpath) -class FileLogSession(Session): +class ResultLog(object): - def __init__(self, config): - super(FileLogSession, self).__init__(config) - self.bus.subscribe(self.log_event_to_file) - if hasattr(config.option, 'filelog'): - filelog = config.option.filelog - self.logfile = open(filelog, 'w') # line buffering ? + def __init__(self, bus, logfile): + bus.subscribe(self.log_event_to_file) + self.logfile = logfile #open(logpath, 'w') # line buffering ? def write_log_entry(self, shortrepr, name, longrepr): print >>self.logfile, "%s %s" % (shortrepr, name) Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Mon Sep 22 14:34:54 2008 @@ -9,6 +9,7 @@ from py.__.test import event, outcome from py.__.test.event import EventBus import py.__.test.custompdb +from py.__.test.resultlog import ResultLog # used for genitems() from py.__.test.outcome import Exit @@ -34,6 +35,10 @@ print >>f, ev f.flush() self.bus.subscribe(eventwrite) + resultlog = self.config.option.resultlog + if resultlog: + f = py.path.local(resultlog).open('w') + self.resultlog = ResultLog(self.bus, f) def fixoptions(self): """ check, fix and determine conflicting options. """ Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Mon Sep 22 14:34:54 2008 @@ -181,6 +181,27 @@ s = eventlog.read() assert s.find("TestrunStart") != -1 + def test_session_resultlog(self): + from py.__.test.collect import Item + from py.__.test.runner import OutcomeRepr + + resultlog = self.tmpdir.join("test_session_resultlog") + config = py.test.config._reparse([self.tmpdir, + '--resultlog=%s' % resultlog]) + + session = config.initsession() + + item = Item("a", config=config) + outcome = OutcomeRepr('execute', '.', '') + rep_ev = event.ItemTestReport(item, passed=outcome) + + session.bus.notify(rep_ev) + + session.resultlog.logfile.flush() + + s = resultlog.read() + assert s.find(". a") != -1 + def test_tracedir_tracer(self): tracedir = self.tmpdir.join("tracedir") config = py.test.config._reparse([self.tmpdir, Copied: py/trunk/py/test/testing/test_resultlog.py (from r58238, py/trunk/contrib/filelog/test/test_filelog.py) ============================================================================== --- py/trunk/contrib/filelog/test/test_filelog.py (original) +++ py/trunk/py/test/testing/test_resultlog.py Mon Sep 22 14:34:54 2008 @@ -1,14 +1,10 @@ -import py - -try: - from filelog import session -except ImportError: - # convenience, make these tests runnable where filelog lives under pypy - from pypy.tool.pytest.filelog import session - import os, StringIO +import py + +from py.__.test import resultlog from py.__.test.collect import Node, Item, FSCollector +from py.__.test.event import EventBus from py.__.test.event import ItemTestReport, CollectionReport from py.__.test.event import InternalException from py.__.test.runner import OutcomeRepr @@ -26,7 +22,7 @@ p3 = Node('()', parent = p2) item = Item('c', parent = p3) - res = session.generic_path(item) + res = resultlog.generic_path(item) assert res == 'a.B().c' p0 = FSCollector('proj/test', config='dummy') @@ -36,7 +32,7 @@ p4 = Node('c', parent=p3) item = Item('[1]', parent = p4) - res = session.generic_path(item) + res = resultlog.generic_path(item) assert res == 'test/a:B().c[1]' @@ -52,66 +48,49 @@ return node return Item(names[-1], parent=node) -class TestFileLogSession(object): - - - def test_sanity(self): - option = Fake(eventlog=None) - config = Fake(option=option) - - session.FileLogSession(config) +class TestResultLog(object): - def test_open_logfile(self): - logfname = os.tempnam() - - option = Fake(eventlog=None, filelog=logfname) - config = Fake(option=option) + def test_create(self): + bus = EventBus() + logfile = object() - sess = session.FileLogSession(config) - - assert len(sess.bus._subscribers) == 1 - - assert sess.logfile - assert os.path.exists(logfname) - - sess.logfile.close() - os.unlink(logfname) + reslog = resultlog.ResultLog(bus, logfile) + assert len(bus._subscribers) == 1 + assert reslog.logfile is logfile def test_write_log_entry(self): - option = Fake(eventlog=None) - config = Fake(option=option) - sess = session.FileLogSession(config) - - sess.logfile = StringIO.StringIO() - sess.write_log_entry('.', 'name', '') - entry = sess.logfile.getvalue() + reslog = resultlog.ResultLog(EventBus(), None) + + reslog.logfile = StringIO.StringIO() + reslog.write_log_entry('.', 'name', '') + entry = reslog.logfile.getvalue() assert entry[-1] == '\n' entry_lines = entry.splitlines() assert len(entry_lines) == 1 assert entry_lines[0] == '. name' - sess.logfile = StringIO.StringIO() - sess.write_log_entry('s', 'name', 'Skipped') - entry = sess.logfile.getvalue() + reslog.logfile = StringIO.StringIO() + reslog.write_log_entry('s', 'name', 'Skipped') + entry = reslog.logfile.getvalue() assert entry[-1] == '\n' entry_lines = entry.splitlines() assert len(entry_lines) == 2 assert entry_lines[0] == 's name' assert entry_lines[1] == ' Skipped' - sess.logfile = StringIO.StringIO() - sess.write_log_entry('s', 'name', 'Skipped\n') - entry = sess.logfile.getvalue() + reslog.logfile = StringIO.StringIO() + reslog.write_log_entry('s', 'name', 'Skipped\n') + entry = reslog.logfile.getvalue() assert entry[-1] == '\n' entry_lines = entry.splitlines() assert len(entry_lines) == 2 assert entry_lines[0] == 's name' assert entry_lines[1] == ' Skipped' - sess.logfile = StringIO.StringIO() + reslog.logfile = StringIO.StringIO() longrepr = ' tb1\n tb 2\nE tb3\nSome Error' - sess.write_log_entry('F', 'name', longrepr) - entry = sess.logfile.getvalue() + reslog.write_log_entry('F', 'name', longrepr) + entry = reslog.logfile.getvalue() assert entry[-1] == '\n' entry_lines = entry.splitlines() assert len(entry_lines) == 5 @@ -119,11 +98,8 @@ assert entry_lines[1:] == [' '+line for line in longrepr.splitlines()] def test_log_outcome(self): - option = Fake(eventlog=None) - config = Fake(option=option) - sess = session.FileLogSession(config) + reslog = resultlog.ResultLog(EventBus(), StringIO.StringIO()) - sess.logfile = StringIO.StringIO() colitem = make_item('some', 'path', 'a', 'b') try: @@ -134,67 +110,61 @@ outcome=OutcomeRepr('execute', 'F', the_repr) ev = Fake(colitem=colitem, outcome=outcome) - sess.log_outcome(ev) + reslog.log_outcome(ev) - entry = sess.logfile.getvalue() + entry = reslog.logfile.getvalue() entry_lines = entry.splitlines() assert entry_lines[0] == 'F some.path.a.b' assert entry_lines[-1][0] == ' ' assert 'ValueError' in entry - def test_item_test_passed(self): - option = Fake(eventlog=None) - config = Fake(option=option) - sess = session.FileLogSession(config) - sess.logfile = StringIO.StringIO() + def test_item_test_passed(self): + bus = EventBus() + reslog = resultlog.ResultLog(bus, StringIO.StringIO()) colitem = make_item('proj/test', 'proj/test/mod', 'a', 'b') outcome=OutcomeRepr('execute', '.', '') rep_ev = ItemTestReport(colitem, passed=outcome) - sess.bus.notify(rep_ev) + bus.notify(rep_ev) - lines = sess.logfile.getvalue().splitlines() + lines = reslog.logfile.getvalue().splitlines() assert len(lines) == 1 line = lines[0] assert line.startswith(". ") assert line[2:] == 'test/mod:a.b' - def test_collection_report(self): - option = Fake(eventlog=None) - config = Fake(option=option) - sess = session.FileLogSession(config) - sess.logfile = StringIO.StringIO() + def test_collection_report(self): + bus = EventBus() + reslog = resultlog.ResultLog(bus, None) + reslog.logfile = StringIO.StringIO() colitem = make_item('proj/test', 'proj/test/mod', 'A', None) - outcome=OutcomeRepr('execute', '', '') rep_ev = CollectionReport(colitem, object(), passed=outcome) - sess.bus.notify(rep_ev) + bus.notify(rep_ev) - entry = sess.logfile.getvalue() + entry = reslog.logfile.getvalue() assert not entry - sess.logfile = StringIO.StringIO() + reslog.logfile = StringIO.StringIO() outcome=OutcomeRepr('execute', 'F', 'Some Error') rep_ev = CollectionReport(colitem, object(), failed=outcome) - sess.bus.notify(rep_ev) + bus.notify(rep_ev) - lines = sess.logfile.getvalue().splitlines() + lines = reslog.logfile.getvalue().splitlines() assert len(lines) == 2 assert lines[0] == 'F test/mod:A' def test_internal_exception(self): # they are produced for example by a teardown failing # at the end of the run - option = Fake(eventlog=None) - config = Fake(option=option) - sess = session.FileLogSession(config) - sess.logfile = StringIO.StringIO() + bus = EventBus() + reslog = resultlog.ResultLog(bus, StringIO.StringIO()) try: raise ValueError @@ -203,9 +173,9 @@ internal = InternalException(excinfo) - sess.bus.notify(internal) + bus.notify(internal) - entry = sess.logfile.getvalue() + entry = reslog.logfile.getvalue() entry_lines = entry.splitlines() assert entry_lines[0].startswith('! ') From pedronis at codespeak.net Mon Sep 22 14:49:41 2008 From: pedronis at codespeak.net (pedronis at codespeak.net) Date: Mon, 22 Sep 2008 14:49:41 +0200 (CEST) Subject: [py-svn] r58318 - py/trunk/contrib/filelog Message-ID: <20080922124941.E2283169ED7@codespeak.net> Author: pedronis Date: Mon Sep 22 14:49:41 2008 New Revision: 58318 Removed: py/trunk/contrib/filelog/ Log: (iko, pedronis) this lives in py.lib trunk proper now From hpk at codespeak.net Mon Sep 22 15:15:47 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 22 Sep 2008 15:15:47 +0200 (CEST) Subject: [py-svn] r58319 - in py/trunk/py/test/looponfail: . testing Message-ID: <20080922131547.CFB44169E1B@codespeak.net> Author: hpk Date: Mon Sep 22 15:15:47 2008 New Revision: 58319 Modified: py/trunk/py/test/looponfail/remote.py py/trunk/py/test/looponfail/testing/test_remote.py Log: be more grateful on tests that disappear during a looponfailing session. Modified: py/trunk/py/test/looponfail/remote.py ============================================================================== --- py/trunk/py/test/looponfail/remote.py (original) +++ py/trunk/py/test/looponfail/remote.py Mon Sep 22 15:15:47 2008 @@ -137,8 +137,14 @@ session.reporter._tw.hasmarkup = hasmarkup session.reporter._tw.fullwidth = width if trails: - colitems = [py.test.collect.Collector._fromtrail(x, config) - for x in trails] + colitems = [] + for trail in trails: + try: + colitem = py.test.collect.Collector._fromtrail(trail, config) + except AssertionError, e: + #XXX session.bus.notify of "test disappeared" + continue + colitems.append(colitem) else: colitems = None session.shouldclose = channel.isclosed Modified: py/trunk/py/test/looponfail/testing/test_remote.py ============================================================================== --- py/trunk/py/test/looponfail/testing/test_remote.py (original) +++ py/trunk/py/test/looponfail/testing/test_remote.py Mon Sep 22 15:15:47 2008 @@ -112,3 +112,30 @@ session.loop_once(loopstate) assert len(loopstate.colitems) == 1 + + def test_looponfailing_removed_test(self): + modcol = self.getmodulecol(""" + def test_one(): + assert 0 + def test_two(): + assert 0 + """) + session = LooponfailingSession(modcol._config) + loopstate = LoopState() + session.remotecontrol.setup() + loopstate.colitems = [] + session.loop_once(loopstate) + assert len(loopstate.colitems) == 2 + + modcol.fspath.write(py.code.Source(""" + def test_xxx(): # renamed test + assert 0 + def test_two(): + assert 1 # pass now + """)) + assert session.statrecorder.check() + session.loop_once(loopstate) + assert len(loopstate.colitems) == 0 + + session.loop_once(loopstate) + assert len(loopstate.colitems) == 1 From pedronis at codespeak.net Mon Sep 22 21:30:16 2008 From: pedronis at codespeak.net (pedronis at codespeak.net) Date: Mon, 22 Sep 2008 21:30:16 +0200 (CEST) Subject: [py-svn] r58340 - py/trunk/py/cmdline/testing Message-ID: <20080922193016.38E62169E7A@codespeak.net> Author: pedronis Date: Mon Sep 22 21:30:13 2008 New Revision: 58340 Modified: py/trunk/py/cmdline/testing/test_cmdline.py Log: this seems to need to be this way, otherwise a py.lookup from PATH will be used or a failure will occur instead Modified: py/trunk/py/cmdline/testing/test_cmdline.py ============================================================================== --- py/trunk/py/cmdline/testing/test_cmdline.py (original) +++ py/trunk/py/cmdline/testing/test_cmdline.py Mon Sep 22 21:30:13 2008 @@ -4,7 +4,7 @@ class TestPyLookup(AcceptBase): def test_basic(self): p = self.makepyfile(hello="def x(): pass") - result = self.run("py.lookup", "pass") + result = self.runpybin("py.lookup", "pass") suptest.assert_lines_contain_lines(result.outlines, ['%s:*def x(): pass' %(p.basename)] ) From pedronis at codespeak.net Mon Sep 22 22:34:41 2008 From: pedronis at codespeak.net (pedronis at codespeak.net) Date: Mon, 22 Sep 2008 22:34:41 +0200 (CEST) Subject: [py-svn] r58350 - py/trunk/py/cmdline Message-ID: <20080922203441.5C05D169F09@codespeak.net> Author: pedronis Date: Mon Sep 22 22:34:40 2008 New Revision: 58350 Modified: py/trunk/py/cmdline/pysvnwcrevert.py Log: use optparse Modified: py/trunk/py/cmdline/pysvnwcrevert.py ============================================================================== --- py/trunk/py/cmdline/pysvnwcrevert.py (original) +++ py/trunk/py/cmdline/pysvnwcrevert.py Mon Sep 22 22:34:40 2008 @@ -1,6 +1,6 @@ #! /usr/bin/env python """\ -py.svnwcrevert WCPATH [precious...] +py.svnwcrevert [options] WCPATH Running this script and then 'svn up' puts the working copy WCPATH in a state as clean as a fresh check-out. @@ -14,12 +14,9 @@ The goal of this script is to leave the working copy with some files and directories possibly missing, but - most importantly - in a state where the following 'svn up' won't just crash. - -Optionally filenames that should be left untouched can be passed as arguments -too. """ -import py +import sys, py def kill(p, root): print '< %s' % (p.relto(root),) @@ -43,12 +40,17 @@ elif p.check(dir=1): svnwcrevert(p, root) +# XXX add a functional test +optparse = py.compat.optparse -# XXX use optparse, and add a functional test +parser = optparse.OptionParser(usage=__doc__) +parser.add_option("-p", "--precious", + action="append", dest="precious", default=[], + help="preserve files with this name") def main(): - import sys - if len(sys.argv) < 2: - print __doc__ + opts, args = parser.parse_args() + if len(args) != 1: + parser.print_help() sys.exit(2) - svnwcrevert(py.path.local(sys.argv[1]), precious=sys.argv[1:]) + svnwcrevert(py.path.local(args[0]), precious=opts.precious) From hpk at codespeak.net Tue Sep 23 08:29:19 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 23 Sep 2008 08:29:19 +0200 (CEST) Subject: [py-svn] r58360 - py/trunk/py/path/svn Message-ID: <20080923062919.3DA15169E51@codespeak.net> Author: hpk Date: Tue Sep 23 08:29:17 2008 New Revision: 58360 Modified: py/trunk/py/path/svn/wccommand.py Log: factor import of xml into one method to deal more gracefully with implementations that don't have expat. Modified: py/trunk/py/path/svn/wccommand.py ============================================================================== --- py/trunk/py/path/svn/wccommand.py (original) +++ py/trunk/py/path/svn/wccommand.py Tue Sep 23 08:29:17 2008 @@ -9,8 +9,6 @@ """ import os, sys, time, re, calendar -from xml.dom import minidom -from xml.parsers.expat import ExpatError import py from py.__.path import common from py.__.path.svn import cache @@ -481,13 +479,10 @@ 'svn log --xml %s %s %s "%s"' % ( rev_opt, verbose_opt, auth_opt, self.strpath)) - from xml.dom import minidom - from xml.parsers.expat import ExpatError + minidom,ExpatError = importxml() try: tree = minidom.parse(stdout) except ExpatError: - # XXX not entirely sure about this exception... shouldn't it be - # some py.error.* something? raise ValueError('no such revision') result = [] for logentry in filter(None, tree.firstChild.childNodes): @@ -645,6 +640,7 @@ # unchanged ones in the status object so this is far from ideal rootstatus = WCStatus(rootwcpath, rev, modrev, author) update_rev = None + minidom, ExpatError = importxml() try: doc = minidom.parseString(data) except ExpatError, e: @@ -809,3 +805,10 @@ def error_enhance((cls, error, tb)): raise cls, error, tb +def importxml(cache=[]): + if cache: + return cache + from xml.dom import minidom + from xml.parsers.expat import ExpatError + cache.extend([minidom, ExpatError]) + return cache From hpk at codespeak.net Tue Sep 23 12:41:24 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 23 Sep 2008 12:41:24 +0200 (CEST) Subject: [py-svn] r58375 - py/trunk/py/doc Message-ID: <20080923104124.D0B35168502@codespeak.net> Author: hpk Date: Tue Sep 23 12:41:22 2008 New Revision: 58375 Added: py/trunk/py/doc/draft_pyfs Modified: py/trunk/py/doc/future.txt Log: some updates on the filesystem front Added: py/trunk/py/doc/draft_pyfs ============================================================================== --- (empty file) +++ py/trunk/py/doc/draft_pyfs Tue Sep 23 12:41:22 2008 @@ -0,0 +1,79 @@ + +Let's do a walk through a memory filesystem. + +.. >>> import py + + +working with directories +--------------------------------- + +Let's create some directories and list them from memory:: + +>>> fs = py.fs.MemoryFS() +>>> fs.mkdir("x") +>>> fs.mkdir("y") +>>> fs.listdir() +['x', 'y'] + + +Creating, removing and reading files +--------------------------------------------- + +>>> f = fs.open('x/file', 'w') +>>> f.write("hello world") +>>> f.close() +>>> fs.listdir("x") +['file'] +>>> f = fs.open("x/file", 'r') +>>> f.readlines() +['hello world'] +>>> f.seek(6) +>>> f.read(3) +"wor" +>>> f.read() +"ld" +>>> f.close() +>>> fs.remove("y") +>>> fs.listdir() +['x'] +>>> fs.remove("non-existent") +py.error.ENOENT + +stat / checking for meta information +--------------------------------------- + +>>> stat = memory.stat("x") +>>> stat.isdir() +True +>>> stat.isfile() +False +>>> stat.exists() +True +>>> stat.islink() +False + +Linking to other objects +-------------------------------------------------------- + +First an example how to link internally, i.e. within the +filesystem. + +>>> fs.link("newitem", "x") +>>> fs.stat("newitem").islink() +True +>>> fs.stat("newitem").isfile() +True +>>> fs.remove("newitem") # only deletes the link itself +>>> fs.stat("x").exists() + +cross-filesystem references +--------------------------------- + +>>> otherfs = py.fs.MemoryFS() + +XXX + +>>> fs.setproxy("newitem", otherfs, "otheritem") +>>> fs.stat("newitem").exists() +False +>>> otherfs.mkdir("otheritem") Modified: py/trunk/py/doc/future.txt ============================================================================== --- py/trunk/py/doc/future.txt (original) +++ py/trunk/py/doc/future.txt Tue Sep 23 12:41:22 2008 @@ -90,7 +90,7 @@ Refactor path implementations to use a Filesystem Abstraction ============================================================= -It seems like a good idea to refactor all python implementations to +It seems like a good idea to refactor all `py.path`_ Path implementations to use an internal Filesystem abstraction. The current code base would be transformed to have Filesystem implementations for e.g. local, subversion and subversion "working copy" filesystems. Today @@ -110,6 +110,20 @@ `reiserfs v4 features`_ at the Filesystem level but that is a can of subsequent worms). +Also interesting to check out is Will McGugan's work on +his `fs package`_. + +I think the main question is what the very fundamental +filesystem API should look like. Here are some doctests +on how a `draft py.fs`_ could look like. There also +is Matthew Scotts `dictproxy patch`_ which adds +``py.path.dict`` and ``py.path.proxy``. + + +.. _`dictproxy patch`: http://codespeak.net/pipermail/py-dev/attachments/20050128/d9595512/virtual1-0001.bin +.. _`draft py.fs`: draft_pyfs +.. _`py.path`: http://codespeak.net/py/dist/path.html +.. _`fs package`: http://www.willmcgugan.com/2008/09/21/announcing-fs-010-a-python-file-system/#comment-60276 .. _`memoryfs`: http://codespeak.net/svn/user/arigo/hack/pyfuse/memoryfs.py .. _`dictfs`: http://codespeak.net/pipermail/py-dev/2005-January/000191.html .. _`pylufs`: http://codespeak.net/svn/user/arigo/hack/pylufs/ From hpk at codespeak.net Tue Sep 23 16:25:19 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 23 Sep 2008 16:25:19 +0200 (CEST) Subject: [py-svn] r58384 - py/trunk/py/test/testing Message-ID: <20080923142519.4F656169F94@codespeak.net> Author: hpk Date: Tue Sep 23 16:25:16 2008 New Revision: 58384 Added: py/trunk/py/test/testing/test_recording.py (contents, props changed) Log: adding a test for unimplemented recording feature Added: py/trunk/py/test/testing/test_recording.py ============================================================================== --- (empty file) +++ py/trunk/py/test/testing/test_recording.py Tue Sep 23 16:25:16 2008 @@ -0,0 +1,45 @@ +import py,sys +py.test.skip("implementation missing: recording") + +from py.__.test.testing import suptest +from py.__.test.acceptance_test import AcceptBase + +class TestRecordingAccept(AcceptBase): + def test_recording_and_back(self): + p = self.makepyfile(test_one=""" + import py + def test_fail(): + assert x + def test_skip(): + py.test.skip("hello") + def test_pass(): + pass + """) + rdir = py.path.local("rdir") + result = self.runpytest(p, "--record=%s" %(rdir)) + record = py.test.RecordDir(result) + testrun = record.getlastrun() + assert testrun.sys.platform == sys.platform + assert testrun.sys.version_info == sys.version_info + assert testrun.sys.executable == sys.executable + + baseadress = ("test_one.py",) + failures = testrun.getfailures() + assert len(failures) == 1 + failure = failures[0] + assert failure.testaddress == baseadress + ("test_fail",) + assert failure.location.find("test_one.py:3") != -1 + assert failure.errmessage + assert failure.reprfailure # probably just a string for now + + skipped = testrun.getskipped() + assert len(skipped) == 1 + skip = skipped[0] + assert skip.testaddress == baseaddress + ("test_skip",) + assert skip.location == "test_one.py:7" + + passed = testrun.getpassed() + assert len(passed) == 1 + p = passed[0] + assert p.testaddress == baseaddress + ("test_skip",) + From hpk at codespeak.net Tue Sep 23 16:28:13 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 23 Sep 2008 16:28:13 +0200 (CEST) Subject: [py-svn] r58385 - in py/trunk/py: . execnet/testing misc misc/testing path/svn/testing process process/testing Message-ID: <20080923142813.B1102169F95@codespeak.net> Author: hpk Date: Tue Sep 23 16:28:13 2008 New Revision: 58385 Added: py/trunk/py/process/killproc.py - copied, changed from r58363, py/trunk/py/misc/killproc.py py/trunk/py/process/testing/test_killproc.py - copied, changed from r58363, py/trunk/py/misc/testing/test_oskill.py Removed: py/trunk/py/misc/killproc.py py/trunk/py/misc/testing/test_oskill.py Modified: py/trunk/py/__init__.py py/trunk/py/execnet/testing/test_gateway.py py/trunk/py/path/svn/testing/test_auth.py Log: * move cross-platform process kill functionality and move it to py.process.kill(pid) * simplify test_auth.py * use new functionality from some tests Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Tue Sep 23 16:28:13 2008 @@ -99,6 +99,7 @@ 'process.__doc__' : ('./process/__init__.py', '__doc__'), 'process.cmdexec' : ('./process/cmdexec.py', 'cmdexec'), + 'process.kill' : ('./process/killproc.py', 'kill'), 'process.ForkedFunc' : ('./process/forkedfunc.py', 'ForkedFunc'), # path implementation Modified: py/trunk/py/execnet/testing/test_gateway.py ============================================================================== --- py/trunk/py/execnet/testing/test_gateway.py (original) +++ py/trunk/py/execnet/testing/test_gateway.py Tue Sep 23 16:28:13 2008 @@ -516,8 +516,6 @@ def test_waitclose_on_remote_killed(self): py.test.skip("fix needed: dying remote process does not cause waitclose() to fail") - if not hasattr(py.std.os, 'kill'): - py.test.skip("no os.kill") gw = py.execnet.PopenGateway() channel = gw.remote_exec(""" import os @@ -527,7 +525,7 @@ channel.send("#" * 100) """) remotepid = channel.receive() - os.kill(remotepid, 9) + py.process.kill(remotepid) py.test.raises(channel.RemoteError, "channel.waitclose(TESTTIMEOUT)") py.test.raises(EOFError, channel.send, None) py.test.raises(EOFError, channel.receive) Deleted: /py/trunk/py/misc/killproc.py ============================================================================== --- /py/trunk/py/misc/killproc.py Tue Sep 23 16:28:13 2008 +++ (empty file) @@ -1,10 +0,0 @@ - -import py -import os, sys - -def killproc(pid): - if sys.platform == "win32": - py.process.cmdexec("taskkill /F /PID %d" %(pid,)) - else: - os.kill(pid, 15) - Deleted: /py/trunk/py/misc/testing/test_oskill.py ============================================================================== --- /py/trunk/py/misc/testing/test_oskill.py Tue Sep 23 16:28:13 2008 +++ (empty file) @@ -1,23 +0,0 @@ - -import py, sys - -from py.__.misc.killproc import killproc - -def test_win_killsubprocess(): - if sys.platform == 'win32' and not py.path.local.sysfind('taskkill'): - py.test.skip("you\'re using an older version of windows, which " - "doesn\'t support 'taskkill' - py.misc.killproc is not " - "available") - try: - import subprocess - except ImportError: - py.test.skip("no subprocess module") - tmp = py.test.ensuretemp("test_win_killsubprocess") - t = tmp.join("t.py") - t.write("import time ; time.sleep(100)") - proc = py.std.subprocess.Popen([sys.executable, str(t)]) - assert proc.poll() is None # no return value yet - killproc(proc.pid) - ret = proc.wait() - assert ret != 0 - Modified: py/trunk/py/path/svn/testing/test_auth.py ============================================================================== --- py/trunk/py/path/svn/testing/test_auth.py (original) +++ py/trunk/py/path/svn/testing/test_auth.py Tue Sep 23 16:28:13 2008 @@ -3,7 +3,6 @@ import svntestbase from threading import Thread import time -from py.__.misc.killproc import killproc from py.__.conftest import option def make_repo_auth(repo, userdata): @@ -269,6 +268,10 @@ func_name)) self.auth = py.path.SvnAuth('johnny', 'foo', cache_auth=False, interactive=False) + self.port, self.pid = self._start_svnserve() + + def teardown_method(self, method): + py.process.kill(self.pid) def _start_svnserve(self): make_repo_auth(self.repopath, {'johnny': ('foo', 'rw')}) @@ -279,203 +282,168 @@ class TestSvnWCAuthFunctional(SvnAuthFunctionalTestBase): def test_checkout_constructor_arg(self): - port, pid = self._start_svnserve() - try: - wc = py.path.svnwc(self.temppath, auth=self.auth) - wc.checkout( - 'svn://localhost:%s/%s' % (port, self.repopath.basename)) - assert wc.join('.svn').check() - finally: - # XXX can we do this in a teardown_method too? not sure if that's - # guaranteed to get called... - killproc(pid) + port = self.port + wc = py.path.svnwc(self.temppath, auth=self.auth) + wc.checkout( + 'svn://localhost:%s/%s' % (port, self.repopath.basename)) + assert wc.join('.svn').check() def test_checkout_function_arg(self): - port, pid = self._start_svnserve() - try: - wc = py.path.svnwc(self.temppath, auth=self.auth) - wc.checkout( - 'svn://localhost:%s/%s' % (port, self.repopath.basename)) - assert wc.join('.svn').check() - finally: - killproc(pid) + port = self.port + wc = py.path.svnwc(self.temppath, auth=self.auth) + wc.checkout( + 'svn://localhost:%s/%s' % (port, self.repopath.basename)) + assert wc.join('.svn').check() def test_checkout_failing_non_interactive(self): - port, pid = self._start_svnserve() - try: - auth = py.path.SvnAuth('johnny', 'bar', cache_auth=False, - interactive=False) - wc = py.path.svnwc(self.temppath, auth) - py.test.raises(Exception, - ("wc.checkout('svn://localhost:%s/%s' % " - "(port, self.repopath.basename))")) - finally: - killproc(pid) + port = self.port + auth = py.path.SvnAuth('johnny', 'bar', cache_auth=False, + interactive=False) + wc = py.path.svnwc(self.temppath, auth) + py.test.raises(Exception, + ("wc.checkout('svn://localhost:%s/%s' % " + "(port, self.repopath.basename))")) def test_log(self): - port, pid = self._start_svnserve() - try: - wc = py.path.svnwc(self.temppath, self.auth) - wc.checkout( - 'svn://localhost:%s/%s' % (port, self.repopath.basename)) - foo = wc.ensure('foo.txt') - wc.commit('added foo.txt') - log = foo.log() - assert len(log) == 1 - assert log[0].msg == 'added foo.txt' - finally: - killproc(pid) + port = self.port + wc = py.path.svnwc(self.temppath, self.auth) + wc.checkout( + 'svn://localhost:%s/%s' % (port, self.repopath.basename)) + foo = wc.ensure('foo.txt') + wc.commit('added foo.txt') + log = foo.log() + assert len(log) == 1 + assert log[0].msg == 'added foo.txt' def test_switch(self): - port, pid = self._start_svnserve() - try: - wc = py.path.svnwc(self.temppath, auth=self.auth) - svnurl = 'svn://localhost:%s/%s' % (port, self.repopath.basename) - wc.checkout(svnurl) - wc.ensure('foo', dir=True).ensure('foo.txt').write('foo') - wc.commit('added foo dir with foo.txt file') - wc.ensure('bar', dir=True) - wc.commit('added bar dir') - bar = wc.join('bar') - bar.switch(svnurl + '/foo') - assert bar.join('foo.txt') - finally: - killproc(pid) + port = self.port + wc = py.path.svnwc(self.temppath, auth=self.auth) + svnurl = 'svn://localhost:%s/%s' % (port, self.repopath.basename) + wc.checkout(svnurl) + wc.ensure('foo', dir=True).ensure('foo.txt').write('foo') + wc.commit('added foo dir with foo.txt file') + wc.ensure('bar', dir=True) + wc.commit('added bar dir') + bar = wc.join('bar') + bar.switch(svnurl + '/foo') + assert bar.join('foo.txt') def test_update(self): - port, pid = self._start_svnserve() - try: - wc1 = py.path.svnwc(self.temppath.ensure('wc1', dir=True), - auth=self.auth) - wc2 = py.path.svnwc(self.temppath.ensure('wc2', dir=True), - auth=self.auth) - wc1.checkout( - 'svn://localhost:%s/%s' % (port, self.repopath.basename)) - wc2.checkout( - 'svn://localhost:%s/%s' % (port, self.repopath.basename)) - wc1.ensure('foo', dir=True) - wc1.commit('added foo dir') - wc2.update() - assert wc2.join('foo').check() - - auth = py.path.SvnAuth('unknown', 'unknown', interactive=False) - wc2.auth = auth - py.test.raises(Exception, 'wc2.update()') - finally: - killproc(pid) + port = self.port + wc1 = py.path.svnwc(self.temppath.ensure('wc1', dir=True), + auth=self.auth) + wc2 = py.path.svnwc(self.temppath.ensure('wc2', dir=True), + auth=self.auth) + wc1.checkout( + 'svn://localhost:%s/%s' % (port, self.repopath.basename)) + wc2.checkout( + 'svn://localhost:%s/%s' % (port, self.repopath.basename)) + wc1.ensure('foo', dir=True) + wc1.commit('added foo dir') + wc2.update() + assert wc2.join('foo').check() + + auth = py.path.SvnAuth('unknown', 'unknown', interactive=False) + wc2.auth = auth + py.test.raises(Exception, 'wc2.update()') def test_lock_unlock_status(self): - port, pid = self._start_svnserve() - try: - wc = py.path.svnwc(self.temppath, auth=self.auth) - wc.checkout( - 'svn://localhost:%s/%s' % (port, self.repopath.basename,)) - wc.ensure('foo', file=True) - wc.commit('added foo file') - foo = wc.join('foo') - foo.lock() - status = foo.status() - assert status.locked - foo.unlock() - status = foo.status() - assert not status.locked - - auth = py.path.SvnAuth('unknown', 'unknown', interactive=False) - foo.auth = auth - py.test.raises(Exception, 'foo.lock()') - py.test.raises(Exception, 'foo.unlock()') - finally: - killproc(pid) + port = self.port + wc = py.path.svnwc(self.temppath, auth=self.auth) + wc.checkout( + 'svn://localhost:%s/%s' % (port, self.repopath.basename,)) + wc.ensure('foo', file=True) + wc.commit('added foo file') + foo = wc.join('foo') + foo.lock() + status = foo.status() + assert status.locked + foo.unlock() + status = foo.status() + assert not status.locked + + auth = py.path.SvnAuth('unknown', 'unknown', interactive=False) + foo.auth = auth + py.test.raises(Exception, 'foo.lock()') + py.test.raises(Exception, 'foo.unlock()') def test_diff(self): - port, pid = self._start_svnserve() - try: - wc = py.path.svnwc(self.temppath, auth=self.auth) - wc.checkout( - 'svn://localhost:%s/%s' % (port, self.repopath.basename,)) - wc.ensure('foo', file=True) - wc.commit('added foo file') - wc.update() - rev = int(wc.status().rev) - foo = wc.join('foo') - foo.write('bar') - diff = foo.diff() - assert '\n+bar\n' in diff - foo.commit('added some content') - diff = foo.diff() - assert not diff - diff = foo.diff(rev=rev) - assert '\n+bar\n' in diff - - auth = py.path.SvnAuth('unknown', 'unknown', interactive=False) - foo.auth = auth - py.test.raises(Exception, 'foo.diff(rev=rev)') - finally: - killproc(pid) + port = self.port + wc = py.path.svnwc(self.temppath, auth=self.auth) + wc.checkout( + 'svn://localhost:%s/%s' % (port, self.repopath.basename,)) + wc.ensure('foo', file=True) + wc.commit('added foo file') + wc.update() + rev = int(wc.status().rev) + foo = wc.join('foo') + foo.write('bar') + diff = foo.diff() + assert '\n+bar\n' in diff + foo.commit('added some content') + diff = foo.diff() + assert not diff + diff = foo.diff(rev=rev) + assert '\n+bar\n' in diff + + auth = py.path.SvnAuth('unknown', 'unknown', interactive=False) + foo.auth = auth + py.test.raises(Exception, 'foo.diff(rev=rev)') class TestSvnURLAuthFunctional(SvnAuthFunctionalTestBase): def test_listdir(self): - port, pid = self._start_svnserve() - try: - u = py.path.svnurl( - 'svn://localhost:%s/%s' % (port, self.repopath.basename), - auth=self.auth) - u.ensure('foo') - paths = u.listdir() - assert len(paths) == 1 - assert paths[0].auth is self.auth - - auth = SvnAuth('foo', 'bar', interactive=False) - u = py.path.svnurl( - 'svn://localhost:%s/%s' % (port, self.repopath.basename), - auth=auth) - py.test.raises(Exception, 'u.listdir()') - finally: - killproc(pid) + port = self.port + u = py.path.svnurl( + 'svn://localhost:%s/%s' % (port, self.repopath.basename), + auth=self.auth) + u.ensure('foo') + paths = u.listdir() + assert len(paths) == 1 + assert paths[0].auth is self.auth + + auth = SvnAuth('foo', 'bar', interactive=False) + u = py.path.svnurl( + 'svn://localhost:%s/%s' % (port, self.repopath.basename), + auth=auth) + py.test.raises(Exception, 'u.listdir()') def test_copy(self): - port, pid = self._start_svnserve() - try: - u = py.path.svnurl( - 'svn://localhost:%s/%s' % (port, self.repopath.basename), - auth=self.auth) - foo = u.ensure('foo') - bar = u.join('bar') - foo.copy(bar) - assert bar.check() - assert bar.auth is self.auth - - auth = SvnAuth('foo', 'bar', interactive=False) - u = py.path.svnurl( - 'svn://localhost:%s/%s' % (port, self.repopath.basename), - auth=auth) - foo = u.join('foo') - bar = u.join('bar') - py.test.raises(Exception, 'foo.copy(bar)') - finally: - killproc(pid) + port = self.port + u = py.path.svnurl( + 'svn://localhost:%s/%s' % (port, self.repopath.basename), + auth=self.auth) + foo = u.ensure('foo') + bar = u.join('bar') + foo.copy(bar) + assert bar.check() + assert bar.auth is self.auth + + auth = SvnAuth('foo', 'bar', interactive=False) + u = py.path.svnurl( + 'svn://localhost:%s/%s' % (port, self.repopath.basename), + auth=auth) + foo = u.join('foo') + bar = u.join('bar') + py.test.raises(Exception, 'foo.copy(bar)') def test_write_read(self): - port, pid = self._start_svnserve() - try: - u = py.path.svnurl( - 'svn://localhost:%s/%s' % (port, self.repopath.basename), - auth=self.auth) - foo = u.ensure('foo') - fp = foo.open() - try: - data = fp.read() - finally: - fp.close() - assert data == '' - - auth = SvnAuth('foo', 'bar', interactive=False) - u = py.path.svnurl( - 'svn://localhost:%s/%s' % (port, self.repopath.basename), - auth=auth) - foo = u.join('foo') - py.test.raises(Exception, 'foo.open()') - finally: - killproc(pid) + port = self.port + u = py.path.svnurl( + 'svn://localhost:%s/%s' % (port, self.repopath.basename), + auth=self.auth) + foo = u.ensure('foo') + fp = foo.open() + try: + data = fp.read() + finally: + fp.close() + assert data == '' + + auth = SvnAuth('foo', 'bar', interactive=False) + u = py.path.svnurl( + 'svn://localhost:%s/%s' % (port, self.repopath.basename), + auth=auth) + foo = u.join('foo') + py.test.raises(Exception, 'foo.open()') # XXX rinse, repeat... :| Copied: py/trunk/py/process/killproc.py (from r58363, py/trunk/py/misc/killproc.py) ============================================================================== --- py/trunk/py/misc/killproc.py (original) +++ py/trunk/py/process/killproc.py Tue Sep 23 16:28:13 2008 @@ -1,10 +1,23 @@ - import py import os, sys -def killproc(pid): - if sys.platform == "win32": - py.process.cmdexec("taskkill /F /PID %d" %(pid,)) - else: - os.kill(pid, 15) - +if sys.platform == "win32": + try: + import ctypes + except ImportError: + def dokill(pid): + py.process.cmdexec("taskkill /F /PID %d" %(pid,)) + else: + def dokill(pid): + PROCESS_TERMINATE = 1 + handle = ctypes.windll.kernel32.OpenProcess( + PROCESS_TERMINATE, False, process.pid) + ctypes.windll.kernel32.TerminateProcess(handle, -1) + ctypes.windll.kernel32.CloseHandle(handle) +else: + def dokill(pid): + os.kill(pid, 15) + +def kill(pid): + """ kill process by id. """ + dokill(pid) Copied: py/trunk/py/process/testing/test_killproc.py (from r58363, py/trunk/py/misc/testing/test_oskill.py) ============================================================================== --- py/trunk/py/misc/testing/test_oskill.py (original) +++ py/trunk/py/process/testing/test_killproc.py Tue Sep 23 16:28:13 2008 @@ -1,23 +1,13 @@ import py, sys -from py.__.misc.killproc import killproc - -def test_win_killsubprocess(): - if sys.platform == 'win32' and not py.path.local.sysfind('taskkill'): - py.test.skip("you\'re using an older version of windows, which " - "doesn\'t support 'taskkill' - py.misc.killproc is not " - "available") - try: - import subprocess - except ImportError: - py.test.skip("no subprocess module") - tmp = py.test.ensuretemp("test_win_killsubprocess") +def test_kill(): + subprocess = py.test.importorskip("subprocess") + tmp = py.test.ensuretemp("test_kill") t = tmp.join("t.py") t.write("import time ; time.sleep(100)") proc = py.std.subprocess.Popen([sys.executable, str(t)]) assert proc.poll() is None # no return value yet - killproc(proc.pid) + py.process.kill(proc.pid) ret = proc.wait() assert ret != 0 - From hpk at codespeak.net Tue Sep 23 16:42:30 2008 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 23 Sep 2008 16:42:30 +0200 (CEST) Subject: [py-svn] r58386 - py/trunk/py/doc Message-ID: <20080923144230.BF8EC169F4A@codespeak.net> Author: hpk Date: Tue Sep 23 16:42:27 2008 New Revision: 58386 Modified: py/trunk/py/doc/download.txt Log: adding info for debian and rpm systems Modified: py/trunk/py/doc/download.txt ============================================================================== --- py/trunk/py/doc/download.txt (original) +++ py/trunk/py/doc/download.txt Tue Sep 23 16:42:27 2008 @@ -12,8 +12,9 @@ The py lib and its tools are expected to work well on Linux, Windows and OSX, Python versions 2.3, 2.4, 2.5 and 2.6. -Note that we provide binary eggs for Windows machines - on -non-Windows systems you need a working C-compiler in order to +We provide binary eggs for Windows machines. + +On other systems you need a working C-compiler in order to install the full py lib. If you don't have a compiler available you can still install the py lib but without greenlets - look below for the ``install_lib`` target. @@ -25,8 +26,21 @@ (Unfortunately we don't know about a way to execute this code automatically during the above install). +Installing on Debian or Fedora +=================================== + +On Debian systems look for ``python-codespeak-lib``. +*This package is probably outdated - if somebody +can help with bringing this up to date, +that would be very much appreciated.* + +Dwayne Bailey has thankfully put together a Fedora `RPM`_. + +.. _`RPM`: http://translate.sourceforge.net/releases/testing/fedora/pylib-0.9.2-1.fc9.noarch.rpm + .. _`setuptools installation`: http://pypi.python.org/pypi/setuptools + Downloading a tar/zip archive and installing that ===================================================