From py-svn at codespeak.net Thu Jan 1 05:21:37 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Thu, 1 Jan 2009 05:21:37 +0100 (CET) Subject: [py-svn] Scarlett's home video stolen Message-ID: <20090101042137.830BC16840A@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Fri Jan 2 14:04:01 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Fri, 2 Jan 2009 14:04:01 +0100 (CET) Subject: [py-svn] Your massive 2009 tool Message-ID: <20090102130401.D3E37168487@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Tue Jan 6 14:47:49 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Tue, 6 Jan 2009 14:47:49 +0100 (CET) Subject: [py-svn] Finally a breakthrough in male enlargement Message-ID: <20090106134749.298F016849B@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Tue Jan 6 14:51:49 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Tue, 6 Jan 2009 14:51:49 +0100 (CET) Subject: [py-svn] Absolutely free Message-ID: <20090106135149.050F21684FA@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Tue Jan 6 03:29:30 2009 From: py-svn at codespeak.net (admin@Viagra.com) Date: Tue, 6 Jan 2009 07:29:30 +0500 Subject: [py-svn] psomt py-svn@codespeak.net; New Year 70% OFF on Pfizer nxgs Message-ID: An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Wed Jan 7 20:05:37 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Wed, 7 Jan 2009 20:05:37 +0100 (CET) Subject: [py-svn] Discover the secrets of thousands here Message-ID: <20090107190537.5A998168545@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Thu Jan 8 19:23:40 2009 From: py-svn at codespeak.net (Doctor Amos) Date: Thu, 8 Jan 2009 19:23:40 +0100 (CET) Subject: [py-svn] (Canadian Pharmacy Message) Solving ALL love making problems in a matter of few minutes. Message-ID: <20090108082223.3589.qmail@mta.email.webmd.com> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Thu Jan 8 22:13:47 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Thu, 8 Jan 2009 22:13:47 +0100 (CET) Subject: [py-svn] Thinking of satisfying your lady in bed? Message-ID: <20090108211347.5ADA116849E@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Thu Jan 8 22:35:27 2009 From: py-svn at codespeak.net (Doctor Kira) Date: Fri, 9 Jan 2009 04:35:27 +0700 Subject: [py-svn] =?utf-8?q?Additional_20=25_Off_Sale_=E2=80=93_This_Weeke?= =?utf-8?q?nd_Only?= Message-ID: <49458-6571-ZTEVQ7-SLPN-40CF-SL5YA-3CE0-H-M2-20090108-19db97de9d922b8493@e-dialog.com> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Fri Jan 9 21:35:28 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Fri, 9 Jan 2009 21:35:28 +0100 (CET) Subject: [py-svn] 3 inches can be yours Message-ID: <20090109203528.7F48416846B@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Sun Jan 11 09:31:32 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Sun, 11 Jan 2009 09:31:32 +0100 (CET) Subject: [py-svn] Give her the time of her life Message-ID: <20090111083132.BF17B16843F@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Mon Jan 12 16:24:41 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Mon, 12 Jan 2009 16:24:41 +0100 (CET) Subject: [py-svn] I've gained inches and thickness Message-ID: <20090112152441.54875168442@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Tue Jan 13 04:34:44 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Tue, 13 Jan 2009 04:34:44 +0100 (CET) Subject: [py-svn] Pleasure her the right ways Message-ID: <20090113033444.B6D77168423@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Wed Jan 14 07:49:30 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Wed, 14 Jan 2009 07:49:30 +0100 (CET) Subject: [py-svn] The secret to a huge package Message-ID: <20090114064930.6E01A1684C9@codespeak.net> An HTML attachment was scrubbed... URL: From hpk at codespeak.net Wed Jan 14 21:07:07 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 14 Jan 2009 21:07:07 +0100 (CET) Subject: [py-svn] r60974 - py/trunk/py/doc Message-ID: <20090114200707.35E801684A3@codespeak.net> Author: hpk Date: Wed Jan 14 21:07:05 2009 New Revision: 60974 Modified: py/trunk/py/doc/test.txt Log: some reformulations, less "magic" mentionings Modified: py/trunk/py/doc/test.txt ============================================================================== --- py/trunk/py/doc/test.txt (original) +++ py/trunk/py/doc/test.txt Wed Jan 14 21:07:05 2009 @@ -65,12 +65,6 @@ assertion fails the test ``reporter`` will provide you with a very helpful analysis and a clean traceback. -Note that in order to display helpful analysis of a failing -``assert`` statement some magic takes place behind the -scenes. For now, you only need to know that if something -looks strange or you suspect a bug in that -*behind-the-scenes-magic* you may turn off the magic by -providing the ``--nomagic`` option. how to write assertions about exceptions ---------------------------------------- @@ -223,17 +217,16 @@ failure situation. ``py.test`` uses the same order for presenting tracebacks as Python -itself: the outer function is shown first, and the most recent call is -shown last. Similarly, a ``py.test`` reported traceback starts with your -failing test function and then works its way downwards. If the maximum -recursion depth has been exceeded during the running of a test, for -instance because of infinite recursion, ``py.test`` will indicate -where in the code the recursion was taking place. You can -inhibit traceback "cutting" magic by supplying ``--fulltrace``. - -There is also the possibility of usind ``--tb=short`` to get the regular Python -tracebacks (which can sometimes be useful when they are extremely long). Or you -can use ``--tb=no`` to not show any tracebacks at all. +itself: the oldest function call is shown first, and the most recent call is +shown last. A ``py.test`` reported traceback starts with your +failing test function. If the maximum recursion depth has been +exceeded during the running of a test, for instance because of +infinite recursion, ``py.test`` will indicate where in the +code the recursion was taking place. You can inhibit +traceback "cutting" magic by supplying ``--fulltrace``. + +There is also the possibility of using ``--tb=short`` to get regular CPython +tracebacks. Or you can use ``--tb=no`` to not show any tracebacks at all. no inheritance requirement -------------------------- From hpk at codespeak.net Wed Jan 14 21:08:54 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 14 Jan 2009 21:08:54 +0100 (CET) Subject: [py-svn] r60975 - py/trunk Message-ID: <20090114200854.B15E41684A3@codespeak.net> Author: hpk Date: Wed Jan 14 21:08:52 2009 New Revision: 60975 Modified: py/trunk/TODO.txt Log: a bit of planning for 1.0 Modified: py/trunk/TODO.txt ============================================================================== --- py/trunk/TODO.txt (original) +++ py/trunk/TODO.txt Wed Jan 14 21:08:52 2009 @@ -1,13 +1,24 @@ Things to do for 1.0.0 ========================= +- ease building of py lib, only depend on python interp + - separate py.magic.greenlet into its own project, and remove c-extension + +- scale down "magic" impressions & improve py.test: + - documentation entry points for users + - documentation entry points for extenders + - advertise plugins instead of conftest! + - new config system + - small things: --nomagic -> --no-reassert + - deprecate py.magic + py.test -------------- - compatilibity: honour/warn item.run() method of test items (so far probably only execute() is warned about or the other way round) -- introduce plugin arch, port to plugins: +- introduce plugin arch, port existing things to plugins: - importorskip - filelog - chtmpdir per method @@ -83,6 +94,8 @@ - refine doctests usage (particularly skips of doctests if some imports/conditions are not satisfied) + - check if it works on win32 + - refine error reporting (don't show python tracebacks) - generalization of "host specifications" for execnet and py.test --dist usages in particular (see also revision 37500 which @@ -111,6 +124,14 @@ - have config options from environment, command line or conftest's + have py/doc/config/"OPTNAME".txt for each option pypy-style + + py.test --showconfig shows current configuration according + to envvars, cmdlineopts and conftests considered for your dir location. + + py.test --help-conftest lists all possible environment envs + py.test --help-env lists all possible environment envs + - consider features of py.apigen (recheck closed "M1016") - integrate rlcompleter2 (make it remotely workable) From hpk at codespeak.net Wed Jan 14 22:02:16 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 14 Jan 2009 22:02:16 +0100 (CET) Subject: [py-svn] r60976 - py/trunk/py/execnet Message-ID: <20090114210216.E71EF16851C@codespeak.net> Author: hpk Date: Wed Jan 14 22:02:14 2009 New Revision: 60976 Added: py/trunk/py/execnet/improve-remote-tracebacks.txt (contents, props changed) Log: a few notes regarding improving remote tracebacks for py.execnet Added: py/trunk/py/execnet/improve-remote-tracebacks.txt ============================================================================== --- (empty file) +++ py/trunk/py/execnet/improve-remote-tracebacks.txt Wed Jan 14 22:02:14 2009 @@ -0,0 +1,31 @@ +Goal: "remote" tracebacks have file/lineno references to local source code + +Current Problem: + tracebacks have file/lineno references that point to one + large file (consisting of all source code of all modules that + we sent as a bootstrap). Makes debugging execnet harder. + +solution variant 0: minimal change to source code + at master side: send name/source pairs + at slave side: put each module into own file, re-using basename of source file + +solution variant 1: aimed at simplicity + pack and send zip of py lib containing py.__.execnet, py.__.thread, etc + put this into some file-location at the other side, add it to sys.path + (or experiment with zipfile-imports over RAM and sys.meta_hooks/path_hooks) + +solution variant 2: aimed at minimizing bandwidth usage + + initiating side receiving side + ------------------------------------------------------------- + loop: + send [dottedname1: hash1] + serve_hash_requests + if not lookup(hash1): + send(dottedname1: hash1) + content = receive(hash1) + else: + content = get(hash1) + + + From hpk at codespeak.net Wed Jan 14 22:07:05 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 14 Jan 2009 22:07:05 +0100 (CET) Subject: [py-svn] r60977 - py/trunk Message-ID: <20090114210705.9E88616851C@codespeak.net> Author: hpk Date: Wed Jan 14 22:07:05 2009 New Revision: 60977 Modified: py/trunk/TODO.txt Log: add a few notes that i discussed with Brian when i was in Seattle Modified: py/trunk/TODO.txt ============================================================================== --- py/trunk/TODO.txt (original) +++ py/trunk/TODO.txt Wed Jan 14 22:07:05 2009 @@ -86,6 +86,39 @@ or other code that collects data from running a program (in our case running the tests) +Criticism and solutions +-------------------------------- +"too big": + - too much code, you need entire py lib, hard to include into app + + have a small pytest boostrap that loads pylib.zip from net + + provide smaller script ala simpy + - lots of cmdline options, possibilities, documentation + rather unsorted + +"needless differences between py.test and nosetests": + - py.test.skip + - raises + +"tutorial structure missing", e.g.: + - "how to get started" in a minimal way, also how to use + existing conftests/plugins + - how to configure py.test + - how to write plugins/extensions + +"too much magic" + - re-execution of assert expressions + + rename "--nomagic" to something that turns off "superassertions" + + hint at --tb=... + - get rid of py/magic directory + +has a good ui but could be better + - support developer communication, e.g. py.test + --sendfailures=freenode-pypy + --sendfailures=pocoo # prints out paste.pocoo.url with traceback + - graphical interface, probably QT + - generally store test results and use them for subsequent calls + + ld (review and shift to above) ================================= @@ -257,3 +290,5 @@ self.outfile.close() IOError: [Errno 32] Broken pipe + + From hpk at codespeak.net Wed Jan 14 23:29:33 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 14 Jan 2009 23:29:33 +0100 (CET) Subject: [py-svn] r60979 - py/branch/pytestplugin Message-ID: <20090114222933.49B7C16851E@codespeak.net> Author: hpk Date: Wed Jan 14 23:29:32 2009 New Revision: 60979 Added: py/branch/pytestplugin/ (props changed) - copied from r60978, py/trunk/ Log: open a branch for implementing pytest plugins From hpk at codespeak.net Wed Jan 14 23:45:32 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 14 Jan 2009 23:45:32 +0100 (CET) Subject: [py-svn] r60981 - in py/branch/pytestplugin/py/test: . plugin plugin/testing testing Message-ID: <20090114224532.97C931684A6@codespeak.net> Author: hpk Date: Wed Jan 14 23:45:31 2009 New Revision: 60981 Added: py/branch/pytestplugin/py/test/plugin/ py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py (contents, props changed) - copied, changed from r60979, py/branch/pytestplugin/py/test/resultlog.py py/branch/pytestplugin/py/test/plugin/testing/ py/branch/pytestplugin/py/test/plugin/testing/__init__.py (contents, props changed) py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py (contents, props changed) - copied, changed from r60979, py/branch/pytestplugin/py/test/testing/test_resultlog.py Removed: py/branch/pytestplugin/py/test/resultlog.py py/branch/pytestplugin/py/test/testing/test_resultlog.py Modified: py/branch/pytestplugin/py/test/defaultconftest.py py/branch/pytestplugin/py/test/session.py py/branch/pytestplugin/py/test/testing/test_config.py Log: play a bit with API/plugin protocol by moving the resultlog option to a plugin Modified: py/branch/pytestplugin/py/test/defaultconftest.py ============================================================================== --- py/branch/pytestplugin/py/test/defaultconftest.py (original) +++ py/branch/pytestplugin/py/test/defaultconftest.py Wed Jan 14 23:45:31 2009 @@ -113,7 +113,4 @@ Option('', '--session', action="store", dest="session", default=None, 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/branch/pytestplugin/py/test/plugin/pytest_resultlog.py (from r60979, py/branch/pytestplugin/py/test/resultlog.py) ============================================================================== --- py/branch/pytestplugin/py/test/resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py Wed Jan 14 23:45:31 2009 @@ -1,6 +1,28 @@ import py from py.__.test import event +def register_plugin(config): + config.register_plugin(ResultLogPlugin()) + +class ResultLogPlugin: + def pytest_addoptions(self, config): + config.addoptions("resultlog options", + config.Option('--resultlog', action="store", + default=None, dest="resultlog", + help="path for machine-readable result log") + ) + + def pytest_session_init(self, session): + resultlog = session.config.option.resultlog + if resultlog: + logfile = open(resultlog, 'w', 1) # line buffered + self.resultlog = ResultLog(logfile) + session.bus.subscribe(self.resultlog.log_event_to_file) + + def pytest_session_finish(self, session): + if hasattr(self, 'resultlog'): + session.bus.unsubscribe(self.resultlog.log_event_to_file) + def generic_path(item): chain = item.listchain() gpath = [chain[0].name] @@ -26,8 +48,7 @@ class ResultLog(object): - def __init__(self, bus, logfile): - bus.subscribe(self.log_event_to_file) + def __init__(self, logfile): self.logfile = logfile # preferably line buffered def write_log_entry(self, shortrepr, name, longrepr): @@ -49,6 +70,3 @@ elif isinstance(ev, event.InternalException): path = ev.repr.reprcrash.path # fishing :( self.write_log_entry('!', path, str(ev.repr)) - - - Added: py/branch/pytestplugin/py/test/plugin/testing/__init__.py ============================================================================== --- (empty file) +++ py/branch/pytestplugin/py/test/plugin/testing/__init__.py Wed Jan 14 23:45:31 2009 @@ -0,0 +1 @@ +# Copied: py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py (from r60979, py/branch/pytestplugin/py/test/testing/test_resultlog.py) ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py Wed Jan 14 23:45:31 2009 @@ -2,14 +2,18 @@ import py -from py.__.test import resultlog +import pytest_resultlog as resultlog + +from py.__.test import event +from py.__.test.session import Session +from py.__.test.testing import suptest +from py.__.test.config import Config 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 - class Fake(object): def __init__(self, **kwds): self.__dict__.update(kwds) @@ -48,19 +52,24 @@ return node return Item(names[-1], parent=node) +def test_registration(): + # this is a half-functional test, maybe should evolve to + # become a generic test that evolves to automatically test + # plugin registration + p = py.test.ensuretemp("registration").ensure("logfile") + myplugin = resultlog.ResultLogPlugin() + config = Config() + myplugin.pytest_addoptions(config) + config.parse(['--resultlog=%s' % p]) + session = Session(config) + myplugin.pytest_session_init(session) + assert myplugin.resultlog.log_event_to_file in session.bus._subscribers + myplugin.pytest_session_finish(session) + assert myplugin.resultlog.log_event_to_file not in session.bus._subscribers + class TestResultLog(object): - - def test_create(self): - bus = EventBus() - logfile = object() - - reslog = resultlog.ResultLog(bus, logfile) - assert len(bus._subscribers) == 1 - assert reslog.logfile is logfile - def test_write_log_entry(self): - reslog = resultlog.ResultLog(EventBus(), None) - + reslog = resultlog.ResultLog(None) reslog.logfile = StringIO.StringIO() reslog.write_log_entry('.', 'name', '') entry = reslog.logfile.getvalue() @@ -98,7 +107,7 @@ assert entry_lines[1:] == [' '+line for line in longrepr.splitlines()] def test_log_outcome(self): - reslog = resultlog.ResultLog(EventBus(), StringIO.StringIO()) + reslog = resultlog.ResultLog(StringIO.StringIO()) colitem = make_item('some', 'path', 'a', 'b') @@ -121,7 +130,8 @@ def test_item_test_passed(self): bus = EventBus() - reslog = resultlog.ResultLog(bus, StringIO.StringIO()) + reslog = resultlog.ResultLog(StringIO.StringIO()) + bus.subscribe(reslog.log_event_to_file) colitem = make_item('proj/test', 'proj/test/mod', 'a', 'b') @@ -138,7 +148,8 @@ def test_collection_report(self): bus = EventBus() - reslog = resultlog.ResultLog(bus, None) + reslog = resultlog.ResultLog(None) + bus.subscribe(reslog.log_event_to_file) reslog.logfile = StringIO.StringIO() colitem = make_item('proj/test', 'proj/test/mod', 'A', None) @@ -164,7 +175,8 @@ # they are produced for example by a teardown failing # at the end of the run bus = EventBus() - reslog = resultlog.ResultLog(bus, StringIO.StringIO()) + reslog = resultlog.ResultLog(StringIO.StringIO()) + bus.subscribe(reslog.log_event_to_file) try: raise ValueError @@ -182,3 +194,25 @@ assert os.path.basename(__file__)[:-1] in entry_lines[0] #.py/.pyc assert entry_lines[-1][0] == ' ' assert 'ValueError' in entry + +class TestSessionResultlog(suptest.FileCreation): + def test_session_resultlog(self): + py.test.skip("requires plugin registration mechanism") + 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) + + s = resultlog.read() + assert s.find(". a") != -1 + Deleted: /py/branch/pytestplugin/py/test/resultlog.py ============================================================================== --- /py/branch/pytestplugin/py/test/resultlog.py Wed Jan 14 23:45:31 2009 +++ (empty file) @@ -1,54 +0,0 @@ -import py -from py.__.test import event - -def generic_path(item): - chain = item.listchain() - gpath = [chain[0].name] - fspath = chain[0].fspath - fspart = False - for node in chain[1:]: - newfspath = node.fspath - if newfspath == fspath: - if fspart: - gpath.append(':') - fspart = False - else: - gpath.append('.') - else: - gpath.append('/') - fspart = True - name = node.name - if name[0] in '([': - gpath.pop() - gpath.append(name) - fspath = newfspath - return ''.join(gpath) - -class ResultLog(object): - - def __init__(self, bus, logfile): - bus.subscribe(self.log_event_to_file) - self.logfile = logfile # preferably line buffered - - def write_log_entry(self, shortrepr, name, longrepr): - print >>self.logfile, "%s %s" % (shortrepr, name) - for line in longrepr.splitlines(): - print >>self.logfile, " %s" % line - - def log_outcome(self, ev): - outcome = ev.outcome - gpath = generic_path(ev.colitem) - self.write_log_entry(outcome.shortrepr, gpath, str(outcome.longrepr)) - - def log_event_to_file(self, ev): - if isinstance(ev, event.ItemTestReport): - self.log_outcome(ev) - elif isinstance(ev, event.CollectionReport): - if not ev.passed: - self.log_outcome(ev) - elif isinstance(ev, event.InternalException): - path = ev.repr.reprcrash.path # fishing :( - self.write_log_entry('!', path, str(ev.repr)) - - - Modified: py/branch/pytestplugin/py/test/session.py ============================================================================== --- py/branch/pytestplugin/py/test/session.py (original) +++ py/branch/pytestplugin/py/test/session.py Wed Jan 14 23:45:31 2009 @@ -9,7 +9,6 @@ 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 @@ -35,10 +34,6 @@ print >>f, ev f.flush() self.bus.subscribe(eventwrite) - resultlog = self.config.option.resultlog - if resultlog: - f = open(resultlog, 'w', 1) # line buffered - self.resultlog = ResultLog(self.bus, f) def fixoptions(self): """ check, fix and determine conflicting options. """ Modified: py/branch/pytestplugin/py/test/testing/test_config.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_config.py (original) +++ py/branch/pytestplugin/py/test/testing/test_config.py Wed Jan 14 23:45:31 2009 @@ -181,25 +181,6 @@ 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) - - 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, Deleted: /py/branch/pytestplugin/py/test/testing/test_resultlog.py ============================================================================== --- /py/branch/pytestplugin/py/test/testing/test_resultlog.py Wed Jan 14 23:45:31 2009 +++ (empty file) @@ -1,184 +0,0 @@ -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 - - -class Fake(object): - def __init__(self, **kwds): - self.__dict__.update(kwds) - - -def test_generic_path(): - p1 = Node('a', config='dummy') - assert p1.fspath is None - p2 = Node('B', parent=p1) - p3 = Node('()', parent = p2) - item = Item('c', parent = p3) - - res = resultlog.generic_path(item) - assert res == 'a.B().c' - - p0 = FSCollector('proj/test', config='dummy') - p1 = FSCollector('proj/test/a', parent=p0) - p2 = Node('B', parent=p1) - p3 = Node('()', parent = p2) - p4 = Node('c', parent=p3) - item = Item('[1]', parent = p4) - - res = resultlog.generic_path(item) - assert res == 'test/a:B().c[1]' - - -def make_item(*names): - node = None - config = "dummy" - for name in names[:-1]: - if '/' in name: - node = FSCollector(name, parent=node, config=config) - else: - node = Node(name, parent=node, config=config) - if names[-1] is None: - return node - return Item(names[-1], parent=node) - -class TestResultLog(object): - - def test_create(self): - bus = EventBus() - logfile = object() - - reslog = resultlog.ResultLog(bus, logfile) - assert len(bus._subscribers) == 1 - assert reslog.logfile is logfile - - def test_write_log_entry(self): - 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' - - 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' - - 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' - - reslog.logfile = StringIO.StringIO() - longrepr = ' tb1\n tb 2\nE tb3\nSome Error' - reslog.write_log_entry('F', 'name', longrepr) - entry = reslog.logfile.getvalue() - assert entry[-1] == '\n' - entry_lines = entry.splitlines() - assert len(entry_lines) == 5 - assert entry_lines[0] == 'F name' - assert entry_lines[1:] == [' '+line for line in longrepr.splitlines()] - - def test_log_outcome(self): - reslog = resultlog.ResultLog(EventBus(), StringIO.StringIO()) - - colitem = make_item('some', 'path', 'a', 'b') - - try: - raise ValueError - except ValueError: - the_repr = py.code.ExceptionInfo().getrepr() - - outcome=OutcomeRepr('execute', 'F', the_repr) - ev = Fake(colitem=colitem, outcome=outcome) - - reslog.log_outcome(ev) - - 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): - 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) - - bus.notify(rep_ev) - - 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): - 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) - - bus.notify(rep_ev) - - entry = reslog.logfile.getvalue() - assert not entry - - reslog.logfile = StringIO.StringIO() - outcome=OutcomeRepr('execute', 'F', 'Some Error') - rep_ev = CollectionReport(colitem, object(), failed=outcome) - - bus.notify(rep_ev) - - 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 - bus = EventBus() - reslog = resultlog.ResultLog(bus, StringIO.StringIO()) - - try: - raise ValueError - except ValueError: - excinfo = py.code.ExceptionInfo() - - internal = InternalException(excinfo) - - bus.notify(internal) - - entry = reslog.logfile.getvalue() - entry_lines = entry.splitlines() - - assert entry_lines[0].startswith('! ') - assert os.path.basename(__file__)[:-1] in entry_lines[0] #.py/.pyc - assert entry_lines[-1][0] == ' ' - assert 'ValueError' in entry From hpk at codespeak.net Thu Jan 15 13:20:56 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 15 Jan 2009 13:20:56 +0100 (CET) Subject: [py-svn] r60991 - in py/branch/pytestplugin/py/test/plugin: . testing Message-ID: <20090115122056.97839168531@codespeak.net> Author: hpk Date: Thu Jan 15 13:20:54 2009 New Revision: 60991 Modified: py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py Log: * introduce configure/unconfigure hooks for plugins, * some import cleanup Modified: py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py Thu Jan 15 13:20:54 2009 @@ -1,3 +1,7 @@ +""" +pytest "resultlog" plugin for machine-readable logging of test results. + +""" import py from py.__.test import event @@ -12,16 +16,20 @@ help="path for machine-readable result log") ) - def pytest_session_init(self, session): - resultlog = session.config.option.resultlog + def pytest_configure(self, config): + resultlog = config.option.resultlog if resultlog: logfile = open(resultlog, 'w', 1) # line buffered self.resultlog = ResultLog(logfile) - session.bus.subscribe(self.resultlog.log_event_to_file) - def pytest_session_finish(self, session): + def pytest_unconfigure(self, config): if hasattr(self, 'resultlog'): - session.bus.unsubscribe(self.resultlog.log_event_to_file) + self.resultlog.logfile.close() + del self.resultlog + + def pytest_event(self, event): + if self.resultlog: + self.resultlog.log_event_to_file(event) def generic_path(item): chain = item.listchain() Modified: py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py Thu Jan 15 13:20:54 2009 @@ -9,9 +9,6 @@ from py.__.test.testing import suptest from py.__.test.config import Config 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 class Fake(object): @@ -60,12 +57,12 @@ myplugin = resultlog.ResultLogPlugin() config = Config() myplugin.pytest_addoptions(config) + assert not config.option.resultlog config.parse(['--resultlog=%s' % p]) - session = Session(config) - myplugin.pytest_session_init(session) - assert myplugin.resultlog.log_event_to_file in session.bus._subscribers - myplugin.pytest_session_finish(session) - assert myplugin.resultlog.log_event_to_file not in session.bus._subscribers + myplugin.pytest_configure(config) + assert myplugin.resultlog + myplugin.pytest_unconfigure(config) + assert not hasattr(myplugin, 'resultlog') class TestResultLog(object): def test_write_log_entry(self): @@ -129,14 +126,14 @@ assert 'ValueError' in entry def test_item_test_passed(self): - bus = EventBus() + bus = event.EventBus() reslog = resultlog.ResultLog(StringIO.StringIO()) bus.subscribe(reslog.log_event_to_file) colitem = make_item('proj/test', 'proj/test/mod', 'a', 'b') outcome=OutcomeRepr('execute', '.', '') - rep_ev = ItemTestReport(colitem, passed=outcome) + rep_ev = event.ItemTestReport(colitem, passed=outcome) bus.notify(rep_ev) @@ -147,14 +144,14 @@ assert line[2:] == 'test/mod:a.b' def test_collection_report(self): - bus = EventBus() + bus = event.EventBus() reslog = resultlog.ResultLog(None) bus.subscribe(reslog.log_event_to_file) reslog.logfile = StringIO.StringIO() colitem = make_item('proj/test', 'proj/test/mod', 'A', None) outcome=OutcomeRepr('execute', '', '') - rep_ev = CollectionReport(colitem, object(), passed=outcome) + rep_ev = event.CollectionReport(colitem, object(), passed=outcome) bus.notify(rep_ev) @@ -163,7 +160,7 @@ reslog.logfile = StringIO.StringIO() outcome=OutcomeRepr('execute', 'F', 'Some Error') - rep_ev = CollectionReport(colitem, object(), failed=outcome) + rep_ev = event.CollectionReport(colitem, object(), failed=outcome) bus.notify(rep_ev) @@ -174,7 +171,7 @@ def test_internal_exception(self): # they are produced for example by a teardown failing # at the end of the run - bus = EventBus() + bus = event.EventBus() reslog = resultlog.ResultLog(StringIO.StringIO()) bus.subscribe(reslog.log_event_to_file) @@ -183,7 +180,7 @@ except ValueError: excinfo = py.code.ExceptionInfo() - internal = InternalException(excinfo) + internal = event.InternalException(excinfo) bus.notify(internal) From hpk at codespeak.net Thu Jan 15 14:28:24 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 15 Jan 2009 14:28:24 +0100 (CET) Subject: [py-svn] r60993 - in py/branch/pytestplugin/py/test: . dsession/testing plugin plugin/testing testing Message-ID: <20090115132824.0341F168546@codespeak.net> Author: hpk Date: Thu Jan 15 14:28:23 2009 New Revision: 60993 Modified: py/branch/pytestplugin/py/test/defaultconftest.py py/branch/pytestplugin/py/test/dsession/testing/test_functional_dsession.py py/branch/pytestplugin/py/test/event.py py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py py/branch/pytestplugin/py/test/session.py py/branch/pytestplugin/py/test/testing/test_config.py py/branch/pytestplugin/py/test/testing/test_event.py Log: * factoring eventlogging into another plugin * making eventclasses available on events for easier isinstance-checking * normalizing/refining planned plugin-API a bit more Modified: py/branch/pytestplugin/py/test/defaultconftest.py ============================================================================== --- py/branch/pytestplugin/py/test/defaultconftest.py (original) +++ py/branch/pytestplugin/py/test/defaultconftest.py Thu Jan 15 14:28:23 2009 @@ -52,9 +52,6 @@ Option('', '--pdb', action="store_true", dest="usepdb", default=False, help="start pdb (the Python debugger) on errors."), - Option('', '--eventlog', - action="store", dest="eventlog", default=None, - help="write reporting events to given file."), Option('', '--tracedir', action="store", dest="tracedir", default=None, help="write tracing information to the given directory."), Modified: py/branch/pytestplugin/py/test/dsession/testing/test_functional_dsession.py ============================================================================== --- py/branch/pytestplugin/py/test/dsession/testing/test_functional_dsession.py (original) +++ py/branch/pytestplugin/py/test/dsession/testing/test_functional_dsession.py Thu Jan 15 14:28:23 2009 @@ -32,15 +32,6 @@ config = self.parseconfig(self.tmpdir, '-d') py.test.raises(SystemExit, "config.initsession()") - def test_session_eventlog_dist(self): - self.makepyfile(conftest="dist_hosts=['localhost']\n") - eventlog = self.tmpdir.join("mylog") - config = self.parseconfig(self.tmpdir, '-d', '--eventlog=%s' % eventlog) - session = config.initsession() - session.bus.notify(event.TestrunStart()) - s = eventlog.read() - assert s.find("TestrunStart") != -1 - def test_conftest_options(self): self.makepyfile(conftest=""" print "importing conftest" Modified: py/branch/pytestplugin/py/test/event.py ============================================================================== --- py/branch/pytestplugin/py/test/event.py (original) +++ py/branch/pytestplugin/py/test/event.py Thu Jan 15 14:28:23 2009 @@ -151,3 +151,12 @@ self.host = host self.root = root + +# make all eventclasses available on BaseEvent so that +# consumers of events can easily filter by +# 'isinstance(event, event.Name)' checks + +for name, cls in vars().items(): + if hasattr(cls, '__bases__') and issubclass(cls, BaseEvent): + setattr(BaseEvent, name, cls) +# Modified: py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py Thu Jan 15 14:28:23 2009 @@ -1,14 +1,19 @@ """ pytest "resultlog" plugin for machine-readable logging of test results. - """ +plugin_name = "pytest_resultlog" +plugin_version = "0.1" +plugin_author = "Samuele Pedroni, Anders Hammarquist" + import py -from py.__.test import event -def register_plugin(config): +def pytest_initplugin(config): config.register_plugin(ResultLogPlugin()) class ResultLogPlugin: + __doc__ = __doc__ + resultlog = None + def pytest_addoptions(self, config): config.addoptions("resultlog options", config.Option('--resultlog', action="store", @@ -23,7 +28,7 @@ self.resultlog = ResultLog(logfile) def pytest_unconfigure(self, config): - if hasattr(self, 'resultlog'): + if self.resultlog: self.resultlog.logfile.close() del self.resultlog @@ -70,11 +75,11 @@ self.write_log_entry(outcome.shortrepr, gpath, str(outcome.longrepr)) def log_event_to_file(self, ev): - if isinstance(ev, event.ItemTestReport): + if isinstance(ev, ev.ItemTestReport): self.log_outcome(ev) - elif isinstance(ev, event.CollectionReport): + elif isinstance(ev, ev.CollectionReport): if not ev.passed: self.log_outcome(ev) - elif isinstance(ev, event.InternalException): + elif isinstance(ev, ev.InternalException): path = ev.repr.reprcrash.path # fishing :( self.write_log_entry('!', path, str(ev.repr)) Modified: py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py Thu Jan 15 14:28:23 2009 @@ -62,7 +62,7 @@ myplugin.pytest_configure(config) assert myplugin.resultlog myplugin.pytest_unconfigure(config) - assert not hasattr(myplugin, 'resultlog') + assert not myplugin.resultlog class TestResultLog(object): def test_write_log_entry(self): Modified: py/branch/pytestplugin/py/test/session.py ============================================================================== --- py/branch/pytestplugin/py/test/session.py (original) +++ py/branch/pytestplugin/py/test/session.py Thu Jan 15 14:28:23 2009 @@ -26,14 +26,6 @@ self.config = config self.bus = EventBus() self._nomatch = False - eventlog = self.config.option.eventlog - if eventlog: - self.eventlog = py.path.local(eventlog) - f = self.eventlog.open("w") - def eventwrite(ev): - print >>f, ev - f.flush() - self.bus.subscribe(eventwrite) def fixoptions(self): """ check, fix and determine conflicting options. """ Modified: py/branch/pytestplugin/py/test/testing/test_config.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_config.py (original) +++ py/branch/pytestplugin/py/test/testing/test_config.py Thu Jan 15 14:28:23 2009 @@ -172,15 +172,6 @@ assert 42 == 43 """) - def test_session_eventlog(self): - eventlog = self.tmpdir.join("test_session_eventlog") - config = py.test.config._reparse([self.tmpdir, - '--eventlog=%s' % eventlog]) - session = config.initsession() - session.bus.notify(event.TestrunStart()) - s = eventlog.read() - assert s.find("TestrunStart") != -1 - def test_tracedir_tracer(self): tracedir = self.tmpdir.join("tracedir") config = py.test.config._reparse([self.tmpdir, Modified: py/branch/pytestplugin/py/test/testing/test_event.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_event.py (original) +++ py/branch/pytestplugin/py/test/testing/test_event.py Thu Jan 15 14:28:23 2009 @@ -35,6 +35,10 @@ bus.notify(2) assert l == [1] +def test_event_attributes(): + for name, value in vars(event).items(): + if py.std.inspect.isclass(value) and issubclass(value, event.BaseEvent): + assert hasattr(event.BaseEvent, value.__name__) class TestItemTestReport(suptest.InlineCollection): def test_toterminal(self): From hpk at codespeak.net Thu Jan 15 14:30:42 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 15 Jan 2009 14:30:42 +0100 (CET) Subject: [py-svn] r60994 - in py/branch/pytestplugin/py/test/plugin: . testing Message-ID: <20090115133042.494E71680AB@codespeak.net> Author: hpk Date: Thu Jan 15 14:30:41 2009 New Revision: 60994 Added: py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py (contents, props changed) py/branch/pytestplugin/py/test/plugin/testing/test_eventlog.py (contents, props changed) Log: forgot to checkin the eventlog plugin with 60993 Added: py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py ============================================================================== --- (empty file) +++ py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py Thu Jan 15 14:30:41 2009 @@ -0,0 +1,37 @@ +""" +pytest "eventlog" plugin for logging pytest events to a file. +""" +plugin_name = "pytest_eventlog" +plugin_version = "0.1" +plugin_author = "holger krekel" + +import py + +def pytest_initplugin(config): + config.register_plugin(EventLogPlugin()) + +class EventLogPlugin: + __doc__ = __doc__ + + def pytest_addoptions(self, config): + config.addoptions("eventlog options", + Option('', '--eventlog', + action="store", dest="eventlog", default=None, + help="write all pytest events to specific file."), + ) + + def pytest_configure(self, config): + eventlog = self.config.option.eventlog + if eventlog: + self.eventlogfile = py.path.local(eventlog).open("w") + + def pytest_unconfigure(self): + if hasattr(self, 'eventlogfile'): + self.eventlogfile.close() + del self.eventlogfile + + def pytest_event(self, event): + if hasattr(self, 'eventlogfile'): + f = self.eventlogfile + print >>f, event + f.flush() Added: py/branch/pytestplugin/py/test/plugin/testing/test_eventlog.py ============================================================================== --- (empty file) +++ py/branch/pytestplugin/py/test/plugin/testing/test_eventlog.py Thu Jan 15 14:30:41 2009 @@ -0,0 +1,18 @@ +import py +from py.__.test.testing import suptest +import pytest_eventlog + +def test_basic(): + py.test.skip("implement plugin regirstation and support for generic testing of plugins") + py.test.support.plugin_test_generic(pytest_eventlog) + +class TestEventlogPlugin(suptest.FileCreation): + def test_session_eventlog(self): + py.test.skip("requires plugin registration") + eventlog = self.tmpdir.join("test_session_eventlog") + config = py.test.config._reparse([self.tmpdir, + '--eventlog=%s' % eventlog]) + session = config.initsession() + session.bus.notify(event.TestrunStart()) + s = eventlog.read() + assert s.find("TestrunStart") != -1 From py-svn at codespeak.net Fri Jan 16 12:39:44 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Fri, 16 Jan 2009 12:39:44 +0100 (CET) Subject: [py-svn] Lick her till she comes Message-ID: <20090116113944.64612169E14@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Sun Jan 18 21:37:58 2009 From: py-svn at codespeak.net (Ad Infinitum) Date: Sun, 18 Jan 2009 21:37:58 +0100 (CET) Subject: [py-svn] She loves suckin my monster weapon now Message-ID: <20090118203758.2121B168514@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Mon Jan 19 00:37:08 2009 From: py-svn at codespeak.net (Acr Nally Communications Inc) Date: Mon, 19 Jan 2009 00:37:08 +0100 (CET) Subject: [py-svn] The best kept medical secret Message-ID: <20090118233708.C9E331684C3@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Mon Jan 19 13:47:28 2009 From: py-svn at codespeak.net (Access Incorporated) Date: Mon, 19 Jan 2009 13:47:28 +0100 (CET) Subject: [py-svn] Ride her like a cowboy Message-ID: <20090119124728.6EA23168071@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Tue Jan 20 10:41:36 2009 From: py-svn at codespeak.net (Cugliari) Date: Tue, 20 Jan 2009 10:41:36 +0100 (CET) Subject: [py-svn] Growth is assured with this Message-ID: <20090120094136.56937168403@codespeak.net> An HTML attachment was scrubbed... URL: From hpk at codespeak.net Tue Jan 20 12:34:44 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 20 Jan 2009 12:34:44 +0100 (CET) Subject: [py-svn] r61154 - in py/branch/pytestplugin/py/test: . testing Message-ID: <20090120113444.5C9A0168421@codespeak.net> Author: hpk Date: Tue Jan 20 12:34:42 2009 New Revision: 61154 Added: py/branch/pytestplugin/py/test/pmanage.py (contents, props changed) py/branch/pytestplugin/py/test/testing/test_pmanage.py (contents, props changed) Log: adding draft pluginmanager Added: py/branch/pytestplugin/py/test/pmanage.py ============================================================================== --- (empty file) +++ py/branch/pytestplugin/py/test/pmanage.py Tue Jan 20 12:34:42 2009 @@ -0,0 +1,53 @@ +""" +manage bootstrap, lifecycle and interaction with plugins +""" +import py + +class PluginManager(object): + def __init__(self): + self._plugins = {} + + def load(self, plugin): + for attrname in 'name', 'version': + assert hasattr(plugin, attrname) + name, version = plugin.name, plugin.version + if name in self._plugins: + raise ValueError("plugin with name %r aready loaded" % name) + self.trace("adding plugin %s-%s:" % (name, version)) + self._plugins[name] = plugin + + def _tryimport(self, modname): + try: + return __import__(modname, None, None, "__doc__") + except ImportError: + pass + + def trace(self, msg): + print >>py.std.sys.stderr, msg + + def loadbyname(self, pname): + pluginmod = self._tryimport(pname) + if pluginmod is None: + self.trace("could not import plugin: %s" %(pname)) + else: + for name, cls in vars(pluginmod).items(): + if name.endswith("Plugin"): + if getattr(cls, '__module__', None) == pluginmod.__name__: + plugininstance = cls() + self.load(plugininstance) + return plugininstance + + def getplugin(self, pname): + return self._plugins[pname] + + # + # API for calling methods of the plugins + # + def call_plugins(self, methname, **args): + for plugin in self._plugins.values(): + method = getattr(plugin, methname, None) + if method is not None: + method(**args) + + def forward_event(self, event): + self.call_plugins('pytest_event', event=event) Added: py/branch/pytestplugin/py/test/testing/test_pmanage.py ============================================================================== --- (empty file) +++ py/branch/pytestplugin/py/test/testing/test_pmanage.py Tue Jan 20 12:34:42 2009 @@ -0,0 +1,51 @@ +import sys +import py +from py.__.test.pmanage import PluginManager + +class TestPluginManager: + def setup_method(self, method): + self.tmpdir = py.test.ensuretemp("%s.%s.%s" % + (__name__, self.__class__.__name__, method.__name__)) + + def test_loadbyname(self): + pm = PluginManager() + assert pm.loadbyname("x.y.z.a.b.c") is None + + sys.path.insert(0, str(self.tmpdir)) + pluginname = "testinitplugin" + self.tmpdir.join(pluginname + ".py").write(py.code.Source(""" + class MyPlugin: + name = "myplugin" + version = "0.1" + """)) + plugininstance = pm.loadbyname(pluginname) + assert plugininstance.version == "0.1" + assert plugininstance.name == "myplugin" + py.test.raises(ValueError, "pm.loadbyname(pluginname)") + + def test_callplugins(self): + pm = PluginManager() + class MyPlugin: + name, version = "myplugin 0.1".split() + def method(self, arg): + pass + pm.load(MyPlugin()) + py.test.raises(ValueError, "pm.load(MyPlugin())") + py.test.raises(TypeError, 'pm.call_plugins("method")') + py.test.raises(TypeError, 'pm.call_plugins("method", 42)') + pm.call_plugins("method", arg=42) + py.test.raises(TypeError, 'pm.call_plugins("method", arg=42, s=13)') + + def test_getplugin(self): + pm = PluginManager() + assert py.test.raises(LookupError, "pm.getplugin('xxx')") + + class MyPlugin: + name, version = "myplugin 0.1".split() + def method(self, arg): + pass + pm.load(MyPlugin()) + myplugin1 = pm.getplugin("myplugin") + myplugin2 = pm.getplugin("myplugin") + assert myplugin1 is myplugin2 + From hpk at codespeak.net Tue Jan 20 15:01:55 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 20 Jan 2009 15:01:55 +0100 (CET) Subject: [py-svn] r61159 - in py/branch/pytestplugin/py: . doc test test/plugin test/plugin/testing test/testing Message-ID: <20090120140155.1741A1684BA@codespeak.net> Author: hpk Date: Tue Jan 20 15:01:54 2009 New Revision: 61159 Added: py/branch/pytestplugin/py/test/plugin/__init__.py (contents, props changed) Modified: py/branch/pytestplugin/py/conftest.py py/branch/pytestplugin/py/doc/impl-test.txt py/branch/pytestplugin/py/test/cmdline.py py/branch/pytestplugin/py/test/config.py py/branch/pytestplugin/py/test/defaultconftest.py py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py py/branch/pytestplugin/py/test/plugin/testing/test_eventlog.py py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py py/branch/pytestplugin/py/test/pmanage.py py/branch/pytestplugin/py/test/runner.py py/branch/pytestplugin/py/test/testing/test_config.py py/branch/pytestplugin/py/test/testing/test_pmanage.py Log: * registration/bootstrap mechanism for plugins * some initial documentation * a generic tester for plugins * beginning of integration into py.test runs Modified: py/branch/pytestplugin/py/conftest.py ============================================================================== --- py/branch/pytestplugin/py/conftest.py (original) +++ py/branch/pytestplugin/py/conftest.py Tue Jan 20 15:01:54 2009 @@ -19,7 +19,7 @@ import py Option = py.test.config.Option -option = py.test.config.addoptions("execnet options", +option = py.test.config.addoptions("py lib options", Option('-S', '', action="store", dest="sshtarget", default=None, help=("target to run tests requiring ssh, e.g. " Modified: py/branch/pytestplugin/py/doc/impl-test.txt ============================================================================== --- py/branch/pytestplugin/py/doc/impl-test.txt (original) +++ py/branch/pytestplugin/py/doc/impl-test.txt Tue Jan 20 15:01:54 2009 @@ -270,3 +270,54 @@ function with the given (usually empty set of) arguments. .. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev + + +pytest plugins +================== + +Loading and Configuring plugins +------------------------------------- + +py.test loads plugins at startup and during test collection. +You can instruct py.test to load plugins by placing a comma separated list +into a ``conftest.py``:: + + pytest_plugins_required = "pytest_plugin1", "pytest_plugin2" + +or by setting an environment variable:: + + PYTEST_PLUGINS_REQUIRED=pytest_plugin1,pytest_plugin2 + +pytest loads all plugin requirements from all conftest and environment settings +that it discovers. Note that there is no guaranteed order of initialization. + +Rules for writing plugins +------------------------------ + +A plugin module or package must be named ``pytest_name`` with +'name' all lowercase it must have a plugin class named +``Name`` in its root namespace. A Plugin class may implement +the following attributes and methods: + +* pytest_cmdlineoptions: a list of optparse-style py.test.config.Option objects +* pytest_configure(self, config): called after command line options have been parsed +* pytest_unconfigure(self, config): called before the test process quits +* pytest_event(self, event): called for each `pytest event`_ + +_`pytest event`: +Pytest Events +------------------- + +XXX Various reporting events. + +Example plugins +----------------------- + +XXX here are a few existing plugins: + +* adding new reporting facilities, e.g. + pytest_resultlog: log test results in machine-readable form to a file + pytest_eventlog: log all internal pytest events to a file + +* extending test execution, e.g. + pytest_apigen: tracing values of function/method calls when running tests Modified: py/branch/pytestplugin/py/test/cmdline.py ============================================================================== --- py/branch/pytestplugin/py/test/cmdline.py (original) +++ py/branch/pytestplugin/py/test/cmdline.py Tue Jan 20 15:01:54 2009 @@ -9,9 +9,11 @@ if args is None: args = py.std.sys.argv[1:] config = py.test.config - config.parse(args) + config.parse(args) + #config.pluginmanager.call_plugins("configure", config) session = config.initsession() exitstatus = session.main() + #config.pluginmanager.call_plugins("unconfigure", config) raise SystemExit(exitstatus) def warn_about_missing_assertion(): Modified: py/branch/pytestplugin/py/test/config.py ============================================================================== --- py/branch/pytestplugin/py/test/config.py (original) +++ py/branch/pytestplugin/py/test/config.py Tue Jan 20 15:01:54 2009 @@ -3,6 +3,7 @@ import py from conftesthandle import Conftest from py.__.test.defaultconftest import adddefaultoptions +from py.__.test.pmanage import PluginManager optparse = py.compat.optparse @@ -34,6 +35,18 @@ self._parser = optparse.OptionParser( usage="usage: %prog [options] [query] [filenames of tests]") self._conftest = Conftest() + self.pluginmanager = PluginManager() + + def _bootstrapcmdlineconfig(self, args): + adddefaultoptions(self) + self._conftest.setinitial(args) + for mod in self._conftest.getconftestmodules(None): + self.pluginmanager.consider_module(mod) + + opts = [] + for name, options in self.pluginmanager.listattr("pytest_cmdlineoptions"): + opts.extend(options) + self.addoptions("options added by plugins", *opts) def parse(self, args): """ parse cmdline arguments into this config object. @@ -42,8 +55,7 @@ assert not self._initialized, ( "can only parse cmdline args at most once per Config object") self._initialized = True - adddefaultoptions(self) - self._conftest.setinitial(args) + self._bootstrapcmdlineconfig(args) args = [str(x) for x in args] cmdlineoption, args = self._parser.parse_args(args) self.option.__dict__.update(vars(cmdlineoption)) @@ -163,6 +175,7 @@ cls = self._getsessionclass() session = cls(self) session.fixoptions() + #session.bus.subscribe(self.pluginmanager.forward_event) session.reporter = self.initreporter(session.bus) return session Modified: py/branch/pytestplugin/py/test/defaultconftest.py ============================================================================== --- py/branch/pytestplugin/py/test/defaultconftest.py (original) +++ py/branch/pytestplugin/py/test/defaultconftest.py Tue Jan 20 15:01:54 2009 @@ -10,6 +10,11 @@ conf_iocapture = "fd" # overridable from conftest.py +pytest_plugins_required = [ + 'py.__.test.plugin.pytest_resultlog', + 'py.__.test.plugin.pytest_eventlog', +] + # =================================================== # Distributed testing specific options Added: py/branch/pytestplugin/py/test/plugin/__init__.py ============================================================================== --- (empty file) +++ py/branch/pytestplugin/py/test/plugin/__init__.py Tue Jan 20 15:01:54 2009 @@ -0,0 +1 @@ +# Modified: py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py Tue Jan 20 15:01:54 2009 @@ -1,31 +1,20 @@ """ -pytest "eventlog" plugin for logging pytest events to a file. + eventlog plugin for logging pytest events to a file. """ -plugin_name = "pytest_eventlog" -plugin_version = "0.1" -plugin_author = "holger krekel" - import py -def pytest_initplugin(config): - config.register_plugin(EventLogPlugin()) - -class EventLogPlugin: - __doc__ = __doc__ - - def pytest_addoptions(self, config): - config.addoptions("eventlog options", - Option('', '--eventlog', - action="store", dest="eventlog", default=None, - help="write all pytest events to specific file."), - ) - +class Eventlog: + """ eventlog plugin for logging pytest events to a file. """ + pytest_cmdlineoptions = [ + py.test.config.Option('--eventlog', action='store', dest="eventlog", + help="write all pytest events to a specific file") + ] def pytest_configure(self, config): eventlog = self.config.option.eventlog if eventlog: self.eventlogfile = py.path.local(eventlog).open("w") - def pytest_unconfigure(self): + def pytest_unconfigure(self, config): if hasattr(self, 'eventlogfile'): self.eventlogfile.close() del self.eventlogfile Modified: py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py Tue Jan 20 15:01:54 2009 @@ -1,26 +1,14 @@ """ -pytest "resultlog" plugin for machine-readable logging of test results. """ -plugin_name = "pytest_resultlog" -plugin_version = "0.1" -plugin_author = "Samuele Pedroni, Anders Hammarquist" - import py -def pytest_initplugin(config): - config.register_plugin(ResultLogPlugin()) - -class ResultLogPlugin: - __doc__ = __doc__ - resultlog = None - - def pytest_addoptions(self, config): - config.addoptions("resultlog options", - config.Option('--resultlog', action="store", - default=None, dest="resultlog", +class Resultlog: + """resultlog plugin for machine-readable logging of test results. + """ + pytest_cmdlineoptions = [ + py.test.config.Option('--resultlog', action="store", dest="resultlog", help="path for machine-readable result log") - ) - + ] def pytest_configure(self, config): resultlog = config.option.resultlog if resultlog: @@ -28,12 +16,12 @@ self.resultlog = ResultLog(logfile) def pytest_unconfigure(self, config): - if self.resultlog: + if hasattr(self, 'resultlog'): self.resultlog.logfile.close() del self.resultlog def pytest_event(self, event): - if self.resultlog: + if hasattr(self, 'resultlog'): self.resultlog.log_event_to_file(event) def generic_path(item): Modified: py/branch/pytestplugin/py/test/plugin/testing/test_eventlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/testing/test_eventlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/testing/test_eventlog.py Tue Jan 20 15:01:54 2009 @@ -1,10 +1,9 @@ import py from py.__.test.testing import suptest -import pytest_eventlog +from py.__.test.pmanage import plugintester -def test_basic(): - py.test.skip("implement plugin regirstation and support for generic testing of plugins") - py.test.support.plugin_test_generic(pytest_eventlog) +def test_generic(): + plugintester("py.__.test.plugin.pytest_eventlog") class TestEventlogPlugin(suptest.FileCreation): def test_session_eventlog(self): Modified: py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py Tue Jan 20 15:01:54 2009 @@ -2,7 +2,8 @@ import py -import pytest_resultlog as resultlog +import py.__.test.plugin.pytest_resultlog as resultlog +from py.__.test.pmanage import plugintester from py.__.test import event from py.__.test.session import Session @@ -49,20 +50,8 @@ return node return Item(names[-1], parent=node) -def test_registration(): - # this is a half-functional test, maybe should evolve to - # become a generic test that evolves to automatically test - # plugin registration - p = py.test.ensuretemp("registration").ensure("logfile") - myplugin = resultlog.ResultLogPlugin() - config = Config() - myplugin.pytest_addoptions(config) - assert not config.option.resultlog - config.parse(['--resultlog=%s' % p]) - myplugin.pytest_configure(config) - assert myplugin.resultlog - myplugin.pytest_unconfigure(config) - assert not myplugin.resultlog +def test_generic(): + plugintester("py.__.test.plugin.pytest_resultlog") class TestResultLog(object): def test_write_log_entry(self): Modified: py/branch/pytestplugin/py/test/pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/pmanage.py (original) +++ py/branch/pytestplugin/py/test/pmanage.py Tue Jan 20 15:01:54 2009 @@ -3,51 +3,86 @@ """ import py +pytest_plugins_required = "pytest_plugins_required" + class PluginManager(object): def __init__(self): self._plugins = {} - def load(self, plugin): - for attrname in 'name', 'version': - assert hasattr(plugin, attrname) - name, version = plugin.name, plugin.version + def addpluginclass(self, pluginclass): + name = pluginclass.__name__.lower() if name in self._plugins: raise ValueError("plugin with name %r aready loaded" % name) - self.trace("adding plugin %s-%s:" % (name, version)) - self._plugins[name] = plugin - - def _tryimport(self, modname): - try: - return __import__(modname, None, None, "__doc__") - except ImportError: - pass + self.trace("instantiating and adding: %s" %(pluginclass,)) + self._plugins[name] = pluginclass() def trace(self, msg): print >>py.std.sys.stderr, msg - def loadbyname(self, pname): - pluginmod = self._tryimport(pname) - if pluginmod is None: - self.trace("could not import plugin: %s" %(pname)) - else: - for name, cls in vars(pluginmod).items(): - if name.endswith("Plugin"): - if getattr(cls, '__module__', None) == pluginmod.__name__: - plugininstance = cls() - self.load(plugininstance) - return plugininstance + def import_plugin(self, importname): + prefix = "pytest_" + lastpart = importname.split(".")[-1] + if not lastpart.startswith(prefix): + raise ValueError("in importname %r: %r does not start with %r" %( + importname, lastpart, prefix)) + mod = __import__(importname, None, None, "__doc__") + clsname = lastpart[len(prefix):].capitalize() + pluginclass = getattr(mod, clsname) + self.addpluginclass(pluginclass) def getplugin(self, pname): - return self._plugins[pname] + return self._plugins[pname.lower()] + def consider_module(self, mod): + for spec in getattr(mod, pytest_plugins_required, ()): + if spec: + self.import_plugin(spec) # # API for calling methods of the plugins # - def call_plugins(self, methname, **args): + def callplugins(self, methname, **args): for plugin in self._plugins.values(): method = getattr(plugin, methname, None) if method is not None: method(**args) - def forward_event(self, event): - self.call_plugins('pytest_event', event=event) + def listattr(self, attrname): + l = [] + for name, plugin in self._plugins.items(): + try: + attrvalue = getattr(plugin, attrname) + l.append((name, attrvalue)) + except AttributeError: + continue + return l + +def plugintester_nocalls(impname): + pname = impname.split("_")[-1] + pm = PluginManager() + pm.import_plugin(impname) + plugin = pm.getplugin(pname) + assert plugin + callhooklist = [ + ("pytest_configure", "config"), + ("pytest_unconfigure", "config"), + ("pytest_event", "event"), + ] + plugin_pytest_methods = dict([item for item in vars(plugin.__class__).items() + if item[0].startswith("pytest_")]) + print plugin_pytest_methods + for name, argnames in callhooklist: + if name in plugin_pytest_methods: + func = plugin_pytest_methods.pop(name) + for argname in argnames.split(","): + assert argname in func.func_code.co_varnames + try: + value = plugin_pytest_methods.pop("pytest_cmdlineoptions") + except KeyError: + pass + else: + assert isinstance(value, list) + assert not plugin_pytest_methods + + +def plugintester(impname): + plugintester_nocalls(impname) Modified: py/branch/pytestplugin/py/test/runner.py ============================================================================== --- py/branch/pytestplugin/py/test/runner.py (original) +++ py/branch/pytestplugin/py/test/runner.py Tue Jan 20 15:01:54 2009 @@ -68,7 +68,10 @@ def teardown(self): self.colitem._setupstate.teardown_exact(self.colitem) def execute(self): + #self.colitem.config.pluginmanager.pre_execute(self.colitem) self.colitem.runtest() + #self.colitem.config.pluginmanager.post_execute(self.colitem) + def makereport(self, res, when, excinfo, outerr): if excinfo: kw = self.getkw(when, excinfo, outerr) Modified: py/branch/pytestplugin/py/test/testing/test_config.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_config.py (original) +++ py/branch/pytestplugin/py/test/testing/test_config.py Tue Jan 20 15:01:54 2009 @@ -426,7 +426,6 @@ for col in col.listchain(): assert col._config is config - def test_config_picklability(self): import cPickle config = py.test.config._reparse([self.tmpdir]) Modified: py/branch/pytestplugin/py/test/testing/test_pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_pmanage.py (original) +++ py/branch/pytestplugin/py/test/testing/test_pmanage.py Tue Jan 20 15:01:54 2009 @@ -7,45 +7,69 @@ self.tmpdir = py.test.ensuretemp("%s.%s.%s" % (__name__, self.__class__.__name__, method.__name__)) - def test_loadbyname(self): + def test_import_plugin(self): pm = PluginManager() - assert pm.loadbyname("x.y.z.a.b.c") is None + py.test.raises(ValueError, 'pm.import_plugin("x.y")') + py.test.raises(ValueError, 'pm.import_plugin("pytest_x.y")') sys.path.insert(0, str(self.tmpdir)) - pluginname = "testinitplugin" - self.tmpdir.join(pluginname + ".py").write(py.code.Source(""" - class MyPlugin: - name = "myplugin" - version = "0.1" - """)) - plugininstance = pm.loadbyname(pluginname) - assert plugininstance.version == "0.1" - assert plugininstance.name == "myplugin" - py.test.raises(ValueError, "pm.loadbyname(pluginname)") + try: + pluginname = "pytest_hello" + self.tmpdir.join(pluginname + ".py").write(py.code.Source(""" + class Hello: + pass + """)) + pm.import_plugin(pluginname) + plugin = pm.getplugin("hello") + finally: + sys.path.remove(str(self.tmpdir)) - def test_callplugins(self): + def test_consider_module(self): pm = PluginManager() - class MyPlugin: - name, version = "myplugin 0.1".split() - def method(self, arg): - pass - pm.load(MyPlugin()) - py.test.raises(ValueError, "pm.load(MyPlugin())") - py.test.raises(TypeError, 'pm.call_plugins("method")') - py.test.raises(TypeError, 'pm.call_plugins("method", 42)') - pm.call_plugins("method", arg=42) - py.test.raises(TypeError, 'pm.call_plugins("method", arg=42, s=13)') + sys.path.insert(0, str(self.tmpdir)) + try: + self.tmpdir.join("pytest_plug1.py").write("class Plug1: pass") + self.tmpdir.join("pytest_plug2.py").write("class Plug2: pass") + mod = py.std.new.module("temp") + mod.pytest_plugins_required = ["pytest_plug1", "pytest_plug2"] + pm.consider_module(mod) + assert pm.getplugin("plug1").__class__.__name__ == "Plug1" + assert pm.getplugin("plug2").__class__.__name__ == "Plug2" + finally: + sys.path.remove(str(self.tmpdir)) + + def test_addpluginclass(self): + pm = PluginManager() + class My: + pass + pm.addpluginclass(My) + py.test.raises(ValueError, "pm.addpluginclass(My)") # double registration def test_getplugin(self): pm = PluginManager() - assert py.test.raises(LookupError, "pm.getplugin('xxx')") + assert py.test.raises(LookupError, "pm.getplugin('_xxx')") - class MyPlugin: - name, version = "myplugin 0.1".split() + class PluGin: pass + pm.addpluginclass(PluGin) + myplugin1 = pm.getplugin("plugin") + myplugin2 = pm.getplugin("Plugin") + assert myplugin1 is myplugin2 + + def test_callplugins(self): + pm = PluginManager() + class My: def method(self, arg): pass - pm.load(MyPlugin()) - myplugin1 = pm.getplugin("myplugin") - myplugin2 = pm.getplugin("myplugin") - assert myplugin1 is myplugin2 + pm.addpluginclass(My) + py.test.raises(TypeError, 'pm.callplugins("method")') + py.test.raises(TypeError, 'pm.callplugins("method", 42)') + pm.callplugins("method", arg=42) + py.test.raises(TypeError, 'pm.callplugins("method", arg=42, s=13)') + def test_iterplugins(self): + pm = PluginManager() + class My2: + x = 42 + pm.addpluginclass(My2) + assert not pm.listattr("hello") + assert pm.listattr("x") == [('my2', 42)] From hpk at codespeak.net Tue Jan 20 15:44:53 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 20 Jan 2009 15:44:53 +0100 (CET) Subject: [py-svn] r61161 - py/branch/pytestplugin/py/test Message-ID: <20090120144453.578AA1684A6@codespeak.net> Author: hpk Date: Tue Jan 20 15:44:52 2009 New Revision: 61161 Modified: py/branch/pytestplugin/py/test/config.py py/branch/pytestplugin/py/test/pmanage.py Log: cosmetics Modified: py/branch/pytestplugin/py/test/config.py ============================================================================== --- py/branch/pytestplugin/py/test/config.py (original) +++ py/branch/pytestplugin/py/test/config.py Tue Jan 20 15:44:52 2009 @@ -42,11 +42,11 @@ self._conftest.setinitial(args) for mod in self._conftest.getconftestmodules(None): self.pluginmanager.consider_module(mod) - + # XXX think about sorting/grouping of options from user-perspective opts = [] for name, options in self.pluginmanager.listattr("pytest_cmdlineoptions"): opts.extend(options) - self.addoptions("options added by plugins", *opts) + self.addoptions("ungrouped options added by plugins", *opts) def parse(self, args): """ parse cmdline arguments into this config object. Modified: py/branch/pytestplugin/py/test/pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/pmanage.py (original) +++ py/branch/pytestplugin/py/test/pmanage.py Tue Jan 20 15:44:52 2009 @@ -56,6 +56,9 @@ continue return l + +# generic test methods + def plugintester_nocalls(impname): pname = impname.split("_")[-1] pm = PluginManager() From hpk at codespeak.net Wed Jan 21 14:42:51 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 21 Jan 2009 14:42:51 +0100 (CET) Subject: [py-svn] r61184 - in py/branch/pytestplugin/py/test: . plugin plugin/testing testing Message-ID: <20090121134251.05662168438@codespeak.net> Author: hpk Date: Wed Jan 21 14:42:49 2009 New Revision: 61184 Modified: py/branch/pytestplugin/py/test/config.py py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py py/branch/pytestplugin/py/test/plugin/testing/test_eventlog.py py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py py/branch/pytestplugin/py/test/pmanage.py py/branch/pytestplugin/py/test/testing/test_pmanage.py Log: * plugins now receive events * extend generic testing of plugins, remove skipped plugin related tests Modified: py/branch/pytestplugin/py/test/config.py ============================================================================== --- py/branch/pytestplugin/py/test/config.py (original) +++ py/branch/pytestplugin/py/test/config.py Wed Jan 21 14:42:49 2009 @@ -175,7 +175,7 @@ cls = self._getsessionclass() session = cls(self) session.fixoptions() - #session.bus.subscribe(self.pluginmanager.forward_event) + session.bus.subscribe(self.pluginmanager.forward_event) session.reporter = self.initreporter(session.bus) return session Modified: py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py Wed Jan 21 14:42:49 2009 @@ -10,7 +10,7 @@ help="write all pytest events to a specific file") ] def pytest_configure(self, config): - eventlog = self.config.option.eventlog + eventlog = config.option.eventlog if eventlog: self.eventlogfile = py.path.local(eventlog).open("w") Modified: py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py Wed Jan 21 14:42:49 2009 @@ -1,12 +1,10 @@ -""" -""" import py class Resultlog: """resultlog plugin for machine-readable logging of test results. """ pytest_cmdlineoptions = [ - py.test.config.Option('--resultlog', action="store", dest="resultlog", + py.test.config.Option('--resultlog', action="store", dest="resultlog", default=None, help="path for machine-readable result log") ] def pytest_configure(self, config): Modified: py/branch/pytestplugin/py/test/plugin/testing/test_eventlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/testing/test_eventlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/testing/test_eventlog.py Wed Jan 21 14:42:49 2009 @@ -1,17 +1,12 @@ import py -from py.__.test.testing import suptest -from py.__.test.pmanage import plugintester +from py.__.test.testing import plugintester def test_generic(): - plugintester("py.__.test.plugin.pytest_eventlog") - -class TestEventlogPlugin(suptest.FileCreation): - def test_session_eventlog(self): - py.test.skip("requires plugin registration") - eventlog = self.tmpdir.join("test_session_eventlog") - config = py.test.config._reparse([self.tmpdir, - '--eventlog=%s' % eventlog]) - session = config.initsession() - session.bus.notify(event.TestrunStart()) - s = eventlog.read() - assert s.find("TestrunStart") != -1 + impname = "py.__.test.plugin.pytest_eventlog" + plugintester.nocalls(impname) + tmpdir = plugintester.functional( + "py.__.test.plugin.pytest_eventlog", '--eventlog=event.log') + s = tmpdir.join("event.log").read() + assert s.find("TestrunStart") != -1 + assert s.find("ItemTestReport") != -1 + assert s.find("TestrunFinish") != -1 Modified: py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py Wed Jan 21 14:42:49 2009 @@ -1,9 +1,18 @@ +""" + +test module for result log plugin + +XXX try to avoid low-level construction of items (see make_item) or move + functionality to some place where other tests use it as well. + + +""" import os, StringIO import py import py.__.test.plugin.pytest_resultlog as resultlog -from py.__.test.pmanage import plugintester +from py.__.test.testing import plugintester from py.__.test import event from py.__.test.session import Session @@ -51,7 +60,16 @@ return Item(names[-1], parent=node) def test_generic(): - plugintester("py.__.test.plugin.pytest_resultlog") + plugintester.nocalls("py.__.test.plugin.pytest_resultlog") + tmpdir = plugintester.functional("py.__.test.plugin.pytest_resultlog", + '--resultlog=resultlog') + resultlog = tmpdir.join("resultlog") + s = resultlog.readlines(cr=0) + suptest.assert_lines_contain_lines(s, [ + ". *:test_pass", + "F *:test_fail", + "s *:test_skip", + ]) class TestResultLog(object): def test_write_log_entry(self): @@ -181,24 +199,3 @@ assert entry_lines[-1][0] == ' ' assert 'ValueError' in entry -class TestSessionResultlog(suptest.FileCreation): - def test_session_resultlog(self): - py.test.skip("requires plugin registration mechanism") - 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) - - s = resultlog.read() - assert s.find(". a") != -1 - Modified: py/branch/pytestplugin/py/test/pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/pmanage.py (original) +++ py/branch/pytestplugin/py/test/pmanage.py Wed Jan 21 14:42:49 2009 @@ -5,6 +5,8 @@ pytest_plugins_required = "pytest_plugins_required" +DEBUG = False + class PluginManager(object): def __init__(self): self._plugins = {} @@ -17,7 +19,8 @@ self._plugins[name] = pluginclass() def trace(self, msg): - print >>py.std.sys.stderr, msg + if DEBUG: + print >>py.std.sys.stderr, msg def import_plugin(self, importname): prefix = "pytest_" @@ -37,14 +40,16 @@ for spec in getattr(mod, pytest_plugins_required, ()): if spec: self.import_plugin(spec) + # # API for calling methods of the plugins # def callplugins(self, methname, **args): - for plugin in self._plugins.values(): - method = getattr(plugin, methname, None) - if method is not None: - method(**args) + for name, method in self.listattr(methname): + method(**args) + + def forward_event(self, event): + self.callplugins("pytest_event", event=event) def listattr(self, attrname): l = [] @@ -56,36 +61,3 @@ continue return l - -# generic test methods - -def plugintester_nocalls(impname): - pname = impname.split("_")[-1] - pm = PluginManager() - pm.import_plugin(impname) - plugin = pm.getplugin(pname) - assert plugin - callhooklist = [ - ("pytest_configure", "config"), - ("pytest_unconfigure", "config"), - ("pytest_event", "event"), - ] - plugin_pytest_methods = dict([item for item in vars(plugin.__class__).items() - if item[0].startswith("pytest_")]) - print plugin_pytest_methods - for name, argnames in callhooklist: - if name in plugin_pytest_methods: - func = plugin_pytest_methods.pop(name) - for argname in argnames.split(","): - assert argname in func.func_code.co_varnames - try: - value = plugin_pytest_methods.pop("pytest_cmdlineoptions") - except KeyError: - pass - else: - assert isinstance(value, list) - assert not plugin_pytest_methods - - -def plugintester(impname): - plugintester_nocalls(impname) Modified: py/branch/pytestplugin/py/test/testing/test_pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_pmanage.py (original) +++ py/branch/pytestplugin/py/test/testing/test_pmanage.py Wed Jan 21 14:42:49 2009 @@ -1,6 +1,7 @@ import sys import py from py.__.test.pmanage import PluginManager +from py.__.test.event import NOP class TestPluginManager: def setup_method(self, method): @@ -66,10 +67,22 @@ pm.callplugins("method", arg=42) py.test.raises(TypeError, 'pm.callplugins("method", arg=42, s=13)') - def test_iterplugins(self): + def test_listattr(self): pm = PluginManager() class My2: x = 42 pm.addpluginclass(My2) assert not pm.listattr("hello") assert pm.listattr("x") == [('my2', 42)] + + def test_pytest_event(self): + pm = PluginManager() + l = [] + class My2: + def pytest_event(self, event): + l.append(event) + pm.addpluginclass(My2) + ev = NOP() + pm.forward_event(ev) + assert len(l) == 1 + assert l[0] is ev From hpk at codespeak.net Wed Jan 21 16:06:40 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 21 Jan 2009 16:06:40 +0100 (CET) Subject: [py-svn] r61196 - py/branch/pytestplugin/py/test/testing Message-ID: <20090121150640.8F695168447@codespeak.net> Author: hpk Date: Wed Jan 21 16:06:40 2009 New Revision: 61196 Added: py/branch/pytestplugin/py/test/testing/plugintester.py (contents, props changed) Log: forgot to checkin the plugintester with last checkin Added: py/branch/pytestplugin/py/test/testing/plugintester.py ============================================================================== --- (empty file) +++ py/branch/pytestplugin/py/test/testing/plugintester.py Wed Jan 21 16:06:40 2009 @@ -0,0 +1,71 @@ +import py +from py.__.test.pmanage import PluginManager + +# generic test methods + +def nocalls(impname): + pname = impname.split("_")[-1] + pm = PluginManager() + pm.import_plugin(impname) + plugin = pm.getplugin(pname) + assert plugin + callhooklist = [ + ("pytest_configure", "config"), + ("pytest_unconfigure", "config"), + ("pytest_event", "event"), + ] + plugin_pytest_methods = dict([item for item in vars(plugin.__class__).items() + if item[0].startswith("pytest_")]) + print plugin_pytest_methods + for name, argnames in callhooklist: + if name in plugin_pytest_methods: + func = plugin_pytest_methods.pop(name) + for argname in argnames.split(","): + assert argname in func.func_code.co_varnames + try: + value = plugin_pytest_methods.pop("pytest_cmdlineoptions") + except KeyError: + pass + else: + assert isinstance(value, list) + assert not plugin_pytest_methods + +def setupfs(tmpdir): + tmpdir.join("test_x.py").write(py.code.Source(""" + import py + def test_pass(): + pass + def test_fail(): + assert 0 + def test_skip(): + py.test.skip('hello') + """)) + +def functional(impname, *cmdlineopts): + from py.__.test.config import Config + tmpdir = py.test.ensuretemp("autotest_" + impname) + pname = impname.split("_")[-1] + olddir = tmpdir.chdir() + try: + config = Config() + setupfs(tmpdir) + config.parse([tmpdir] + list(cmdlineopts)) + try: + config.pluginmanager.import_plugin(pname) + except ValueError: + pass # already registered + plugin = config.pluginmanager.getplugin(pname) + assert plugin + if hasattr(plugin, 'pytest_configure'): + plugin.pytest_configure(config) + session = config.initsession() + exitstatus = session.main() + if hasattr(plugin, 'pytest_unconfigure'): + plugin.pytest_unconfigure(config) + return tmpdir + finally: + olddir.chdir() + +def plugintester(impname): + plugintester_nocalls(impname) + plugintester_standard_events(impname) From hpk at codespeak.net Wed Jan 21 17:42:51 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 21 Jan 2009 17:42:51 +0100 (CET) Subject: [py-svn] r61201 - in py/branch/pytestplugin/py/test: . dsession looponfail plugin plugin/testing report report/testing testing Message-ID: <20090121164251.83C34168459@codespeak.net> Author: hpk Date: Wed Jan 21 17:42:48 2009 New Revision: 61201 Added: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py (contents, props changed) - copied, changed from r61020, py/branch/pytestplugin/py/test/report/terminal.py py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py (contents, props changed) - copied, changed from r61020, py/branch/pytestplugin/py/test/report/testing/test_terminal.py Removed: py/branch/pytestplugin/py/test/report/base.py py/branch/pytestplugin/py/test/report/collectonly.py py/branch/pytestplugin/py/test/report/terminal.py py/branch/pytestplugin/py/test/report/testing/test_basereporter.py py/branch/pytestplugin/py/test/report/testing/test_collectonly.py py/branch/pytestplugin/py/test/report/testing/test_terminal.py Modified: py/branch/pytestplugin/py/test/cmdline.py py/branch/pytestplugin/py/test/config.py py/branch/pytestplugin/py/test/defaultconftest.py py/branch/pytestplugin/py/test/dsession/dsession.py py/branch/pytestplugin/py/test/looponfail/remote.py py/branch/pytestplugin/py/test/session.py py/branch/pytestplugin/py/test/testing/test_config.py Log: * move terminal reporters to the pytest_terminal.py plugin * move and change terminal reporter tests accordingly * somewhat of an intermediate checkin because the intiialization/finalization procedure for plugins is not satisfying Modified: py/branch/pytestplugin/py/test/cmdline.py ============================================================================== --- py/branch/pytestplugin/py/test/cmdline.py (original) +++ py/branch/pytestplugin/py/test/cmdline.py Wed Jan 21 17:42:48 2009 @@ -10,10 +10,8 @@ args = py.std.sys.argv[1:] config = py.test.config config.parse(args) - #config.pluginmanager.call_plugins("configure", config) session = config.initsession() exitstatus = session.main() - #config.pluginmanager.call_plugins("unconfigure", config) raise SystemExit(exitstatus) def warn_about_missing_assertion(): Modified: py/branch/pytestplugin/py/test/config.py ============================================================================== --- py/branch/pytestplugin/py/test/config.py (original) +++ py/branch/pytestplugin/py/test/config.py Wed Jan 21 17:42:48 2009 @@ -37,11 +37,14 @@ self._conftest = Conftest() self.pluginmanager = PluginManager() + def _initplugins(self): + for mod in self._conftest.getconftestmodules(None): + self.pluginmanager.consider_module(mod) + def _bootstrapcmdlineconfig(self, args): adddefaultoptions(self) self._conftest.setinitial(args) - for mod in self._conftest.getconftestmodules(None): - self.pluginmanager.consider_module(mod) + self._initplugins() # XXX think about sorting/grouping of options from user-perspective opts = [] for name, options in self.pluginmanager.listattr("pytest_cmdlineoptions"): @@ -78,6 +81,7 @@ self._initialized = True self.topdir = py.path.local(topdir) self._mergerepr(self._repr) + self._initplugins() del self._repr def _makerepr(self): @@ -162,21 +166,11 @@ except AttributeError: return self._conftest.rget(name, path) - def initreporter(self, bus): - if self.option.collectonly: - from py.__.test.report.collectonly import Reporter - else: - from py.__.test.report.terminal import Reporter - rep = Reporter(self, bus=bus) - return rep - def initsession(self): """ return an initialized session object. """ cls = self._getsessionclass() session = cls(self) session.fixoptions() - session.bus.subscribe(self.pluginmanager.forward_event) - session.reporter = self.initreporter(session.bus) return session def _getsessionclass(self): Modified: py/branch/pytestplugin/py/test/defaultconftest.py ============================================================================== --- py/branch/pytestplugin/py/test/defaultconftest.py (original) +++ py/branch/pytestplugin/py/test/defaultconftest.py Wed Jan 21 17:42:48 2009 @@ -11,6 +11,7 @@ conf_iocapture = "fd" # overridable from conftest.py pytest_plugins_required = [ + 'py.__.test.plugin.pytest_terminal', 'py.__.test.plugin.pytest_resultlog', 'py.__.test.plugin.pytest_eventlog', ] @@ -70,9 +71,6 @@ Option('', '--nomagic', action="store_true", dest="nomagic", default=False, help="refrain from using magic as much as possible."), - Option('', '--collectonly', - action="store_true", dest="collectonly", default=False, - help="only collect tests, don't execute them."), Option('', '--traceconfig', action="store_true", dest="traceconfig", default=False, help="trace considerations of conftest.py files."), Modified: py/branch/pytestplugin/py/test/dsession/dsession.py ============================================================================== --- py/branch/pytestplugin/py/test/dsession/dsession.py (original) +++ py/branch/pytestplugin/py/test/dsession/dsession.py Wed Jan 21 17:42:48 2009 @@ -79,11 +79,13 @@ def main(self, colitems=None): colitems = self.getinitialitems(colitems) - self.bus.notify(event.TestrunStart()) + #self.bus.notify(event.TestrunStart()) + self.sessionstarts() self.setup_hosts() exitstatus = self.loop(colitems) - self.bus.notify(event.TestrunFinish(exitstatus=exitstatus)) + #self.bus.notify(event.TestrunFinish(exitstatus=exitstatus)) self.teardown_hosts() + self.sessionfinishes() return exitstatus def loop_once(self, loopstate): Modified: py/branch/pytestplugin/py/test/looponfail/remote.py ============================================================================== --- py/branch/pytestplugin/py/test/looponfail/remote.py (original) +++ py/branch/pytestplugin/py/test/looponfail/remote.py Wed Jan 21 17:42:48 2009 @@ -13,7 +13,6 @@ from py.__.test.session import Session from py.__.test.outcome import Failed, Passed, Skipped from py.__.test.dsession.mypickle import PickleChannel -from py.__.test.report.terminal import TerminalReporter from py.__.test import event from py.__.test.looponfail import util @@ -83,8 +82,8 @@ from py.__.test.looponfail.remote import slave_runsession from py.__.test.dsession import masterslave config = masterslave.receive_and_send_pickled_config(channel) - width, hasmarkup = channel.receive() - slave_runsession(channel, config, width, hasmarkup) + fullwidth, hasmarkup = channel.receive() + slave_runsession(channel, config, fullwidth, hasmarkup) """, stdout=out, stderr=out) channel = PickleChannel(channel) masterslave.send_and_receive_pickled_config( @@ -117,7 +116,7 @@ finally: self.ensure_teardown() -def slave_runsession(channel, config, width, hasmarkup): +def slave_runsession(channel, config, fullwidth, hasmarkup): """ we run this on the other side. """ if config.option.debug: def DEBUG(*args): @@ -134,8 +133,10 @@ DEBUG("SLAVE: initsession()") session = config.initsession() - session.reporter._tw.hasmarkup = hasmarkup - session.reporter._tw.fullwidth = width + # XXX configure the reporter object's terminal writer more directly + # XXX and write a test for this remote-terminal setting logic + config.pytest_terminal_hasmarkup = hasmarkup + config.pytest_terminal_fullwidth = fullwidth if trails: colitems = [] for trail in trails: @@ -151,6 +152,7 @@ #def sendevent(ev): # channel.send(ev) #session.bus.subscribe(sendevent) + failreports = [] def recordfailures(ev): if isinstance(ev, event.BaseReport): Copied: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py (from r61020, py/branch/pytestplugin/py/test/report/terminal.py) ============================================================================== --- py/branch/pytestplugin/py/test/report/terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Wed Jan 21 17:42:48 2009 @@ -1,12 +1,91 @@ import py import sys from py.__.test import event -from py.__.test.report.base import BaseReporter -from py.__.test.report.base import getrelpath, repr_pythonversion +from py.__.test.collect import getrelpath + +class Terminal(object): + """ Terminal Reporter plugin to write information about test run to terminal. """ + pytest_cmdlineoptions = [ + py.test.config.Option('', '--collectonly', + action="store_true", dest="collectonly", + default=False, + help="only collect tests, don't execute them."), + ] + + def pytest_configure(self, config): + if config.option.collectonly: + self.reporter = CollectonlyReporter(config) + else: + self.reporter = TerminalReporter(config) + # XXX see remote.py's XXX + for attr in 'pytest_terminal_hasmarkup', 'pytest_terminal_fullwidth': + if hasattr(config, attr): + #print "SETTING TERMINAL OPTIONS", attr, getattr(config, attr) + name = attr.split("_")[-1] + assert hasattr(self.reporter._tw, name), name + setattr(self.reporter._tw, name, getattr(config, attr)) + + def pytest_event(self, event): + self.reporter.processevent(event) + +class BaseReporter(object): + def __init__(self): + self._reset() + + def _reset(self): + self._passed = [] + self._skipped = [] + self._failed = [] + self._deselected = [] + + def processevent(self, ev): + evname = ev.__class__.__name__ + repmethod = getattr(self, "rep_%s" % evname, None) + if repmethod is None: + self.rep(ev) + else: + repmethod(ev) + + def rep(self, ev): + pass + + def rep_ItemTestReport(self, ev): + if ev.skipped: + self._skipped.append(ev) + elif ev.failed: + self._failed.append(ev) + elif ev.passed: + self._passed.append(ev) + + def rep_CollectionReport(self, ev): + if ev.skipped: + self._skipped.append(ev) + elif ev.failed: + self._failed.append(ev) + else: + pass # don't record passed collections + + def rep_TestrunStart(self, ev): + self._reset() + + def rep_Deselected(self, ev): + self._deselected.extend(ev.items) + + def _folded_skips(self): + d = {} + for event in self._skipped: + longrepr = event.outcome.longrepr + key = longrepr.path, longrepr.lineno, longrepr.message + d.setdefault(key, []).append(event) + l = [] + for key, events in d.iteritems(): + l.append((len(events),) + key) + return l + class TerminalReporter(BaseReporter): - def __init__(self, config, file=None, bus=None): - super(TerminalReporter, self).__init__(bus=bus) + def __init__(self, config, file=None): + super(TerminalReporter, self).__init__() self.config = config self.curdir = py.path.local() if file is None: @@ -216,4 +295,50 @@ py.std.sys.executable, repr_pythonversion())) -Reporter = TerminalReporter + + +import py + +class CollectonlyReporter(BaseReporter): + INDENT = " " + + def __init__(self, config, out=None): + super(CollectonlyReporter, self).__init__() + self.config = config + if out is None: + out = py.std.sys.stdout + self.out = py.io.TerminalWriter(out) + self.indent = "" + self._failed = [] + + def outindent(self, line): + self.out.line(self.indent + str(line)) + + def rep_CollectionStart(self, ev): + self.outindent(ev.collector) + self.indent += self.INDENT + + def rep_ItemStart(self, event): + self.outindent(event.item) + + def rep_CollectionReport(self, ev): + super(CollectonlyReporter, self).rep_CollectionReport(ev) + if ev.failed: + self.outindent("!!! %s !!!" % ev.outcome.longrepr.reprcrash.message) + elif ev.skipped: + self.outindent("!!! %s !!!" % ev.outcome.longrepr.message) + self.indent = self.indent[:-len(self.INDENT)] + + def rep_TestrunFinish(self, session): + for ev in self._failed: + ev.toterminal(self.out) + +Reporter = CollectonlyReporter +def repr_pythonversion(v=None): + if v is None: + v = sys.version_info + try: + return "%s.%s.%s-%s-%s" % v + except (TypeError, ValueError): + return str(v) + Copied: py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py (from r61020, py/branch/pytestplugin/py/test/report/testing/test_terminal.py) ============================================================================== --- py/branch/pytestplugin/py/test/report/testing/test_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py Wed Jan 21 17:42:48 2009 @@ -1,24 +1,20 @@ import py import sys -from py.__.test.report.terminal import TerminalReporter +from py.__.test.plugin.pytest_terminal import TerminalReporter, repr_pythonversion +from py.__.test.plugin.pytest_terminal import CollectonlyReporter, getrelpath +from py.__.test.plugin.pytest_terminal import BaseReporter from py.__.test import event #from py.__.test.testing import suptest from py.__.test.runner import basic_run_report from py.__.test.testing.suptest import InlineCollection, popvalue from py.__.test.testing.suptest import assert_stringio_contains_lines from py.__.test.dsession.hostmanage import Host, makehostup -from py.__.test.report.base import repr_pythonversion class TestTerminal(InlineCollection): - def test_session_reporter_subscription(self): - config = py.test.config._reparse(['xxx']) - session = config.initsession() - session.sessionstarts() - rep = session.reporter - assert isinstance(rep, TerminalReporter) - assert rep.processevent in session.bus._subscribers - session.sessionfinishes() - #assert rep.processevent not in session.bus._subscribers + #def test_reporter_subscription_by_default(self): + # config = py.test.config._reparse(['xxx']) + # plugin = config.pluginmanager.getplugin("pytest_terminal") + # assert plugin def test_hostup(self): item = self.getitem("def test_func(): pass") @@ -95,7 +91,8 @@ import xyz """, withsession=True) stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, bus=self.session.bus, file=stringio) + rep = TerminalReporter(modcol._config, file=stringio) + self.session.bus.subscribe(rep.processevent) rep.processevent(event.TestrunStart()) l = list(self.session.genitems([modcol])) assert len(l) == 0 @@ -199,7 +196,7 @@ pass """, withsession=True) stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, bus=self.session.bus, file=stringio) + rep = TerminalReporter(modcol._config, file=stringio) l = list(self.session.genitems([modcol])) assert len(l) == 1 rep.processevent(event.ItemStart(l[0])) @@ -218,7 +215,7 @@ """, configargs=("--showskipsummary",) + ("-v",)*verbose, withsession=True) stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, bus=self.session.bus, file=stringio) + rep = TerminalReporter(modcol._config, file=stringio) rep.processevent(event.TestrunStart()) try: for item in self.session.genitems([modcol]): @@ -245,3 +242,132 @@ def test_verbose_keyboard_interrupt(self): self.test_keyboard_interrupt(verbose=True) + +class TestCollectonly(InlineCollection): + def test_collectonly_basic(self): + modcol = self.getmodulecol(configargs=['--collectonly'], source=""" + def test_func(): + pass + """) + stringio = py.std.cStringIO.StringIO() + rep = CollectonlyReporter(modcol._config, out=stringio) + indent = rep.indent + rep.processevent(event.CollectionStart(modcol)) + s = popvalue(stringio) + assert s == "" + + item = modcol.join("test_func") + rep.processevent(event.ItemStart(item)) + s = popvalue(stringio) + assert s.find("Function 'test_func'") != -1 + rep.processevent(event.CollectionReport(modcol, [], passed="")) + assert rep.indent == indent + + def test_collectonly_skipped_module(self): + modcol = self.getmodulecol(configargs=['--collectonly'], source=""" + import py + py.test.skip("nomod") + """, withsession=True) + stringio = py.std.cStringIO.StringIO() + rep = CollectonlyReporter(modcol._config, out=stringio) + self.session.bus.subscribe(rep.processevent) + cols = list(self.session.genitems([modcol])) + assert len(cols) == 0 + assert_stringio_contains_lines(stringio, """ + + !!! Skipped: 'nomod' !!! + """) + + def test_collectonly_failed_module(self): + modcol = self.getmodulecol(configargs=['--collectonly'], source=""" + raise ValueError(0) + """, withsession=True) + stringio = py.std.cStringIO.StringIO() + rep = CollectonlyReporter(modcol._config, out=stringio) + self.session.bus.subscribe(rep.processevent) + cols = list(self.session.genitems([modcol])) + assert len(cols) == 0 + assert_stringio_contains_lines(stringio, """ + + !!! ValueError: 0 !!! + """) + +class TestBaseReporter: + def test_dispatch_to_matching_method(self): + l = [] + class MyReporter(BaseReporter): + def rep_TestrunStart(self, ev): + l.append(ev) + rep = MyReporter() + ev = event.TestrunStart() + rep.processevent(ev) + assert len(l) == 1 + assert l[0] is ev + + def test_dispatch_to_default(self): + l = [] + class MyReporter(BaseReporter): + def rep(self, ev): + l.append(ev) + rep = MyReporter() + ev = event.NOP() + rep.processevent(ev) + assert len(l) == 1 + assert l[0] is ev + + def test_TestItemReport_one(self): + for outcome in 'passed skipped failed'.split(): + rep = BaseReporter() + ev = event.ItemTestReport(None, **{outcome:True}) + rep.processevent(ev) + assert getattr(rep, '_' + outcome) == [ev] + + def test_CollectionReport(self): + for outcome in 'skipped failed'.split(): + rep = BaseReporter() + ev = event.CollectionReport(None, None, **{outcome:True}) + rep.processevent(ev) + assert getattr(rep, '_' + outcome) == [ev] + + def test_skip_reasons(self): + from py.__.test.runner import OutcomeRepr + rep = BaseReporter() + class longrepr: + path = 'xyz' + lineno = 3 + message = "justso" + out1 = OutcomeRepr(None, None, longrepr) + out2 = OutcomeRepr(None, None, longrepr) + ev1 = event.CollectionReport(None, None, skipped=out1) + ev2 = event.ItemTestReport(None, skipped=out2) + rep.processevent(ev1) + rep.processevent(ev2) + assert len(rep._skipped) == 2 + l = rep._folded_skips() + assert len(l) == 1 + num, fspath, lineno, reason = l[0] + assert num == 2 + assert fspath == longrepr.path + assert lineno == longrepr.lineno + assert reason == longrepr.message + +def test_repr_python_version(): + py.magic.patch(sys, 'version_info', (2, 5, 1, 'final', 0)) + try: + assert repr_pythonversion() == "2.5.1-final-0" + py.std.sys.version_info = x = (2,3) + assert repr_pythonversion() == str(x) + finally: + py.magic.revert(sys, 'version_info') + +def test_getrelpath(): + curdir = py.path.local() + sep = curdir.sep + s = getrelpath(curdir, curdir.join("hello", "world")) + assert s == "hello" + sep + "world" + + s = getrelpath(curdir, curdir.dirpath().join("sister")) + assert s == ".." + sep + "sister" + assert getrelpath(curdir, curdir.dirpath()) == ".." + + assert getrelpath(curdir, "hello") == "hello" Deleted: /py/branch/pytestplugin/py/test/report/base.py ============================================================================== --- /py/branch/pytestplugin/py/test/report/base.py Wed Jan 21 17:42:48 2009 +++ (empty file) @@ -1,74 +0,0 @@ -from py.__.test import event -from py.__.test.collect import getrelpath - -import sys - -class BaseReporter(object): - def __init__(self, bus=None): - self._reset() - self._bus = bus - if bus: - self._bus.subscribe(self.processevent) - - def _reset(self): - self._passed = [] - self._skipped = [] - self._failed = [] - self._deselected = [] - - def deactivate(self): - if self._bus: - self._bus.unsubscribe(self.processevent) - - def processevent(self, ev): - evname = ev.__class__.__name__ - repmethod = getattr(self, "rep_%s" % evname, None) - if repmethod is None: - self.rep(ev) - else: - repmethod(ev) - - def rep(self, ev): - pass - - def rep_ItemTestReport(self, ev): - if ev.skipped: - self._skipped.append(ev) - elif ev.failed: - self._failed.append(ev) - elif ev.passed: - self._passed.append(ev) - - def rep_CollectionReport(self, ev): - if ev.skipped: - self._skipped.append(ev) - elif ev.failed: - self._failed.append(ev) - else: - pass # don't record passed collections - - def rep_TestrunStart(self, ev): - self._reset() - - def rep_Deselected(self, ev): - self._deselected.extend(ev.items) - - def _folded_skips(self): - d = {} - for event in self._skipped: - longrepr = event.outcome.longrepr - key = longrepr.path, longrepr.lineno, longrepr.message - d.setdefault(key, []).append(event) - l = [] - for key, events in d.iteritems(): - l.append((len(events),) + key) - return l - -def repr_pythonversion(v=None): - if v is None: - v = sys.version_info - try: - return "%s.%s.%s-%s-%s" % v - except (TypeError, ValueError): - return str(v) - Deleted: /py/branch/pytestplugin/py/test/report/collectonly.py ============================================================================== --- /py/branch/pytestplugin/py/test/report/collectonly.py Wed Jan 21 17:42:48 2009 +++ (empty file) @@ -1,43 +0,0 @@ - -""" --collectonly session, not to spread logic all over the place -""" -import py -from py.__.test.report.base import BaseReporter -from py.__.test.outcome import Skipped as Skipped2 -from py.__.test.outcome import Skipped - -class CollectonlyReporter(BaseReporter): - INDENT = " " - - def __init__(self, config, out=None, bus=None): - super(CollectonlyReporter, self).__init__(bus=bus) - self.config = config - if out is None: - out = py.std.sys.stdout - self.out = py.io.TerminalWriter(out) - self.indent = "" - self._failed = [] - - def outindent(self, line): - self.out.line(self.indent + str(line)) - - def rep_CollectionStart(self, ev): - self.outindent(ev.collector) - self.indent += self.INDENT - - def rep_ItemStart(self, event): - self.outindent(event.item) - - def rep_CollectionReport(self, ev): - super(CollectonlyReporter, self).rep_CollectionReport(ev) - if ev.failed: - self.outindent("!!! %s !!!" % ev.outcome.longrepr.reprcrash.message) - elif ev.skipped: - self.outindent("!!! %s !!!" % ev.outcome.longrepr.message) - self.indent = self.indent[:-len(self.INDENT)] - - def rep_TestrunFinish(self, session): - for ev in self._failed: - ev.toterminal(self.out) - -Reporter = CollectonlyReporter Deleted: /py/branch/pytestplugin/py/test/report/terminal.py ============================================================================== --- /py/branch/pytestplugin/py/test/report/terminal.py Wed Jan 21 17:42:48 2009 +++ (empty file) @@ -1,219 +0,0 @@ -import py -import sys -from py.__.test import event -from py.__.test.report.base import BaseReporter -from py.__.test.report.base import getrelpath, repr_pythonversion - -class TerminalReporter(BaseReporter): - def __init__(self, config, file=None, bus=None): - super(TerminalReporter, self).__init__(bus=bus) - self.config = config - self.curdir = py.path.local() - if file is None: - file = py.std.sys.stdout - self._tw = py.io.TerminalWriter(file) - - def _reset(self): - self.currentfspath = None - super(TerminalReporter, self)._reset() - - def write_fspath_result(self, fspath, res): - if fspath != self.currentfspath: - self._tw.line() - relpath = getrelpath(self.curdir, fspath) - self._tw.write(relpath + " ") - self.currentfspath = fspath - self._tw.write(res) - - def write_ensure_prefix(self, prefix, extra=""): - if self.currentfspath != prefix: - self._tw.line() - self.currentfspath = prefix - self._tw.write(prefix) - if extra: - self._tw.write(extra) - self.currentfspath = -2 - - def ensure_newline(self): - if self.currentfspath: - self._tw.line() - self.currentfspath = None - - def write_line(self, line, **markup): - line = str(line) - self.ensure_newline() - self._tw.line(line, **markup) - - def write_sep(self, sep, title=None, **markup): - 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) - - def rep_HostGatewayReady(self, ev): - if self.config.option.verbose: - self.write_line("HostGatewayReady: %s" %(ev.host,)) - - def rep_HostUp(self, ev): - d = ev.platinfo.copy() - d['hostid'] = ev.host.hostid - d['version'] = repr_pythonversion(d['sys.version_info']) - self.write_line("HOSTUP: %(hostid)s %(sys.platform)s " - "%(sys.executable)s - Python %(version)s" % - d) - - def rep_HostDown(self, ev): - host = ev.host - error = ev.error - if error: - self.write_line("HostDown %s: %s" %(host.hostid, error)) - - def rep_ItemStart(self, ev): - if self.config.option.verbose: - info = ev.item.repr_metainfo() - line = info.verboseline(basedir=self.curdir) + " " - extra = "" - 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: - self.write_sep("!", "RESCHEDULING %s " %(ev.items,)) - - def rep_ItemTestReport(self, ev): - super(TerminalReporter, self).rep_ItemTestReport(ev) - fspath = ev.colitem.fspath - if not self.config.option.verbose: - self.write_fspath_result(fspath, self.getoutcomeletter(ev)) - else: - info = ev.colitem.repr_metainfo() - line = info.verboseline(basedir=self.curdir) + " " - word = self.getoutcomeword(ev) - self.write_ensure_prefix(line, word) - - def rep_CollectionReport(self, ev): - super(TerminalReporter, self).rep_CollectionReport(ev) - fspath = ev.colitem.fspath - if ev.failed or ev.skipped: - msg = self.getcollectoutcome(ev) - self.write_fspath_result(fspath, "- " + msg) - - def rep_TestrunStart(self, ev): - super(TerminalReporter, self).rep_TestrunStart(ev) - self.write_sep("=", "test session starts", bold=True) - self._sessionstarttime = py.std.time.time() - #self.out_hostinfo() - - def rep_TestrunFinish(self, ev): - self._tw.line("") - 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() - self.summary_stats() - - def rep_LooponfailingInfo(self, ev): - if ev.failreports: - self.write_sep("#", "LOOPONFAILING", red=True) - for report in ev.failreports: - try: - loc = report.outcome.longrepr.reprcrash - except AttributeError: - loc = str(report.outcome.longrepr)[:50] - self.write_line(loc, red=True) - self.write_sep("#", "waiting for changes") - for rootdir in ev.rootdirs: - self.write_line("### Watching: %s" %(rootdir,), bold=True) - - if 0: - print "#" * 60 - print "# looponfailing: mode: %d failures args" % len(failures) - for ev in failurereports: - name = "/".join(ev.colitem.listnames()) # XXX - print "Failure at: %r" % (name,) - print "# watching py files below %s" % rootdir - print "# ", "^" * len(str(rootdir)) - failures = [ev.colitem for ev in failurereports] - if not failures: - failures = colitems - - # - # summaries for TestrunFinish - # - - def summary_failures(self): - if self._failed and self.config.option.tbstyle != "no": - self.write_sep("=", "FAILURES") - for ev in self._failed: - self.write_sep("_") - ev.toterminal(self._tw) - - def summary_stats(self): - session_duration = py.std.time.time() - self._sessionstarttime - numfailed = len(self._failed) - numskipped = len(self._skipped) - numpassed = len(self._passed) - sum = numfailed + numpassed - self.write_sep("=", "%d/%d passed + %d skips in %.2f seconds" % - (numpassed, sum, numskipped, session_duration), bold=True) - if numfailed == 0: - self.write_sep("=", "failures: no failures :)", green=True) - else: - self.write_sep("=", "failures: %d" %(numfailed), red=True) - - def summary_deselected(self): - if not self._deselected: - return - self.write_sep("=", "%d tests deselected by %r" %( - len(self._deselected), self.config.option.keyword), bold=True) - - - def summary_skips(self): - if not self._failed or self.config.option.showskipsummary: - folded_skips = self._folded_skips() - if folded_skips: - self.write_sep("_", "skipped test summary") - 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, - py.std.sys.executable, - repr_pythonversion())) - -Reporter = TerminalReporter Deleted: /py/branch/pytestplugin/py/test/report/testing/test_basereporter.py ============================================================================== --- /py/branch/pytestplugin/py/test/report/testing/test_basereporter.py Wed Jan 21 17:42:48 2009 +++ (empty file) @@ -1,94 +0,0 @@ -import py -from py.__.test.report.base import BaseReporter -from py.__.test.event import EventBus -from py.__.test import event -from py.__.test.runner import OutcomeRepr -from py.__.test.report.base import getrelpath, repr_pythonversion -import sys - -class TestBaseReporter: - def test_activate(self): - bus = EventBus() - rep = BaseReporter(bus=bus) - assert bus._subscribers - assert rep.processevent in bus._subscribers - rep.deactivate() - assert not bus._subscribers - - def test_dispatch_to_matching_method(self): - l = [] - class MyReporter(BaseReporter): - def rep_TestrunStart(self, ev): - l.append(ev) - rep = MyReporter() - ev = event.TestrunStart() - rep.processevent(ev) - assert len(l) == 1 - assert l[0] is ev - - def test_dispatch_to_default(self): - l = [] - class MyReporter(BaseReporter): - def rep(self, ev): - l.append(ev) - rep = MyReporter() - ev = event.NOP() - rep.processevent(ev) - assert len(l) == 1 - assert l[0] is ev - - def test_TestItemReport_one(self): - for outcome in 'passed skipped failed'.split(): - rep = BaseReporter() - ev = event.ItemTestReport(None, **{outcome:True}) - rep.processevent(ev) - assert getattr(rep, '_' + outcome) == [ev] - - def test_CollectionReport(self): - for outcome in 'skipped failed'.split(): - rep = BaseReporter() - ev = event.CollectionReport(None, None, **{outcome:True}) - rep.processevent(ev) - assert getattr(rep, '_' + outcome) == [ev] - - def test_skip_reasons(self): - rep = BaseReporter() - class longrepr: - path = 'xyz' - lineno = 3 - message = "justso" - out1 = OutcomeRepr(None, None, longrepr) - out2 = OutcomeRepr(None, None, longrepr) - ev1 = event.CollectionReport(None, None, skipped=out1) - ev2 = event.ItemTestReport(None, skipped=out2) - rep.processevent(ev1) - rep.processevent(ev2) - assert len(rep._skipped) == 2 - l = rep._folded_skips() - assert len(l) == 1 - num, fspath, lineno, reason = l[0] - assert num == 2 - assert fspath == longrepr.path - assert lineno == longrepr.lineno - assert reason == longrepr.message - -def test_repr_python_version(): - py.magic.patch(sys, 'version_info', (2, 5, 1, 'final', 0)) - try: - assert repr_pythonversion() == "2.5.1-final-0" - py.std.sys.version_info = x = (2,3) - assert repr_pythonversion() == str(x) - finally: - py.magic.revert(sys, 'version_info') - -def test_getrelpath(): - curdir = py.path.local() - sep = curdir.sep - s = getrelpath(curdir, curdir.join("hello", "world")) - assert s == "hello" + sep + "world" - - s = getrelpath(curdir, curdir.dirpath().join("sister")) - assert s == ".." + sep + "sister" - assert getrelpath(curdir, curdir.dirpath()) == ".." - - assert getrelpath(curdir, "hello") == "hello" Deleted: /py/branch/pytestplugin/py/test/report/testing/test_collectonly.py ============================================================================== --- /py/branch/pytestplugin/py/test/report/testing/test_collectonly.py Wed Jan 21 17:42:48 2009 +++ (empty file) @@ -1,53 +0,0 @@ -import py -from py.__.test.report.collectonly import CollectonlyReporter -from py.__.test import event -from py.__.test.testing.suptest import InlineCollection, popvalue -from py.__.test.testing.suptest import assert_stringio_contains_lines - -class TestCollectonly(InlineCollection): - def test_collectonly_basic(self): - modcol = self.getmodulecol(configargs=['--collectonly'], source=""" - def test_func(): - pass - """) - stringio = py.std.cStringIO.StringIO() - rep = CollectonlyReporter(modcol._config, out=stringio) - indent = rep.indent - rep.processevent(event.CollectionStart(modcol)) - s = popvalue(stringio) - assert s == "" - - item = modcol.join("test_func") - rep.processevent(event.ItemStart(item)) - s = popvalue(stringio) - assert s.find("Function 'test_func'") != -1 - rep.processevent(event.CollectionReport(modcol, [], passed="")) - assert rep.indent == indent - - def test_collectonly_skipped_module(self): - modcol = self.getmodulecol(configargs=['--collectonly'], source=""" - import py - py.test.skip("nomod") - """, withsession=True) - stringio = py.std.cStringIO.StringIO() - rep = CollectonlyReporter(modcol._config, bus=self.session.bus, out=stringio) - cols = list(self.session.genitems([modcol])) - assert len(cols) == 0 - assert_stringio_contains_lines(stringio, """ - - !!! Skipped: 'nomod' !!! - """) - - def test_collectonly_failed_module(self): - modcol = self.getmodulecol(configargs=['--collectonly'], source=""" - raise ValueError(0) - """, withsession=True) - stringio = py.std.cStringIO.StringIO() - rep = CollectonlyReporter(modcol._config, bus=self.session.bus, out=stringio) - cols = list(self.session.genitems([modcol])) - assert len(cols) == 0 - assert_stringio_contains_lines(stringio, """ - - !!! ValueError: 0 !!! - """) - Deleted: /py/branch/pytestplugin/py/test/report/testing/test_terminal.py ============================================================================== --- /py/branch/pytestplugin/py/test/report/testing/test_terminal.py Wed Jan 21 17:42:48 2009 +++ (empty file) @@ -1,247 +0,0 @@ -import py -import sys -from py.__.test.report.terminal import TerminalReporter -from py.__.test import event -#from py.__.test.testing import suptest -from py.__.test.runner import basic_run_report -from py.__.test.testing.suptest import InlineCollection, popvalue -from py.__.test.testing.suptest import assert_stringio_contains_lines -from py.__.test.dsession.hostmanage import Host, makehostup -from py.__.test.report.base import repr_pythonversion - -class TestTerminal(InlineCollection): - def test_session_reporter_subscription(self): - config = py.test.config._reparse(['xxx']) - session = config.initsession() - session.sessionstarts() - rep = session.reporter - assert isinstance(rep, TerminalReporter) - assert rep.processevent in session.bus._subscribers - session.sessionfinishes() - #assert rep.processevent not in session.bus._subscribers - - def test_hostup(self): - item = self.getitem("def test_func(): pass") - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(item._config, file=stringio) - rep.processevent(event.TestrunStart()) - host = Host("localhost") - rep.processevent(makehostup(host)) - s = popvalue(stringio) - expect = "%s %s %s - Python %s" %(host.hostid, sys.platform, - sys.executable, repr_pythonversion(sys.version_info)) - assert s.find(expect) != -1 - - def test_pass_skip_fail(self): - modcol = self.getmodulecol(""" - import py - def test_ok(): - pass - def test_skip(): - py.test.skip("xx") - def test_func(): - assert 0 - """, 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) - s = popvalue(stringio) - assert s.find("test_pass_skip_fail.py .sF") != -1 - rep.processevent(event.TestrunFinish()) - assert_stringio_contains_lines(stringio, [ - " def test_func():", - "> assert 0", - "E assert 0", - ]) - - def test_pass_skip_fail_verbose(self): - modcol = self.getmodulecol(""" - import py - def test_ok(): - pass - def test_skip(): - py.test.skip("xx") - def test_func(): - assert 0 - """, configargs=("-v",), withsession=True) - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) - rep.processevent(event.TestrunStart()) - items = modcol.collect() - for item in items: - rep.processevent(event.ItemStart(item)) - s = stringio.getvalue().strip() - assert s.endswith(item.name) - ev = basic_run_report(item) - rep.processevent(ev) - - assert_stringio_contains_lines(stringio, [ - "*test_pass_skip_fail_verbose.py:2: *test_ok*PASS", - "*test_pass_skip_fail_verbose.py:4: *test_skip*SKIP", - "*test_pass_skip_fail_verbose.py:6: *test_func*FAIL", - ]) - rep.processevent(event.TestrunFinish()) - assert_stringio_contains_lines(stringio, [ - " def test_func():", - "> assert 0", - "E assert 0", - ]) - - def test_collect_fail(self): - modcol = self.getmodulecol(""" - import xyz - """, withsession=True) - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, bus=self.session.bus, file=stringio) - rep.processevent(event.TestrunStart()) - l = list(self.session.genitems([modcol])) - assert len(l) == 0 - s = popvalue(stringio) - print s - assert s.find("test_collect_fail.py - ImportError: No module named") != -1 - rep.processevent(event.TestrunFinish()) - assert_stringio_contains_lines(stringio, [ - "> import xyz", - "E ImportError: No module named xyz" - ]) - - def test_internal_exception(self): - modcol = self.getmodulecol("def test_one(): pass") - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) - excinfo = py.test.raises(ValueError, "raise ValueError('hello')") - rep.processevent(event.InternalException(excinfo)) - s = popvalue(stringio) - assert s.find("InternalException:") != -1 - - def test_hostready_crash(self): - modcol = self.getmodulecol(""" - def test_one(): - pass - """, configargs=("-v",)) - stringio = py.std.cStringIO.StringIO() - host1 = Host("localhost") - rep = TerminalReporter(modcol._config, file=stringio) - rep.processevent(event.HostGatewayReady(host1, None)) - s = popvalue(stringio) - assert s.find("HostGatewayReady") != -1 - rep.processevent(event.HostDown(host1, "myerror")) - s = popvalue(stringio) - assert s.find("HostDown") != -1 - assert s.find("myerror") != -1 - - def test_writeline(self): - modcol = self.getmodulecol("def test_one(): pass") - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) - rep.write_fspath_result(py.path.local("xy.py"), '.') - rep.write_line("hello world") - lines = popvalue(stringio).split('\n') - assert not lines[0] - assert lines[1].endswith("xy.py .") - assert lines[2] == "hello world" - - def test_looponfailingreport(self): - modcol = self.getmodulecol(""" - def test_fail(): - assert 0 - def test_fail2(): - raise ValueError() - """) - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) - reports = [basic_run_report(x) for x in modcol.collect()] - rep.processevent(event.LooponfailingInfo(reports, [modcol._config.topdir])) - assert_stringio_contains_lines(stringio, [ - "*test_looponfailingreport.py:2: assert 0", - "*test_looponfailingreport.py:4: ValueError*", - "*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 - - 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 - - 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!') - 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()) - 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) - 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", - ]) - 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/branch/pytestplugin/py/test/session.py ============================================================================== --- py/branch/pytestplugin/py/test/session.py (original) +++ py/branch/pytestplugin/py/test/session.py Wed Jan 21 17:42:48 2009 @@ -85,7 +85,10 @@ def sessionstarts(self): """ setup any neccessary resources ahead of the test run. """ + self.config.pluginmanager.callplugins("pytest_configure", config=self.config) + self.bus.subscribe(self.config.pluginmanager.forward_event) self.bus.notify(event.TestrunStart()) + # XXX the following is not used or neccessary for the DSession subclass self._failurelist = [] self.bus.subscribe(self._processfailures) @@ -100,7 +103,11 @@ self.bus.notify(event.TestrunFinish(exitstatus=exitstatus, excinfo=excinfo)) self.bus.unsubscribe(self._processfailures) - #self.reporter.deactivate() + # XXX call plugin's unconfigure and write tests for + # that, conflicts with looponfailing/remote's sending + # of an event after the session has finished. + #self.bus.unsubscribe(self.config.pluginmanager.forward_event) + #self.config.pluginmanager.callplugins("pytest_unconfigure", config=self.config) return self._failurelist def getinitialitems(self, colitems): Modified: py/branch/pytestplugin/py/test/testing/test_config.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_config.py (original) +++ py/branch/pytestplugin/py/test/testing/test_config.py Wed Jan 21 17:42:48 2009 @@ -4,6 +4,7 @@ from py.__.test.config import gettopdir from py.__.test.testing import suptest from py.__.test import event +from py.__.test.config import Config def getcolitems(config): return [config.getfsnode(arg) for arg in config.args] From hpk at codespeak.net Wed Jan 21 17:44:22 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 21 Jan 2009 17:44:22 +0100 (CET) Subject: [py-svn] r61202 - in py/branch/pytestplugin/py/test: attic attic/testing report Message-ID: <20090121164422.78FE716806C@codespeak.net> Author: hpk Date: Wed Jan 21 17:44:21 2009 New Revision: 61202 Added: py/branch/pytestplugin/py/test/attic/ (props changed) - copied from r61020, py/branch/pytestplugin/py/test/report/ Removed: py/branch/pytestplugin/py/test/attic/base.py py/branch/pytestplugin/py/test/attic/collectonly.py py/branch/pytestplugin/py/test/attic/terminal.py py/branch/pytestplugin/py/test/attic/testing/test_basereporter.py py/branch/pytestplugin/py/test/attic/testing/test_collectonly.py py/branch/pytestplugin/py/test/attic/testing/test_terminal.py py/branch/pytestplugin/py/test/report/ Log: move away all the old reporters to the py.test "attic" Deleted: /py/branch/pytestplugin/py/test/report/base.py ============================================================================== --- /py/branch/pytestplugin/py/test/report/base.py Wed Jan 21 17:44:21 2009 +++ (empty file) @@ -1,74 +0,0 @@ -from py.__.test import event -from py.__.test.collect import getrelpath - -import sys - -class BaseReporter(object): - def __init__(self, bus=None): - self._reset() - self._bus = bus - if bus: - self._bus.subscribe(self.processevent) - - def _reset(self): - self._passed = [] - self._skipped = [] - self._failed = [] - self._deselected = [] - - def deactivate(self): - if self._bus: - self._bus.unsubscribe(self.processevent) - - def processevent(self, ev): - evname = ev.__class__.__name__ - repmethod = getattr(self, "rep_%s" % evname, None) - if repmethod is None: - self.rep(ev) - else: - repmethod(ev) - - def rep(self, ev): - pass - - def rep_ItemTestReport(self, ev): - if ev.skipped: - self._skipped.append(ev) - elif ev.failed: - self._failed.append(ev) - elif ev.passed: - self._passed.append(ev) - - def rep_CollectionReport(self, ev): - if ev.skipped: - self._skipped.append(ev) - elif ev.failed: - self._failed.append(ev) - else: - pass # don't record passed collections - - def rep_TestrunStart(self, ev): - self._reset() - - def rep_Deselected(self, ev): - self._deselected.extend(ev.items) - - def _folded_skips(self): - d = {} - for event in self._skipped: - longrepr = event.outcome.longrepr - key = longrepr.path, longrepr.lineno, longrepr.message - d.setdefault(key, []).append(event) - l = [] - for key, events in d.iteritems(): - l.append((len(events),) + key) - return l - -def repr_pythonversion(v=None): - if v is None: - v = sys.version_info - try: - return "%s.%s.%s-%s-%s" % v - except (TypeError, ValueError): - return str(v) - Deleted: /py/branch/pytestplugin/py/test/report/collectonly.py ============================================================================== --- /py/branch/pytestplugin/py/test/report/collectonly.py Wed Jan 21 17:44:21 2009 +++ (empty file) @@ -1,43 +0,0 @@ - -""" --collectonly session, not to spread logic all over the place -""" -import py -from py.__.test.report.base import BaseReporter -from py.__.test.outcome import Skipped as Skipped2 -from py.__.test.outcome import Skipped - -class CollectonlyReporter(BaseReporter): - INDENT = " " - - def __init__(self, config, out=None, bus=None): - super(CollectonlyReporter, self).__init__(bus=bus) - self.config = config - if out is None: - out = py.std.sys.stdout - self.out = py.io.TerminalWriter(out) - self.indent = "" - self._failed = [] - - def outindent(self, line): - self.out.line(self.indent + str(line)) - - def rep_CollectionStart(self, ev): - self.outindent(ev.collector) - self.indent += self.INDENT - - def rep_ItemStart(self, event): - self.outindent(event.item) - - def rep_CollectionReport(self, ev): - super(CollectonlyReporter, self).rep_CollectionReport(ev) - if ev.failed: - self.outindent("!!! %s !!!" % ev.outcome.longrepr.reprcrash.message) - elif ev.skipped: - self.outindent("!!! %s !!!" % ev.outcome.longrepr.message) - self.indent = self.indent[:-len(self.INDENT)] - - def rep_TestrunFinish(self, session): - for ev in self._failed: - ev.toterminal(self.out) - -Reporter = CollectonlyReporter Deleted: /py/branch/pytestplugin/py/test/report/terminal.py ============================================================================== --- /py/branch/pytestplugin/py/test/report/terminal.py Wed Jan 21 17:44:21 2009 +++ (empty file) @@ -1,219 +0,0 @@ -import py -import sys -from py.__.test import event -from py.__.test.report.base import BaseReporter -from py.__.test.report.base import getrelpath, repr_pythonversion - -class TerminalReporter(BaseReporter): - def __init__(self, config, file=None, bus=None): - super(TerminalReporter, self).__init__(bus=bus) - self.config = config - self.curdir = py.path.local() - if file is None: - file = py.std.sys.stdout - self._tw = py.io.TerminalWriter(file) - - def _reset(self): - self.currentfspath = None - super(TerminalReporter, self)._reset() - - def write_fspath_result(self, fspath, res): - if fspath != self.currentfspath: - self._tw.line() - relpath = getrelpath(self.curdir, fspath) - self._tw.write(relpath + " ") - self.currentfspath = fspath - self._tw.write(res) - - def write_ensure_prefix(self, prefix, extra=""): - if self.currentfspath != prefix: - self._tw.line() - self.currentfspath = prefix - self._tw.write(prefix) - if extra: - self._tw.write(extra) - self.currentfspath = -2 - - def ensure_newline(self): - if self.currentfspath: - self._tw.line() - self.currentfspath = None - - def write_line(self, line, **markup): - line = str(line) - self.ensure_newline() - self._tw.line(line, **markup) - - def write_sep(self, sep, title=None, **markup): - 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) - - def rep_HostGatewayReady(self, ev): - if self.config.option.verbose: - self.write_line("HostGatewayReady: %s" %(ev.host,)) - - def rep_HostUp(self, ev): - d = ev.platinfo.copy() - d['hostid'] = ev.host.hostid - d['version'] = repr_pythonversion(d['sys.version_info']) - self.write_line("HOSTUP: %(hostid)s %(sys.platform)s " - "%(sys.executable)s - Python %(version)s" % - d) - - def rep_HostDown(self, ev): - host = ev.host - error = ev.error - if error: - self.write_line("HostDown %s: %s" %(host.hostid, error)) - - def rep_ItemStart(self, ev): - if self.config.option.verbose: - info = ev.item.repr_metainfo() - line = info.verboseline(basedir=self.curdir) + " " - extra = "" - 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: - self.write_sep("!", "RESCHEDULING %s " %(ev.items,)) - - def rep_ItemTestReport(self, ev): - super(TerminalReporter, self).rep_ItemTestReport(ev) - fspath = ev.colitem.fspath - if not self.config.option.verbose: - self.write_fspath_result(fspath, self.getoutcomeletter(ev)) - else: - info = ev.colitem.repr_metainfo() - line = info.verboseline(basedir=self.curdir) + " " - word = self.getoutcomeword(ev) - self.write_ensure_prefix(line, word) - - def rep_CollectionReport(self, ev): - super(TerminalReporter, self).rep_CollectionReport(ev) - fspath = ev.colitem.fspath - if ev.failed or ev.skipped: - msg = self.getcollectoutcome(ev) - self.write_fspath_result(fspath, "- " + msg) - - def rep_TestrunStart(self, ev): - super(TerminalReporter, self).rep_TestrunStart(ev) - self.write_sep("=", "test session starts", bold=True) - self._sessionstarttime = py.std.time.time() - #self.out_hostinfo() - - def rep_TestrunFinish(self, ev): - self._tw.line("") - 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() - self.summary_stats() - - def rep_LooponfailingInfo(self, ev): - if ev.failreports: - self.write_sep("#", "LOOPONFAILING", red=True) - for report in ev.failreports: - try: - loc = report.outcome.longrepr.reprcrash - except AttributeError: - loc = str(report.outcome.longrepr)[:50] - self.write_line(loc, red=True) - self.write_sep("#", "waiting for changes") - for rootdir in ev.rootdirs: - self.write_line("### Watching: %s" %(rootdir,), bold=True) - - if 0: - print "#" * 60 - print "# looponfailing: mode: %d failures args" % len(failures) - for ev in failurereports: - name = "/".join(ev.colitem.listnames()) # XXX - print "Failure at: %r" % (name,) - print "# watching py files below %s" % rootdir - print "# ", "^" * len(str(rootdir)) - failures = [ev.colitem for ev in failurereports] - if not failures: - failures = colitems - - # - # summaries for TestrunFinish - # - - def summary_failures(self): - if self._failed and self.config.option.tbstyle != "no": - self.write_sep("=", "FAILURES") - for ev in self._failed: - self.write_sep("_") - ev.toterminal(self._tw) - - def summary_stats(self): - session_duration = py.std.time.time() - self._sessionstarttime - numfailed = len(self._failed) - numskipped = len(self._skipped) - numpassed = len(self._passed) - sum = numfailed + numpassed - self.write_sep("=", "%d/%d passed + %d skips in %.2f seconds" % - (numpassed, sum, numskipped, session_duration), bold=True) - if numfailed == 0: - self.write_sep("=", "failures: no failures :)", green=True) - else: - self.write_sep("=", "failures: %d" %(numfailed), red=True) - - def summary_deselected(self): - if not self._deselected: - return - self.write_sep("=", "%d tests deselected by %r" %( - len(self._deselected), self.config.option.keyword), bold=True) - - - def summary_skips(self): - if not self._failed or self.config.option.showskipsummary: - folded_skips = self._folded_skips() - if folded_skips: - self.write_sep("_", "skipped test summary") - 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, - py.std.sys.executable, - repr_pythonversion())) - -Reporter = TerminalReporter Deleted: /py/branch/pytestplugin/py/test/report/testing/test_basereporter.py ============================================================================== --- /py/branch/pytestplugin/py/test/report/testing/test_basereporter.py Wed Jan 21 17:44:21 2009 +++ (empty file) @@ -1,94 +0,0 @@ -import py -from py.__.test.report.base import BaseReporter -from py.__.test.event import EventBus -from py.__.test import event -from py.__.test.runner import OutcomeRepr -from py.__.test.report.base import getrelpath, repr_pythonversion -import sys - -class TestBaseReporter: - def test_activate(self): - bus = EventBus() - rep = BaseReporter(bus=bus) - assert bus._subscribers - assert rep.processevent in bus._subscribers - rep.deactivate() - assert not bus._subscribers - - def test_dispatch_to_matching_method(self): - l = [] - class MyReporter(BaseReporter): - def rep_TestrunStart(self, ev): - l.append(ev) - rep = MyReporter() - ev = event.TestrunStart() - rep.processevent(ev) - assert len(l) == 1 - assert l[0] is ev - - def test_dispatch_to_default(self): - l = [] - class MyReporter(BaseReporter): - def rep(self, ev): - l.append(ev) - rep = MyReporter() - ev = event.NOP() - rep.processevent(ev) - assert len(l) == 1 - assert l[0] is ev - - def test_TestItemReport_one(self): - for outcome in 'passed skipped failed'.split(): - rep = BaseReporter() - ev = event.ItemTestReport(None, **{outcome:True}) - rep.processevent(ev) - assert getattr(rep, '_' + outcome) == [ev] - - def test_CollectionReport(self): - for outcome in 'skipped failed'.split(): - rep = BaseReporter() - ev = event.CollectionReport(None, None, **{outcome:True}) - rep.processevent(ev) - assert getattr(rep, '_' + outcome) == [ev] - - def test_skip_reasons(self): - rep = BaseReporter() - class longrepr: - path = 'xyz' - lineno = 3 - message = "justso" - out1 = OutcomeRepr(None, None, longrepr) - out2 = OutcomeRepr(None, None, longrepr) - ev1 = event.CollectionReport(None, None, skipped=out1) - ev2 = event.ItemTestReport(None, skipped=out2) - rep.processevent(ev1) - rep.processevent(ev2) - assert len(rep._skipped) == 2 - l = rep._folded_skips() - assert len(l) == 1 - num, fspath, lineno, reason = l[0] - assert num == 2 - assert fspath == longrepr.path - assert lineno == longrepr.lineno - assert reason == longrepr.message - -def test_repr_python_version(): - py.magic.patch(sys, 'version_info', (2, 5, 1, 'final', 0)) - try: - assert repr_pythonversion() == "2.5.1-final-0" - py.std.sys.version_info = x = (2,3) - assert repr_pythonversion() == str(x) - finally: - py.magic.revert(sys, 'version_info') - -def test_getrelpath(): - curdir = py.path.local() - sep = curdir.sep - s = getrelpath(curdir, curdir.join("hello", "world")) - assert s == "hello" + sep + "world" - - s = getrelpath(curdir, curdir.dirpath().join("sister")) - assert s == ".." + sep + "sister" - assert getrelpath(curdir, curdir.dirpath()) == ".." - - assert getrelpath(curdir, "hello") == "hello" Deleted: /py/branch/pytestplugin/py/test/report/testing/test_collectonly.py ============================================================================== --- /py/branch/pytestplugin/py/test/report/testing/test_collectonly.py Wed Jan 21 17:44:21 2009 +++ (empty file) @@ -1,53 +0,0 @@ -import py -from py.__.test.report.collectonly import CollectonlyReporter -from py.__.test import event -from py.__.test.testing.suptest import InlineCollection, popvalue -from py.__.test.testing.suptest import assert_stringio_contains_lines - -class TestCollectonly(InlineCollection): - def test_collectonly_basic(self): - modcol = self.getmodulecol(configargs=['--collectonly'], source=""" - def test_func(): - pass - """) - stringio = py.std.cStringIO.StringIO() - rep = CollectonlyReporter(modcol._config, out=stringio) - indent = rep.indent - rep.processevent(event.CollectionStart(modcol)) - s = popvalue(stringio) - assert s == "" - - item = modcol.join("test_func") - rep.processevent(event.ItemStart(item)) - s = popvalue(stringio) - assert s.find("Function 'test_func'") != -1 - rep.processevent(event.CollectionReport(modcol, [], passed="")) - assert rep.indent == indent - - def test_collectonly_skipped_module(self): - modcol = self.getmodulecol(configargs=['--collectonly'], source=""" - import py - py.test.skip("nomod") - """, withsession=True) - stringio = py.std.cStringIO.StringIO() - rep = CollectonlyReporter(modcol._config, bus=self.session.bus, out=stringio) - cols = list(self.session.genitems([modcol])) - assert len(cols) == 0 - assert_stringio_contains_lines(stringio, """ - - !!! Skipped: 'nomod' !!! - """) - - def test_collectonly_failed_module(self): - modcol = self.getmodulecol(configargs=['--collectonly'], source=""" - raise ValueError(0) - """, withsession=True) - stringio = py.std.cStringIO.StringIO() - rep = CollectonlyReporter(modcol._config, bus=self.session.bus, out=stringio) - cols = list(self.session.genitems([modcol])) - assert len(cols) == 0 - assert_stringio_contains_lines(stringio, """ - - !!! ValueError: 0 !!! - """) - Deleted: /py/branch/pytestplugin/py/test/report/testing/test_terminal.py ============================================================================== --- /py/branch/pytestplugin/py/test/report/testing/test_terminal.py Wed Jan 21 17:44:21 2009 +++ (empty file) @@ -1,247 +0,0 @@ -import py -import sys -from py.__.test.report.terminal import TerminalReporter -from py.__.test import event -#from py.__.test.testing import suptest -from py.__.test.runner import basic_run_report -from py.__.test.testing.suptest import InlineCollection, popvalue -from py.__.test.testing.suptest import assert_stringio_contains_lines -from py.__.test.dsession.hostmanage import Host, makehostup -from py.__.test.report.base import repr_pythonversion - -class TestTerminal(InlineCollection): - def test_session_reporter_subscription(self): - config = py.test.config._reparse(['xxx']) - session = config.initsession() - session.sessionstarts() - rep = session.reporter - assert isinstance(rep, TerminalReporter) - assert rep.processevent in session.bus._subscribers - session.sessionfinishes() - #assert rep.processevent not in session.bus._subscribers - - def test_hostup(self): - item = self.getitem("def test_func(): pass") - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(item._config, file=stringio) - rep.processevent(event.TestrunStart()) - host = Host("localhost") - rep.processevent(makehostup(host)) - s = popvalue(stringio) - expect = "%s %s %s - Python %s" %(host.hostid, sys.platform, - sys.executable, repr_pythonversion(sys.version_info)) - assert s.find(expect) != -1 - - def test_pass_skip_fail(self): - modcol = self.getmodulecol(""" - import py - def test_ok(): - pass - def test_skip(): - py.test.skip("xx") - def test_func(): - assert 0 - """, 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) - s = popvalue(stringio) - assert s.find("test_pass_skip_fail.py .sF") != -1 - rep.processevent(event.TestrunFinish()) - assert_stringio_contains_lines(stringio, [ - " def test_func():", - "> assert 0", - "E assert 0", - ]) - - def test_pass_skip_fail_verbose(self): - modcol = self.getmodulecol(""" - import py - def test_ok(): - pass - def test_skip(): - py.test.skip("xx") - def test_func(): - assert 0 - """, configargs=("-v",), withsession=True) - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) - rep.processevent(event.TestrunStart()) - items = modcol.collect() - for item in items: - rep.processevent(event.ItemStart(item)) - s = stringio.getvalue().strip() - assert s.endswith(item.name) - ev = basic_run_report(item) - rep.processevent(ev) - - assert_stringio_contains_lines(stringio, [ - "*test_pass_skip_fail_verbose.py:2: *test_ok*PASS", - "*test_pass_skip_fail_verbose.py:4: *test_skip*SKIP", - "*test_pass_skip_fail_verbose.py:6: *test_func*FAIL", - ]) - rep.processevent(event.TestrunFinish()) - assert_stringio_contains_lines(stringio, [ - " def test_func():", - "> assert 0", - "E assert 0", - ]) - - def test_collect_fail(self): - modcol = self.getmodulecol(""" - import xyz - """, withsession=True) - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, bus=self.session.bus, file=stringio) - rep.processevent(event.TestrunStart()) - l = list(self.session.genitems([modcol])) - assert len(l) == 0 - s = popvalue(stringio) - print s - assert s.find("test_collect_fail.py - ImportError: No module named") != -1 - rep.processevent(event.TestrunFinish()) - assert_stringio_contains_lines(stringio, [ - "> import xyz", - "E ImportError: No module named xyz" - ]) - - def test_internal_exception(self): - modcol = self.getmodulecol("def test_one(): pass") - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) - excinfo = py.test.raises(ValueError, "raise ValueError('hello')") - rep.processevent(event.InternalException(excinfo)) - s = popvalue(stringio) - assert s.find("InternalException:") != -1 - - def test_hostready_crash(self): - modcol = self.getmodulecol(""" - def test_one(): - pass - """, configargs=("-v",)) - stringio = py.std.cStringIO.StringIO() - host1 = Host("localhost") - rep = TerminalReporter(modcol._config, file=stringio) - rep.processevent(event.HostGatewayReady(host1, None)) - s = popvalue(stringio) - assert s.find("HostGatewayReady") != -1 - rep.processevent(event.HostDown(host1, "myerror")) - s = popvalue(stringio) - assert s.find("HostDown") != -1 - assert s.find("myerror") != -1 - - def test_writeline(self): - modcol = self.getmodulecol("def test_one(): pass") - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) - rep.write_fspath_result(py.path.local("xy.py"), '.') - rep.write_line("hello world") - lines = popvalue(stringio).split('\n') - assert not lines[0] - assert lines[1].endswith("xy.py .") - assert lines[2] == "hello world" - - def test_looponfailingreport(self): - modcol = self.getmodulecol(""" - def test_fail(): - assert 0 - def test_fail2(): - raise ValueError() - """) - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) - reports = [basic_run_report(x) for x in modcol.collect()] - rep.processevent(event.LooponfailingInfo(reports, [modcol._config.topdir])) - assert_stringio_contains_lines(stringio, [ - "*test_looponfailingreport.py:2: assert 0", - "*test_looponfailingreport.py:4: ValueError*", - "*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 - - 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 - - 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!') - 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()) - 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) - 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", - ]) - 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) From hpk at codespeak.net Fri Jan 23 16:22:13 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 23 Jan 2009 16:22:13 +0100 (CET) Subject: [py-svn] r61265 - in py/branch/pytestplugin/py/test: . testing Message-ID: <20090123152213.AD19E168509@codespeak.net> Author: hpk Date: Fri Jan 23 16:22:10 2009 New Revision: 61265 Modified: py/branch/pytestplugin/py/test/config.py py/branch/pytestplugin/py/test/pmanage.py py/branch/pytestplugin/py/test/testing/test_pmanage.py Log: move plugin registration and initilaization into pluginmanager, with tests. Modified: py/branch/pytestplugin/py/test/config.py ============================================================================== --- py/branch/pytestplugin/py/test/config.py (original) +++ py/branch/pytestplugin/py/test/config.py Fri Jan 23 16:22:10 2009 @@ -37,20 +37,6 @@ self._conftest = Conftest() self.pluginmanager = PluginManager() - def _initplugins(self): - for mod in self._conftest.getconftestmodules(None): - self.pluginmanager.consider_module(mod) - - def _bootstrapcmdlineconfig(self, args): - adddefaultoptions(self) - self._conftest.setinitial(args) - self._initplugins() - # XXX think about sorting/grouping of options from user-perspective - opts = [] - for name, options in self.pluginmanager.listattr("pytest_cmdlineoptions"): - opts.extend(options) - self.addoptions("ungrouped options added by plugins", *opts) - def parse(self, args): """ parse cmdline arguments into this config object. Note that this can only be called once per testing process. @@ -58,7 +44,10 @@ assert not self._initialized, ( "can only parse cmdline args at most once per Config object") self._initialized = True - self._bootstrapcmdlineconfig(args) + adddefaultoptions(self) + self._conftest.setinitial(args) + self.pluginmanager.registerplugins(self._conftest.getconftestmodules(None)) + self.pluginmanager.add_cmdlineoptions(self) args = [str(x) for x in args] cmdlineoption, args = self._parser.parse_args(args) self.option.__dict__.update(vars(cmdlineoption)) @@ -81,7 +70,7 @@ self._initialized = True self.topdir = py.path.local(topdir) self._mergerepr(self._repr) - self._initplugins() + self.pluginmanager.registerplugins(self._conftest.getconftestmodules(None)) del self._repr def _makerepr(self): Modified: py/branch/pytestplugin/py/test/pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/pmanage.py (original) +++ py/branch/pytestplugin/py/test/pmanage.py Fri Jan 23 16:22:10 2009 @@ -41,8 +41,19 @@ if spec: self.import_plugin(spec) + def registerplugins(self, conftestmodules): + for mod in conftestmodules: + self.consider_module(mod) + + def add_cmdlineoptions(self, config): + # XXX think about sorting/grouping of options from user-perspective + opts = [] + for name, options in self.listattr("pytest_cmdlineoptions"): + opts.extend(options) + config.addoptions("ungrouped options added by plugins", *opts) + # - # API for calling methods of the plugins + # API for calling methods of registered plugins # def callplugins(self, methname, **args): for name, method in self.listattr(methname): Modified: py/branch/pytestplugin/py/test/testing/test_pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_pmanage.py (original) +++ py/branch/pytestplugin/py/test/testing/test_pmanage.py Fri Jan 23 16:22:10 2009 @@ -39,6 +39,18 @@ finally: sys.path.remove(str(self.tmpdir)) + def test_register_plugins(self): + pm = PluginManager() + sys.path.insert(0, str(self.tmpdir)) + try: + self.tmpdir.join("pytest_rplug.py").write("class Rplug: pass") + mod = py.std.new.module("temp") + mod.pytest_plugins_required = ["pytest_rplug"] + pm.registerplugins([mod]) + assert pm.getplugin("rplug").__class__.__name__ == "Rplug" + finally: + sys.path.remove(str(self.tmpdir)) + def test_addpluginclass(self): pm = PluginManager() class My: @@ -86,3 +98,17 @@ pm.forward_event(ev) assert len(l) == 1 assert l[0] is ev + + def test_addcmdlineoptions(self): + from py.__.test.config import Config + pm = PluginManager() + class My: + pytest_cmdlineoptions = [ + py.test.config.Option("--hello", dest="dest", default=242) + ] + pm.addpluginclass(My) + config = Config() + pm.add_cmdlineoptions(config) + opt = config._parser.get_option("--hello") + assert opt + assert opt.default == 242 From hpk at codespeak.net Fri Jan 23 17:52:17 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 23 Jan 2009 17:52:17 +0100 (CET) Subject: [py-svn] r61274 - in py/branch/pytestplugin/py/test: . testing Message-ID: <20090123165217.31E121684EE@codespeak.net> Author: hpk Date: Fri Jan 23 17:52:16 2009 New Revision: 61274 Modified: py/branch/pytestplugin/py/test/cmdline.py py/branch/pytestplugin/py/test/config.py py/branch/pytestplugin/py/test/pmanage.py py/branch/pytestplugin/py/test/session.py py/branch/pytestplugin/py/test/testing/plugintester.py py/branch/pytestplugin/py/test/testing/test_config.py py/branch/pytestplugin/py/test/testing/test_pmanage.py Log: move plugin configure/unconfigure and eventbus initialization to pluginmanager thus making it independent from session handling Modified: py/branch/pytestplugin/py/test/cmdline.py ============================================================================== --- py/branch/pytestplugin/py/test/cmdline.py (original) +++ py/branch/pytestplugin/py/test/cmdline.py Fri Jan 23 17:52:16 2009 @@ -10,8 +10,10 @@ args = py.std.sys.argv[1:] config = py.test.config config.parse(args) + config.pluginmanager.configure(config) session = config.initsession() exitstatus = session.main() + config.pluginmanager.unconfigure(config) raise SystemExit(exitstatus) def warn_about_missing_assertion(): Modified: py/branch/pytestplugin/py/test/config.py ============================================================================== --- py/branch/pytestplugin/py/test/config.py (original) +++ py/branch/pytestplugin/py/test/config.py Fri Jan 23 17:52:16 2009 @@ -4,6 +4,7 @@ from conftesthandle import Conftest from py.__.test.defaultconftest import adddefaultoptions from py.__.test.pmanage import PluginManager +from py.__.test.event import EventBus optparse = py.compat.optparse @@ -35,6 +36,7 @@ self._parser = optparse.OptionParser( usage="usage: %prog [options] [query] [filenames of tests]") self._conftest = Conftest() + self.bus = EventBus() self.pluginmanager = PluginManager() def parse(self, args): @@ -72,6 +74,7 @@ self._mergerepr(self._repr) self.pluginmanager.registerplugins(self._conftest.getconftestmodules(None)) del self._repr + self.pluginmanager.configure(self) def _makerepr(self): l = [] Modified: py/branch/pytestplugin/py/test/pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/pmanage.py (original) +++ py/branch/pytestplugin/py/test/pmanage.py Fri Jan 23 17:52:16 2009 @@ -45,6 +45,9 @@ for mod in conftestmodules: self.consider_module(mod) + # + # API for interacting with registered and instantiated plugin objects + # def add_cmdlineoptions(self, config): # XXX think about sorting/grouping of options from user-perspective opts = [] @@ -52,10 +55,8 @@ opts.extend(options) config.addoptions("ungrouped options added by plugins", *opts) - # - # API for calling methods of registered plugins - # def callplugins(self, methname, **args): + """ call method with the given name for each plugin. pass along kwargs. """ for name, method in self.listattr(methname): method(**args) @@ -72,3 +73,13 @@ continue return l + def configure(self, config): + # XXX if we want to allow post-configure addition of + # plugins this logic here has to be refined + assert self.forward_event not in config.bus._subscribers + config.bus._subscribers.append(self.forward_event) + self.callplugins("pytest_configure", config=config) + + def unconfigure(self, config): + self.callplugins("pytest_unconfigure", config=config) + config.bus._subscribers.remove(self.forward_event) Modified: py/branch/pytestplugin/py/test/session.py ============================================================================== --- py/branch/pytestplugin/py/test/session.py (original) +++ py/branch/pytestplugin/py/test/session.py Fri Jan 23 17:52:16 2009 @@ -24,7 +24,7 @@ """ def __init__(self, config): self.config = config - self.bus = EventBus() + self.bus = config.bus # shortcut self._nomatch = False def fixoptions(self): @@ -85,8 +85,6 @@ def sessionstarts(self): """ setup any neccessary resources ahead of the test run. """ - self.config.pluginmanager.callplugins("pytest_configure", config=self.config) - self.bus.subscribe(self.config.pluginmanager.forward_event) self.bus.notify(event.TestrunStart()) # XXX the following is not used or neccessary for the DSession subclass self._failurelist = [] @@ -103,11 +101,6 @@ self.bus.notify(event.TestrunFinish(exitstatus=exitstatus, excinfo=excinfo)) self.bus.unsubscribe(self._processfailures) - # XXX call plugin's unconfigure and write tests for - # that, conflicts with looponfailing/remote's sending - # of an event after the session has finished. - #self.bus.unsubscribe(self.config.pluginmanager.forward_event) - #self.config.pluginmanager.callplugins("pytest_unconfigure", config=self.config) return self._failurelist def getinitialitems(self, colitems): Modified: py/branch/pytestplugin/py/test/testing/plugintester.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/plugintester.py (original) +++ py/branch/pytestplugin/py/test/testing/plugintester.py Fri Jan 23 17:52:16 2009 @@ -56,12 +56,10 @@ pass # already registered plugin = config.pluginmanager.getplugin(pname) assert plugin - if hasattr(plugin, 'pytest_configure'): - plugin.pytest_configure(config) + config.pluginmanager.configure(config) session = config.initsession() exitstatus = session.main() - if hasattr(plugin, 'pytest_unconfigure'): - plugin.pytest_unconfigure(config) + config.pluginmanager.unconfigure(config) return tmpdir finally: olddir.chdir() Modified: py/branch/pytestplugin/py/test/testing/test_config.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_config.py (original) +++ py/branch/pytestplugin/py/test/testing/test_config.py Fri Jan 23 17:52:16 2009 @@ -115,6 +115,10 @@ config2._initialized = False # we have to do that from tests config2._repr = config._makerepr() config2._initafterpickle(topdir=tmp.dirpath()) + # we check that config "remote" config objects + # have correct plugin initialization + assert config2.pluginmanager._plugins + assert config2.pluginmanager.forward_event in config2.bus._subscribers for col1, col2 in zip(getcolitems(config), getcolitems(config2)): assert col1.fspath == col2.fspath cols = getcolitems(config2) @@ -138,7 +142,6 @@ assert config.getvalue('x') == 1 config._mergerepr(repr2) assert config.option.verbose == 42 - def test_config_rconfig(): tmp = py.test.ensuretemp("rconfigopt") Modified: py/branch/pytestplugin/py/test/testing/test_pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_pmanage.py (original) +++ py/branch/pytestplugin/py/test/testing/test_pmanage.py Fri Jan 23 17:52:16 2009 @@ -2,6 +2,7 @@ import py from py.__.test.pmanage import PluginManager from py.__.test.event import NOP +from py.__.test.config import Config as pytestConfig class TestPluginManager: def setup_method(self, method): @@ -87,6 +88,22 @@ assert not pm.listattr("hello") assert pm.listattr("x") == [('my2', 42)] + def test_eventbus_interaction(self): + pm = PluginManager() + l = [] + class My3: + def pytest_configure(self, config): + l.append("configure") + def pytest_unconfigure(self, config): + l.append("unconfigure") + pm.addpluginclass(My3) + config = pytestConfig() + pm.configure(config) + assert pm.forward_event in config.bus._subscribers + pm.unconfigure(config) + assert pm.forward_event not in config.bus._subscribers + assert l == ['configure', 'unconfigure'] + def test_pytest_event(self): pm = PluginManager() l = [] @@ -100,15 +117,15 @@ assert l[0] is ev def test_addcmdlineoptions(self): - from py.__.test.config import Config pm = PluginManager() class My: pytest_cmdlineoptions = [ py.test.config.Option("--hello", dest="dest", default=242) ] pm.addpluginclass(My) - config = Config() + config = pytestConfig() pm.add_cmdlineoptions(config) opt = config._parser.get_option("--hello") assert opt assert opt.default == 242 + From fijal at codespeak.net Sat Jan 24 09:44:05 2009 From: fijal at codespeak.net (fijal at codespeak.net) Date: Sat, 24 Jan 2009 09:44:05 +0100 (CET) Subject: [py-svn] r61296 - py/trunk/py/test/testing Message-ID: <20090124084405.0F8841684B5@codespeak.net> Author: fijal Date: Sat Jan 24 09:44:03 2009 New Revision: 61296 Modified: py/trunk/py/test/testing/test_session.py Log: a failing test 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 Sat Jan 24 09:44:03 2009 @@ -292,6 +292,20 @@ assert len(colfail) == 1 assert len(colskipped) == 1 + def test_minus_x_import_error(self): + py.test.skip("fails") + o = self.tmpdir + tfile = o.join('test_one.py').write(py.code.Source(""" + xxxx + """)) + tfile2 = o.join('test_two.py').write(py.code.Source(""" + yyyyy + """)) + sorter = self.events_from_cmdline('-x') + finished = sorter.get(event.CollectionReport) + colfail = [x for x in finished if x.failed] + assert len(colfail) == 1 + class TestNewSessionDSession(SessionTests): def parseconfig(self, *args): args = ('-n1',) + args From hpk at codespeak.net Sat Jan 24 10:30:47 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 24 Jan 2009 10:30:47 +0100 (CET) Subject: [py-svn] r61297 - py/branch/pytestplugin/py/test Message-ID: <20090124093047.197B41684C7@codespeak.net> Author: hpk Date: Sat Jan 24 10:30:45 2009 New Revision: 61297 Modified: py/branch/pytestplugin/py/test/runner.py Log: docstring, report crash as "C" instead of "X" Modified: py/branch/pytestplugin/py/test/runner.py ============================================================================== --- py/branch/pytestplugin/py/test/runner.py (original) +++ py/branch/pytestplugin/py/test/runner.py Sat Jan 24 10:30:45 2009 @@ -14,7 +14,9 @@ import py.__.test.custompdb class RobustRun(object): - """ a robust setup/execute/teardown protocol. """ + """ a robust setup/execute/teardown protocol used both for test collectors + and test items. + """ def __init__(self, colitem, pdb=None): self.colitem = colitem self.getcapture = colitem._config._getcapture @@ -47,6 +49,7 @@ return self.makereport(res, when, excinfo, outerr) def getkw(self, when, excinfo, outerr): + """construct keyword arguments for a non-passing run of a collector""" if excinfo.errisinstance(Skipped): outcome = "skipped" shortrepr = "s" @@ -148,5 +151,5 @@ ("X", "CRASHED"), ("%s:%s: CRASHED with signal %d" %(path, lineno, result.signal)), ] - outcomerepr = OutcomeRepr(when="???", shortrepr="X", longrepr=longrepr) + outcomerepr = OutcomeRepr(when="???", shortrepr="C", longrepr=longrepr) return event.ItemTestReport(item, failed=outcomerepr) From hpk at codespeak.net Sat Jan 24 13:25:30 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 24 Jan 2009 13:25:30 +0100 (CET) Subject: [py-svn] r61298 - in py/branch/pytestplugin/py/test: . plugin plugin/testing testing Message-ID: <20090124122530.302581684AA@codespeak.net> Author: hpk Date: Sat Jan 24 13:25:29 2009 New Revision: 61298 Modified: py/branch/pytestplugin/py/test/event.py py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py py/branch/pytestplugin/py/test/plugin/pytest_terminal.py py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py py/branch/pytestplugin/py/test/runner.py py/branch/pytestplugin/py/test/testing/suptest.py py/branch/pytestplugin/py/test/testing/test_collect.py py/branch/pytestplugin/py/test/testing/test_session.py Log: * start un-tangling handling of CollectionReports and ItemTestReports * refactor resultlog plugin tests to depend less on py.test internal details * fix suptest to properly handle plugins * somewhat intermediate checkin * all tests pass Modified: py/branch/pytestplugin/py/test/event.py ============================================================================== --- py/branch/pytestplugin/py/test/event.py (original) +++ py/branch/pytestplugin/py/test/event.py Sat Jan 24 13:25:29 2009 @@ -99,9 +99,30 @@ class CollectionReport(BaseReport): """ Collection Report. """ - def __init__(self, colitem, result, **kwargs): - super(CollectionReport, self).__init__(colitem, **kwargs) - self.result = result + skipped = failed = passed = False + + def __init__(self, colitem, result, excinfo=None, when=None, outerr=None): + self.colitem = colitem + if not excinfo: + self.passed = True + self.result = result + else: + self.when = when + self.outerr = outerr + if excinfo.errisinstance(Skipped): + self.skipped = True + self.reason = str(excinfo.value) + self.longrepr = excinfo._getreprcrash() + else: + self.longrepr = self.colitem._repr_failure_py(excinfo, outerr) + self.failed = True + + def toterminal(self, out): + longrepr = self.longrepr + if hasattr(longrepr, 'toterminal'): + longrepr.toterminal(out) + else: + out.line(str(longrepr)) class LooponfailingInfo(BaseEvent): def __init__(self, failreports, rootdirs): Modified: py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py Sat Jan 24 13:25:29 2009 @@ -55,10 +55,28 @@ for line in longrepr.splitlines(): print >>self.logfile, " %s" % line + def getoutcomecodes(self, ev): + if isinstance(ev, ev.CollectionReport): + # encode pass/fail/skip indepedent of terminal reporting semantics + # XXX handle collection and item reports more uniformly + if ev.passed: + code = "." + else: + if ev.failed: + code = "F" + elif ev.skipped: + code = "S" + longrepr = str(ev.longrepr) + else: + assert isinstance(ev, ev.ItemTestReport) + code = ev.outcome.shortrepr # XXX make independent from reporting + longrepr = str(ev.outcome.longrepr) + return code, longrepr + def log_outcome(self, ev): - outcome = ev.outcome gpath = generic_path(ev.colitem) - self.write_log_entry(outcome.shortrepr, gpath, str(outcome.longrepr)) + shortrepr, longrepr = self.getoutcomecodes(ev) + self.write_log_entry(shortrepr, gpath, longrepr) def log_event_to_file(self, ev): if isinstance(ev, ev.ItemTestReport): Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Sat Jan 24 13:25:29 2009 @@ -74,7 +74,10 @@ def _folded_skips(self): d = {} for event in self._skipped: - longrepr = event.outcome.longrepr + if isinstance(event, event.CollectionReport): + longrepr = event.longrepr + else: + longrepr = event.outcome.longrepr key = longrepr.path, longrepr.lineno, longrepr.message d.setdefault(key, []).append(event) l = [] @@ -136,12 +139,6 @@ 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) @@ -195,10 +192,15 @@ def rep_CollectionReport(self, ev): super(TerminalReporter, self).rep_CollectionReport(ev) - fspath = ev.colitem.fspath - if ev.failed or ev.skipped: - msg = self.getcollectoutcome(ev) - self.write_fspath_result(fspath, "- " + msg) + if not ev.passed: + if ev.failed: + msg = ev.longrepr.reprcrash.message + elif ev.skipped: + msg = "Skipped:" + ev.reason + else: + msg = None + if msg is not None: + self.write_fspath_result(ev.colitem.fspath, "- " + msg) def rep_TestrunStart(self, ev): super(TerminalReporter, self).rep_TestrunStart(ev) @@ -324,9 +326,9 @@ def rep_CollectionReport(self, ev): super(CollectonlyReporter, self).rep_CollectionReport(ev) if ev.failed: - self.outindent("!!! %s !!!" % ev.outcome.longrepr.reprcrash.message) + self.outindent("!!! %s !!!" % ev.longrepr.reprcrash.message) elif ev.skipped: - self.outindent("!!! %s !!!" % ev.outcome.longrepr.message) + self.outindent("!!! %s !!!" % ev.longrepr.message) self.indent = self.indent[:-len(self.INDENT)] def rep_TestrunFinish(self, session): Modified: py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/testing/test_resultlog.py Sat Jan 24 13:25:29 2009 @@ -2,31 +2,17 @@ test module for result log plugin -XXX try to avoid low-level construction of items (see make_item) or move - functionality to some place where other tests use it as well. - - """ import os, StringIO import py import py.__.test.plugin.pytest_resultlog as resultlog -from py.__.test.testing import plugintester - +from py.__.test.testing import plugintester, suptest from py.__.test import event -from py.__.test.session import Session -from py.__.test.testing import suptest -from py.__.test.config import Config -from py.__.test.collect import Node, Item, FSCollector -from py.__.test.runner import OutcomeRepr - -class Fake(object): - def __init__(self, **kwds): - self.__dict__.update(kwds) - def test_generic_path(): + from py.__.test.collect import Node, Item, FSCollector p1 = Node('a', config='dummy') assert p1.fspath is None p2 = Node('B', parent=p1) @@ -46,19 +32,6 @@ res = resultlog.generic_path(item) assert res == 'test/a:B().c[1]' - -def make_item(*names): - node = None - config = "dummy" - for name in names[:-1]: - if '/' in name: - node = FSCollector(name, parent=node, config=config) - else: - node = Node(name, parent=node, config=config) - if names[-1] is None: - return node - return Item(names[-1], parent=node) - def test_generic(): plugintester.nocalls("py.__.test.plugin.pytest_resultlog") tmpdir = plugintester.functional("py.__.test.plugin.pytest_resultlog", @@ -71,109 +44,95 @@ "s *:test_skip", ]) -class TestResultLog(object): - def test_write_log_entry(self): - reslog = resultlog.ResultLog(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' - - 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' - - 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' - - reslog.logfile = StringIO.StringIO() - longrepr = ' tb1\n tb 2\nE tb3\nSome Error' - reslog.write_log_entry('F', 'name', longrepr) - entry = reslog.logfile.getvalue() - assert entry[-1] == '\n' - entry_lines = entry.splitlines() - assert len(entry_lines) == 5 - assert entry_lines[0] == 'F name' - assert entry_lines[1:] == [' '+line for line in longrepr.splitlines()] +def test_write_log_entry(): + reslog = resultlog.ResultLog(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' + + 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' + + 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' + + reslog.logfile = StringIO.StringIO() + longrepr = ' tb1\n tb 2\nE tb3\nSome Error' + reslog.write_log_entry('F', 'name', longrepr) + entry = reslog.logfile.getvalue() + assert entry[-1] == '\n' + entry_lines = entry.splitlines() + assert len(entry_lines) == 5 + assert entry_lines[0] == 'F name' + assert entry_lines[1:] == [' '+line for line in longrepr.splitlines()] - def test_log_outcome(self): - reslog = resultlog.ResultLog(StringIO.StringIO()) - - colitem = make_item('some', 'path', 'a', 'b') - - try: - raise ValueError - except ValueError: - the_repr = py.code.ExceptionInfo().getrepr() - - outcome=OutcomeRepr('execute', 'F', the_repr) - ev = Fake(colitem=colitem, outcome=outcome) - - reslog.log_outcome(ev) - - 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): - bus = event.EventBus() - reslog = resultlog.ResultLog(StringIO.StringIO()) - bus.subscribe(reslog.log_event_to_file) - - colitem = make_item('proj/test', 'proj/test/mod', 'a', 'b') - - outcome=OutcomeRepr('execute', '.', '') - rep_ev = event.ItemTestReport(colitem, passed=outcome) - - bus.notify(rep_ev) - - lines = reslog.logfile.getvalue().splitlines() - assert len(lines) == 1 - line = lines[0] - assert line.startswith(". ") - assert line[2:] == 'test/mod:a.b' - +class TestWithFunctionIntegration(suptest.InlineSession): + # XXX (hpk) i think that the resultlog plugin should + # provide a Parser object so that one can remain + # ignorant regarding formatting details. + def getresultlog(self, args): + resultlog = self.tmpdir.join("resultlog") + self.parse_and_run("--resultlog=%s" % resultlog, args) + return filter(None, resultlog.readlines(cr=0)) + def test_collection_report(self): - bus = event.EventBus() - reslog = resultlog.ResultLog(None) - bus.subscribe(reslog.log_event_to_file) - - reslog.logfile = StringIO.StringIO() - colitem = make_item('proj/test', 'proj/test/mod', 'A', None) - outcome=OutcomeRepr('execute', '', '') - rep_ev = event.CollectionReport(colitem, object(), passed=outcome) - - bus.notify(rep_ev) - - entry = reslog.logfile.getvalue() - assert not entry - - reslog.logfile = StringIO.StringIO() - outcome=OutcomeRepr('execute', 'F', 'Some Error') - rep_ev = event.CollectionReport(colitem, object(), failed=outcome) + ok = self.makepyfile(test_collection_ok="") + skip = self.makepyfile(test_collection_skip="import py ; py.test.skip('hello')") + fail = self.makepyfile(test_collection_fail="XXX") - bus.notify(rep_ev) + lines = self.getresultlog(ok) + assert not lines - lines = reslog.logfile.getvalue().splitlines() + lines = self.getresultlog(skip) assert len(lines) == 2 - assert lines[0] == 'F test/mod:A' + assert lines[0].startswith("S ") + assert lines[0].endswith("test_collection_skip.py") + assert lines[1].startswith(" ") + assert lines[1].endswith("test_collection_skip.py:1: Skipped: 'hello'") + + lines = self.getresultlog(fail) + assert lines + assert lines[0].startswith("F ") + assert lines[0].endswith("test_collection_fail.py"), lines[0] + for x in lines[1:]: + assert x.startswith(" ") + assert "XXX" in "".join(lines[1:]) + + def test_log_test_outcomes(self): + mod = self.makepyfile(test_mod=""" + import py + def test_pass(): pass + def test_skip(): py.test.skip("hello") + def test_fail(): raise ValueError("val") + """) + lines = self.getresultlog(mod) + assert len(lines) >= 3 + assert lines[0].startswith(". ") + assert lines[0].endswith("test_pass") + assert lines[1].startswith("s "), lines[1] + assert lines[1].endswith("test_skip") + assert lines[2].find("hello") != -1 + + assert lines[3].startswith("F ") + assert lines[3].endswith("test_fail") + tb = "".join(lines[4:]) + assert tb.find("ValueError") != -1 def test_internal_exception(self): # they are produced for example by a teardown failing Modified: py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py Sat Jan 24 13:25:29 2009 @@ -260,7 +260,7 @@ rep.processevent(event.ItemStart(item)) s = popvalue(stringio) assert s.find("Function 'test_func'") != -1 - rep.processevent(event.CollectionReport(modcol, [], passed="")) + rep.processevent(event.CollectionReport(modcol, [], excinfo=None)) assert rep.indent == indent def test_collectonly_skipped_module(self): @@ -322,23 +322,28 @@ rep.processevent(ev) assert getattr(rep, '_' + outcome) == [ev] - def test_CollectionReport(self): + def test_CollectionReport_events_are_counted(self): for outcome in 'skipped failed'.split(): rep = BaseReporter() - ev = event.CollectionReport(None, None, **{outcome:True}) + ev = event.CollectionReport(None, None) + setattr(ev, outcome, True) rep.processevent(ev) assert getattr(rep, '_' + outcome) == [ev] - def test_skip_reasons(self): - from py.__.test.runner import OutcomeRepr + def test_skip_reasons_folding(self): rep = BaseReporter() class longrepr: path = 'xyz' lineno = 3 message = "justso" - out1 = OutcomeRepr(None, None, longrepr) + + ev1 = event.CollectionReport(None, None) + ev1.when = "execute" + ev1.skipped = True + ev1.longrepr = longrepr + + from py.__.test.runner import OutcomeRepr out2 = OutcomeRepr(None, None, longrepr) - ev1 = event.CollectionReport(None, None, skipped=out1) ev2 = event.ItemTestReport(None, skipped=out2) rep.processevent(ev1) rep.processevent(ev2) Modified: py/branch/pytestplugin/py/test/runner.py ============================================================================== --- py/branch/pytestplugin/py/test/runner.py (original) +++ py/branch/pytestplugin/py/test/runner.py Sat Jan 24 13:25:29 2009 @@ -95,11 +95,13 @@ def execute(self): return self.colitem._memocollect() def makereport(self, res, when, excinfo, outerr): - if excinfo: - kw = self.getkw(when, excinfo, outerr) - else: - kw = {'passed': OutcomeRepr(when, '', "")} - return event.CollectionReport(self.colitem, res, **kw) + return event.CollectionReport(self.colitem, res, excinfo, when, outerr) + +# if excinfo: +# kw = self.getkw(when, excinfo, outerr) +# else: +# kw = {'passed': OutcomeRepr(when, '', "")} +# return event.CollectionReport(self.colitem, res, **kw) NORESULT = object() # Modified: py/branch/pytestplugin/py/test/testing/suptest.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/suptest.py (original) +++ py/branch/pytestplugin/py/test/testing/suptest.py Sat Jan 24 13:25:29 2009 @@ -213,9 +213,11 @@ class InlineSession(InlineCollection): def parse_and_run(self, *args): config = self.parseconfig(*args) + config.pluginmanager.configure(config) session = config.initsession() sorter = EventSorter(config, session) session.main() + config.pluginmanager.unconfigure(config) return sorter def popvalue(stringio): Modified: py/branch/pytestplugin/py/test/testing/test_collect.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_collect.py (original) +++ py/branch/pytestplugin/py/test/testing/test_collect.py Sat Jan 24 13:25:29 2009 @@ -489,7 +489,7 @@ and x.failed] assert len(failures) == 1 ev = failures[0] - assert ev.outcome.longrepr.reprcrash.message.startswith("SyntaxError") + assert ev.longrepr.reprcrash.message.startswith("SyntaxError") def test_example_items1(self): self.tmp.ensure("test_example.py").write(py.code.Source(''' Modified: py/branch/pytestplugin/py/test/testing/test_session.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_session.py (original) +++ py/branch/pytestplugin/py/test/testing/test_session.py Sat Jan 24 13:25:29 2009 @@ -122,7 +122,7 @@ sorter = self.events_from_cmdline() l = sorter.getfailedcollections() assert len(l) == 1 - out = l[0].outcome.longrepr.reprcrash.message + out = l[0].longrepr.reprcrash.message assert out.find('does_not_work') != -1 def test_raises_output(self): @@ -145,7 +145,7 @@ yield None """) failures = sorter.getfailedcollections() - out = failures[0].outcome.longrepr.reprcrash.message + out = failures[0].longrepr.reprcrash.message i = out.find('TypeError') assert i != -1 @@ -153,7 +153,7 @@ sorter = self.events_from_runsource("this is really not python") l = sorter.getfailedcollections() assert len(l) == 1 - out = l[0].outcome.longrepr.reprcrash.message + out = l[0].longrepr.reprcrash.message assert out.find(str('not python')) != -1 def test_exit_first_problem(self): From hpk at codespeak.net Sat Jan 24 13:44:44 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 24 Jan 2009 13:44:44 +0100 (CET) Subject: [py-svn] r61299 - in py/branch/pytestplugin/py/test: . plugin plugin/testing Message-ID: <20090124124444.D540316850F@codespeak.net> Author: hpk Date: Sat Jan 24 13:44:42 2009 New Revision: 61299 Modified: py/branch/pytestplugin/py/test/event.py py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py py/branch/pytestplugin/py/test/plugin/pytest_terminal.py py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py Log: unify skip/fail "longrepr" attribute of CollectionReports Modified: py/branch/pytestplugin/py/test/event.py ============================================================================== --- py/branch/pytestplugin/py/test/event.py (original) +++ py/branch/pytestplugin/py/test/event.py Sat Jan 24 13:44:42 2009 @@ -109,12 +109,11 @@ else: self.when = when self.outerr = outerr + self.longrepr = self.colitem._repr_failure_py(excinfo, outerr) if excinfo.errisinstance(Skipped): self.skipped = True self.reason = str(excinfo.value) - self.longrepr = excinfo._getreprcrash() else: - self.longrepr = self.colitem._repr_failure_py(excinfo, outerr) self.failed = True def toterminal(self, out): Modified: py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py Sat Jan 24 13:44:42 2009 @@ -66,7 +66,7 @@ code = "F" elif ev.skipped: code = "S" - longrepr = str(ev.longrepr) + longrepr = str(ev.longrepr.reprcrash) else: assert isinstance(ev, ev.ItemTestReport) code = ev.outcome.shortrepr # XXX make independent from reporting Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Sat Jan 24 13:44:42 2009 @@ -76,9 +76,10 @@ for event in self._skipped: if isinstance(event, event.CollectionReport): longrepr = event.longrepr + entry = event.longrepr.reprcrash else: - longrepr = event.outcome.longrepr - key = longrepr.path, longrepr.lineno, longrepr.message + entry = event.outcome.longrepr + key = entry.path, entry.lineno, entry.message d.setdefault(key, []).append(event) l = [] for key, events in d.iteritems(): @@ -325,10 +326,8 @@ def rep_CollectionReport(self, ev): super(CollectonlyReporter, self).rep_CollectionReport(ev) - if ev.failed: + if not ev.passed: self.outindent("!!! %s !!!" % ev.longrepr.reprcrash.message) - elif ev.skipped: - self.outindent("!!! %s !!!" % ev.longrepr.message) self.indent = self.indent[:-len(self.INDENT)] def rep_TestrunFinish(self, session): Modified: py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py Sat Jan 24 13:44:42 2009 @@ -333,9 +333,10 @@ def test_skip_reasons_folding(self): rep = BaseReporter() class longrepr: - path = 'xyz' - lineno = 3 - message = "justso" + class reprcrash: + path = 'xyz' + lineno = 3 + message = "justso" ev1 = event.CollectionReport(None, None) ev1.when = "execute" @@ -343,7 +344,7 @@ ev1.longrepr = longrepr from py.__.test.runner import OutcomeRepr - out2 = OutcomeRepr(None, None, longrepr) + out2 = OutcomeRepr(None, None, longrepr.reprcrash) ev2 = event.ItemTestReport(None, skipped=out2) rep.processevent(ev1) rep.processevent(ev2) @@ -352,9 +353,9 @@ assert len(l) == 1 num, fspath, lineno, reason = l[0] assert num == 2 - assert fspath == longrepr.path - assert lineno == longrepr.lineno - assert reason == longrepr.message + assert fspath == longrepr.reprcrash.path + assert lineno == longrepr.reprcrash.lineno + assert reason == longrepr.reprcrash.message def test_repr_python_version(): py.magic.patch(sys, 'version_info', (2, 5, 1, 'final', 0)) From hpk at codespeak.net Sat Jan 24 14:44:32 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 24 Jan 2009 14:44:32 +0100 (CET) Subject: [py-svn] r61303 - in py/branch/pytestplugin/py/test: . dsession dsession/testing looponfail/testing plugin plugin/testing testing Message-ID: <20090124134432.1C5B016850F@codespeak.net> Author: hpk Date: Sat Jan 24 14:44:31 2009 New Revision: 61303 Modified: py/branch/pytestplugin/py/test/dsession/dsession.py py/branch/pytestplugin/py/test/dsession/testing/test_dsession.py py/branch/pytestplugin/py/test/event.py py/branch/pytestplugin/py/test/looponfail/testing/test_util.py py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py py/branch/pytestplugin/py/test/plugin/pytest_terminal.py py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py py/branch/pytestplugin/py/test/runner.py py/branch/pytestplugin/py/test/testing/test_event.py py/branch/pytestplugin/py/test/testing/test_runner_functional.py py/branch/pytestplugin/py/test/testing/test_session.py Log: * unify CollectionReport and ItemTestReport attributes some more * move their construction into constructor of the respective classes this simplifies the runner protocol (see runner.py) Modified: py/branch/pytestplugin/py/test/dsession/dsession.py ============================================================================== --- py/branch/pytestplugin/py/test/dsession/dsession.py (original) +++ py/branch/pytestplugin/py/test/dsession/dsession.py Sat Jan 24 14:44:31 2009 @@ -12,7 +12,6 @@ Collector = (py.test.collect.Collector, py.test.collect.Collector) from py.__.test.runner import basic_run_report, basic_collect_report from py.__.test.session import Session -from py.__.test.runner import OutcomeRepr from py.__.test import outcome import Queue @@ -221,9 +220,8 @@ self.trace("removed %r, host=%r" %(item,host.hostid)) def handle_crashitem(self, item, host): - longrepr = "%r CRASHED THE HOST %r" %(item, host) - outcome = OutcomeRepr(when="execute", shortrepr="c", longrepr=longrepr) - rep = event.ItemTestReport(item, failed=outcome) + longrepr = "!!! Host %r crashed during running of test %r" %(host, item) + rep = event.ItemTestReport(item, when="???", excinfo=longrepr) self.bus.notify(rep) def setup_hosts(self): Modified: py/branch/pytestplugin/py/test/dsession/testing/test_dsession.py ============================================================================== --- py/branch/pytestplugin/py/test/dsession/testing/test_dsession.py (original) +++ py/branch/pytestplugin/py/test/dsession/testing/test_dsession.py Sat Jan 24 14:44:31 2009 @@ -182,8 +182,8 @@ testrep = [x for x in events if isinstance(x, event.ItemTestReport)][0] assert testrep.failed assert testrep.colitem == item1 - assert str(testrep.outcome.longrepr).find("CRASHED") != -1 - assert str(testrep.outcome.longrepr).find(host.hostname) != -1 + assert str(testrep.longrepr).find("crashed") != -1 + assert str(testrep.longrepr).find(host.hostname) != -1 def test_hostup_adds_to_available(self): item = self.getitem("def test_func(): pass") Modified: py/branch/pytestplugin/py/test/event.py ============================================================================== --- py/branch/pytestplugin/py/test/event.py (original) +++ py/branch/pytestplugin/py/test/event.py Sat Jan 24 14:44:31 2009 @@ -75,16 +75,8 @@ class BaseReport(BaseEvent): - failed = passed = skipped = None - def __init__(self, colitem, **kwargs): - self.colitem = colitem - assert len(kwargs) == 1, kwargs - name, value = kwargs.items()[0] - setattr(self, name, True) - self.outcome = value - def toterminal(self, out): - longrepr = self.outcome.longrepr + longrepr = self.longrepr if hasattr(longrepr, 'toterminal'): longrepr.toterminal(out) else: @@ -92,7 +84,34 @@ class ItemTestReport(BaseReport): """ Test Execution Report. """ + failed = passed = skipped = False + def __init__(self, colitem, excinfo=None, when=None, outerr=None): + self.colitem = colitem + if not excinfo: + self.passed = True + self.shortrepr = "." + else: + self.when = when + if not isinstance(excinfo, py.code.ExceptionInfo): + self.failed = True + shortrepr = "?" + longrepr = excinfo + elif excinfo.errisinstance(Skipped): + self.skipped = True + shortrepr = "s" + longrepr = self.colitem._repr_failure_py(excinfo, outerr) + else: + self.failed = True + shortrepr = self.colitem.shortfailurerepr + if self.when == "execute": + longrepr = self.colitem.repr_failure(excinfo, outerr) + else: # exception in setup or teardown + longrepr = self.colitem._repr_failure_py(excinfo, outerr) + shortrepr = shortrepr.lower() + self.shortrepr = shortrepr + self.longrepr = longrepr + class CollectionStart(BaseEvent): def __init__(self, collector): self.collector = collector Modified: py/branch/pytestplugin/py/test/looponfail/testing/test_util.py ============================================================================== --- py/branch/pytestplugin/py/test/looponfail/testing/test_util.py (original) +++ py/branch/pytestplugin/py/test/looponfail/testing/test_util.py Sat Jan 24 14:44:31 2009 @@ -69,7 +69,8 @@ bus.notify(event.NOP()) assert recorder.events assert not recorder.getfailures() - rep = event.ItemTestReport(None, failed=True) + rep = event.ItemTestReport(None, None) + rep.failed = True bus.notify(rep) failures = recorder.getfailures() assert failures == [rep] Modified: py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py Sat Jan 24 14:44:31 2009 @@ -59,24 +59,28 @@ if isinstance(ev, ev.CollectionReport): # encode pass/fail/skip indepedent of terminal reporting semantics # XXX handle collection and item reports more uniformly - if ev.passed: - code = "." - else: - if ev.failed: - code = "F" - elif ev.skipped: - code = "S" + assert not ev.passed + if ev.failed: + code = "F" + elif ev.skipped: + code = "S" longrepr = str(ev.longrepr.reprcrash) else: assert isinstance(ev, ev.ItemTestReport) - code = ev.outcome.shortrepr # XXX make independent from reporting - longrepr = str(ev.outcome.longrepr) + code = ev.shortrepr + if ev.passed: + longrepr = "" + elif ev.failed: + longrepr = str(ev.longrepr) + elif ev.skipped: + longrepr = str(ev.longrepr.reprcrash.message) return code, longrepr def log_outcome(self, ev): - gpath = generic_path(ev.colitem) - shortrepr, longrepr = self.getoutcomecodes(ev) - self.write_log_entry(shortrepr, gpath, longrepr) + if (not ev.passed or isinstance(ev, ev.ItemTestReport)): + gpath = generic_path(ev.colitem) + shortrepr, longrepr = self.getoutcomecodes(ev) + self.write_log_entry(shortrepr, gpath, longrepr) def log_event_to_file(self, ev): if isinstance(ev, ev.ItemTestReport): Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Sat Jan 24 14:44:31 2009 @@ -74,11 +74,7 @@ def _folded_skips(self): d = {} for event in self._skipped: - if isinstance(event, event.CollectionReport): - longrepr = event.longrepr - entry = event.longrepr.reprcrash - else: - entry = event.outcome.longrepr + entry = event.longrepr.reprcrash key = entry.path, entry.lineno, entry.message d.setdefault(key, []).append(event) l = [] @@ -131,14 +127,18 @@ self.ensure_newline() self._tw.sep(sep, title, **markup) - def getoutcomeletter(self, item): - return item.outcome.shortrepr + def getoutcomeletter(self, event): + return event.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 getoutcomeword(self, event): + if event.passed: + return self._tw.markup("PASS", green=True) + elif event.failed: + return self._tw.markup("FAIL", red=True) + elif event.skipped: + return "SKIP" + else: + return self._tw.markup("???", red=True) def rep_InternalException(self, ev): for line in str(ev.repr).split("\n"): @@ -226,9 +226,9 @@ self.write_sep("#", "LOOPONFAILING", red=True) for report in ev.failreports: try: - loc = report.outcome.longrepr.reprcrash + loc = report.longrepr.reprcrash except AttributeError: - loc = str(report.outcome.longrepr)[:50] + loc = str(report.longrepr)[:50] self.write_line(loc, red=True) self.write_sep("#", "waiting for changes") for rootdir in ev.rootdirs: Modified: py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/testing/test_terminal.py Sat Jan 24 14:44:31 2009 @@ -318,7 +318,8 @@ def test_TestItemReport_one(self): for outcome in 'passed skipped failed'.split(): rep = BaseReporter() - ev = event.ItemTestReport(None, **{outcome:True}) + ev = event.ItemTestReport(None) + setattr(ev, outcome, True) rep.processevent(ev) assert getattr(rep, '_' + outcome) == [ev] @@ -343,9 +344,8 @@ ev1.skipped = True ev1.longrepr = longrepr - from py.__.test.runner import OutcomeRepr - out2 = OutcomeRepr(None, None, longrepr.reprcrash) - ev2 = event.ItemTestReport(None, skipped=out2) + ev2 = event.ItemTestReport(None, excinfo=longrepr) + ev2.skipped = True rep.processevent(ev1) rep.processevent(ev2) assert len(rep._skipped) == 2 Modified: py/branch/pytestplugin/py/test/runner.py ============================================================================== --- py/branch/pytestplugin/py/test/runner.py (original) +++ py/branch/pytestplugin/py/test/runner.py Sat Jan 24 14:44:31 2009 @@ -48,23 +48,6 @@ excinfo = py.code.ExceptionInfo() return self.makereport(res, when, excinfo, outerr) - def getkw(self, when, excinfo, outerr): - """construct keyword arguments for a non-passing run of a collector""" - if excinfo.errisinstance(Skipped): - outcome = "skipped" - shortrepr = "s" - longrepr = excinfo._getreprcrash() - else: - outcome = "failed" - if when == "execute": - longrepr = self.colitem.repr_failure(excinfo, outerr) - shortrepr = self.colitem.shortfailurerepr - else: - longrepr = self.colitem._repr_failure_py(excinfo, outerr) - shortrepr = self.colitem.shortfailurerepr.lower() - kw = { outcome: OutcomeRepr(when, shortrepr, longrepr)} - return kw - class ItemRunner(RobustRun): def setup(self): self.colitem._setupstate.prepare(self.colitem) @@ -76,11 +59,7 @@ #self.colitem.config.pluginmanager.post_execute(self.colitem) def makereport(self, res, when, excinfo, outerr): - if excinfo: - kw = self.getkw(when, excinfo, outerr) - else: - kw = {'passed': OutcomeRepr(when, '.', "")} - testrep = event.ItemTestReport(self.colitem, **kw) + testrep = event.ItemTestReport(self.colitem, excinfo, when, outerr) if self.pdb and testrep.failed: tw = py.io.TerminalWriter() testrep.toterminal(tw) @@ -97,26 +76,11 @@ def makereport(self, res, when, excinfo, outerr): return event.CollectionReport(self.colitem, res, excinfo, when, outerr) -# if excinfo: -# kw = self.getkw(when, excinfo, outerr) -# else: -# kw = {'passed': OutcomeRepr(when, '', "")} -# return event.CollectionReport(self.colitem, res, **kw) - NORESULT = object() # # public entrypoints / objects # -class OutcomeRepr(object): - def __init__(self, when, shortrepr, longrepr): - self.when = when - self.shortrepr = shortrepr - self.longrepr = longrepr - def __str__(self): - return " Author: hpk Date: Sat Jan 24 14:48:44 2009 New Revision: 61304 Modified: py/branch/pytestplugin/py/test/runner.py Log: remove one XXX and unused import Modified: py/branch/pytestplugin/py/test/runner.py ============================================================================== --- py/branch/pytestplugin/py/test/runner.py (original) +++ py/branch/pytestplugin/py/test/runner.py Sat Jan 24 14:48:44 2009 @@ -9,7 +9,7 @@ import py, os, sys from py.__.test import event -from py.__.test.outcome import Skipped, Exit +from py.__.test.outcome import Exit from py.__.test.dsession.mypickle import ImmutablePickler import py.__.test.custompdb @@ -117,9 +117,4 @@ ("X", "CRASHED"), ("%s:%s: CRASHED with signal %d" %(path, lineno, result.signal)), ] - # XXX hackish to modify "pass" event - ev = event.ItemTestReport(item) - ev.failed = True - ev.when = "???" - ev.longrepr = longrepr - return ev + return event.ItemTestReport(item, excinfo=longrepr, when="???") From hpk at codespeak.net Sat Jan 24 19:50:05 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 24 Jan 2009 19:50:05 +0100 (CET) Subject: [py-svn] r61311 - in py/branch/pytestplugin/py/test/plugin: . testing Message-ID: <20090124185005.038C7168546@codespeak.net> Author: hpk Date: Sat Jan 24 19:50:03 2009 New Revision: 61311 Added: py/branch/pytestplugin/py/test/plugin/conftest.py (contents, props changed) py/branch/pytestplugin/py/test/plugin/pytest_xfail.py (contents, props changed) Removed: py/branch/pytestplugin/py/test/plugin/testing/ Modified: py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Log: make plugins more self-contained: shift plugin tests to the plugin file itself. Added: py/branch/pytestplugin/py/test/plugin/conftest.py ============================================================================== --- (empty file) +++ py/branch/pytestplugin/py/test/plugin/conftest.py Sat Jan 24 19:50:03 2009 @@ -0,0 +1,6 @@ +import py + +class Directory(py.test.collect.Directory): + def consider_file(self, path, usefilters): + if path.basename.startswith("pytest_") and path.ext == ".py": + return self.Module(path, parent=self) Modified: py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py Sat Jan 24 19:50:03 2009 @@ -24,3 +24,21 @@ f = self.eventlogfile print >>f, event f.flush() + +# =============================================================================== +# +# plugin tests +# +# =============================================================================== + +from py.__.test.testing import plugintester + +def test_generic(): + impname = "py.__.test.plugin.pytest_eventlog" + plugintester.nocalls(impname) + tmpdir = plugintester.functional( + "py.__.test.plugin.pytest_eventlog", '--eventlog=event.log') + s = tmpdir.join("event.log").read() + assert s.find("TestrunStart") != -1 + assert s.find("ItemTestReport") != -1 + assert s.find("TestrunFinish") != -1 Modified: py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py Sat Jan 24 19:50:03 2009 @@ -91,3 +91,161 @@ elif isinstance(ev, ev.InternalException): path = ev.repr.reprcrash.path # fishing :( self.write_log_entry('!', path, str(ev.repr)) + +# =============================================================================== +# +# plugin tests +# +# =============================================================================== + +import os, StringIO +from py.__.test.testing import plugintester, suptest +from py.__.test import event + +def test_generic_path(): + from py.__.test.collect import Node, Item, FSCollector + p1 = Node('a', config='dummy') + assert p1.fspath is None + p2 = Node('B', parent=p1) + p3 = Node('()', parent = p2) + item = Item('c', parent = p3) + + res = generic_path(item) + assert res == 'a.B().c' + + p0 = FSCollector('proj/test', config='dummy') + p1 = FSCollector('proj/test/a', parent=p0) + p2 = Node('B', parent=p1) + p3 = Node('()', parent = p2) + p4 = Node('c', parent=p3) + item = Item('[1]', parent = p4) + + res = generic_path(item) + assert res == 'test/a:B().c[1]' + +def test_generic(): + plugintester.nocalls("py.__.test.plugin.pytest_resultlog") + tmpdir = plugintester.functional("py.__.test.plugin.pytest_resultlog", + '--resultlog=resultlog') + resultlog = tmpdir.join("resultlog") + s = resultlog.readlines(cr=0) + suptest.assert_lines_contain_lines(s, [ + ". *:test_pass", + "F *:test_fail", + "s *:test_skip", + ]) + +def test_write_log_entry(): + reslog = ResultLog(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' + + 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' + + 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' + + reslog.logfile = StringIO.StringIO() + longrepr = ' tb1\n tb 2\nE tb3\nSome Error' + reslog.write_log_entry('F', 'name', longrepr) + entry = reslog.logfile.getvalue() + assert entry[-1] == '\n' + entry_lines = entry.splitlines() + assert len(entry_lines) == 5 + assert entry_lines[0] == 'F name' + assert entry_lines[1:] == [' '+line for line in longrepr.splitlines()] + +class TestWithFunctionIntegration(suptest.InlineSession): + # XXX (hpk) i think that the resultlog plugin should + # provide a Parser object so that one can remain + # ignorant regarding formatting details. + def getresultlog(self, args): + resultlog = self.tmpdir.join("resultlog") + self.parse_and_run("--resultlog=%s" % resultlog, args) + return filter(None, resultlog.readlines(cr=0)) + + def test_collection_report(self): + ok = self.makepyfile(test_collection_ok="") + skip = self.makepyfile(test_collection_skip="import py ; py.test.skip('hello')") + fail = self.makepyfile(test_collection_fail="XXX") + + lines = self.getresultlog(ok) + assert not lines + + lines = self.getresultlog(skip) + assert len(lines) == 2 + assert lines[0].startswith("S ") + assert lines[0].endswith("test_collection_skip.py") + assert lines[1].startswith(" ") + assert lines[1].endswith("test_collection_skip.py:1: Skipped: 'hello'") + + lines = self.getresultlog(fail) + assert lines + assert lines[0].startswith("F ") + assert lines[0].endswith("test_collection_fail.py"), lines[0] + for x in lines[1:]: + assert x.startswith(" ") + assert "XXX" in "".join(lines[1:]) + + def test_log_test_outcomes(self): + mod = self.makepyfile(test_mod=""" + import py + def test_pass(): pass + def test_skip(): py.test.skip("hello") + def test_fail(): raise ValueError("val") + """) + lines = self.getresultlog(mod) + assert len(lines) >= 3 + assert lines[0].startswith(". ") + assert lines[0].endswith("test_pass") + assert lines[1].startswith("s "), lines[1] + assert lines[1].endswith("test_skip") + assert lines[2].find("hello") != -1 + + assert lines[3].startswith("F ") + assert lines[3].endswith("test_fail") + tb = "".join(lines[4:]) + assert tb.find("ValueError") != -1 + + def test_internal_exception(self): + # they are produced for example by a teardown failing + # at the end of the run + bus = event.EventBus() + reslog = ResultLog(StringIO.StringIO()) + bus.subscribe(reslog.log_event_to_file) + + try: + raise ValueError + except ValueError: + excinfo = py.code.ExceptionInfo() + + internal = event.InternalException(excinfo) + + bus.notify(internal) + + entry = reslog.logfile.getvalue() + entry_lines = entry.splitlines() + + assert entry_lines[0].startswith('! ') + assert os.path.basename(__file__)[:-1] in entry_lines[0] #.py/.pyc + assert entry_lines[-1][0] == ' ' + assert 'ValueError' in entry + Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Sat Jan 24 19:50:03 2009 @@ -343,3 +343,382 @@ except (TypeError, ValueError): return str(v) +# =============================================================================== +# +# plugin tests +# +# =============================================================================== + +from py.__.test import event +from py.__.test.runner import basic_run_report +from py.__.test.testing import suptest +from py.__.test.testing.suptest import popvalue, assert_stringio_contains_lines +from py.__.test.dsession.hostmanage import Host, makehostup + +class TestTerminal(suptest.InlineCollection): + #def test_reporter_subscription_by_default(self): + # config = py.test.config._reparse(['xxx']) + # plugin = config.pluginmanager.getplugin("pytest_terminal") + # assert plugin + + def test_hostup(self): + item = self.getitem("def test_func(): pass") + stringio = py.std.cStringIO.StringIO() + rep = TerminalReporter(item._config, file=stringio) + rep.processevent(event.TestrunStart()) + host = Host("localhost") + rep.processevent(makehostup(host)) + s = popvalue(stringio) + expect = "%s %s %s - Python %s" %(host.hostid, sys.platform, + sys.executable, repr_pythonversion(sys.version_info)) + assert s.find(expect) != -1 + + def test_pass_skip_fail(self): + modcol = self.getmodulecol(""" + import py + def test_ok(): + pass + def test_skip(): + py.test.skip("xx") + def test_func(): + assert 0 + """, 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) + s = popvalue(stringio) + assert s.find("test_pass_skip_fail.py .sF") != -1 + rep.processevent(event.TestrunFinish()) + assert_stringio_contains_lines(stringio, [ + " def test_func():", + "> assert 0", + "E assert 0", + ]) + + def test_pass_skip_fail_verbose(self): + modcol = self.getmodulecol(""" + import py + def test_ok(): + pass + def test_skip(): + py.test.skip("xx") + def test_func(): + assert 0 + """, configargs=("-v",), withsession=True) + stringio = py.std.cStringIO.StringIO() + rep = TerminalReporter(modcol._config, file=stringio) + rep.processevent(event.TestrunStart()) + items = modcol.collect() + for item in items: + rep.processevent(event.ItemStart(item)) + s = stringio.getvalue().strip() + assert s.endswith(item.name) + ev = basic_run_report(item) + rep.processevent(ev) + + assert_stringio_contains_lines(stringio, [ + "*test_pass_skip_fail_verbose.py:2: *test_ok*PASS", + "*test_pass_skip_fail_verbose.py:4: *test_skip*SKIP", + "*test_pass_skip_fail_verbose.py:6: *test_func*FAIL", + ]) + rep.processevent(event.TestrunFinish()) + assert_stringio_contains_lines(stringio, [ + " def test_func():", + "> assert 0", + "E assert 0", + ]) + + def test_collect_fail(self): + modcol = self.getmodulecol(""" + import xyz + """, withsession=True) + stringio = py.std.cStringIO.StringIO() + rep = TerminalReporter(modcol._config, file=stringio) + self.session.bus.subscribe(rep.processevent) + rep.processevent(event.TestrunStart()) + l = list(self.session.genitems([modcol])) + assert len(l) == 0 + s = popvalue(stringio) + print s + assert s.find("test_collect_fail.py - ImportError: No module named") != -1 + rep.processevent(event.TestrunFinish()) + assert_stringio_contains_lines(stringio, [ + "> import xyz", + "E ImportError: No module named xyz" + ]) + + def test_internal_exception(self): + modcol = self.getmodulecol("def test_one(): pass") + stringio = py.std.cStringIO.StringIO() + rep = TerminalReporter(modcol._config, file=stringio) + excinfo = py.test.raises(ValueError, "raise ValueError('hello')") + rep.processevent(event.InternalException(excinfo)) + s = popvalue(stringio) + assert s.find("InternalException:") != -1 + + def test_hostready_crash(self): + modcol = self.getmodulecol(""" + def test_one(): + pass + """, configargs=("-v",)) + stringio = py.std.cStringIO.StringIO() + host1 = Host("localhost") + rep = TerminalReporter(modcol._config, file=stringio) + rep.processevent(event.HostGatewayReady(host1, None)) + s = popvalue(stringio) + assert s.find("HostGatewayReady") != -1 + rep.processevent(event.HostDown(host1, "myerror")) + s = popvalue(stringio) + assert s.find("HostDown") != -1 + assert s.find("myerror") != -1 + + def test_writeline(self): + modcol = self.getmodulecol("def test_one(): pass") + stringio = py.std.cStringIO.StringIO() + rep = TerminalReporter(modcol._config, file=stringio) + rep.write_fspath_result(py.path.local("xy.py"), '.') + rep.write_line("hello world") + lines = popvalue(stringio).split('\n') + assert not lines[0] + assert lines[1].endswith("xy.py .") + assert lines[2] == "hello world" + + def test_looponfailingreport(self): + modcol = self.getmodulecol(""" + def test_fail(): + assert 0 + def test_fail2(): + raise ValueError() + """) + stringio = py.std.cStringIO.StringIO() + rep = TerminalReporter(modcol._config, file=stringio) + reports = [basic_run_report(x) for x in modcol.collect()] + rep.processevent(event.LooponfailingInfo(reports, [modcol._config.topdir])) + assert_stringio_contains_lines(stringio, [ + "*test_looponfailingreport.py:2: assert 0", + "*test_looponfailingreport.py:4: ValueError*", + "*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 + + 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, 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 + + 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!') + def test_interrupt_me(): + raise KeyboardInterrupt # simulating the user + """, configargs=("--showskipsummary",) + ("-v",)*verbose, + withsession=True) + stringio = py.std.cStringIO.StringIO() + rep = TerminalReporter(modcol._config, file=stringio) + rep.processevent(event.TestrunStart()) + 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) + 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", + ]) + 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) + +class TestCollectonly(suptest.InlineCollection): + def test_collectonly_basic(self): + modcol = self.getmodulecol(configargs=['--collectonly'], source=""" + def test_func(): + pass + """) + stringio = py.std.cStringIO.StringIO() + rep = CollectonlyReporter(modcol._config, out=stringio) + indent = rep.indent + rep.processevent(event.CollectionStart(modcol)) + s = popvalue(stringio) + assert s == "" + + item = modcol.join("test_func") + rep.processevent(event.ItemStart(item)) + s = popvalue(stringio) + assert s.find("Function 'test_func'") != -1 + rep.processevent(event.CollectionReport(modcol, [], excinfo=None)) + assert rep.indent == indent + + def test_collectonly_skipped_module(self): + modcol = self.getmodulecol(configargs=['--collectonly'], source=""" + import py + py.test.skip("nomod") + """, withsession=True) + stringio = py.std.cStringIO.StringIO() + rep = CollectonlyReporter(modcol._config, out=stringio) + self.session.bus.subscribe(rep.processevent) + cols = list(self.session.genitems([modcol])) + assert len(cols) == 0 + assert_stringio_contains_lines(stringio, """ + + !!! Skipped: 'nomod' !!! + """) + + def test_collectonly_failed_module(self): + modcol = self.getmodulecol(configargs=['--collectonly'], source=""" + raise ValueError(0) + """, withsession=True) + stringio = py.std.cStringIO.StringIO() + rep = CollectonlyReporter(modcol._config, out=stringio) + self.session.bus.subscribe(rep.processevent) + cols = list(self.session.genitems([modcol])) + assert len(cols) == 0 + assert_stringio_contains_lines(stringio, """ + + !!! ValueError: 0 !!! + """) + +class TestBaseReporter: + def test_dispatch_to_matching_method(self): + l = [] + class MyReporter(BaseReporter): + def rep_TestrunStart(self, ev): + l.append(ev) + rep = MyReporter() + ev = event.TestrunStart() + rep.processevent(ev) + assert len(l) == 1 + assert l[0] is ev + + def test_dispatch_to_default(self): + l = [] + class MyReporter(BaseReporter): + def rep(self, ev): + l.append(ev) + rep = MyReporter() + ev = event.NOP() + rep.processevent(ev) + assert len(l) == 1 + assert l[0] is ev + + def test_TestItemReport_one(self): + for outcome in 'passed skipped failed'.split(): + rep = BaseReporter() + ev = event.ItemTestReport(None) + setattr(ev, outcome, True) + rep.processevent(ev) + assert getattr(rep, '_' + outcome) == [ev] + + def test_CollectionReport_events_are_counted(self): + for outcome in 'skipped failed'.split(): + rep = BaseReporter() + ev = event.CollectionReport(None, None) + setattr(ev, outcome, True) + rep.processevent(ev) + assert getattr(rep, '_' + outcome) == [ev] + + def test_skip_reasons_folding(self): + rep = BaseReporter() + class longrepr: + class reprcrash: + path = 'xyz' + lineno = 3 + message = "justso" + + ev1 = event.CollectionReport(None, None) + ev1.when = "execute" + ev1.skipped = True + ev1.longrepr = longrepr + + ev2 = event.ItemTestReport(None, excinfo=longrepr) + ev2.skipped = True + rep.processevent(ev1) + rep.processevent(ev2) + assert len(rep._skipped) == 2 + l = rep._folded_skips() + assert len(l) == 1 + num, fspath, lineno, reason = l[0] + assert num == 2 + assert fspath == longrepr.reprcrash.path + assert lineno == longrepr.reprcrash.lineno + assert reason == longrepr.reprcrash.message + +def test_repr_python_version(): + py.magic.patch(sys, 'version_info', (2, 5, 1, 'final', 0)) + try: + assert repr_pythonversion() == "2.5.1-final-0" + py.std.sys.version_info = x = (2,3) + assert repr_pythonversion() == str(x) + finally: + py.magic.revert(sys, 'version_info') + +def test_getrelpath(): + curdir = py.path.local() + sep = curdir.sep + s = getrelpath(curdir, curdir.join("hello", "world")) + assert s == "hello" + sep + "world" + + s = getrelpath(curdir, curdir.dirpath().join("sister")) + assert s == ".." + sep + "sister" + assert getrelpath(curdir, curdir.dirpath()) == ".." + + assert getrelpath(curdir, "hello") == "hello" Added: py/branch/pytestplugin/py/test/plugin/pytest_xfail.py ============================================================================== --- (empty file) +++ py/branch/pytestplugin/py/test/plugin/pytest_xfail.py Sat Jan 24 19:50:03 2009 @@ -0,0 +1,62 @@ +""" +py.test xfail plugin for marking and reporting "expected to fail" tests. + +example usage: + + @py.test.xfail + def test_hello(): + ... + assert 0 + +you can also provide meta information + @py.test.xfail("issue97") + def test_hello(): + ... + assert 0 + + +plugin should work for distributed in a distributed setting as well. +for now, you will need to manually install the plugin remotely. +XXX see to transfer plugin to the remote side. +""" + +def xfail(func=None, reason=None): + def xfaildecorator(func): + func.reason = reason + return func + if func is not None: + return xfaildecorator(func) + return xfaildecorator + +class Xfail: + version = "0.1" + + def pytest_namespace_extension(self): + return {'xfail': xfail} + + def pytest_report_event(self, resultevent): + """ return category/verbose and shortletter. """ + if 'xfail' in resultevent.funcdict: + if resultevent.failed: + return "xfailed", "x" + else: + return "passed_xfailed", "P" + + #def pytest_terminal_summary_category_sort(self, name1, name2): + # ... + + def pytest_terminal_summary_category(self, terminalreporter, categoryname, events): + if categoryname == "xfailed": + return True # print nothing for tests that failed and were expected to fail + elif category.name == "passed_xfailed": + self.write_sep("=", "unexpectedly passing tests") + for ev in events: + ev.toterminal(self._tw) # XXX somehow list all passing tests + +# =============================================================================== +# +# plugin tests +# +# =============================================================================== + +from py.__.test.testing import plugintester From hpk at codespeak.net Sat Jan 24 19:59:31 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 24 Jan 2009 19:59:31 +0100 (CET) Subject: [py-svn] r61312 - py/branch/pytestplugin/py/test/plugin Message-ID: <20090124185931.77CEF16853E@codespeak.net> Author: hpk Date: Sat Jan 24 19:59:31 2009 New Revision: 61312 Modified: py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py Log: remove redundant docstring Modified: py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py Sat Jan 24 19:59:31 2009 @@ -1,6 +1,3 @@ -""" - eventlog plugin for logging pytest events to a file. -""" import py class Eventlog: From hpk at codespeak.net Sat Jan 24 20:16:23 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 24 Jan 2009 20:16:23 +0100 (CET) Subject: [py-svn] r61314 - py/branch/pytestplugin/py/test/plugin Message-ID: <20090124191623.37FB1168509@codespeak.net> Author: hpk Date: Sat Jan 24 20:16:22 2009 New Revision: 61314 Modified: py/branch/pytestplugin/py/test/plugin/pytest_xfail.py Log: the xfail plugin would like to use the xfile plugin ... Modified: py/branch/pytestplugin/py/test/plugin/pytest_xfail.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_xfail.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_xfail.py Sat Jan 24 20:16:22 2009 @@ -1,42 +1,16 @@ """ py.test xfail plugin for marking and reporting "expected to fail" tests. - -example usage: - - @py.test.xfail - def test_hello(): - ... - assert 0 - -you can also provide meta information - @py.test.xfail("issue97") + @py.test.funcdict(xfail="needs refactoring") def test_hello(): ... assert 0 - - -plugin should work for distributed in a distributed setting as well. -for now, you will need to manually install the plugin remotely. -XXX see to transfer plugin to the remote side. """ +import py -def xfail(func=None, reason=None): - def xfaildecorator(func): - func.reason = reason - return func - if func is not None: - return xfaildecorator(func) - return xfaildecorator - class Xfail: - version = "0.1" - - def pytest_namespace_extension(self): - return {'xfail': xfail} - - def pytest_report_event(self, resultevent): + def pytest_progress_resultevent(self, resultevent): """ return category/verbose and shortletter. """ - if 'xfail' in resultevent.funcdict: + if 'xfail' in resultevent.funcdict: #the if resultevent.failed: return "xfailed", "x" else: @@ -45,6 +19,7 @@ #def pytest_terminal_summary_category_sort(self, name1, name2): # ... + # a hook implemented called by the terminalreporter instance/plugin def pytest_terminal_summary_category(self, terminalreporter, categoryname, events): if categoryname == "xfailed": return True # print nothing for tests that failed and were expected to fail @@ -60,3 +35,10 @@ # =============================================================================== from py.__.test.testing import plugintester + +def test_generic(): + py.test.skip("implement various bits above first") + impname = "py.__.test.plugin.pytest_xfail" + plugintester.nocalls(impname) + tmpdir = plugintester.functional("py.__.test.plugin.pytest_xfail") + # XXX From hpk at codespeak.net Sat Jan 24 20:55:04 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 24 Jan 2009 20:55:04 +0100 (CET) Subject: [py-svn] r61315 - in py/branch/pytestplugin/py: . test test/plugin test/testing Message-ID: <20090124195504.D5A9C168544@codespeak.net> Author: hpk Date: Sat Jan 24 20:55:02 2009 New Revision: 61315 Modified: py/branch/pytestplugin/py/__init__.py py/branch/pytestplugin/py/test/event.py py/branch/pytestplugin/py/test/outcome.py py/branch/pytestplugin/py/test/plugin/pytest_xfail.py py/branch/pytestplugin/py/test/testing/test_runner_functional.py Log: work out xfail plugin some more, start implementing it by means of introducing py.test.funcattr decorator Modified: py/branch/pytestplugin/py/__init__.py ============================================================================== --- py/branch/pytestplugin/py/__init__.py (original) +++ py/branch/pytestplugin/py/__init__.py Sat Jan 24 20:55:02 2009 @@ -65,6 +65,7 @@ # helpers for use from test functions or collectors 'test.__doc__' : ('./test/__init__.py', '__doc__'), 'test.raises' : ('./test/outcome.py', 'raises'), + 'test.funcattr' : ('./test/outcome.py', 'funcattr',), 'test.deprecated_call' : ('./test/outcome.py', 'deprecated_call'), 'test.skip' : ('./test/outcome.py', 'skip'), 'test.importorskip' : ('./test/outcome.py', 'importorskip'), Modified: py/branch/pytestplugin/py/test/event.py ============================================================================== --- py/branch/pytestplugin/py/test/event.py (original) +++ py/branch/pytestplugin/py/test/event.py Sat Jan 24 20:55:02 2009 @@ -81,13 +81,14 @@ longrepr.toterminal(out) else: out.line(str(longrepr)) - + class ItemTestReport(BaseReport): """ Test Execution Report. """ failed = passed = skipped = False def __init__(self, colitem, excinfo=None, when=None, outerr=None): self.colitem = colitem + self.funcattr = getattr(colitem.obj, 'func_dict', {}) if not excinfo: self.passed = True self.shortrepr = "." Modified: py/branch/pytestplugin/py/test/outcome.py ============================================================================== --- py/branch/pytestplugin/py/test/outcome.py (original) +++ py/branch/pytestplugin/py/test/outcome.py Sat Jan 24 20:55:02 2009 @@ -139,6 +139,13 @@ raise AssertionError("%r did not produce DeprecationWarning" %(func,)) return ret +class funcattr: + """ decorator for setting function attributes. """ + def __init__(self, **kw): + self.kw = kw + def __call__(self, func): + func.func_dict.update(self.kw) + return func # exitcodes for the command line EXIT_OK = 0 Modified: py/branch/pytestplugin/py/test/plugin/pytest_xfail.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_xfail.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_xfail.py Sat Jan 24 20:55:02 2009 @@ -1,6 +1,6 @@ """ py.test xfail plugin for marking and reporting "expected to fail" tests. - @py.test.funcdict(xfail="needs refactoring") + @py.test.setattr(xfail="needs refactoring") def test_hello(): ... assert 0 Modified: py/branch/pytestplugin/py/test/testing/test_runner_functional.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_runner_functional.py (original) +++ py/branch/pytestplugin/py/test/testing/test_runner_functional.py Sat Jan 24 20:55:02 2009 @@ -5,6 +5,15 @@ from py.__.code.excinfo import ReprExceptionInfo class BaseTests(InlineCollection): + def test_funcattr(self): + ev = self.runitem(""" + import py + @py.test.funcattr(xfail="needs refactoring") + def test_func(): + raise Exit() + """) + assert ev.funcattr['xfail'] == "needs refactoring" + def test_passfunction(self): ev = self.runitem(""" def test_func(): @@ -172,6 +181,7 @@ else: py.test.fail("did not raise") + class TestExecutionNonForked(BaseTests): def getrunner(self): return basic_run_report From hpk at codespeak.net Sat Jan 24 21:32:54 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 24 Jan 2009 21:32:54 +0100 (CET) Subject: [py-svn] r61319 - in py/branch/pytestplugin/py: . doc test test/plugin test/testing Message-ID: <20090124203254.A7ED5168541@codespeak.net> Author: hpk Date: Sat Jan 24 21:32:51 2009 New Revision: 61319 Modified: py/branch/pytestplugin/py/__init__.py py/branch/pytestplugin/py/doc/impl-test.txt py/branch/pytestplugin/py/test/collect.py py/branch/pytestplugin/py/test/event.py py/branch/pytestplugin/py/test/outcome.py py/branch/pytestplugin/py/test/plugin/pytest_terminal.py py/branch/pytestplugin/py/test/plugin/pytest_xfail.py py/branch/pytestplugin/py/test/pycollect.py py/branch/pytestplugin/py/test/testing/test_runner_functional.py Log: re-use the concept of keywords for writing plugins Modified: py/branch/pytestplugin/py/__init__.py ============================================================================== --- py/branch/pytestplugin/py/__init__.py (original) +++ py/branch/pytestplugin/py/__init__.py Sat Jan 24 21:32:51 2009 @@ -65,7 +65,7 @@ # helpers for use from test functions or collectors 'test.__doc__' : ('./test/__init__.py', '__doc__'), 'test.raises' : ('./test/outcome.py', 'raises'), - 'test.funcattr' : ('./test/outcome.py', 'funcattr',), + 'test.keywords' : ('./test/outcome.py', 'keywords',), 'test.deprecated_call' : ('./test/outcome.py', 'deprecated_call'), 'test.skip' : ('./test/outcome.py', 'skip'), 'test.importorskip' : ('./test/outcome.py', 'importorskip'), Modified: py/branch/pytestplugin/py/doc/impl-test.txt ============================================================================== --- py/branch/pytestplugin/py/doc/impl-test.txt (original) +++ py/branch/pytestplugin/py/doc/impl-test.txt Sat Jan 24 21:32:51 2009 @@ -288,8 +288,8 @@ PYTEST_PLUGINS_REQUIRED=pytest_plugin1,pytest_plugin2 -pytest loads all plugin requirements from all conftest and environment settings -that it discovers. Note that there is no guaranteed order of initialization. +pytest loads all plugins from all conftest and environment settings +that it discovers. There is no guaranteed order of initialization. Rules for writing plugins ------------------------------ @@ -310,14 +310,20 @@ XXX Various reporting events. +keywords +----------- +XXX doc py.test.keywords somewhere + Example plugins ----------------------- XXX here are a few existing plugins: -* adding new reporting facilities, e.g. +* adding reporting facilities, e.g. + pytest_terminal: default reporter for writing info to terminals pytest_resultlog: log test results in machine-readable form to a file pytest_eventlog: log all internal pytest events to a file + pytest_xfail: * extending test execution, e.g. pytest_apigen: tracing values of function/method calls when running tests Modified: py/branch/pytestplugin/py/test/collect.py ============================================================================== --- py/branch/pytestplugin/py/test/collect.py (original) +++ py/branch/pytestplugin/py/test/collect.py Sat Jan 24 21:32:51 2009 @@ -200,6 +200,9 @@ cur = next return cur + def readkeywords(self): + return dict([(x, True) for x in self._keywords()]) + def _keywords(self): return [self.name] @@ -284,7 +287,6 @@ return repr repr_failure = _repr_failure_py - shortfailurerepr = "F" class Collector(Node): Modified: py/branch/pytestplugin/py/test/event.py ============================================================================== --- py/branch/pytestplugin/py/test/event.py (original) +++ py/branch/pytestplugin/py/test/event.py Sat Jan 24 21:32:51 2009 @@ -88,7 +88,7 @@ def __init__(self, colitem, excinfo=None, when=None, outerr=None): self.colitem = colitem - self.funcattr = getattr(colitem.obj, 'func_dict', {}) + self.keywords = colitem and colitem.readkeywords() if not excinfo: self.passed = True self.shortrepr = "." Modified: py/branch/pytestplugin/py/test/outcome.py ============================================================================== --- py/branch/pytestplugin/py/test/outcome.py (original) +++ py/branch/pytestplugin/py/test/outcome.py Sat Jan 24 21:32:51 2009 @@ -139,7 +139,7 @@ raise AssertionError("%r did not produce DeprecationWarning" %(func,)) return ret -class funcattr: +class keywords: """ decorator for setting function attributes. """ def __init__(self, **kw): self.kw = kw Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Sat Jan 24 21:32:51 2009 @@ -334,7 +334,6 @@ for ev in self._failed: ev.toterminal(self.out) -Reporter = CollectonlyReporter def repr_pythonversion(v=None): if v is None: v = sys.version_info Modified: py/branch/pytestplugin/py/test/plugin/pytest_xfail.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_xfail.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_xfail.py Sat Jan 24 21:32:51 2009 @@ -1,6 +1,6 @@ """ py.test xfail plugin for marking and reporting "expected to fail" tests. - @py.test.setattr(xfail="needs refactoring") + @py.test.keywords(xfail="needs refactoring") def test_hello(): ... assert 0 @@ -10,7 +10,7 @@ class Xfail: def pytest_progress_resultevent(self, resultevent): """ return category/verbose and shortletter. """ - if 'xfail' in resultevent.funcdict: #the + if 'xfail' in resultevent.keywords: #the if resultevent.failed: return "xfailed", "x" else: Modified: py/branch/pytestplugin/py/test/pycollect.py ============================================================================== --- py/branch/pytestplugin/py/test/pycollect.py (original) +++ py/branch/pytestplugin/py/test/pycollect.py Sat Jan 24 21:32:51 2009 @@ -309,6 +309,11 @@ if callobj is not _dummy: self._obj = callobj + def readkeywords(self): + d = super(Function, self).readkeywords() + d.update(self.obj.func_dict) + return d + def runtest(self): """ execute the given test function. """ if not self._deprecated_testexecution(): Modified: py/branch/pytestplugin/py/test/testing/test_runner_functional.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_runner_functional.py (original) +++ py/branch/pytestplugin/py/test/testing/test_runner_functional.py Sat Jan 24 21:32:51 2009 @@ -8,11 +8,11 @@ def test_funcattr(self): ev = self.runitem(""" import py - @py.test.funcattr(xfail="needs refactoring") + @py.test.keywords(xfail="needs refactoring") def test_func(): raise Exit() """) - assert ev.funcattr['xfail'] == "needs refactoring" + assert ev.keywords['xfail'] == "needs refactoring" def test_passfunction(self): ev = self.runitem(""" From hpk at codespeak.net Sat Jan 24 22:35:10 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 24 Jan 2009 22:35:10 +0100 (CET) Subject: [py-svn] r61320 - in py/branch/pytestplugin/py/test: . plugin testing Message-ID: <20090124213510.38CD116853D@codespeak.net> Author: hpk Date: Sat Jan 24 22:35:07 2009 New Revision: 61320 Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py py/branch/pytestplugin/py/test/plugin/pytest_xfail.py py/branch/pytestplugin/py/test/pmanage.py py/branch/pytestplugin/py/test/testing/plugintester.py py/branch/pytestplugin/py/test/testing/test_pmanage.py Log: start implementing neccessary support and hooks for xfail plugin, enable generic tests Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Sat Jan 24 22:35:07 2009 @@ -127,6 +127,12 @@ self.ensure_newline() self._tw.sep(sep, title, **markup) + def getoutcomeletterword(self, event): + result = self.config.pluginmanager.callfirst("pytest_report_teststatus", event=event) + if result is not None: + return result + return self.getoutcomeletter(event), self.getoutcomeword(event) + def getoutcomeletter(self, event): return event.shortrepr @@ -183,12 +189,12 @@ def rep_ItemTestReport(self, ev): super(TerminalReporter, self).rep_ItemTestReport(ev) fspath = ev.colitem.fspath + letter, word = self.getoutcomeletterword(ev) if not self.config.option.verbose: - self.write_fspath_result(fspath, self.getoutcomeletter(ev)) + self.write_fspath_result(fspath, letter) else: info = ev.colitem.repr_metainfo() line = info.verboseline(basedir=self.curdir) + " " - word = self.getoutcomeword(ev) self.write_ensure_prefix(line, word) def rep_CollectionReport(self, ev): Modified: py/branch/pytestplugin/py/test/plugin/pytest_xfail.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_xfail.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_xfail.py Sat Jan 24 22:35:07 2009 @@ -7,20 +7,20 @@ """ import py -class Xfail: - def pytest_progress_resultevent(self, resultevent): +class Xfail(object): + def pytest_report_teststatus(self, event): """ return category/verbose and shortletter. """ if 'xfail' in resultevent.keywords: #the if resultevent.failed: - return "xfailed", "x" + return "x", "xfailed" else: - return "passed_xfailed", "P" + return "P", "passed_xfailed" #def pytest_terminal_summary_category_sort(self, name1, name2): # ... # a hook implemented called by the terminalreporter instance/plugin - def pytest_terminal_summary_category(self, terminalreporter, categoryname, events): + def xxx_pytest_terminal_summary_category(self, terminalreporter, categoryname, events): if categoryname == "xfailed": return True # print nothing for tests that failed and were expected to fail elif category.name == "passed_xfailed": @@ -37,7 +37,6 @@ from py.__.test.testing import plugintester def test_generic(): - py.test.skip("implement various bits above first") impname = "py.__.test.plugin.pytest_xfail" plugintester.nocalls(impname) tmpdir = plugintester.functional("py.__.test.plugin.pytest_xfail") Modified: py/branch/pytestplugin/py/test/pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/pmanage.py (original) +++ py/branch/pytestplugin/py/test/pmanage.py Sat Jan 24 22:35:07 2009 @@ -9,6 +9,7 @@ class PluginManager(object): def __init__(self): + # XXX turn this into ordered dict? self._plugins = {} def addpluginclass(self, pluginclass): @@ -56,10 +57,17 @@ config.addoptions("ungrouped options added by plugins", *opts) def callplugins(self, methname, **args): - """ call method with the given name for each plugin. pass along kwargs. """ + """ call all plugins with the given method name and args. """ for name, method in self.listattr(methname): method(**args) + def callfirst(self, methname, **args): + """ call all plugins with the given method name and args. """ + for name, method in self.listattr(methname): + res = method(**args) + if res is not None: + return res + def forward_event(self, event): self.callplugins("pytest_event", event=event) Modified: py/branch/pytestplugin/py/test/testing/plugintester.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/plugintester.py (original) +++ py/branch/pytestplugin/py/test/testing/plugintester.py Sat Jan 24 22:35:07 2009 @@ -12,6 +12,7 @@ callhooklist = [ ("pytest_configure", "config"), ("pytest_unconfigure", "config"), + ("pytest_report_teststatus", "event"), ("pytest_event", "event"), ] plugin_pytest_methods = dict([item for item in vars(plugin.__class__).items() @@ -51,7 +52,7 @@ setupfs(tmpdir) config.parse([tmpdir] + list(cmdlineopts)) try: - config.pluginmanager.import_plugin(pname) + config.pluginmanager.import_plugin(impname) except ValueError: pass # already registered plugin = config.pluginmanager.getplugin(pname) Modified: py/branch/pytestplugin/py/test/testing/test_pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_pmanage.py (original) +++ py/branch/pytestplugin/py/test/testing/test_pmanage.py Sat Jan 24 22:35:07 2009 @@ -80,6 +80,26 @@ pm.callplugins("method", arg=42) py.test.raises(TypeError, 'pm.callplugins("method", arg=42, s=13)') + def test_callfirst(self): + pm = PluginManager() + class My1: + def method(self): + pass + class My2: + def method(self): + return True + class My3: + def method(self): + return None + assert pm.callfirst("method") is None + assert pm.callfirst("methodnotexists") is None + pm.addpluginclass(My1) + assert pm.callfirst("method") is None + pm.addpluginclass(My2) + assert pm.callfirst("method") == True + pm.addpluginclass(My3) + assert pm.callfirst("method") == True + def test_listattr(self): pm = PluginManager() class My2: From py-svn at codespeak.net Sun Jan 25 04:33:31 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Sun, 25 Jan 2009 04:33:31 +0100 (CET) Subject: [py-svn] Massive job cuts Message-ID: <20090125033331.56EDA168532@codespeak.net> An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Mon Jan 26 02:38:27 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Mon, 26 Jan 2009 02:38:27 +0100 (CET) Subject: [py-svn] The best kind of natural high Message-ID: <20090126013827.EC0B61684D1@codespeak.net> An HTML attachment was scrubbed... URL: From hpk at codespeak.net Mon Jan 26 16:36:00 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 26 Jan 2009 16:36:00 +0100 (CET) Subject: [py-svn] r61357 - in py/branch/pytestplugin/py/test: plugin testing Message-ID: <20090126153600.41AE6169E08@codespeak.net> Author: hpk Date: Mon Jan 26 16:35:59 2009 New Revision: 61357 Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py py/branch/pytestplugin/py/test/plugin/pytest_xfail.py py/branch/pytestplugin/py/test/testing/acceptance_test.py Log: using a dict for handling test result in the test reporter Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Mon Jan 26 16:35:59 2009 @@ -33,10 +33,7 @@ self._reset() def _reset(self): - self._passed = [] - self._skipped = [] - self._failed = [] - self._deselected = [] + self.status2event = {} def processevent(self, ev): evname = ev.__class__.__name__ @@ -50,30 +47,26 @@ pass def rep_ItemTestReport(self, ev): - if ev.skipped: - self._skipped.append(ev) - elif ev.failed: - self._failed.append(ev) - elif ev.passed: - self._passed.append(ev) + for name in 'skipped failed passed'.split(): + if getattr(ev, name): + self.status2event.setdefault(name, []).append(ev) + return def rep_CollectionReport(self, ev): - if ev.skipped: - self._skipped.append(ev) - elif ev.failed: - self._failed.append(ev) - else: - pass # don't record passed collections + for name in 'skipped failed'.split(): + if getattr(ev, name): + self.status2event.setdefault(name, []).append(ev) + return def rep_TestrunStart(self, ev): self._reset() def rep_Deselected(self, ev): - self._deselected.extend(ev.items) + self.status2event.setdefault('deselected', []).extend(ev.items) def _folded_skips(self): d = {} - for event in self._skipped: + for event in self.status2event.get("skipped", []): entry = event.longrepr.reprcrash key = entry.path, entry.lineno, entry.message d.setdefault(key, []).append(event) @@ -202,12 +195,9 @@ if not ev.passed: if ev.failed: msg = ev.longrepr.reprcrash.message + self.write_fspath_result(ev.colitem.fspath, "F") elif ev.skipped: - msg = "Skipped:" + ev.reason - else: - msg = None - if msg is not None: - self.write_fspath_result(ev.colitem.fspath, "- " + msg) + self.write_fspath_result(ev.colitem.fspath, "S") def rep_TestrunStart(self, ev): super(TerminalReporter, self).rep_TestrunStart(ev) @@ -257,34 +247,33 @@ # def summary_failures(self): - if self._failed and self.config.option.tbstyle != "no": + if "failed" in self.status2event and self.config.option.tbstyle != "no": self.write_sep("=", "FAILURES") - for ev in self._failed: + for ev in self.status2event['failed']: self.write_sep("_") ev.toterminal(self._tw) def summary_stats(self): session_duration = py.std.time.time() - self._sessionstarttime - numfailed = len(self._failed) - numskipped = len(self._skipped) - numpassed = len(self._passed) - sum = numfailed + numpassed - self.write_sep("=", "%d/%d passed + %d skips in %.2f seconds" % - (numpassed, sum, numskipped, session_duration), bold=True) - if numfailed == 0: - self.write_sep("=", "failures: no failures :)", green=True) - else: - self.write_sep("=", "failures: %d" %(numfailed), red=True) + + keys = "failed passed skipped deselected".split() + parts = [] + for key in keys: + if key in self.status2event: + parts.append("%d %s" %(len(self.status2event[key]), key)) + line = ", ".join(parts) + # XXX coloring + self.write_sep("=", "%s in %.2f seconds" %(line, session_duration)) def summary_deselected(self): - if not self._deselected: - return - self.write_sep("=", "%d tests deselected by %r" %( - len(self._deselected), self.config.option.keyword), bold=True) + l = self.status2event.get("deselected", None) + if l: + self.write_sep("=", "%d tests deselected by %r" %( + len(l), self.config.option.keyword), bold=True) def summary_skips(self): - if not self._failed or self.config.option.showskipsummary: + if "failed" not in self.status2event or self.config.option.showskipsummary: folded_skips = self._folded_skips() if folded_skips: self.write_sep("_", "skipped test summary") @@ -448,7 +437,7 @@ assert len(l) == 0 s = popvalue(stringio) print s - assert s.find("test_collect_fail.py - ImportError: No module named") != -1 + assert s.find("test_collect_fail.py F") != -1 rep.processevent(event.TestrunFinish()) assert_stringio_contains_lines(stringio, [ "> import xyz", @@ -671,7 +660,7 @@ ev = event.ItemTestReport(None) setattr(ev, outcome, True) rep.processevent(ev) - assert getattr(rep, '_' + outcome) == [ev] + assert rep.status2event[outcome] def test_CollectionReport_events_are_counted(self): for outcome in 'skipped failed'.split(): @@ -679,7 +668,7 @@ ev = event.CollectionReport(None, None) setattr(ev, outcome, True) rep.processevent(ev) - assert getattr(rep, '_' + outcome) == [ev] + assert rep.status2event[outcome] def test_skip_reasons_folding(self): rep = BaseReporter() @@ -698,7 +687,7 @@ ev2.skipped = True rep.processevent(ev1) rep.processevent(ev2) - assert len(rep._skipped) == 2 + assert len(rep.status2event['skipped']) == 2 l = rep._folded_skips() assert len(l) == 1 num, fspath, lineno, reason = l[0] Modified: py/branch/pytestplugin/py/test/plugin/pytest_xfail.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_xfail.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_xfail.py Mon Jan 26 16:35:59 2009 @@ -9,12 +9,12 @@ class Xfail(object): def pytest_report_teststatus(self, event): - """ return category/verbose and shortletter. """ - if 'xfail' in resultevent.keywords: #the + """ return shortletter and verbose word. """ + if 'xfail' in resultevent.keywords: if resultevent.failed: - return "x", "xfailed" + return "passed", "x", "xfail" else: - return "P", "passed_xfailed" + return "failed", "P", "xpass" #def pytest_terminal_summary_category_sort(self, name1, name2): # ... Modified: py/branch/pytestplugin/py/test/testing/acceptance_test.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/acceptance_test.py (original) +++ py/branch/pytestplugin/py/test/testing/acceptance_test.py Mon Jan 26 16:35:59 2009 @@ -128,7 +128,7 @@ result = self.runpytest() extra = assert_lines_contain_lines(result.outlines, [ "*test_one.py ss", - "*test_two.py - Skipped*", + "*test_two.py S", "___* skipped test summary *_", "*conftest.py:3: *3* Skipped: 'test'", ]) @@ -180,7 +180,7 @@ old.chdir() extra = assert_lines_contain_lines(result.outlines, [ "test_one.py ..", - "* failures: no failures*", + "* 2 pass*", ]) assert result.ret == 0 @@ -196,8 +196,7 @@ "*localhost* %s %s - Python %s*" %( py.std.sys.platform, py.std.sys.executable, verinfo), "*test_one.py .", - "=* 1/1 passed + 0 skips in *.[0-9][0-9] seconds *=", - "=* no failures :)*=", + "=* 1 passed in *.[0-9][0-9] seconds *=", ]) def test_traceback_failure(self): @@ -305,8 +304,7 @@ "HOSTUP: localhost*Python*", #"HOSTUP: localhost*Python*", #"HOSTUP: localhost*Python*", - "*1/3 passed + 1 skip*", - "*failures: 2*", + "*2 failed, 1 passed, 1 skipped*", ]) assert result.ret == 1 @@ -341,8 +339,7 @@ "*localhost*Python*", "*localhost*Python*", "HostDown*localhost*TERMINATED*", - "*1/4 passed + 1 skip*", - "*failures: 3*", + "*3 failed, 1 passed, 1 skipped*" ]) assert result.ret == 1 @@ -358,7 +355,7 @@ assert_lines_contain_lines(result.outlines, [ #"*test_inter() INTERRUPTED", "*KEYBOARD INTERRUPT*", - "*0/1 passed*", + "*1 failed*", ]) def test_verbose_reporting(self): @@ -414,7 +411,7 @@ child.expect(".*hello.*") child.expect("(Pdb)") child.sendeof() - child.expect("failures: 1") + child.expect("1 failed") if child.isalive(): child.wait() @@ -430,13 +427,13 @@ child.timeout = EXPECTTIMEOUT child.expect("assert 1 == 0") child.expect("test_one.py:") - child.expect("failures: 1") + child.expect("1 failed") child.expect("waiting for changes") test_one.write(py.code.Source(""" def test_1(): assert 1 == 1 """)) child.expect("MODIFIED.*test_one.py", timeout=4.0) - child.expect("failures: no failures", timeout=5.0) + child.expect("1 passed", timeout=5.0) child.kill(15) From hpk at codespeak.net Mon Jan 26 18:07:30 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 26 Jan 2009 18:07:30 +0100 (CET) Subject: [py-svn] r61368 - in py/branch/pytestplugin/py/test: plugin testing Message-ID: <20090126170730.26AD7168576@codespeak.net> Author: hpk Date: Mon Jan 26 18:07:29 2009 New Revision: 61368 Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py py/branch/pytestplugin/py/test/plugin/pytest_xfail.py py/branch/pytestplugin/py/test/testing/plugintester.py Log: xfail plugin begins to work and pass an acceptance test. Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Mon Jan 26 18:07:29 2009 @@ -120,11 +120,14 @@ self.ensure_newline() self._tw.sep(sep, title, **markup) - def getoutcomeletterword(self, event): + def getcategoryletterword(self, event): result = self.config.pluginmanager.callfirst("pytest_report_teststatus", event=event) if result is not None: return result - return self.getoutcomeletter(event), self.getoutcomeword(event) + for cat in 'skipped failed passed ???'.split(): + if getattr(event, cat, None): + break + return cat, self.getoutcomeletter(event), self.getoutcomeword(event) def getoutcomeletter(self, event): return event.shortrepr @@ -180,9 +183,10 @@ self.write_sep("!", "RESCHEDULING %s " %(ev.items,)) def rep_ItemTestReport(self, ev): - super(TerminalReporter, self).rep_ItemTestReport(ev) + #super(TerminalReporter, self).rep_ItemTestReport(ev) fspath = ev.colitem.fspath - letter, word = self.getoutcomeletterword(ev) + cat, letter, word = self.getcategoryletterword(ev) + self.status2event.setdefault(cat, []).append(ev) if not self.config.option.verbose: self.write_fspath_result(fspath, letter) else: @@ -210,6 +214,7 @@ if ev.exitstatus in (0, 1, 2): self.summary_failures() self.summary_skips() + self.config.pluginmanager.callplugins("pytest_terminal_summary", terminalreporter=self) if ev.excrepr is not None: self.summary_final_exc(ev.excrepr) if ev.exitstatus == 2: Modified: py/branch/pytestplugin/py/test/plugin/pytest_xfail.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_xfail.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_xfail.py Mon Jan 26 18:07:29 2009 @@ -8,25 +8,32 @@ import py class Xfail(object): + def pytest_report_teststatus(self, event): """ return shortletter and verbose word. """ - if 'xfail' in resultevent.keywords: - if resultevent.failed: - return "passed", "x", "xfail" + if 'xfail' in event.keywords: + if event.failed: + return "xfailed", "x", "xfail" else: - return "failed", "P", "xpass" + return "xpassed", "P", "xpass" - #def pytest_terminal_summary_category_sort(self, name1, name2): - # ... - # a hook implemented called by the terminalreporter instance/plugin - def xxx_pytest_terminal_summary_category(self, terminalreporter, categoryname, events): - if categoryname == "xfailed": - return True # print nothing for tests that failed and were expected to fail - elif category.name == "passed_xfailed": - self.write_sep("=", "unexpectedly passing tests") - for ev in events: - ev.toterminal(self._tw) # XXX somehow list all passing tests + def pytest_terminal_summary(self, terminalreporter): + tr = terminalreporter + xfailed = tr.status2event.get("xfailed") + if xfailed: + tr.write_sep("_", "EXPECTED XFAILURES") + for event in xfailed: + entry = event.longrepr.reprcrash + key = entry.path, entry.lineno, entry.message + reason = event.longrepr.reprcrash.message + tr._tw.line("%s:%d: %s" %(entry.path, entry.lineno, entry.message)) + + xpassed = terminalreporter.status2event.get("xpassed") + if xpassed: + tr.write_sep("_", "UNEXPECTEDLY PASSING") + for event in xpassed: + tr._tw.line("%s: xpassed" %(event.colitem,)) # =============================================================================== # @@ -39,5 +46,21 @@ def test_generic(): impname = "py.__.test.plugin.pytest_xfail" plugintester.nocalls(impname) - tmpdir = plugintester.functional("py.__.test.plugin.pytest_xfail") - # XXX + +from py.__.test.testing.acceptance_test import AcceptBase, assert_lines_contain_lines + +class TestXFailAcceptance(AcceptBase): + def test_xfail(self): + p = self.makepyfile(test_one=""" + import py + @py.test.keywords(xfail=True) + def test_this(): + assert 0 + """) + self.makepyfile(conftest="""pytest_plugins_required='py.__.test.plugin.pytest_xfail',""") + result = self.runpytest(p) + extra = assert_lines_contain_lines(result.outlines, [ + "*XFAILURES*", + "*test_one.py:4: assert 0*", + ]) + assert result.ret == 1 Modified: py/branch/pytestplugin/py/test/testing/plugintester.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/plugintester.py (original) +++ py/branch/pytestplugin/py/test/testing/plugintester.py Mon Jan 26 18:07:29 2009 @@ -13,6 +13,7 @@ ("pytest_configure", "config"), ("pytest_unconfigure", "config"), ("pytest_report_teststatus", "event"), + ("pytest_terminal_summary", "terminalreporter"), ("pytest_event", "event"), ] plugin_pytest_methods = dict([item for item in vars(plugin.__class__).items() From py-svn at codespeak.net Tue Jan 27 02:33:21 2009 From: py-svn at codespeak.net (py-svn at codespeak.net) Date: Tue, 27 Jan 2009 02:33:21 +0100 (CET) Subject: [py-svn] Semi-hard is just not good enough Message-ID: <20090127013321.BFD251684A2@codespeak.net> An HTML attachment was scrubbed... URL: From hpk at codespeak.net Tue Jan 27 16:03:41 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 27 Jan 2009 16:03:41 +0100 (CET) Subject: [py-svn] r61391 - in py/branch/pytestplugin/py/test: . plugin testing Message-ID: <20090127150341.C8F56169DB5@codespeak.net> Author: hpk Date: Tue Jan 27 16:03:39 2009 New Revision: 61391 Added: py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py (contents, props changed) Modified: py/branch/pytestplugin/py/test/plugin/pytest_xfail.py py/branch/pytestplugin/py/test/pmanage.py py/branch/pytestplugin/py/test/pycollect.py py/branch/pytestplugin/py/test/testing/test_pmanage.py py/branch/pytestplugin/py/test/testing/test_runner_functional.py Log: * plugins now can provide arguments for test functions * a * simple pytest_tmpdir plugin showcases this functionality Added: py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py ============================================================================== --- (empty file) +++ py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py Tue Jan 27 16:03:39 2009 @@ -0,0 +1,44 @@ +""" +example: + + pytest_plugins_required = "pytest_tmpdir" + + def test_plugin(tmpdir): + tmpdir.join("hello").write("hello") + +""" +import py + +class Tmpdir: + """ pytest plugin for providing temporary directories + to test functions and methods. + """ + + def pytest_pyitemexecute_arg(self, pyfuncitem, argname): + """ return (value, finalizer) tuple or None. + + the value will be provided to the test function. + the finalizer (if not None) will be called after the test + function has been executed (i.e. pyfuncitem.execute() returns). + """ + if argname == "tmpdir": + basename = "_".join(pyfuncitem.listnames()) + # move to config + return py.test.ensuretemp(basename), None + +# =============================================================================== +# +# plugin tests +# +# =============================================================================== + +def test_pyitemexecute_arg(): + class Pseudoitem: + def listnames(self): + return ['a', 'b'] + t = Tmpdir() + p, finalizer = t.pytest_pyitemexecute_arg(Pseudoitem(), "tmpdir") + assert finalizer is None + assert p.check() + assert p.basename == "a_b" + assert t.pytest_pyitemexecute_arg(Pseudoitem(), "something") is None Modified: py/branch/pytestplugin/py/test/plugin/pytest_xfail.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_xfail.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_xfail.py Tue Jan 27 16:03:39 2009 @@ -8,7 +8,6 @@ import py class Xfail(object): - def pytest_report_teststatus(self, event): """ return shortletter and verbose word. """ if 'xfail' in event.keywords: Modified: py/branch/pytestplugin/py/test/pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/pmanage.py (original) +++ py/branch/pytestplugin/py/test/pmanage.py Tue Jan 27 16:03:39 2009 @@ -15,30 +15,42 @@ def addpluginclass(self, pluginclass): name = pluginclass.__name__.lower() if name in self._plugins: - raise ValueError("plugin with name %r aready loaded" % name) - self.trace("instantiating and adding: %s" %(pluginclass,)) - self._plugins[name] = pluginclass() + self.trace("plugin with name %r aready loaded" % name) + return + plugin = pluginclass() + if hasattr(self, '_configured') and hasattr(plugin, 'pytest_configure'): + plugin.pytest_configure(config=self._configured) + self.trace("instantiating and configuring: %s" %(pluginclass,)) + else: + self.trace("instantiating plugin: %s" %(pluginclass,)) + self._plugins[name] = plugin def trace(self, msg): if DEBUG: print >>py.std.sys.stderr, msg - def import_plugin(self, importname): - prefix = "pytest_" - lastpart = importname.split(".")[-1] - if not lastpart.startswith(prefix): - raise ValueError("in importname %r: %r does not start with %r" %( - importname, lastpart, prefix)) - mod = __import__(importname, None, None, "__doc__") - clsname = lastpart[len(prefix):].capitalize() - pluginclass = getattr(mod, clsname) - self.addpluginclass(pluginclass) + def import_plugin(self, importspec): + if not isinstance(importspec, basestring): + self.addpluginclass(importspec) + else: + prefix = "pytest_" + lastpart = importspec.split(".")[-1] + if not lastpart.startswith(prefix): + raise ValueError("in importspec %r: %r does not start with %r" %( + importspec, lastpart, prefix)) + mod = __import__(importspec, None, None, "__doc__") + clsname = lastpart[len(prefix):].capitalize() + pluginclass = getattr(mod, clsname) + self.addpluginclass(pluginclass) def getplugin(self, pname): return self._plugins[pname.lower()] def consider_module(self, mod): - for spec in getattr(mod, pytest_plugins_required, ()): + attr = getattr(mod, pytest_plugins_required, ()) + if isinstance(attr, str): + attr = (attr,) + for spec in attr: if spec: self.import_plugin(spec) @@ -82,11 +94,10 @@ return l def configure(self, config): - # XXX if we want to allow post-configure addition of - # plugins this logic here has to be refined assert self.forward_event not in config.bus._subscribers config.bus._subscribers.append(self.forward_event) self.callplugins("pytest_configure", config=config) + self._configured = config def unconfigure(self, config): self.callplugins("pytest_unconfigure", config=config) Modified: py/branch/pytestplugin/py/test/pycollect.py ============================================================================== --- py/branch/pytestplugin/py/test/pycollect.py (original) +++ py/branch/pytestplugin/py/test/pycollect.py Tue Jan 27 16:03:39 2009 @@ -159,7 +159,14 @@ return super(Module, self).collect() def _getobj(self): - return self._memoizedcall('_obj', self.fspath.pyimport) + return self._memoizedcall('_obj', self._importtestmodule) + + def _importtestmodule(self): + # we assume we are only called once per module + mod = self.fspath.pyimport() + print "imported test module", mod + self._config.pluginmanager.consider_module(mod) + return mod def setup(self): if not self._config.option.nomagic: @@ -305,6 +312,7 @@ """ def __init__(self, name, parent=None, config=None, args=(), callobj=_dummy): super(Function, self).__init__(name, parent, config=config) + self._argfinalizers = [] self._args = args if callobj is not _dummy: self._obj = callobj @@ -317,7 +325,38 @@ def runtest(self): """ execute the given test function. """ if not self._deprecated_testexecution(): - self.obj(*self._args) + kw = self.getkwargs() + self.obj(*self._args, **kw) + + def teardown(self): + finalizers = self._argfinalizers + while finalizers: + call = finalizers.pop() + call() + super(Function, self).teardown() + + def getkwargs(self): + kwargs = {} + if not self._args: + # XXX care for yielded/generated tests as well?! + funcobj = self.obj + startindex = getattr(funcobj, 'im_self', None) and 1 or 0 + for argname in py.std.inspect.getargs(self.obj.func_code)[0][startindex:]: + res = self.fillarg(argname, kwargs) + return kwargs + + def fillarg(self, argname, kwargs): + res = self._config.pluginmanager.callfirst( + "pytest_itemexecute_arg", pyfuncitem=self, argname=argname) + if res is not None: + value, argfinalizer = res + kwargs[argname] = value + if argfinalizer is not None: + self._argfinalizers.append(argfinalizer) + else: + self._config.pluginmanager.trace( + "could not find argument %r, plugins=%r" %( + argname,self._config.pluginmanager._plugins)) def __eq__(self, other): try: Modified: py/branch/pytestplugin/py/test/testing/test_pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_pmanage.py (original) +++ py/branch/pytestplugin/py/test/testing/test_pmanage.py Tue Jan 27 16:03:39 2009 @@ -9,7 +9,7 @@ self.tmpdir = py.test.ensuretemp("%s.%s.%s" % (__name__, self.__class__.__name__, method.__name__)) - def test_import_plugin(self): + def test_import_plugin_importname(self): pm = PluginManager() py.test.raises(ValueError, 'pm.import_plugin("x.y")') py.test.raises(ValueError, 'pm.import_plugin("pytest_x.y")') @@ -26,6 +26,29 @@ finally: sys.path.remove(str(self.tmpdir)) + def test_import_plugin_class(self): + pm = PluginManager() + class SomePlugin: + pass + pm.import_plugin(SomePlugin) + plugin = pm.getplugin("someplugin") + assert isinstance(plugin, SomePlugin) + i = len(pm._plugins) + pm.import_plugin(SomePlugin) + assert len(pm._plugins) == i + + def test_addpluginclass_post_configure(self): + pm = PluginManager() + l = [] + class SomePlugin: + def pytest_configure(self, config): + l.append(config) + conf = pytestConfig() + pm.configure(config=conf) + pm.import_plugin(SomePlugin) + assert len(l) == 1 + assert l[0] is conf + def test_consider_module(self): pm = PluginManager() sys.path.insert(0, str(self.tmpdir)) @@ -57,7 +80,9 @@ class My: pass pm.addpluginclass(My) - py.test.raises(ValueError, "pm.addpluginclass(My)") # double registration + assert len(pm._plugins) == 1 + pm.addpluginclass(My) + assert len(pm._plugins) == 1 def test_getplugin(self): pm = PluginManager() Modified: py/branch/pytestplugin/py/test/testing/test_runner_functional.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_runner_functional.py (original) +++ py/branch/pytestplugin/py/test/testing/test_runner_functional.py Tue Jan 27 16:03:39 2009 @@ -1,10 +1,52 @@ import py -from py.__.test.testing.suptest import InlineCollection +from py.__.test.testing.suptest import InlineCollection, getItemTestReport from py.__.test.runner import basic_run_report, forked_run_report, basic_collect_report from py.__.test.runner import RobustRun from py.__.code.excinfo import ReprExceptionInfo class BaseTests(InlineCollection): + def test_getwkargs_function(self): + ev, fn = getItemTestReport(""" + import py + + provided = [] + class MyPlugin: + def pytest_itemexecute_arg(self, pyfuncitem, argname): + if argname == "myxyz": + res = 42 + provided.append(res) + return res, provided.pop + if argname == "item": + return pyfuncitem, None + + pytest_plugins_required = MyPlugin, + + def test_func(myxyz, item): + assert myxyz == 42 + assert item._argfinalizers + assert len(provided) == 1 + """) + assert ev.passed, ev.longrepr + + def test_getwkargs_boundmethod(self): + ev, fn = getItemTestReport(""" + import py + + provided = [] + class MyPlugin: + def pytest_itemexecute_arg(self, pyfuncitem, argname): + assert argname != 'self' + if argname == "hello": + return "world", None + + pytest_plugins_required = MyPlugin, + + class TestForMethod: + def test_func(self, hello): + assert hello == "world" + """) + assert ev.passed, ev.longrepr + def test_funcattr(self): ev = self.runitem(""" import py From hpk at codespeak.net Tue Jan 27 17:32:12 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 27 Jan 2009 17:32:12 +0100 (CET) Subject: [py-svn] r61399 - in py/branch/pytestplugin/py: doc test test/plugin test/testing Message-ID: <20090127163212.CD66B169E2F@codespeak.net> Author: hpk Date: Tue Jan 27 17:32:09 2009 New Revision: 61399 Modified: py/branch/pytestplugin/py/doc/impl-test.txt py/branch/pytestplugin/py/doc/test.txt py/branch/pytestplugin/py/test/defaultconftest.py py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py py/branch/pytestplugin/py/test/plugin/pytest_xfail.py py/branch/pytestplugin/py/test/pmanage.py py/branch/pytestplugin/py/test/testing/test_pmanage.py py/branch/pytestplugin/py/test/testing/test_runner_functional.py Log: document and refine specification of plugins in test modules, conftest or plugin modules themselves. Modified: py/branch/pytestplugin/py/doc/impl-test.txt ============================================================================== --- py/branch/pytestplugin/py/doc/impl-test.txt (original) +++ py/branch/pytestplugin/py/doc/impl-test.txt Tue Jan 27 17:32:09 2009 @@ -275,45 +275,51 @@ pytest plugins ================== -Loading and Configuring plugins -------------------------------------- -py.test loads plugins at startup and during test collection. -You can instruct py.test to load plugins by placing a comma separated list -into a ``conftest.py``:: +specifying plugins for directories or test modules +--------------------------------------------------------- - pytest_plugins_required = "pytest_plugin1", "pytest_plugin2" +py.test loads plugins at tool startup and when first +importing a test module. It looks at the ``pytest_plugins`` +variable in the conftest or the test module. -or by setting an environment variable:: +Example +++++++++++ +If you create a ``conftest.py file with the following content:: - PYTEST_PLUGINS_REQUIRED=pytest_plugin1,pytest_plugin2 + pytest_plugins = "pytest_plugin1", MyLocalPluginClass -pytest loads all plugins from all conftest and environment settings -that it discovers. There is no guaranteed order of initialization. +then test execution within that directory can make use +of the according instantiated plugins: -Rules for writing plugins ------------------------------- +* the module ``pytest_plugin1`` will be imported and + and its contained `Plugin1`` class instantiated. + A plugin module can put its dependencies into + a "pytest_plugins" attribute at module level as well. -A plugin module or package must be named ``pytest_name`` with -'name' all lowercase it must have a plugin class named -``Name`` in its root namespace. A Plugin class may implement -the following attributes and methods: +* the ``MyLocalPluginClass`` will be instantiated + and added to the pluginmanager. + + +Plugin methods +---------------------------------- + +A Plugin class may implement the following attributes and methods: * pytest_cmdlineoptions: a list of optparse-style py.test.config.Option objects * pytest_configure(self, config): called after command line options have been parsed * pytest_unconfigure(self, config): called before the test process quits * pytest_event(self, event): called for each `pytest event`_ +XXX document missing pytest_* hooks + _`pytest event`: + Pytest Events ------------------- XXX Various reporting events. -keywords ------------ -XXX doc py.test.keywords somewhere - Example plugins ----------------------- @@ -323,7 +329,8 @@ pytest_terminal: default reporter for writing info to terminals pytest_resultlog: log test results in machine-readable form to a file pytest_eventlog: log all internal pytest events to a file - pytest_xfail: + pytest_xfail: "expected to fail" test marker + pytest_tmpdir: provide temporary directories to test functions * extending test execution, e.g. pytest_apigen: tracing values of function/method calls when running tests Modified: py/branch/pytestplugin/py/doc/test.txt ============================================================================== --- py/branch/pytestplugin/py/doc/test.txt (original) +++ py/branch/pytestplugin/py/doc/test.txt Tue Jan 27 17:32:09 2009 @@ -147,15 +147,16 @@ py.test -k test_simple -will run all tests that are found from the current directory -and where the word "test_simple" equals the start of one part of the -path leading up to the test item. Directory and file basenames as well -as function, class and function/method names each form a possibly -matching name. - - Note that the exact semantics are still experimental but - should always remain intuitive. - +will run all tests that match the given keyword. +By default, all filename parts and class/function names +of a test function are put into the set of keywords +for a given test. You may specify additional kewords +like this:: + + @py.test.keywords("webtests") + def test_send_http(): + ... + testing with multiple python versions / executables --------------------------------------------------- Modified: py/branch/pytestplugin/py/test/defaultconftest.py ============================================================================== --- py/branch/pytestplugin/py/test/defaultconftest.py (original) +++ py/branch/pytestplugin/py/test/defaultconftest.py Tue Jan 27 17:32:09 2009 @@ -10,10 +10,10 @@ conf_iocapture = "fd" # overridable from conftest.py -pytest_plugins_required = [ - 'py.__.test.plugin.pytest_terminal', - 'py.__.test.plugin.pytest_resultlog', - 'py.__.test.plugin.pytest_eventlog', +pytest_plugins = [ + 'pytest_terminal', + 'pytest_resultlog', + 'pytest_eventlog', ] # =================================================== Modified: py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py Tue Jan 27 17:32:09 2009 @@ -1,7 +1,7 @@ """ example: - pytest_plugins_required = "pytest_tmpdir" + pytest_plugins = "pytest_tmpdir" def test_plugin(tmpdir): tmpdir.join("hello").write("hello") @@ -13,7 +13,6 @@ """ pytest plugin for providing temporary directories to test functions and methods. """ - def pytest_pyitemexecute_arg(self, pyfuncitem, argname): """ return (value, finalizer) tuple or None. Modified: py/branch/pytestplugin/py/test/plugin/pytest_xfail.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_xfail.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_xfail.py Tue Jan 27 17:32:09 2009 @@ -56,7 +56,7 @@ def test_this(): assert 0 """) - self.makepyfile(conftest="""pytest_plugins_required='py.__.test.plugin.pytest_xfail',""") + self.makepyfile(conftest="""pytest_plugins='py.__.test.plugin.pytest_xfail',""") result = self.runpytest(p) extra = assert_lines_contain_lines(result.outlines, [ "*XFAILURES*", Modified: py/branch/pytestplugin/py/test/pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/pmanage.py (original) +++ py/branch/pytestplugin/py/test/pmanage.py Tue Jan 27 17:32:09 2009 @@ -3,7 +3,7 @@ """ import py -pytest_plugins_required = "pytest_plugins_required" +pytest_plugins = "pytest_plugins" DEBUG = False @@ -33,22 +33,27 @@ if not isinstance(importspec, basestring): self.addpluginclass(importspec) else: - prefix = "pytest_" lastpart = importspec.split(".")[-1] - if not lastpart.startswith(prefix): - raise ValueError("in importspec %r: %r does not start with %r" %( - importspec, lastpart, prefix)) - mod = __import__(importspec, None, None, "__doc__") - clsname = lastpart[len(prefix):].capitalize() + modprefix = "pytest_" + if not lastpart.startswith(modprefix): + raise ValueError("in importspec %r: %r does not start with %r" + %(importspec, lastpart, modprefix)) + try: + mod = __import__(importspec, None, None, "__doc__") + except ImportError, e: + mod = __import__("py.__.test.plugin.%s" %(importspec), None, None, '__doc__') + clsname = lastpart[len(modprefix):].capitalize() + print "mod=%s, classname=%s" %(mod,clsname) pluginclass = getattr(mod, clsname) + self.consider_module(mod) self.addpluginclass(pluginclass) def getplugin(self, pname): return self._plugins[pname.lower()] def consider_module(self, mod): - attr = getattr(mod, pytest_plugins_required, ()) - if isinstance(attr, str): + attr = getattr(mod, pytest_plugins, ()) + if not isinstance(attr, (list, tuple)): attr = (attr,) for spec in attr: if spec: Modified: py/branch/pytestplugin/py/test/testing/test_pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_pmanage.py (original) +++ py/branch/pytestplugin/py/test/testing/test_pmanage.py Tue Jan 27 17:32:09 2009 @@ -26,6 +26,28 @@ finally: sys.path.remove(str(self.tmpdir)) + def test_import_plugin_defaults_to_pytestplugin(self): + from py.__.test.defaultconftest import pytest_plugins + for name in pytest_plugins: + if isinstance(name, str): + break + sys.path.insert(0, str(self.tmpdir)) + try: + self.tmpdir.join(name + ".py").write(py.code.Source(""" + class Terminal: + pass + class AnotherPlugin: + pass + pytest_plugins = AnotherPlugin + """)) + pm = PluginManager() + pm.import_plugin(name) + plugin = pm.getplugin("terminal") + print pm._plugins + plugin = pm.getplugin("anotherplugin") + finally: + sys.path.remove(str(self.tmpdir)) + def test_import_plugin_class(self): pm = PluginManager() class SomePlugin: @@ -56,7 +78,7 @@ self.tmpdir.join("pytest_plug1.py").write("class Plug1: pass") self.tmpdir.join("pytest_plug2.py").write("class Plug2: pass") mod = py.std.new.module("temp") - mod.pytest_plugins_required = ["pytest_plug1", "pytest_plug2"] + mod.pytest_plugins = ["pytest_plug1", "pytest_plug2"] pm.consider_module(mod) assert pm.getplugin("plug1").__class__.__name__ == "Plug1" assert pm.getplugin("plug2").__class__.__name__ == "Plug2" @@ -69,7 +91,7 @@ try: self.tmpdir.join("pytest_rplug.py").write("class Rplug: pass") mod = py.std.new.module("temp") - mod.pytest_plugins_required = ["pytest_rplug"] + mod.pytest_plugins = ["pytest_rplug"] pm.registerplugins([mod]) assert pm.getplugin("rplug").__class__.__name__ == "Rplug" finally: Modified: py/branch/pytestplugin/py/test/testing/test_runner_functional.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_runner_functional.py (original) +++ py/branch/pytestplugin/py/test/testing/test_runner_functional.py Tue Jan 27 17:32:09 2009 @@ -19,7 +19,7 @@ if argname == "item": return pyfuncitem, None - pytest_plugins_required = MyPlugin, + pytest_plugins = MyPlugin, def test_func(myxyz, item): assert myxyz == 42 @@ -39,7 +39,7 @@ if argname == "hello": return "world", None - pytest_plugins_required = MyPlugin, + pytest_plugins = MyPlugin, class TestForMethod: def test_func(self, hello): From hpk at codespeak.net Tue Jan 27 17:38:23 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 27 Jan 2009 17:38:23 +0100 (CET) Subject: [py-svn] r61402 - in py/branch/pytestplugin/py/test: . testing Message-ID: <20090127163823.438F2169E29@codespeak.net> Author: hpk Date: Tue Jan 27 17:38:22 2009 New Revision: 61402 Modified: py/branch/pytestplugin/py/test/pmanage.py py/branch/pytestplugin/py/test/testing/test_pmanage.py Log: remove debug debug print, remove imported module in a test Modified: py/branch/pytestplugin/py/test/pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/pmanage.py (original) +++ py/branch/pytestplugin/py/test/pmanage.py Tue Jan 27 17:38:22 2009 @@ -43,7 +43,6 @@ except ImportError, e: mod = __import__("py.__.test.plugin.%s" %(importspec), None, None, '__doc__') clsname = lastpart[len(modprefix):].capitalize() - print "mod=%s, classname=%s" %(mod,clsname) pluginclass = getattr(mod, clsname) self.consider_module(mod) self.addpluginclass(pluginclass) Modified: py/branch/pytestplugin/py/test/testing/test_pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_pmanage.py (original) +++ py/branch/pytestplugin/py/test/testing/test_pmanage.py Tue Jan 27 17:38:22 2009 @@ -43,6 +43,7 @@ pm = PluginManager() pm.import_plugin(name) plugin = pm.getplugin("terminal") + del sys.modules[name] print pm._plugins plugin = pm.getplugin("anotherplugin") finally: From hpk at codespeak.net Wed Jan 28 10:40:13 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 28 Jan 2009 10:40:13 +0100 (CET) Subject: [py-svn] r61417 - py/branch/pytestplugin/py/test/testing Message-ID: <20090128094013.9838216849B@codespeak.net> Author: hpk Date: Wed Jan 28 10:40:11 2009 New Revision: 61417 Modified: py/branch/pytestplugin/py/test/testing/acceptance_test.py Log: this seems to avoid a timeout problem Modified: py/branch/pytestplugin/py/test/testing/acceptance_test.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/acceptance_test.py (original) +++ py/branch/pytestplugin/py/test/testing/acceptance_test.py Wed Jan 28 10:40:11 2009 @@ -400,15 +400,15 @@ spawn = self.getspawn() self.makepyfile(test_one=""" def test_1(): - #hello - assert 1 == 0 + i = 0 + assert i == 1 """) child = spawn("%s %s --pdb test_one.py" % (py.std.sys.executable, pytestpath)) child.timeout = EXPECTTIMEOUT - child.expect(".*def test_1.*") - child.expect(".*hello.*") + #child.expect(".*def test_1.*") + child.expect(".*i = 0.*") child.expect("(Pdb)") child.sendeof() child.expect("1 failed") From hpk at codespeak.net Wed Jan 28 15:35:56 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 28 Jan 2009 15:35:56 +0100 (CET) Subject: [py-svn] r61424 - in py/branch/pytestplugin/py: doc test test/plugin test/testing Message-ID: <20090128143556.4BE32168554@codespeak.net> Author: hpk Date: Wed Jan 28 15:35:54 2009 New Revision: 61424 Added: py/branch/pytestplugin/py/test/plugin/pytest_plugintester.py (contents, props changed) py/branch/pytestplugin/py/test/plugin/pytest_pytester.py (contents, props changed) Modified: py/branch/pytestplugin/py/doc/impl-test.txt py/branch/pytestplugin/py/test/config.py py/branch/pytestplugin/py/test/conftesthandle.py py/branch/pytestplugin/py/test/plugin/conftest.py py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py py/branch/pytestplugin/py/test/plugin/pytest_terminal.py py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py py/branch/pytestplugin/py/test/pmanage.py py/branch/pytestplugin/py/test/pycollect.py py/branch/pytestplugin/py/test/testing/test_conftesthandle.py py/branch/pytestplugin/py/test/testing/test_pmanage.py Log: introduce new plugins for testing py.test and its plugins refine plugin initialization logic Modified: py/branch/pytestplugin/py/doc/impl-test.txt ============================================================================== --- py/branch/pytestplugin/py/doc/impl-test.txt (original) +++ py/branch/pytestplugin/py/doc/impl-test.txt Wed Jan 28 15:35:54 2009 @@ -311,7 +311,7 @@ * pytest_unconfigure(self, config): called before the test process quits * pytest_event(self, event): called for each `pytest event`_ -XXX document missing pytest_* hooks +XXX reference APIcheck'ed full documentation _`pytest event`: @@ -331,6 +331,8 @@ pytest_eventlog: log all internal pytest events to a file pytest_xfail: "expected to fail" test marker pytest_tmpdir: provide temporary directories to test functions + pytest_plugintester: generic apichecks, support for functional plugin tests + pytest_pytester: support for testing py.test runs * extending test execution, e.g. pytest_apigen: tracing values of function/method calls when running tests Modified: py/branch/pytestplugin/py/test/config.py ============================================================================== --- py/branch/pytestplugin/py/test/config.py (original) +++ py/branch/pytestplugin/py/test/config.py Wed Jan 28 15:35:54 2009 @@ -35,9 +35,9 @@ self.option = CmdOptions() self._parser = optparse.OptionParser( usage="usage: %prog [options] [query] [filenames of tests]") - self._conftest = Conftest() self.bus = EventBus() self.pluginmanager = PluginManager() + self._conftest = Conftest(onimport=self.pluginmanager.consider_module) def parse(self, args): """ parse cmdline arguments into this config object. @@ -48,7 +48,6 @@ self._initialized = True adddefaultoptions(self) self._conftest.setinitial(args) - self.pluginmanager.registerplugins(self._conftest.getconftestmodules(None)) self.pluginmanager.add_cmdlineoptions(self) args = [str(x) for x in args] cmdlineoption, args = self._parser.parse_args(args) @@ -72,7 +71,6 @@ self._initialized = True self.topdir = py.path.local(topdir) self._mergerepr(self._repr) - self.pluginmanager.registerplugins(self._conftest.getconftestmodules(None)) del self._repr self.pluginmanager.configure(self) Modified: py/branch/pytestplugin/py/test/conftesthandle.py ============================================================================== --- py/branch/pytestplugin/py/test/conftesthandle.py (original) +++ py/branch/pytestplugin/py/test/conftesthandle.py Wed Jan 28 15:35:54 2009 @@ -9,8 +9,9 @@ conftest.py files may result in added cmdline options. XXX """ - def __init__(self, path=None): + def __init__(self, path=None, onimport=None): self._path2confmods = {} + self._onimport = onimport if path is not None: self.setinitial([path]) @@ -37,11 +38,11 @@ except KeyError: dp = path.dirpath() if dp == path: - return [importconfig(defaultconftestpath)] + return [self.importconftest(defaultconftestpath)] clist = self.getconftestmodules(dp) conftestpath = path.join("conftest.py") if conftestpath.check(file=1): - clist.append(importconfig(conftestpath)) + clist.append(self.importconftest(conftestpath)) self._path2confmods[path] = clist # be defensive: avoid changes from caller side to # affect us by always returning a copy of the actual list @@ -61,15 +62,17 @@ continue raise KeyError, name -def importconfig(configpath): - # We could have used caching here, but it's redundant since - # they're cached on path anyway, so we use it only when doing rget_path - assert configpath.check(), configpath - if not configpath.dirpath('__init__.py').check(file=1): - # HACK: we don't want any "globally" imported conftest.py, - # prone to conflicts and subtle problems - modname = str(configpath).replace('.', configpath.sep) - mod = configpath.pyimport(modname=modname) - else: - mod = configpath.pyimport() - return mod + def importconftest(self, conftestpath): + # Using caching here looks redundant since ultimately + # sys.modules caches already + assert conftestpath.check(), conftestpath + if not conftestpath.dirpath('__init__.py').check(file=1): + # HACK: we don't want any "globally" imported conftest.py, + # prone to conflicts and subtle problems + modname = str(conftestpath).replace('.', conftestpath.sep) + mod = conftestpath.pyimport(modname=modname) + else: + mod = conftestpath.pyimport() + if self._onimport: + self._onimport(mod) + return mod Modified: py/branch/pytestplugin/py/test/plugin/conftest.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/conftest.py (original) +++ py/branch/pytestplugin/py/test/plugin/conftest.py Wed Jan 28 15:35:54 2009 @@ -1,5 +1,7 @@ import py +pytest_plugins = "pytest_pytester", "pytest_plugintester" + class Directory(py.test.collect.Directory): def consider_file(self, path, usefilters): if path.basename.startswith("pytest_") and path.ext == ".py": Modified: py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_eventlog.py Wed Jan 28 15:35:54 2009 @@ -23,19 +23,19 @@ f.flush() # =============================================================================== -# # plugin tests -# # =============================================================================== -from py.__.test.testing import plugintester +def test_generic(plugintester): + plugintester.apicheck() -def test_generic(): - impname = "py.__.test.plugin.pytest_eventlog" - plugintester.nocalls(impname) - tmpdir = plugintester.functional( - "py.__.test.plugin.pytest_eventlog", '--eventlog=event.log') - s = tmpdir.join("event.log").read() + fstester = plugintester.fstester() + fstester.makepyfile(test_one=""" + def test_pass(): + pass + """) + fstester.runpytest("--eventlog=event.log") + s = fstester.tmpdir.join("event.log").read() assert s.find("TestrunStart") != -1 assert s.find("ItemTestReport") != -1 assert s.find("TestrunFinish") != -1 Added: py/branch/pytestplugin/py/test/plugin/pytest_plugintester.py ============================================================================== --- (empty file) +++ py/branch/pytestplugin/py/test/plugin/pytest_plugintester.py Wed Jan 28 15:35:54 2009 @@ -0,0 +1,127 @@ +""" +plugin with support classes and functions for testing pytest functionality +""" +import py + +class Plugintester: + def pytest_itemexecute_arg(self, pyfuncitem, argname): + if argname == "plugintester": + suptest = PluginTester(pyfuncitem) + else: + return None + return suptest, suptest.finalize + +class Support(object): + def __init__(self, pyfuncitem): + """ instantiated per function that requests it. """ + self.pyfuncitem = pyfuncitem + + def getmoditem(self): + for colitem in self.pyfuncitem.listchain(): + if isinstance(colitem, colitem.Module): + return colitem + def _getimpname(self): + return self.getmoditem().obj.__name__ + + def finalize(self): + """ called after test function finished execution""" + +class PluginTester(Support): + def fstester(self, cmdlineargs=()): + # XXX import differently, eg. + # FSTester = self.pyfuncitem._config.pluginmanager.getpluginattr("pytester", "FSTester") + from pytest_pytester import FSTester + impname = self._getimpname() + tmpdir = py.test.ensuretemp(impname) + crunner = FSTester(tmpdir) + crunner.ensureplugin(impname) + return crunner + + def apicheck(self, impname=None): + from py.__.test.pmanage import PluginManager + if not impname: + impname = self.getmoditem().obj.__name__ + print "loading and checking", impname + fail = False + pm = PluginManager() + plugin = pm.import_plugin(impname) + methods = collectattr(plugin.__class__) + hooks = collectattr(PytestPluginHooks) + getargs = py.std.inspect.getargs + while methods: + name, method = methods.popitem() + if name not in hooks: + print "definition of unknown hook: %s" % name + fail = True + else: + hook = hooks[name] + if not hasattr(hook, 'func_code'): + continue # XXX do some checks on attributes as well? + method_args = getargs(method.func_code) + hookargs = getargs(hook.func_code) + for arg, hookarg in zip(method_args[0], hookargs[0]): + if arg != hookarg: + print "argument mismatch:" + print "required:", formatdef(method) + print "actual :", formatdef(hook) + fail = True + break + if not fail: + print "matching hook:", formatdef(method) + if fail: + py.test.fail("Plugin API error") + +def collectattr(obj, prefix="pytest_"): + methods = {} + for apiname in vars(obj): + if apiname.startswith(prefix): + methods[apiname] = getattr(obj, apiname) + return methods + +def formatdef(func): + formatargspec = py.std.inspect.formatargspec + getargspec = py.std.inspect.formatargspec + return "%s%s" %( + func.func_name, + py.std.inspect.formatargspec(*py.std.inspect.getargspec(func)) + ) + + +class PytestPluginHooks: + def __init__(self): + """ usually called only once per test process. """ + + pytest_cmdlineoptions = [] + + def pytest_configure(self, config): + """ called after command line options have been parsed. + and all plugins and initial conftest files been loaded. + ``config`` provides access to all such configuration values. + """ + def pytest_unconfigure(self, config): + """ called before test process is exited. + """ + + def pytest_event(self, event): + """ called for each internal py.test event. """ + + def pytest_itemexecute_arg(self, pyfuncitem, argname): + """ provide (value, finalizer) for an open test function argument. + + the finalizer (if not None) will be called after the test + function has been executed (i.e. pyfuncitem.execute() returns). + """ + def pytest_termreport_result(self, event): + """ return (category, short, verbose) information about the given result event. + ``category`` will be used for counting tests and + pytest_termreport_summary will be called for each category. + ``short`` will be used for printing progress info like "...F.." + ``verbose`` is used for printing verbose information. + """ + +# =============================================================================== +# plugin tests +# =============================================================================== + +def test_generic(plugintester): + plugintester.apicheck() Added: py/branch/pytestplugin/py/test/plugin/pytest_pytester.py ============================================================================== --- (empty file) +++ py/branch/pytestplugin/py/test/plugin/pytest_pytester.py Wed Jan 28 15:35:54 2009 @@ -0,0 +1,138 @@ +""" +pytes plugin for easing testing of pytest runs themselves. +""" + +import py + +class RunResult: + def __init__(self, ret, outlines, errlines): + self.ret = ret + self.outlines = outlines + self.errlines = errlines + +class FileCreation(object): + def makepyfile(self, **kwargs): + return self._makefile('.py', **kwargs) + def maketxtfile(self, **kwargs): + return self._makefile('.txt', **kwargs) + def _makefile(self, ext, **kwargs): + ret = None + for name, value in kwargs.iteritems(): + p = self.tmpdir.join(name).new(ext=ext) + source = py.code.Source(value) + p.write(str(py.code.Source(value)).lstrip()) + if ret is None: + ret = p + return ret + +class FSTester(FileCreation): + def __init__(self, tmpdir): + self.tmpdir = tmpdir + self._plugins = [] + + def ensureplugin(self, impname): + assert isinstance(impname, str) + if not impname in self._plugins: + self._plugins.append(impname) + + def _writeconftest(self): + p = self.tmpdir.join("conftest.py") + pstring = repr(self._plugins) + p.write("import py ; pytest_plugins = %s" % pstring) + #else: + # lines = p.readlines(cr=0) + # for i, line in py.builtin.enumerate(lines): + # if line.find("pytest_plugins") != -1: + # lines[i] = line + ", %s" % pstring + # break + # else: + # lines.append(pstring) + # p.write("\n".join(lines)) + + def prepare(self): + self._writeconftest() + + def popen(self, cmdargs, stdout, stderr, **kw): + if not hasattr(py.std, 'subprocess'): + py.test.skip("no subprocess module") + return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) + + def run(self, *cmdargs): + self.prepare() + old = self.tmpdir.chdir() + #print "chdir", self.tmpdir + try: + return self._run(*cmdargs) + finally: + old.chdir() + + def _run(self, *cmdargs): + cmdargs = map(str, cmdargs) + p1 = py.path.local("stdout") + p2 = py.path.local("stderr") + print "running", cmdargs, "curdir=", py.path.local() + popen = self.popen(cmdargs, stdout=p1.open("w"), stderr=p2.open("w")) + ret = popen.wait() + out, err = p1.readlines(cr=0), p2.readlines(cr=0) + if err: + for line in err: + print >>py.std.sys.stderr, line + return RunResult(ret, out, err) + + def runpybin(self, scriptname, *args): + bindir = py.path.local(py.__file__).dirpath("bin") + if py.std.sys.platform == "win32": + script = bindir.join("win32", scriptname + ".cmd") + else: + script = bindir.join(scriptname) + assert script.check() + return self.run(script, *args) + + def runpytest(self, *args): + return self.runpybin("py.test", *args) + +class Pytester: + def pytest_itemexecute_arg(self, pyfuncitem, argname): + if argname == "linecomp": + return LineComp(), None + +class LineComp: + def assert_contains_lines(self, lines1, lines2): + """ assert that lines2 are contained (linearly) in lines1. + return a list of extralines found. + """ + from fnmatch import fnmatch + __tracebackhide__ = True + if hasattr(lines1, "getvalue"): + lines1 = lines1.getvalue().split("\n") + if isinstance(lines2, str): + lines2 = py.code.Source(lines2) + if isinstance(lines2, py.code.Source): + lines2 = lines2.strip().lines + + extralines = [] + lines1 = lines1[:] + nextline = None + for line in lines2: + nomatchprinted = False + while lines1: + nextline = lines1.pop(0) + if line == nextline: + print "exact match:", repr(line) + break + elif fnmatch(nextline, line): + print "fnmatch:", repr(line) + print " with:", repr(nextline) + break + else: + if not nomatchprinted: + print "nomatch:", repr(line) + nomatchprinted = True + print " and:", repr(nextline) + extralines.append(nextline) + else: + if line != nextline: + #__tracebackhide__ = True + raise AssertionError("expected line not found: %r" % line) + extralines.extend(lines1) + return extralines Modified: py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py Wed Jan 28 15:35:54 2009 @@ -99,8 +99,7 @@ # =============================================================================== import os, StringIO -from py.__.test.testing import plugintester, suptest -from py.__.test import event +from py.__.test.testing import suptest def test_generic_path(): from py.__.test.collect import Node, Item, FSCollector @@ -123,18 +122,6 @@ res = generic_path(item) assert res == 'test/a:B().c[1]' -def test_generic(): - plugintester.nocalls("py.__.test.plugin.pytest_resultlog") - tmpdir = plugintester.functional("py.__.test.plugin.pytest_resultlog", - '--resultlog=resultlog') - resultlog = tmpdir.join("resultlog") - s = resultlog.readlines(cr=0) - suptest.assert_lines_contain_lines(s, [ - ". *:test_pass", - "F *:test_fail", - "s *:test_skip", - ]) - def test_write_log_entry(): reslog = ResultLog(None) reslog.logfile = StringIO.StringIO() @@ -172,6 +159,7 @@ assert len(entry_lines) == 5 assert entry_lines[0] == 'F name' assert entry_lines[1:] == [' '+line for line in longrepr.splitlines()] + class TestWithFunctionIntegration(suptest.InlineSession): # XXX (hpk) i think that the resultlog plugin should @@ -228,6 +216,7 @@ def test_internal_exception(self): # they are produced for example by a teardown failing # at the end of the run + from py.__.test import event bus = event.EventBus() reslog = ResultLog(StringIO.StringIO()) bus.subscribe(reslog.log_event_to_file) @@ -249,3 +238,24 @@ assert entry_lines[-1][0] == ' ' assert 'ValueError' in entry +def test_generic(plugintester, linecomp): + plugintester.apicheck() + fstester = plugintester.fstester() + fstester.makepyfile(test_one=""" + import py + def test_pass(): + pass + def test_fail(): + assert 0 + def test_skip(): + py.test.skip("") + """) + fstester.runpytest("--resultlog=result.log") + s = fstester.tmpdir.join("result.log").readlines(cr=0) + + linecomp.assert_contains_lines(s, [ + ". *:test_pass", + "F *:test_fail", + "s *:test_skip", + ]) + Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Wed Jan 28 15:35:54 2009 @@ -1,6 +1,5 @@ import py import sys -from py.__.test import event from py.__.test.collect import getrelpath class Terminal(object): @@ -298,10 +297,6 @@ py.std.sys.executable, repr_pythonversion())) - - -import py - class CollectonlyReporter(BaseReporter): INDENT = " " @@ -372,7 +367,7 @@ sys.executable, repr_pythonversion(sys.version_info)) assert s.find(expect) != -1 - def test_pass_skip_fail(self): + def test_pass_skip_fail(self, linecomp): modcol = self.getmodulecol(""" import py def test_ok(): @@ -391,7 +386,7 @@ s = popvalue(stringio) assert s.find("test_pass_skip_fail.py .sF") != -1 rep.processevent(event.TestrunFinish()) - assert_stringio_contains_lines(stringio, [ + linecomp.assert_contains_lines(stringio, [ " def test_func():", "> assert 0", "E assert 0", @@ -548,7 +543,7 @@ print s assert s.find("test_show_path_before_running_test.py") != -1 - def test_keyboard_interrupt(self, verbose=False): + def pseudo_keyboard_interrupt(self, verbose=False): modcol = self.getmodulecol(""" def test_foobar(): assert 0 @@ -584,8 +579,11 @@ see_details = "raise KeyboardInterrupt # simulating the user" in text assert see_details == verbose + def test_keyboard_interrupt(self): + self.pseudo_keyboard_interrupt() + def test_verbose_keyboard_interrupt(self): - self.test_keyboard_interrupt(verbose=True) + self.pseudo_keyboard_interrupt(verbose=True) class TestCollectonly(suptest.InlineCollection): def test_collectonly_basic(self): Modified: py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py Wed Jan 28 15:35:54 2009 @@ -14,12 +14,6 @@ to test functions and methods. """ def pytest_pyitemexecute_arg(self, pyfuncitem, argname): - """ return (value, finalizer) tuple or None. - - the value will be provided to the test function. - the finalizer (if not None) will be called after the test - function has been executed (i.e. pyfuncitem.execute() returns). - """ if argname == "tmpdir": basename = "_".join(pyfuncitem.listnames()) # move to config Modified: py/branch/pytestplugin/py/test/pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/pmanage.py (original) +++ py/branch/pytestplugin/py/test/pmanage.py Wed Jan 28 15:35:54 2009 @@ -24,6 +24,7 @@ else: self.trace("instantiating plugin: %s" %(pluginclass,)) self._plugins[name] = plugin + return plugin def trace(self, msg): if DEBUG: @@ -31,7 +32,7 @@ def import_plugin(self, importspec): if not isinstance(importspec, basestring): - self.addpluginclass(importspec) + return self.addpluginclass(importspec) else: lastpart = importspec.split(".")[-1] modprefix = "pytest_" @@ -44,8 +45,9 @@ mod = __import__("py.__.test.plugin.%s" %(importspec), None, None, '__doc__') clsname = lastpart[len(modprefix):].capitalize() pluginclass = getattr(mod, clsname) + result = self.addpluginclass(pluginclass) self.consider_module(mod) - self.addpluginclass(pluginclass) + return result def getplugin(self, pname): return self._plugins[pname.lower()] @@ -58,10 +60,6 @@ if spec: self.import_plugin(spec) - def registerplugins(self, conftestmodules): - for mod in conftestmodules: - self.consider_module(mod) - # # API for interacting with registered and instantiated plugin objects # Modified: py/branch/pytestplugin/py/test/pycollect.py ============================================================================== --- py/branch/pytestplugin/py/test/pycollect.py (original) +++ py/branch/pytestplugin/py/test/pycollect.py Wed Jan 28 15:35:54 2009 @@ -164,7 +164,7 @@ def _importtestmodule(self): # we assume we are only called once per module mod = self.fspath.pyimport() - print "imported test module", mod + #print "imported test module", mod self._config.pluginmanager.consider_module(mod) return mod @@ -354,9 +354,10 @@ if argfinalizer is not None: self._argfinalizers.append(argfinalizer) else: - self._config.pluginmanager.trace( - "could not find argument %r, plugins=%r" %( - argname,self._config.pluginmanager._plugins)) + #self._config.pluginmanager.trace( + # "could not find argument %r, plugins=%r" %( + # argname,self._config.pluginmanager._plugins)) + raise TypeError("could not provide funcargument %r" %(argname,)) def __eq__(self, other): try: Modified: py/branch/pytestplugin/py/test/testing/test_conftesthandle.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_conftesthandle.py (original) +++ py/branch/pytestplugin/py/test/testing/test_conftesthandle.py Wed Jan 28 15:35:54 2009 @@ -16,6 +16,16 @@ conftest.setinitial([self.basedir.join("adir")]) assert conftest.rget("a") == 1 + def test_onimport(self): + l = [] + conftest = Conftest(onimport=l.append) + conftest.setinitial([self.basedir.join("adir")]) + assert len(l) == 2 # default + the one + assert conftest.rget("a") == 1 + assert conftest.rget("b", self.basedir.join("adir", "b")) == 2 + assert len(l) == 3 + + def test_immediate_initialiation_and_incremental_are_the_same(self): conftest = Conftest() snap0 = len(conftest._path2confmods) Modified: py/branch/pytestplugin/py/test/testing/test_pmanage.py ============================================================================== --- py/branch/pytestplugin/py/test/testing/test_pmanage.py (original) +++ py/branch/pytestplugin/py/test/testing/test_pmanage.py Wed Jan 28 15:35:54 2009 @@ -86,17 +86,6 @@ finally: sys.path.remove(str(self.tmpdir)) - def test_register_plugins(self): - pm = PluginManager() - sys.path.insert(0, str(self.tmpdir)) - try: - self.tmpdir.join("pytest_rplug.py").write("class Rplug: pass") - mod = py.std.new.module("temp") - mod.pytest_plugins = ["pytest_rplug"] - pm.registerplugins([mod]) - assert pm.getplugin("rplug").__class__.__name__ == "Rplug" - finally: - sys.path.remove(str(self.tmpdir)) def test_addpluginclass(self): pm = PluginManager() From hpk at codespeak.net Wed Jan 28 19:24:17 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 28 Jan 2009 19:24:17 +0100 (CET) Subject: [py-svn] r61431 - in py/branch/pytestplugin/py: doc path path/testing test test/plugin Message-ID: <20090128182417.8DE39169E6D@codespeak.net> Author: hpk Date: Wed Jan 28 19:24:13 2009 New Revision: 61431 Modified: py/branch/pytestplugin/py/doc/path.txt py/branch/pytestplugin/py/path/common.py py/branch/pytestplugin/py/path/testing/common.py py/branch/pytestplugin/py/test/collect.py py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Log: make getrelpath method a general method (bestrelpath()) on path objects. this gets rid of another "__" import from the terminal plugin. Modified: py/branch/pytestplugin/py/doc/path.txt ============================================================================== --- py/branch/pytestplugin/py/doc/path.txt (original) +++ py/branch/pytestplugin/py/doc/path.txt Wed Jan 28 19:24:13 2009 @@ -132,6 +132,10 @@ >>> sep = py.path.local.sep >>> p2.relto(p1).replace(sep, '/') # os-specific path sep in the string 'baz/qux' + >>> p2.bestrelpath(p1) + '../..' + >>> p2.join(p2.bestrelpath(p1)) == p1 + True >>> p3 = p1 / 'baz/qux' # the / operator allows joining, too >>> p2 == p3 True Modified: py/branch/pytestplugin/py/path/common.py ============================================================================== --- py/branch/pytestplugin/py/path/common.py (original) +++ py/branch/pytestplugin/py/path/common.py Wed Jan 28 19:24:13 2009 @@ -152,6 +152,30 @@ return strself[len(strrelpath):] return "" + def bestrelpath(self, dest): + """ return relative path from self to dest + such that self.join(bestrelpath) == dest. + if not such path can be determined return dest. + """ + try: + base = self.common(dest) + if not base: # can be the case on windows + return dest + self2base = self.relto(base) + reldest = dest.relto(base) + if self2base: + n = self2base.count(self.sep) + 1 + else: + n = 0 + l = ['..'] * n + if reldest: + l.append(reldest) + target = dest.sep.join(l) + return target + except AttributeError: + return dest + + def parts(self, reverse=False): """ return a root-first list of all ancestor directories plus the path itself. Modified: py/branch/pytestplugin/py/path/testing/common.py ============================================================================== --- py/branch/pytestplugin/py/path/testing/common.py (original) +++ py/branch/pytestplugin/py/path/testing/common.py Wed Jan 28 19:24:13 2009 @@ -120,6 +120,18 @@ assert self.root.check(notrelto=l) assert not self.root.check(relto=l) + def test_bestrelpath(self): + curdir = self.root + sep = curdir.sep + s = curdir.bestrelpath(curdir.join("hello", "world")) + assert s == "hello" + sep + "world" + + s = curdir.bestrelpath(curdir.dirpath().join("sister")) + assert s == ".." + sep + "sister" + assert curdir.bestrelpath(curdir.dirpath()) == ".." + + assert curdir.bestrelpath("hello") == "hello" + def test_relto_not_relative(self): l1=self.root.join("bcde") l2=self.root.join("b") Modified: py/branch/pytestplugin/py/test/collect.py ============================================================================== --- py/branch/pytestplugin/py/test/collect.py (original) +++ py/branch/pytestplugin/py/test/collect.py Wed Jan 28 19:24:13 2009 @@ -67,7 +67,7 @@ params = self.__dict__.copy() if self.fspath: if basedir is not None: - params['fspath'] = getrelpath(basedir, self.fspath) + params['fspath'] = basedir.bestrelpath(self.fspath) if self.lineno is not None: params['lineno'] = self.lineno + 1 @@ -481,27 +481,6 @@ def runtest(self): """ execute this test item.""" - -def getrelpath(curdir, dest): - try: - base = curdir.common(dest) - if not base: # can be the case on windows - return dest - curdir2base = curdir.relto(base) - reldest = dest.relto(base) - if curdir2base: - n = curdir2base.count(curdir.sep) + 1 - else: - n = 0 - l = ['..'] * n - if reldest: - l.append(reldest) - target = dest.sep.join(l) - return target - except AttributeError: - return dest - - def warnoldcollect(): APIWARN("1.0", "implement collector.collect() instead of " Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Wed Jan 28 19:24:13 2009 @@ -1,6 +1,5 @@ import py import sys -from py.__.test.collect import getrelpath class Terminal(object): """ Terminal Reporter plugin to write information about test run to terminal. """ @@ -91,7 +90,7 @@ def write_fspath_result(self, fspath, res): if fspath != self.currentfspath: self._tw.line() - relpath = getrelpath(self.curdir, fspath) + relpath = self.curdir.bestrelpath(fspath) self._tw.write(relpath + " ") self.currentfspath = fspath self._tw.write(res) @@ -707,15 +706,3 @@ assert repr_pythonversion() == str(x) finally: py.magic.revert(sys, 'version_info') - -def test_getrelpath(): - curdir = py.path.local() - sep = curdir.sep - s = getrelpath(curdir, curdir.join("hello", "world")) - assert s == "hello" + sep + "world" - - s = getrelpath(curdir, curdir.dirpath().join("sister")) - assert s == ".." + sep + "sister" - assert getrelpath(curdir, curdir.dirpath()) == ".." - - assert getrelpath(curdir, "hello") == "hello" From hpk at codespeak.net Wed Jan 28 21:06:59 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 28 Jan 2009 21:06:59 +0100 (CET) Subject: [py-svn] r61433 - py/branch/pytestplugin/py/test/plugin Message-ID: <20090128200659.3A7FB16857F@codespeak.net> Author: hpk Date: Wed Jan 28 21:06:57 2009 New Revision: 61433 Modified: py/branch/pytestplugin/py/test/plugin/pytest_plugintester.py py/branch/pytestplugin/py/test/plugin/pytest_pytester.py py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Log: getting close to not importing internal objects in the plugins (except pytest_plugintester and pytest_pytester whose purpose is to provide objects for testing plugins and test runs) Modified: py/branch/pytestplugin/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_plugintester.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_plugintester.py Wed Jan 28 21:06:57 2009 @@ -6,10 +6,10 @@ class Plugintester: def pytest_itemexecute_arg(self, pyfuncitem, argname): if argname == "plugintester": - suptest = PluginTester(pyfuncitem) + pt = PluginTester(pyfuncitem) else: return None - return suptest, suptest.finalize + return pt, pt.finalize class Support(object): def __init__(self, pyfuncitem): Modified: py/branch/pytestplugin/py/test/plugin/pytest_pytester.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_pytester.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_pytester.py Wed Jan 28 21:06:57 2009 @@ -91,20 +91,24 @@ def runpytest(self, *args): return self.runpybin("py.test", *args) -class Pytester: - def pytest_itemexecute_arg(self, pyfuncitem, argname): - if argname == "linecomp": - return LineComp(), None - class LineComp: - def assert_contains_lines(self, lines1, lines2): + + def __init__(self): + self.stringio = py.std.StringIO.StringIO() + + def assert_contains_lines(self, lines1=None, lines2=None): """ assert that lines2 are contained (linearly) in lines1. return a list of extralines found. """ from fnmatch import fnmatch __tracebackhide__ = True + if lines2 is None: + lines2 = lines1 + lines1 = self.stringio if hasattr(lines1, "getvalue"): - lines1 = lines1.getvalue().split("\n") + val = lines1.getvalue() + lines1.truncate(0) # remove what we got + lines1 = val.split("\n") if isinstance(lines2, str): lines2 = py.code.Source(lines2) if isinstance(lines2, py.code.Source): @@ -136,3 +140,40 @@ raise AssertionError("expected line not found: %r" % line) extralines.extend(lines1) return extralines + +class TSession: + def __init__(self, pyfuncitem): + self.pyfuncitem = pyfuncitem + self.tmpdir = py.test.ensuretemp("_".join(pyfuncitem.listnames())) + self.fstester = FSTester(self.tmpdir) + #self.capture = py.io.StdCapture() + + #def finalize(self): + # self.capture.reset() + # + def genitems(self, colitems): + return self.session.genitems(colitems) + + def parseconfig(self, *args): + return py.test.config._reparse(list(args)) + + def getitem(self, source, funcname="test_func"): + modcol = self.getmodulecol(source) + item = modcol.join(funcname) + assert item is not None, "%r item not found in module:\n%s" %(funcname, source) + return item + + def getmodulecol(self, source, configargs=(), withsession=False): + kw = {self.pyfuncitem.name: py.code.Source(source).strip()} + path = self.fstester.makepyfile(**kw) + self.config = self.parseconfig(path, *configargs) + self.session = self.config.initsession() + return self.config.getfsnode(path) + +class Pytester: + def pytest_itemexecute_arg(self, pyfuncitem, argname): + if argname == "linecomp": + return LineComp(), None + elif argname == "tsession": + tsession = TSession(pyfuncitem) + return tsession, None Modified: py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_resultlog.py Wed Jan 28 21:06:57 2009 @@ -99,7 +99,6 @@ # =============================================================================== import os, StringIO -from py.__.test.testing import suptest def test_generic_path(): from py.__.test.collect import Node, Item, FSCollector @@ -161,31 +160,33 @@ assert entry_lines[1:] == [' '+line for line in longrepr.splitlines()] -class TestWithFunctionIntegration(suptest.InlineSession): +class TestWithFunctionIntegration: # XXX (hpk) i think that the resultlog plugin should # provide a Parser object so that one can remain # ignorant regarding formatting details. - def getresultlog(self, args): - resultlog = self.tmpdir.join("resultlog") - self.parse_and_run("--resultlog=%s" % resultlog, args) + def getresultlog(self, fstester, arg): + resultlog = fstester.tmpdir.join("resultlog") + args = ["--resultlog=%s" % resultlog] + [arg] + fstester.runpytest(*args) return filter(None, resultlog.readlines(cr=0)) - def test_collection_report(self): - ok = self.makepyfile(test_collection_ok="") - skip = self.makepyfile(test_collection_skip="import py ; py.test.skip('hello')") - fail = self.makepyfile(test_collection_fail="XXX") + def test_collection_report(self, plugintester): + fstester = plugintester.fstester() + ok = fstester.makepyfile(test_collection_ok="") + skip = fstester.makepyfile(test_collection_skip="import py ; py.test.skip('hello')") + fail = fstester.makepyfile(test_collection_fail="XXX") - lines = self.getresultlog(ok) + lines = self.getresultlog(fstester, ok) assert not lines - lines = self.getresultlog(skip) + lines = self.getresultlog(fstester, skip) assert len(lines) == 2 assert lines[0].startswith("S ") assert lines[0].endswith("test_collection_skip.py") assert lines[1].startswith(" ") assert lines[1].endswith("test_collection_skip.py:1: Skipped: 'hello'") - lines = self.getresultlog(fail) + lines = self.getresultlog(fstester, fail) assert lines assert lines[0].startswith("F ") assert lines[0].endswith("test_collection_fail.py"), lines[0] @@ -193,14 +194,15 @@ assert x.startswith(" ") assert "XXX" in "".join(lines[1:]) - def test_log_test_outcomes(self): - mod = self.makepyfile(test_mod=""" + def test_log_test_outcomes(self, plugintester): + fstester = plugintester.fstester() + mod = fstester.makepyfile(test_mod=""" import py def test_pass(): pass def test_skip(): py.test.skip("hello") def test_fail(): raise ValueError("val") """) - lines = self.getresultlog(mod) + lines = self.getresultlog(fstester, mod) assert len(lines) >= 3 assert lines[0].startswith(". ") assert lines[0].endswith("test_pass") Modified: py/branch/pytestplugin/py/test/plugin/pytest_terminal.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_terminal.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_terminal.py Wed Jan 28 21:06:57 2009 @@ -344,30 +344,21 @@ from py.__.test import event from py.__.test.runner import basic_run_report -from py.__.test.testing import suptest -from py.__.test.testing.suptest import popvalue, assert_stringio_contains_lines from py.__.test.dsession.hostmanage import Host, makehostup -class TestTerminal(suptest.InlineCollection): - #def test_reporter_subscription_by_default(self): - # config = py.test.config._reparse(['xxx']) - # plugin = config.pluginmanager.getplugin("pytest_terminal") - # assert plugin - - def test_hostup(self): - item = self.getitem("def test_func(): pass") - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(item._config, file=stringio) - rep.processevent(event.TestrunStart()) +class TestTerminal: + def test_hostup(self, tsession, linecomp): + item = tsession.getitem("def test_func(): pass") + rep = TerminalReporter(item._config, linecomp.stringio) host = Host("localhost") rep.processevent(makehostup(host)) - s = popvalue(stringio) - expect = "%s %s %s - Python %s" %(host.hostid, sys.platform, + linecomp.assert_contains_lines([ + "*%s %s %s - Python %s" %(host.hostid, sys.platform, sys.executable, repr_pythonversion(sys.version_info)) - assert s.find(expect) != -1 + ]) - def test_pass_skip_fail(self, linecomp): - modcol = self.getmodulecol(""" + def test_pass_skip_fail(self, tsession, linecomp): + modcol = tsession.getmodulecol(""" import py def test_ok(): pass @@ -375,24 +366,25 @@ py.test.skip("xx") def test_func(): assert 0 - """, withsession=True) - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) + """) + rep = TerminalReporter(modcol._config, file=linecomp.stringio) rep.processevent(event.TestrunStart()) - for item in self.session.genitems([modcol]): + for item in tsession.genitems([modcol]): ev = basic_run_report(item) rep.processevent(ev) - s = popvalue(stringio) - assert s.find("test_pass_skip_fail.py .sF") != -1 + linecomp.assert_contains_lines([ + "*test_pass_skip_fail.py .sF" + ]) + rep.processevent(event.TestrunFinish()) - linecomp.assert_contains_lines(stringio, [ + linecomp.assert_contains_lines([ " def test_func():", "> assert 0", "E assert 0", ]) - def test_pass_skip_fail_verbose(self): - modcol = self.getmodulecol(""" + def test_pass_skip_fail_verbose(self, tsession, linecomp): + modcol = tsession.getmodulecol(""" import py def test_ok(): pass @@ -400,122 +392,116 @@ py.test.skip("xx") def test_func(): assert 0 - """, configargs=("-v",), withsession=True) - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) + """, configargs=("-v",)) + rep = TerminalReporter(modcol._config, file=linecomp.stringio) rep.processevent(event.TestrunStart()) items = modcol.collect() for item in items: rep.processevent(event.ItemStart(item)) - s = stringio.getvalue().strip() + s = linecomp.stringio.getvalue().strip() assert s.endswith(item.name) ev = basic_run_report(item) rep.processevent(ev) - assert_stringio_contains_lines(stringio, [ + linecomp.assert_contains_lines([ "*test_pass_skip_fail_verbose.py:2: *test_ok*PASS", "*test_pass_skip_fail_verbose.py:4: *test_skip*SKIP", "*test_pass_skip_fail_verbose.py:6: *test_func*FAIL", ]) rep.processevent(event.TestrunFinish()) - assert_stringio_contains_lines(stringio, [ + linecomp.assert_contains_lines([ " def test_func():", "> assert 0", "E assert 0", ]) - def test_collect_fail(self): - modcol = self.getmodulecol(""" - import xyz - """, withsession=True) - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) - self.session.bus.subscribe(rep.processevent) + def test_collect_fail(self, tsession, linecomp): + modcol = tsession.getmodulecol("import xyz") + rep = TerminalReporter(modcol._config, file=linecomp.stringio) + tsession.session.bus.subscribe(rep.processevent) rep.processevent(event.TestrunStart()) - l = list(self.session.genitems([modcol])) + l = list(tsession.genitems([modcol])) assert len(l) == 0 - s = popvalue(stringio) - print s - assert s.find("test_collect_fail.py F") != -1 + linecomp.assert_contains_lines([ + "*test_collect_fail.py F*" + ]) rep.processevent(event.TestrunFinish()) - assert_stringio_contains_lines(stringio, [ + linecomp.assert_contains_lines([ "> import xyz", "E ImportError: No module named xyz" ]) - def test_internal_exception(self): - modcol = self.getmodulecol("def test_one(): pass") - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) + def test_internal_exception(self, tsession, linecomp): + modcol = tsession.getmodulecol("def test_one(): pass") + rep = TerminalReporter(modcol._config, file=linecomp.stringio) excinfo = py.test.raises(ValueError, "raise ValueError('hello')") rep.processevent(event.InternalException(excinfo)) - s = popvalue(stringio) - assert s.find("InternalException:") != -1 + linecomp.assert_contains_lines([ + "InternalException: >*raise ValueError*" + ]) - def test_hostready_crash(self): - modcol = self.getmodulecol(""" + def test_hostready_crash(self, tsession, linecomp): + modcol = tsession.getmodulecol(""" def test_one(): pass """, configargs=("-v",)) - stringio = py.std.cStringIO.StringIO() host1 = Host("localhost") - rep = TerminalReporter(modcol._config, file=stringio) + rep = TerminalReporter(modcol._config, file=linecomp.stringio) rep.processevent(event.HostGatewayReady(host1, None)) - s = popvalue(stringio) - assert s.find("HostGatewayReady") != -1 + linecomp.assert_contains_lines([ + "*HostGatewayReady*" + ]) rep.processevent(event.HostDown(host1, "myerror")) - s = popvalue(stringio) - assert s.find("HostDown") != -1 - assert s.find("myerror") != -1 + linecomp.assert_contains_lines([ + "*HostDown*myerror*", + ]) - def test_writeline(self): - modcol = self.getmodulecol("def test_one(): pass") + def test_writeline(self, tsession, linecomp): + modcol = tsession.getmodulecol("def test_one(): pass") stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) + rep = TerminalReporter(modcol._config, file=linecomp.stringio) rep.write_fspath_result(py.path.local("xy.py"), '.') rep.write_line("hello world") - lines = popvalue(stringio).split('\n') + lines = linecomp.stringio.getvalue().split('\n') assert not lines[0] assert lines[1].endswith("xy.py .") assert lines[2] == "hello world" - def test_looponfailingreport(self): - modcol = self.getmodulecol(""" + def test_looponfailingreport(self, tsession, linecomp): + modcol = tsession.getmodulecol(""" def test_fail(): assert 0 def test_fail2(): raise ValueError() """) - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) + rep = TerminalReporter(modcol._config, file=linecomp.stringio) reports = [basic_run_report(x) for x in modcol.collect()] rep.processevent(event.LooponfailingInfo(reports, [modcol._config.topdir])) - assert_stringio_contains_lines(stringio, [ + linecomp.assert_contains_lines([ "*test_looponfailingreport.py:2: assert 0", "*test_looponfailingreport.py:4: ValueError*", "*waiting*", "*%s*" % (modcol._config.topdir), ]) - def test_tb_option(self): + def test_tb_option(self, tsession, linecomp): for tbopt in ["no", "short", "long"]: print 'testing --tb=%s...' % tbopt - modcol = self.getmodulecol(""" + modcol = tsession.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) + """, configargs=("--tb=%s" % tbopt,)) + rep = TerminalReporter(modcol._config, file=linecomp.stringio) rep.processevent(event.TestrunStart()) - for item in self.session.genitems([modcol]): + for item in tsession.genitems([modcol]): ev = basic_run_report(item) rep.processevent(ev) rep.processevent(event.TestrunFinish()) - s = popvalue(stringio) + s = linecomp.stringio.getvalue() if tbopt == "long": assert 'print 6*7' in s else: @@ -527,66 +513,64 @@ assert 'FAILURES' not in s assert '--calling--' not in s assert 'IndexError' not in s + linecomp.stringio.truncate(0) - def test_show_path_before_running_test(self): - modcol = self.getmodulecol(""" + def test_show_path_before_running_test(self, tsession, linecomp): + modcol = tsession.getmodulecol(""" def test_foobar(): pass - """, withsession=True) - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) - l = list(self.session.genitems([modcol])) + """) + rep = TerminalReporter(modcol._config, file=linecomp.stringio) + l = list(tsession.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 + linecomp.assert_contains_lines([ + "*test_show_path_before_running_test.py*" + ]) - def pseudo_keyboard_interrupt(self, verbose=False): - modcol = self.getmodulecol(""" + def pseudo_keyboard_interrupt(self, tsession, linecomp, verbose=False): + modcol = tsession.getmodulecol(""" def test_foobar(): assert 0 def test_spamegg(): import py; py.test.skip('skip me please!') def test_interrupt_me(): raise KeyboardInterrupt # simulating the user - """, configargs=("--showskipsummary",) + ("-v",)*verbose, - withsession=True) - stringio = py.std.cStringIO.StringIO() - rep = TerminalReporter(modcol._config, file=stringio) + """, configargs=("--showskipsummary",) + ("-v",)*verbose) + rep = TerminalReporter(modcol._config, file=linecomp.stringio) rep.processevent(event.TestrunStart()) try: - for item in self.session.genitems([modcol]): + for item in tsession.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) + s = linecomp.stringio.getvalue() if not verbose: assert s.find("_keyboard_interrupt.py Fs") != -1 rep.processevent(event.TestrunFinish(exitstatus=2, excinfo=excinfo)) - assert_stringio_contains_lines(stringio, [ + text = linecomp.stringio.getvalue() + linecomp.assert_contains_lines([ " def test_foobar():", "> assert 0", "E assert 0", ]) - 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_keyboard_interrupt(self): - self.pseudo_keyboard_interrupt() + def test_keyboard_interrupt(self, tsession, linecomp): + self.pseudo_keyboard_interrupt(tsession, linecomp) - def test_verbose_keyboard_interrupt(self): - self.pseudo_keyboard_interrupt(verbose=True) + def test_verbose_keyboard_interrupt(self, tsession, linecomp): + self.pseudo_keyboard_interrupt(tsession, linecomp, verbose=True) -class TestCollectonly(suptest.InlineCollection): - def test_collectonly_basic(self): - modcol = self.getmodulecol(configargs=['--collectonly'], source=""" +class TestCollectonly: + def test_collectonly_basic(self, tsession, linecomp): + modcol = tsession.getmodulecol(configargs=['--collectonly'], source=""" def test_func(): pass """) @@ -594,42 +578,43 @@ rep = CollectonlyReporter(modcol._config, out=stringio) indent = rep.indent rep.processevent(event.CollectionStart(modcol)) - s = popvalue(stringio) - assert s == "" - + linecomp.assert_contains_lines(stringio, [ + "" + ]) item = modcol.join("test_func") rep.processevent(event.ItemStart(item)) - s = popvalue(stringio) - assert s.find("Function 'test_func'") != -1 + linecomp.assert_contains_lines(stringio, [ + " ", + ]) rep.processevent(event.CollectionReport(modcol, [], excinfo=None)) assert rep.indent == indent - def test_collectonly_skipped_module(self): - modcol = self.getmodulecol(configargs=['--collectonly'], source=""" + def test_collectonly_skipped_module(self, tsession, linecomp): + modcol = tsession.getmodulecol(configargs=['--collectonly'], source=""" import py py.test.skip("nomod") - """, withsession=True) + """) stringio = py.std.cStringIO.StringIO() rep = CollectonlyReporter(modcol._config, out=stringio) - self.session.bus.subscribe(rep.processevent) - cols = list(self.session.genitems([modcol])) + tsession.session.bus.subscribe(rep.processevent) + cols = list(tsession.genitems([modcol])) assert len(cols) == 0 - assert_stringio_contains_lines(stringio, """ - + linecomp.assert_contains_lines(stringio, """ + !!! Skipped: 'nomod' !!! """) - def test_collectonly_failed_module(self): - modcol = self.getmodulecol(configargs=['--collectonly'], source=""" + def test_collectonly_failed_module(self, tsession, linecomp): + modcol = tsession.getmodulecol(configargs=['--collectonly'], source=""" raise ValueError(0) - """, withsession=True) + """) stringio = py.std.cStringIO.StringIO() rep = CollectonlyReporter(modcol._config, out=stringio) - self.session.bus.subscribe(rep.processevent) - cols = list(self.session.genitems([modcol])) + tsession.session.bus.subscribe(rep.processevent) + cols = list(tsession.genitems([modcol])) assert len(cols) == 0 - assert_stringio_contains_lines(stringio, """ - + linecomp.assert_contains_lines(stringio, """ + !!! ValueError: 0 !!! """) From hpk at codespeak.net Thu Jan 29 15:06:37 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 29 Jan 2009 15:06:37 +0100 (CET) Subject: [py-svn] r61449 - py/branch/pytestplugin/py/test/plugin Message-ID: <20090129140637.B22241684E6@codespeak.net> Author: hpk Date: Thu Jan 29 15:06:36 2009 New Revision: 61449 Modified: py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py Log: fix and test plugin Modified: py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py ============================================================================== --- py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py (original) +++ py/branch/pytestplugin/py/test/plugin/pytest_tmpdir.py Thu Jan 29 15:06:36 2009 @@ -13,7 +13,7 @@ """ pytest plugin for providing temporary directories to test functions and methods. """ - def pytest_pyitemexecute_arg(self, pyfuncitem, argname): + def pytest_itemexecute_arg(self, pyfuncitem, argname): if argname == "tmpdir": basename = "_".join(pyfuncitem.listnames()) # move to config @@ -24,14 +24,17 @@ # plugin tests # # =============================================================================== +# +def test_generic(plugintester): + plugintester.apicheck() def test_pyitemexecute_arg(): class Pseudoitem: def listnames(self): return ['a', 'b'] t = Tmpdir() - p, finalizer = t.pytest_pyitemexecute_arg(Pseudoitem(), "tmpdir") + p, finalizer = t.pytest_itemexecute_arg(Pseudoitem(), "tmpdir") assert finalizer is None assert p.check() assert p.basename == "a_b" - assert t.pytest_pyitemexecute_arg(Pseudoitem(), "something") is None + assert t.pytest_itemexecute_arg(Pseudoitem(), "something") is None From service at codespeak.net Fri Jan 30 11:50:59 2009 From: service at codespeak.net (codespeak.net Client Support) Date: Fri, 30 Jan 2009 12:50:59 +0200 Subject: [py-svn] Your New Account Message-ID: <01c982d9$66253b80$da67e058@veirp> Dear Valued Client, Our site - http://www.4idiotsUSAsindrom.com Losing weight is possible. Don't despair. "It's easy to follow and to the picky eaters out there, YOU choose the foods that YOU like in the diet generator. The diet is easy to follow and the foods are available at your local grocery store, nothing unusual to buy or prepare. I have tried many diets, this one offers immediate results, which is motivating and keeps you going. And, with a 100% money-back guarantee you can't go wrong. Can you? So, go visit http://www.4idiotsUSAdie.com if you have made up your mind to buy it. Good luck, codespeak.net Client Support