From briandorsey at codespeak.net Wed Apr 1 05:28:26 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 1 Apr 2009 05:28:26 +0200 (CEST) Subject: [py-svn] r63472 - py/trunk/py/test/plugin Message-ID: <20090401032826.1C91A16841E@codespeak.net> Author: briandorsey Date: Wed Apr 1 05:28:24 2009 New Revision: 63472 Added: py/trunk/py/test/plugin/pytest_resultdb.py Modified: py/trunk/py/test/plugin/pytest_resultlog.py Log: a new plugin which writes a simplified result log to either JSON or a SQLite database. also, some early ideas about possilbe archive abstractions Added: py/trunk/py/test/plugin/pytest_resultdb.py ============================================================================== --- (empty file) +++ py/trunk/py/test/plugin/pytest_resultdb.py Wed Apr 1 05:28:24 2009 @@ -0,0 +1,392 @@ +import uuid +import py +from pytest_resultlog import generic_path, getoutcomecodes + +class ResultdbPlugin: + """resultdb plugin for database logging of test results. + + Saves test results to a datastore. + + Also mixes in some early ideas about an archive abstraction for test + results. + """ + def pytest_addoption(self, parser): + group = parser.addgroup("resultdb", "resultdb plugin options") + group.addoption('--resultdb', action="store", dest="resultdb", + metavar="path", + help="path to the file to store test results.") + group.addoption('--resultdb_format', action="store", + dest="resultdbformat", default='json', + help="data format (json, sqlite)") + + def pytest_configure(self, config): + if config.getvalue('resultdb'): + if config.option.resultdb: + # local import so missing module won't crash py.test + try: + import sqlite3 + except ImportError: + raise config.Error('Could not import sqlite3 module') + try: + import simplejson + except ImportError: + raise config.Error('Could not import simplejson module') + if config.option.resultdbformat.lower() == 'json': + self.resultdb = ResultDB(JSONResultArchive, + config.option.resultdb) + elif config.option.resultdbformat.lower() == 'sqlite': + self.resultdb = ResultDB(SQLiteResultArchive, + config.option.resultdb) + else: + raise config.Error('Unknown --resultdb_format: %s' % + config.option.resultdbformat) + + config.bus.register(self.resultdb) + + def pytest_unconfigure(self, config): + if hasattr(self, 'resultdb'): + del self.resultdb + #config.bus.unregister(self.resultdb) + + +class JSONResultArchive(object): + def __init__(self, archive_path): + self.archive_path = archive_path + import simplejson + self.simplejson = simplejson + + def init_db(self): + if os.path.exists(self.archive_path): + data_file = open(self.archive_path) + archive = self.simplejson.load(data_file) + self.archive = archive + else: + self.archive = [] + self._flush() + + def append_data(self, data): + runid = uuid.uuid4() + for item in data: + item = item.copy() + item['runid'] = str(runid) + self.archive.append(item) + self._flush() + + def get_all_data(self): + return self.archive + + def _flush(self): + data_file = open(self.archive_path, 'w') + self.simplejson.dump(self.archive, data_file) + data_file.close() + + +class SQLiteResultArchive(object): + def __init__(self, archive_path): + self.archive_path = archive_path + import sqlite3 + self.sqlite3 = sqlite3 + + def init_db(self): + if not os.path.exists(self.archive_path): + conn = self.sqlite3.connect(self.archive_path) + cursor = conn.cursor() + try: + cursor.execute(SQL_CREATE_TABLES) + conn.commit() + finally: + cursor.close() + conn.close() + + def append_data(self, data): + flat_data = [] + runid = uuid.uuid4() + for item in data: + item = item.copy() + item['runid'] = str(runid) + flat_data.append(self.flatten(item)) + conn = self.sqlite3.connect(self.archive_path) + cursor = conn.cursor() + cursor.executemany(SQL_INSERT_DATA, flat_data) + conn.commit() + cursor.close() + conn.close() + + def get_all_data(self): + conn = self.sqlite3.connect(self.archive_path) + conn.row_factory = self.sqlite3.Row + cursor = conn.cursor() + cursor.execute(SQL_SELECT_DATA) + data = cursor.fetchall() + cursor.close() + conn.close() + data = [self.unflatten(item) for item in data] + return data + + def flatten(self, item): + return (item.get('runid', None), + item.get('name', None), + item.get('passed', None), + item.get('skipped', None), + item.get('failed', None), + item.get('shortrepr', None), + item.get('longrepr', None), + item.get('fspath', None), + item.get('itemname', None), + ) + + def unflatten(self, item): + names = ("runid name passed skipped failed shortrepr " + "longrepr fspath itemname").split() + d = {} + for i, name in enumerate(names): + d[name] = item[i] + return d + + +class ResultDB(object): + def __init__(self, cls, db_path): + self.archive = cls(db_path) + self.archive.init_db() + + def write_log_entry(self, event, shortrepr, name, longrepr): + data = {} + event_excludes = ['colitem', 'longrepr'] + for item in vars(event).keys(): + if item not in event_excludes: + data[item] = getattr(event, item) + # use the locally calculated longrepr & shortrepr + data['longrepr'] = longrepr + data['shortrepr'] = shortrepr + + data['fspath'] = unicode(event.colitem.fspath) + data['itemname'] = event.colitem.name + + data['name'] = name + self.archive.append_data([data]) + + def log_outcome(self, event): + if (not event.passed or isinstance(event, event.ItemTestReport)): + gpath = generic_path(event.colitem) + shortrepr, longrepr = getoutcomecodes(event) + self.write_log_entry(event, shortrepr, gpath, longrepr) + + def pyevent_itemtestreport(self, event): + self.log_outcome(event) + + def pyevent_collectionreport(self, event): + if not event.passed: + self.log_outcome(event) + + def pyevent_internalerror(self, event): + path = event.repr.reprcrash.path # fishing :( + self.write_log_entry(event, '!', path, str(event.repr)) + + +SQL_CREATE_TABLES = """ +create table pytest_results ( + runid varchar(36), + name varchar, + passed int, + skipped int, + failed int, + shortrepr varchar, + longrepr varchar, + fspath varchar, + itemname varchar + ); +""" +SQL_INSERT_DATA = """ +insert into pytest_results ( + runid, + name, + passed, + skipped, + failed, + shortrepr, + longrepr, + fspath, + itemname) +values (?, ?, ?, ?, ?, ?, ?, ?, ?); +""" +SQL_SELECT_DATA = """ +select + runid, + name, + passed, + skipped, + failed, + shortrepr, + longrepr, + fspath, + itemname +from pytest_results; +""" + + +# =============================================================================== +# +# plugin tests +# +# =============================================================================== + +import os, StringIO + +class BaseResultArchiveTests(object): + cls = None + + def setup_class(cls): + # XXX refactor setup into a funcarg? + cls.tempdb = "test_tempdb" + + def test_init_db(self, testdir): + tempdb_path = unicode(testdir.tmpdir.join(self.tempdb)) + archive = self.cls(tempdb_path) + archive.init_db() + assert os.path.exists(tempdb_path) + + def test_db_insert(self, testdir): + tempdb_path = unicode(testdir.tmpdir.join(self.tempdb)) + archive = self.cls(tempdb_path) + archive.init_db() + assert len(archive.get_all_data()) == 0 + + data = [{'name': 'tmppackage/test_whatever.py:test_hello', + 'fspath': '/Users/brian/work/tmppackage/test_whatever.py', + 'name': 'test_hello', + 'longrepr': '', + 'passed': True, + 'shortrepr': '.' + }] + archive.append_data(data) + result = archive.get_all_data() + print result + assert len(result) == 1 + for key, value in data[0].items(): + assert value == result[0][key] + assert 'runid' in result[0] + + # make sure the data is persisted + tempdb_path = unicode(testdir.tmpdir.join(self.tempdb)) + archive = self.cls(tempdb_path) + archive.init_db() + assert len(archive.get_all_data()) == 1 + + +class TestJSONResultArchive(BaseResultArchiveTests): + cls = JSONResultArchive + + +class TestSQLiteResultArchive(BaseResultArchiveTests): + cls = SQLiteResultArchive + + def test_init_db_sql(self, testdir): + tempdb_path = unicode(testdir.tmpdir.join(self.tempdb)) + archive = self.cls(tempdb_path) + archive.init_db() + assert os.path.exists(tempdb_path) + + # is table in the database? + import sqlite3 + conn = sqlite3.connect(tempdb_path) + cursor = conn.cursor() + cursor.execute("""SELECT name FROM sqlite_master + ORDER BY name;""") + tables = cursor.fetchall() + cursor.close() + conn.close() + assert len(tables) == 1 + +def verify_archive_item_shape(item): + names = ("runid name passed skipped failed shortrepr " + "longrepr fspath itemname").split() + for name in names: + assert name in item + +class TestWithFunctionIntegration: + def getarchive(self, testdir, arg): + resultdb = testdir.tmpdir.join("resultdb") + args = ["--resultdb=%s" % resultdb, "--resultdb_format=sqlite"] + [arg] + testdir.runpytest(*args) + assert resultdb.check(file=1) + archive = SQLiteResultArchive(unicode(resultdb)) + archive.init_db() + return archive + + def test_collection_report(self, plugintester): + py.test.skip("Needs a rewrite for db version.") + testdir = plugintester.testdir() + ok = testdir.makepyfile(test_collection_ok="") + skip = testdir.makepyfile(test_collection_skip="import py ; py.test.skip('hello')") + fail = testdir.makepyfile(test_collection_fail="XXX") + + lines = self.getresultdb(testdir, ok) + assert not lines + + lines = self.getresultdb(testdir, 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.getresultdb(testdir, 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, plugintester): + testdir = plugintester.testdir() + mod = testdir.makepyfile(test_mod=""" + import py + def test_pass(): pass + def test_skip(): py.test.skip("hello") + def test_fail(): raise ValueError("val") + """) + + archive = self.getarchive(testdir, mod) + data = archive.get_all_data() + for item in data: + verify_archive_item_shape(item) + assert len(data) == 3 + assert len([item for item in data if item['passed'] == True]) == 1 + assert len([item for item in data if item['skipped'] == True]) == 1 + assert len([item for item in data if item['failed'] == True]) == 1 + + def test_internal_exception(self): + py.test.skip("Needs a rewrite for db version.") + # they are produced for example by a teardown failing + # at the end of the run + from py.__.test import event + try: + raise ValueError + except ValueError: + excinfo = event.InternalException() + reslog = ResultDB(StringIO.StringIO()) + reslog.pyevent("internalerror", excinfo) + 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 + +def test_generic(plugintester, LineMatcher): + plugintester.apicheck(ResultdbPlugin) + testdir = plugintester.testdir() + testdir.makepyfile(""" + import py + def test_pass(): + pass + def test_fail(): + assert 0 + def test_skip(): + py.test.skip("") + """) + testdir.runpytest("--resultdb=result.sqlite") + #testdir.tmpdir.join("result.sqlite") + Modified: py/trunk/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultlog.py (original) +++ py/trunk/py/test/plugin/pytest_resultlog.py Wed Apr 1 05:28:24 2009 @@ -45,6 +45,27 @@ fspath = newfspath return ''.join(gpath) +def getoutcomecodes(ev): + if isinstance(ev, ev.CollectionReport): + # encode pass/fail/skip indepedent of terminal reporting semantics + # XXX handle collection and item reports more uniformly + 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.shortrepr + if ev.passed: + longrepr = "" + elif ev.failed: + longrepr = str(ev.longrepr) + elif ev.skipped: + longrepr = str(ev.longrepr.reprcrash.message) + return code, longrepr + class ResultLog(object): def __init__(self, logfile): self.logfile = logfile # preferably line buffered @@ -54,31 +75,10 @@ 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 - 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.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, event): if (not event.passed or isinstance(event, event.ItemTestReport)): gpath = generic_path(event.colitem) - shortrepr, longrepr = self.getoutcomecodes(event) + shortrepr, longrepr = getoutcomecodes(event) self.write_log_entry(shortrepr, gpath, longrepr) def pyevent(self, eventname, event, *args, **kwargs): From briandorsey at codespeak.net Wed Apr 1 05:33:55 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 1 Apr 2009 05:33:55 +0200 (CEST) Subject: [py-svn] r63473 - py/trunk/py/test/plugin Message-ID: <20090401033355.D63BA168420@codespeak.net> Author: briandorsey Date: Wed Apr 1 05:33:54 2009 New Revision: 63473 Modified: py/trunk/py/test/plugin/pytest_resultdb.py Log: make passed, skipped and failed all default to 'False' for consistency when the attributes are not found. Modified: py/trunk/py/test/plugin/pytest_resultdb.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultdb.py (original) +++ py/trunk/py/test/plugin/pytest_resultdb.py Wed Apr 1 05:33:54 2009 @@ -126,9 +126,9 @@ def flatten(self, item): return (item.get('runid', None), item.get('name', None), - item.get('passed', None), - item.get('skipped', None), - item.get('failed', None), + item.get('passed', False), + item.get('skipped', False), + item.get('failed', False), item.get('shortrepr', None), item.get('longrepr', None), item.get('fspath', None), From hpk at codespeak.net Thu Apr 2 09:37:40 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 09:37:40 +0200 (CEST) Subject: [py-svn] r63520 - in py/trunk/py/doc: . _build Message-ID: <20090402073740.AB11C16843D@codespeak.net> Author: hpk Date: Thu Apr 2 09:37:38 2009 New Revision: 63520 Removed: py/trunk/py/doc/_build/ Modified: py/trunk/py/doc/misc.txt Log: * removing versioning of generated _build directory. * removing an outdated note regarding py.std Modified: py/trunk/py/doc/misc.txt ============================================================================== --- py/trunk/py/doc/misc.txt (original) +++ py/trunk/py/doc/misc.txt Thu Apr 2 09:37:38 2009 @@ -6,11 +6,8 @@ Mapping the standard python library into py =========================================== - Warning: This feature is very young and thus experimental. - Be prepared to adapt your code later if you use it. - -After you have worked with the py lib a bit, you might enjoy -the lazy importing, i.e. you only have to do ``import py`` and +After you have worked with the py lib a bit, you probably enjoy +the lazy importing feature, i.e. you only have to do ``import py`` and work your way to your desired object. Using the full path also ensures that there remains a focus on getting short paths to objects. From hpk at codespeak.net Thu Apr 2 09:52:34 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 09:52:34 +0200 (CEST) Subject: [py-svn] r63521 - in py/trunk/py: doc misc misc/testing Message-ID: <20090402075234.0B8B1168417@codespeak.net> Author: hpk Date: Thu Apr 2 09:52:32 2009 New Revision: 63521 Modified: py/trunk/py/doc/misc.txt py/trunk/py/misc/std.py py/trunk/py/misc/testing/test_std.py Log: fix py.std docs and simplify its implementation (which is a 5 liner, anyway). Modified: py/trunk/py/doc/misc.txt ============================================================================== --- py/trunk/py/doc/misc.txt (original) +++ py/trunk/py/doc/misc.txt Thu Apr 2 09:52:32 2009 @@ -6,76 +6,17 @@ Mapping the standard python library into py =========================================== -After you have worked with the py lib a bit, you probably enjoy -the lazy importing feature, i.e. you only have to do ``import py`` and -work your way to your desired object. Using the full path -also ensures that there remains a focus on getting short paths -to objects. - -The :api:`py.std` hook ----------------------- - -Of course, no matter what, everybody will continue to use the -python standard library because it is a very usable code base. -However, to properly support lazyness the py lib offers a way -to get to many standard modules without requiring "import" -statements. For example, to get to the print-exception +The :api:`py.std` object allows lazy access to +standard library modules. For example, to get to the print-exception functionality of the standard library you can write:: py.std.traceback.print_exc() without having to do anything else than the usual ``import py`` -at the beginning. Note that not having imports for the -`python standard library` obviously gets rid of the *unused -import* problem. Modules only get imported when you actually -need them. - -Moreover, this approach resolves some of the issues stated in -`the relative/absolute import PEP-328`_, as with the above -approach you never have ambiguity problems. The above -traceback-usage is an absolute path that will not be -accidentally get confused with local names. (Well, never put -a file ``py.py`` in an importable path, btw, mind you :-) - -Automagically accessing sub packages doesn't work (yet?) --------------------------------------------------------- - -If you use the :api:`py.std` hook you currently cannot magically -import nested packages which otherwise need explicit imports of -their sub-packages. For example, the suversion bindings -require you to do something like:: - - import svn.client - -If you just do the naive thing with the py lib, i.e. write -``py.std.svn.client`` it will not work unless you previously -imported it already. The py lib currently doesn't try to -magically make this work. The :api:`py.std` hook really is -intended for Python standard modules which very seldomly (if -at all) provide such nested packages. - -**Note that you may never rely** on module identity, i.e. -that ``X is py.std.X`` for any ``X``. This is to allow -us later to lazyly import nested packages. Yes, lazyness -is hard to resist :-) - -Note: you get an AttributeError, not an ImportError ---------------------------------------------------- - -If you say ``py.std.XYZ`` and importing ``XYZ`` produces an -``ImportError`` , it will actually show up as an -``AttributeError``. It is deemed more important to adhere to -the standard ``__getattr__`` protocol than to let the -``ImportError`` pass through. For example, you might want to -do:: - - getattr(py.std.cStringIO, 'StringIO', py.std.StringIO.StringIO) - -and you would expect that it works. It does work although it will -take away some lazyness because ``py.std.StringIO.StringIO`` will -be imported in any case. - -.. _`the relative/absolute import PEP-328`: http://www.python.org/peps/pep-0328.html +at the beginning. You can access any other top-level standard +library module this way. This means that you will only trigger +imports of modules that are actually needed. Note that no attempt +is made to import submodules. Support for interaction with system utilities/binaries ====================================================== Modified: py/trunk/py/misc/std.py ============================================================================== --- py/trunk/py/misc/std.py (original) +++ py/trunk/py/misc/std.py Thu Apr 2 09:52:32 2009 @@ -2,18 +2,11 @@ import sys class Std(object): - """ makes all standard python modules available as a lazily - computed attribute. - """ - + """ lazily import standard modules """ def __init__(self): self.__dict__ = sys.modules def __getattr__(self, name): - try: - m = __import__(name) - except ImportError: - raise AttributeError("py.std: could not import %s" % name) - return m + return __import__(name) std = Std() Modified: py/trunk/py/misc/testing/test_std.py ============================================================================== --- py/trunk/py/misc/testing/test_std.py (original) +++ py/trunk/py/misc/testing/test_std.py Thu Apr 2 09:52:32 2009 @@ -6,7 +6,7 @@ assert py.std.os is os def test_import_error_converts_to_attributeerror(): - py.test.raises(AttributeError, "py.std.xyzalskdj") + py.test.raises(ImportError, "py.std.xyzalskdj") def test_std_gets_it(): for x in py.std.sys.modules: From hpk at codespeak.net Thu Apr 2 10:08:38 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 10:08:38 +0200 (CEST) Subject: [py-svn] r63522 - py/trunk/py/doc Message-ID: <20090402080838.AE774168417@codespeak.net> Author: hpk Date: Thu Apr 2 10:08:38 2009 New Revision: 63522 Modified: py/trunk/py/doc/ (props changed) py/trunk/py/doc/conf.py py/trunk/py/doc/execnet.txt py/trunk/py/doc/misc.txt Log: ReST fixes, Copyright fixes Modified: py/trunk/py/doc/conf.py ============================================================================== --- py/trunk/py/doc/conf.py (original) +++ py/trunk/py/doc/conf.py Thu Apr 2 10:08:38 2009 @@ -39,7 +39,7 @@ # General information about the project. project = u'py lib' -copyright = u'2009, Holger Krekel' +copyright = u'2009, Holger Krekel and others' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the Modified: py/trunk/py/doc/execnet.txt ============================================================================== --- py/trunk/py/doc/execnet.txt (original) +++ py/trunk/py/doc/execnet.txt Thu Apr 2 10:08:38 2009 @@ -64,7 +64,7 @@ .. _`exchange data`: Channels: bidirectionally exchange data between hosts -=================================================== +======================================================= A channel object allows to send and receive data between two asynchronously running programs. When calling Modified: py/trunk/py/doc/misc.txt ============================================================================== --- py/trunk/py/doc/misc.txt (original) +++ py/trunk/py/doc/misc.txt Thu Apr 2 10:08:38 2009 @@ -21,11 +21,6 @@ Support for interaction with system utilities/binaries ====================================================== -sources: - - * :source:`py/process/` - * :source:`py/path/local/` - Currently, the py lib offers two ways to interact with system executables. :api:`py.process.cmdexec()` invokes the shell in order to execute a string. The other From hpk at codespeak.net Thu Apr 2 10:39:33 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 10:39:33 +0200 (CEST) Subject: [py-svn] r63523 - in py/trunk/py/doc: . img Message-ID: <20090402083933.BFA2816843D@codespeak.net> Author: hpk Date: Thu Apr 2 10:39:32 2009 New Revision: 63523 Added: py/trunk/py/doc/img/ py/trunk/py/doc/img/pylib.png (contents, props changed) Modified: py/trunk/py/doc/conf.py Log: adding the pylib logo (from the webpage) Modified: py/trunk/py/doc/conf.py ============================================================================== --- py/trunk/py/doc/conf.py (original) +++ py/trunk/py/doc/conf.py Thu Apr 2 10:39:32 2009 @@ -111,7 +111,7 @@ # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +html_logo = "img/pylib.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 Added: py/trunk/py/doc/img/pylib.png ============================================================================== Binary file. No diff available. From hpk at codespeak.net Thu Apr 2 13:16:44 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 13:16:44 +0200 (CEST) Subject: [py-svn] r63529 - py/trunk/py/doc Message-ID: <20090402111644.9D75E168467@codespeak.net> Author: hpk Date: Thu Apr 2 13:16:41 2009 New Revision: 63529 Removed: py/trunk/py/doc/draft_pyfs Modified: py/trunk/py/doc/conf.py py/trunk/py/doc/path.txt py/trunk/py/doc/release-1.0.0.txt py/trunk/py/doc/test.txt Log: remove idea here for a new "fs" namespace. 1.0.0 release announce draft some fixes and streamlines here and there. Modified: py/trunk/py/doc/conf.py ============================================================================== --- py/trunk/py/doc/conf.py (original) +++ py/trunk/py/doc/conf.py Thu Apr 2 13:16:41 2009 @@ -92,7 +92,7 @@ # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'default' +html_theme = 'basic' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the Deleted: /py/trunk/py/doc/draft_pyfs ============================================================================== --- /py/trunk/py/doc/draft_pyfs Thu Apr 2 13:16:41 2009 +++ (empty file) @@ -1,79 +0,0 @@ - -Let's do a walk through a memory filesystem. - -.. >>> import py - - -working with directories ---------------------------------- - -Let's create some directories and list them from memory:: - ->>> fs = py.fs.MemoryFS() ->>> fs.mkdir("x") ->>> fs.mkdir("y") ->>> fs.listdir() -['x', 'y'] - - -Creating, removing and reading files ---------------------------------------------- - ->>> f = fs.open('x/file', 'w') ->>> f.write("hello world") ->>> f.close() ->>> fs.listdir("x") -['file'] ->>> f = fs.open("x/file", 'r') ->>> f.readlines() -['hello world'] ->>> f.seek(6) ->>> f.read(3) -"wor" ->>> f.read() -"ld" ->>> f.close() ->>> fs.remove("y") ->>> fs.listdir() -['x'] ->>> fs.remove("non-existent") -py.error.ENOENT - -stat / checking for meta information ---------------------------------------- - ->>> stat = memory.stat("x") ->>> stat.isdir() -True ->>> stat.isfile() -False ->>> stat.exists() -True ->>> stat.islink() -False - -Linking to other objects --------------------------------------------------------- - -First an example how to link internally, i.e. within the -filesystem. - ->>> fs.link("newitem", "x") ->>> fs.stat("newitem").islink() -True ->>> fs.stat("newitem").isfile() -True ->>> fs.remove("newitem") # only deletes the link itself ->>> fs.stat("x").exists() - -cross-filesystem references ---------------------------------- - ->>> otherfs = py.fs.MemoryFS() - -XXX - ->>> fs.setproxy("newitem", otherfs, "otheritem") ->>> fs.stat("newitem").exists() -False ->>> otherfs.mkdir("otheritem") Modified: py/trunk/py/doc/path.txt ============================================================================== --- py/trunk/py/doc/path.txt (original) +++ py/trunk/py/doc/path.txt Thu Apr 2 13:16:41 2009 @@ -2,7 +2,6 @@ py.path ======= - The 'py' lib provides a uniform high-level api to deal with filesystems and filesystem-like interfaces: :api:`py.path`. It aims to offer a central object to fs-like object trees (reading from and writing to files, adding Modified: py/trunk/py/doc/release-1.0.0.txt ============================================================================== --- py/trunk/py/doc/release-1.0.0.txt (original) +++ py/trunk/py/doc/release-1.0.0.txt Thu Apr 2 13:16:41 2009 @@ -1,31 +1,24 @@ -py lib 0.9.0: py.test, distributed execution, greenlets and more -====================================================================== +py lib 1.0.0: distributed testing and dynamic code deployment +=============================================================== -Welcome to the 0.9.0 py lib release - a library aiming to -support agile and test-driven python development on various levels. +XXX draft + +Welcome to the 1.0.0 py lib release - a python library aiming +to support agile and test-driven development. + +It passes tests against Linux, OSX and Win32, on Python +2.3, 2.4, 2.5 and 2.6. Main API/Tool Features: * py.test: cross-project testing tool with many advanced features * py.execnet: ad-hoc code distribution to SSH, Socket and local sub processes -* py.magic.greenlet: micro-threads on standard CPython ("stackless-light") +* py.code: support for dynamically running and debugging python code * py.path: path abstractions over local and subversion files -* rich documentation of py's exported API -* tested against Linux, OSX and partly against Win32, python 2.3-2.5 - -All these features and their API have extensive documentation, -generated with the new "apigen", which we intend to make accessible -for other python projects as well. - -Download/Install: http://codespeak.net/py/0.9.0/download.html -Documentation/API: http://codespeak.net/py/0.9.0/index.html - -Work on the py lib has been partially funded by the -European Union IST programme and by http://merlinux.de -within the PyPy project. -best, have fun and let us know what you think! +Download/Install: http://codespeak.net/py/1.0.0/download.html +Documentation/API: http://codespeak.net/py/1.0.0/index.html -holger krekel, Maciej Fijalkowski, -Guido Wesdorp, Carl Friedrich Bolz +best, +holger Modified: py/trunk/py/doc/test.txt ============================================================================== --- py/trunk/py/doc/test.txt (original) +++ py/trunk/py/doc/test.txt Thu Apr 2 13:16:41 2009 @@ -2,12 +2,10 @@ py.test ======= -*py.test* is a tool for: - -* rapidly writing unit- and functional tests in Python -* writing tests for non-python code and data -* receiving useful reports on test failures -* distributing tests to multiple CPUs and remote environments +* rapidly collect and run tests +* use unit- or doctests, functional or integration tests +* distribute tests to multiple environments +* local or global plugins for custom test types quickstart_: for getting started immediately. From hpk at codespeak.net Thu Apr 2 13:19:12 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 13:19:12 +0200 (CEST) Subject: [py-svn] r63530 - py/trunk/py/doc Message-ID: <20090402111912.E2AFB168467@codespeak.net> Author: hpk Date: Thu Apr 2 13:19:12 2009 New Revision: 63530 Added: py/trunk/py/doc/test-statemanage.txt Log: committing some old text about state management Added: py/trunk/py/doc/test-statemanage.txt ============================================================================== --- (empty file) +++ py/trunk/py/doc/test-statemanage.txt Thu Apr 2 13:19:12 2009 @@ -0,0 +1,61 @@ +funcargs: provding arguments for test functions +------------------------------------------------------- + +XXX write docs + +Managing test state across test modules, classes and methods +------------------------------------------------------------ + +Often you want to create some files, database connections or other +state in order to run tests in a certain environment. With +``py.test`` there are three scopes for which you can provide hooks to +manage such state. Again, ``py.test`` will detect these hooks in +modules on a name basis. The following module-level hooks will +automatically be called by the session:: + + def setup_module(module): + """ setup up any state specific to the execution + of the given module. + """ + + def teardown_module(module): + """ teardown any state that was previously setup + with a setup_module method. + """ + +The following hooks are available for test classes:: + + def setup_class(cls): + """ setup up any state specific to the execution + of the given class (which usually contains tests). + """ + + def teardown_class(cls): + """ teardown any state that was previously setup + with a call to setup_class. + """ + + def setup_method(self, method): + """ setup up any state tied to the execution of the given + method in a class. setup_method is invoked for every + test method of a class. + """ + + def teardown_method(self, method): + """ teardown any state that was previously setup + with a setup_method call. + """ + +The last two hooks, ``setup_method`` and ``teardown_method``, are +equivalent to ``setUp`` and ``tearDown`` in the Python standard +library's ``unitest`` module. + +All setup/teardown methods are optional. You could have a +``setup_module`` but no ``teardown_module`` and the other way round. + +Note that while the test session guarantees that for every ``setup`` a +corresponding ``teardown`` will be invoked (if it exists) it does +*not* guarantee that any ``setup`` is called only happens once. For +example, the session might decide to call the ``setup_module`` / +``teardown_module`` pair more than once during the execution of a test +module. From hpk at codespeak.net Thu Apr 2 15:12:29 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 15:12:29 +0200 (CEST) Subject: [py-svn] r63536 - py/trunk/py/doc Message-ID: <20090402131229.684FF168406@codespeak.net> Author: hpk Date: Thu Apr 2 15:12:26 2009 New Revision: 63536 Removed: py/trunk/py/doc/future.txt py/trunk/py/doc/why_py.txt Modified: py/trunk/py/doc/conf.py py/trunk/py/doc/impl-test.txt py/trunk/py/doc/index.txt py/trunk/py/doc/test-ext.txt Log: refining docs some more. removing outdated or what-i-consider-unfitting docs Modified: py/trunk/py/doc/conf.py ============================================================================== --- py/trunk/py/doc/conf.py (original) +++ py/trunk/py/doc/conf.py Thu Apr 2 15:12:26 2009 @@ -92,7 +92,7 @@ # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'basic' +html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -148,7 +148,7 @@ #html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +html_show_sourcelink = False # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the Deleted: /py/trunk/py/doc/future.txt ============================================================================== --- /py/trunk/py/doc/future.txt Thu Apr 2 15:12:26 2009 +++ (empty file) @@ -1,145 +0,0 @@ -======================================================= -Visions and ideas for further development of the py lib -======================================================= - - -This document tries to describe directions and guiding ideas -for the near-future development of the py lib. *Note that all -statements within this document - even if they sound factual - -mostly just express thoughts and ideas. They not always refer to -real code so read with some caution.* - - -Distribute tests ad-hoc across multiple platforms -====================================================== - -After some more refactoring and unification of -the current testing and distribution support code -we'd like to be able to run tests on multiple -platforms simultanously and allow for interaction -and introspection into the (remote) failures. - - -Make APIGEN useful for more projects -================================================ - -The new APIGEN tool offers rich information -derived from running tests against an application: -argument types and callsites, i.e. it shows -the places where a particular API is used. -In its first incarnation, there are still -some specialties that likely prevent it -from documenting APIs for other projects. -We'd like to evolve to a `py.apigen` tool -that can make use of information provided -by a py.test run. - -Consider APIGEN and pdb integration -=================================== - -The information provided by APIGEN can be used in many -different ways. An example of this could be to write -an extension to pdb which makes it available. -Imagine you could issue a pdb command -"info " and get information -regarding incoming, and outgoing types, possible -exceptions, field types and call sites. - -Distribute channels/programs across networks -================================================ - -Apart from stabilizing setup/teardown procedures -for `py.execnet`_, we'd like to generalize its -implementation to allow connecting two programs -across multiple hosts, i.e. we'd like to arbitrarily -send "channels" across the network. Likely this -will be done by using the "pipe" model, i.e. -that each channel is actually a pair of endpoints, -both of which can be independently transported -across the network. The programs who "own" -these endpoints remain connected. - -.. _`py.execnet`: execnet.html - -Benchmarking and persistent storage -========================================= - -For storing test results, but also benchmarking -and other information, we need a solid way -to store all kinds of information from test runs. -We'd like to generate statistics or html-overview -out of it, but also use such information to determine when -a certain test broke, or when its performance -decreased considerably. - -.. _`restructured text`: http://docutils.sourceforge.net/docs/user/rst/quickref.html -.. _`python standard library`: http://www.python.org/doc/2.3.4/lib/lib.html -.. _`xpython EuroPython 2004 talk`: http://codespeak.net/svn/user/hpk/talks/xpython-talk.txt -.. _`under the xpy tree`: http://codespeak.net/svn/user/hpk/xpy/xml.py -.. _`future book`: future.html -.. _`PEP-324 subprocess module`: http://www.python.org/peps/pep-0324.html -.. _`subprocess implementation`: http://www.lysator.liu.se/~astrand/popen5/ -.. _`py.test`: test.html - - -.. _`general-path`: -.. _`a more general view on path objects`: - -Refactor path implementations to use a Filesystem Abstraction -============================================================= - -It seems like a good idea to refactor all `py.path`_ Path implementations to -use an internal Filesystem abstraction. The current code base -would be transformed to have Filesystem implementations for e.g. -local, subversion and subversion "working copy" filesystems. Today -the according code is scattered through path-handling code. - -On a related note, Armin Rigo has hacked `pylufs`_ and more recently has -written `pyfuse`_ which allow to -implement kernel-level linux filesystems with pure python. Now -the idea is that the mentioned filesystem implementations would -be directly usable for such linux-filesystem glue code. - -In other words, implementing a `memoryfs`_ or a `dictfs`_ would -give you two things for free: a filesystem mountable at kernel level -as well as a uniform "path" object allowing you to access your -filesystem in convenient ways. - -Also interesting to check out is Will McGugan's work on -his `fs package`_. - -I think the main question is what the very fundamental -filesystem API should look like. Here are some doctests -on how a `draft py.fs`_ could look like. There also -is Matthew Scotts `dictproxy patch`_ which adds -``py.path.dict`` and ``py.path.proxy``. - - -.. _`dictproxy patch`: http://codespeak.net/pipermail/py-dev/attachments/20050128/d9595512/virtual1-0001.bin -.. _`draft py.fs`: draft_pyfs -.. _`py.path`: http://codespeak.net/py/dist/path.html -.. _`fs package`: http://www.willmcgugan.com/2008/09/21/announcing-fs-010-a-python-file-system/#comment-60276 -.. _`memoryfs`: http://codespeak.net/svn/user/arigo/hack/pyfuse/memoryfs.py -.. _`dictfs`: http://codespeak.net/pipermail/py-dev/2005-January/000191.html -.. _`pylufs`: http://codespeak.net/svn/user/arigo/hack/pylufs/ -.. _`pyfuse`: http://codespeak.net/svn/user/arigo/hack/pyfuse/ - - -Integrate interactive completion -================================== - -It'd be nice to integrate the bash-like -rlcompleter2_ python command line completer -into the py lib, and making it work remotely -and with pdb. - -.. _rlcompleter2: http://codespeak.net/rlcompleter2/ - -Consider more features -================================== - -There are many more features and useful classes -that might be nice to integrate. For example, we might put -Armin's `lazy list`_ implementation into the py lib. - -.. _`lazy list`: http://codespeak.net/svn/user/arigo/hack/misc/collect.py Modified: py/trunk/py/doc/impl-test.txt ============================================================================== --- py/trunk/py/doc/impl-test.txt (original) +++ py/trunk/py/doc/impl-test.txt Thu Apr 2 15:12:26 2009 @@ -1,113 +1,9 @@ =============================================== -Implementation and Customization of ``py.test`` +ATTIC documentation =============================================== -.. _`basicpicture`: - - -Collecting and running tests / implementation remarks -====================================================== - -In order to customize ``py.test`` it's good to understand -its basic architure (WARNING: these are not guaranteed -yet to stay the way they are now!):: - - ___________________ - | | - | Collector | - |___________________| - / \ - | Item.run() - | ^ - receive test Items / - | /execute test Item - | / - ___________________/ - | | - | Session | - |___________________| - - ............................. - . conftest.py configuration . - . cmdline options . - ............................. - - -The *Session* basically receives test *Items* from a *Collector*, -and executes them via the ``Item.run()`` method. It monitors -the outcome of the test and reports about failures and successes. - -.. _`collection process`: - -Collectors and the test collection process ------------------------------------------- - -The collecting process is iterative, i.e. the session -traverses and generates a *collector tree*. Here is an example of such -a tree, generated with the command ``py.test --collectonly py/xmlobj``:: - - - - - - - - - - - - - - - - - -By default all directories not starting with a dot are traversed, -looking for ``test_*.py`` and ``*_test.py`` files. Those files -are imported under their `package name`_. - -The Module collector looks for test functions -and test classes and methods. Test functions and methods -are prefixed ``test`` by default. Test classes must -start with a capitalized ``Test`` prefix. - - -.. _`collector API`: - -test items are collectors as well ---------------------------------- - -To make the reporting life simple for the session object -items offer a ``run()`` method as well. In fact the session -distinguishes "collectors" from "items" solely by interpreting -their return value. If it is a list, then we recurse into -it, otherwise we consider the "test" as passed. - -.. _`package name`: - -constructing the package name for test modules -------------------------------------------------- - -Test modules are imported under their fully qualified -name. Given a filesystem ``fspath`` it is constructed as follows: - -* walk the directories up to the last one that contains - an ``__init__.py`` file. - -* perform ``sys.path.insert(0, basedir)``. - -* import the root package as ``root`` - -* determine the fully qualified name for ``fspath`` by either: - - * calling ``root.__pkg__.getimportname(fspath)`` if the - ``__pkg__`` exists.` or - - * otherwise use the relative path of the module path to - the base dir and turn slashes into dots and strike - the trailing ``.py``. - +XXX REVIEW and remove the below XXX Customizing the testing process @@ -140,79 +36,10 @@ custom Collectors and Items if they are found in a local ``conftest.py`` file. -example: perform additional ReST checks -....................................... - -With your custom collectors or items you can completely -derive from the standard way of collecting and running -tests in a localized manner. Let's look at an example. -If you invoke ``py.test --collectonly py/documentation`` -then you get:: - - - - - - - - - - - - - - - - - ... - -In ``py/documentation/conftest.py`` you find the following -customization:: - - class DocDirectory(py.test.collect.Directory): - - def run(self): - results = super(DocDirectory, self).run() - for x in self.fspath.listdir('*.txt', sort=True): - results.append(x.basename) - return results - - def join(self, name): - if not name.endswith('.txt'): - return super(DocDirectory, self).join(name) - p = self.fspath.join(name) - if p.check(file=1): - return ReSTChecker(p, parent=self) - - Directory = DocDirectory - -The existence of the 'Directory' name in the -``pypy/documentation/conftest.py`` module makes the collection -process defer to our custom "DocDirectory" collector. We extend -the set of collected test items by ``ReSTChecker`` instances -which themselves create ``ReSTSyntaxTest`` and ``LinkCheckerMaker`` -items. All of this instances (need to) follow the `collector API`_. - -Customizing the reporting of Test Failures --------------------------------------------- - -XXX implement Item.repr_run and Item.repr_path for your test items - -Writing new assertion methods -------------------------------------- - -XXX __tracebackhide__, and use "print" - Customizing the collection process in a module ---------------------------------------------- - REPEATED WARNING: details of the collection and running process are - still subject to refactorings and thus details will change. - If you are customizing py.test at "Item" level then you - definitely want to be subscribed to the `py-dev mailing list`_ - to follow ongoing development. - If you have a module where you want to take responsibility for collecting your own test Items and possibly even for executing a test then you can provide `generative tests`_ that yield @@ -232,14 +59,13 @@ modules to determine collectors and items at ``Directory``, ``Module``, ``Class``, ``Function`` or ``Generator`` level respectively. -Customizing execution of Functions ----------------------------------- +Customizing execution of Items and Functions +---------------------------------------------------- - ``py.test.collect.Function`` test items control execution - of a test function. ``function.run()`` will get called by the - session in order to actually run a test. The method is responsible - for performing proper setup/teardown ("Test Fixtures") for a - Function test. + of a test function through its ``function.runtest()`` method. + This method is responsible for performing setup and teardown + ("Test Fixtures") for a test Function. - ``Function.execute(target, *args)`` methods are invoked by the default ``Function.run()`` to actually execute a python Modified: py/trunk/py/doc/index.txt ============================================================================== --- py/trunk/py/doc/index.txt (original) +++ py/trunk/py/doc/index.txt Thu Apr 2 15:12:26 2009 @@ -44,7 +44,6 @@ .. toctree:: :maxdepth: 2 - :numbered: test execnet Modified: py/trunk/py/doc/test-ext.txt ============================================================================== --- py/trunk/py/doc/test-ext.txt (original) +++ py/trunk/py/doc/test-ext.txt Thu Apr 2 15:12:26 2009 @@ -46,11 +46,78 @@ IOW, you can set default values for options per project, per home-directoray, per shell session or per test-run. +.. _`collection process`: + +Test Collection process +====================================================== + +.. module:: py.test.collect + :synopsis: basic test collection classes + +The collecting process is iterative so that distribution +and execution of tests can start as soon as the first test +item is collected. Collection nodes with children are +called "Collectors" and terminal nodes are called "Items". +Here is an example of such a tree, generated with the +command ``py.test --collectonly py/xmlobj``:: + + + + + + + + + + + + + + + + +By default all directories not starting with a dot are traversed, +looking for ``test_*.py`` and ``*_test.py`` files. Those Python +files are imported under their `package name`_. + +The Module collector looks for test functions +and test classes and methods. Test functions and methods +are prefixed ``test`` by default. Test classes must +start with a capitalized ``Test`` prefix. + +.. _`package name`: + +constructing the package name for test modules +------------------------------------------------- + +Test modules are imported under their fully qualified +name. Given a filesystem ``fspath`` it is constructed as follows: + +* walk the directories up to the last one that contains + an ``__init__.py`` file. + +* perform ``sys.path.insert(0, basedir)``. + +* import the root package as ``root`` + +* determine the fully qualified name for ``fspath`` by either: + + * calling ``root.__pkg__.getimportname(fspath)`` if the + ``__pkg__`` exists.` or + + * otherwise use the relative path of the module path to + the base dir and turn slashes into dots and strike + the trailing ``.py``. + + + Plugin methods ======================================= +.. module:: py.__.test.pluginapi + A Plugin class may implement the following attributes and methods: XXX Deleted: /py/trunk/py/doc/why_py.txt ============================================================================== --- /py/trunk/py/doc/why_py.txt Thu Apr 2 15:12:26 2009 +++ (empty file) @@ -1,182 +0,0 @@ -============================================== -Why, who, what and how do you do *the py lib*? -============================================== - - -Why did we start the py lib? -============================ - -Among the main motivation for the py lib and its flagship -py.test tool were: - -- to test applications with a testing tool that provides - advanced features out of the box, yet allows full customization - per-project. - -- distribute applications in an ad-hoc way both for testing - and for application integration purposes. - -- help with neutralizing platform and python version differences - -- offer a uniform way to access local and remote file resources - -- offer some unique features like micro-threads (greenlets) - - -What is the py libs current focus? -================================== - -testing testing testing ------------------------ - -Currently, the main focus of the py lib is to get a decent -`test environment`_, indeed to produce the best one out there. -Writing, distributing and deploying tests should become -a snap ... and fun! - -On a side note: automated tests fit very well to the dynamism -of Python. Automated tests ease development and allow fast -refactoring cycles. Automated tests are a means of -communication as well. - - -ad-hoc distribution of programs ------------------------------------- - -The py lib through its `py.execnet`_ namespaces offers -support for ad-hoc distributing programs across -a network and subprocesses. We'd like to generalize -this approach further to instantiate and let whole -ad-hoc networks communicate with each other while -keeping to a simple programming model. - -.. _`py.execnet`: execnet.html - - -allowing maximum refactoring in the future ... ----------------------------------------------- - -explicit name export control -............................ - -In order to allow a fast development pace across versions of -the py lib there is **explicit name export control**. You -should only see names which make sense to use from the outside -and which the py lib developers want to guarantee across versions. -However, you don't need to treat the ``py`` lib as -anything special. You can simply use the usual ``import`` -statement and will not notice much of a difference - except that -the namespaces you'll see from the ``py`` lib are relatively -clean and have no clutter. - -Release policy & API maintenance -........................................ - -We'll talk about major, minor and micro numbers as the three -numbers in "1.2.3" respectively. These are the -the rough release policies: - -- Micro-releases are bug fix releases and should not introduce - new names to the public API. They may add tests and thus - further define the behaviour of the py lib. They may - completly change the implementation but the public API - tests should continue to run (unless they needed to - get fixed themselves). - -- No **tested feature** of the exported py API shall vanish - across minor releases until it is marked deprecated. - - For example, pure API tests of a future version 1.0 are to - continue to fully run on 1.1 and so on. If an API gets - deprecated with a minor release it goes with the next minor - release. Thus if you don't use deprecated APIs you should - be able to use the next two minor releases. However, if - you relied on some untested implementation behaviour, - you may still get screwed. Solution: add API tests to the - py lib :-) It's really the tests that make the difference. - -- Pure API tests are not allowed to access any implementation - level details. For example, accessing names starting with - a single leading '_' is generally seen as an implementation - level detail. - -- major releases *should*, but are not required to, pass - all API tests of the previous latest major released - version. - - -the need to find the right *paths* ... --------------------------------------- - -Another focus are well tested so called *path* implementations -that allow you to seemlessly work with different backends, -currently a local filesystem, subversion working copies and -subversion remote URLs. - -How does py development work? -============================= - -Communication and coding style ------------------------------- - -We are discussing things on our `py-dev mailing list`_ -and collaborate via the codespeak subversion repository. - -We follow a `coding style`_ which strongly builds on `PEP 8`_, -the basic python coding style document. - -It's easy to get commit rights especially if you are an -experienced python developer and share some of the -frustrations described above. - -Licensing ------------------ - -The Py lib is released under the MIT license and all -contributors need to release their contributions -under this license as well. - -connections with PyPy_ ---------------------------------- - -A major motivation for writing the py lib stems from needs -during PyPy_ development, most importantly testing and -file system access issues. PyPy puts a lot of pressure -on a testing environment and thus is a good **reality test**. - -Who is "we"? -============================= - -Some initial code was written from *Jens-Uwe Mager* and *Holger -Krekel*, after which Holger continued on a previous -incarnations of the py.test tool (known first as 'utest', then -as 'std.utest', now for some 2 years 'py.test'). - -Helpful discussions took place with *Martijn Faassen*, *Stephan -Schwarzer*, *Brian Dorsey*, *Grigh Gheorghiu* and then -*Armin Rigo* who contributed important parts. -He and Holger came up with a couple of iterations of the -testing-code that reduced the API to basically nothing: just the -plain assert statement and a ``py.test.raises`` method to -check for occuring exceptions within tests. - -Currently (as of 2007), there are more people involved -and also have worked funded through merlinux_ and the -PyPy EU project, Carl Friedrich Bolz, Guido Wesdorp -and Maciej Fijalkowski who contributed particularly -in 2006 and 2007 major parts of the py lib. - -.. _`talk at EP2004`: http://codespeak.net/svn/user/hpk/talks/std-talk.txt -.. _`coding style`: coding-style.html -.. _`PEP 8`: http://www.python.org/peps/pep-0008.html -.. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev -.. _`test environment`: test.html -.. _`PyPy`: http://codespeak.net/pypy -.. _future: future.html -.. _`py.test tool and library`: test.html -.. _merlinux: http://merlinux.de - --- - -.. [#] FOSS is an evolving acronym for Free and Open Source Software - From hpk at codespeak.net Thu Apr 2 15:24:06 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 15:24:06 +0200 (CEST) Subject: [py-svn] r63539 - in py/trunk: . doc doc/_static doc/_templates doc/img py/doc Message-ID: <20090402132406.B9458168436@codespeak.net> Author: hpk Date: Thu Apr 2 15:24:02 2009 New Revision: 63539 Added: py/trunk/doc/ - copied from r63522, py/trunk/py/doc/ py/trunk/doc/Makefile - copied unchanged from r63538, py/trunk/py/doc/Makefile py/trunk/doc/__init__.py - copied unchanged from r63538, py/trunk/py/doc/__init__.py py/trunk/doc/_static/ - copied from r63538, py/trunk/py/doc/_static/ py/trunk/doc/_templates/ - copied from r63538, py/trunk/py/doc/_templates/ py/trunk/doc/bin.txt - copied unchanged from r63538, py/trunk/py/doc/bin.txt py/trunk/doc/code.txt - copied unchanged from r63538, py/trunk/py/doc/code.txt py/trunk/doc/coding-style.txt - copied unchanged from r63538, py/trunk/py/doc/coding-style.txt py/trunk/doc/conf.py - copied unchanged from r63538, py/trunk/py/doc/conf.py py/trunk/doc/confrest.py - copied unchanged from r63538, py/trunk/py/doc/confrest.py py/trunk/doc/contact.txt - copied unchanged from r63538, py/trunk/py/doc/contact.txt py/trunk/doc/download.txt - copied unchanged from r63538, py/trunk/py/doc/download.txt py/trunk/doc/execnet.txt - copied unchanged from r63538, py/trunk/py/doc/execnet.txt py/trunk/doc/img/ - copied from r63538, py/trunk/py/doc/img/ py/trunk/doc/impl-test.txt - copied unchanged from r63538, py/trunk/py/doc/impl-test.txt py/trunk/doc/index.txt - copied unchanged from r63538, py/trunk/py/doc/index.txt py/trunk/doc/io.txt - copied unchanged from r63538, py/trunk/py/doc/io.txt py/trunk/doc/links.txt - copied unchanged from r63538, py/trunk/py/doc/links.txt py/trunk/doc/log.txt - copied unchanged from r63538, py/trunk/py/doc/log.txt py/trunk/doc/make.bat - copied unchanged from r63538, py/trunk/py/doc/make.bat py/trunk/doc/misc.txt - copied unchanged from r63538, py/trunk/py/doc/misc.txt py/trunk/doc/path.txt - copied unchanged from r63538, py/trunk/py/doc/path.txt py/trunk/doc/release-0.9.0.txt - copied unchanged from r63538, py/trunk/py/doc/release-0.9.0.txt py/trunk/doc/release-0.9.2.txt - copied unchanged from r63538, py/trunk/py/doc/release-0.9.2.txt py/trunk/doc/release-1.0.0.txt - copied unchanged from r63538, py/trunk/py/doc/release-1.0.0.txt py/trunk/doc/releases.txt - copied unchanged from r63538, py/trunk/py/doc/releases.txt py/trunk/doc/roles.py - copied unchanged from r63538, py/trunk/py/doc/roles.py py/trunk/doc/style.css - copied unchanged from r63538, py/trunk/py/doc/style.css py/trunk/doc/test-config.txt - copied unchanged from r63538, py/trunk/py/doc/test-config.txt py/trunk/doc/test-dist.txt - copied unchanged from r63538, py/trunk/py/doc/test-dist.txt py/trunk/doc/test-examples.txt - copied unchanged from r63538, py/trunk/py/doc/test-examples.txt py/trunk/doc/test-ext.txt - copied unchanged from r63538, py/trunk/py/doc/test-ext.txt py/trunk/doc/test-features.txt - copied unchanged from r63538, py/trunk/py/doc/test-features.txt py/trunk/doc/test-plugins.txt - copied unchanged from r63538, py/trunk/py/doc/test-plugins.txt py/trunk/doc/test-quickstart.txt - copied unchanged from r63538, py/trunk/py/doc/test-quickstart.txt py/trunk/doc/test-statemanage.txt - copied unchanged from r63538, py/trunk/py/doc/test-statemanage.txt py/trunk/doc/test.txt - copied unchanged from r63538, py/trunk/py/doc/test.txt py/trunk/doc/xml.txt - copied unchanged from r63538, py/trunk/py/doc/xml.txt Removed: py/trunk/doc/draft_pyfs py/trunk/doc/future.txt py/trunk/doc/why_py.txt py/trunk/py/doc/ Modified: py/trunk/MANIFEST py/trunk/setup.py Log: moving py/doc to doc/ and removing some more files Modified: py/trunk/MANIFEST ============================================================================== --- py/trunk/MANIFEST (original) +++ py/trunk/MANIFEST Thu Apr 2 15:24:02 2009 @@ -83,25 +83,30 @@ py/compat/testing/test_textwrap.py py/compat/textwrap.py py/conftest.py +py/doc/Makefile py/doc/__init__.py py/doc/bin.txt py/doc/code.txt py/doc/coding-style.txt +py/doc/conf.py py/doc/confrest.py py/doc/contact.txt py/doc/download.txt -py/doc/draft_pyfs py/doc/execnet.txt -py/doc/future.txt +py/doc/img/pylib.png py/doc/impl-test.txt py/doc/index.txt py/doc/io.txt py/doc/links.txt py/doc/log.txt +py/doc/make.bat py/doc/misc.txt py/doc/path.txt py/doc/release-0.9.0.txt py/doc/release-0.9.2.txt +py/doc/release-1.0.0.txt +py/doc/releases.txt +py/doc/roles.py py/doc/style.css py/doc/test-config.txt py/doc/test-dist.txt @@ -110,8 +115,8 @@ py/doc/test-features.txt py/doc/test-plugins.txt py/doc/test-quickstart.txt +py/doc/test-statemanage.txt py/doc/test.txt -py/doc/why_py.txt py/doc/xml.txt py/env.cmd py/env.py @@ -300,12 +305,15 @@ py/test/plugin/pytest_doctest.py py/test/plugin/pytest_eventlog.py py/test/plugin/pytest_execnetcleanup.py +py/test/plugin/pytest_figleaf.py py/test/plugin/pytest_iocapture.py py/test/plugin/pytest_monkeypatch.py py/test/plugin/pytest_plugintester.py py/test/plugin/pytest_pocoo.py +py/test/plugin/pytest_pylint.py py/test/plugin/pytest_pytester.py py/test/plugin/pytest_restdoc.py +py/test/plugin/pytest_resultdb.py py/test/plugin/pytest_resultlog.py py/test/plugin/pytest_terminal.py py/test/plugin/pytest_tmpdir.py Deleted: /py/trunk/py/doc/draft_pyfs ============================================================================== --- /py/trunk/py/doc/draft_pyfs Thu Apr 2 15:24:02 2009 +++ (empty file) @@ -1,79 +0,0 @@ - -Let's do a walk through a memory filesystem. - -.. >>> import py - - -working with directories ---------------------------------- - -Let's create some directories and list them from memory:: - ->>> fs = py.fs.MemoryFS() ->>> fs.mkdir("x") ->>> fs.mkdir("y") ->>> fs.listdir() -['x', 'y'] - - -Creating, removing and reading files ---------------------------------------------- - ->>> f = fs.open('x/file', 'w') ->>> f.write("hello world") ->>> f.close() ->>> fs.listdir("x") -['file'] ->>> f = fs.open("x/file", 'r') ->>> f.readlines() -['hello world'] ->>> f.seek(6) ->>> f.read(3) -"wor" ->>> f.read() -"ld" ->>> f.close() ->>> fs.remove("y") ->>> fs.listdir() -['x'] ->>> fs.remove("non-existent") -py.error.ENOENT - -stat / checking for meta information ---------------------------------------- - ->>> stat = memory.stat("x") ->>> stat.isdir() -True ->>> stat.isfile() -False ->>> stat.exists() -True ->>> stat.islink() -False - -Linking to other objects --------------------------------------------------------- - -First an example how to link internally, i.e. within the -filesystem. - ->>> fs.link("newitem", "x") ->>> fs.stat("newitem").islink() -True ->>> fs.stat("newitem").isfile() -True ->>> fs.remove("newitem") # only deletes the link itself ->>> fs.stat("x").exists() - -cross-filesystem references ---------------------------------- - ->>> otherfs = py.fs.MemoryFS() - -XXX - ->>> fs.setproxy("newitem", otherfs, "otheritem") ->>> fs.stat("newitem").exists() -False ->>> otherfs.mkdir("otheritem") Deleted: /py/trunk/py/doc/future.txt ============================================================================== --- /py/trunk/py/doc/future.txt Thu Apr 2 15:24:02 2009 +++ (empty file) @@ -1,145 +0,0 @@ -======================================================= -Visions and ideas for further development of the py lib -======================================================= - - -This document tries to describe directions and guiding ideas -for the near-future development of the py lib. *Note that all -statements within this document - even if they sound factual - -mostly just express thoughts and ideas. They not always refer to -real code so read with some caution.* - - -Distribute tests ad-hoc across multiple platforms -====================================================== - -After some more refactoring and unification of -the current testing and distribution support code -we'd like to be able to run tests on multiple -platforms simultanously and allow for interaction -and introspection into the (remote) failures. - - -Make APIGEN useful for more projects -================================================ - -The new APIGEN tool offers rich information -derived from running tests against an application: -argument types and callsites, i.e. it shows -the places where a particular API is used. -In its first incarnation, there are still -some specialties that likely prevent it -from documenting APIs for other projects. -We'd like to evolve to a `py.apigen` tool -that can make use of information provided -by a py.test run. - -Consider APIGEN and pdb integration -=================================== - -The information provided by APIGEN can be used in many -different ways. An example of this could be to write -an extension to pdb which makes it available. -Imagine you could issue a pdb command -"info " and get information -regarding incoming, and outgoing types, possible -exceptions, field types and call sites. - -Distribute channels/programs across networks -================================================ - -Apart from stabilizing setup/teardown procedures -for `py.execnet`_, we'd like to generalize its -implementation to allow connecting two programs -across multiple hosts, i.e. we'd like to arbitrarily -send "channels" across the network. Likely this -will be done by using the "pipe" model, i.e. -that each channel is actually a pair of endpoints, -both of which can be independently transported -across the network. The programs who "own" -these endpoints remain connected. - -.. _`py.execnet`: execnet.html - -Benchmarking and persistent storage -========================================= - -For storing test results, but also benchmarking -and other information, we need a solid way -to store all kinds of information from test runs. -We'd like to generate statistics or html-overview -out of it, but also use such information to determine when -a certain test broke, or when its performance -decreased considerably. - -.. _`restructured text`: http://docutils.sourceforge.net/docs/user/rst/quickref.html -.. _`python standard library`: http://www.python.org/doc/2.3.4/lib/lib.html -.. _`xpython EuroPython 2004 talk`: http://codespeak.net/svn/user/hpk/talks/xpython-talk.txt -.. _`under the xpy tree`: http://codespeak.net/svn/user/hpk/xpy/xml.py -.. _`future book`: future.html -.. _`PEP-324 subprocess module`: http://www.python.org/peps/pep-0324.html -.. _`subprocess implementation`: http://www.lysator.liu.se/~astrand/popen5/ -.. _`py.test`: test.html - - -.. _`general-path`: -.. _`a more general view on path objects`: - -Refactor path implementations to use a Filesystem Abstraction -============================================================= - -It seems like a good idea to refactor all `py.path`_ Path implementations to -use an internal Filesystem abstraction. The current code base -would be transformed to have Filesystem implementations for e.g. -local, subversion and subversion "working copy" filesystems. Today -the according code is scattered through path-handling code. - -On a related note, Armin Rigo has hacked `pylufs`_ and more recently has -written `pyfuse`_ which allow to -implement kernel-level linux filesystems with pure python. Now -the idea is that the mentioned filesystem implementations would -be directly usable for such linux-filesystem glue code. - -In other words, implementing a `memoryfs`_ or a `dictfs`_ would -give you two things for free: a filesystem mountable at kernel level -as well as a uniform "path" object allowing you to access your -filesystem in convenient ways. - -Also interesting to check out is Will McGugan's work on -his `fs package`_. - -I think the main question is what the very fundamental -filesystem API should look like. Here are some doctests -on how a `draft py.fs`_ could look like. There also -is Matthew Scotts `dictproxy patch`_ which adds -``py.path.dict`` and ``py.path.proxy``. - - -.. _`dictproxy patch`: http://codespeak.net/pipermail/py-dev/attachments/20050128/d9595512/virtual1-0001.bin -.. _`draft py.fs`: draft_pyfs -.. _`py.path`: http://codespeak.net/py/dist/path.html -.. _`fs package`: http://www.willmcgugan.com/2008/09/21/announcing-fs-010-a-python-file-system/#comment-60276 -.. _`memoryfs`: http://codespeak.net/svn/user/arigo/hack/pyfuse/memoryfs.py -.. _`dictfs`: http://codespeak.net/pipermail/py-dev/2005-January/000191.html -.. _`pylufs`: http://codespeak.net/svn/user/arigo/hack/pylufs/ -.. _`pyfuse`: http://codespeak.net/svn/user/arigo/hack/pyfuse/ - - -Integrate interactive completion -================================== - -It'd be nice to integrate the bash-like -rlcompleter2_ python command line completer -into the py lib, and making it work remotely -and with pdb. - -.. _rlcompleter2: http://codespeak.net/rlcompleter2/ - -Consider more features -================================== - -There are many more features and useful classes -that might be nice to integrate. For example, we might put -Armin's `lazy list`_ implementation into the py lib. - -.. _`lazy list`: http://codespeak.net/svn/user/arigo/hack/misc/collect.py Deleted: /py/trunk/py/doc/why_py.txt ============================================================================== --- /py/trunk/py/doc/why_py.txt Thu Apr 2 15:24:02 2009 +++ (empty file) @@ -1,182 +0,0 @@ -============================================== -Why, who, what and how do you do *the py lib*? -============================================== - - -Why did we start the py lib? -============================ - -Among the main motivation for the py lib and its flagship -py.test tool were: - -- to test applications with a testing tool that provides - advanced features out of the box, yet allows full customization - per-project. - -- distribute applications in an ad-hoc way both for testing - and for application integration purposes. - -- help with neutralizing platform and python version differences - -- offer a uniform way to access local and remote file resources - -- offer some unique features like micro-threads (greenlets) - - -What is the py libs current focus? -================================== - -testing testing testing ------------------------ - -Currently, the main focus of the py lib is to get a decent -`test environment`_, indeed to produce the best one out there. -Writing, distributing and deploying tests should become -a snap ... and fun! - -On a side note: automated tests fit very well to the dynamism -of Python. Automated tests ease development and allow fast -refactoring cycles. Automated tests are a means of -communication as well. - - -ad-hoc distribution of programs ------------------------------------- - -The py lib through its `py.execnet`_ namespaces offers -support for ad-hoc distributing programs across -a network and subprocesses. We'd like to generalize -this approach further to instantiate and let whole -ad-hoc networks communicate with each other while -keeping to a simple programming model. - -.. _`py.execnet`: execnet.html - - -allowing maximum refactoring in the future ... ----------------------------------------------- - -explicit name export control -............................ - -In order to allow a fast development pace across versions of -the py lib there is **explicit name export control**. You -should only see names which make sense to use from the outside -and which the py lib developers want to guarantee across versions. -However, you don't need to treat the ``py`` lib as -anything special. You can simply use the usual ``import`` -statement and will not notice much of a difference - except that -the namespaces you'll see from the ``py`` lib are relatively -clean and have no clutter. - -Release policy & API maintenance -........................................ - -We'll talk about major, minor and micro numbers as the three -numbers in "1.2.3" respectively. These are the -the rough release policies: - -- Micro-releases are bug fix releases and should not introduce - new names to the public API. They may add tests and thus - further define the behaviour of the py lib. They may - completly change the implementation but the public API - tests should continue to run (unless they needed to - get fixed themselves). - -- No **tested feature** of the exported py API shall vanish - across minor releases until it is marked deprecated. - - For example, pure API tests of a future version 1.0 are to - continue to fully run on 1.1 and so on. If an API gets - deprecated with a minor release it goes with the next minor - release. Thus if you don't use deprecated APIs you should - be able to use the next two minor releases. However, if - you relied on some untested implementation behaviour, - you may still get screwed. Solution: add API tests to the - py lib :-) It's really the tests that make the difference. - -- Pure API tests are not allowed to access any implementation - level details. For example, accessing names starting with - a single leading '_' is generally seen as an implementation - level detail. - -- major releases *should*, but are not required to, pass - all API tests of the previous latest major released - version. - - -the need to find the right *paths* ... --------------------------------------- - -Another focus are well tested so called *path* implementations -that allow you to seemlessly work with different backends, -currently a local filesystem, subversion working copies and -subversion remote URLs. - -How does py development work? -============================= - -Communication and coding style ------------------------------- - -We are discussing things on our `py-dev mailing list`_ -and collaborate via the codespeak subversion repository. - -We follow a `coding style`_ which strongly builds on `PEP 8`_, -the basic python coding style document. - -It's easy to get commit rights especially if you are an -experienced python developer and share some of the -frustrations described above. - -Licensing ------------------ - -The Py lib is released under the MIT license and all -contributors need to release their contributions -under this license as well. - -connections with PyPy_ ---------------------------------- - -A major motivation for writing the py lib stems from needs -during PyPy_ development, most importantly testing and -file system access issues. PyPy puts a lot of pressure -on a testing environment and thus is a good **reality test**. - -Who is "we"? -============================= - -Some initial code was written from *Jens-Uwe Mager* and *Holger -Krekel*, after which Holger continued on a previous -incarnations of the py.test tool (known first as 'utest', then -as 'std.utest', now for some 2 years 'py.test'). - -Helpful discussions took place with *Martijn Faassen*, *Stephan -Schwarzer*, *Brian Dorsey*, *Grigh Gheorghiu* and then -*Armin Rigo* who contributed important parts. -He and Holger came up with a couple of iterations of the -testing-code that reduced the API to basically nothing: just the -plain assert statement and a ``py.test.raises`` method to -check for occuring exceptions within tests. - -Currently (as of 2007), there are more people involved -and also have worked funded through merlinux_ and the -PyPy EU project, Carl Friedrich Bolz, Guido Wesdorp -and Maciej Fijalkowski who contributed particularly -in 2006 and 2007 major parts of the py lib. - -.. _`talk at EP2004`: http://codespeak.net/svn/user/hpk/talks/std-talk.txt -.. _`coding style`: coding-style.html -.. _`PEP 8`: http://www.python.org/peps/pep-0008.html -.. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev -.. _`test environment`: test.html -.. _`PyPy`: http://codespeak.net/pypy -.. _future: future.html -.. _`py.test tool and library`: test.html -.. _merlinux: http://merlinux.de - --- - -.. [#] FOSS is an evolving acronym for Free and Open Source Software - Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Thu Apr 2 15:24:02 2009 @@ -1,7 +1,7 @@ """ setup file for 'py' package based on: - https://codespeak.net/svn/py/trunk, revision=63342 + https://codespeak.net/svn/py/trunk, revision=63519 autogenerated by gensetup.py """ @@ -32,7 +32,6 @@ """ -CEXTENSION = False def main(): setup( name='py', @@ -45,10 +44,6 @@ platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others', author_email='holger at merlinux.eu, py-dev at codespeak.net', - ext_modules = CEXTENSION and - [Extension("py.c-extension.greenlet.greenlet", - ["py/c-extension/greenlet/greenlet.c"]),] - or [], entry_points={'console_scripts': ['py.cleanup = py.cmdline:pycleanup', 'py.countloc = py.cmdline:pycountloc', @@ -137,23 +132,26 @@ 'compat/LICENSE', 'compat/testing/test_doctest.txt', 'compat/testing/test_doctest2.txt', + 'doc/Makefile', 'doc/bin.txt', 'doc/code.txt', 'doc/coding-style.txt', 'doc/contact.txt', 'doc/download.txt', - 'doc/draft_pyfs', 'doc/execnet.txt', - 'doc/future.txt', + 'doc/img/pylib.png', 'doc/impl-test.txt', 'doc/index.txt', 'doc/io.txt', 'doc/links.txt', 'doc/log.txt', + 'doc/make.bat', 'doc/misc.txt', 'doc/path.txt', 'doc/release-0.9.0.txt', 'doc/release-0.9.2.txt', + 'doc/release-1.0.0.txt', + 'doc/releases.txt', 'doc/style.css', 'doc/test-config.txt', 'doc/test-dist.txt', @@ -162,8 +160,8 @@ 'doc/test-features.txt', 'doc/test-plugins.txt', 'doc/test-quickstart.txt', + 'doc/test-statemanage.txt', 'doc/test.txt', - 'doc/why_py.txt', 'doc/xml.txt', 'env.cmd', 'execnet/NOTES', From hpk at codespeak.net Thu Apr 2 15:33:16 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 15:33:16 +0200 (CEST) Subject: [py-svn] r63540 - in py/trunk/py/test: plugin testing Message-ID: <20090402133316.A8C82168404@codespeak.net> Author: hpk Date: Thu Apr 2 15:33:14 2009 New Revision: 63540 Modified: py/trunk/py/test/plugin/pytest_resultdb.py py/trunk/py/test/testing/test_config.py Log: fixing shallow test bugs Modified: py/trunk/py/test/plugin/pytest_resultdb.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultdb.py (original) +++ py/trunk/py/test/plugin/pytest_resultdb.py Thu Apr 2 15:33:14 2009 @@ -276,6 +276,9 @@ class TestJSONResultArchive(BaseResultArchiveTests): cls = JSONResultArchive + def setup_method(self, method): + py.test.importorskip("simplejson") + class TestSQLiteResultArchive(BaseResultArchiveTests): cls = SQLiteResultArchive Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Thu Apr 2 15:33:14 2009 @@ -55,13 +55,9 @@ type="int", dest="gdest", help="g value."), ) """) - old = testdir.chdir() - try: - py.test.raises(ValueError, """ - py.test.config._reparse(['-g', '17']) - """) - finally: - old.chdir() + py.test.raises(ValueError, """ + py.test.config._reparse(['-g', '17']) + """) def test_parsing_again_fails(self, tmpdir): config = py.test.config._reparse([tmpdir]) From hpk at codespeak.net Thu Apr 2 15:43:27 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 15:43:27 +0200 (CEST) Subject: [py-svn] r63542 - greenlet/trunk/contrib/pygreen py/trunk/contrib/pygreen Message-ID: <20090402134327.397DB168417@codespeak.net> Author: hpk Date: Thu Apr 2 15:43:26 2009 New Revision: 63542 Added: greenlet/trunk/contrib/pygreen/ - copied from r63541, py/trunk/contrib/pygreen/ Removed: py/trunk/contrib/pygreen/ Log: moving pygreen to greenlet contrib dir From hpk at codespeak.net Thu Apr 2 15:57:43 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 15:57:43 +0200 (CEST) Subject: [py-svn] r63543 - in py/trunk/py: . test/plugin Message-ID: <20090402135743.78B56168460@codespeak.net> Author: hpk Date: Thu Apr 2 15:57:42 2009 New Revision: 63543 Modified: py/trunk/py/conftest.py py/trunk/py/test/plugin/pytest_figleaf.py py/trunk/py/test/plugin/pytest_resultdb.py Log: some import-related skips, test suite passes again. Modified: py/trunk/py/conftest.py ============================================================================== --- py/trunk/py/conftest.py (original) +++ py/trunk/py/conftest.py Thu Apr 2 15:57:42 2009 @@ -1,4 +1,4 @@ -pytest_plugins = 'pytest_doctest', 'pytest_pytester', 'pytest_restdoc' +pytest_plugins = 'pytest_doctest', 'pytest_pytester' # , 'pytest_restdoc' rsyncignore = ['c-extension/greenlet/build'] import py Modified: py/trunk/py/test/plugin/pytest_figleaf.py ============================================================================== --- py/trunk/py/test/plugin/pytest_figleaf.py (original) +++ py/trunk/py/test/plugin/pytest_figleaf.py Thu Apr 2 15:57:42 2009 @@ -54,11 +54,11 @@ return coverage - def test_generic(plugintester): plugintester.apicheck(FigleafPlugin) def test_functional(testdir): + py.test.importorskip("figleaf") testdir.plugins.append('figleaf') testdir.makepyfile(""" def f(): Modified: py/trunk/py/test/plugin/pytest_resultdb.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultdb.py (original) +++ py/trunk/py/test/plugin/pytest_resultdb.py Thu Apr 2 15:57:42 2009 @@ -308,6 +308,7 @@ class TestWithFunctionIntegration: def getarchive(self, testdir, arg): + py.test.importorskip("simplejson") resultdb = testdir.tmpdir.join("resultdb") args = ["--resultdb=%s" % resultdb, "--resultdb_format=sqlite"] + [arg] testdir.runpytest(*args) @@ -378,7 +379,7 @@ assert entry_lines[-1][0] == ' ' assert 'ValueError' in entry -def test_generic(plugintester, LineMatcher): +def test_generic(plugintester): plugintester.apicheck(ResultdbPlugin) testdir = plugintester.testdir() testdir.makepyfile(""" From briandorsey at codespeak.net Thu Apr 2 18:59:26 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Thu, 2 Apr 2009 18:59:26 +0200 (CEST) Subject: [py-svn] r63544 - py/trunk/py Message-ID: <20090402165926.24B57168464@codespeak.net> Author: briandorsey Date: Thu Apr 2 18:59:24 2009 New Revision: 63544 Modified: py/trunk/py/LICENSE Log: fix typo in my name Modified: py/trunk/py/LICENSE ============================================================================== --- py/trunk/py/LICENSE (original) +++ py/trunk/py/LICENSE Thu Apr 2 18:59:24 2009 @@ -10,7 +10,7 @@ Carl Friedrich Bolz, cfbolz at gmx de Armin Rigo, arigo at tunes org Maciek Fijalkowski, fijal at genesilico.pl - Briand Dorsey, briandorsey at gmail com + Brian Dorsey, briandorsey at gmail com merlinux GmbH, Germany, office at merlinux eu Contributors include:: From briandorsey at codespeak.net Thu Apr 2 19:07:19 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Thu, 2 Apr 2009 19:07:19 +0200 (CEST) Subject: [py-svn] r63545 - py/trunk/doc Message-ID: <20090402170719.4F633168409@codespeak.net> Author: briandorsey Date: Thu Apr 2 19:07:18 2009 New Revision: 63545 Modified: py/trunk/doc/index.txt py/trunk/doc/test-statemanage.txt py/trunk/doc/test.txt Log: fixes to toctree updated headings so the outline works Modified: py/trunk/doc/index.txt ============================================================================== --- py/trunk/doc/index.txt (original) +++ py/trunk/doc/index.txt Thu Apr 2 19:07:18 2009 @@ -54,8 +54,6 @@ io log misc - why_py - future coding-style links contact Modified: py/trunk/doc/test-statemanage.txt ============================================================================== --- py/trunk/doc/test-statemanage.txt (original) +++ py/trunk/doc/test-statemanage.txt Thu Apr 2 19:07:18 2009 @@ -1,10 +1,14 @@ +================= +Managing state +================= + funcargs: provding arguments for test functions -------------------------------------------------------- +=========================================================== XXX write docs Managing test state across test modules, classes and methods ------------------------------------------------------------- +============================================================ Often you want to create some files, database connections or other state in order to run tests in a certain environment. With Modified: py/trunk/doc/test.txt ============================================================================== --- py/trunk/doc/test.txt (original) +++ py/trunk/doc/test.txt Thu Apr 2 19:07:18 2009 @@ -36,5 +36,6 @@ test-ext test-dist test-config + test-statemanage test-examples impl-test From hpk at codespeak.net Thu Apr 2 19:34:18 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 19:34:18 +0200 (CEST) Subject: [py-svn] r63546 - py/trunk Message-ID: <20090402173418.2F5E1168459@codespeak.net> Author: hpk Date: Thu Apr 2 19:34:16 2009 New Revision: 63546 Modified: py/trunk/MANIFEST py/trunk/README.txt py/trunk/TODO.txt py/trunk/setup.py Log: small updates, regen setup.py Modified: py/trunk/MANIFEST ============================================================================== --- py/trunk/MANIFEST (original) +++ py/trunk/MANIFEST Thu Apr 2 19:34:16 2009 @@ -3,13 +3,6 @@ MANIFEST README.txt TODO.txt -example/execnet/popen_read_multiple.py -example/genhtml.py -example/genhtmlcss.py -example/genxml.py -example/pytest/failure_demo.py -example/pytest/test_failures.py -example/pytest/test_setup_flow_example.py ez_setup.py py/LICENSE py/__init__.py @@ -83,41 +76,6 @@ py/compat/testing/test_textwrap.py py/compat/textwrap.py py/conftest.py -py/doc/Makefile -py/doc/__init__.py -py/doc/bin.txt -py/doc/code.txt -py/doc/coding-style.txt -py/doc/conf.py -py/doc/confrest.py -py/doc/contact.txt -py/doc/download.txt -py/doc/execnet.txt -py/doc/img/pylib.png -py/doc/impl-test.txt -py/doc/index.txt -py/doc/io.txt -py/doc/links.txt -py/doc/log.txt -py/doc/make.bat -py/doc/misc.txt -py/doc/path.txt -py/doc/release-0.9.0.txt -py/doc/release-0.9.2.txt -py/doc/release-1.0.0.txt -py/doc/releases.txt -py/doc/roles.py -py/doc/style.css -py/doc/test-config.txt -py/doc/test-dist.txt -py/doc/test-examples.txt -py/doc/test-ext.txt -py/doc/test-features.txt -py/doc/test-plugins.txt -py/doc/test-quickstart.txt -py/doc/test-statemanage.txt -py/doc/test.txt -py/doc/xml.txt py/env.cmd py/env.py py/execnet/NOTES Modified: py/trunk/README.txt ============================================================================== --- py/trunk/README.txt (original) +++ py/trunk/README.txt Thu Apr 2 19:34:16 2009 @@ -5,7 +5,6 @@ * py.execnet: ad-hoc distributed execution * py.code: dynamic code generation and introspection * py.path: uniform local and svn path objects -* py.magic.greenlet: micro-threads It includes code and contributions from several people, listed in the LICENSE file. Modified: py/trunk/TODO.txt ============================================================================== --- py/trunk/TODO.txt (original) +++ py/trunk/TODO.txt Thu Apr 2 19:34:16 2009 @@ -1,9 +1,6 @@ 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 Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Thu Apr 2 19:34:16 2009 @@ -1,7 +1,7 @@ """ setup file for 'py' package based on: - https://codespeak.net/svn/py/trunk, revision=63519 + https://codespeak.net/svn/py/trunk, revision=63545 autogenerated by gensetup.py """ @@ -10,7 +10,7 @@ import ez_setup ez_setup.use_setuptools() from setuptools import setup, Extension - + long_description = """ The py lib is an extensible library for testing, distributed processing and @@ -72,7 +72,6 @@ 'py.code.testing', 'py.compat', 'py.compat.testing', - 'py.doc', 'py.execnet', 'py.execnet.script', 'py.execnet.testing', @@ -132,37 +131,6 @@ 'compat/LICENSE', 'compat/testing/test_doctest.txt', 'compat/testing/test_doctest2.txt', - 'doc/Makefile', - 'doc/bin.txt', - 'doc/code.txt', - 'doc/coding-style.txt', - 'doc/contact.txt', - 'doc/download.txt', - 'doc/execnet.txt', - 'doc/img/pylib.png', - 'doc/impl-test.txt', - 'doc/index.txt', - 'doc/io.txt', - 'doc/links.txt', - 'doc/log.txt', - 'doc/make.bat', - 'doc/misc.txt', - 'doc/path.txt', - 'doc/release-0.9.0.txt', - 'doc/release-0.9.2.txt', - 'doc/release-1.0.0.txt', - 'doc/releases.txt', - 'doc/style.css', - 'doc/test-config.txt', - 'doc/test-dist.txt', - 'doc/test-examples.txt', - 'doc/test-ext.txt', - 'doc/test-features.txt', - 'doc/test-plugins.txt', - 'doc/test-quickstart.txt', - 'doc/test-statemanage.txt', - 'doc/test.txt', - 'doc/xml.txt', 'env.cmd', 'execnet/NOTES', 'execnet/improve-remote-tracebacks.txt', From hpk at codespeak.net Thu Apr 2 19:58:53 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 19:58:53 +0200 (CEST) Subject: [py-svn] r63547 - in py/trunk/py: . misc misc/testing test test/dist test/looponfail test/plugin test/testing Message-ID: <20090402175853.62F92168491@codespeak.net> Author: hpk Date: Thu Apr 2 19:58:51 2009 New Revision: 63547 Modified: py/trunk/py/LICENSE py/trunk/py/_com.py py/trunk/py/misc/testing/test_com.py py/trunk/py/misc/testing/test_warn.py py/trunk/py/misc/warn.py py/trunk/py/test/dist/dsession.py py/trunk/py/test/looponfail/remote.py py/trunk/py/test/plugin/pytest_execnetcleanup.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_resultdb.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/pytestplugin.py py/trunk/py/test/session.py py/trunk/py/test/testing/test_pytestplugin.py Log: renaming all event consumers to use the "__" convention that is also used for funcargs. Modified: py/trunk/py/LICENSE ============================================================================== --- py/trunk/py/LICENSE (original) +++ py/trunk/py/LICENSE Thu Apr 2 19:58:51 2009 @@ -1,9 +1,8 @@ -py lib Copyright holders, 2003-2008 +py lib Copyright holders, 2003-2009 ======================================= -Except when otherwise stated (look for LICENSE files or information at -the beginning of each file) the files in the 'py' directory are -copyrighted by one or more of the following people and organizations: +The files in the 'py' directory are copyrighted by one or more +of the following people and organizations: Holger Krekel, holger at merlinux eu Guido Wesdorp, johnny at johnnydebris net Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Thu Apr 2 19:58:51 2009 @@ -9,7 +9,7 @@ :: >>> class MyPlugin: - ... def pyevent_plugin_registered(self, plugin): + ... def pyevent__plugin_registered(self, plugin): ... print "registering", plugin.__class__.__name__ ... >>> import py @@ -149,7 +149,7 @@ def notify(self, eventname, *args, **kwargs): #print "notifying", eventname, args, kwargs - MultiCall(self.listattr("pyevent_" + eventname), + MultiCall(self.listattr("pyevent__" + eventname), *args, **kwargs).execute() #print "calling anonymous hooks", args, kwargs MultiCall(self.listattr("pyevent"), Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Thu Apr 2 19:58:51 2009 @@ -100,9 +100,9 @@ plugins = PyPlugins() l = [] class MyApi: - def pyevent_plugin_registered(self, plugin): + def pyevent__plugin_registered(self, plugin): l.append(plugin) - def pyevent_plugin_unregistered(self, plugin): + def pyevent__plugin_unregistered(self, plugin): l.remove(plugin) myapi = MyApi() plugins.register(myapi) @@ -178,7 +178,7 @@ plugins = PyPlugins() l = [] class api1: - def pyevent_hello(self): + def pyevent__hello(self): l.append("hellospecific") class api2: def pyevent(self, name, *args): @@ -201,7 +201,7 @@ excinfo = py.test.raises(ImportError, "plugins.consider_module(mod)") mod.pylib = "os" class Events(list): - def pyevent_importingmodule(self, mod): + def pyevent__importingmodule(self, mod): self.append(mod) l = Events() plugins.register(l) @@ -226,17 +226,17 @@ old.chdir() class TestPyPluginsEvents: - def test_pyevent_named_dispatch(self): + def test_pyevent__named_dispatch(self): plugins = PyPlugins() l = [] class A: - def pyevent_name(self, x): + def pyevent__name(self, x): l.append(x) plugins.register(A()) plugins.notify("name", 13) assert l == [13] - def test_pyevent_anonymous_dispatch(self): + def test_pyevent__anonymous_dispatch(self): plugins = PyPlugins() l = [] class A: Modified: py/trunk/py/misc/testing/test_warn.py ============================================================================== --- py/trunk/py/misc/testing/test_warn.py (original) +++ py/trunk/py/misc/testing/test_warn.py Thu Apr 2 19:58:51 2009 @@ -9,7 +9,7 @@ self.bus.register(self) self.warnings = [] - def pyevent_WARNING(self, warning): + def pyevent__WARNING(self, warning): self.warnings.append(warning) def test_event_generation(self): Modified: py/trunk/py/misc/warn.py ============================================================================== --- py/trunk/py/misc/warn.py (original) +++ py/trunk/py/misc/warn.py Thu Apr 2 19:58:51 2009 @@ -18,7 +18,7 @@ self.bus = bus bus.register(self) - def pyevent_WARNING(self, warning): + def pyevent__WARNING(self, warning): # forward to python warning system py.std.warnings.warn_explicit(warning, category=Warning, filename=str(warning.path), Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Thu Apr 2 19:58:51 2009 @@ -25,27 +25,27 @@ self.shuttingdown = False self.testsfailed = False - def pyevent_itemtestreport(self, event): + def pyevent__itemtestreport(self, event): if event.colitem in self.dsession.item2nodes: self.dsession.removeitem(event.colitem, event.node) if event.failed: self.testsfailed = True - def pyevent_collectionreport(self, event): + def pyevent__collectionreport(self, event): if event.passed: self.colitems.extend(event.result) - def pyevent_testnodeready(self, node): + def pyevent__testnodeready(self, node): self.dsession.addnode(node) - def pyevent_testnodedown(self, node, error=None): + def pyevent__testnodedown(self, node, error=None): pending = self.dsession.removenode(node) if pending: crashitem = pending[0] self.dsession.handle_crashitem(crashitem, node) self.colitems.extend(pending[1:]) - def pyevent_rescheduleitems(self, event): + def pyevent__rescheduleitems(self, event): self.colitems.extend(event.items) self.dowork = False # avoid busywait Modified: py/trunk/py/test/looponfail/remote.py ============================================================================== --- py/trunk/py/test/looponfail/remote.py (original) +++ py/trunk/py/test/looponfail/remote.py Thu Apr 2 19:58:51 2009 @@ -139,10 +139,10 @@ session.shouldclose = channel.isclosed class Failures(list): - def pyevent_itemtestreport(self, ev): + def pyevent__itemtestreport(self, ev): if ev.failed: self.append(ev) - pyevent_collectionreport = pyevent_itemtestreport + pyevent__collectionreport = pyevent__itemtestreport failreports = Failures() session.bus.register(failreports) Modified: py/trunk/py/test/plugin/pytest_execnetcleanup.py ============================================================================== --- py/trunk/py/test/plugin/pytest_execnetcleanup.py (original) +++ py/trunk/py/test/plugin/pytest_execnetcleanup.py Thu Apr 2 19:58:51 2009 @@ -11,21 +11,21 @@ if self._debug: print "[execnetcleanup %0x] %s %s" %(id(self), msg, args) - def pyevent_gateway_init(self, gateway): + def pyevent__gateway_init(self, gateway): self.trace("init", gateway) if self._gateways is not None: self._gateways.append(gateway) - def pyevent_gateway_exit(self, gateway): + def pyevent__gateway_exit(self, gateway): self.trace("exit", gateway) if self._gateways is not None: self._gateways.remove(gateway) - def pyevent_testrunstart(self, event): + def pyevent__testrunstart(self, event): self.trace("testrunstart", event) self._gateways = [] - def pyevent_testrunfinish(self, event): + def pyevent__testrunfinish(self, event): self.trace("testrunfinish", event) l = [] for gw in self._gateways: Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Thu Apr 2 19:58:51 2009 @@ -76,7 +76,7 @@ if fail: py.test.fail("Plugin API error") -def collectattr(obj, prefixes=("pytest_", "pyevent_")): +def collectattr(obj, prefixes=("pytest_", "pyevent__")): methods = {} for apiname in vars(obj): for prefix in prefixes: @@ -141,63 +141,63 @@ def pyevent(self, eventname, *args, **kwargs): """ called for each testing event. """ - def pyevent_gateway_init(self, gateway): + def pyevent__gateway_init(self, gateway): """ called after a gateway has been initialized. """ - def pyevent_gateway_exit(self, gateway): + def pyevent__gateway_exit(self, gateway): """ called when gateway is being exited. """ - def pyevent_gwmanage_rsyncstart(self, source, gateways): + def pyevent__gwmanage_rsyncstart(self, source, gateways): """ called before rsyncing a directory to remote gateways takes place. """ - def pyevent_gwmanage_rsyncfinish(self, source, gateways): + def pyevent__gwmanage_rsyncfinish(self, source, gateways): """ called after rsyncing a directory to remote gateways takes place. """ - def pyevent_trace(self, category, msg): + def pyevent__trace(self, category, msg): """ called for tracing events. """ - def pyevent_internalerror(self, event): + def pyevent__internalerror(self, event): """ called for internal errors. """ - def pyevent_itemstart(self, item, node): + def pyevent__itemstart(self, item, node): """ test item gets collected. """ - def pyevent_itemtestreport(self, event): + def pyevent__itemtestreport(self, event): """ test has been run. """ - def pyevent_deselected(self, event): + def pyevent__deselected(self, event): """ item has been dselected. """ - def pyevent_collectionstart(self, event): + def pyevent__collectionstart(self, event): """ collector starts collecting. """ - def pyevent_collectionreport(self, event): + def pyevent__collectionreport(self, event): """ collector finished collecting. """ - def pyevent_testrunstart(self, event): + def pyevent__testrunstart(self, event): """ whole test run starts. """ - def pyevent_testrunfinish(self, event): + def pyevent__testrunfinish(self, event): """ whole test run finishes. """ - def pyevent_gwmanage_newgateway(self, gateway): + def pyevent__gwmanage_newgateway(self, gateway): """ execnet gateway manager has instantiated a gateway. The gateway will have an 'id' attribute that is unique within the gateway manager context. """ - def pyevent_testnodeready(self, node): + def pyevent__testnodeready(self, node): """ Node is ready to operate. """ - def pyevent_testnodedown(self, node, error): + def pyevent__testnodedown(self, node, error): """ Node is down. """ - def pyevent_rescheduleitems(self, event): + def pyevent__rescheduleitems(self, event): """ Items from a node that went down. """ - def pyevent_looponfailinfo(self, event): + def pyevent__looponfailinfo(self, event): """ info for repeating failing tests. """ - def pyevent_plugin_registered(self, plugin): + def pyevent__plugin_registered(self, plugin): """ a new py lib plugin got registered. """ Modified: py/trunk/py/test/plugin/pytest_resultdb.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultdb.py (original) +++ py/trunk/py/test/plugin/pytest_resultdb.py Thu Apr 2 19:58:51 2009 @@ -171,14 +171,14 @@ shortrepr, longrepr = getoutcomecodes(event) self.write_log_entry(event, shortrepr, gpath, longrepr) - def pyevent_itemtestreport(self, event): + def pyevent__itemtestreport(self, event): self.log_outcome(event) - def pyevent_collectionreport(self, event): + def pyevent__collectionreport(self, event): if not event.passed: self.log_outcome(event) - def pyevent_internalerror(self, event): + def pyevent__internalerror(self, event): path = event.repr.reprcrash.path # fishing :( self.write_log_entry(event, '!', path, str(event.repr)) Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Thu Apr 2 19:58:51 2009 @@ -82,11 +82,11 @@ else: return "???", dict(red=True) - def pyevent_internalerror(self, event): + def pyevent__internalerror(self, event): for line in str(event.repr).split("\n"): self.write_line("InternalException: " + line) - def pyevent_gwmanage_newgateway(self, gateway, rinfo): + def pyevent__gwmanage_newgateway(self, gateway, rinfo): #self.write_line("%s instantiated gateway from spec %r" %(gateway.id, gateway.spec._spec)) d = {} d['version'] = repr_pythonversion(rinfo.version_info) @@ -105,18 +105,18 @@ self.write_line(infoline) self.gateway2info[gateway] = infoline - def pyevent_gwmanage_rsyncstart(self, source, gateways): + def pyevent__gwmanage_rsyncstart(self, source, gateways): targets = ", ".join([gw.id for gw in gateways]) msg = "rsyncstart: %s -> %s" %(source, targets) if not self.config.option.verbose: msg += " # use --verbose to see rsync progress" self.write_line(msg) - def pyevent_gwmanage_rsyncfinish(self, source, gateways): + def pyevent__gwmanage_rsyncfinish(self, source, gateways): targets = ", ".join([gw.id for gw in gateways]) self.write_line("rsyncfinish: %s -> %s" %(source, targets)) - def pyevent_plugin_registered(self, plugin): + def pyevent__plugin_registered(self, plugin): if self.config.option.traceconfig: msg = "PLUGIN registered: %s" %(plugin,) # XXX this event may happen during setup/teardown time @@ -124,19 +124,19 @@ # which garbles our output if we use self.write_line self.write_line(msg) - def pyevent_testnodeready(self, node): + def pyevent__testnodeready(self, node): self.write_line("%s txnode ready to receive tests" %(node.gateway.id,)) - def pyevent_testnodedown(self, node, error): + def pyevent__testnodedown(self, node, error): if error: self.write_line("%s node down, error: %s" %(node.gateway.id, error)) - def pyevent_trace(self, category, msg): + def pyevent__trace(self, category, msg): if self.config.option.debug or \ self.config.option.traceconfig and category.find("config") != -1: self.write_line("[%s] %s" %(category, msg)) - def pyevent_itemstart(self, item, node=None): + def pyevent__itemstart(self, item, node=None): if self.config.option.debug: info = item.repr_metainfo() line = info.verboseline(basedir=self.curdir) + " " @@ -154,14 +154,14 @@ #self.write_fspath_result(fspath, "") self.write_ensure_prefix(line, "") - def pyevent_rescheduleitems(self, event): + def pyevent__rescheduleitems(self, event): if self.config.option.debug: self.write_sep("!", "RESCHEDULING %s " %(event.items,)) - def pyevent_deselected(self, event): + def pyevent__deselected(self, event): self.stats.setdefault('deselected', []).append(event) - def pyevent_itemtestreport(self, event): + def pyevent__itemtestreport(self, event): fspath = event.colitem.fspath cat, letter, word = self.getcategoryletterword(event) if isinstance(word, tuple): @@ -184,7 +184,7 @@ self._tw.write(" " + line) self.currentfspath = -2 - def pyevent_collectionreport(self, event): + def pyevent__collectionreport(self, event): if not event.passed: if event.failed: self.stats.setdefault("failed", []).append(event) @@ -194,7 +194,7 @@ self.stats.setdefault("skipped", []).append(event) self.write_fspath_result(event.colitem.fspath, "S") - def pyevent_testrunstart(self, event): + def pyevent__testrunstart(self, event): self.write_sep("=", "test session starts", bold=True) self._sessionstarttime = py.std.time.time() @@ -219,7 +219,7 @@ for i, testarg in py.builtin.enumerate(self.config.args): self.write_line("test object %d: %s" %(i+1, testarg)) - def pyevent_testrunfinish(self, event): + def pyevent__testrunfinish(self, event): self._tw.line("") if event.exitstatus in (0, 1, 2): self.summary_failures() @@ -232,7 +232,7 @@ self.summary_deselected() self.summary_stats() - def pyevent_looponfailinfo(self, event): + def pyevent__looponfailinfo(self, event): if event.failreports: self.write_sep("#", "LOOPONFAILING", red=True) for report in event.failreports: @@ -309,20 +309,20 @@ def outindent(self, line): self.out.line(self.indent + str(line)) - def pyevent_collectionstart(self, event): + def pyevent__collectionstart(self, event): self.outindent(event.collector) self.indent += self.INDENT - def pyevent_itemstart(self, item, node=None): + def pyevent__itemstart(self, item, node=None): self.outindent(item) - def pyevent_collectionreport(self, event): + def pyevent__collectionreport(self, event): if not event.passed: self.outindent("!!! %s !!!" % event.longrepr.reprcrash.message) self._failed.append(event) self.indent = self.indent[:-len(self.INDENT)] - def pyevent_testrunfinish(self, event): + def pyevent__testrunfinish(self, event): if self._failed: self.out.sep("!", "collection failures") for event in self._failed: @@ -438,7 +438,7 @@ modcol = testdir.getmodulecol("def test_one(): pass") rep = TerminalReporter(modcol.config, file=linecomp.stringio) excinfo = py.test.raises(ValueError, "raise ValueError('hello')") - rep.pyevent_internalerror(event.InternalException(excinfo)) + rep.pyevent__internalerror(event.InternalException(excinfo)) linecomp.assert_contains_lines([ "InternalException: >*raise ValueError*" ]) @@ -462,16 +462,16 @@ platform = "xyz" cwd = "qwe" - rep.pyevent_gwmanage_newgateway(gw1, rinfo) + rep.pyevent__gwmanage_newgateway(gw1, rinfo) linecomp.assert_contains_lines([ "X1*popen*xyz*2.5*" ]) - rep.pyevent_gwmanage_rsyncstart(source="hello", gateways=[gw1, gw2]) + rep.pyevent__gwmanage_rsyncstart(source="hello", gateways=[gw1, gw2]) linecomp.assert_contains_lines([ "rsyncstart: hello -> X1, X2" ]) - rep.pyevent_gwmanage_rsyncfinish(source="hello", gateways=[gw1, gw2]) + rep.pyevent__gwmanage_rsyncfinish(source="hello", gateways=[gw1, gw2]) linecomp.assert_contains_lines([ "rsyncfinish: hello -> X1, X2" ]) @@ -496,7 +496,7 @@ """) rep = TerminalReporter(modcol.config, file=linecomp.stringio) reports = [basic_run_report(x) for x in modcol.collect()] - rep.pyevent_looponfailinfo(event.LooponfailingInfo(reports, [modcol.config.topdir])) + rep.pyevent__looponfailinfo(event.LooponfailingInfo(reports, [modcol.config.topdir])) linecomp.assert_contains_lines([ "*test_looponfailreport.py:2: assert 0", "*test_looponfailreport.py:4: ValueError*", Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Thu Apr 2 19:58:51 2009 @@ -82,7 +82,7 @@ mc = py._com.MultiCall(methods, parser=parser) mc.execute() - def pyevent_plugin_registered(self, plugin): + def pyevent__plugin_registered(self, plugin): if hasattr(self, '_config'): self.pyplugins.call_plugin(plugin, "pytest_addoption", parser=self._config._parser) self.pyplugins.call_plugin(plugin, "pytest_configure", config=self._config) Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Thu Apr 2 19:58:51 2009 @@ -81,12 +81,12 @@ """ setup any neccessary resources ahead of the test run. """ self.bus.notify("testrunstart", event.TestrunStart()) - def pyevent_itemtestreport(self, rep): + def pyevent__itemtestreport(self, rep): if rep.failed: self._testsfailed = True if self.config.option.exitfirst: self.shouldstop = True - pyevent_collectionreport = pyevent_itemtestreport + pyevent__collectionreport = pyevent__itemtestreport def sessionfinishes(self, exitstatus=0, excinfo=None): """ teardown any resources after a test run. """ Modified: py/trunk/py/test/testing/test_pytestplugin.py ============================================================================== --- py/trunk/py/test/testing/test_pytestplugin.py (original) +++ py/trunk/py/test/testing/test_pytestplugin.py Thu Apr 2 19:58:51 2009 @@ -177,7 +177,7 @@ class A: def pytest_configure(self, config): l.append(self) - def pyevent_hello(self, obj): + def pyevent__hello(self, obj): events.append(obj) config.bus.register(A()) From hpk at codespeak.net Thu Apr 2 20:59:33 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 20:59:33 +0200 (CEST) Subject: [py-svn] r63548 - py/trunk/doc Message-ID: <20090402185933.19D05168483@codespeak.net> Author: hpk Date: Thu Apr 2 20:59:31 2009 New Revision: 63548 Modified: py/trunk/doc/coding-style.txt py/trunk/doc/contact.txt py/trunk/doc/impl-test.txt py/trunk/doc/index.txt py/trunk/doc/misc.txt py/trunk/doc/test-ext.txt py/trunk/doc/test-features.txt py/trunk/doc/test.txt Log: fixing lots of little issues with the docs Modified: py/trunk/doc/coding-style.txt ============================================================================== --- py/trunk/doc/coding-style.txt (original) +++ py/trunk/doc/coding-style.txt Thu Apr 2 20:59:31 2009 @@ -61,11 +61,5 @@ - Try to put the tests close to the tested code, don't overload directories with names. -- If you think of exporting new py lib APIs, discuss it first on the - `py-dev mailing list`_ and possibly write a chapter in our - `future_` book. Communication is considered a key here to make - sure that the py lib develops in a consistent way. - .. _`PEP 8 Style Guide for Python Code`: http://www.python.org/peps/pep-0008.html .. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev -.. _`future`: future.html Modified: py/trunk/doc/contact.txt ============================================================================== --- py/trunk/doc/contact.txt (original) +++ py/trunk/doc/contact.txt Thu Apr 2 20:59:31 2009 @@ -16,8 +16,6 @@ .. _`merlinux.eu`: http://merlinux.eu -.. _future: future.html - .. _`get an account`: .. _tetamap: http://tetamap.wordpress.com Modified: py/trunk/doc/impl-test.txt ============================================================================== --- py/trunk/doc/impl-test.txt (original) +++ py/trunk/doc/impl-test.txt Thu Apr 2 20:59:31 2009 @@ -2,7 +2,6 @@ ATTIC documentation =============================================== - XXX REVIEW and remove the below XXX Modified: py/trunk/doc/index.txt ============================================================================== --- py/trunk/doc/index.txt (original) +++ py/trunk/doc/index.txt Thu Apr 2 20:59:31 2009 @@ -33,11 +33,7 @@ .. _`py.test`: test.html .. _`py lib scripts`: bin.html .. _`py.xml`: xml.html -.. _`Why What how py?`: why_py.html -.. _`future`: future.html .. _`miscellaneous features`: misc.html -.. _`0.9.2 release announcement`: release-0.9.2.html - Full Contents =================================== @@ -64,4 +60,3 @@ * :ref:`genindex` * :ref:`modindex` * :ref:`search` - Modified: py/trunk/doc/misc.txt ============================================================================== --- py/trunk/doc/misc.txt (original) +++ py/trunk/doc/misc.txt Thu Apr 2 20:59:31 2009 @@ -2,7 +2,6 @@ Miscellaneous features of the py lib ==================================== - Mapping the standard python library into py =========================================== @@ -52,32 +51,14 @@ local paths have ``sysexec`` ---------------------------- -The py lib currently offers a stripped down functionality of what -the new `PEP-324 subprocess module`_ offers. The main functionality -of synchronously executing a system executable has a straightforward API:: +In order to synchronously execute an executable file you +can use ``sysexec``:: binsvn.sysexec('ls', 'http://codespeak.net/svn') where ``binsvn`` is a path that points to the ``svn`` commandline -binary. Note that this function would not offer any shell-escaping -so you really have to pass in separated arguments. This idea -fits nicely into `a more general view on path objects`_. - -For a first go, we are just reusing the existing `subprocess -implementation`_ but don't expose any of its API apart -from the above ``sysexec()`` method. - -Note, however, that currently the support for the ``sysexec`` interface on -win32 is not thoroughly tested. If you run into problems with it, we are -interested to hear about them. If you are running a Python older than 2.4 you -will have to install the `pywin32 package`_. - - -.. _`future book`: future.html -.. _`PEP-324 subprocess module`: http://www.python.org/peps/pep-0324.html -.. _`subprocess implementation`: http://www.lysator.liu.se/~astrand/popen5/ -.. _`a more general view on path objects`: future.html#general-path -.. _`pywin32 package`: http://pywin32.sourceforge.net/ +binary. Note that this function does not offer any shell-escaping +so you have to pass in already separated arguments. finding an executable local path -------------------------------- Modified: py/trunk/doc/test-ext.txt ============================================================================== --- py/trunk/doc/test-ext.txt (original) +++ py/trunk/doc/test-ext.txt Thu Apr 2 20:59:31 2009 @@ -51,9 +51,6 @@ Test Collection process ====================================================== -.. module:: py.test.collect - :synopsis: basic test collection classes - The collecting process is iterative so that distribution and execution of tests can start as soon as the first test item is collected. Collection nodes with children are @@ -110,22 +107,9 @@ the trailing ``.py``. - - - -Plugin methods -======================================= - -.. module:: py.__.test.pluginapi - -A Plugin class may implement the following attributes and methods: - -XXX - -_`pytest event`: - -Pytest Events +Plugin hooks and events ======================================= -XXX +See definitions at +http://codespeak.net/svn/py/trunk/py/test/plugin/api.py Modified: py/trunk/doc/test-features.txt ============================================================================== --- py/trunk/doc/test-features.txt (original) +++ py/trunk/doc/test-features.txt Thu Apr 2 20:59:31 2009 @@ -23,7 +23,7 @@ or class with a leading ``Test`` name is collected. .. _`generative tests`: -.. _`collection process`: impl-test.html#collection-process +.. _`collection process`: test-ext.html#collection-process load-balance tests to multiple CPUs =================================== Modified: py/trunk/doc/test.txt ============================================================================== --- py/trunk/doc/test.txt (original) +++ py/trunk/doc/test.txt Thu Apr 2 20:59:31 2009 @@ -24,7 +24,6 @@ .. _`distributed testing`: test-dist.html - Contents: .. toctree:: From briandorsey at codespeak.net Thu Apr 2 21:00:08 2009 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Thu, 2 Apr 2009 21:00:08 +0200 (CEST) Subject: [py-svn] r63549 - in py/trunk/doc: . _templates/pylib _templates/pylib/static Message-ID: <20090402190008.C4CAB168483@codespeak.net> Author: briandorsey Date: Thu Apr 2 21:00:08 2009 New Revision: 63549 Added: py/trunk/doc/_templates/pylib/ py/trunk/doc/_templates/pylib/static/ py/trunk/doc/_templates/pylib/static/style.css_t - copied, changed from r63543, py/trunk/doc/style.css py/trunk/doc/_templates/pylib/theme.conf Removed: py/trunk/doc/style.css Modified: py/trunk/doc/Makefile py/trunk/doc/conf.py Log: Attempted to create a theme based on the previous pylib pages. This initial version looks worse than either the old pages or the default sphinx output. Can revert back to the default theme by referting the change to conf.py Also added a new make target to force all html to be rebuilt. Sphinx doesn't detect changes to stylesheets in themes. Modified: py/trunk/doc/Makefile ============================================================================== --- py/trunk/doc/Makefile (original) +++ py/trunk/doc/Makefile Thu Apr 2 21:00:08 2009 @@ -16,6 +16,7 @@ help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" + @echo " htmlall to force make all standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @@ -34,6 +35,11 @@ @echo @echo "Build finished. The HTML pages are in _build/html." +htmlall: + $(SPHINXBUILD) -a -b html $(ALLSPHINXOPTS) _build/html + @echo + @echo "Build finished. The HTML pages are in _build/html." + dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml @echo Copied: py/trunk/doc/_templates/pylib/static/style.css_t (from r63543, py/trunk/doc/style.css) ============================================================================== --- py/trunk/doc/style.css (original) +++ py/trunk/doc/_templates/pylib/static/style.css_t Thu Apr 2 21:00:08 2009 @@ -1,5 +1,7 @@ + at import url("basic.css"); + body,body.editor,body.body { - font: 110% "Times New Roman", Arial, Verdana, Helvetica, serif; + font: 110% Verdana, Helvetica, Arial, serif; background: White; color: Black; } Added: py/trunk/doc/_templates/pylib/theme.conf ============================================================================== --- (empty file) +++ py/trunk/doc/_templates/pylib/theme.conf Thu Apr 2 21:00:08 2009 @@ -0,0 +1,9 @@ +[theme] +inherit = default +stylesheet = style.css +pygments_style = sphinx + +[options] +rightsidebar = false +stickysidebar = false + Modified: py/trunk/doc/conf.py ============================================================================== --- py/trunk/doc/conf.py (original) +++ py/trunk/doc/conf.py Thu Apr 2 21:00:08 2009 @@ -92,7 +92,7 @@ # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'default' +html_theme = 'pylib' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -100,7 +100,7 @@ #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +html_theme_path = ['_templates'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". Deleted: /py/trunk/doc/style.css ============================================================================== --- /py/trunk/doc/style.css Thu Apr 2 21:00:08 2009 +++ (empty file) @@ -1,1078 +0,0 @@ -body,body.editor,body.body { - font: 110% "Times New Roman", Arial, Verdana, Helvetica, serif; - background: White; - color: Black; -} - -a, a.reference { - text-decoration: none; -} -a[href]:hover { text-decoration: underline; } - -img { - border: none; - vertical-align: middle; -} - -p, div.text { - text-align: left; - line-height: 1.5em; - margin: 0.5em 0em 0em 0em; -} - - - -p a:active { - color: Red; - background-color: transparent; -} - -p img { - border: 0; - margin: 0; -} - -img.inlinephoto { - padding: 0; - padding-right: 1em; - padding-top: 0.7em; - float: left; -} - -hr { - clear: both; - height: 1px; - color: #8CACBB; - background-color: transparent; -} - - -ul { - line-height: 1.5em; - /*list-style-image: url("bullet.gif"); */ - margin-left: 1.5em; - padding:0; -} - -ol { - line-height: 1.5em; - margin-left: 1.5em; - padding:0; -} - -ul a, ol a { - text-decoration: underline; -} - -dl { -} - -dt { - font-weight: bold; -} - -dd { - line-height: 1.5em; - margin-bottom: 1em; -} - -blockquote { - font-family: Times, "Times New Roman", serif; - font-style: italic; - font-size: 120%; -} - -code { - color: Black; - /*background-color: #dee7ec;*/ - background-color: #cccccc; -} - -pre { - padding: 1em; - border: 1px solid #8cacbb; - color: Black; - background-color: #dee7ec; - background-color: #cccccc; - overflow: auto; -} - - -.netscape4 { - display: none; -} - -/* main page styles */ - -/*a[href]:hover { color: black; text-decoration: underline; } -a[href]:link { color: black; text-decoration: underline; } -a[href] { color: black; text-decoration: underline; } -*/ - -span.menu_selected { - color: black; - font: 140% Verdana, Helvetica, Arial, sans-serif; - text-decoration: none; - padding-right: 0.3em; - background-color: #cccccc; -} - - -a.menu { - /*color: #3ba6ec; */ - font: 140% Verdana, Helvetica, Arial, sans-serif; - text-decoration: none; - padding-right: 0.3em; -} - -a.menu[href]:visited, a.menu[href]:link{ - /*color: #3ba6ec; */ - font: 140% Verdana, Helvetica, Arial, sans-serif; - text-decoration: none; -} - -a.menu[href]:hover { - /*color: black;*/ -} - -div.project_title{ - /*border-spacing: 20px;*/ - font: 160% Verdana, Helvetica, Arial, sans-serif; - color: #3ba6ec; - vertical-align: middle; - padding-bottom: 0.3em; -} - -a.wikicurrent { - font: 100% Verdana, Helvetica, Arial, sans-serif; - color: #3ba6ec; - vertical-align: middle; -} - - -table.body { - border: 0; - /*padding: 0; - border-spacing: 0px; - border-collapse: separate; - */ -} - -td.page-header-left { - padding: 5px; - /*border-bottom: 1px solid #444444;*/ -} - -td.page-header-top { - padding: 0; - - /*border-bottom: 1px solid #444444;*/ -} - -td.sidebar { - padding: 1 0 0 1; -} - -td.sidebar p.classblock { - padding: 0 5 0 5; - margin: 1 1 1 1; - border: 1px solid #444444; - background-color: #eeeeee; -} - -td.sidebar p.userblock { - padding: 0 5 0 5; - margin: 1 1 1 1; - border: 1px solid #444444; - background-color: #eeeeff; -} - -td.content { - padding: 1 5 1 5; - vertical-align: top; - width: 100%; -} - -p.ok-message { - background-color: #22bb22; - padding: 5 5 5 5; - color: white; - font-weight: bold; -} -p.error-message { - background-color: #bb2222; - padding: 5 5 5 5; - color: white; - font-weight: bold; -} - -p:first-child { - margin: 0 ; - padding: 0; -} - -/* style for forms */ -table.form { - padding: 2; - border-spacing: 0px; - border-collapse: separate; -} - -table.form th { - color: #333388; - text-align: right; - vertical-align: top; - font-weight: normal; -} -table.form th.header { - font-weight: bold; - background-color: #eeeeff; - text-align: left; -} - -table.form th.required { - font-weight: bold; -} - -table.form td { - color: #333333; - empty-cells: show; - vertical-align: top; -} - -table.form td.optional { - font-weight: bold; - font-style: italic; -} - -table.form td.html { - color: #777777; -} - -/* style for lists */ -table.list { - border-spacing: 0px; - border-collapse: separate; - vertical-align: top; - padding-top: 0; - width: 100%; -} - -table.list th { - padding: 0 4 0 4; - color: #404070; - background-color: #eeeeff; - border-right: 1px solid #404070; - border-top: 1px solid #404070; - border-bottom: 1px solid #404070; - vertical-align: top; - empty-cells: show; -} -table.list th a[href]:hover { color: #404070 } -table.list th a[href]:link { color: #404070 } -table.list th a[href] { color: #404070 } -table.list th.group { - background-color: #f4f4ff; - text-align: center; - font-size: 120%; -} - -table.list td { - padding: 0 4 0 4; - border: 0 2 0 2; - border-right: 1px solid #404070; - color: #404070; - background-color: white; - vertical-align: top; - empty-cells: show; -} - -table.list tr.normal td { - background-color: white; - white-space: nowrap; -} - -table.list tr.alt td { - background-color: #efefef; - white-space: nowrap; -} - -table.list td:first-child { - border-left: 1px solid #404070; - border-right: 1px solid #404070; -} - -table.list th:first-child { - border-left: 1px solid #404070; - border-right: 1px solid #404070; -} - -table.list tr.navigation th { - text-align: right; -} -table.list tr.navigation th:first-child { - border-right: none; - text-align: left; -} - - -/* style for message displays */ -table.messages { - border-spacing: 0px; - border-collapse: separate; - width: 100%; -} - -table.messages th.header{ - padding-top: 10px; - border-bottom: 1px solid gray; - font-weight: bold; - background-color: white; - color: #707040; -} - -table.messages th { - font-weight: bold; - color: black; - text-align: left; - border-bottom: 1px solid #afafaf; -} - -table.messages td { - font-family: monospace; - background-color: #efefef; - border-bottom: 1px solid #afafaf; - color: black; - empty-cells: show; - border-right: 1px solid #afafaf; - vertical-align: top; - padding: 2 5 2 5; -} - -table.messages td:first-child { - border-left: 1px solid #afafaf; - border-right: 1px solid #afafaf; -} - -/* style for file displays */ -table.files { - border-spacing: 0px; - border-collapse: separate; - width: 100%; -} - -table.files th.header{ - padding-top: 10px; - border-bottom: 1px solid gray; - font-weight: bold; - background-color: white; - color: #707040; -} - -table.files th { - border-bottom: 1px solid #afafaf; - font-weight: bold; - text-align: left; -} - -table.files td { - font-family: monospace; - empty-cells: show; -} - -/* style for history displays */ -table.history { - border-spacing: 0px; - border-collapse: separate; - width: 100%; -} - -table.history th.header{ - padding-top: 10px; - border-bottom: 1px solid gray; - font-weight: bold; - background-color: white; - color: #707040; - font-size: 100%; -} - -table.history th { - border-bottom: 1px solid #afafaf; - font-weight: bold; - text-align: left; - font-size: 90%; -} - -table.history td { - font-size: 90%; - vertical-align: top; - empty-cells: show; -} - - -/* style for class list */ -table.classlist { - border-spacing: 0px; - border-collapse: separate; - width: 100%; -} - -table.classlist th.header{ - padding-top: 10px; - border-bottom: 1px solid gray; - font-weight: bold; - background-color: white; - color: #707040; -} - -table.classlist th { - font-weight: bold; - text-align: left; -} - - -/* style for class help display */ -table.classhelp { - border-spacing: 0px; - border-collapse: separate; - width: 100%; -} - -table.classhelp th { - font-weight: bold; - text-align: left; - color: #707040; -} - -table.classhelp td { - padding: 2 2 2 2; - border: 1px solid black; - text-align: left; - vertical-align: top; - empty-cells: show; -} - - -/* style for "other" displays */ -table.otherinfo { - border-spacing: 0px; - border-collapse: separate; - width: 100%; -} - -table.otherinfo th.header{ - padding-top: 10px; - border-bottom: 1px solid gray; - font-weight: bold; - background-color: white; - color: #707040; -} - -table.otherinfo th { - border-bottom: 1px solid #afafaf; - font-weight: bold; - text-align: left; -} - -input { - border: 1px solid #8cacbb; - color: Black; - background-color: white; - vertical-align: middle; - margin-bottom: 1px; /* IE bug fix */ - padding: 0.1em; -} - -select { - border: 1px solid #8cacbb; - color: Black; - background-color: white; - vertical-align: middle; - margin-bottom: 1px; /* IE bug fix */ - padding: 0.1em; -} - - -a.nonexistent { - color: #FF2222; -} -a.nonexistent:visited { - color: #FF2222; -} -a.external { - color: #AA6600; -} - -/* -dl,ul,ol { - margin-top: 1pt; -} -tt,pre { - font-family: Lucida Console,Courier New,Courier,monotype; - font-size: 12pt; -} -pre.code { - margin-top: 8pt; - margin-bottom: 8pt; - background-color: #FFFFEE; - white-space:pre; - border-style:solid; - border-width:1pt; - border-color:#999999; - color:#111111; - padding:5px; - width:100%; -} -*/ -div.diffold { - background-color: #FFFF80; - border-style:none; - border-width:thin; - width:100%; -} -div.diffnew { - background-color: #80FF80; - border-style:none; - border-width:thin; - width:100%; -} -div.message { - margin-top: 6pt; - background-color: #E8FFE8; - border-style:solid; - border-width:1pt; - border-color:#999999; - color:#440000; - padding:5px; - width:100%; -} -strong.highlight { - background-color: #FFBBBB; -/* as usual, NetScape fucks up with innocent CSS - border-color: #FFAAAA; - border-style: solid; - border-width: 1pt; -*/ -} - -table.navibar { - background-color: #C8C8C8; - border-spacing: 3px; -} -td.navibar { - background-color: #E8E8E8; - vertical-align: top; - text-align: right; - padding: 0px; -} - -div.pagename { - font-size: 140%; - color: blue; - text-align: center; - font-weight: bold; - background-color: white; - padding: 0 ; -} - -a.wikiaction, input.wikiaction { - color: black; - text-decoration: None; - text-align: center; - color: black; - /*border: 1px solid #3ba6ec; */ - margin: 4px; - padding: 5; - padding-bottom: 0; - white-space: nowrap; -} - -a.wikiaction[href]:hover { - color: black; - text-decoration: none; - /*background-color: #dddddd; */ -} - -span.wikiuserpref { - padding-top: 1em; - font-size: 120%; -} - -div.wikitrail { - vertical-align: bottom; - /*font-size: -1;*/ - padding-top: 1em; - display: none; -} - -div.wikiaction { - vertical-align: middle; - /*border-bottom: 1px solid #8cacbb;*/ - padding-bottom:1em; - text-align: left; - width: 100%; -} - -div.wikieditmenu { - text-align: right; -} - -form.wikiedit { - border: 1px solid #8cacbb; - background-color: #f0f0f0; - background-color: #fabf00; - padding: 1em; - padding-right: 0em; -} - -div.legenditem { - padding-top: 0.5em; - padding-left: 0.3em; -} - -span.wikitoken { - background-color: #eeeeee; -} - - -div#contentspace h1:first-child, div.heading:first-child { - padding-top: 0; - margin-top: 0; -} -div#contentspace h2:first-child { - padding-top: 0; - margin-top: 0; -} - -/* heading and paragraph text */ - -div.heading, h1 { - font-family: Verdana, Helvetica, Arial, sans-serif; - background-color: #58b3ef; - background-color: #FFFFFF; - /*color: #4893cf;*/ - color: black; - padding-top: 1.0em; - padding-bottom:0.2em; - text-align: left; - margin-top: 0em; - /*margin-bottom:8pt;*/ - font-weight: bold; - font-size: 115%; - border-bottom: 1px solid #8CACBB; -} - - -h1, h2, h3, h4, h5, h6 { - color: Black; - clear: left; - font: 100% Verdana, Helvetica, Arial, sans-serif; - margin: 0; - padding-left: 0em; - padding-top: 1em; - padding-bottom: 0.2em; - /*border-bottom: 1px solid #8CACBB;*/ -} -/* h1,h2 { padding-top: 0; }*/ - - -h1 { font-size: 145%; } -h2 { font-size: 135%; } -h3 { font-size: 125%; } -h4 { font-size: 120%; } -h5 { font-size: 110%; } -h6 { font-size: 80%; } - -h1 a { text-decoration: None;} - -div.exception { - background-color: #bb2222; - padding: 5 5 5 5; - color: white; - font-weight: bold; -} -pre.exception { - font-size: 110%; - padding: 1em; - border: 1px solid #8cacbb; - color: Black; - background-color: #dee7ec; - background-color: #cccccc; -} - -/* defines for navgiation bar (documentation) */ - - -div.direntry { - padding-top: 0.3em; - padding-bottom: 0.3em; - margin-right: 1em; - font-weight: bold; - background-color: #dee7ec; - font-size: 110%; -} - -div.fileentry { - font-family: Verdana, Helvetica, Arial, sans-serif; - padding-bottom: 0.3em; - white-space: nowrap; - line-height: 150%; -} - -a.fileentry { - white-space: nowrap; -} - - -span.left { - text-align: left; -} -span.right { - text-align: right; -} - -div.navbar { - /*margin: 0;*/ - font-size: 80% /*smaller*/; - font-weight: bold; - text-align: left; - /* position: fixed; */ - top: 100pt; - left: 0pt; /* auto; */ - width: 120pt; - /* right: auto; - right: 0pt; 2em; */ -} - - -div.history a { - /* font-size: 70%; */ -} - -div.wikiactiontitle { - font-weight: bold; -} - -/* REST defines */ - -div.document { - margin: 0; -} - -h1.title { - margin: 0; - margin-bottom: 0.5em; -} - -td.toplist { - vertical-align: top; -} - -img#pyimg { - position: absolute; - top: 4px; - left: 4px; -} - -div#navspace { - position: absolute; - top: 100px; - left: 11px; - font-size: 100%; - width: 150px; - overflow: hidden; /* scroll; */ -} - -div#metaspace { - position: absolute; - top: 10px; - left: 170px; -} - -div#errorline { - position: relative; - top: 5px; - float: right; -} - -div#contentspace { - position: absolute; - /* font: 120% "Times New Roman", serif;*/ - font: 110% Verdana, Helvetica, Arial, sans-serif; - top: 100px; - left: 170px; - margin-right: 5px; -} - -div#menubar { -/* width: 400px; */ - float: left; -} - -/* for the documentation page */ -div#docinfoline { - position: relative; - top: 5px; - left: 0px; - - /*background-color: #dee7ec; */ - padding: 5pt; - padding-bottom: 1em; - color: black; - /*border-width: 1pt; - border-style: solid;*/ - -} - -div#docnavlist { - /*background-color: #dee7ec; */ - padding: 5pt; - padding-bottom: 2em; - color: black; - border-width: 1pt; - /*border-style: solid;*/ -} - - -/* text markup */ - -div.listtitle { - color: Black; - clear: left; - font: 120% Verdana, Helvetica, Arial, sans-serif; - margin: 0; - padding-left: 0em; - padding-top: 0em; - padding-bottom: 0.2em; - margin-right: 0.5em; - border-bottom: 1px solid #8CACBB; -} - -div.actionbox h3 { - padding-top: 0; - padding-right: 0.5em; - padding-left: 0.5em; - background-color: #fabf00; - text-align: center; - border: 1px solid black; /* 8cacbb; */ -} - -div.actionbox a { - display: block; - padding-bottom: 0.5em; - padding-top: 0.5em; - margin-left: 0.5em; -} - -div.actionbox a.history { - display: block; - padding-bottom: 0.5em; - padding-top: 0.5em; - margin-left: 0.5em; - font-size: 90%; -} - -div.actionbox { - margin-bottom: 2em; - padding-bottom: 1em; - overflow: hidden; /* scroll; */ -} - -/* taken from docutils (oh dear, a bit senseless) */ -ol.simple, ul.simple { - margin-bottom: 1em } - -ol.arabic { - list-style: decimal } - -ol.loweralpha { - list-style: lower-alpha } - -ol.upperalpha { - list-style: upper-alpha } - -ol.lowerroman { - list-style: lower-roman } - -ol.upperroman { - list-style: upper-roman } - - -/* -:Author: David Goodger -:Contact: goodger at users.sourceforge.net -:date: $Date: 2003/01/22 22:26:48 $ -:version: $Revision: 1.29 $ -:copyright: This stylesheet has been placed in the public domain. - -Default cascading style sheet for the HTML output of Docutils. -*/ -/* -.first { - margin-top: 0 } - -.last { - margin-bottom: 0 } - -a.toc-backref { - text-decoration: none ; - color: black } - -dd { - margin-bottom: 0.5em } - -div.abstract { - margin: 2em 5em } - -div.abstract p.topic-title { - font-weight: bold ; - text-align: center } - -div.attention, div.caution, div.danger, div.error, div.hint, -div.important, div.note, div.tip, div.warning { - margin: 2em ; - border: medium outset ; - padding: 1em } - -div.attention p.admonition-title, div.caution p.admonition-title, -div.danger p.admonition-title, div.error p.admonition-title, -div.warning p.admonition-title { - color: red ; - font-weight: bold ; - font-family: sans-serif } - -div.hint p.admonition-title, div.important p.admonition-title, -div.note p.admonition-title, div.tip p.admonition-title { - font-weight: bold ; - font-family: sans-serif } - -div.dedication { - margin: 2em 5em ; - text-align: center ; - font-style: italic } - -div.dedication p.topic-title { - font-weight: bold ; - font-style: normal } - -div.figure { - margin-left: 2em } - -div.footer, div.header { - font-size: smaller } - -div.system-messages { - margin: 5em } - -div.system-messages h1 { - color: red } - -div.system-message { - border: medium outset ; - padding: 1em } - -div.system-message p.system-message-title { - color: red ; - font-weight: bold } - -div.topic { - margin: 2em } - -h1.title { - text-align: center } - -h2.subtitle { - text-align: center } - -hr { - width: 75% } - -p.caption { - font-style: italic } - -p.credits { - font-style: italic ; - font-size: smaller } - -p.label { - white-space: nowrap } - -p.topic-title { - font-weight: bold } - -pre.address { - margin-bottom: 0 ; - margin-top: 0 ; - font-family: serif ; - font-size: 100% } - -pre.line-block { - font-family: serif ; - font-size: 100% } - -pre.literal-block, pre.doctest-block { - margin-left: 2em ; - margin-right: 2em ; - background-color: #eeeeee } - -span.classifier { - font-family: sans-serif ; - font-style: oblique } - -span.classifier-delimiter { - font-family: sans-serif ; - font-weight: bold } - -span.interpreted { - font-family: sans-serif } - -span.option { - white-space: nowrap } - -span.option-argument { - font-style: italic } - -span.pre { - white-space: pre } - -span.problematic { - color: red } - -table { - margin-top: 0.5em ; - margin-bottom: 0.5em } - -table.citation { - border-left: solid thin gray ; - padding-left: 0.5ex } - -table.docinfo { - margin: 2em 4em } - -table.footnote { - border-left: solid thin black ; - padding-left: 0.5ex } - -td, th { - padding-left: 0.5em ; - padding-right: 0.5em ; - vertical-align: top } - -th.docinfo-name, th.field-name { - font-weight: bold ; - text-align: left ; - white-space: nowrap } - -h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { - font-size: 100% } - -tt { - background-color: #eeeeee } - -ul.auto-toc { - list-style-type: none } -*/ - -div.section { - margin-top: 1.0em ; -} From hpk at codespeak.net Thu Apr 2 21:17:00 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 21:17:00 +0200 (CEST) Subject: [py-svn] r63550 - py/trunk/py/test/plugin Message-ID: <20090402191700.8FD5616847E@codespeak.net> Author: hpk Date: Thu Apr 2 21:16:57 2009 New Revision: 63550 Added: py/trunk/py/test/plugin/api.py - copied, changed from r63547, py/trunk/py/test/plugin/pytest_plugintester.py Modified: py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_restdoc.py Log: * move hook and event API definitions to py/test/plugin/api.py * make "py.test" work on "doc" directory again. Copied: py/trunk/py/test/plugin/api.py (from r63547, py/trunk/py/test/plugin/pytest_plugintester.py) ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/api.py Thu Apr 2 21:16:57 2009 @@ -1,102 +1,11 @@ """ -plugin with support classes and functions for testing pytest functionality +API definitions for pytest plugin hooks and events """ -import py - -class PlugintesterPlugin: - """ test support code for testing pytest plugins. """ - def pytest_funcarg__plugintester(self, pyfuncitem): - pt = PluginTester(pyfuncitem) - pyfuncitem.addfinalizer(pt.finalize) - return pt - -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 finalize(self): - """ called after test function finished execution""" - -class PluginTester(Support): - def testdir(self): - # XXX import differently, eg. - # FSTester = self.pyfuncitem.config.pytestplugins.getpluginattr("pytester", "FSTester") - from pytest_pytester import TmpTestdir - crunner = TmpTestdir(self.pyfuncitem) - self.pyfuncitem.addfinalizer(crunner.finalize) - # - for colitem in self.pyfuncitem.listchain(): - if isinstance(colitem, py.test.collect.Module) and \ - colitem.name.startswith("pytest_"): - crunner.plugins.append(colitem.fspath.purebasename) - break - return crunner - - def apicheck(self, pluginclass): - print "loading and checking", pluginclass - fail = False - pm = py.test._PytestPlugins() - methods = collectattr(pluginclass) - hooks = collectattr(PytestPluginHooks) - getargs = py.std.inspect.getargs - - def isgenerichook(name): - return name.startswith("pytest_funcarg__") - - while methods: - name, method = methods.popitem() - if isgenerichook(name): - continue - if name not in hooks: - print "found 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) - if '__call__' in method_args[0]: - method_args[0].remove('__call__') - hookargs = getargs(hook.func_code) - for arg, hookarg in zip(method_args[0], hookargs[0]): - if arg != hookarg: - print "argument mismatch:" - print "actual : %s.%s" %(pluginclass.__name__, formatdef(method)) - print "required:", formatdef(hook) - fail = True - break - if not fail: - print "matching hook:", formatdef(method) - if fail: - py.test.fail("Plugin API error") - -def collectattr(obj, prefixes=("pytest_", "pyevent__")): - methods = {} - for apiname in vars(obj): - for prefix in prefixes: - 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. """ +class PluginHooks: + # ------------------------------------------------------------------------------ + # Command line and configuration hooks + # ------------------------------------------------------------------------------ def pytest_addoption(self, parser): """ called before commandline parsing. """ @@ -109,13 +18,10 @@ """ called before test process is exited. """ - def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): - """ return True if we consumed/did the call to the python function item. """ - - def pytest_item_makereport(self, item, excinfo, when, outerr): - """ return ItemTestReport event for the given test outcome. """ + # ------------------------------------------------------------------------------ # collection hooks + # ------------------------------------------------------------------------------ def pytest_collect_file(self, path, parent): """ return Collection node or None. """ @@ -130,6 +36,16 @@ def pytest_pymodule_makeitem(self, modcol, name, obj): """ return custom item/collector for a python object in a module, or None. """ + # ------------------------------------------------------------------------------ + # runtest related hooks + # ------------------------------------------------------------------------------ + + def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): + """ return True if we consumed/did the call to the python function item. """ + + def pytest_item_makereport(self, item, excinfo, when, outerr): + """ return ItemTestReport event for the given test outcome. """ + # reporting hooks (invoked from pytest_terminal.py) def pytest_report_teststatus(self, event): """ return shortletter and verbose word. """ @@ -137,9 +53,10 @@ def pytest_terminal_summary(self, terminalreporter): """ add additional section in terminal summary reporting. """ +class Events: # Events def pyevent(self, eventname, *args, **kwargs): - """ called for each testing event. """ + """ generically called for each notification event. """ def pyevent__gateway_init(self, gateway): """ called after a gateway has been initialized. """ @@ -186,13 +103,13 @@ within the gateway manager context. """ def pyevent__testnodeready(self, node): - """ Node is ready to operate. """ + """ Test Node is ready to operate. """ def pyevent__testnodedown(self, node, error): - """ Node is down. """ + """ Test Node is down. """ def pyevent__rescheduleitems(self, event): - """ Items from a node that went down. """ + """ reschedule Items from a node that went down. """ def pyevent__looponfailinfo(self, event): """ info for repeating failing tests. """ @@ -201,9 +118,3 @@ """ a new py lib plugin got registered. """ -# =============================================================================== -# plugin tests -# =============================================================================== - -def test_generic(plugintester): - plugintester.apicheck(PlugintesterPlugin) Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Thu Apr 2 21:16:57 2009 @@ -2,6 +2,7 @@ plugin with support classes and functions for testing pytest functionality """ import py +from py.__.test.plugin import api class PlugintesterPlugin: """ test support code for testing pytest plugins. """ @@ -43,7 +44,8 @@ fail = False pm = py.test._PytestPlugins() methods = collectattr(pluginclass) - hooks = collectattr(PytestPluginHooks) + hooks = collectattr(api.PluginHooks) + hooks.update(collectattr(api.Events)) getargs = py.std.inspect.getargs def isgenerichook(name): @@ -92,115 +94,6 @@ py.std.inspect.formatargspec(*py.std.inspect.getargspec(func)) ) - -class PytestPluginHooks: - def __init__(self): - """ usually called only once per test process. """ - - def pytest_addoption(self, parser): - """ called before commandline parsing. """ - - 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_pyfunc_call(self, pyfuncitem, args, kwargs): - """ return True if we consumed/did the call to the python function item. """ - - def pytest_item_makereport(self, item, excinfo, when, outerr): - """ return ItemTestReport event for the given test outcome. """ - - # collection hooks - def pytest_collect_file(self, path, parent): - """ return Collection node or None. """ - - def pytest_collect_recurse(self, path, parent): - """ return True/False to cause/prevent recursion into given directory. - return None if you do not want to make the decision. - """ - - def pytest_collect_directory(self, path, parent): - """ return Collection node or None. """ - - def pytest_pymodule_makeitem(self, modcol, name, obj): - """ return custom item/collector for a python object in a module, or None. """ - - # reporting hooks (invoked from pytest_terminal.py) - def pytest_report_teststatus(self, event): - """ return shortletter and verbose word. """ - - def pytest_terminal_summary(self, terminalreporter): - """ add additional section in terminal summary reporting. """ - - # Events - def pyevent(self, eventname, *args, **kwargs): - """ called for each testing event. """ - - def pyevent__gateway_init(self, gateway): - """ called after a gateway has been initialized. """ - - def pyevent__gateway_exit(self, gateway): - """ called when gateway is being exited. """ - - def pyevent__gwmanage_rsyncstart(self, source, gateways): - """ called before rsyncing a directory to remote gateways takes place. """ - - def pyevent__gwmanage_rsyncfinish(self, source, gateways): - """ called after rsyncing a directory to remote gateways takes place. """ - - def pyevent__trace(self, category, msg): - """ called for tracing events. """ - - def pyevent__internalerror(self, event): - """ called for internal errors. """ - - def pyevent__itemstart(self, item, node): - """ test item gets collected. """ - - def pyevent__itemtestreport(self, event): - """ test has been run. """ - - def pyevent__deselected(self, event): - """ item has been dselected. """ - - def pyevent__collectionstart(self, event): - """ collector starts collecting. """ - - def pyevent__collectionreport(self, event): - """ collector finished collecting. """ - - def pyevent__testrunstart(self, event): - """ whole test run starts. """ - - def pyevent__testrunfinish(self, event): - """ whole test run finishes. """ - - def pyevent__gwmanage_newgateway(self, gateway): - """ execnet gateway manager has instantiated a gateway. - The gateway will have an 'id' attribute that is unique - within the gateway manager context. - """ - def pyevent__testnodeready(self, node): - """ Node is ready to operate. """ - - def pyevent__testnodedown(self, node, error): - """ Node is down. """ - - def pyevent__rescheduleitems(self, event): - """ Items from a node that went down. """ - - def pyevent__looponfailinfo(self, event): - """ info for repeating failing tests. """ - - def pyevent__plugin_registered(self, plugin): - """ a new py lib plugin got registered. """ - - # =============================================================================== # plugin tests # =============================================================================== Modified: py/trunk/py/test/plugin/pytest_restdoc.py ============================================================================== --- py/trunk/py/test/plugin/pytest_restdoc.py (original) +++ py/trunk/py/test/plugin/pytest_restdoc.py Thu Apr 2 21:16:57 2009 @@ -85,6 +85,18 @@ directive.register_linkrole('api', self.resolve_linkrole) directive.register_linkrole('source', self.resolve_linkrole) + # XXX fake sphinx' "toctree" and refs + directive.register_linkrole('ref', self.resolve_linkrole) + + from docutils.parsers.rst import directives + def toctree_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + return [] + toctree_directive.content = 1 + toctree_directive.options = {'maxdepth': int, 'glob': directives.flag, + 'hidden': directives.flag} + directives.register_directive('toctree', toctree_directive) + def resolve_linkrole(self, name, text, check=True): apigen_relpath = self.project.apigen_relpath @@ -125,6 +137,8 @@ else: relpath += '.html' return (text, apigen_relpath + 'source/%s' % (relpath,)) + elif name == 'ref': + return ("", "") def _checkskip(self, lpath, htmlpath=None): if not self.config.getvalue("forcegen"): From hpk at codespeak.net Thu Apr 2 21:21:07 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 21:21:07 +0200 (CEST) Subject: [py-svn] r63551 - py/trunk/doc Message-ID: <20090402192107.73524168494@codespeak.net> Author: hpk Date: Thu Apr 2 21:21:06 2009 New Revision: 63551 Added: py/trunk/doc/style.css - copied unchanged from r63548, py/trunk/doc/style.css Log: keep style.css around here From hpk at codespeak.net Thu Apr 2 21:22:20 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 21:22:20 +0200 (CEST) Subject: [py-svn] r63552 - py/trunk/doc Message-ID: <20090402192220.951DD168494@codespeak.net> Author: hpk Date: Thu Apr 2 21:22:20 2009 New Revision: 63552 Added: py/trunk/doc/conftest.py Log: adding processing of pytest_restdoc plugin back for now. Added: py/trunk/doc/conftest.py ============================================================================== --- (empty file) +++ py/trunk/doc/conftest.py Thu Apr 2 21:22:20 2009 @@ -0,0 +1,2 @@ +#XXX make work: excludedirs = ['_build'] +pytest_plugins = ['pytest_restdoc'] From hpk at codespeak.net Thu Apr 2 21:47:03 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Apr 2009 21:47:03 +0200 (CEST) Subject: [py-svn] r63553 - in py/trunk: doc py py/test/plugin Message-ID: <20090402194703.40EE51684A4@codespeak.net> Author: hpk Date: Thu Apr 2 21:47:01 2009 New Revision: 63553 Modified: py/trunk/doc/conftest.py py/trunk/py/conftest.py py/trunk/py/test/plugin/pytest_execnetcleanup.py Log: * allow doc to be tested distributedly * be more eager to cleanup execnet gateway after tests Modified: py/trunk/doc/conftest.py ============================================================================== --- py/trunk/doc/conftest.py (original) +++ py/trunk/doc/conftest.py Thu Apr 2 21:47:01 2009 @@ -1,2 +1,3 @@ #XXX make work: excludedirs = ['_build'] pytest_plugins = ['pytest_restdoc'] +rsyncdirs = ['.'] Modified: py/trunk/py/conftest.py ============================================================================== --- py/trunk/py/conftest.py (original) +++ py/trunk/py/conftest.py Thu Apr 2 21:47:01 2009 @@ -1,4 +1,5 @@ pytest_plugins = 'pytest_doctest', 'pytest_pytester' # , 'pytest_restdoc' +rsyncdirs = ['../doc'] rsyncignore = ['c-extension/greenlet/build'] import py Modified: py/trunk/py/test/plugin/pytest_execnetcleanup.py ============================================================================== --- py/trunk/py/test/plugin/pytest_execnetcleanup.py (original) +++ py/trunk/py/test/plugin/pytest_execnetcleanup.py Thu Apr 2 21:47:01 2009 @@ -33,6 +33,14 @@ l.append(gw) #for gw in l: # gw.join() + # + def pytest_pyfunc_call(self, __call__, pyfuncitem, args, kwargs): + if self._gateways is not None: + gateways = self._gateways[:] + res = __call__.execute(firstresult=True) + while len(self._gateways) > len(gateways): + self._gateways[-1].exit() + return res def test_generic(plugintester): plugintester.apicheck(ExecnetcleanupPlugin) From hpk at codespeak.net Fri Apr 3 12:57:37 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 3 Apr 2009 12:57:37 +0200 (CEST) Subject: [py-svn] r63560 - in py/trunk/py/test: . dist/testing plugin testing Message-ID: <20090403105737.07BE3168045@codespeak.net> Author: hpk Date: Fri Apr 3 12:57:34 2009 New Revision: 63560 Modified: py/trunk/py/test/dist/testing/test_txnode.py py/trunk/py/test/event.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_eventlog.py py/trunk/py/test/plugin/pytest_execnetcleanup.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_resultdb.py py/trunk/py/test/plugin/pytest_resultlog.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/session.py py/trunk/py/test/testing/test_session.py Log: beginning to review/polish test events * pyevent() now receives args and kwargs as simple arguments * refactoring event handling in tests Modified: py/trunk/py/test/dist/testing/test_txnode.py ============================================================================== --- py/trunk/py/test/dist/testing/test_txnode.py (original) +++ py/trunk/py/test/dist/testing/test_txnode.py Fri Apr 3 12:57:34 2009 @@ -9,7 +9,7 @@ self.queue = queue bus.register(self) - def pyevent(self, eventname, *args, **kwargs): + def pyevent(self, eventname, args, kwargs): self.queue.put((eventname, args, kwargs)) def geteventargs(self, eventname, timeout=2.0): @@ -48,7 +48,7 @@ assert not self.node.channel.isclosed() return self.node - def finalize(self): + def xfinalize(self): if hasattr(self, 'node'): gw = self.node.gateway print "exiting:", gw @@ -56,7 +56,7 @@ def pytest_funcarg__mysetup(pyfuncitem): mysetup = MySetup(pyfuncitem) - pyfuncitem.addfinalizer(mysetup.finalize) + #pyfuncitem.addfinalizer(mysetup.finalize) return mysetup def pytest_funcarg__testdir(__call__, pyfuncitem): Modified: py/trunk/py/test/event.py ============================================================================== --- py/trunk/py/test/event.py (original) +++ py/trunk/py/test/event.py Fri Apr 3 12:57:34 2009 @@ -22,10 +22,6 @@ # Basic Live Reporting Events # ---------------------------------------------------------------------- -class TestrunStart(BaseEvent): - def __init__(self): - self.timestart = time.time() - class TestrunFinish(BaseEvent): def __init__(self, exitstatus=0, excinfo=None): self.exitstatus = exitstatus Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Fri Apr 3 12:57:34 2009 @@ -55,7 +55,7 @@ class Events: # Events - def pyevent(self, eventname, *args, **kwargs): + def pyevent(self, eventname, args, kwargs): """ generically called for each notification event. """ def pyevent__gateway_init(self, gateway): @@ -76,7 +76,7 @@ def pyevent__internalerror(self, event): """ called for internal errors. """ - def pyevent__itemstart(self, item, node): + def pyevent__itemstart(self, item, node=None): """ test item gets collected. """ def pyevent__itemtestreport(self, event): @@ -91,7 +91,7 @@ def pyevent__collectionreport(self, event): """ collector finished collecting. """ - def pyevent__testrunstart(self, event): + def pyevent__testrunstart(self): """ whole test run starts. """ def pyevent__testrunfinish(self, event): Modified: py/trunk/py/test/plugin/pytest_eventlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_eventlog.py (original) +++ py/trunk/py/test/plugin/pytest_eventlog.py Fri Apr 3 12:57:34 2009 @@ -16,7 +16,7 @@ self.eventlogfile.close() del self.eventlogfile - def pyevent(self, eventname, *args, **kwargs): + def pyevent(self, eventname, args, kwargs): if hasattr(self, 'eventlogfile'): f = self.eventlogfile print >>f, eventname, args, kwargs @@ -36,6 +36,6 @@ """) testdir.runpytest("--eventlog=event.log") s = testdir.tmpdir.join("event.log").read() - assert s.find("TestrunStart") != -1 + assert s.find("testrunstart") != -1 assert s.find("ItemTestReport") != -1 assert s.find("TestrunFinish") != -1 Modified: py/trunk/py/test/plugin/pytest_execnetcleanup.py ============================================================================== --- py/trunk/py/test/plugin/pytest_execnetcleanup.py (original) +++ py/trunk/py/test/plugin/pytest_execnetcleanup.py Fri Apr 3 12:57:34 2009 @@ -21,8 +21,8 @@ if self._gateways is not None: self._gateways.remove(gateway) - def pyevent__testrunstart(self, event): - self.trace("testrunstart", event) + def pyevent__testrunstart(self): + self.trace("testrunstart") self._gateways = [] def pyevent__testrunfinish(self, event): Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Fri Apr 3 12:57:34 2009 @@ -3,8 +3,10 @@ """ import py +import inspect from py.__.test import event from py.__.test.config import Config as pytestConfig +import api class PytesterPlugin: def pytest_funcarg__linecomp(self, pyfuncitem): @@ -257,6 +259,13 @@ def __repr__(self): return "" %(self.name, self.args) +class ParsedEvent: + def __init__(self, locals): + self.__dict__ = locals.copy() + + def __repr__(self): + return "" %(self.__dict__,) + class EventRecorder(object): def __init__(self, pyplugins, debug=False): # True): self.events = [] @@ -264,7 +273,7 @@ self.debug = debug pyplugins.register(self) - def pyevent(self, name, *args, **kwargs): + def pyevent(self, name, args, kwargs): if name == "plugin_registered" and args == (self,): return if self.debug: @@ -278,12 +287,42 @@ return event raise KeyError("popevent: %r not found in %r" %(name, self.events)) + def getevents(self, eventname): + method = self.geteventmethod(eventname) + l = [] + for event in self.events: + if event.name == eventname: + pevent = method(*event.args, **event.kwargs) + l.append(pevent) + return l + + def geteventmethod(self, eventname): + mname = "pyevent__" + eventname + method = getattr(api.Events, mname) + args, varargs, varkw, default = inspect.getargspec(method) + assert args[0] == "self" + args = args[1:] + fspec = inspect.formatargspec(args, varargs, varkw, default) + source = """def %(mname)s%(fspec)s: + return ParsedEvent(locals())""" % locals() + print source + exec source + return locals()[mname] + + + def firstparsedevent(self, eventname): + return self.parsedevents(eventname)[0] + def get(self, cls): l = [] for event in self.events: - value = event.args[0] - if isinstance(value, cls): - l.append(value) + try: + value = event.args[0] + except IndexError: + continue + else: + if isinstance(value, cls): + l.append(value) return l def getnamed(self, *names): Modified: py/trunk/py/test/plugin/pytest_resultdb.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultdb.py (original) +++ py/trunk/py/test/plugin/pytest_resultdb.py Fri Apr 3 12:57:34 2009 @@ -370,7 +370,7 @@ except ValueError: excinfo = event.InternalException() reslog = ResultDB(StringIO.StringIO()) - reslog.pyevent("internalerror", excinfo) + reslog.pyevent("internalerror", (excinfo,), {}) entry = reslog.logfile.getvalue() entry_lines = entry.splitlines() Modified: py/trunk/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultlog.py (original) +++ py/trunk/py/test/plugin/pytest_resultlog.py Fri Apr 3 12:57:34 2009 @@ -81,7 +81,9 @@ shortrepr, longrepr = getoutcomecodes(event) self.write_log_entry(shortrepr, gpath, longrepr) - def pyevent(self, eventname, event, *args, **kwargs): + def pyevent(self, eventname, args, kwargs): + if args: + event = args[0] if eventname == "itemtestreport": self.log_outcome(event) elif eventname == "collectionreport": @@ -91,6 +93,7 @@ path = event.repr.reprcrash.path # fishing :( self.write_log_entry('!', path, str(event.repr)) + # =============================================================================== # # plugin tests @@ -223,7 +226,7 @@ except ValueError: excinfo = event.InternalException() reslog = ResultLog(StringIO.StringIO()) - reslog.pyevent("internalerror", excinfo) + reslog.pyevent("internalerror", (excinfo,), {}) entry = reslog.logfile.getvalue() entry_lines = entry.splitlines() Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Fri Apr 3 12:57:34 2009 @@ -194,7 +194,7 @@ self.stats.setdefault("skipped", []).append(event) self.write_fspath_result(event.colitem.fspath, "S") - def pyevent__testrunstart(self, event): + def pyevent__testrunstart(self): self.write_sep("=", "test session starts", bold=True) self._sessionstarttime = py.std.time.time() @@ -370,7 +370,7 @@ """) rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.config.bus.register(rep) - rep.config.bus.notify("testrunstart", event.TestrunStart()) + rep.config.bus.notify("testrunstart") for item in testdir.genitems([modcol]): ev = basic_run_report(item) @@ -397,7 +397,7 @@ """, configargs=("-v",)) rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.config.bus.register(rep) - rep.config.bus.notify("testrunstart", event.TestrunStart()) + rep.config.bus.notify("testrunstart") items = modcol.collect() rep.config.option.debug = True # for item in items: @@ -422,7 +422,7 @@ modcol = testdir.getmodulecol("import xyz") rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.config.bus.register(rep) - rep.config.bus.notify("testrunstart", event.TestrunStart()) + rep.config.bus.notify("testrunstart") l = list(testdir.genitems([modcol])) assert len(l) == 0 linecomp.assert_contains_lines([ @@ -518,8 +518,8 @@ """, configargs=("--tb=%s" % tbopt,)) rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.config.bus.register(rep) - rep.config.bus.notify("testrunstart", event.TestrunStart()) - rep.config.bus.notify("testrunstart", event.TestrunStart()) + rep.config.bus.notify("testrunstart") + rep.config.bus.notify("testrunstart") for item in testdir.genitems([modcol]): rep.config.bus.notify("itemtestreport", basic_run_report(item)) rep.config.bus.notify("testrunfinish", event.TestrunFinish()) @@ -566,7 +566,7 @@ rep = TerminalReporter(modcol.config, file=linecomp.stringio) modcol.config.bus.register(rep) bus = modcol.config.bus - bus.notify("testrunstart", event.TestrunStart()) + bus.notify("testrunstart") try: for item in testdir.genitems([modcol]): bus.notify("itemtestreport", basic_run_report(item)) Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Fri Apr 3 12:57:34 2009 @@ -79,7 +79,7 @@ def sessionstarts(self): """ setup any neccessary resources ahead of the test run. """ - self.bus.notify("testrunstart", event.TestrunStart()) + self.bus.notify("testrunstart") def pyevent__itemtestreport(self, rep): if rep.failed: 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 Fri Apr 3 12:57:34 2009 @@ -25,11 +25,11 @@ assert failed[0].colitem.name == "test_one_one" assert failed[1].colitem.name == "test_other" assert failed[2].colitem.name == "test_two" - itemstarted = sorter.getnamed("itemstart") + itemstarted = sorter.getevents("itemstart") assert len(itemstarted) == 4 - colstarted = sorter.getnamed("collectionstart") + colstarted = sorter.getevents("collectionstart") assert len(colstarted) == 1 - col = colstarted[0].collector + col = colstarted[0].event.collector assert isinstance(col, py.test.collect.Module) def test_nested_import_error(self, testdir): From hpk at codespeak.net Fri Apr 3 12:59:36 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 3 Apr 2009 12:59:36 +0200 (CEST) Subject: [py-svn] r63562 - in py/trunk/py: . misc/testing Message-ID: <20090403105936.7EF381683F7@codespeak.net> Author: hpk Date: Fri Apr 3 12:59:34 2009 New Revision: 63562 Modified: py/trunk/py/_com.py py/trunk/py/misc/testing/test_com.py Log: fixing _com regarding pyevents as well Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Fri Apr 3 12:59:34 2009 @@ -51,7 +51,11 @@ if needscall: res = currentmethod(self, *self.args, **self.kwargs) else: - res = currentmethod(*self.args, **self.kwargs) + #try: + res = currentmethod(*self.args, **self.kwargs) + #except TypeError: + # print currentmethod.__module__, currentmethod.__name__, self.args, self.kwargs + # raise if hasattr(self, '_ex1'): self.results = [res] break @@ -152,7 +156,6 @@ MultiCall(self.listattr("pyevent__" + eventname), *args, **kwargs).execute() #print "calling anonymous hooks", args, kwargs - MultiCall(self.listattr("pyevent"), - eventname, *args, **kwargs).execute() + MultiCall(self.listattr("pyevent"), eventname, args, kwargs).execute() pyplugins = PyPlugins() Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Fri Apr 3 12:59:34 2009 @@ -181,7 +181,7 @@ def pyevent__hello(self): l.append("hellospecific") class api2: - def pyevent(self, name, *args): + def pyevent(self, name, args, kwargs): if name == "hello": l.append(name + "anonymous") plugins.register(api1()) @@ -240,7 +240,7 @@ plugins = PyPlugins() l = [] class A: - def pyevent(self, name, *args, **kwargs): + def pyevent(self, name, args, kwargs): if name == "name": l.extend([args, kwargs]) From hpk at codespeak.net Fri Apr 3 14:18:26 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 3 Apr 2009 14:18:26 +0200 (CEST) Subject: [py-svn] r63563 - in py/trunk/py/test: . plugin Message-ID: <20090403121826.132751684C7@codespeak.net> Author: hpk Date: Fri Apr 3 14:18:25 2009 New Revision: 63563 Modified: py/trunk/py/test/event.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_eventlog.py py/trunk/py/test/plugin/pytest_execnetcleanup.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/session.py Log: the TestrunFinish event class bites the dust. Modified: py/trunk/py/test/event.py ============================================================================== --- py/trunk/py/test/event.py (original) +++ py/trunk/py/test/event.py Fri Apr 3 14:18:25 2009 @@ -22,15 +22,6 @@ # Basic Live Reporting Events # ---------------------------------------------------------------------- -class TestrunFinish(BaseEvent): - def __init__(self, exitstatus=0, excinfo=None): - self.exitstatus = exitstatus - if excinfo is None: - self.excrepr = None - else: - self.excrepr = excinfo.getrepr() - self.timeend = time.time() - class InternalException(BaseEvent): def __init__(self, excinfo=None): if excinfo is None: Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Fri Apr 3 14:18:25 2009 @@ -94,7 +94,7 @@ def pyevent__testrunstart(self): """ whole test run starts. """ - def pyevent__testrunfinish(self, event): + def pyevent__testrunfinish(self, exitstatus, excrepr=None): """ whole test run finishes. """ def pyevent__gwmanage_newgateway(self, gateway): Modified: py/trunk/py/test/plugin/pytest_eventlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_eventlog.py (original) +++ py/trunk/py/test/plugin/pytest_eventlog.py Fri Apr 3 14:18:25 2009 @@ -38,4 +38,4 @@ s = testdir.tmpdir.join("event.log").read() assert s.find("testrunstart") != -1 assert s.find("ItemTestReport") != -1 - assert s.find("TestrunFinish") != -1 + assert s.find("testrunfinish") != -1 Modified: py/trunk/py/test/plugin/pytest_execnetcleanup.py ============================================================================== --- py/trunk/py/test/plugin/pytest_execnetcleanup.py (original) +++ py/trunk/py/test/plugin/pytest_execnetcleanup.py Fri Apr 3 14:18:25 2009 @@ -25,8 +25,8 @@ self.trace("testrunstart") self._gateways = [] - def pyevent__testrunfinish(self, event): - self.trace("testrunfinish", event) + def pyevent__testrunfinish(self, exitstatus, excrepr=None): + self.trace("testrunfinish", exitstatus) l = [] for gw in self._gateways: gw.exit() Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Fri Apr 3 14:18:25 2009 @@ -219,15 +219,15 @@ for i, testarg in py.builtin.enumerate(self.config.args): self.write_line("test object %d: %s" %(i+1, testarg)) - def pyevent__testrunfinish(self, event): + def pyevent__testrunfinish(self, exitstatus, excrepr=None): self._tw.line("") - if event.exitstatus in (0, 1, 2): + if exitstatus in (0, 1, 2): self.summary_failures() self.summary_skips() self.config.pytestplugins.call_each("pytest_terminal_summary", terminalreporter=self) - if event.excrepr is not None: - self.summary_final_exc(event.excrepr) - if event.exitstatus == 2: + if excrepr is not None: + self.summary_final_exc(excrepr) + if exitstatus == 2: self.write_sep("!", "KEYBOARD INTERRUPT") self.summary_deselected() self.summary_stats() @@ -246,7 +246,7 @@ self.write_line("### Watching: %s" %(rootdir,), bold=True) # - # summaries for TestrunFinish + # summaries for testrunfinish # def summary_failures(self): @@ -322,7 +322,7 @@ self._failed.append(event) self.indent = self.indent[:-len(self.INDENT)] - def pyevent__testrunfinish(self, event): + def pyevent__testrunfinish(self, exitstatus, excrepr=None): if self._failed: self.out.sep("!", "collection failures") for event in self._failed: @@ -378,7 +378,7 @@ linecomp.assert_contains_lines([ "*test_pass_skip_fail.py .sF" ]) - rep.config.bus.notify("testrunfinish", event.TestrunFinish()) + rep.config.bus.notify("testrunfinish", exitstatus=1) linecomp.assert_contains_lines([ " def test_func():", "> assert 0", @@ -411,7 +411,7 @@ "*test_pass_skip_fail_verbose.py:4: *test_skip*SKIP*", "*test_pass_skip_fail_verbose.py:6: *test_func*FAIL*", ]) - rep.config.bus.notify("testrunfinish", event.TestrunFinish()) + rep.config.bus.notify("testrunfinish", exitstatus=1) linecomp.assert_contains_lines([ " def test_func():", "> assert 0", @@ -428,7 +428,7 @@ linecomp.assert_contains_lines([ "*test_collect_fail.py F*" ]) - rep.config.bus.notify("testrunfinish", event.TestrunFinish()) + rep.config.bus.notify("testrunfinish", exitstatus=1) linecomp.assert_contains_lines([ "> import xyz", "E ImportError: No module named xyz" @@ -522,7 +522,7 @@ rep.config.bus.notify("testrunstart") for item in testdir.genitems([modcol]): rep.config.bus.notify("itemtestreport", basic_run_report(item)) - rep.config.bus.notify("testrunfinish", event.TestrunFinish()) + rep.config.bus.notify("testrunfinish", exitstatus=1) s = linecomp.stringio.getvalue() if tbopt == "long": print s @@ -577,7 +577,7 @@ s = linecomp.stringio.getvalue() if not verbose: assert s.find("_keyboard_interrupt.py Fs") != -1 - bus.notify("testrunfinish", event.TestrunFinish(exitstatus=2, excinfo=excinfo)) + bus.notify("testrunfinish", exitstatus=2, excrepr=excinfo.getrepr()) text = linecomp.stringio.getvalue() linecomp.assert_contains_lines([ " def test_foobar():", Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Fri Apr 3 14:18:25 2009 @@ -91,7 +91,8 @@ def sessionfinishes(self, exitstatus=0, excinfo=None): """ teardown any resources after a test run. """ self.bus.notify("testrunfinish", - event.TestrunFinish(exitstatus=exitstatus, excinfo=excinfo)) + exitstatus=exitstatus, + excrepr=excinfo and excinfo.getrepr() or None) def getinitialitems(self, colitems): if colitems is None: From hpk at codespeak.net Fri Apr 3 16:18:47 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 3 Apr 2009 16:18:47 +0200 (CEST) Subject: [py-svn] r63566 - in py/trunk/py/test: . dist dist/testing plugin Message-ID: <20090403141847.027221684CE@codespeak.net> Author: hpk Date: Fri Apr 3 16:18:47 2009 New Revision: 63566 Modified: py/trunk/py/test/dist/dsession.py py/trunk/py/test/dist/testing/test_txnode.py py/trunk/py/test/dist/txnode.py py/trunk/py/test/event.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_resultdb.py py/trunk/py/test/plugin/pytest_resultlog.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/pytestplugin.py py/trunk/py/test/session.py Log: the InternalException event class bites the dust. Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Fri Apr 3 16:18:47 2009 @@ -140,7 +140,7 @@ except KeyboardInterrupt: exitstatus = outcome.EXIT_INTERRUPTED except: - self.bus.notify("internalerror", event.InternalException()) + self.config.pytestplugins.notify_exception() exitstatus = outcome.EXIT_INTERNALERROR self.config.bus.unregister(loopstate) if exitstatus == 0 and self._testsfailed: Modified: py/trunk/py/test/dist/testing/test_txnode.py ============================================================================== --- py/trunk/py/test/dist/testing/test_txnode.py (original) +++ py/trunk/py/test/dist/testing/test_txnode.py Fri Apr 3 16:18:47 2009 @@ -109,7 +109,7 @@ node = mysetup.makenode(item.config) node.channel.close() py.test.raises(IOError, "node.send(item)") - #ev = self.geteventargs(event.InternalException) + #ev = self.getevents("internalerror") #assert ev.excinfo.errisinstance(IOError) def test_send_one(self, testdir, mysetup): Modified: py/trunk/py/test/dist/txnode.py ============================================================================== --- py/trunk/py/test/dist/txnode.py (original) +++ py/trunk/py/test/dist/txnode.py Fri Apr 3 16:18:47 2009 @@ -63,7 +63,7 @@ except: excinfo = py.code.ExceptionInfo() print "!" * 20, excinfo - self.notify("internalerror", event.InternalException(excinfo)) + self.notify_internal(excinfo) def send(self, item): assert item is not None @@ -127,7 +127,8 @@ except KeyboardInterrupt: raise except: - self.sendevent("internalerror", event.InternalException()) + er = py.code.ExceptionInfo().getrepr(funcargs=True, showlocals=True) + self.sendevent("internalerror", excrepr=er) raise def runtest(self, item): Modified: py/trunk/py/test/event.py ============================================================================== --- py/trunk/py/test/event.py (original) +++ py/trunk/py/test/event.py Fri Apr 3 16:18:47 2009 @@ -19,23 +19,12 @@ pass # ---------------------------------------------------------------------- -# Basic Live Reporting Events -# ---------------------------------------------------------------------- - -class InternalException(BaseEvent): - def __init__(self, excinfo=None): - if excinfo is None: - excinfo = py.code.ExceptionInfo() - self.repr = excinfo.getrepr(funcargs=True, showlocals=True) - -# ---------------------------------------------------------------------- # Events related to collecting and executing test Items # ---------------------------------------------------------------------- class Deselected(BaseEvent): def __init__(self, items): self.items = items - class BaseReport(BaseEvent): def toterminal(self, out): Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Fri Apr 3 16:18:47 2009 @@ -73,7 +73,7 @@ def pyevent__trace(self, category, msg): """ called for tracing events. """ - def pyevent__internalerror(self, event): + def pyevent__internalerror(self, excrepr): """ called for internal errors. """ def pyevent__itemstart(self, item, node=None): Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Fri Apr 3 16:18:47 2009 @@ -264,7 +264,7 @@ self.__dict__ = locals.copy() def __repr__(self): - return "" %(self.__dict__,) + return "" %(self.__dict__,) class EventRecorder(object): def __init__(self, pyplugins, debug=False): # True): @@ -288,6 +288,7 @@ raise KeyError("popevent: %r not found in %r" %(name, self.events)) def getevents(self, eventname): + """ return list of ParsedEvent instances matching the given eventname. """ method = self.geteventmethod(eventname) l = [] for event in self.events: Modified: py/trunk/py/test/plugin/pytest_resultdb.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultdb.py (original) +++ py/trunk/py/test/plugin/pytest_resultdb.py Fri Apr 3 16:18:47 2009 @@ -178,10 +178,10 @@ if not event.passed: self.log_outcome(event) - def pyevent__internalerror(self, event): - path = event.repr.reprcrash.path # fishing :( - self.write_log_entry(event, '!', path, str(event.repr)) - + def pyevent__internalerror(self, excrepr): + path = excrepr.reprcrash.path + XXX # we don't have an event + self.write_log_entry(event, '!', path, str(excrepr)) SQL_CREATE_TABLES = """ create table pytest_results ( @@ -368,9 +368,9 @@ try: raise ValueError except ValueError: - excinfo = event.InternalException() + excinfo = py.code.ExceptionInfo() reslog = ResultDB(StringIO.StringIO()) - reslog.pyevent("internalerror", (excinfo,), {}) + reslog.pyevent("internalerror", (excinfo.getrepr(),), {}) entry = reslog.logfile.getvalue() entry_lines = entry.splitlines() Modified: py/trunk/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultlog.py (original) +++ py/trunk/py/test/plugin/pytest_resultlog.py Fri Apr 3 16:18:47 2009 @@ -90,8 +90,9 @@ if not event.passed: self.log_outcome(event) elif eventname == "internalerror": - path = event.repr.reprcrash.path # fishing :( - self.write_log_entry('!', path, str(event.repr)) + excrepr = args[0] + path = excrepr.reprcrash.path # fishing :( + self.write_log_entry('!', path, str(excrepr)) # =============================================================================== @@ -224,9 +225,9 @@ try: raise ValueError except ValueError: - excinfo = event.InternalException() + excinfo = py.code.ExceptionInfo() reslog = ResultLog(StringIO.StringIO()) - reslog.pyevent("internalerror", (excinfo,), {}) + reslog.pyevent("internalerror", (excinfo.getrepr(),), {}) entry = reslog.logfile.getvalue() entry_lines = entry.splitlines() Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Fri Apr 3 16:18:47 2009 @@ -82,9 +82,9 @@ else: return "???", dict(red=True) - def pyevent__internalerror(self, event): - for line in str(event.repr).split("\n"): - self.write_line("InternalException: " + line) + def pyevent__internalerror(self, excrepr): + for line in str(excrepr).split("\n"): + self.write_line("INTERNALERROR> " + line) def pyevent__gwmanage_newgateway(self, gateway, rinfo): #self.write_line("%s instantiated gateway from spec %r" %(gateway.id, gateway.spec._spec)) @@ -438,9 +438,9 @@ modcol = testdir.getmodulecol("def test_one(): pass") rep = TerminalReporter(modcol.config, file=linecomp.stringio) excinfo = py.test.raises(ValueError, "raise ValueError('hello')") - rep.pyevent__internalerror(event.InternalException(excinfo)) + rep.pyevent__internalerror(excinfo.getrepr()) linecomp.assert_contains_lines([ - "InternalException: >*raise ValueError*" + "INTERNALERROR> *raise ValueError*" ]) def test_gwmanage_events(self, testdir, linecomp): Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Fri Apr 3 16:18:47 2009 @@ -77,6 +77,12 @@ def notify(self, eventname, *args, **kwargs): return self.pyplugins.notify(eventname, *args, **kwargs) + def notify_exception(self, excinfo=None): + if excinfo is None: + excinfo = py.code.ExceptionInfo() + excrepr = excinfo.getrepr(funcargs=True, showlocals=True) + return self.notify("internalerror", excrepr) + def do_addoption(self, parser): methods = self.pyplugins.listattr("pytest_addoption", reverse=True) mc = py._com.MultiCall(methods, parser=parser) Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Fri Apr 3 16:18:47 2009 @@ -121,7 +121,7 @@ exitstatus = outcome.EXIT_INTERRUPTED except: captured_excinfo = py.code.ExceptionInfo() - self.bus.notify("internalerror", event.InternalException(captured_excinfo)) + self.config.pytestplugins.notify_exception(captured_excinfo) exitstatus = outcome.EXIT_INTERNALERROR if exitstatus == 0 and self._testsfailed: exitstatus = outcome.EXIT_TESTSFAILED From hpk at codespeak.net Fri Apr 3 17:47:17 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 3 Apr 2009 17:47:17 +0200 (CEST) Subject: [py-svn] r63571 - in py/trunk/py/test: . dist dist/testing plugin testing Message-ID: <20090403154717.0BB341684D8@codespeak.net> Author: hpk Date: Fri Apr 3 17:47:16 2009 New Revision: 63571 Modified: py/trunk/py/test/dist/dsession.py py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/event.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/session.py py/trunk/py/test/testing/test_genitems.py py/trunk/py/test/testing/test_session.py Log: kill another few test "events" Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Fri Apr 3 17:47:16 2009 @@ -45,8 +45,8 @@ self.dsession.handle_crashitem(crashitem, node) self.colitems.extend(pending[1:]) - def pyevent__rescheduleitems(self, event): - self.colitems.extend(event.items) + def pyevent__rescheduleitems(self, items): + self.colitems.extend(items) self.dowork = False # avoid busywait class DSession(Session): @@ -175,7 +175,7 @@ if isinstance(next, py.test.collect.Item): senditems.append(next) else: - self.bus.notify("collectionstart", event.CollectionStart(next)) + self.bus.notify("collectionstart", next) self.queueevent("collectionreport", basic_collect_report(next)) if self.config.option.dist == "each": self.senditems_each(senditems) @@ -202,7 +202,7 @@ tosend[:] = tosend[room:] # update inplace if tosend: # we have some left, give it to the main loop - self.queueevent("rescheduleitems", event.RescheduleItems(tosend)) + self.queueevent("rescheduleitems", tosend) def senditems_load(self, tosend): if not tosend: @@ -224,7 +224,7 @@ break if tosend: # we have some left, give it to the main loop - self.queueevent("rescheduleitems", event.RescheduleItems(tosend)) + self.queueevent("rescheduleitems", tosend) def removeitem(self, item, node): if item not in self.item2nodes: Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Fri Apr 3 17:47:16 2009 @@ -101,8 +101,8 @@ assert session.node2pending[node2] == sent2 name, args, kwargs = session.queue.get(block=False) assert name == "rescheduleitems" - ev, = args - assert ev.items == [item] + items, = args + assert items == [item] def test_keyboardinterrupt(self, testdir): item = testdir.getitem("def test_func(): pass") @@ -125,9 +125,8 @@ session = DSession(item.config) node = MockNode() session.addnode(node) - ev = event.RescheduleItems([item]) loopstate = session._initloopstate([]) - session.queueevent("rescheduleitems", ev) + session.queueevent("rescheduleitems", [item]) session.loop_once(loopstate) # check that RescheduleEvents are not immediately # rescheduled if there are no nodes @@ -298,17 +297,15 @@ remaining = session.filteritems(items) assert remaining == [] - event = evrec.events[-1] - assert event.name == "deselected" - assert event.args[0].items == items + event = evrec.getevents("deselected")[-1] + assert event.items == items modcol.config.option.keyword = "test_fail" remaining = session.filteritems(items) assert remaining == [items[0]] - event = evrec.events[-1] - assert event.name == "deselected" - assert event.args[0].items == [items[1]] + event = evrec.getevents("deselected")[-1] + assert event.items == [items[1]] def test_testnodedown_shutdown_after_completion(self, testdir): item = testdir.getitem("def test_func(): pass") Modified: py/trunk/py/test/event.py ============================================================================== --- py/trunk/py/test/event.py (original) +++ py/trunk/py/test/event.py Fri Apr 3 17:47:16 2009 @@ -12,9 +12,6 @@ for key, value in self.__dict__.items()] return "<%s %s>" %(self.__class__.__name__, " ".join(l),) -def timestamp(): - return time.time() - class NOP(BaseEvent): pass @@ -22,10 +19,6 @@ # Events related to collecting and executing test Items # ---------------------------------------------------------------------- -class Deselected(BaseEvent): - def __init__(self, items): - self.items = items - class BaseReport(BaseEvent): def toterminal(self, out): longrepr = self.longrepr @@ -73,9 +66,6 @@ self.shortrepr = shortrepr self.longrepr = longrepr -class CollectionStart(BaseEvent): - def __init__(self, collector): - self.collector = collector class CollectionReport(BaseReport): """ Collection Report. """ @@ -108,30 +98,6 @@ self.failreports = failreports self.rootdirs = rootdirs -# ---------------------------------------------------------------------- -# Distributed Testing Events -# ---------------------------------------------------------------------- - -class RescheduleItems(BaseEvent): - def __init__(self, items): - self.items = items - -# --------------------------------------------------------------------- -# Events related to rsyncing -# --------------------------------------------------------------------- - -class HostRSyncing(BaseEvent): - def __init__(self, host, root, remotepath, synced): - self.host = host - self.root = root - self.remotepath = remotepath - self.synced = synced - -class HostRSyncRootReady(BaseEvent): - def __init__(self, host, root): - 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 Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Fri Apr 3 17:47:16 2009 @@ -82,10 +82,10 @@ def pyevent__itemtestreport(self, event): """ test has been run. """ - def pyevent__deselected(self, event): - """ item has been dselected. """ + def pyevent__deselected(self, items): + """ collected items that were deselected (by keyword). """ - def pyevent__collectionstart(self, event): + def pyevent__collectionstart(self, collector): """ collector starts collecting. """ def pyevent__collectionreport(self, event): Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Fri Apr 3 17:47:16 2009 @@ -158,8 +158,8 @@ if self.config.option.debug: self.write_sep("!", "RESCHEDULING %s " %(event.items,)) - def pyevent__deselected(self, event): - self.stats.setdefault('deselected', []).append(event) + def pyevent__deselected(self, items): + self.stats.setdefault('deselected', []).append(items) def pyevent__itemtestreport(self, event): fspath = event.colitem.fspath @@ -309,8 +309,8 @@ def outindent(self, line): self.out.line(self.indent + str(line)) - def pyevent__collectionstart(self, event): - self.outindent(event.collector) + def pyevent__collectionstart(self, collector): + self.outindent(collector) self.indent += self.INDENT def pyevent__itemstart(self, item, node=None): @@ -627,7 +627,7 @@ rep = CollectonlyReporter(modcol.config, out=linecomp.stringio) modcol.config.bus.register(rep) indent = rep.indent - rep.config.bus.notify("collectionstart", event.CollectionStart(modcol)) + rep.config.bus.notify("collectionstart", modcol) linecomp.assert_contains_lines([ "" ]) Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Fri Apr 3 17:47:16 2009 @@ -42,7 +42,7 @@ yield next else: assert isinstance(next, Collector) - notify("collectionstart", event.CollectionStart(next)) + notify("collectionstart", next) ev = basic_collect_report(next) if ev.passed: for x in self.genitems(ev.result, keywordexpr): @@ -67,7 +67,7 @@ continue remaining.append(colitem) if deselected: - self.bus.notify("deselected", event.Deselected(deselected, )) + self.bus.notify("deselected", deselected) if self.config.option.keyword.endswith(":"): self._nomatch = True return remaining Modified: py/trunk/py/test/testing/test_genitems.py ============================================================================== --- py/trunk/py/test/testing/test_genitems.py (original) +++ py/trunk/py/test/testing/test_genitems.py Fri Apr 3 17:47:16 2009 @@ -74,7 +74,7 @@ passed, skipped, failed = sorter.listoutcomes() assert len(failed) == 1 assert failed[0].colitem.name == name - assert len(sorter.getnamed('deselected')) == 1 + assert len(sorter.getevents('deselected')) == 1 for keyword in ['test_one', 'est_on']: #yield check, keyword, 'test_one' @@ -102,7 +102,7 @@ passed, skipped, failed = sorter.listoutcomes() assert len(passed) == 1 assert passed[0].colitem.name == "test_2" - dlist = sorter.getnamed("deselected") + dlist = sorter.getevents("deselected") assert len(dlist) == 1 assert dlist[0].items[0].name == 'test_1' @@ -116,7 +116,7 @@ passed, skipped, failed = sorter.listoutcomes() assert len(passed) == 2 assert not failed - dlist = sorter.getnamed("deselected") + dlist = sorter.getevents("deselected") assert len(dlist) == 1 item = dlist[0].items[0] assert item.name == "test_one" Modified: py/trunk/py/test/testing/test_session.py ============================================================================== --- py/trunk/py/test/testing/test_session.py (original) +++ py/trunk/py/test/testing/test_session.py Fri Apr 3 17:47:16 2009 @@ -29,7 +29,7 @@ assert len(itemstarted) == 4 colstarted = sorter.getevents("collectionstart") assert len(colstarted) == 1 - col = colstarted[0].event.collector + col = colstarted[0].collector assert isinstance(col, py.test.collect.Module) def test_nested_import_error(self, testdir): @@ -204,7 +204,7 @@ itemstarted = sorter.getnamed("itemstart") assert len(itemstarted) == 3 assert not sorter.getnamed("itemtestreport") - started = sorter.getnamed("collectionstart") + started = sorter.getevents("collectionstart") finished = sorter.getnamed("collectionreport") assert len(started) == len(finished) assert len(started) == 8 From hpk at codespeak.net Fri Apr 3 18:26:22 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 3 Apr 2009 18:26:22 +0200 (CEST) Subject: [py-svn] r63575 - in py/trunk/py/test: . dist/testing looponfail plugin Message-ID: <20090403162622.498821684CB@codespeak.net> Author: hpk Date: Fri Apr 3 18:26:21 2009 New Revision: 63575 Modified: py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/event.py py/trunk/py/test/looponfail/remote.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_resultdb.py py/trunk/py/test/plugin/pytest_resultlog.py py/trunk/py/test/plugin/pytest_terminal.py Log: killing yet more test "events" and redundant code Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Fri Apr 3 18:26:21 2009 @@ -131,9 +131,9 @@ # check that RescheduleEvents are not immediately # rescheduled if there are no nodes assert loopstate.dowork == False - session.queueevent("anonymous", event.NOP()) + session.queueevent("anonymous") session.loop_once(loopstate) - session.queueevent("anonymous", event.NOP()) + session.queueevent("anonymous") session.loop_once(loopstate) assert node.sent == [[item]] session.queueevent("itemtestreport", run(item, node)) Modified: py/trunk/py/test/event.py ============================================================================== --- py/trunk/py/test/event.py (original) +++ py/trunk/py/test/event.py Fri Apr 3 18:26:21 2009 @@ -6,20 +6,16 @@ import time from py.__.test.outcome import Skipped -class BaseEvent(object): +# ---------------------------------------------------------------------- +# Events related to collecting and executing test Items +# ---------------------------------------------------------------------- + +class BaseReport(object): def __repr__(self): l = ["%s=%s" %(key, value) for key, value in self.__dict__.items()] return "<%s %s>" %(self.__class__.__name__, " ".join(l),) -class NOP(BaseEvent): - pass - -# ---------------------------------------------------------------------- -# Events related to collecting and executing test Items -# ---------------------------------------------------------------------- - -class BaseReport(BaseEvent): def toterminal(self, out): longrepr = self.longrepr if hasattr(longrepr, 'toterminal'): @@ -93,16 +89,3 @@ else: out.line(str(longrepr)) -class LooponfailingInfo(BaseEvent): - def __init__(self, failreports, rootdirs): - self.failreports = failreports - self.rootdirs = rootdirs - -# 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/trunk/py/test/looponfail/remote.py ============================================================================== --- py/trunk/py/test/looponfail/remote.py (original) +++ py/trunk/py/test/looponfail/remote.py Fri Apr 3 18:26:21 2009 @@ -39,7 +39,6 @@ colitems = loopstate.colitems loopstate.wasfailing = colitems and len(colitems) loopstate.colitems = self.remotecontrol.runsession(colitems or ()) - #ev = event.LooponfailingInfo(loopstate.failreports, self.rootdirs) self.remotecontrol.setup() class LoopState: @@ -149,6 +148,7 @@ DEBUG("SLAVE: starting session.main()") session.main(colitems) - ev = event.LooponfailingInfo(list(failreports), [config.topdir]) - session.bus.notify("looponfailinfo", ev) - channel.send([x.colitem._totrail() for x in failreports if x.failed]) + session.bus.notify("looponfailinfo", + failreports=list(failreports), + rootdirs=[config.topdir]) + channel.send([x.colitem._totrail() for x in failreports]) Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Fri Apr 3 18:26:21 2009 @@ -108,10 +108,10 @@ def pyevent__testnodedown(self, node, error): """ Test Node is down. """ - def pyevent__rescheduleitems(self, event): + def pyevent__rescheduleitems(self, items): """ reschedule Items from a node that went down. """ - def pyevent__looponfailinfo(self, event): + def pyevent__looponfailinfo(self, failreports, rootdirs): """ info for repeating failing tests. """ def pyevent__plugin_registered(self, plugin): Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Fri Apr 3 18:26:21 2009 @@ -394,7 +394,7 @@ def test_eventrecorder(): bus = py._com.PyPlugins() recorder = EventRecorder(bus) - bus.notify("anonymous", event.NOP()) + bus.notify("anonymous") assert recorder.events assert not recorder.getfailures() rep = event.ItemTestReport(None, None) Modified: py/trunk/py/test/plugin/pytest_resultdb.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultdb.py (original) +++ py/trunk/py/test/plugin/pytest_resultdb.py Fri Apr 3 18:26:21 2009 @@ -1,9 +1,9 @@ import uuid import py -from pytest_resultlog import generic_path, getoutcomecodes +from pytest_resultlog import ResultLog class ResultdbPlugin: - """resultdb plugin for database logging of test results. + """XXX in progress: resultdb plugin for database logging of test results. Saves test results to a datastore. @@ -144,12 +144,12 @@ return d -class ResultDB(object): +class ResultDB(ResultLog): def __init__(self, cls, db_path): self.archive = cls(db_path) self.archive.init_db() - def write_log_entry(self, event, shortrepr, name, longrepr): + def write_log_entry(self, testpath, shortrepr, longrepr): data = {} event_excludes = ['colitem', 'longrepr'] for item in vars(event).keys(): @@ -159,29 +159,9 @@ data['longrepr'] = longrepr data['shortrepr'] = shortrepr - data['fspath'] = unicode(event.colitem.fspath) - data['itemname'] = event.colitem.name - - data['name'] = name + data['testpath'] = unicode(testpath) self.archive.append_data([data]) - def log_outcome(self, event): - if (not event.passed or isinstance(event, event.ItemTestReport)): - gpath = generic_path(event.colitem) - shortrepr, longrepr = getoutcomecodes(event) - self.write_log_entry(event, shortrepr, gpath, longrepr) - - def pyevent__itemtestreport(self, event): - self.log_outcome(event) - - def pyevent__collectionreport(self, event): - if not event.passed: - self.log_outcome(event) - - def pyevent__internalerror(self, excrepr): - path = excrepr.reprcrash.path - XXX # we don't have an event - self.write_log_entry(event, '!', path, str(excrepr)) SQL_CREATE_TABLES = """ create table pytest_results ( @@ -370,7 +350,7 @@ except ValueError: excinfo = py.code.ExceptionInfo() reslog = ResultDB(StringIO.StringIO()) - reslog.pyevent("internalerror", (excinfo.getrepr(),), {}) + reslog.pyevent__internalerror(excinfo.getrepr) entry = reslog.logfile.getvalue() entry_lines = entry.splitlines() Modified: py/trunk/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultlog.py (original) +++ py/trunk/py/test/plugin/pytest_resultlog.py Fri Apr 3 18:26:21 2009 @@ -44,55 +44,43 @@ gpath.append(name) fspath = newfspath return ''.join(gpath) - -def getoutcomecodes(ev): - if isinstance(ev, ev.CollectionReport): - # encode pass/fail/skip indepedent of terminal reporting semantics - # XXX handle collection and item reports more uniformly - 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.shortrepr - if ev.passed: - longrepr = "" - elif ev.failed: - longrepr = str(ev.longrepr) - elif ev.skipped: - longrepr = str(ev.longrepr.reprcrash.message) - return code, longrepr class ResultLog(object): def __init__(self, logfile): self.logfile = logfile # preferably line buffered - def write_log_entry(self, shortrepr, name, longrepr): - print >>self.logfile, "%s %s" % (shortrepr, name) + def write_log_entry(self, testpath, shortrepr, longrepr): + print >>self.logfile, "%s %s" % (shortrepr, testpath) for line in longrepr.splitlines(): print >>self.logfile, " %s" % line - def log_outcome(self, event): - if (not event.passed or isinstance(event, event.ItemTestReport)): - gpath = generic_path(event.colitem) - shortrepr, longrepr = getoutcomecodes(event) - self.write_log_entry(shortrepr, gpath, longrepr) - - def pyevent(self, eventname, args, kwargs): - if args: - event = args[0] - if eventname == "itemtestreport": - self.log_outcome(event) - elif eventname == "collectionreport": - if not event.passed: - self.log_outcome(event) - elif eventname == "internalerror": - excrepr = args[0] - path = excrepr.reprcrash.path # fishing :( - self.write_log_entry('!', path, str(excrepr)) + def log_outcome(self, event, shortrepr, longrepr): + testpath = generic_path(event.colitem) + self.write_log_entry(testpath, shortrepr, longrepr) + + def pyevent__itemtestreport(self, event): + code = event.shortrepr + if event.passed: + longrepr = "" + elif event.failed: + longrepr = str(event.longrepr) + elif event.skipped: + longrepr = str(event.longrepr.reprcrash.message) + self.log_outcome(event, code, longrepr) + + def pyevent__collectionreport(self, event): + if not event.passed: + if event.failed: + code = "F" + else: + assert event.skipped + code = "S" + longrepr = str(event.longrepr.reprcrash) + self.log_outcome(event, code, longrepr) + + def pyevent__internalerror(self, excrepr): + path = excrepr.reprcrash.path + self.write_log_entry(path, '!', str(excrepr)) # =============================================================================== @@ -127,7 +115,7 @@ def test_write_log_entry(): reslog = ResultLog(None) reslog.logfile = StringIO.StringIO() - reslog.write_log_entry('.', 'name', '') + reslog.write_log_entry('name', '.', '') entry = reslog.logfile.getvalue() assert entry[-1] == '\n' entry_lines = entry.splitlines() @@ -135,7 +123,7 @@ assert entry_lines[0] == '. name' reslog.logfile = StringIO.StringIO() - reslog.write_log_entry('s', 'name', 'Skipped') + reslog.write_log_entry('name', 's', 'Skipped') entry = reslog.logfile.getvalue() assert entry[-1] == '\n' entry_lines = entry.splitlines() @@ -144,7 +132,7 @@ assert entry_lines[1] == ' Skipped' reslog.logfile = StringIO.StringIO() - reslog.write_log_entry('s', 'name', 'Skipped\n') + reslog.write_log_entry('name', 's', 'Skipped\n') entry = reslog.logfile.getvalue() assert entry[-1] == '\n' entry_lines = entry.splitlines() @@ -154,7 +142,7 @@ reslog.logfile = StringIO.StringIO() longrepr = ' tb1\n tb 2\nE tb3\nSome Error' - reslog.write_log_entry('F', 'name', longrepr) + reslog.write_log_entry('name', 'F', longrepr) entry = reslog.logfile.getvalue() assert entry[-1] == '\n' entry_lines = entry.splitlines() @@ -227,7 +215,7 @@ except ValueError: excinfo = py.code.ExceptionInfo() reslog = ResultLog(StringIO.StringIO()) - reslog.pyevent("internalerror", (excinfo.getrepr(),), {}) + reslog.pyevent__internalerror(excinfo.getrepr()) entry = reslog.logfile.getvalue() entry_lines = entry.splitlines() Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Fri Apr 3 18:26:21 2009 @@ -154,9 +154,9 @@ #self.write_fspath_result(fspath, "") self.write_ensure_prefix(line, "") - def pyevent__rescheduleitems(self, event): + def pyevent__rescheduleitems(self, items): if self.config.option.debug: - self.write_sep("!", "RESCHEDULING %s " %(event.items,)) + self.write_sep("!", "RESCHEDULING %s " %(items,)) def pyevent__deselected(self, items): self.stats.setdefault('deselected', []).append(items) @@ -232,17 +232,17 @@ self.summary_deselected() self.summary_stats() - def pyevent__looponfailinfo(self, event): - if event.failreports: + def pyevent__looponfailinfo(self, failreports, rootdirs): + if failreports: self.write_sep("#", "LOOPONFAILING", red=True) - for report in event.failreports: + for report in failreports: try: loc = report.longrepr.reprcrash except AttributeError: loc = str(report.longrepr)[:50] self.write_line(loc, red=True) self.write_sep("#", "waiting for changes") - for rootdir in event.rootdirs: + for rootdir in rootdirs: self.write_line("### Watching: %s" %(rootdir,), bold=True) # @@ -496,7 +496,7 @@ """) rep = TerminalReporter(modcol.config, file=linecomp.stringio) reports = [basic_run_report(x) for x in modcol.collect()] - rep.pyevent__looponfailinfo(event.LooponfailingInfo(reports, [modcol.config.topdir])) + rep.pyevent__looponfailinfo(reports, [modcol.config.topdir]) linecomp.assert_contains_lines([ "*test_looponfailreport.py:2: assert 0", "*test_looponfailreport.py:4: ValueError*", From hpk at codespeak.net Fri Apr 3 19:39:51 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 3 Apr 2009 19:39:51 +0200 (CEST) Subject: [py-svn] r63579 - py/trunk/py/test Message-ID: <20090403173951.1832F1684A4@codespeak.net> Author: hpk Date: Fri Apr 3 19:39:49 2009 New Revision: 63579 Modified: py/trunk/py/test/runner.py Log: step one in simplifying runtest() collect() semantics Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Fri Apr 3 19:39:49 2009 @@ -11,7 +11,6 @@ from py.__.test import event from py.__.test.outcome import Exit from py.__.test.dist.mypickle import ImmutablePickler -import py.__.test.custompdb class RobustRun(object): """ a robust setup/execute/teardown protocol used both for test collectors @@ -25,20 +24,22 @@ def __repr__(self): return "<%s colitem=%s>" %(self.__class__.__name__, self.colitem) + +class ItemRunner(RobustRun): def run(self): """ return result of running setup, execution, teardown procedures. """ excinfo = None - res = NORESULT capture = self.getcapture() try: try: when = "setup" - self.setup() + self.colitem.config._setupstate.prepare(self.colitem) try: - res = self.execute() + when = "execute" + res = self.colitem.runtest() finally: when = "teardown" - self.teardown() + self.colitem.config._setupstate.teardown_exact(self.colitem) when = "execute" finally: outerr = capture.reset() @@ -46,19 +47,9 @@ raise except: excinfo = py.code.ExceptionInfo() - return self.makereport(res, when, excinfo, outerr) - -class ItemRunner(RobustRun): - def setup(self): - self.colitem.config._setupstate.prepare(self.colitem) - def teardown(self): - self.colitem.config._setupstate.teardown_exact(self.colitem) - def execute(self): - #self.colitem.config.pytestplugins.pre_execute(self.colitem) - self.colitem.runtest() - #self.colitem.config.pytestplugins.post_execute(self.colitem) + return self.makereport(when, excinfo, outerr) - def makereport(self, res, when, excinfo, outerr): + def makereport(self, when, excinfo, outerr): testrep = self.colitem.config.pytestplugins.call_firstresult( "pytest_item_makereport", item=self.colitem, excinfo=excinfo, when=when, outerr=outerr) @@ -69,12 +60,22 @@ return testrep class CollectorRunner(RobustRun): - def setup(self): - pass - def teardown(self): - pass - def execute(self): - return self.colitem._memocollect() + def run(self): + """ return result of running setup, execution, teardown procedures. """ + excinfo = None + res = NORESULT + capture = self.getcapture() + try: + try: + res = self.colitem._memocollect() + finally: + outerr = capture.reset() + except (Exit, KeyboardInterrupt): + raise + except: + excinfo = py.code.ExceptionInfo() + return self.makereport(res, "execute", excinfo, outerr) + def makereport(self, res, when, excinfo, outerr): return event.CollectionReport(self.colitem, res, excinfo, when, outerr) From hpk at codespeak.net Fri Apr 3 19:45:25 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 3 Apr 2009 19:45:25 +0200 (CEST) Subject: [py-svn] r63580 - in py/trunk/py: . io/testing magic test/plugin test/testing Message-ID: <20090403174525.C049A1684AD@codespeak.net> Author: hpk Date: Fri Apr 3 19:45:25 2009 New Revision: 63580 Removed: py/trunk/py/magic/greenlet.py Modified: py/trunk/py/__init__.py py/trunk/py/io/testing/test_terminalwriter.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/testing/test_pycollect.py py/trunk/py/test/testing/test_session.py Log: * removing usage of py.magic.patch * removing obsolete greenlet.py Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Fri Apr 3 19:45:25 2009 @@ -117,7 +117,6 @@ # some nice slightly magic APIs 'magic.__doc__' : ('./magic/__init__.py', '__doc__'), - 'magic.greenlet' : ('./magic/greenlet.py', 'greenlet'), 'magic.invoke' : ('./magic/invoke.py', 'invoke'), 'magic.revoke' : ('./magic/invoke.py', 'revoke'), 'magic.patch' : ('./magic/patch.py', 'patch'), Modified: py/trunk/py/io/testing/test_terminalwriter.py ============================================================================== --- py/trunk/py/io/testing/test_terminalwriter.py (original) +++ py/trunk/py/io/testing/test_terminalwriter.py Fri Apr 3 19:45:25 2009 @@ -6,22 +6,15 @@ if sys.platform == 'win32': py.test.skip('Not relevant on win32') -def test_terminalwriter_computes_width(): - py.magic.patch(terminalwriter, 'get_terminal_width', lambda: 42) - try: - tw = py.io.TerminalWriter() - assert tw.fullwidth == 42 - finally: - py.magic.revert(terminalwriter, 'get_terminal_width') - -def test_terminalwriter_defaultwidth_80(): - py.magic.patch(terminalwriter, '_getdimensions', lambda: 0/0) - try: - tw = py.io.TerminalWriter() - assert tw.fullwidth == int(os.environ.get('COLUMNS', 80)) -1 - finally: - py.magic.revert(terminalwriter, '_getdimensions') - +def test_terminalwriter_computes_width(monkeypatch): + monkeypatch.setattr(terminalwriter, 'get_terminal_width', lambda: 42) + tw = py.io.TerminalWriter() + assert tw.fullwidth == 42 + +def test_terminalwriter_defaultwidth_80(monkeypatch): + monkeypatch.setattr(terminalwriter, '_getdimensions', lambda: 0/0) + tw = py.io.TerminalWriter() + assert tw.fullwidth == int(os.environ.get('COLUMNS', 80)) -1 def test_terminalwriter_default_instantiation(): tw = py.io.TerminalWriter(stringio=True) Deleted: /py/trunk/py/magic/greenlet.py ============================================================================== --- /py/trunk/py/magic/greenlet.py Fri Apr 3 19:45:25 2009 +++ (empty file) @@ -1,12 +0,0 @@ -import sys -if '_stackless' in sys.builtin_module_names: - # when running on top of a pypy with stackless support - from _stackless import greenlet -elif hasattr(sys, 'pypy_objspaceclass'): - raise ImportError("Detected pypy without stackless support") -else: - # regular CPython - import py - gdir = py.path.local(py.__file__).dirpath() - path = gdir.join('c-extension', 'greenlet', 'greenlet.c') - greenlet = path._getpymodule().greenlet Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Fri Apr 3 19:45:25 2009 @@ -667,14 +667,11 @@ !!! ValueError: 0 !!! """) -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_repr_python_version(monkeypatch): + monkeypatch.setattr(sys, 'version_info', (2, 5, 1, 'final', 0)) + assert repr_pythonversion() == "2.5.1-final-0" + py.std.sys.version_info = x = (2,3) + assert repr_pythonversion() == str(x) def test_generic(plugintester): plugintester.apicheck(TerminalPlugin) Modified: py/trunk/py/test/testing/test_pycollect.py ============================================================================== --- py/trunk/py/test/testing/test_pycollect.py (original) +++ py/trunk/py/test/testing/test_pycollect.py Fri Apr 3 19:45:25 2009 @@ -21,22 +21,16 @@ py.test.raises(SyntaxError, modcol.collect) py.test.raises(SyntaxError, modcol.run) - def test_module_assertion_setup(self, testdir): + def test_module_assertion_setup(self, testdir, monkeypatch): modcol = testdir.getmodulecol("pass") from py.__.magic import assertion l = [] - py.magic.patch(assertion, "invoke", lambda: l.append(None)) - try: - modcol.setup() - finally: - py.magic.revert(assertion, "invoke") + monkeypatch.setattr(assertion, "invoke", lambda: l.append(None)) + modcol.setup() x = l.pop() assert x is None - py.magic.patch(assertion, "revoke", lambda: l.append(None)) - try: - modcol.teardown() - finally: - py.magic.revert(assertion, "revoke") + monkeypatch.setattr(assertion, "revoke", lambda: l.append(None)) + modcol.teardown() x = l.pop() assert x is None 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 Fri Apr 3 19:45:25 2009 @@ -135,7 +135,8 @@ assert skips[0].skipped class TestNewSession(SessionTests): - def test_pdb_run(self, testdir): + def test_pdb_run(self, testdir, monkeypatch): + import py.__.test.custompdb tfile = testdir.makepyfile(""" def test_usepdb(): assert 0 @@ -143,11 +144,8 @@ l = [] def mypdb(*args): l.append(args) - py.magic.patch(py.__.test.custompdb, 'post_mortem', mypdb) - try: - sorter = testdir.inline_run('--pdb', tfile) - finally: - py.magic.revert(py.__.test.custompdb, 'post_mortem') + monkeypatch.setattr(py.__.test.custompdb, 'post_mortem', mypdb) + sorter = testdir.inline_run('--pdb', tfile) rep = sorter.getreport("test_usepdb") assert rep.failed assert len(l) == 1 From hpk at codespeak.net Fri Apr 3 22:16:04 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 3 Apr 2009 22:16:04 +0200 (CEST) Subject: [py-svn] r63584 - py/trunk/py/test Message-ID: <20090403201604.3CF981684C8@codespeak.net> Author: hpk Date: Fri Apr 3 22:16:02 2009 New Revision: 63584 Modified: py/trunk/py/test/event.py py/trunk/py/test/runner.py Log: killing more code, simplifying running of tests. Modified: py/trunk/py/test/event.py ============================================================================== --- py/trunk/py/test/event.py (original) +++ py/trunk/py/test/event.py Fri Apr 3 22:16:02 2009 @@ -67,13 +67,12 @@ """ Collection Report. """ skipped = failed = passed = False - def __init__(self, colitem, result, excinfo=None, when=None, outerr=None): + def __init__(self, colitem, result, excinfo=None, outerr=None): self.colitem = colitem if not excinfo: self.passed = True self.result = result else: - self.when = when self.outerr = outerr self.longrepr = self.colitem._repr_failure_py(excinfo, outerr) if excinfo.errisinstance(Skipped): Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Fri Apr 3 22:16:02 2009 @@ -25,70 +25,49 @@ return "<%s colitem=%s>" %(self.__class__.__name__, self.colitem) -class ItemRunner(RobustRun): - def run(self): - """ return result of running setup, execution, teardown procedures. """ - excinfo = None - capture = self.getcapture() +def basic_run_report(item, pdb=None): + """ return report about setting up and running a test item. """ + excinfo = None + capture = item.config._getcapture() + try: try: + when = "setup" + item.config._setupstate.prepare(item) try: - when = "setup" - self.colitem.config._setupstate.prepare(self.colitem) - try: - when = "execute" - res = self.colitem.runtest() - finally: - when = "teardown" - self.colitem.config._setupstate.teardown_exact(self.colitem) - when = "execute" + when = "execute" + res = item.runtest() finally: - outerr = capture.reset() - except (Exit, KeyboardInterrupt): - raise - except: - excinfo = py.code.ExceptionInfo() - return self.makereport(when, excinfo, outerr) - - def makereport(self, when, excinfo, outerr): - testrep = self.colitem.config.pytestplugins.call_firstresult( - "pytest_item_makereport", item=self.colitem, - excinfo=excinfo, when=when, outerr=outerr) - if self.pdb and testrep.failed: - tw = py.io.TerminalWriter() - testrep.toterminal(tw) - self.pdb(excinfo) - return testrep - -class CollectorRunner(RobustRun): - def run(self): - """ return result of running setup, execution, teardown procedures. """ - excinfo = None - res = NORESULT - capture = self.getcapture() + when = "teardown" + item.config._setupstate.teardown_exact(item) + when = "execute" + finally: + outerr = capture.reset() + except (Exit, KeyboardInterrupt): + raise + except: + excinfo = py.code.ExceptionInfo() + testrep = item.config.pytestplugins.call_firstresult( + "pytest_item_makereport", item=item, + excinfo=excinfo, when=when, outerr=outerr) + if pdb and testrep.failed: + tw = py.io.TerminalWriter() + testrep.toterminal(tw) + pdb(excinfo) + return testrep + +def basic_collect_report(collector): + excinfo = res = None + try: + capture = collector.config._getcapture() try: - try: - res = self.colitem._memocollect() - finally: - outerr = capture.reset() - except (Exit, KeyboardInterrupt): - raise - except: - excinfo = py.code.ExceptionInfo() - return self.makereport(res, "execute", excinfo, outerr) - - def makereport(self, res, when, excinfo, outerr): - return event.CollectionReport(self.colitem, res, excinfo, when, outerr) - -NORESULT = object() -# -# public entrypoints / objects -# - -def basic_collect_report(item): - return CollectorRunner(item).run() - -def basic_run_report(item, pdb=None): - return ItemRunner(item, pdb=pdb).run() + res = collector._memocollect() + finally: + outerr = capture.reset() + except (Exit, KeyboardInterrupt): + raise + except: + excinfo = py.code.ExceptionInfo() + return event.CollectionReport(collector, res, excinfo, outerr) from cPickle import Pickler, Unpickler from cStringIO import StringIO From hpk at codespeak.net Fri Apr 3 23:18:42 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 3 Apr 2009 23:18:42 +0200 (CEST) Subject: [py-svn] r63588 - in py/trunk/py/test: . dist dist/testing plugin Message-ID: <20090403211842.9DD851684A6@codespeak.net> Author: hpk Date: Fri Apr 3 23:18:41 2009 New Revision: 63588 Modified: py/trunk/py/test/collect.py py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/dist/txnode.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/pytestplugin.py py/trunk/py/test/session.py Log: shift running of test item to be done through a plugin method. Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Fri Apr 3 23:18:41 2009 @@ -465,14 +465,8 @@ return self.config.pytestplugins.call_each( 'pytest_collect_directory', path=path, parent=self) -from py.__.test.runner import basic_run_report, forked_run_report class Item(Node): """ a basic test item. """ - def _getrunner(self): - if self.config.option.boxed: - return forked_run_report - return basic_run_report - def _deprecated_testexecution(self): if self.__class__.run != Item.run: warnoldtestrun() Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Fri Apr 3 23:18:41 2009 @@ -7,10 +7,9 @@ XSpec = py.execnet.XSpec def run(item, node): - runner = item._getrunner() - rep = runner(item) - rep.node = node - return rep + report = item.config.pytestplugins.do_itemrun(item) + report.node = node + return report class MockNode: def __init__(self): Modified: py/trunk/py/test/dist/txnode.py ============================================================================== --- py/trunk/py/test/dist/txnode.py (original) +++ py/trunk/py/test/dist/txnode.py Fri Apr 3 23:18:41 2009 @@ -132,7 +132,5 @@ raise def runtest(self, item): - runner = item._getrunner() - testrep = runner(item) - self.sendevent("itemtestreport", testrep) - + report = item.config.pytestplugins.do_itemrun(item) + self.sendevent("itemtestreport", report) Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Fri Apr 3 23:18:41 2009 @@ -36,6 +36,9 @@ def pytest_pymodule_makeitem(self, modcol, name, obj): """ return custom item/collector for a python object in a module, or None. """ + def pytest_itemrun(self, item, pdb=None): + """ run given test item and return test report. """ + # ------------------------------------------------------------------------------ # runtest related hooks # ------------------------------------------------------------------------------ Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Fri Apr 3 23:18:41 2009 @@ -3,6 +3,15 @@ class DefaultPlugin: """ Plugin implementing defaults and general options. """ + def pytest_itemrun(self, item, pdb=None): + from py.__.test.runner import basic_run_report, forked_run_report + if item.config.option.boxed: + runner = forked_run_report + else: + runner = basic_run_report + report = runner(item, pdb=pdb) + return report + def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): pyfuncitem.obj(*args, **kwargs) @@ -13,8 +22,8 @@ path in parent.config.args: if ext == ".py": return parent.Module(path, parent=parent) - - def pytest_collect_directory(self, path, parent): + + def pytest_collect_recurse(self, path, parent): #excludelist = parent._config.getvalue_pathlist('dir_exclude', path) #if excludelist and path in excludelist: # return @@ -24,7 +33,11 @@ if path == arg or arg.relto(path): break else: - return + return False + return True + + def pytest_collect_directory(self, path, parent): + # XXX reconsider the following comment # not use parent.Directory here as we generally # want dir/conftest.py to be able to # define Directory(dir) already Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Fri Apr 3 23:18:41 2009 @@ -105,6 +105,9 @@ self.pyplugins.call_each("pytest_unconfigure", config=config) config.bus.unregister(self) + def do_itemrun(self, item, pdb=None): + return self.pyplugins.call_firstresult("pytest_itemrun", item=item, pdb=pdb) + # # XXX old code to automatically load classes # Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Fri Apr 3 23:18:41 2009 @@ -133,7 +133,6 @@ post_mortem(excinfo._excinfo[2]) def runtest(self, item): - runner = item._getrunner() pdb = self.config.option.usepdb and self.runpdb or None - testrep = runner(item, pdb=pdb) - self.bus.notify("itemtestreport", testrep) + report = item.config.pytestplugins.do_itemrun(item, pdb=pdb) + self.bus.notify("itemtestreport", report) From hpk at codespeak.net Sat Apr 4 00:36:34 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 4 Apr 2009 00:36:34 +0200 (CEST) Subject: [py-svn] r63593 - in py/trunk/py/test: . dist dist/testing plugin Message-ID: <20090403223634.F188A1684C8@codespeak.net> Author: hpk Date: Sat Apr 4 00:36:29 2009 New Revision: 63593 Modified: py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/dist/testing/test_txnode.py py/trunk/py/test/dist/txnode.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/pytestplugin.py py/trunk/py/test/session.py Log: have plugin method run the test item and report about it Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Sat Apr 4 00:36:29 2009 @@ -7,9 +7,10 @@ XSpec = py.execnet.XSpec def run(item, node): - report = item.config.pytestplugins.do_itemrun(item) - report.node = node - return report + from py.__.test.runner import basic_run_report + rep = basic_run_report(item) + rep.node = node + return rep class MockNode: def __init__(self): @@ -218,7 +219,7 @@ session.loop_once(loopstate) assert node.sent == [[item]] - ev = run(item, node) + ev = run(item, node) session.queueevent("itemtestreport", ev) session.loop_once(loopstate) assert loopstate.shuttingdown Modified: py/trunk/py/test/dist/testing/test_txnode.py ============================================================================== --- py/trunk/py/test/dist/testing/test_txnode.py (original) +++ py/trunk/py/test/dist/testing/test_txnode.py Sat Apr 4 00:36:29 2009 @@ -28,6 +28,8 @@ if name == eventname: return args events.append(name) + if name == "internalerror": + print str(kwargs["excrepr"]) class MySetup: def __init__(self, pyfuncitem): Modified: py/trunk/py/test/dist/txnode.py ============================================================================== --- py/trunk/py/test/dist/txnode.py (original) +++ py/trunk/py/test/dist/txnode.py Sat Apr 4 00:36:29 2009 @@ -106,12 +106,16 @@ def sendevent(self, eventname, *args, **kwargs): self.channel.send((eventname, args, kwargs)) + def pyevent__itemtestreport(self, report): + self.sendevent("itemtestreport", report) + def run(self): channel = self.channel self.config, basetemp = channel.receive() if basetemp: self.config.basetemp = py.path.local(basetemp) self.config.pytestplugins.do_configure(self.config) + self.config.pytestplugins.register(self) self.sendevent("slaveready") try: while 1: @@ -121,16 +125,12 @@ break if isinstance(task, list): for item in task: - self.runtest(item) + item.config.pytestplugins.do_itemrun(item) else: - self.runtest(task) + task.config.pytestplugins.do_itemrun(item=task) except KeyboardInterrupt: raise except: er = py.code.ExceptionInfo().getrepr(funcargs=True, showlocals=True) self.sendevent("internalerror", excrepr=er) raise - - def runtest(self, item): - report = item.config.pytestplugins.do_itemrun(item) - self.sendevent("itemtestreport", report) Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Sat Apr 4 00:36:29 2009 @@ -10,7 +10,8 @@ else: runner = basic_run_report report = runner(item, pdb=pdb) - return report + item.config.pytestplugins.notify("itemtestreport", report) + return True def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): pyfuncitem.obj(*args, **kwargs) Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Sat Apr 4 00:36:29 2009 @@ -106,7 +106,9 @@ config.bus.unregister(self) def do_itemrun(self, item, pdb=None): - return self.pyplugins.call_firstresult("pytest_itemrun", item=item, pdb=pdb) + res = self.pyplugins.call_firstresult("pytest_itemrun", item=item, pdb=pdb) + if res is None: + raise ValueError("could not run %r" %(item,)) # # XXX old code to automatically load classes Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Sat Apr 4 00:36:29 2009 @@ -105,7 +105,6 @@ colitems = self.getinitialitems(colitems) self.shouldstop = False self.sessionstarts() - #self.bus.notify("testnodeready", maketestnodeready()) exitstatus = outcome.EXIT_OK captured_excinfo = None try: @@ -134,5 +133,4 @@ def runtest(self, item): pdb = self.config.option.usepdb and self.runpdb or None - report = item.config.pytestplugins.do_itemrun(item, pdb=pdb) - self.bus.notify("itemtestreport", report) + item.config.pytestplugins.do_itemrun(item, pdb=pdb) From hpk at codespeak.net Sat Apr 4 01:23:17 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 4 Apr 2009 01:23:17 +0200 (CEST) Subject: [py-svn] r63597 - in py/trunk/py/test: . dist testing Message-ID: <20090403232317.DF6A01684CE@codespeak.net> Author: hpk Date: Sat Apr 4 01:23:16 2009 New Revision: 63597 Modified: py/trunk/py/test/dist/txnode.py py/trunk/py/test/runner.py py/trunk/py/test/testing/test_runner_functional.py Log: remove unused code. fix errors. Modified: py/trunk/py/test/dist/txnode.py ============================================================================== --- py/trunk/py/test/dist/txnode.py (original) +++ py/trunk/py/test/dist/txnode.py Sat Apr 4 01:23:16 2009 @@ -19,8 +19,8 @@ self.putevent = putevent self.gateway = gateway self.channel = install_slave(gateway, config) - self.channel.setcallback(self.callback, endmarker=self.ENDMARK) self._sendslaveready = slaveready + self.channel.setcallback(self.callback, endmarker=self.ENDMARK) self._down = False def notify(self, eventname, *args, **kwargs): @@ -63,7 +63,7 @@ except: excinfo = py.code.ExceptionInfo() print "!" * 20, excinfo - self.notify_internal(excinfo) + self.config.pytestplugins.notify_exception(excinfo) def send(self, item): assert item is not None Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Sat Apr 4 01:23:16 2009 @@ -12,19 +12,6 @@ from py.__.test.outcome import Exit from py.__.test.dist.mypickle import ImmutablePickler -class RobustRun(object): - """ 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 - self.pdb = pdb - - def __repr__(self): - return "<%s colitem=%s>" %(self.__class__.__name__, self.colitem) - - def basic_run_report(item, pdb=None): """ return report about setting up and running a test item. """ excinfo = None Modified: py/trunk/py/test/testing/test_runner_functional.py ============================================================================== --- py/trunk/py/test/testing/test_runner_functional.py (original) +++ py/trunk/py/test/testing/test_runner_functional.py Sat Apr 4 01:23:16 2009 @@ -1,6 +1,5 @@ import py 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: @@ -262,10 +261,3 @@ assert ev.skipped -class TestRunnerRepr: - def test_runner_repr(self, testdir): - item = testdir.getitem("def test_func(): pass") - robustrun = RobustRun(item) - r = repr(robustrun) - assert r - assert r.find(item.name) != -1 From hpk at codespeak.net Sat Apr 4 02:23:05 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 4 Apr 2009 02:23:05 +0200 (CEST) Subject: [py-svn] r63598 - in py/trunk/py/test: . dist looponfail plugin Message-ID: <20090404002305.212771684D3@codespeak.net> Author: hpk Date: Sat Apr 4 02:23:04 2009 New Revision: 63598 Modified: py/trunk/py/test/dist/dsession.py py/trunk/py/test/dist/txnode.py py/trunk/py/test/looponfail/remote.py py/trunk/py/test/looponfail/util.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/session.py Log: cleanup of imports and shuffling of plugin method. Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Sat Apr 4 02:23:04 2009 @@ -5,7 +5,6 @@ """ import py -from py.__.test import event from py.__.test.runner import basic_run_report, basic_collect_report from py.__.test.session import Session from py.__.test import outcome @@ -237,6 +236,7 @@ self.node2pending[node].remove(item) def handle_crashitem(self, item, node): + from py.__.test import event longrepr = "!!! Node %r crashed during running of test %r" %(node, item) rep = event.ItemTestReport(item, when="???", excinfo=longrepr) rep.node = node Modified: py/trunk/py/test/dist/txnode.py ============================================================================== --- py/trunk/py/test/dist/txnode.py (original) +++ py/trunk/py/test/dist/txnode.py Sat Apr 4 02:23:04 2009 @@ -2,10 +2,8 @@ Manage setup, running and local representation of remote nodes/processes. """ import py -from py.__.test import event from py.__.test.dist.mypickle import PickleChannel - class TXNode(object): """ Represents a Test Execution environment in the controlling process. - sets up a slave node through an execnet gateway Modified: py/trunk/py/test/looponfail/remote.py ============================================================================== --- py/trunk/py/test/looponfail/remote.py (original) +++ py/trunk/py/test/looponfail/remote.py Sat Apr 4 02:23:04 2009 @@ -12,7 +12,6 @@ import py from py.__.test.session import Session from py.__.test.dist.mypickle import PickleChannel -from py.__.test import event from py.__.test.looponfail import util class LooponfailingSession(Session): Modified: py/trunk/py/test/looponfail/util.py ============================================================================== --- py/trunk/py/test/looponfail/util.py (original) +++ py/trunk/py/test/looponfail/util.py Sat Apr 4 02:23:04 2009 @@ -1,5 +1,4 @@ import py -from py.__.test import event class StatRecorder: def __init__(self, rootdirlist): Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Sat Apr 4 02:23:04 2009 @@ -13,6 +13,10 @@ item.config.pytestplugins.notify("itemtestreport", report) return True + def pytest_item_makereport(self, item, excinfo, when, outerr): + from py.__.test import event + return event.ItemTestReport(item, excinfo, when, outerr) + def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): pyfuncitem.obj(*args, **kwargs) @@ -163,10 +167,6 @@ from py.__.test.dist.dsession import DSession config.setsessionclass(DSession) - def pytest_item_makereport(self, item, excinfo, when, outerr): - from py.__.test import event - return event.ItemTestReport(item, excinfo, when, outerr) - def test_implied_different_sessions(tmpdir): def x(*args): config = py.test.config._reparse([tmpdir] + list(args)) Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Sat Apr 4 02:23:04 2009 @@ -6,7 +6,7 @@ """ import py -from py.__.test import event, outcome +from py.__.test import outcome # imports used for genitems() Item = py.test.collect.Item From hpk at codespeak.net Sat Apr 4 02:34:21 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 4 Apr 2009 02:34:21 +0200 (CEST) Subject: [py-svn] r63600 - in py/trunk/py/test: . dist dist/testing plugin Message-ID: <20090404003421.67C9616843A@codespeak.net> Author: hpk Date: Sat Apr 4 02:34:20 2009 New Revision: 63600 Removed: py/trunk/py/test/event.py Modified: py/trunk/py/test/dist/dsession.py py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/dist/testing/test_nodemanage.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_resultdb.py py/trunk/py/test/plugin/pytest_resultlog.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/runner.py Log: merge remaining content of event.py into runner.py. Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Sat Apr 4 02:34:20 2009 @@ -5,7 +5,7 @@ """ import py -from py.__.test.runner import basic_run_report, basic_collect_report +from py.__.test.runner import basic_run_report, basic_collect_report, ItemTestReport from py.__.test.session import Session from py.__.test import outcome from py.__.test.dist.nodemanage import NodeManager @@ -236,9 +236,8 @@ self.node2pending[node].remove(item) def handle_crashitem(self, item, node): - from py.__.test import event longrepr = "!!! Node %r crashed during running of test %r" %(node, item) - rep = event.ItemTestReport(item, when="???", excinfo=longrepr) + rep = ItemTestReport(item, when="???", excinfo=longrepr) rep.node = node self.bus.notify("itemtestreport", rep) Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Sat Apr 4 02:34:20 2009 @@ -1,6 +1,5 @@ from py.__.test.dist.dsession import DSession from py.__.test.runner import basic_collect_report -from py.__.test import event from py.__.test import outcome import py Modified: py/trunk/py/test/dist/testing/test_nodemanage.py ============================================================================== --- py/trunk/py/test/dist/testing/test_nodemanage.py (original) +++ py/trunk/py/test/dist/testing/test_nodemanage.py Sat Apr 4 02:34:20 2009 @@ -5,8 +5,6 @@ import py from py.__.test.dist.nodemanage import NodeManager -from py.__.test import event - def pytest_funcarg__source(pyfuncitem): return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source") def pytest_funcarg__dest(pyfuncitem): Deleted: /py/trunk/py/test/event.py ============================================================================== --- /py/trunk/py/test/event.py Sat Apr 4 02:34:20 2009 +++ (empty file) @@ -1,90 +0,0 @@ -""" - test collection and execution events -""" - -import py -import time -from py.__.test.outcome import Skipped - -# ---------------------------------------------------------------------- -# Events related to collecting and executing test Items -# ---------------------------------------------------------------------- - -class BaseReport(object): - def __repr__(self): - l = ["%s=%s" %(key, value) - for key, value in self.__dict__.items()] - return "<%s %s>" %(self.__class__.__name__, " ".join(l),) - - def toterminal(self, out): - longrepr = self.longrepr - if hasattr(longrepr, 'toterminal'): - 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 - if colitem and when != "setup": - self.keywords = colitem.readkeywords() - else: - # if we fail during setup it might mean - # we are not able to access the underlying object - # this might e.g. happen if we are unpickled - # and our parent collector did not collect us - # (because it e.g. skipped for platform reasons) - self.keywords = {} - 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 CollectionReport(BaseReport): - """ Collection Report. """ - skipped = failed = passed = False - - def __init__(self, colitem, result, excinfo=None, outerr=None): - self.colitem = colitem - if not excinfo: - self.passed = True - self.result = result - else: - self.outerr = outerr - self.longrepr = self.colitem._repr_failure_py(excinfo, outerr) - if excinfo.errisinstance(Skipped): - self.skipped = True - self.reason = str(excinfo.value) - else: - self.failed = True - - def toterminal(self, out): - longrepr = self.longrepr - if hasattr(longrepr, 'toterminal'): - longrepr.toterminal(out) - else: - out.line(str(longrepr)) - Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Sat Apr 4 02:34:20 2009 @@ -14,8 +14,8 @@ return True def pytest_item_makereport(self, item, excinfo, when, outerr): - from py.__.test import event - return event.ItemTestReport(item, excinfo, when, outerr) + from py.__.test import runner + return runner.ItemTestReport(item, excinfo, when, outerr) def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): pyfuncitem.obj(*args, **kwargs) Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Sat Apr 4 02:34:20 2009 @@ -4,7 +4,7 @@ import py import inspect -from py.__.test import event +from py.__.test import runner from py.__.test.config import Config as pytestConfig import api @@ -374,7 +374,7 @@ """ return a testreport whose dotted import path matches """ __tracebackhide__ = True l = [] - for rep in self.get(event.ItemTestReport): + for rep in self.get(runner.ItemTestReport): if inamepart in rep.colitem.listnames(): l.append(rep) if not l: @@ -397,23 +397,23 @@ bus.notify("anonymous") assert recorder.events assert not recorder.getfailures() - rep = event.ItemTestReport(None, None) + rep = runner.ItemTestReport(None, None) rep.passed = False rep.failed = True bus.notify("itemtestreport", rep) failures = recorder.getfailures() assert failures == [rep] - failures = recorder.get(event.ItemTestReport) + failures = recorder.get(runner.ItemTestReport) assert failures == [rep] failures = recorder.getnamed("itemtestreport") assert failures == [rep] - rep = event.ItemTestReport(None, None) + rep = runner.ItemTestReport(None, None) rep.passed = False rep.skipped = True bus.notify("itemtestreport", rep) - rep = event.CollectionReport(None, None) + rep = runner.CollectionReport(None, None) rep.passed = False rep.failed = True bus.notify("itemtestreport", rep) Modified: py/trunk/py/test/plugin/pytest_resultdb.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultdb.py (original) +++ py/trunk/py/test/plugin/pytest_resultdb.py Sat Apr 4 02:34:20 2009 @@ -344,7 +344,6 @@ py.test.skip("Needs a rewrite for db version.") # they are produced for example by a teardown failing # at the end of the run - from py.__.test import event try: raise ValueError except ValueError: Modified: py/trunk/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultlog.py (original) +++ py/trunk/py/test/plugin/pytest_resultlog.py Sat Apr 4 02:34:20 2009 @@ -209,7 +209,6 @@ 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 try: raise ValueError except ValueError: Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Sat Apr 4 02:34:20 2009 @@ -353,8 +353,7 @@ # # =============================================================================== -from py.__.test import event -from py.__.test.runner import basic_run_report +from py.__.test import runner class TestTerminal: @@ -373,7 +372,7 @@ rep.config.bus.notify("testrunstart") for item in testdir.genitems([modcol]): - ev = basic_run_report(item) + ev = runner.basic_run_report(item) rep.config.bus.notify("itemtestreport", ev) linecomp.assert_contains_lines([ "*test_pass_skip_fail.py .sF" @@ -404,7 +403,7 @@ rep.config.bus.notify("itemstart", item, None) s = linecomp.stringio.getvalue().strip() assert s.endswith(item.name) - rep.config.bus.notify("itemtestreport", basic_run_report(item)) + rep.config.bus.notify("itemtestreport", runner.basic_run_report(item)) linecomp.assert_contains_lines([ "*test_pass_skip_fail_verbose.py:2: *test_ok*PASS*", @@ -495,7 +494,7 @@ raise ValueError() """) rep = TerminalReporter(modcol.config, file=linecomp.stringio) - reports = [basic_run_report(x) for x in modcol.collect()] + reports = [runner.basic_run_report(x) for x in modcol.collect()] rep.pyevent__looponfailinfo(reports, [modcol.config.topdir]) linecomp.assert_contains_lines([ "*test_looponfailreport.py:2: assert 0", @@ -521,7 +520,8 @@ rep.config.bus.notify("testrunstart") rep.config.bus.notify("testrunstart") for item in testdir.genitems([modcol]): - rep.config.bus.notify("itemtestreport", basic_run_report(item)) + rep.config.bus.notify("itemtestreport", + runner.basic_run_report(item)) rep.config.bus.notify("testrunfinish", exitstatus=1) s = linecomp.stringio.getvalue() if tbopt == "long": @@ -569,7 +569,8 @@ bus.notify("testrunstart") try: for item in testdir.genitems([modcol]): - bus.notify("itemtestreport", basic_run_report(item)) + bus.notify("itemtestreport", + runner.basic_run_report(item)) except KeyboardInterrupt: excinfo = py.code.ExceptionInfo() else: @@ -602,12 +603,12 @@ lineno = 3 message = "justso" - ev1 = event.CollectionReport(None, None) + ev1 = runner.CollectionReport(None, None) ev1.when = "execute" ev1.skipped = True ev1.longrepr = longrepr - ev2 = event.ItemTestReport(None, excinfo=longrepr) + ev2 = runner.ItemTestReport(None, excinfo=longrepr) ev2.skipped = True l = folded_skips([ev1, ev2]) @@ -637,7 +638,7 @@ " ", ]) rep.config.bus.notify( "collectionreport", - event.CollectionReport(modcol, [], excinfo=None)) + runner.CollectionReport(modcol, [], excinfo=None)) assert rep.indent == indent def test_collectonly_skipped_module(self, testdir, linecomp): Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Sat Apr 4 02:34:20 2009 @@ -8,9 +8,7 @@ import py, os, sys -from py.__.test import event -from py.__.test.outcome import Exit -from py.__.test.dist.mypickle import ImmutablePickler +from py.__.test.outcome import Exit, Skipped def basic_run_report(item, pdb=None): """ return report about setting up and running a test item. """ @@ -54,14 +52,14 @@ raise except: excinfo = py.code.ExceptionInfo() - return event.CollectionReport(collector, res, excinfo, outerr) + return CollectionReport(collector, res, excinfo, outerr) from cPickle import Pickler, Unpickler from cStringIO import StringIO def forked_run_report(item, pdb=None): EXITSTATUS_TESTEXIT = 4 - + from py.__.test.dist.mypickle import ImmutablePickler ipickle = ImmutablePickler(uneven=0) ipickle.selfmemoize(item.config) def runforked(): @@ -86,4 +84,83 @@ ("X", "CRASHED"), ("%s:%s: CRASHED with signal %d" %(path, lineno, result.signal)), ] - return event.ItemTestReport(item, excinfo=longrepr, when="???") + return ItemTestReport(item, excinfo=longrepr, when="???") + +class BaseReport(object): + def __repr__(self): + l = ["%s=%s" %(key, value) + for key, value in self.__dict__.items()] + return "<%s %s>" %(self.__class__.__name__, " ".join(l),) + + def toterminal(self, out): + longrepr = self.longrepr + if hasattr(longrepr, 'toterminal'): + 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 + if colitem and when != "setup": + self.keywords = colitem.readkeywords() + else: + # if we fail during setup it might mean + # we are not able to access the underlying object + # this might e.g. happen if we are unpickled + # and our parent collector did not collect us + # (because it e.g. skipped for platform reasons) + self.keywords = {} + 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 CollectionReport(BaseReport): + """ Collection Report. """ + skipped = failed = passed = False + + def __init__(self, colitem, result, excinfo=None, outerr=None): + self.colitem = colitem + if not excinfo: + self.passed = True + self.result = result + else: + self.outerr = outerr + self.longrepr = self.colitem._repr_failure_py(excinfo, outerr) + if excinfo.errisinstance(Skipped): + self.skipped = True + self.reason = str(excinfo.value) + else: + self.failed = True + + def toterminal(self, out): + longrepr = self.longrepr + if hasattr(longrepr, 'toterminal'): + longrepr.toterminal(out) + else: + out.line(str(longrepr)) + From hpk at codespeak.net Sat Apr 4 02:35:28 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 4 Apr 2009 02:35:28 +0200 (CEST) Subject: [py-svn] r63601 - py/trunk/py/test/dist Message-ID: <20090404003528.64F111684A6@codespeak.net> Author: hpk Date: Sat Apr 4 02:35:27 2009 New Revision: 63601 Modified: py/trunk/py/test/dist/dsession.py Log: remove dead code. Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Sat Apr 4 02:35:27 2009 @@ -251,14 +251,3 @@ def teardown(self): """ teardown any resources after a test run. """ self.nodemanager.teardown_nodes() - -# debugging function -def dump_picklestate(item): - l = [] - while 1: - state = item.__getstate__() - l.append(state) - item = state[-1] - if len(state) != 2: - break - return l From hpk at codespeak.net Sat Apr 4 21:06:23 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 4 Apr 2009 21:06:23 +0200 (CEST) Subject: [py-svn] r63628 - in py/trunk/py/test: . plugin testing Message-ID: <20090404190623.23FE6168461@codespeak.net> Author: hpk Date: Sat Apr 4 21:06:20 2009 New Revision: 63628 Added: py/trunk/py/test/testing/test_fixture_and_setup.py - copied, changed from r63589, py/trunk/py/test/testing/test_setup_nested.py Removed: py/trunk/py/test/testing/test_setup_nested.py Modified: py/trunk/py/test/config.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/runner.py Log: * shuffle SetupState and fixture handling into runner.py * introduce a itemsetupreport and new setupitem/teardownitem methods. * more tests Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Sat Apr 4 21:06:20 2009 @@ -3,6 +3,7 @@ from py.__.test import parseopt from py.__.misc.warn import APIWARN +from py.__.test.runner import SetupState def ensuretemp(string, dir=1): """ return temporary directory path with @@ -312,34 +313,6 @@ else: return pkgdir.dirpath() -class SetupState(object): - """ shared state for setting up/tearing down test items or collectors. """ - def __init__(self): - self.stack = [] - - def teardown_all(self): - while self.stack: - col = self.stack.pop() - col.teardown() - - def teardown_exact(self, item): - if self.stack and self.stack[-1] == item: - col = self.stack.pop() - col.teardown() - - def prepare(self, colitem): - """ setup objects along the collector chain to the test-method - Teardown any unneccessary previously setup objects.""" - - needed_collectors = colitem.listchain() - while self.stack: - if self.stack == needed_collectors[:len(self.stack)]: - break - col = self.stack.pop() - col.teardown() - for col in needed_collectors[len(self.stack):]: - col.setup() - self.stack.append(col) # this is the one per-process instance of py.test configuration config_per_process = Config( Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Sat Apr 4 21:06:20 2009 @@ -85,6 +85,9 @@ def pyevent__itemtestreport(self, event): """ test has been run. """ + def pyevent__itemsetupreport(self, rep): + """ test has been run. """ + def pyevent__deselected(self, items): """ collected items that were deselected (by keyword). """ Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Sat Apr 4 21:06:20 2009 @@ -74,6 +74,11 @@ if hasattr(self, '_olddir'): self._olddir.chdir() + def geteventrecorder(self, config): + evrec = EventRecorder(config.bus) + self.pyfuncitem.addfinalizer(lambda: config.bus.unregister(evrec)) + return evrec + def chdir(self): old = self.tmpdir.chdir() if not hasattr(self, '_olddir'): @@ -174,9 +179,12 @@ 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 + moditems = modcol.collect() + for item in modcol.collect(): + if item.name == funcname: + return item + else: + assert 0, "%r item not found in module:\n%s" %(funcname, source) def getitems(self, source): modcol = self.getmodulecol(source) @@ -284,12 +292,13 @@ for i, event in py.builtin.enumerate(self.events): if event.name == name: del self.events[i] - return event + eventparser = self.geteventparser(name) + return eventparser(*event.args, **event.kwargs) raise KeyError("popevent: %r not found in %r" %(name, self.events)) def getevents(self, eventname): """ return list of ParsedEvent instances matching the given eventname. """ - method = self.geteventmethod(eventname) + method = self.geteventparser(eventname) l = [] for event in self.events: if event.name == eventname: @@ -297,23 +306,18 @@ l.append(pevent) return l - def geteventmethod(self, eventname): + def geteventparser(self, eventname): mname = "pyevent__" + eventname method = getattr(api.Events, mname) args, varargs, varkw, default = inspect.getargspec(method) assert args[0] == "self" args = args[1:] fspec = inspect.formatargspec(args, varargs, varkw, default) - source = """def %(mname)s%(fspec)s: - return ParsedEvent(locals())""" % locals() - print source - exec source + code = py.code.compile("""def %(mname)s%(fspec)s: + return ParsedEvent(locals())""" % locals()) + exec code return locals()[mname] - - def firstparsedevent(self, eventname): - return self.parsedevents(eventname)[0] - def get(self, cls): l = [] for event in self.events: Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Sat Apr 4 21:06:20 2009 @@ -6,7 +6,7 @@ * and generating report events about it """ -import py, os, sys +import py from py.__.test.outcome import Exit, Skipped @@ -55,7 +55,6 @@ return CollectionReport(collector, res, excinfo, outerr) from cPickle import Pickler, Unpickler -from cStringIO import StringIO def forked_run_report(item, pdb=None): EXITSTATUS_TESTEXIT = 4 @@ -66,7 +65,7 @@ try: testrep = basic_run_report(item) except (KeyboardInterrupt, Exit): - os._exit(EXITSTATUS_TESTEXIT) + py.std.os._exit(EXITSTATUS_TESTEXIT) return ipickle.dumps(testrep) ff = py.process.ForkedFunc(runforked) @@ -164,3 +163,70 @@ else: out.line(str(longrepr)) +class ItemSetupReport(BaseReport): + """ Test Execution Report. """ + failed = passed = skipped = False + + def __init__(self, item, excinfo=None, outerr=None): + self.item = item + self.outerr = outerr + if not excinfo: + self.passed = True + else: + if excinfo.errisinstance(Skipped): + self.skipped = True + else: + self.failed = True + self.excrepr = item._repr_failure_py(excinfo, outerr) + +class SetupState(object): + """ shared state for setting up/tearing down test items or collectors. """ + def __init__(self): + self.stack = [] + + def teardown_all(self): + while self.stack: + col = self.stack.pop() + col.teardown() + + def teardown_exact(self, item): + if self.stack and self.stack[-1] == item: + col = self.stack.pop() + col.teardown() + + def prepare(self, colitem): + """ setup objects along the collector chain to the test-method + Teardown any unneccessary previously setup objects.""" + needed_collectors = colitem.listchain() + while self.stack: + if self.stack == needed_collectors[:len(self.stack)]: + break + col = self.stack.pop() + col.teardown() + for col in needed_collectors[len(self.stack):]: + col.setup() + self.stack.append(col) + + def fixturecall(self, callable, item): + excinfo = None + capture = item.config._getcapture() + try: + try: + callable(item) + except (KeyboardInterrupt, SystemExit): + raise + except: + excinfo = py.code.ExceptionInfo() + finally: + outerr = capture.reset() + if not excinfo: + return True + else: + rep = ItemSetupReport(item, excinfo, outerr) + item.config.pytestplugins.notify("itemsetupreport", rep) + + def setupitem(self, item): + return self.fixturecall(self.prepare, item) + + def teardownitem(self, item): + self.fixturecall(self.teardown_exact, item) Copied: py/trunk/py/test/testing/test_fixture_and_setup.py (from r63589, py/trunk/py/test/testing/test_setup_nested.py) ============================================================================== --- py/trunk/py/test/testing/test_setup_nested.py (original) +++ py/trunk/py/test/testing/test_fixture_and_setup.py Sat Apr 4 21:06:20 2009 @@ -138,3 +138,74 @@ assert not hasattr(self, 'world') """) sorter.assertoutcome(passed=4, failed=0) + +from py.__.test.config import SetupState + +class TestSetupState: + def test_setupitem_works(self, testdir): + item = testdir.getitem(""" + def setup_module(mod): + pass + def test_func(): + pass + """) + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + res = setup.setupitem(item) + assert res + + def test_setupitem_fails(self, testdir): + item = testdir.getitem(""" + def setup_module(mod): + print "world" + raise ValueError(42) + def test_func(): + pass + """) + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + res = setup.setupitem(item) + assert not res + rep = evrec.popevent("itemsetupreport").rep + assert rep.failed + assert not rep.skipped + assert rep.excrepr + assert "42" in str(rep.excrepr) + assert rep.outerr[0].find("world") != -1 + + def test_teardownitem_fails(self, testdir): + item = testdir.getitem(""" + def test_func(): + pass + def teardown_function(func): + print "13" + raise ValueError(25) + """) + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + res = setup.setupitem(item) + assert res + setup.teardownitem(item) + rep = evrec.popevent("itemsetupreport").rep + assert rep.item == item + assert rep.failed + assert not rep.passed + assert "13" in rep.outerr[0] + assert "25" in str(rep.excrepr) + + def test_setupitem_skips(self, testdir): + item = testdir.getitem(""" + import py + def setup_module(mod): + py.test.skip("17") + def test_func(): + pass + """) + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + setup.setupitem(item) + rep = evrec.popevent("itemsetupreport").rep + assert not rep.failed + assert rep.skipped + assert rep.excrepr + assert "17" in str(rep.excrepr) Deleted: /py/trunk/py/test/testing/test_setup_nested.py ============================================================================== --- /py/trunk/py/test/testing/test_setup_nested.py Sat Apr 4 21:06:20 2009 +++ (empty file) @@ -1,140 +0,0 @@ -# -# test correct setup/teardowns at -# module, class, and instance level - -def test_module_and_function_setup(testdir): - sorter = testdir.inline_runsource(""" - modlevel = [] - def setup_module(module): - assert not modlevel - module.modlevel.append(42) - - def teardown_module(module): - modlevel.pop() - - def setup_function(function): - function.answer = 17 - - def teardown_function(function): - del function.answer - - def test_modlevel(): - assert modlevel[0] == 42 - assert test_modlevel.answer == 17 - - class TestFromClass: - def test_module(self): - assert modlevel[0] == 42 - assert not hasattr(test_modlevel, 'answer') - """) - rep = sorter.getreport("test_modlevel") - assert rep.passed - rep = sorter.getreport("test_module") - assert rep.passed - -def test_class_setup(testdir): - sorter = testdir.inline_runsource(""" - class TestSimpleClassSetup: - clslevel = [] - def setup_class(cls): - cls.clslevel.append(23) - - def teardown_class(cls): - cls.clslevel.pop() - - def test_classlevel(self): - assert self.clslevel[0] == 23 - - class TestInheritedClassSetupStillWorks(TestSimpleClassSetup): - def test_classlevel_anothertime(self): - assert self.clslevel == [23] - - def test_cleanup(): - assert not TestSimpleClassSetup.clslevel - assert not TestInheritedClassSetupStillWorks.clslevel - """) - sorter.assertoutcome(passed=1+2+1) - -def test_method_setup(testdir): - sorter = testdir.inline_runsource(""" - class TestSetupMethod: - def setup_method(self, meth): - self.methsetup = meth - def teardown_method(self, meth): - del self.methsetup - - def test_some(self): - assert self.methsetup == self.test_some - - def test_other(self): - assert self.methsetup == self.test_other - """) - sorter.assertoutcome(passed=2) - -def test_method_generator_setup(testdir): - sorter = testdir.inline_runsource(""" - class TestSetupTeardownOnInstance: - def setup_class(cls): - cls.classsetup = True - - def setup_method(self, method): - self.methsetup = method - - def test_generate(self): - assert self.classsetup - assert self.methsetup == self.test_generate - yield self.generated, 5 - yield self.generated, 2 - - def generated(self, value): - assert self.classsetup - assert self.methsetup == self.test_generate - assert value == 5 - """) - sorter.assertoutcome(passed=1, failed=1) - -def test_func_generator_setup(testdir): - sorter = testdir.inline_runsource(""" - import sys - - def setup_module(mod): - print "setup_module" - mod.x = [] - - def setup_function(fun): - print "setup_function" - x.append(1) - - def teardown_function(fun): - print "teardown_function" - x.pop() - - def test_one(): - assert x == [1] - def check(): - print "check" - print >>sys.stderr, "e" - assert x == [1] - yield check - assert x == [1] - """) - rep = sorter.getreport("test_one") - assert rep.passed - -def test_method_setup_uses_fresh_instances(testdir): - sorter = testdir.inline_runsource(""" - class TestSelfState1: - def __init__(self): - self.hello = 42 - def test_hello(self): - self.world = 23 - def test_afterhello(self): - assert not hasattr(self, 'world') - assert self.hello == 42 - class TestSelfState2: - def test_hello(self): - self.world = 10 - def test_world(self): - assert not hasattr(self, 'world') - """) - sorter.assertoutcome(passed=4, failed=0) From hpk at codespeak.net Sat Apr 4 22:19:19 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sat, 4 Apr 2009 22:19:19 +0200 (CEST) Subject: [py-svn] r63630 - in py/trunk/py/test: . plugin testing Message-ID: <20090404201919.A662E168428@codespeak.net> Author: hpk Date: Sat Apr 4 22:19:18 2009 New Revision: 63630 Added: py/trunk/py/test/plugin/pytest_pdb.py py/trunk/py/test/testing/test_runner.py py/trunk/py/test/testing/test_setup_functional.py - copied, changed from r63628, py/trunk/py/test/testing/test_fixture_and_setup.py Removed: py/trunk/py/test/testing/test_fixture_and_setup.py Modified: py/trunk/py/test/defaultconftest.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/runner.py Log: some more code to prepare for substituting the fixture/runtest code, using more plugins. Modified: py/trunk/py/test/defaultconftest.py ============================================================================== --- py/trunk/py/test/defaultconftest.py (original) +++ py/trunk/py/test/defaultconftest.py Sat Apr 4 22:19:18 2009 @@ -10,5 +10,5 @@ Function = py.test.collect.Function Instance = py.test.collect.Instance -pytest_plugins = "default terminal xfail tmpdir execnetcleanup monkeypatch".split() +pytest_plugins = "default terminal xfail tmpdir execnetcleanup monkeypatch pdb".split() Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Sat Apr 4 22:19:18 2009 @@ -85,9 +85,12 @@ def pyevent__itemtestreport(self, event): """ test has been run. """ - def pyevent__itemsetupreport(self, rep): + def pyevent__item_runtest_finished(self, item, excinfo, outerr): """ test has been run. """ + def pyevent__itemsetupreport(self, rep): + """ a report on running a fixture function. """ + def pyevent__deselected(self, items): """ collected items that were deselected (by keyword). """ Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Sat Apr 4 22:19:18 2009 @@ -17,6 +17,11 @@ from py.__.test import runner return runner.ItemTestReport(item, excinfo, when, outerr) + def pyevent__item_runtest_finished(self, item, excinfo, outerr): + from py.__.test import runner + rep = runner.ItemTestReport(item, excinfo, "execute", outerr) + item.config.pytestplugins.notify("itemtestreport", rep) + def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): pyfuncitem.obj(*args, **kwargs) Added: py/trunk/py/test/plugin/pytest_pdb.py ============================================================================== --- (empty file) +++ py/trunk/py/test/plugin/pytest_pdb.py Sat Apr 4 22:19:18 2009 @@ -0,0 +1,9 @@ +from py.__.test.custompdb import post_mortem + +class PdbPlugin: + def pytest_item_runtest_finished(self, item, excinfo, outerr): + if excinfo and item.config.option.usepdb: + tw = py.io.TerminalWriter() + repr = excinfo.getrepr() + repr.toterminal(tw) + post_mortem(excinfo._excinfo[2]) Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Sat Apr 4 22:19:18 2009 @@ -166,7 +166,6 @@ class ItemSetupReport(BaseReport): """ Test Execution Report. """ failed = passed = skipped = False - def __init__(self, item, excinfo=None, outerr=None): self.item = item self.outerr = outerr @@ -177,7 +176,7 @@ self.skipped = True else: self.failed = True - self.excrepr = item._repr_failure_py(excinfo, outerr) + self.excrepr = item._repr_failure_py(excinfo, []) class SetupState(object): """ shared state for setting up/tearing down test items or collectors. """ @@ -207,26 +206,42 @@ col.setup() self.stack.append(col) - def fixturecall(self, callable, item): - excinfo = None - capture = item.config._getcapture() - try: - try: - callable(item) - except (KeyboardInterrupt, SystemExit): - raise - except: - excinfo = py.code.ExceptionInfo() - finally: - outerr = capture.reset() - if not excinfo: - return True - else: + def do_setup(self, item): + excinfo, outerr = guarded_call(item.config._getcapture(), + lambda: self.prepare(item) + ) + rep = ItemSetupReport(item, excinfo, outerr) + item.config.pytestplugins.notify("itemsetupreport", rep) + return not excinfo + + def do_teardown(self, item): + excinfo, outerr = guarded_call(item.config._getcapture(), + lambda: self.teardown_exact(item) + ) + if excinfo: rep = ItemSetupReport(item, excinfo, outerr) item.config.pytestplugins.notify("itemsetupreport", rep) - def setupitem(self, item): - return self.fixturecall(self.prepare, item) + def do_fixture_and_runtest(self, item): + """ setup fixture and perform actual item.runtest(). """ + if self.do_setup(item): + excinfo, outerr = guarded_call(item.config._getcapture(), + lambda: item.runtest()) + item.config.pytestplugins.notify( + "item_runtest_finished", + item=item, excinfo=excinfo, outerr=outerr) + self.do_teardown(item) + +def guarded_call(capture, call): + excinfo = None + try: + try: + call() + except (KeyboardInterrupt, Exit): + raise + except: + excinfo = py.code.ExceptionInfo() + finally: + outerr = capture.reset() + return excinfo, outerr - def teardownitem(self, item): - self.fixturecall(self.teardown_exact, item) Deleted: /py/trunk/py/test/testing/test_fixture_and_setup.py ============================================================================== --- /py/trunk/py/test/testing/test_fixture_and_setup.py Sat Apr 4 22:19:18 2009 +++ (empty file) @@ -1,211 +0,0 @@ -# -# test correct setup/teardowns at -# module, class, and instance level - -def test_module_and_function_setup(testdir): - sorter = testdir.inline_runsource(""" - modlevel = [] - def setup_module(module): - assert not modlevel - module.modlevel.append(42) - - def teardown_module(module): - modlevel.pop() - - def setup_function(function): - function.answer = 17 - - def teardown_function(function): - del function.answer - - def test_modlevel(): - assert modlevel[0] == 42 - assert test_modlevel.answer == 17 - - class TestFromClass: - def test_module(self): - assert modlevel[0] == 42 - assert not hasattr(test_modlevel, 'answer') - """) - rep = sorter.getreport("test_modlevel") - assert rep.passed - rep = sorter.getreport("test_module") - assert rep.passed - -def test_class_setup(testdir): - sorter = testdir.inline_runsource(""" - class TestSimpleClassSetup: - clslevel = [] - def setup_class(cls): - cls.clslevel.append(23) - - def teardown_class(cls): - cls.clslevel.pop() - - def test_classlevel(self): - assert self.clslevel[0] == 23 - - class TestInheritedClassSetupStillWorks(TestSimpleClassSetup): - def test_classlevel_anothertime(self): - assert self.clslevel == [23] - - def test_cleanup(): - assert not TestSimpleClassSetup.clslevel - assert not TestInheritedClassSetupStillWorks.clslevel - """) - sorter.assertoutcome(passed=1+2+1) - -def test_method_setup(testdir): - sorter = testdir.inline_runsource(""" - class TestSetupMethod: - def setup_method(self, meth): - self.methsetup = meth - def teardown_method(self, meth): - del self.methsetup - - def test_some(self): - assert self.methsetup == self.test_some - - def test_other(self): - assert self.methsetup == self.test_other - """) - sorter.assertoutcome(passed=2) - -def test_method_generator_setup(testdir): - sorter = testdir.inline_runsource(""" - class TestSetupTeardownOnInstance: - def setup_class(cls): - cls.classsetup = True - - def setup_method(self, method): - self.methsetup = method - - def test_generate(self): - assert self.classsetup - assert self.methsetup == self.test_generate - yield self.generated, 5 - yield self.generated, 2 - - def generated(self, value): - assert self.classsetup - assert self.methsetup == self.test_generate - assert value == 5 - """) - sorter.assertoutcome(passed=1, failed=1) - -def test_func_generator_setup(testdir): - sorter = testdir.inline_runsource(""" - import sys - - def setup_module(mod): - print "setup_module" - mod.x = [] - - def setup_function(fun): - print "setup_function" - x.append(1) - - def teardown_function(fun): - print "teardown_function" - x.pop() - - def test_one(): - assert x == [1] - def check(): - print "check" - print >>sys.stderr, "e" - assert x == [1] - yield check - assert x == [1] - """) - rep = sorter.getreport("test_one") - assert rep.passed - -def test_method_setup_uses_fresh_instances(testdir): - sorter = testdir.inline_runsource(""" - class TestSelfState1: - def __init__(self): - self.hello = 42 - def test_hello(self): - self.world = 23 - def test_afterhello(self): - assert not hasattr(self, 'world') - assert self.hello == 42 - class TestSelfState2: - def test_hello(self): - self.world = 10 - def test_world(self): - assert not hasattr(self, 'world') - """) - sorter.assertoutcome(passed=4, failed=0) - -from py.__.test.config import SetupState - -class TestSetupState: - def test_setupitem_works(self, testdir): - item = testdir.getitem(""" - def setup_module(mod): - pass - def test_func(): - pass - """) - evrec = testdir.geteventrecorder(item.config) - setup = SetupState() - res = setup.setupitem(item) - assert res - - def test_setupitem_fails(self, testdir): - item = testdir.getitem(""" - def setup_module(mod): - print "world" - raise ValueError(42) - def test_func(): - pass - """) - evrec = testdir.geteventrecorder(item.config) - setup = SetupState() - res = setup.setupitem(item) - assert not res - rep = evrec.popevent("itemsetupreport").rep - assert rep.failed - assert not rep.skipped - assert rep.excrepr - assert "42" in str(rep.excrepr) - assert rep.outerr[0].find("world") != -1 - - def test_teardownitem_fails(self, testdir): - item = testdir.getitem(""" - def test_func(): - pass - def teardown_function(func): - print "13" - raise ValueError(25) - """) - evrec = testdir.geteventrecorder(item.config) - setup = SetupState() - res = setup.setupitem(item) - assert res - setup.teardownitem(item) - rep = evrec.popevent("itemsetupreport").rep - assert rep.item == item - assert rep.failed - assert not rep.passed - assert "13" in rep.outerr[0] - assert "25" in str(rep.excrepr) - - def test_setupitem_skips(self, testdir): - item = testdir.getitem(""" - import py - def setup_module(mod): - py.test.skip("17") - def test_func(): - pass - """) - evrec = testdir.geteventrecorder(item.config) - setup = SetupState() - setup.setupitem(item) - rep = evrec.popevent("itemsetupreport").rep - assert not rep.failed - assert rep.skipped - assert rep.excrepr - assert "17" in str(rep.excrepr) Added: py/trunk/py/test/testing/test_runner.py ============================================================================== --- (empty file) +++ py/trunk/py/test/testing/test_runner.py Sat Apr 4 22:19:18 2009 @@ -0,0 +1,91 @@ + +from py.__.test.config import SetupState + +class TestSetupState: + def test_setup_ok(self, testdir): + item = testdir.getitem(""" + def setup_module(mod): + pass + def test_func(): + pass + """) + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + res = setup.do_setup(item) + assert res + + def test_setup_fails(self, testdir): + item = testdir.getitem(""" + def setup_module(mod): + print "world" + raise ValueError(42) + def test_func(): + pass + """) + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + res = setup.do_setup(item) + assert not res + rep = evrec.popevent("itemsetupreport").rep + assert rep.failed + assert not rep.skipped + assert rep.excrepr + assert "42" in str(rep.excrepr) + assert rep.outerr[0].find("world") != -1 + + def test_teardown_fails(self, testdir): + item = testdir.getitem(""" + def test_func(): + pass + def teardown_function(func): + print "13" + raise ValueError(25) + """) + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + res = setup.do_setup(item) + assert res + rep = evrec.popevent("itemsetupreport").rep + assert rep.passed + setup.do_teardown(item) + rep = evrec.popevent("itemsetupreport").rep + assert rep.item == item + assert rep.failed + assert not rep.passed + assert "13" in rep.outerr[0] + assert "25" in str(rep.excrepr) + + def test_setupitem_skips(self, testdir): + item = testdir.getitem(""" + import py + def setup_module(mod): + py.test.skip("17") + def test_func(): + pass + """) + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + setup.do_setup(item) + rep = evrec.popevent("itemsetupreport").rep + assert not rep.failed + assert rep.skipped + assert rep.excrepr + assert "17" in str(rep.excrepr) + + def test_runtest_ok(self, testdir): + item = testdir.getitem("def test_func(): pass") + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + setup.do_fixture_and_runtest(item) + rep = evrec.popevent("itemtestreport").event + assert rep.passed + + def test_runtest_fails(self, testdir): + item = testdir.getitem("def test_func(): assert 0") + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + setup.do_fixture_and_runtest(item) + event = evrec.popevent("item_runtest_finished") + assert event.excinfo + + Copied: py/trunk/py/test/testing/test_setup_functional.py (from r63628, py/trunk/py/test/testing/test_fixture_and_setup.py) ============================================================================== --- py/trunk/py/test/testing/test_fixture_and_setup.py (original) +++ py/trunk/py/test/testing/test_setup_functional.py Sat Apr 4 22:19:18 2009 @@ -139,73 +139,3 @@ """) sorter.assertoutcome(passed=4, failed=0) -from py.__.test.config import SetupState - -class TestSetupState: - def test_setupitem_works(self, testdir): - item = testdir.getitem(""" - def setup_module(mod): - pass - def test_func(): - pass - """) - evrec = testdir.geteventrecorder(item.config) - setup = SetupState() - res = setup.setupitem(item) - assert res - - def test_setupitem_fails(self, testdir): - item = testdir.getitem(""" - def setup_module(mod): - print "world" - raise ValueError(42) - def test_func(): - pass - """) - evrec = testdir.geteventrecorder(item.config) - setup = SetupState() - res = setup.setupitem(item) - assert not res - rep = evrec.popevent("itemsetupreport").rep - assert rep.failed - assert not rep.skipped - assert rep.excrepr - assert "42" in str(rep.excrepr) - assert rep.outerr[0].find("world") != -1 - - def test_teardownitem_fails(self, testdir): - item = testdir.getitem(""" - def test_func(): - pass - def teardown_function(func): - print "13" - raise ValueError(25) - """) - evrec = testdir.geteventrecorder(item.config) - setup = SetupState() - res = setup.setupitem(item) - assert res - setup.teardownitem(item) - rep = evrec.popevent("itemsetupreport").rep - assert rep.item == item - assert rep.failed - assert not rep.passed - assert "13" in rep.outerr[0] - assert "25" in str(rep.excrepr) - - def test_setupitem_skips(self, testdir): - item = testdir.getitem(""" - import py - def setup_module(mod): - py.test.skip("17") - def test_func(): - pass - """) - evrec = testdir.geteventrecorder(item.config) - setup = SetupState() - setup.setupitem(item) - rep = evrec.popevent("itemsetupreport").rep - assert not rep.failed - assert rep.skipped - assert rep.excrepr - assert "17" in str(rep.excrepr) From hpk at codespeak.net Sun Apr 5 20:40:17 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 5 Apr 2009 20:40:17 +0200 (CEST) Subject: [py-svn] r63683 - py/trunk/contrib Message-ID: <20090405184017.1EBC91684F4@codespeak.net> Author: hpk Date: Sun Apr 5 20:40:16 2009 New Revision: 63683 Added: py/trunk/contrib/svn-sync-repo.py Log: a small script to perform svn-hotsyncing Added: py/trunk/contrib/svn-sync-repo.py ============================================================================== --- (empty file) +++ py/trunk/contrib/svn-sync-repo.py Sun Apr 5 20:40:16 2009 @@ -0,0 +1,116 @@ +#!/usr/bin/env python + +""" + +small utility for hot-syncing a svn repository through ssh. +uses py.execnet. + +""" + +import py +import sys, os + +def usage(): + arg0 = sys.argv[0] + print """%s [user@]remote-host:/repo/location localrepo [identity keyfile]""" % (arg0,) + + +def main(args): + remote = args[0] + localrepo = py.path.local(args[1]) + if not localrepo.check(dir=1): + raise SystemExit("localrepo %s does not exist" %(localrepo,)) + if len(args) == 3: + keyfile = py.path.local(args[2]) + else: + keyfile = None + remote_host, path = remote.split(':', 1) + print "ssh-connecting to", remote_host + gw = getgateway(remote_host, keyfile) + + local_rev = get_svn_youngest(localrepo) + + # local protocol + # 1. client sends rev/repo -> server + # 2. server checks for newer revisions and sends dumps + # 3. client receives dumps, updates local repo + # 4. client goes back to step 1 + c = gw.remote_exec(""" + import py + import os + remote_rev, repopath = channel.receive() + while 1: + rev = py.process.cmdexec('svnlook youngest "%s"' % repopath) + rev = int(rev) + if rev > remote_rev: + revrange = (remote_rev+1, rev) + dumpchannel = channel.gateway.newchannel() + channel.send(revrange) + channel.send(dumpchannel) + + f = os.popen( + "svnadmin dump -q --incremental -r %s:%s %s" + % (revrange[0], revrange[1], repopath), 'r') + try: + maxcount = dumpchannel.receive() + count = maxcount + while 1: + s = f.read(8192) + if not s: + raise EOFError + dumpchannel.send(s) + count = count - 1 + if count <= 0: + ack = dumpchannel.receive() + count = maxcount + + except EOFError: + dumpchannel.close() + remote_rev = rev + else: + # using svn-hook instead would be nice here + py.std.time.sleep(30) + """) + + c.send((local_rev, path)) + print "checking revisions from %d in %s" %(local_rev, remote) + while 1: + revstart, revend = c.receive() + dumpchannel = c.receive() + print "receiving revisions", revstart, "-", revend, "replaying..." + svn_load(localrepo, dumpchannel) + print "current revision", revend + +def svn_load(repo, dumpchannel, maxcount=100): + # every maxcount we will send an ACK to the other + # side in order to synchronise and avoid our side + # growing buffers (py.execnet does not control + # RAM usage or receive queue sizes) + dumpchannel.send(maxcount) + f = os.popen("svnadmin load -q %s" %(repo, ), "w") + count = maxcount + for x in dumpchannel: + sys.stdout.write(".") + sys.stdout.flush() + f.write(x) + count = count - 1 + if count <= 0: + dumpchannel.send(maxcount) + count = maxcount + print >>sys.stdout + f.close() + +def get_svn_youngest(repo): + rev = py.process.cmdexec('svnlook youngest "%s"' % repo) + return int(rev) + +def getgateway(host, keyfile=None): + return py.execnet.SshGateway(host, identity=keyfile) + +if __name__ == '__main__': + if len(sys.argv) < 3: + usage() + raise SystemExit(1) + + main(sys.argv[1:]) + From hpk at codespeak.net Sun Apr 5 22:16:30 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 5 Apr 2009 22:16:30 +0200 (CEST) Subject: [py-svn] r63685 - in py/trunk/py/test: . dist looponfail plugin testing Message-ID: <20090405201630.D57021684D9@codespeak.net> Author: hpk Date: Sun Apr 5 22:16:27 2009 New Revision: 63685 Modified: py/trunk/py/test/dist/dsession.py py/trunk/py/test/dist/txnode.py py/trunk/py/test/looponfail/remote.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_resultlog.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/session.py py/trunk/py/test/testing/test_runner.py Log: internal renaming of variable name Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Sun Apr 5 22:16:27 2009 @@ -24,15 +24,15 @@ self.shuttingdown = False self.testsfailed = False - def pyevent__itemtestreport(self, event): - if event.colitem in self.dsession.item2nodes: - self.dsession.removeitem(event.colitem, event.node) - if event.failed: + def pyevent__itemtestreport(self, rep): + if rep.colitem in self.dsession.item2nodes: + self.dsession.removeitem(rep.colitem, rep.node) + if rep.failed: self.testsfailed = True - def pyevent__collectionreport(self, event): - if event.passed: - self.colitems.extend(event.result) + def pyevent__collectionreport(self, rep): + if rep.passed: + self.colitems.extend(rep.result) def pyevent__testnodeready(self, node): self.dsession.addnode(node) Modified: py/trunk/py/test/dist/txnode.py ============================================================================== --- py/trunk/py/test/dist/txnode.py (original) +++ py/trunk/py/test/dist/txnode.py Sun Apr 5 22:16:27 2009 @@ -104,8 +104,8 @@ def sendevent(self, eventname, *args, **kwargs): self.channel.send((eventname, args, kwargs)) - def pyevent__itemtestreport(self, report): - self.sendevent("itemtestreport", report) + def pyevent__itemtestreport(self, rep): + self.sendevent("itemtestreport", rep) def run(self): channel = self.channel Modified: py/trunk/py/test/looponfail/remote.py ============================================================================== --- py/trunk/py/test/looponfail/remote.py (original) +++ py/trunk/py/test/looponfail/remote.py Sun Apr 5 22:16:27 2009 @@ -137,9 +137,9 @@ session.shouldclose = channel.isclosed class Failures(list): - def pyevent__itemtestreport(self, ev): - if ev.failed: - self.append(ev) + def pyevent__itemtestreport(self, rep): + if rep.failed: + self.append(rep) pyevent__collectionreport = pyevent__itemtestreport failreports = Failures() Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Sun Apr 5 22:16:27 2009 @@ -82,7 +82,7 @@ def pyevent__itemstart(self, item, node=None): """ test item gets collected. """ - def pyevent__itemtestreport(self, event): + def pyevent__itemtestreport(self, rep): """ test has been run. """ def pyevent__item_runtest_finished(self, item, excinfo, outerr): @@ -97,7 +97,7 @@ def pyevent__collectionstart(self, collector): """ collector starts collecting. """ - def pyevent__collectionreport(self, event): + def pyevent__collectionreport(self, rep): """ collector finished collecting. """ def pyevent__testrunstart(self): Modified: py/trunk/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultlog.py (original) +++ py/trunk/py/test/plugin/pytest_resultlog.py Sun Apr 5 22:16:27 2009 @@ -58,25 +58,25 @@ testpath = generic_path(event.colitem) self.write_log_entry(testpath, shortrepr, longrepr) - def pyevent__itemtestreport(self, event): - code = event.shortrepr - if event.passed: + def pyevent__itemtestreport(self, rep): + code = rep.shortrepr + if rep.passed: longrepr = "" - elif event.failed: - longrepr = str(event.longrepr) - elif event.skipped: - longrepr = str(event.longrepr.reprcrash.message) - self.log_outcome(event, code, longrepr) - - def pyevent__collectionreport(self, event): - if not event.passed: - if event.failed: + elif rep.failed: + longrepr = str(rep.longrepr) + elif rep.skipped: + longrepr = str(rep.longrepr.reprcrash.message) + self.log_outcome(rep, code, longrepr) + + def pyevent__collectionreport(self, rep): + if not rep.passed: + if rep.failed: code = "F" else: - assert event.skipped + assert rep.skipped code = "S" - longrepr = str(event.longrepr.reprcrash) - self.log_outcome(event, code, longrepr) + longrepr = str(rep.longrepr.reprcrash) + self.log_outcome(rep, code, longrepr) def pyevent__internalerror(self, excrepr): path = excrepr.reprcrash.path Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Sun Apr 5 22:16:27 2009 @@ -69,15 +69,15 @@ break return cat, self.getoutcomeletter(event), self.getoutcomeword(event) - def getoutcomeletter(self, event): - return event.shortrepr + def getoutcomeletter(self, rep): + return rep.shortrepr - def getoutcomeword(self, event): - if event.passed: + def getoutcomeword(self, rep): + if rep.passed: return "PASS", dict(green=True) - elif event.failed: + elif rep.failed: return "FAIL", dict(red=True) - elif event.skipped: + elif rep.skipped: return "SKIP" else: return "???", dict(red=True) @@ -161,38 +161,38 @@ def pyevent__deselected(self, items): self.stats.setdefault('deselected', []).append(items) - def pyevent__itemtestreport(self, event): - fspath = event.colitem.fspath - cat, letter, word = self.getcategoryletterword(event) + def pyevent__itemtestreport(self, rep): + fspath = rep.colitem.fspath + cat, letter, word = self.getcategoryletterword(rep) if isinstance(word, tuple): word, markup = word else: markup = {} - self.stats.setdefault(cat, []).append(event) + self.stats.setdefault(cat, []).append(rep) if not self.config.option.verbose: self.write_fspath_result(fspath, letter) else: - info = event.colitem.repr_metainfo() + info = rep.colitem.repr_metainfo() line = info.verboseline(basedir=self.curdir) + " " - if not hasattr(event, 'node'): + if not hasattr(rep, 'node'): self.write_ensure_prefix(line, word, **markup) else: self.ensure_newline() - if hasattr(event, 'node'): - self._tw.write("%s " % event.node.gateway.id) + if hasattr(rep, 'node'): + self._tw.write("%s " % rep.node.gateway.id) self._tw.write(word, **markup) self._tw.write(" " + line) self.currentfspath = -2 - def pyevent__collectionreport(self, event): - if not event.passed: - if event.failed: - self.stats.setdefault("failed", []).append(event) - msg = event.longrepr.reprcrash.message - self.write_fspath_result(event.colitem.fspath, "F") - elif event.skipped: - self.stats.setdefault("skipped", []).append(event) - self.write_fspath_result(event.colitem.fspath, "S") + def pyevent__collectionreport(self, rep): + if not rep.passed: + if rep.failed: + self.stats.setdefault("failed", []).append(rep) + msg = rep.longrepr.reprcrash.message + self.write_fspath_result(rep.colitem.fspath, "F") + elif rep.skipped: + self.stats.setdefault("skipped", []).append(rep) + self.write_fspath_result(rep.colitem.fspath, "S") def pyevent__testrunstart(self): self.write_sep("=", "test session starts", bold=True) @@ -316,17 +316,17 @@ def pyevent__itemstart(self, item, node=None): self.outindent(item) - def pyevent__collectionreport(self, event): - if not event.passed: - self.outindent("!!! %s !!!" % event.longrepr.reprcrash.message) - self._failed.append(event) + def pyevent__collectionreport(self, rep): + if not rep.passed: + self.outindent("!!! %s !!!" % rep.longrepr.reprcrash.message) + self._failed.append(rep) self.indent = self.indent[:-len(self.INDENT)] def pyevent__testrunfinish(self, exitstatus, excrepr=None): if self._failed: self.out.sep("!", "collection failures") - for event in self._failed: - event.toterminal(self.out) + for rep in self._failed: + rep.toterminal(self.out) def folded_skips(skipped): d = {} Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Sun Apr 5 22:16:27 2009 @@ -43,11 +43,11 @@ else: assert isinstance(next, Collector) notify("collectionstart", next) - ev = basic_collect_report(next) - if ev.passed: - for x in self.genitems(ev.result, keywordexpr): + rep = basic_collect_report(next) + if rep.passed: + for x in self.genitems(rep.result, keywordexpr): yield x - notify("collectionreport", ev) + notify("collectionreport", rep) if self.shouldstop: break Modified: py/trunk/py/test/testing/test_runner.py ============================================================================== --- py/trunk/py/test/testing/test_runner.py (original) +++ py/trunk/py/test/testing/test_runner.py Sun Apr 5 22:16:27 2009 @@ -77,7 +77,7 @@ evrec = testdir.geteventrecorder(item.config) setup = SetupState() setup.do_fixture_and_runtest(item) - rep = evrec.popevent("itemtestreport").event + rep = evrec.popevent("itemtestreport").rep assert rep.passed def test_runtest_fails(self, testdir): From hpk at codespeak.net Sun Apr 5 22:34:42 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 5 Apr 2009 22:34:42 +0200 (CEST) Subject: [py-svn] r63686 - py/trunk/py/test Message-ID: <20090405203442.037511684F8@codespeak.net> Author: hpk Date: Sun Apr 5 22:34:41 2009 New Revision: 63686 Modified: py/trunk/py/test/collect.py py/trunk/py/test/runner.py Log: fixing docstring, removing dead code. Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Sun Apr 5 22:34:41 2009 @@ -1,22 +1,7 @@ """ -Collect test items at filesystem and python module levels. - -Collectors and test items form a tree. The difference -between a collector and a test item as seen from the session -is smalll. Collectors usually return a list of child -collectors/items whereas items usually return None -indicating a successful test run. - -The is a schematic example of a tree of collectors and test items:: - - Directory - Directory - CustomCollector # provided via conftest's - CustomItem # provided via conftest's - CustomItem # provided via conftest's - Directory - ... - +base test collection objects. +Collectors and test Items form a tree +that is usually built iteratively. """ import py from py.__.misc.warn import APIWARN Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Sun Apr 5 22:34:41 2009 @@ -156,13 +156,6 @@ else: self.failed = True - def toterminal(self, out): - longrepr = self.longrepr - if hasattr(longrepr, 'toterminal'): - longrepr.toterminal(out) - else: - out.line(str(longrepr)) - class ItemSetupReport(BaseReport): """ Test Execution Report. """ failed = passed = skipped = False From mzeidler at codespeak.net Mon Apr 6 20:22:10 2009 From: mzeidler at codespeak.net (mzeidler at codespeak.net) Date: Mon, 6 Apr 2009 20:22:10 +0200 (CEST) Subject: [py-svn] r63740 - py/trunk/contrib/pytest_coverage Message-ID: <20090406182210.C7DD1168531@codespeak.net> Author: mzeidler Date: Mon Apr 6 20:22:08 2009 New Revision: 63740 Added: py/trunk/contrib/pytest_coverage/ Log: Created new plugin-dir for 'pytest_coverage'. From hpk at codespeak.net Tue Apr 7 11:53:02 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 7 Apr 2009 11:53:02 +0200 (CEST) Subject: [py-svn] r63780 - in py/trunk/py/test: dist/testing plugin testing Message-ID: <20090407095302.64FD9168514@codespeak.net> Author: hpk Date: Tue Apr 7 11:53:01 2009 New Revision: 63780 Modified: py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/dist/testing/test_txnode.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/testing/test_genitems.py py/trunk/py/test/testing/test_runner.py py/trunk/py/test/testing/test_session.py Log: some renaming Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Tue Apr 7 11:53:01 2009 @@ -296,14 +296,14 @@ remaining = session.filteritems(items) assert remaining == [] - event = evrec.getevents("deselected")[-1] + event = evrec.getcalls("deselected")[-1] assert event.items == items modcol.config.option.keyword = "test_fail" remaining = session.filteritems(items) assert remaining == [items[0]] - event = evrec.getevents("deselected")[-1] + event = evrec.getcalls("deselected")[-1] assert event.items == [items[1]] def test_testnodedown_shutdown_after_completion(self, testdir): Modified: py/trunk/py/test/dist/testing/test_txnode.py ============================================================================== --- py/trunk/py/test/dist/testing/test_txnode.py (original) +++ py/trunk/py/test/dist/testing/test_txnode.py Tue Apr 7 11:53:01 2009 @@ -111,7 +111,7 @@ node = mysetup.makenode(item.config) node.channel.close() py.test.raises(IOError, "node.send(item)") - #ev = self.getevents("internalerror") + #ev = self.getcalls("internalerror") #assert ev.excinfo.errisinstance(IOError) def test_send_one(self, testdir, mysetup): Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Tue Apr 7 11:53:01 2009 @@ -267,12 +267,12 @@ def __repr__(self): return "" %(self.name, self.args) -class ParsedEvent: +class ParsedCall: def __init__(self, locals): self.__dict__ = locals.copy() def __repr__(self): - return "" %(self.__dict__,) + return "" %(self.__dict__,) class EventRecorder(object): def __init__(self, pyplugins, debug=False): # True): @@ -288,17 +288,17 @@ print "[event: %s]: %s **%s" %(name, ", ".join(map(str, args)), kwargs,) self.events.append(Event(name, args, kwargs)) - def popevent(self, name): + def popcall(self, name): for i, event in py.builtin.enumerate(self.events): if event.name == name: del self.events[i] - eventparser = self.geteventparser(name) + eventparser = self._getcallparser(name) return eventparser(*event.args, **event.kwargs) raise KeyError("popevent: %r not found in %r" %(name, self.events)) - def getevents(self, eventname): - """ return list of ParsedEvent instances matching the given eventname. """ - method = self.geteventparser(eventname) + def getcalls(self, eventname): + """ return list of ParsedCall instances matching the given eventname. """ + method = self._getcallparser(eventname) l = [] for event in self.events: if event.name == eventname: @@ -306,7 +306,7 @@ l.append(pevent) return l - def geteventparser(self, eventname): + def _getcallparser(self, eventname): mname = "pyevent__" + eventname method = getattr(api.Events, mname) args, varargs, varkw, default = inspect.getargspec(method) @@ -314,7 +314,7 @@ args = args[1:] fspec = inspect.formatargspec(args, varargs, varkw, default) code = py.code.compile("""def %(mname)s%(fspec)s: - return ParsedEvent(locals())""" % locals()) + return ParsedCall(locals())""" % locals()) exec code return locals()[mname] Modified: py/trunk/py/test/testing/test_genitems.py ============================================================================== --- py/trunk/py/test/testing/test_genitems.py (original) +++ py/trunk/py/test/testing/test_genitems.py Tue Apr 7 11:53:01 2009 @@ -74,7 +74,7 @@ passed, skipped, failed = sorter.listoutcomes() assert len(failed) == 1 assert failed[0].colitem.name == name - assert len(sorter.getevents('deselected')) == 1 + assert len(sorter.getcalls('deselected')) == 1 for keyword in ['test_one', 'est_on']: #yield check, keyword, 'test_one' @@ -102,7 +102,7 @@ passed, skipped, failed = sorter.listoutcomes() assert len(passed) == 1 assert passed[0].colitem.name == "test_2" - dlist = sorter.getevents("deselected") + dlist = sorter.getcalls("deselected") assert len(dlist) == 1 assert dlist[0].items[0].name == 'test_1' @@ -116,7 +116,7 @@ passed, skipped, failed = sorter.listoutcomes() assert len(passed) == 2 assert not failed - dlist = sorter.getevents("deselected") + dlist = sorter.getcalls("deselected") assert len(dlist) == 1 item = dlist[0].items[0] assert item.name == "test_one" Modified: py/trunk/py/test/testing/test_runner.py ============================================================================== --- py/trunk/py/test/testing/test_runner.py (original) +++ py/trunk/py/test/testing/test_runner.py Tue Apr 7 11:53:01 2009 @@ -26,7 +26,7 @@ setup = SetupState() res = setup.do_setup(item) assert not res - rep = evrec.popevent("itemsetupreport").rep + rep = evrec.popcall("itemsetupreport").rep assert rep.failed assert not rep.skipped assert rep.excrepr @@ -45,10 +45,10 @@ setup = SetupState() res = setup.do_setup(item) assert res - rep = evrec.popevent("itemsetupreport").rep + rep = evrec.popcall("itemsetupreport").rep assert rep.passed setup.do_teardown(item) - rep = evrec.popevent("itemsetupreport").rep + rep = evrec.popcall("itemsetupreport").rep assert rep.item == item assert rep.failed assert not rep.passed @@ -66,7 +66,7 @@ evrec = testdir.geteventrecorder(item.config) setup = SetupState() setup.do_setup(item) - rep = evrec.popevent("itemsetupreport").rep + rep = evrec.popcall("itemsetupreport").rep assert not rep.failed assert rep.skipped assert rep.excrepr @@ -77,7 +77,7 @@ evrec = testdir.geteventrecorder(item.config) setup = SetupState() setup.do_fixture_and_runtest(item) - rep = evrec.popevent("itemtestreport").rep + rep = evrec.popcall("itemtestreport").rep assert rep.passed def test_runtest_fails(self, testdir): @@ -85,7 +85,7 @@ evrec = testdir.geteventrecorder(item.config) setup = SetupState() setup.do_fixture_and_runtest(item) - event = evrec.popevent("item_runtest_finished") + event = evrec.popcall("item_runtest_finished") assert event.excinfo Modified: py/trunk/py/test/testing/test_session.py ============================================================================== --- py/trunk/py/test/testing/test_session.py (original) +++ py/trunk/py/test/testing/test_session.py Tue Apr 7 11:53:01 2009 @@ -25,9 +25,9 @@ assert failed[0].colitem.name == "test_one_one" assert failed[1].colitem.name == "test_other" assert failed[2].colitem.name == "test_two" - itemstarted = sorter.getevents("itemstart") + itemstarted = sorter.getcalls("itemstart") assert len(itemstarted) == 4 - colstarted = sorter.getevents("collectionstart") + colstarted = sorter.getcalls("collectionstart") assert len(colstarted) == 1 col = colstarted[0].collector assert isinstance(col, py.test.collect.Module) @@ -202,7 +202,7 @@ itemstarted = sorter.getnamed("itemstart") assert len(itemstarted) == 3 assert not sorter.getnamed("itemtestreport") - started = sorter.getevents("collectionstart") + started = sorter.getcalls("collectionstart") finished = sorter.getnamed("collectionreport") assert len(started) == len(finished) assert len(started) == 8 From hpk at codespeak.net Tue Apr 7 12:48:58 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 7 Apr 2009 12:48:58 +0200 (CEST) Subject: [py-svn] r63785 - in py/trunk/py/test: . dist/testing plugin testing Message-ID: <20090407104858.713DE168517@codespeak.net> Author: hpk Date: Tue Apr 7 12:48:57 2009 New Revision: 63785 Modified: py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/dist/testing/test_nodemanage.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_doctest.py py/trunk/py/test/plugin/pytest_iocapture.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_unittest.py py/trunk/py/test/runner.py py/trunk/py/test/testing/test_collect.py py/trunk/py/test/testing/test_pytestplugin.py py/trunk/py/test/testing/test_session.py py/trunk/py/test/testing/test_setup_functional.py Log: further renaming, streamlining the testing machinery and helpers. Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Tue Apr 7 12:48:57 2009 @@ -181,7 +181,7 @@ session.loop_once(loopstate) assert loopstate.colitems == [item2] # do not reschedule crash item - testrep = evrec.getfirstnamed("itemtestreport") + testrep = evrec.matchreport(names="itemtestreport") assert testrep.failed assert testrep.colitem == item1 assert str(testrep.longrepr).find("crashed") != -1 @@ -204,9 +204,9 @@ session = DSession(item.config) evrec = EventRecorder(session.bus) - session.queueevent("NOPevent", 42) + session.queueevent("NOP", 42) session.loop_once(session._initloopstate([])) - assert evrec.getfirstnamed('NOPevent') + assert evrec.getcall('NOP') def runthrough(self, item): session = DSession(item.config) @@ -274,10 +274,10 @@ evrec = EventRecorder(session.bus) session.queueevent("itemtestreport", run(item, node)) session.loop_once(loopstate) - assert not evrec.getfirstnamed("testnodedown") + assert not evrec.getcalls("testnodedown") session.queueevent("testnodedown", node, None) session.loop_once(loopstate) - assert evrec.getfirstnamed('testnodedown') == node + assert evrec.getcall('testnodedown').node == node def test_filteritems(self, testdir, EventRecorder): modcol = testdir.getmodulecol(""" Modified: py/trunk/py/test/dist/testing/test_nodemanage.py ============================================================================== --- py/trunk/py/test/dist/testing/test_nodemanage.py (original) +++ py/trunk/py/test/dist/testing/test_nodemanage.py Tue Apr 7 12:48:57 2009 @@ -104,11 +104,11 @@ config = py.test.config._reparse([source, '--debug']) assert config.option.debug nodemanager = NodeManager(config, specs) - evrec = EventRecorder(config.bus, debug=True) + sorter = EventRecorder(config.bus, debug=True) nodemanager.setup_nodes(putevent=[].append) for spec in nodemanager.gwmanager.specs: - l = evrec.getnamed("trace") - print evrec.events + l = sorter.getcalls("trace") + print sorter.events assert l nodemanager.teardown_nodes() Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Tue Apr 7 12:48:57 2009 @@ -61,6 +61,9 @@ def pyevent(self, eventname, args, kwargs): """ generically called for each notification event. """ + def pyevent__NOP(self, *args, **kwargs): + """ the no-operation call. """ + def pyevent__gateway_init(self, gateway): """ called after a gateway has been initialized. """ Modified: py/trunk/py/test/plugin/pytest_doctest.py ============================================================================== --- py/trunk/py/test/plugin/pytest_doctest.py (original) +++ py/trunk/py/test/plugin/pytest_doctest.py Tue Apr 7 12:48:57 2009 @@ -108,9 +108,8 @@ >>> x == 1 False """) - events = testdir.inline_run(p) - ev, = events.getnamed("itemtestreport") - assert ev.failed + sorter = testdir.inline_run(p) + sorter.assertoutcome(failed=1) def test_doctest_unexpected_exception(self, testdir): from py.__.test.outcome import Failed @@ -123,11 +122,9 @@ 2 """) sorter = testdir.inline_run(p) - events = sorter.getnamed("itemtestreport") - assert len(events) == 1 - ev, = events - assert ev.failed - assert ev.longrepr + call = sorter.getcall("itemtestreport") + assert call.rep.failed + assert call.rep.longrepr # XXX #testitem, = items #excinfo = py.test.raises(Failed, "testitem.runtest()") @@ -144,9 +141,8 @@ ''' """) - events = testdir.inline_run(p, "--doctest-modules") - ev, = events.getnamed("itemtestreport") - assert ev.failed + sorter = testdir.inline_run(p, "--doctest-modules") + sorter.assertoutcome(failed=1) def test_txtfile_failing(self, testdir): testdir.plugins.append('pytest_doctest') Modified: py/trunk/py/test/plugin/pytest_iocapture.py ============================================================================== --- py/trunk/py/test/plugin/pytest_iocapture.py (original) +++ py/trunk/py/test/plugin/pytest_iocapture.py Tue Apr 7 12:48:57 2009 @@ -37,8 +37,7 @@ out, err = stdcapture.reset() assert out.startswith("42") """) - ev, = evrec.getnamed("itemtestreport") - assert ev.passed + evrec.assertoutcome(passed=1) def test_stdfd_functional(self, testdir): testdir.plugins.append(IocapturePlugin()) @@ -49,6 +48,4 @@ out, err = stdcapturefd.reset() assert out.startswith("42") """) - ev, = evrec.getnamed("itemtestreport") - assert ev.passed - + evrec.assertoutcome(passed=1) Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Tue Apr 7 12:48:57 2009 @@ -296,12 +296,19 @@ return eventparser(*event.args, **event.kwargs) raise KeyError("popevent: %r not found in %r" %(name, self.events)) - def getcalls(self, eventname): + def getcall(self, name): + l = self.getcalls(name) + assert len(l) == 1, (name, l) + return l[0] + + def getcalls(self, *names): """ return list of ParsedCall instances matching the given eventname. """ - method = self._getcallparser(eventname) + if len(names) == 1 and isinstance(names, str): + names = names.split() l = [] for event in self.events: - if event.name == eventname: + if event.name in names: + method = self._getcallparser(event.name) pevent = method(*event.args, **event.kwargs) l.append(pevent) return l @@ -318,35 +325,34 @@ exec code return locals()[mname] - def get(self, cls): - l = [] - for event in self.events: - try: - value = event.args[0] - except IndexError: - continue - else: - if isinstance(value, cls): - l.append(value) - return l + # functionality for test reports - def getnamed(self, *names): + def getreports(self, names="itemtestreport collectionreport"): + names = names.split() l = [] - for event in self.events: - if event.name in names: - l.append(event.args[0]) + for call in self.getcalls(*names): + l.append(call.rep) return l - def getfirstnamed(self, name): - for event in self.events: - if event.name == name: - return event.args[0] + def matchreport(self, inamepart="", names="itemtestreport collectionreport"): + """ return a testreport whose dotted import path matches """ + l = [] + for rep in self.getreports(names=names): + if not inamepart or inamepart in rep.colitem.listnames(): + l.append(rep) + if not l: + raise ValueError("could not find test report matching %r: no test reports at all!" % + (inamepart,)) + if len(l) > 1: + raise ValueError("found more than one testreport matching %r: %s" %( + inamepart, l)) + return l[0] def getfailures(self, names='itemtestreport collectionreport'): l = [] - for ev in self.getnamed(*names.split()): - if ev.failed: - l.append(ev) + for call in self.getcalls(*names.split()): + if call.rep.failed: + l.append(call.rep) return l def getfailedcollections(self): @@ -356,13 +362,13 @@ passed = [] skipped = [] failed = [] - for ev in self.getnamed('itemtestreport'): # , 'collectionreport'): - if ev.passed: - passed.append(ev) - elif ev.skipped: - skipped.append(ev) - elif ev.failed: - failed.append(ev) + for rep in self.getreports("itemtestreport"): + if rep.passed: + passed.append(rep) + elif rep.skipped: + skipped.append(rep) + elif rep.failed: + failed.append(rep) return passed, skipped, failed def countoutcomes(self): @@ -374,21 +380,6 @@ assert skipped == len(realskipped) assert failed == len(realfailed) - def getreport(self, inamepart): - """ return a testreport whose dotted import path matches """ - __tracebackhide__ = True - l = [] - for rep in self.get(runner.ItemTestReport): - if inamepart in rep.colitem.listnames(): - l.append(rep) - if not l: - raise ValueError("could not find test report matching %r: no test reports at all!" % - (inamepart,)) - if len(l) > 1: - raise ValueError("found more than one testreport matching %r: %s" %( - inamepart, l)) - return l[0] - def clear(self): self.events[:] = [] @@ -407,9 +398,7 @@ bus.notify("itemtestreport", rep) failures = recorder.getfailures() assert failures == [rep] - failures = recorder.get(runner.ItemTestReport) - assert failures == [rep] - failures = recorder.getnamed("itemtestreport") + failures = recorder.getfailures() assert failures == [rep] rep = runner.ItemTestReport(None, None) Modified: py/trunk/py/test/plugin/pytest_unittest.py ============================================================================== --- py/trunk/py/test/plugin/pytest_unittest.py (original) +++ py/trunk/py/test/plugin/pytest_unittest.py Tue Apr 7 12:48:57 2009 @@ -84,8 +84,8 @@ self.assertEquals('foo', 'bar') """) sorter = testdir.inline_run(testpath) - assert sorter.getreport("testpassing").passed - assert sorter.getreport("test_failing").failed + assert sorter.matchreport("testpassing").passed + assert sorter.matchreport("test_failing").failed def test_setup(testdir): testpath = testdir.makepyfile(test_two=""" @@ -98,7 +98,7 @@ self.assertEquals(1, self.foo) """) sorter = testdir.inline_run(testpath) - rep = sorter.getreport("test_setUp") + rep = sorter.matchreport("test_setUp") assert rep.passed def test_teardown(testdir): Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Tue Apr 7 12:48:57 2009 @@ -98,6 +98,7 @@ else: out.line(str(longrepr)) +# XXX rename to runtest() report ? class ItemTestReport(BaseReport): """ Test Execution Report. """ failed = passed = skipped = False @@ -138,11 +139,13 @@ self.longrepr = longrepr +# XXX rename to collectreport class CollectionReport(BaseReport): """ Collection Report. """ skipped = failed = passed = False def __init__(self, colitem, result, excinfo=None, outerr=None): + # XXX rename to collector self.colitem = colitem if not excinfo: self.passed = True Modified: py/trunk/py/test/testing/test_collect.py ============================================================================== --- py/trunk/py/test/testing/test_collect.py (original) +++ py/trunk/py/test/testing/test_collect.py Tue Apr 7 12:48:57 2009 @@ -163,11 +163,11 @@ testdir.plugins.append(Plugin()) testdir.mkdir("hello") testdir.mkdir("world") - evrec = testdir.inline_run() + sorter = testdir.inline_run() assert "hello" in wascalled assert "world" in wascalled # make sure the directories do not get double-appended - colreports = evrec.getnamed("collectionreport") + colreports = sorter.getreports(names="collectionreport") names = [rep.colitem.name for rep in colreports] assert names.count("hello") == 1 @@ -206,11 +206,11 @@ return parent.config.getvalue("XX") """) testdir.mkdir("hello") - evrec = testdir.inline_run(testdir.tmpdir) - names = [rep.colitem.name for rep in evrec.getnamed("collectionreport")] + sorter = testdir.inline_run(testdir.tmpdir) + names = [rep.colitem.name for rep in sorter.getreports("collectionreport")] assert 'hello' not in names evrec = testdir.inline_run(testdir.tmpdir, "--XX") - names = [rep.colitem.name for rep in evrec.getnamed("collectionreport")] + names = [rep.colitem.name for rep in evrec.getreports("collectionreport")] assert 'hello' in names class TestCollectorReprs: Modified: py/trunk/py/test/testing/test_pytestplugin.py ============================================================================== --- py/trunk/py/test/testing/test_pytestplugin.py (original) +++ py/trunk/py/test/testing/test_pytestplugin.py Tue Apr 7 12:48:57 2009 @@ -74,18 +74,17 @@ mod.pytest_plugins = "pytest_a" aplugin = testdir.makepyfile(pytest_a="""class APlugin: pass""") plugins = PytestPlugins() - evrec = EventRecorder(plugins) + sorter = EventRecorder(plugins) #syspath.prepend(aplugin.dirpath()) py.std.sys.path.insert(0, str(aplugin.dirpath())) plugins.consider_module(mod) - evlist = evrec.getnamed("plugin_registered") - assert len(evlist) == 1 - assert evlist[0].__class__.__name__ == "APlugin" + call = sorter.getcall("plugin_registered") + assert call.plugin.__class__.__name__ == "APlugin" # check that it is not registered twice plugins.consider_module(mod) - evlist = evrec.getnamed("plugin_registered") - assert len(evlist) == 1 + l = sorter.getcalls("plugin_registered") + assert len(l) == 1 def test_consider_conftest(self, testdir): pp = PytestPlugins() Modified: py/trunk/py/test/testing/test_session.py ============================================================================== --- py/trunk/py/test/testing/test_session.py (original) +++ py/trunk/py/test/testing/test_session.py Tue Apr 7 12:48:57 2009 @@ -130,9 +130,9 @@ def test_one(): pass """) sorter = testdir.inline_run(testdir.tmpdir) - skips = sorter.getnamed("collectionreport") - assert len(skips) == 1 - assert skips[0].skipped + reports = sorter.getreports("collectionreport") + assert len(reports) == 1 + assert reports[0].skipped class TestNewSession(SessionTests): def test_pdb_run(self, testdir, monkeypatch): @@ -146,7 +146,7 @@ l.append(args) monkeypatch.setattr(py.__.test.custompdb, 'post_mortem', mypdb) sorter = testdir.inline_run('--pdb', tfile) - rep = sorter.getreport("test_usepdb") + rep = sorter.matchreport("test_usepdb") assert rep.failed assert len(l) == 1 tb = py.code.Traceback(l[0][0]) @@ -199,11 +199,11 @@ ) sorter = testdir.inline_run('--collectonly', p.dirpath()) - itemstarted = sorter.getnamed("itemstart") + itemstarted = sorter.getcalls("itemstart") assert len(itemstarted) == 3 - assert not sorter.getnamed("itemtestreport") + assert not sorter.getreports("itemtestreport") started = sorter.getcalls("collectionstart") - finished = sorter.getnamed("collectionreport") + finished = sorter.getreports("collectionreport") assert len(started) == len(finished) assert len(started) == 8 colfail = [x for x in finished if x.failed] @@ -215,7 +215,7 @@ testdir.makepyfile(__init__="") testdir.makepyfile(test_one="xxxx", test_two="yyyy") sorter = testdir.inline_run("-x", testdir.tmpdir) - finished = sorter.getnamed("collectionreport") + finished = sorter.getreports("collectionreport") colfail = [x for x in finished if x.failed] assert len(colfail) == 1 Modified: py/trunk/py/test/testing/test_setup_functional.py ============================================================================== --- py/trunk/py/test/testing/test_setup_functional.py (original) +++ py/trunk/py/test/testing/test_setup_functional.py Tue Apr 7 12:48:57 2009 @@ -27,9 +27,9 @@ assert modlevel[0] == 42 assert not hasattr(test_modlevel, 'answer') """) - rep = sorter.getreport("test_modlevel") + rep = sorter.matchreport("test_modlevel") assert rep.passed - rep = sorter.getreport("test_module") + rep = sorter.matchreport("test_module") assert rep.passed def test_class_setup(testdir): @@ -118,7 +118,7 @@ yield check assert x == [1] """) - rep = sorter.getreport("test_one") + rep = sorter.matchreport("test_one", names="itemtestreport") assert rep.passed def test_method_setup_uses_fresh_instances(testdir): From hpk at codespeak.net Tue Apr 7 13:51:56 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 7 Apr 2009 13:51:56 +0200 (CEST) Subject: [py-svn] r63786 - in py/trunk/py/test: . dist dist/testing looponfail plugin testing Message-ID: <20090407115156.D042316853F@codespeak.net> Author: hpk Date: Tue Apr 7 13:51:55 2009 New Revision: 63786 Modified: py/trunk/py/test/dist/dsession.py py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/looponfail/remote.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_resultlog.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/runner.py py/trunk/py/test/session.py py/trunk/py/test/testing/test_collect.py py/trunk/py/test/testing/test_session.py Log: rename CollectionReport to CollectReport, remove XXX. Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Tue Apr 7 13:51:55 2009 @@ -30,7 +30,7 @@ if rep.failed: self.testsfailed = True - def pyevent__collectionreport(self, rep): + def pyevent__collectreport(self, rep): if rep.passed: self.colitems.extend(rep.result) @@ -175,7 +175,7 @@ senditems.append(next) else: self.bus.notify("collectionstart", next) - self.queueevent("collectionreport", basic_collect_report(next)) + self.queueevent("collectreport", basic_collect_report(next)) if self.config.option.dist == "each": self.senditems_each(senditems) else: Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Tue Apr 7 13:51:55 2009 @@ -80,7 +80,7 @@ session = DSession(modcol.config) session.triggertesting([modcol]) name, args, kwargs = session.queue.get(block=False) - assert name == 'collectionreport' + assert name == 'collectreport' rep, = args assert len(rep.result) == 1 @@ -343,7 +343,7 @@ session.queueevent("itemtestreport", run(item1, node)) # but we have a collection pending - session.queueevent("collectionreport", colreport) + session.queueevent("collectreport", colreport) loopstate = session._initloopstate([]) session.loop_once(loopstate) Modified: py/trunk/py/test/looponfail/remote.py ============================================================================== --- py/trunk/py/test/looponfail/remote.py (original) +++ py/trunk/py/test/looponfail/remote.py Tue Apr 7 13:51:55 2009 @@ -140,7 +140,7 @@ def pyevent__itemtestreport(self, rep): if rep.failed: self.append(rep) - pyevent__collectionreport = pyevent__itemtestreport + pyevent__collectreport = pyevent__itemtestreport failreports = Failures() session.bus.register(failreports) Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Tue Apr 7 13:51:55 2009 @@ -100,7 +100,7 @@ def pyevent__collectionstart(self, collector): """ collector starts collecting. """ - def pyevent__collectionreport(self, rep): + def pyevent__collectreport(self, rep): """ collector finished collecting. """ def pyevent__testrunstart(self): Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Tue Apr 7 13:51:55 2009 @@ -327,14 +327,14 @@ # functionality for test reports - def getreports(self, names="itemtestreport collectionreport"): + def getreports(self, names="itemtestreport collectreport"): names = names.split() l = [] for call in self.getcalls(*names): l.append(call.rep) return l - def matchreport(self, inamepart="", names="itemtestreport collectionreport"): + def matchreport(self, inamepart="", names="itemtestreport collectreport"): """ return a testreport whose dotted import path matches """ l = [] for rep in self.getreports(names=names): @@ -348,7 +348,7 @@ inamepart, l)) return l[0] - def getfailures(self, names='itemtestreport collectionreport'): + def getfailures(self, names='itemtestreport collectreport'): l = [] for call in self.getcalls(*names.split()): if call.rep.failed: @@ -356,7 +356,7 @@ return l def getfailedcollections(self): - return self.getfailures('collectionreport') + return self.getfailures('collectreport') def listoutcomes(self): passed = [] @@ -406,7 +406,7 @@ rep.skipped = True bus.notify("itemtestreport", rep) - rep = runner.CollectionReport(None, None) + rep = runner.CollectReport(None, None) rep.passed = False rep.failed = True bus.notify("itemtestreport", rep) Modified: py/trunk/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultlog.py (original) +++ py/trunk/py/test/plugin/pytest_resultlog.py Tue Apr 7 13:51:55 2009 @@ -68,7 +68,7 @@ longrepr = str(rep.longrepr.reprcrash.message) self.log_outcome(rep, code, longrepr) - def pyevent__collectionreport(self, rep): + def pyevent__collectreport(self, rep): if not rep.passed: if rep.failed: code = "F" Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Tue Apr 7 13:51:55 2009 @@ -184,7 +184,7 @@ self._tw.write(" " + line) self.currentfspath = -2 - def pyevent__collectionreport(self, rep): + def pyevent__collectreport(self, rep): if not rep.passed: if rep.failed: self.stats.setdefault("failed", []).append(rep) @@ -316,7 +316,7 @@ def pyevent__itemstart(self, item, node=None): self.outindent(item) - def pyevent__collectionreport(self, rep): + def pyevent__collectreport(self, rep): if not rep.passed: self.outindent("!!! %s !!!" % rep.longrepr.reprcrash.message) self._failed.append(rep) @@ -603,7 +603,7 @@ lineno = 3 message = "justso" - ev1 = runner.CollectionReport(None, None) + ev1 = runner.CollectReport(None, None) ev1.when = "execute" ev1.skipped = True ev1.longrepr = longrepr @@ -637,8 +637,8 @@ linecomp.assert_contains_lines([ " ", ]) - rep.config.bus.notify( "collectionreport", - runner.CollectionReport(modcol, [], excinfo=None)) + rep.config.bus.notify( "collectreport", + runner.CollectReport(modcol, [], excinfo=None)) assert rep.indent == indent def test_collectonly_skipped_module(self, testdir, linecomp): Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Tue Apr 7 13:51:55 2009 @@ -52,7 +52,7 @@ raise except: excinfo = py.code.ExceptionInfo() - return CollectionReport(collector, res, excinfo, outerr) + return CollectReport(collector, res, excinfo, outerr) from cPickle import Pickler, Unpickler @@ -98,9 +98,7 @@ else: out.line(str(longrepr)) -# XXX rename to runtest() report ? class ItemTestReport(BaseReport): - """ Test Execution Report. """ failed = passed = skipped = False def __init__(self, colitem, excinfo=None, when=None, outerr=None): @@ -139,9 +137,7 @@ self.longrepr = longrepr -# XXX rename to collectreport -class CollectionReport(BaseReport): - """ Collection Report. """ +class CollectReport(BaseReport): skipped = failed = passed = False def __init__(self, colitem, result, excinfo=None, outerr=None): @@ -160,7 +156,6 @@ self.failed = True class ItemSetupReport(BaseReport): - """ Test Execution Report. """ failed = passed = skipped = False def __init__(self, item, excinfo=None, outerr=None): self.item = item Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Tue Apr 7 13:51:55 2009 @@ -47,7 +47,7 @@ if rep.passed: for x in self.genitems(rep.result, keywordexpr): yield x - notify("collectionreport", rep) + notify("collectreport", rep) if self.shouldstop: break @@ -86,7 +86,7 @@ self._testsfailed = True if self.config.option.exitfirst: self.shouldstop = True - pyevent__collectionreport = pyevent__itemtestreport + pyevent__collectreport = pyevent__itemtestreport def sessionfinishes(self, exitstatus=0, excinfo=None): """ teardown any resources after a test run. """ Modified: py/trunk/py/test/testing/test_collect.py ============================================================================== --- py/trunk/py/test/testing/test_collect.py (original) +++ py/trunk/py/test/testing/test_collect.py Tue Apr 7 13:51:55 2009 @@ -167,7 +167,7 @@ assert "hello" in wascalled assert "world" in wascalled # make sure the directories do not get double-appended - colreports = sorter.getreports(names="collectionreport") + colreports = sorter.getreports(names="collectreport") names = [rep.colitem.name for rep in colreports] assert names.count("hello") == 1 @@ -207,10 +207,10 @@ """) testdir.mkdir("hello") sorter = testdir.inline_run(testdir.tmpdir) - names = [rep.colitem.name for rep in sorter.getreports("collectionreport")] + names = [rep.colitem.name for rep in sorter.getreports("collectreport")] assert 'hello' not in names evrec = testdir.inline_run(testdir.tmpdir, "--XX") - names = [rep.colitem.name for rep in evrec.getreports("collectionreport")] + names = [rep.colitem.name for rep in evrec.getreports("collectreport")] assert 'hello' in names class TestCollectorReprs: Modified: py/trunk/py/test/testing/test_session.py ============================================================================== --- py/trunk/py/test/testing/test_session.py (original) +++ py/trunk/py/test/testing/test_session.py Tue Apr 7 13:51:55 2009 @@ -130,7 +130,7 @@ def test_one(): pass """) sorter = testdir.inline_run(testdir.tmpdir) - reports = sorter.getreports("collectionreport") + reports = sorter.getreports("collectreport") assert len(reports) == 1 assert reports[0].skipped @@ -203,7 +203,7 @@ assert len(itemstarted) == 3 assert not sorter.getreports("itemtestreport") started = sorter.getcalls("collectionstart") - finished = sorter.getreports("collectionreport") + finished = sorter.getreports("collectreport") assert len(started) == len(finished) assert len(started) == 8 colfail = [x for x in finished if x.failed] @@ -215,7 +215,7 @@ testdir.makepyfile(__init__="") testdir.makepyfile(test_one="xxxx", test_two="yyyy") sorter = testdir.inline_run("-x", testdir.tmpdir) - finished = sorter.getreports("collectionreport") + finished = sorter.getreports("collectreport") colfail = [x for x in finished if x.failed] assert len(colfail) == 1 From mzeidler at codespeak.net Tue Apr 7 15:58:13 2009 From: mzeidler at codespeak.net (mzeidler at codespeak.net) Date: Tue, 7 Apr 2009 15:58:13 +0200 (CEST) Subject: [py-svn] r63795 - py/trunk/contrib/pytest_coverage Message-ID: <20090407135813.1E5C7168541@codespeak.net> Author: mzeidler Date: Tue Apr 7 15:58:12 2009 New Revision: 63795 Added: py/trunk/contrib/pytest_coverage/__init__.py py/trunk/contrib/pytest_coverage/header_bg.jpg (contents, props changed) py/trunk/contrib/pytest_coverage/links.gif (contents, props changed) Log: Initial import. Added: py/trunk/contrib/pytest_coverage/__init__.py ============================================================================== --- (empty file) +++ py/trunk/contrib/pytest_coverage/__init__.py Tue Apr 7 15:58:12 2009 @@ -0,0 +1,346 @@ +""" +Tested with coverage 2.85 and pygments 1.0 + +TODO: + + 'html-output/*,cover' should be deleted + + credits for coverage + + credits for pygments + + 'Install pygments' after ImportError is to less + + is the way of determining DIR_CSS_RESOURCE ok? + + write plugin test + + '.coverage' still exists in py.test execution dir +""" + +import os +import sys +import re +import shutil +from StringIO import StringIO + +import py + +try: + from pygments import highlight + from pygments.lexers import get_lexer_by_name + from pygments.formatters import HtmlFormatter +except ImportError: + print "Install pygments" # XXX + sys.exit(0) + + +DIR_CUR = str(py.path.local()) +REPORT_FILE = os.path.join(DIR_CUR, '.coverage') +DIR_ANNOTATE_OUTPUT = os.path.join(DIR_CUR, '.coverage_annotate') +COVERAGE_MODULES = set() +# coverage output parsing +REG_COVERAGE_SUMMARY = re.compile('([a-z_\.]+) +([0-9]+) +([0-9]+) +([0-9]+%)') +REG_COVERAGE_SUMMARY_TOTAL = re.compile('(TOTAL) +([0-9]+) +([0-9]+) +([0-9]+%)') +DEFAULT_COVERAGE_OUTPUT = '.coverage_annotation' +# HTML output specific +DIR_CSS_RESOURCE = os.path.dirname(__import__('pytest_coverage').__file__) +CSS_RESOURCE_FILES = ['header_bg.jpg', 'links.gif'] + +COVERAGE_TERM_HEADER = "\nCOVERAGE INFORMATION\n" \ + "====================\n" +HTML_INDEX_HEADER = ''' + + + py.test - Coverage Index + + + + + + Module Coverage + + + + Module + Statements + Executed + Coverage + + ''' +HTML_INDEX_FOOTER = ''' + + + ''' + + +class CoverageHtmlFormatter(HtmlFormatter): + """XXX: doc""" + + def __init__(self, *args, **kwargs): + HtmlFormatter.__init__(self,*args, **kwargs) + self.annotation_infos = kwargs.get('annotation_infos') + + def _highlight_lines(self, tokensource): + """ + XXX: doc + """ + + hls = self.hl_lines + self.annotation_infos = [None] + self.annotation_infos + hls = [l for l, i in enumerate(self.annotation_infos) if i] + for i, (t, value) in enumerate(tokensource): + if t != 1: + yield t, value + if i + 1 in hls: # i + 1 because Python indexes start at 0 + if self.annotation_infos[i+1] == "!": + yield 1, '%s' \ + % value + elif self.annotation_infos[i+1] == ">": + yield 1, '%s' \ + % value + else: + raise ValueError("HHAHA: %s" % self.annotation_infos[i+1]) + else: + yield 1, value + + +def _rename_annotation_files(module_list, dir_annotate_output): + for m in module_list: + mod_fpath = os.path.basename(m.__file__) + if mod_fpath.endswith('pyc'): + mod_fpath = mod_fpath[:-1] + old = os.path.join(dir_annotate_output, '%s,cover'% mod_fpath) + new = os.path.join(dir_annotate_output, '%s,cover'% m.__name__) + if os.path.isfile(old): + shutil.move(old, new) + yield new + +def _generate_module_coverage(mc_path, anotation_infos, src_lines): + #XXX: doc + + code = "".join(src_lines) + mc_path = "%s.html" % mc_path + lexer = get_lexer_by_name("python", stripall=True) + formatter = CoverageHtmlFormatter(linenos=True, noclasses=True, + hl_lines=[1], annotation_infos=anotation_infos) + result = highlight(code, lexer, formatter) + fp = open(mc_path, 'w') + fp.write(result) + fp.close() + +def _parse_modulecoverage(mc_fpath): + #XXX: doc + + fd = open(mc_fpath, 'r') + anotate_infos = [] + src_lines = [] + for line in fd.readlines(): + anotate_info = line[0:2].strip() + if not anotate_info: + anotate_info = None + src_line = line[2:] + anotate_infos.append(anotate_info) + src_lines.append(src_line) + return mc_fpath, anotate_infos, src_lines + +def _parse_coverage_summary(fd): + """Parses coverage summary output.""" + + if hasattr(fd, 'readlines'): + fd.seek(0) + for l in fd.readlines(): + m = REG_COVERAGE_SUMMARY.match(l) + if m: + # yield name, stmts, execs, cover + yield m.group(1), m.group(2), m.group(3), m.group(4) + else: + m = REG_COVERAGE_SUMMARY_TOTAL.match(l) + if m: + # yield name, stmts, execs, cover + yield m.group(1), m.group(2), m.group(3), m.group(4) + + +def _get_coverage_index(mod_name, stmts, execs, cover, annotation_dir): + """ + Generates the index page where are all modulare coverage reports are + linked. + """ + + if mod_name == 'TOTAL': + return '%s%s%s%s\n' % (mod_name, stmts, execs, cover) + covrep_fpath = os.path.join(annotation_dir, '%s,cover.html' % mod_name) + assert os.path.isfile(covrep_fpath) == True + fname = os.path.basename(covrep_fpath) + modlink = '%s' % (fname, mod_name) + return '%s%s%s%s\n' % (modlink, stmts, execs, cover) + + +class CoveragePlugin: + def pytest_addoption(self, parser): + group = parser.addgroup('coverage options') + group.addoption('-C', action='store_true', default=False, + dest = 'coverage', + help=('displays coverage information.')) + group.addoption('--coverage-html', action='store', default=False, + dest='coverage_annotation', + help='path to the coverage HTML output dir.') + group.addoption('--coverage-css-resourcesdir', action='store', + default=DIR_CSS_RESOURCE, + dest='coverage_css_ressourcedir', + help='path to dir with css-resources (%s) for ' + 'being copied to the HTML output dir.' % \ + ", ".join(CSS_RESOURCE_FILES)) + + def pytest_configure(self, config): + if config.getvalue('coverage'): + try: + import coverage + except ImportError: + raise config.Error("To run use the coverage option you have to install " \ + "Ned Batchelder's coverage: "\ + "http://nedbatchelder.com/code/modules/coverage.html") + self.coverage = coverage + self.summary = None + + def pytest_terminal_summary(self, terminalreporter): + if hasattr(self, 'coverage'): + self.coverage.stop() + module_list = [sys.modules[mod] for mod in COVERAGE_MODULES] + module_list.sort() + summary_fd = StringIO() + # get coverage reports by module list + self.coverage.report(module_list, file=summary_fd) + summary = COVERAGE_TERM_HEADER + summary_fd.getvalue() + terminalreporter._tw.write(summary) + + config = terminalreporter.config + dir_annotate_output = config.getvalue('coverage_annotation') + if dir_annotate_output: + if dir_annotate_output == "": + dir_annotate_output = DIR_ANNOTATE_OUTPUT + # create dir + if os.path.isdir(dir_annotate_output): + shutil.rmtree(dir_annotate_output) + os.mkdir(dir_annotate_output) + # generate annotation text files for later parsing + self.coverage.annotate(module_list, dir_annotate_output) + # generate the separate module coverage reports + for mc_fpath in _rename_annotation_files(module_list, \ + dir_annotate_output): + # mc_fpath, anotate_infos, src_lines from _parse_do + _generate_module_coverage(*_parse_modulecoverage(mc_fpath)) + # creating contents for the index pagee for coverage report + idxpage_html = StringIO() + idxpage_html.write(HTML_INDEX_HEADER) + total_sum = None + for args in _parse_coverage_summary(summary_fd): + # mod_name, stmts, execs, cover = args + idxpage_html.write(_get_coverage_index(*args, \ + **dict(annotation_dir=dir_annotate_output))) + idxpage_html.write(HTML_INDEX_FOOTER) + idx_fpath = os.path.join(dir_annotate_output, 'index.html') + idx_fd = open(idx_fpath, 'w') + idx_fd.write(idxpage_html.getvalue()) + idx_fd.close() + + dir_css_resource_dir = config.getvalue('coverage_css_ressourcedir') + if dir_annotate_output and dir_css_resource_dir != "": + if not os.path.isdir(dir_css_resource_dir): + raise config.Error("CSS resource dir not found: '%s'" % \ + dir_css_resource_dir) + for r in CSS_RESOURCE_FILES: + src = os.path.join(dir_css_resource_dir, r) + if os.path.isfile(src): + dest = os.path.join(dir_annotate_output, r) + shutil.copy(src, dest) + + def pyevent__collectionstart(self, collector): + if isinstance(collector, py.__.test.pycollect.Module): + COVERAGE_MODULES.update(getattr(collector.obj, + 'COVERAGE_MODULES', [])) + + def pyevent__testrunstart(self): + if hasattr(self, 'coverage'): + self.coverage.erase() + self.coverage.start() + + +# =============================================================================== +# plugin tests +# =============================================================================== +# XXX +''' +def test_generic(plugintester): + plugintester.apicheck(EventlogPlugin) + + testdir = plugintester.testdir() + testdir.makepyfile(""" + def test_pass(): + pass + """) + testdir.runpytest("--eventlog=event.log") + s = testdir.tmpdir.join("event.log").read() + assert s.find("TestrunStart") != -1 + assert s.find("ItemTestReport") != -1 + assert s.find("TestrunFinish") != -1 +''' Added: py/trunk/contrib/pytest_coverage/header_bg.jpg ============================================================================== Binary file. No diff available. Added: py/trunk/contrib/pytest_coverage/links.gif ============================================================================== Binary file. No diff available. From mzeidler at codespeak.net Tue Apr 7 16:07:59 2009 From: mzeidler at codespeak.net (mzeidler at codespeak.net) Date: Tue, 7 Apr 2009 16:07:59 +0200 (CEST) Subject: [py-svn] r63796 - py/trunk/contrib/pytest_twisted Message-ID: <20090407140759.B126D168427@codespeak.net> Author: mzeidler Date: Tue Apr 7 16:07:59 2009 New Revision: 63796 Added: py/trunk/contrib/pytest_twisted/ py/trunk/contrib/pytest_twisted/__init__.py Log: Initial import for pytest_twisted. Added: py/trunk/contrib/pytest_twisted/__init__.py ============================================================================== --- (empty file) +++ py/trunk/contrib/pytest_twisted/__init__.py Tue Apr 7 16:07:59 2009 @@ -0,0 +1,114 @@ +""" +TODO: + + credits to Ralf Schmitt See: http://twistedmatrix.com/pipermail/twisted-python/2007-February/014872.html + + add option for twisted logging + + write tests +""" + +import os +import sys + +import py +try: + from twisted.internet.defer import Deferred +except ImportError: + print "To use the twisted option you have to install twisted." + sys.exit(0) +try: + from greenlet import greenlet +except ImportError: + print "Since pylib 1.0 greenlet are removed and separately packaged: " \ + "http://pypi.python.org/pypi/greenlet" + sys.exit(0) + +DIR_CUR = str(py.path.local()) + + +def _start_twisted_logging(): + class Logger(object): + """late-bound sys.stdout""" + def write(self, msg): + sys.stdout.write(msg) + + def flush(self): + sys.stdout.flush() + # sys.stdout will be changed by py.test later. + + import twisted.python.log + twisted.python.log.startLogging(Logger(), setStdout=0) + +def _run_twisted(): + """greenlet: run twisted mainloop""" + from twisted.internet import reactor, defer + from twisted.python import log, failure + failure.Failure.cleanFailure = lambda *args: None # make twisted copy traceback... + _start_twisted_logging() # XXX: add py.test option + + def doit(val): + res = gr_tests.switch(val) + if res is None: + reactor.stop() + return + + def done(res): + reactor.callLater(0.0, doit, None) + + def err(res): + reactor.callLater(0.0, doit, res) + + defer.maybeDeferred(res).addCallback(done).addErrback(err) + + reactor.callLater(0.0, doit, None) + reactor.run() + + +class TwistedPlugin: + """Allows to test twisted applications with pytest.""" + + def pytest_addoption(self, parser): + parser.addoption("--twisted", dest="twisted", + help="Allows to test twisted applications with pytest.") + + def pytest_configure(self, config): + twisted = config.getvalue("twisted") + if twisted: + print "Twisted plugin switched on" + gr_twisted.switch() + + def pytest_unconfigure(self, config): + gr_twisted.switch(None) + + def pytest_pyfunc_call(self, pyfuncitem, *args, **kwargs): + def wrapper(func): + res = func.obj() + if isinstance(res, Deferred): + res = gr_twisted.switch(func.obj) + if res: + res.raiseException() + return res + pyfuncitem = wrapper(pyfuncitem) + + +gr_twisted = greenlet(_run_twisted) +gr_tests = greenlet.getcurrent() + +# =============================================================================== +# plugin tests +# =============================================================================== + +# XXX: write test +''' +def test_generic(plugintester): + plugintester.apicheck(EventlogPlugin) + + testdir = plugintester.testdir() + testdir.makepyfile(""" + def test_pass(): + pass + """) + testdir.runpytest("--twisted") + s = testdir.tmpdir.join("event.log").read() + assert s.find("TestrunStart") != -1 + assert s.find("ItemTestReport") != -1 + assert s.find("TestrunFinish") != -1 +''' From hpk at codespeak.net Tue Apr 7 16:28:01 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 7 Apr 2009 16:28:01 +0200 (CEST) Subject: [py-svn] r63797 - in py/trunk/py/test: . testing Message-ID: <20090407142801.E9BEB1684A2@codespeak.net> Author: hpk Date: Tue Apr 7 16:27:57 2009 New Revision: 63797 Modified: py/trunk/py/test/config.py py/trunk/py/test/runner.py py/trunk/py/test/testing/test_config.py Log: introduce a clean "guardedcall" interface for performing calls with exception catching and stdout/stderr capturing Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Tue Apr 7 16:27:57 2009 @@ -286,6 +286,28 @@ roots.append(pydir) return roots + def guardedcall(self, func): + excinfo = result = None + capture = self._getcapture() + try: + try: + result = func() + except KeyboardInterrupt: + raise + except: + excinfo = py.code.ExceptionInfo() + finally: + stdout, stderr = capture.reset() + return CallResult(result, excinfo, stdout, stderr) + +class CallResult: + def __init__(self, result, excinfo, stdout, stderr): + self.stdout = stdout + self.stderr = stderr + self.outerr = (self.stdout, self.stderr) + self.excinfo = excinfo + if excinfo is None: + self.result = result # # helpers Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Tue Apr 7 16:27:57 2009 @@ -198,41 +198,22 @@ self.stack.append(col) def do_setup(self, item): - excinfo, outerr = guarded_call(item.config._getcapture(), - lambda: self.prepare(item) - ) - rep = ItemSetupReport(item, excinfo, outerr) + call = item.config.guardedcall(lambda: self.prepare(item)) + rep = ItemSetupReport(item, call.excinfo, call.outerr) item.config.pytestplugins.notify("itemsetupreport", rep) - return not excinfo + return not call.excinfo def do_teardown(self, item): - excinfo, outerr = guarded_call(item.config._getcapture(), - lambda: self.teardown_exact(item) - ) - if excinfo: - rep = ItemSetupReport(item, excinfo, outerr) + call = item.config.guardedcall(lambda: self.teardown_exact(item)) + if call.excinfo: + rep = ItemSetupReport(item, call.excinfo, call.outerr) item.config.pytestplugins.notify("itemsetupreport", rep) def do_fixture_and_runtest(self, item): """ setup fixture and perform actual item.runtest(). """ if self.do_setup(item): - excinfo, outerr = guarded_call(item.config._getcapture(), - lambda: item.runtest()) + call = item.config.guardedcall(lambda: item.runtest()) item.config.pytestplugins.notify( "item_runtest_finished", - item=item, excinfo=excinfo, outerr=outerr) + item=item, excinfo=call.excinfo, outerr=call.outerr) self.do_teardown(item) - -def guarded_call(capture, call): - excinfo = None - try: - try: - call() - except (KeyboardInterrupt, Exit): - raise - except: - excinfo = py.code.ExceptionInfo() - finally: - outerr = capture.reset() - return excinfo, outerr - Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Tue Apr 7 16:27:57 2009 @@ -137,8 +137,7 @@ assert pl[1] == somepath def test_setsessionclass_and_initsession(self, testdir): - from py.__.test.config import Config - config = Config() + config = testdir.Config() class Session1: def __init__(self, config): self.config = config @@ -149,7 +148,6 @@ py.test.raises(ValueError, "config.setsessionclass(Session1)") - class TestConfigApi_getcolitems: def test_getcolitems_onedir(self, tmpdir): config = py.test.config._reparse([tmpdir]) @@ -214,6 +212,37 @@ assert col.config is config +class TestGuardedCall: + def test_guardedcall_ok(self, testdir): + config = testdir.parseconfig() + def myfunc(x): + print x + print >>py.std.sys.stderr, "hello" + return 7 + call = config.guardedcall(lambda: myfunc(3)) + assert call.excinfo is None + assert call.result == 7 + assert call.stdout.startswith("3") + assert call.stderr.startswith("hello") + + def test_guardedcall_fail(self, testdir): + config = testdir.parseconfig() + def myfunc(x): + print x + raise ValueError(17) + call = config.guardedcall(lambda: myfunc(3)) + assert call.excinfo + assert call.excinfo.type == ValueError + assert not hasattr(call, 'result') + assert call.stdout.startswith("3") + assert not call.stderr + + def test_guardedcall_keyboardinterrupt(self, testdir): + config = testdir.parseconfig() + def myfunc(): + raise KeyboardInterrupt + py.test.raises(KeyboardInterrupt, config.guardedcall, myfunc) + class TestOptionEffects: def test_boxed_option_default(self, testdir): tmpdir = testdir.tmpdir.ensure("subdir", dir=1) From hpk at codespeak.net Tue Apr 7 21:27:04 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 7 Apr 2009 21:27:04 +0200 (CEST) Subject: [py-svn] r63805 - in py/trunk/py/test: . plugin testing Message-ID: <20090407192704.2296516853F@codespeak.net> Author: hpk Date: Tue Apr 7 21:27:00 2009 New Revision: 63805 Added: py/trunk/py/test/plugin/pytest_runner.py Modified: py/trunk/py/test/runner.py py/trunk/py/test/testing/test_runner.py Log: first step towards having a dedicated test-run plugin, some more basics missing Added: py/trunk/py/test/plugin/pytest_runner.py ============================================================================== --- (empty file) +++ py/trunk/py/test/plugin/pytest_runner.py Tue Apr 7 21:27:00 2009 @@ -0,0 +1,273 @@ +import py + +class RunnerPlugin: + def pytest_configure(self, config): + config._setupstate = SetupState() + + def pytest_unconfigure(self, config): + config._setupstate.teardown_all() + + def pytest_item_setup_and_runtest(self, item): + setupstate = item.config._setupstate + call = item.config.guardedcall(lambda: setupstate.prepare(item)) + rep = ItemSetupReport(item, call.excinfo, call.outerr) + if call.excinfo: + item.config.pytestplugins.notify("itemsetupreport", rep) + else: + call = item.config.guardedcall(lambda: item.runtest()) + item.config.mc.pytest_item_runtest_finished( + item=item, excinfo=call.excinfo, outerr=call.outerr) + call = item.config.guardedcall(lambda: self.teardown_exact(item)) + if call.excinfo: + rep = ItemSetupReport(item, call.excinfo, call.outerr) + item.config.pytestplugins.notify("itemsetupreport", rep) + + def pytest_collector_collect(self, collector): + call = item.config.guardedcall(lambda x: collector._memocollect()) + return CollectReport(collector, res, excinfo, outerr) + +class BaseReport(object): + def __repr__(self): + l = ["%s=%s" %(key, value) + for key, value in self.__dict__.items()] + return "<%s %s>" %(self.__class__.__name__, " ".join(l),) + + def toterminal(self, out): + longrepr = self.longrepr + if hasattr(longrepr, 'toterminal'): + longrepr.toterminal(out) + else: + out.line(str(longrepr)) + +class ItemTestReport(BaseReport): + failed = passed = skipped = False + + def __init__(self, colitem, excinfo=None, when=None, outerr=None): + self.colitem = colitem + if colitem and when != "setup": + self.keywords = colitem.readkeywords() + else: + # if we fail during setup it might mean + # we are not able to access the underlying object + # this might e.g. happen if we are unpickled + # and our parent collector did not collect us + # (because it e.g. skipped for platform reasons) + self.keywords = {} + 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 CollectReport(BaseReport): + skipped = failed = passed = False + + def __init__(self, colitem, result, excinfo=None, outerr=None): + # XXX rename to collector + self.colitem = colitem + if not excinfo: + self.passed = True + self.result = result + else: + self.outerr = outerr + self.longrepr = self.colitem._repr_failure_py(excinfo, outerr) + if excinfo.errisinstance(Skipped): + self.skipped = True + self.reason = str(excinfo.value) + else: + self.failed = True + +class ItemSetupReport(BaseReport): + failed = passed = skipped = False + def __init__(self, item, excinfo=None, outerr=None): + self.item = item + self.outerr = outerr + if not excinfo: + self.passed = True + else: + if excinfo.errisinstance(Skipped): + self.skipped = True + else: + self.failed = True + self.excrepr = item._repr_failure_py(excinfo, []) + +class SetupState(object): + """ shared state for setting up/tearing down test items or collectors. """ + def __init__(self): + self.stack = [] + + def teardown_all(self): + while self.stack: + col = self.stack.pop() + col.teardown() + + def teardown_exact(self, item): + if self.stack and self.stack[-1] == item: + col = self.stack.pop() + col.teardown() + + def prepare(self, colitem): + """ setup objects along the collector chain to the test-method + Teardown any unneccessary previously setup objects.""" + needed_collectors = colitem.listchain() + while self.stack: + if self.stack == needed_collectors[:len(self.stack)]: + break + col = self.stack.pop() + col.teardown() + for col in needed_collectors[len(self.stack):]: + col.setup() + self.stack.append(col) + +# =============================================================================== +# +# plugin tests +# +# =============================================================================== + +def test_generic(plugintester): + plugintester.apicheck(RunnerPlugin()) + +class TestSetupState: + def test_setup_prepare(self, testdir): + modcol = testdir.getmodulecol(""" + def setup_function(function): + function.myfunc = function + def test_func1(): + pass + def test_func2(): + pass + def teardown_function(function): + del function.myfunc + """) + item1, item2 = modcol.collect() + setup = SetupState() + setup.prepare(item1) + assert item1.obj.myfunc == item1.obj + assert not hasattr(item2, 'myfunc') + + setup.prepare(item2) + assert item2.obj.myfunc == item2.obj + assert not hasattr(item1, 'myfunc') + + def test_setup_teardown_exact(self, testdir): + item = testdir.getitem(""" + def test_func(): + pass + def teardown_function(function): + function.tear = "down" + """) + setup = SetupState() + setup.prepare(item) + setup.teardown_exact(item) + assert item.obj.tear == "down" + + def test_setup_teardown_exact(self, testdir): + item = testdir.getitem(""" + def setup_module(mod): + mod.x = 1 + def setup_function(function): + function.y = 2 + def test_func(): + pass + def teardown_function(function): + del function.y + def teardown_module(mod): + del mod.x + """) + setup = SetupState() + setup.prepare(item) + assert item.obj.y == 2 + assert item.parent.obj.x == 1 + setup.teardown_all() + assert not hasattr(item.obj, 'y') + assert not hasattr(item.parent.obj, 'x') + +class TestRunnerPlugin: + disabled = True + def test_pytest_item_setup_and_runtest(self, testdir): + item = testdir.getitem("""def test_func(): pass""") + plugin = RunnerPlugin() + plugin.pytest_configure(item.config) + sorter = testdir.geteventrecorder(item.config) + plugin.pytest_item_setup_and_runtest(item) + rep = sorter.getreport("itemtestreport") + assert rep.passed + + +class TestSetupEvents: + disabled = True + + def test_setup_runtest_ok(self, testdir): + sorter = testdir.inline_runsource(""" + def setup_module(mod): + pass + def test_func(): + pass + """) + assert not sorter.getcalls("itemsetupreport") + + def test_setup_fails(self, testdir): + sorter = testdir.inline_runsource(""" + def setup_module(mod): + print "world" + raise ValueError(42) + def test_func(): + pass + """) + rep = sorter.popcall("itemsetupreport").rep + assert rep.failed + assert not rep.skipped + assert rep.excrepr + assert "42" in str(rep.excrepr) + assert rep.outerr[0].find("world") != -1 + + def test_teardown_fails(self, testdir): + sorter = testdir.inline_runsource(""" + def test_func(): + pass + def teardown_function(func): + print "13" + raise ValueError(25) + """) + rep = evrec.popcall("itemsetupreport").rep + assert rep.failed + assert rep.item == item + assert not rep.passed + assert "13" in rep.outerr[0] + assert "25" in str(rep.excrepr) + + def test_setup_skips(self, testdir): + sorter = testdir.inline_runsource(""" + import py + def setup_module(mod): + py.test.skip("17") + def test_func(): + pass + """) + rep = sorter.popcall("itemsetupreport") + assert not rep.failed + assert rep.skipped + assert rep.excrepr + assert "17" in str(rep.excrepr) + + Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Tue Apr 7 21:27:00 2009 @@ -196,24 +196,3 @@ for col in needed_collectors[len(self.stack):]: col.setup() self.stack.append(col) - - def do_setup(self, item): - call = item.config.guardedcall(lambda: self.prepare(item)) - rep = ItemSetupReport(item, call.excinfo, call.outerr) - item.config.pytestplugins.notify("itemsetupreport", rep) - return not call.excinfo - - def do_teardown(self, item): - call = item.config.guardedcall(lambda: self.teardown_exact(item)) - if call.excinfo: - rep = ItemSetupReport(item, call.excinfo, call.outerr) - item.config.pytestplugins.notify("itemsetupreport", rep) - - def do_fixture_and_runtest(self, item): - """ setup fixture and perform actual item.runtest(). """ - if self.do_setup(item): - call = item.config.guardedcall(lambda: item.runtest()) - item.config.pytestplugins.notify( - "item_runtest_finished", - item=item, excinfo=call.excinfo, outerr=call.outerr) - self.do_teardown(item) Modified: py/trunk/py/test/testing/test_runner.py ============================================================================== --- py/trunk/py/test/testing/test_runner.py (original) +++ py/trunk/py/test/testing/test_runner.py Tue Apr 7 21:27:00 2009 @@ -2,6 +2,7 @@ from py.__.test.config import SetupState class TestSetupState: + disabled = True def test_setup_ok(self, testdir): item = testdir.getitem(""" def setup_module(mod): From hpk at codespeak.net Tue Apr 7 22:22:54 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 7 Apr 2009 22:22:54 +0200 (CEST) Subject: [py-svn] r63806 - in py/trunk/py: . misc/testing Message-ID: <20090407202254.F2A6C168544@codespeak.net> Author: hpk Date: Tue Apr 7 22:22:52 2009 New Revision: 63806 Modified: py/trunk/py/__init__.py py/trunk/py/_com.py py/trunk/py/misc/testing/test_com.py Log: adding a MultiAPI helper for managing plugin APIs. Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Tue Apr 7 22:22:52 2009 @@ -57,6 +57,7 @@ '_com.PyPlugins' : ('./_com.py', 'PyPlugins'), '_com.MultiCall' : ('./_com.py', 'MultiCall'), '_com.pyplugins' : ('./_com.py', 'pyplugins'), + '_com.MultiAPI' : ('./_com.py', 'MultiAPI'), # py lib cmdline tools 'cmdline.pytest' : ('./cmdline/pytest.py', 'main',), Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Tue Apr 7 22:22:52 2009 @@ -158,4 +158,26 @@ #print "calling anonymous hooks", args, kwargs MultiCall(self.listattr("pyevent"), eventname, args, kwargs).execute() + +class MultiAPI: + def __init__(self, apiclass, plugins, prefix): + for fullname in vars(apiclass): + if fullname[:2] != "__": + assert fullname.startswith(prefix) + name = fullname[len(prefix):] + mm = CallMaker(plugins, fullname) + setattr(self, name, mm) + +class CallMaker: + def __init__(self, plugins, name): + self.plugins = plugins + self.name = name + + def __repr__(self): + return "" %(self.name, self.plugins) + + def __call__(self, *args, **kwargs): + mc = MultiCall(self.plugins.listattr(self.name), *args, **kwargs) + return mc.execute() + pyplugins = PyPlugins() Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Tue Apr 7 22:22:52 2009 @@ -2,6 +2,7 @@ import py import os from py._com import PyPlugins, MultiCall +from py._com import MultiAPI pytest_plugins = "xfail" @@ -248,3 +249,22 @@ plugins.notify("name", 13, x=15) assert l == [(13, ), {'x':15}] + +class TestMulticallMaker: + def test_happypath(self): + plugins = PyPlugins() + class Api: + def xyz_hello(self, arg): + pass + + mcm = MultiAPI(apiclass=Api, plugins=plugins, prefix="xyz_") + assert hasattr(mcm, 'hello') + assert repr(mcm.hello).find("xyz_hello") != -1 + assert not hasattr(mcm, 'xyz_hello') + class Plugin: + def xyz_hello(self, arg): + return arg + 1 + plugins.register(Plugin()) + l = mcm.hello(3) + assert l == [4] + assert not hasattr(mcm, 'world') From hpk at codespeak.net Tue Apr 7 22:46:51 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 7 Apr 2009 22:46:51 +0200 (CEST) Subject: [py-svn] r63808 - in py/trunk/py: . misc/testing test test/plugin Message-ID: <20090407204651.6988416853F@codespeak.net> Author: hpk Date: Tue Apr 7 22:46:50 2009 New Revision: 63808 Modified: py/trunk/py/__init__.py py/trunk/py/_com.py py/trunk/py/misc/testing/test_com.py py/trunk/py/test/config.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/plugin/pytest_runner.py py/trunk/py/test/pytestplugin.py Log: * refinements/renames to new PluginAPI * have pytest_runner start to use it, passes the main test Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Tue Apr 7 22:46:50 2009 @@ -57,7 +57,7 @@ '_com.PyPlugins' : ('./_com.py', 'PyPlugins'), '_com.MultiCall' : ('./_com.py', 'MultiCall'), '_com.pyplugins' : ('./_com.py', 'pyplugins'), - '_com.MultiAPI' : ('./_com.py', 'MultiAPI'), + '_com.PluginAPI' : ('./_com.py', 'PluginAPI'), # py lib cmdline tools 'cmdline.pytest' : ('./cmdline/pytest.py', 'main',), Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Tue Apr 7 22:46:50 2009 @@ -159,14 +159,16 @@ MultiCall(self.listattr("pyevent"), eventname, args, kwargs).execute() -class MultiAPI: - def __init__(self, apiclass, plugins, prefix): - for fullname in vars(apiclass): - if fullname[:2] != "__": - assert fullname.startswith(prefix) - name = fullname[len(prefix):] - mm = CallMaker(plugins, fullname) +class PluginAPI: + def __init__(self, apiclass, plugins): + self._apiclass = apiclass + self._plugins = plugins + for name in vars(apiclass): + if name[:2] != "__": + mm = CallMaker(plugins, name) setattr(self, name, mm) + def __repr__(self): + return "" %(self._apiclass, self._plugins) class CallMaker: def __init__(self, plugins, name): Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Tue Apr 7 22:46:50 2009 @@ -2,7 +2,7 @@ import py import os from py._com import PyPlugins, MultiCall -from py._com import MultiAPI +from py._com import PluginAPI pytest_plugins = "xfail" @@ -254,15 +254,14 @@ def test_happypath(self): plugins = PyPlugins() class Api: - def xyz_hello(self, arg): + def hello(self, arg): pass - mcm = MultiAPI(apiclass=Api, plugins=plugins, prefix="xyz_") + mcm = PluginAPI(apiclass=Api, plugins=plugins) assert hasattr(mcm, 'hello') - assert repr(mcm.hello).find("xyz_hello") != -1 - assert not hasattr(mcm, 'xyz_hello') + assert repr(mcm.hello).find("hello") != -1 class Plugin: - def xyz_hello(self, arg): + def hello(self, arg): return arg + 1 plugins.register(Plugin()) l = mcm.hello(3) Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Tue Apr 7 22:46:50 2009 @@ -42,6 +42,7 @@ self.pytestplugins = pytestplugins self._conftest = Conftest(onimport=self._onimportconftest) self._setupstate = SetupState() + self.api = pytestplugins._getapi() def _onimportconftest(self, conftestmodule): self.trace("loaded conftestmodule %r" %(conftestmodule,)) Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Tue Apr 7 22:46:50 2009 @@ -39,6 +39,10 @@ def pytest_itemrun(self, item, pdb=None): """ run given test item and return test report. """ + def pytest_item_runtest_finished(self, item, excinfo, outerr): + """ called in-process after runtest() returned. """ + + # ------------------------------------------------------------------------------ # runtest related hooks # ------------------------------------------------------------------------------ Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Tue Apr 7 22:46:50 2009 @@ -17,7 +17,7 @@ from py.__.test import runner return runner.ItemTestReport(item, excinfo, when, outerr) - def pyevent__item_runtest_finished(self, item, excinfo, outerr): + def pytest_item_runtest_finished(self, item, excinfo, outerr): from py.__.test import runner rep = runner.ItemTestReport(item, excinfo, "execute", outerr) item.config.pytestplugins.notify("itemtestreport", rep) Modified: py/trunk/py/test/plugin/pytest_runner.py ============================================================================== --- py/trunk/py/test/plugin/pytest_runner.py (original) +++ py/trunk/py/test/plugin/pytest_runner.py Tue Apr 7 22:46:50 2009 @@ -1,4 +1,5 @@ import py +from outcome import Skipped class RunnerPlugin: def pytest_configure(self, config): @@ -15,7 +16,7 @@ item.config.pytestplugins.notify("itemsetupreport", rep) else: call = item.config.guardedcall(lambda: item.runtest()) - item.config.mc.pytest_item_runtest_finished( + item.config.api.pytest_item_runtest_finished( item=item, excinfo=call.excinfo, outerr=call.outerr) call = item.config.guardedcall(lambda: self.teardown_exact(item)) if call.excinfo: @@ -203,17 +204,15 @@ assert not hasattr(item.parent.obj, 'x') class TestRunnerPlugin: - disabled = True def test_pytest_item_setup_and_runtest(self, testdir): item = testdir.getitem("""def test_func(): pass""") plugin = RunnerPlugin() plugin.pytest_configure(item.config) sorter = testdir.geteventrecorder(item.config) plugin.pytest_item_setup_and_runtest(item) - rep = sorter.getreport("itemtestreport") + rep = sorter.getcall("itemtestreport").rep assert rep.passed - class TestSetupEvents: disabled = True Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Tue Apr 7 22:46:50 2009 @@ -2,6 +2,7 @@ handling py.test plugins. """ import py +from py.__.test.plugin import api class PytestPlugins(object): def __init__(self, pyplugins=None): @@ -11,6 +12,10 @@ self.MultiCall = self.pyplugins.MultiCall self._plugins = {} + def _getapi(self): + return py._com.PluginAPI(apiclass=api.PluginHooks, + plugins=self.pyplugins) + def register(self, plugin): self.pyplugins.register(plugin) def unregister(self, plugin): From mzeidler at codespeak.net Wed Apr 8 12:04:09 2009 From: mzeidler at codespeak.net (mzeidler at codespeak.net) Date: Wed, 8 Apr 2009 12:04:09 +0200 (CEST) Subject: [py-svn] r63821 - py/trunk/contrib/pytest_twisted Message-ID: <20090408100409.647A916848F@codespeak.net> Author: mzeidler Date: Wed Apr 8 12:04:07 2009 New Revision: 63821 Modified: py/trunk/contrib/pytest_twisted/__init__.py Log: Added comment due to the breaking of the setup_method() mechanism. Modified: py/trunk/contrib/pytest_twisted/__init__.py ============================================================================== --- py/trunk/contrib/pytest_twisted/__init__.py (original) +++ py/trunk/contrib/pytest_twisted/__init__.py Wed Apr 8 12:04:07 2009 @@ -1,5 +1,6 @@ """ TODO: + + switching on this plugin breaks 'setup_method' mechanism which then is not previously called before the test function. + credits to Ralf Schmitt See: http://twistedmatrix.com/pipermail/twisted-python/2007-February/014872.html + add option for twisted logging + write tests From hpk at codespeak.net Wed Apr 8 12:06:22 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 8 Apr 2009 12:06:22 +0200 (CEST) Subject: [py-svn] r63822 - in py/trunk/py/test: . plugin Message-ID: <20090408100622.1E0C616848F@codespeak.net> Author: hpk Date: Wed Apr 8 12:06:21 2009 New Revision: 63822 Modified: py/trunk/py/test/collect.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_restdoc.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/plugin/pytest_xfail.py py/trunk/py/test/pycollect.py py/trunk/py/test/pytestplugin.py py/trunk/py/test/runner.py Log: simplifying and strictifying the internal calls to plugins using the new "api" mechanism. Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Wed Apr 8 12:06:21 2009 @@ -438,8 +438,7 @@ return res def consider_file(self, path): - return self.config.pytestplugins.call_each( - 'pytest_collect_file', path=path, parent=self) + return self.config.api.pytest_collect_file(path=path, parent=self) def consider_dir(self, path, usefilters=None): if usefilters is not None: @@ -447,8 +446,8 @@ res = self.config.pytestplugins.call_firstresult( 'pytest_collect_recurse', path=path, parent=self) if res is None or res: - return self.config.pytestplugins.call_each( - 'pytest_collect_directory', path=path, parent=self) + return self.config.api.pytest_collect_directory( + path=path, parent=self) class Item(Node): """ a basic test item. """ Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Wed Apr 8 12:06:21 2009 @@ -35,6 +35,7 @@ def pytest_pymodule_makeitem(self, modcol, name, obj): """ return custom item/collector for a python object in a module, or None. """ + pytest_pymodule_makeitem.firstresult = True def pytest_itemrun(self, item, pdb=None): """ run given test item and return test report. """ @@ -52,14 +53,22 @@ def pytest_item_makereport(self, item, excinfo, when, outerr): """ return ItemTestReport event for the given test outcome. """ + pytest_item_makereport.firstresult = True # reporting hooks (invoked from pytest_terminal.py) - def pytest_report_teststatus(self, event): + def pytest_report_teststatus(self, rep): """ return shortletter and verbose word. """ + pytest_report_teststatus.firstresult = True def pytest_terminal_summary(self, terminalreporter): """ add additional section in terminal summary reporting. """ + # doctest hooks (invoked from pytest_terminal.py) + def pytest_doctest_prepare_content(self, content): + """ return processed content for a given doctest""" + pytest_doctest_prepare_content.firstresult = True + + class Events: # Events def pyevent(self, eventname, args, kwargs): Modified: py/trunk/py/test/plugin/pytest_restdoc.py ============================================================================== --- py/trunk/py/test/plugin/pytest_restdoc.py (original) +++ py/trunk/py/test/plugin/pytest_restdoc.py Wed Apr 8 12:06:21 2009 @@ -154,8 +154,7 @@ class DoctestText(py.test.collect.Item): def runtest(self): content = self._normalize_linesep() - newcontent = self.config.pytestplugins.call_firstresult( - 'pytest_doctest_prepare_content', content=content) + newcontent = self.config.api.pytest_doctest_prepare_content(content=content) if newcontent is not None: content = newcontent s = content Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Wed Apr 8 12:06:21 2009 @@ -60,14 +60,14 @@ self.ensure_newline() self._tw.sep(sep, title, **markup) - def getcategoryletterword(self, event): - res = self.config.pytestplugins.call_firstresult("pytest_report_teststatus", event=event) + def getcategoryletterword(self, rep): + res = self.config.api.pytest_report_teststatus(rep) if res: return res for cat in 'skipped failed passed ???'.split(): - if getattr(event, cat, None): + if getattr(rep, cat, None): break - return cat, self.getoutcomeletter(event), self.getoutcomeword(event) + return cat, self.getoutcomeletter(rep), self.getoutcomeword(rep) def getoutcomeletter(self, rep): return rep.shortrepr @@ -224,7 +224,7 @@ if exitstatus in (0, 1, 2): self.summary_failures() self.summary_skips() - self.config.pytestplugins.call_each("pytest_terminal_summary", terminalreporter=self) + self.config.api.pytest_terminal_summary(terminalreporter=self) if excrepr is not None: self.summary_final_exc(excrepr) if exitstatus == 2: Modified: py/trunk/py/test/plugin/pytest_xfail.py ============================================================================== --- py/trunk/py/test/plugin/pytest_xfail.py (original) +++ py/trunk/py/test/plugin/pytest_xfail.py Wed Apr 8 12:06:21 2009 @@ -22,12 +22,12 @@ res.failed = True return res - def pytest_report_teststatus(self, event): + def pytest_report_teststatus(self, rep): """ return shortletter and verbose word. """ - if 'xfail' in event.keywords: - if event.skipped: + if 'xfail' in rep.keywords: + if rep.skipped: return "xfailed", "x", "xfail" - elif event.failed: + elif rep.failed: return "xpassed", "P", "xpass" # a hook implemented called by the terminalreporter instance/plugin Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Wed Apr 8 12:06:21 2009 @@ -140,8 +140,8 @@ return self.join(name) def makeitem(self, name, obj): - res = self.config.pytestplugins.call_firstresult( - "pytest_pymodule_makeitem", modcol=self, name=name, obj=obj) + res = self.config.api.pytest_pymodule_makeitem( + modcol=self, name=name, obj=obj) if res: return res if (self.classnamefilter(name)) and \ @@ -349,8 +349,8 @@ """ execute the given test function. """ if not self._deprecated_testexecution(): kw = self.lookup_allargs() - ret = self.config.pytestplugins.call_firstresult( - "pytest_pyfunc_call", pyfuncitem=self, args=self._args, kwargs=kw) + ret = self.config.api.pytest_pyfunc_call( + pyfuncitem=self, args=self._args, kwargs=kw) def lookup_allargs(self): kwargs = {} Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Wed Apr 8 12:06:21 2009 @@ -102,12 +102,12 @@ assert not hasattr(self, '_config') config.bus.register(self) self._config = config - self.pyplugins.call_each("pytest_configure", config=self._config) + config.api.pytest_configure(config=self._config) def do_unconfigure(self, config): config = self._config del self._config - self.pyplugins.call_each("pytest_unconfigure", config=config) + config.api.pytest_unconfigure(config=config) config.bus.unregister(self) def do_itemrun(self, item, pdb=None): Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Wed Apr 8 12:06:21 2009 @@ -31,9 +31,8 @@ raise except: excinfo = py.code.ExceptionInfo() - testrep = item.config.pytestplugins.call_firstresult( - "pytest_item_makereport", item=item, - excinfo=excinfo, when=when, outerr=outerr) + testrep = item.config.api.pytest_item_makereport( + item=item, excinfo=excinfo, when=when, outerr=outerr) if pdb and testrep.failed: tw = py.io.TerminalWriter() testrep.toterminal(tw) From hpk at codespeak.net Wed Apr 8 17:15:57 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 8 Apr 2009 17:15:57 +0200 (CEST) Subject: [py-svn] r63845 - in py/trunk/py: . execnet execnet/testing misc/testing test/plugin Message-ID: <20090408151557.BCAC1168525@codespeak.net> Author: hpk Date: Wed Apr 8 17:15:56 2009 New Revision: 63845 Added: py/trunk/py/test/plugin/pytest__pytest.py Modified: py/trunk/py/_com.py py/trunk/py/conftest.py py/trunk/py/execnet/gateway.py py/trunk/py/execnet/register.py py/trunk/py/execnet/testing/test_event.py py/trunk/py/misc/testing/test_com.py py/trunk/py/test/plugin/api.py Log: introduce new _pytest plugin that allows to selectively record plugin calls and do assertions about them. Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Wed Apr 8 17:15:56 2009 @@ -160,26 +160,32 @@ class PluginAPI: - def __init__(self, apiclass, plugins): + def __init__(self, apiclass, plugins=None): self._apiclass = apiclass + if plugins is None: + plugins = pyplugins self._plugins = plugins - for name in vars(apiclass): + for name, method in vars(apiclass).items(): if name[:2] != "__": - mm = CallMaker(plugins, name) + firstresult = getattr(method, 'firstresult', False) + mm = ApiCall(plugins, name, firstresult=firstresult) setattr(self, name, mm) def __repr__(self): return "" %(self._apiclass, self._plugins) -class CallMaker: - def __init__(self, plugins, name): +class ApiCall: + def __init__(self, plugins, name, firstresult): self.plugins = plugins self.name = name + self.firstresult = firstresult def __repr__(self): - return "" %(self.name, self.plugins) + mode = self.firstresult and "firstresult" or "each" + return "" %(self.name, mode, self.plugins) def __call__(self, *args, **kwargs): mc = MultiCall(self.plugins.listattr(self.name), *args, **kwargs) - return mc.execute() + #print "making multicall", self + return mc.execute(firstresult=self.firstresult) pyplugins = PyPlugins() Modified: py/trunk/py/conftest.py ============================================================================== --- py/trunk/py/conftest.py (original) +++ py/trunk/py/conftest.py Wed Apr 8 17:15:56 2009 @@ -1,4 +1,5 @@ -pytest_plugins = 'pytest_doctest', 'pytest_pytester' # , 'pytest_restdoc' +pytest_plugins = '_pytest doctest pytester'.split() + rsyncdirs = ['../doc'] rsyncignore = ['c-extension/greenlet/build'] Modified: py/trunk/py/execnet/gateway.py ============================================================================== --- py/trunk/py/execnet/gateway.py (original) +++ py/trunk/py/execnet/gateway.py Wed Apr 8 17:15:56 2009 @@ -52,6 +52,12 @@ gw.exit() #gw.join() # should work as well +class ExecnetAPI: + def pyexecnet_gateway_init(self, gateway): + """ signal initialisation of new gateway. """ + def pyexecnet_gateway_exit(self, gateway): + """ signal exitting of gateway. """ + # ---------------------------------------------------------- # Base Gateway (used for both remote and local side) # ---------------------------------------------------------- @@ -70,6 +76,12 @@ self._io = io self._channelfactory = ChannelFactory(self, _startcount) self._cleanup.register(self) + try: + from py._com import PluginAPI + except ImportError: + self.api = ExecnetAPI() + else: + self.api = PluginAPI(ExecnetAPI) def _initreceive(self, requestqueue=False): if requestqueue: @@ -331,12 +343,7 @@ self._cleanup.unregister(self) self._stopexec() self._stopsend() - try: - py._com.pyplugins.notify("gateway_exit", self) - except NameError: - # XXX on the remote side 'py' is not imported - # and so we can't notify - pass + self.api.pyexecnet_gateway_exit(gateway=self) def _remote_redirect(self, stdout=None, stderr=None): """ return a handle representing a redirection of a remote Modified: py/trunk/py/execnet/register.py ============================================================================== --- py/trunk/py/execnet/register.py (original) +++ py/trunk/py/execnet/register.py Wed Apr 8 17:15:56 2009 @@ -41,7 +41,7 @@ super(InstallableGateway, self).__init__(io=io, _startcount=1) # XXX we dissallow execution form the other side self._initreceive(requestqueue=False) - py._com.pyplugins.notify("gateway_init", self) + self.api.pyexecnet_gateway_init(gateway=self) def _remote_bootstrap_gateway(self, io, extra=''): """ return Gateway with a asynchronously remotely Modified: py/trunk/py/execnet/testing/test_event.py ============================================================================== --- py/trunk/py/execnet/testing/test_event.py (original) +++ py/trunk/py/execnet/testing/test_event.py Wed Apr 8 17:15:56 2009 @@ -1,11 +1,13 @@ import py pytest_plugins = "pytester" +from py.__.execnet.gateway import ExecnetAPI class TestExecnetEvents: - def test_popengateway(self, eventrecorder): + def test_popengateway_events(self, _pytest): + rec = _pytest.getcallrecorder(ExecnetAPI) gw = py.execnet.PopenGateway() - event = eventrecorder.popevent("gateway_init") - assert event.args[0] == gw + call = rec.popcall("pyexecnet_gateway_init") + assert call.gateway == gw gw.exit() - event = eventrecorder.popevent("gateway_exit") - assert event.args[0] == gw + call = rec.popcall("pyexecnet_gateway_exit") + assert call.gateway == gw Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Wed Apr 8 17:15:56 2009 @@ -250,7 +250,7 @@ assert l == [(13, ), {'x':15}] -class TestMulticallMaker: +class TestPluginAPI: def test_happypath(self): plugins = PyPlugins() class Api: @@ -267,3 +267,22 @@ l = mcm.hello(3) assert l == [4] assert not hasattr(mcm, 'world') + + def test_firstresult(self): + plugins = PyPlugins() + class Api: + def hello(self, arg): pass + hello.firstresult = True + + mcm = PluginAPI(apiclass=Api, plugins=plugins) + class Plugin: + def hello(self, arg): + return arg + 1 + plugins.register(Plugin()) + res = mcm.hello(3) + assert res == 4 + + def test_default_plugins(self): + class Api: pass + mcm = PluginAPI(apiclass=Api) + assert mcm._plugins == py._com.pyplugins Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Wed Apr 8 17:15:56 2009 @@ -48,7 +48,7 @@ # runtest related hooks # ------------------------------------------------------------------------------ - def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): + def pytest_pyfunc_call(self, call, pyfuncitem, args, kwargs): """ return True if we consumed/did the call to the python function item. """ def pytest_item_makereport(self, item, excinfo, when, outerr): Added: py/trunk/py/test/plugin/pytest__pytest.py ============================================================================== --- (empty file) +++ py/trunk/py/test/plugin/pytest__pytest.py Wed Apr 8 17:15:56 2009 @@ -0,0 +1,104 @@ +import py + +class _pytestPlugin: + def pytest_funcarg___pytest(self, pyfuncitem): + return PytestArg(pyfuncitem) + +class PytestArg: + def __init__(self, pyfuncitem): + self.pyfuncitem = pyfuncitem + + def getcallrecorder(self, apiclass, pyplugins=None): + if pyplugins is None: + pyplugins = self.pyfuncitem.config.pytestplugins.pyplugins + callrecorder = CallRecorder(pyplugins) + callrecorder.start_recording(apiclass) + self.pyfuncitem.addfinalizer(callrecorder.finalize) + return callrecorder + + +class ParsedCall: + def __init__(self, name, locals): + assert '_name' not in locals + self.__dict__.update(locals) + self._name = name + + def __repr__(self): + return "" %(self.__dict__,) + +class CallRecorder: + def __init__(self, pyplugins): + self._pyplugins = pyplugins + self.calls = [] + self._recorders = {} + + def start_recording(self, apiclass): + assert apiclass not in self._recorders + class RecordCalls: + _recorder = self + for name, method in vars(apiclass).items(): + if name[0] != "_": + setattr(RecordCalls, name, self._getcallparser(method)) + recorder = RecordCalls() + self._recorders[apiclass] = recorder + self._pyplugins.register(recorder) + + def finalize(self): + for recorder in self._recorders.values(): + self._pyplugins.unregister(recorder) + + def _getcallparser(self, method): + name = method.__name__ + args, varargs, varkw, default = py.std.inspect.getargspec(method) + assert args[0] == "self" + fspec = py.std.inspect.formatargspec(args, varargs, varkw, default) + # we use exec because we want to have early type + # errors on wrong input arguments, using + # *args/**kwargs delays this and gives errors + # elsewhere + exec py.code.compile(""" + def %(name)s%(fspec)s: + self._recorder.calls.append( + ParsedCall(%(name)r, locals())) + """ % locals()) + return locals()[name] + + def popcall(self, name): + for i, call in py.builtin.enumerate(self.calls): + if call._name == name: + del self.calls[i] + return call + raise ValueError("could not find call %r in %r" %(name, self.calls)) + +def test_generic(plugintester): + plugintester.apicheck(_pytestPlugin) + +def test_callrecorder_basic(): + pyplugins = py._com.PyPlugins() + rec = CallRecorder(pyplugins) + class ApiClass: + def xyz(self, arg): + pass + rec.start_recording(ApiClass) + pyplugins.call_each("xyz", 123) + call = rec.popcall("xyz") + assert call.arg == 123 + assert call._name == "xyz" + py.test.raises(ValueError, "rec.popcall('abc')") + +def test_functional(testdir, linecomp): + sorter = testdir.inline_runsource(""" + import py + pytest_plugins="_pytest" + def test_func(_pytest): + class ApiClass: + def xyz(self, arg): pass + rec = _pytest.getcallrecorder(ApiClass) + class Plugin: + def xyz(self, arg): + return arg + 1 + rec._pyplugins.register(Plugin()) + res = rec._pyplugins.call_firstresult("xyz", 41) + assert res == 42 + """) + sorter.assertoutcome(passed=1) From hpk at codespeak.net Wed Apr 8 17:19:50 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 8 Apr 2009 17:19:50 +0200 (CEST) Subject: [py-svn] r63846 - py/trunk/py/test/plugin Message-ID: <20090408151950.EB3DC168554@codespeak.net> Author: hpk Date: Wed Apr 8 17:19:46 2009 New Revision: 63846 Modified: py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_runner.py Log: fix bug Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Wed Apr 8 17:19:46 2009 @@ -48,7 +48,7 @@ # runtest related hooks # ------------------------------------------------------------------------------ - def pytest_pyfunc_call(self, call, pyfuncitem, args, kwargs): + def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): """ return True if we consumed/did the call to the python function item. """ def pytest_item_makereport(self, item, excinfo, when, outerr): Modified: py/trunk/py/test/plugin/pytest_runner.py ============================================================================== --- py/trunk/py/test/plugin/pytest_runner.py (original) +++ py/trunk/py/test/plugin/pytest_runner.py Wed Apr 8 17:19:46 2009 @@ -1,5 +1,5 @@ import py -from outcome import Skipped +from py.__.test.outcome import Skipped class RunnerPlugin: def pytest_configure(self, config): From hpk at codespeak.net Wed Apr 8 19:42:24 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 8 Apr 2009 19:42:24 +0200 (CEST) Subject: [py-svn] r63868 - in py/trunk/py/test: . testing Message-ID: <20090408174224.92799168514@codespeak.net> Author: hpk Date: Wed Apr 8 19:42:23 2009 New Revision: 63868 Modified: py/trunk/py/test/pycollect.py py/trunk/py/test/testing/test_pycollect.py Log: move towards having funcarg setup be part of setup() Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Wed Apr 8 19:42:23 2009 @@ -327,6 +327,7 @@ super(Function, self).__init__(name, parent, config=config) self._finalizers = [] self._args = args + self.funcargs = {} if callobj is not _dummy: self._obj = callobj @@ -348,13 +349,15 @@ def runtest(self): """ execute the given test function. """ if not self._deprecated_testexecution(): - kw = self.lookup_allargs() + self.setupargs() # XXX move to setup() / consider funcargs plugin ret = self.config.api.pytest_pyfunc_call( - pyfuncitem=self, args=self._args, kwargs=kw) + pyfuncitem=self, args=self._args, kwargs=self.funcargs) - def lookup_allargs(self): - kwargs = {} - if not self._args: + def setupargs(self): + if self._args: + # generator case: we don't do anything then + pass + else: # standard Python Test function/method case funcobj = self.obj startindex = getattr(funcobj, 'im_self', None) and 1 or 0 @@ -363,16 +366,13 @@ if i < startindex: continue try: - kwargs[argname] = self.lookup_onearg(argname) + self.funcargs[argname] = self.lookup_onearg(argname) except LookupError, e: numdefaults = len(funcobj.func_defaults or ()) if i + numdefaults >= len(argnames): continue # continue # seems that our args have defaults else: raise - else: - pass # XXX lookup of arguments for yielded/generated tests as well ? - return kwargs def lookup_onearg(self, argname): prefix = "pytest_funcarg__" Modified: py/trunk/py/test/testing/test_pycollect.py ============================================================================== --- py/trunk/py/test/testing/test_pycollect.py (original) +++ py/trunk/py/test/testing/test_pycollect.py Wed Apr 8 19:42:23 2009 @@ -250,7 +250,7 @@ return 42 """) item = testdir.getitem("def test_func(some): pass") - exc = py.test.raises(LookupError, "item.lookup_allargs()") + exc = py.test.raises(LookupError, "item.setupargs()") s = str(exc.value) assert s.find("something") != -1 @@ -260,8 +260,8 @@ def pytest_funcarg__some(self, pyfuncitem): return pyfuncitem.name item.config.pytestplugins.register(Provider()) - kw = item.lookup_allargs() - assert len(kw) == 1 + item.setupargs() + assert len(item.funcargs) == 1 def test_funcarg_lookup_default_gets_overriden(self, testdir): item = testdir.getitem("def test_func(some=42, other=13): pass") @@ -269,9 +269,9 @@ def pytest_funcarg__other(self, pyfuncitem): return pyfuncitem.name item.config.pytestplugins.register(Provider()) - kw = item.lookup_allargs() - assert len(kw) == 1 - name, value = kw.popitem() + item.setupargs() + assert len(item.funcargs) == 1 + name, value = item.funcargs.popitem() assert name == "other" assert value == item.name @@ -283,10 +283,10 @@ def pytest_funcarg__other(self, pyfuncitem): return 42 item.config.pytestplugins.register(Provider()) - kw = item.lookup_allargs() - assert len(kw) == 2 - assert kw['some'] == "test_func" - assert kw['other'] == 42 + item.setupargs() + assert len(item.funcargs) == 2 + assert item.funcargs['some'] == "test_func" + assert item.funcargs['other'] == 42 def test_funcarg_addfinalizer(self, testdir): item = testdir.getitem("def test_func(some): pass") @@ -296,9 +296,9 @@ pyfuncitem.addfinalizer(lambda: l.append(42)) return 3 item.config.pytestplugins.register(Provider()) - kw = item.lookup_allargs() - assert len(kw) == 1 - assert kw['some'] == 3 + item.setupargs() + assert len(item.funcargs) == 1 + assert item.funcargs['some'] == 3 assert len(l) == 0 item.teardown() assert len(l) == 1 @@ -318,10 +318,10 @@ item1, item2 = testdir.genitems([modcol]) modcol.setup() assert modcol.config.pytestplugins.isregistered(modcol.obj) - kwargs = item1.lookup_allargs() - assert kwargs['something'] == "test_method" - kwargs = item2.lookup_allargs() - assert kwargs['something'] == "test_func" + item1.setupargs() + assert item1.funcargs['something'] == "test_method" + item2.setupargs() + assert item2.funcargs['something'] == "test_func" modcol.teardown() assert not modcol.config.pytestplugins.isregistered(modcol.obj) From hpk at codespeak.net Wed Apr 8 19:50:15 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 8 Apr 2009 19:50:15 +0200 (CEST) Subject: [py-svn] r63869 - in py/trunk/py: . execnet execnet/testing test/plugin Message-ID: <20090408175015.95D38168525@codespeak.net> Author: hpk Date: Wed Apr 8 19:50:14 2009 New Revision: 63869 Modified: py/trunk/py/__init__.py py/trunk/py/_com.py py/trunk/py/execnet/gateway.py py/trunk/py/execnet/gwmanage.py py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_execnetcleanup.py py/trunk/py/test/plugin/pytest_terminal.py Log: * moving execnet events to become api plugin calls. * defining Execnet hooks in an explicit API Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Wed Apr 8 19:50:14 2009 @@ -146,6 +146,7 @@ # gateways into remote contexts 'execnet.__doc__' : ('./execnet/__init__.py', '__doc__'), + 'execnet._API' : ('./execnet/gateway.py', 'ExecnetAPI'), 'execnet.SocketGateway' : ('./execnet/register.py', 'SocketGateway'), 'execnet.PopenGateway' : ('./execnet/register.py', 'PopenGateway'), 'execnet.SshGateway' : ('./execnet/register.py', 'SshGateway'), Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Wed Apr 8 19:50:14 2009 @@ -36,26 +36,8 @@ def execute(self, firstresult=False): while self.methods: - self.currentmethod = currentmethod = self.methods.pop() - # provide call introspection if "__call__" is the first positional argument - if hasattr(currentmethod, 'im_self'): - varnames = currentmethod.im_func.func_code.co_varnames - needscall = varnames[1:2] == ('__call__',) - else: - try: - varnames = currentmethod.func_code.co_varnames - except AttributeError: - # builtin function - varnames = () - needscall = varnames[:1] == ('__call__',) - if needscall: - res = currentmethod(self, *self.args, **self.kwargs) - else: - #try: - res = currentmethod(*self.args, **self.kwargs) - #except TypeError: - # print currentmethod.__module__, currentmethod.__name__, self.args, self.kwargs - # raise + currentmethod = self.methods.pop() + res = self.execute_method(currentmethod) if hasattr(self, '_ex1'): self.results = [res] break @@ -70,6 +52,28 @@ if self.results: return self.results[-1] + def execute_method(self, currentmethod): + self.currentmethod = currentmethod + # provide call introspection if "__call__" is the first positional argument + if hasattr(currentmethod, 'im_self'): + varnames = currentmethod.im_func.func_code.co_varnames + needscall = varnames[1:2] == ('__call__',) + else: + try: + varnames = currentmethod.func_code.co_varnames + except AttributeError: + # builtin function + varnames = () + needscall = varnames[:1] == ('__call__',) + if needscall: + return currentmethod(self, *self.args, **self.kwargs) + else: + #try: + return currentmethod(*self.args, **self.kwargs) + #except TypeError: + # print currentmethod.__module__, currentmethod.__name__, self.args, self.kwargs + # raise + def exclude_other_results(self): self._ex1 = True Modified: py/trunk/py/execnet/gateway.py ============================================================================== --- py/trunk/py/execnet/gateway.py (original) +++ py/trunk/py/execnet/gateway.py Wed Apr 8 19:50:14 2009 @@ -57,6 +57,17 @@ """ signal initialisation of new gateway. """ def pyexecnet_gateway_exit(self, gateway): """ signal exitting of gateway. """ + + def pyexecnet_gwmanage_newgateway(self, gateway, platinfo): + """ called when a manager has made a new gateway. """ + + def pyexecnet_gwmanage_rsyncstart(self, source, gateways): + """ called before rsyncing a directory to remote gateways takes place. """ + + def pyexecnet_gwmanage_rsyncfinish(self, source, gateways): + """ called after rsyncing a directory to remote gateways takes place. """ + + # ---------------------------------------------------------- # Base Gateway (used for both remote and local side) @@ -76,12 +87,11 @@ self._io = io self._channelfactory = ChannelFactory(self, _startcount) self._cleanup.register(self) - try: + if _startcount == 1: # only import 'py' on the "client" side from py._com import PluginAPI - except ImportError: - self.api = ExecnetAPI() - else: self.api = PluginAPI(ExecnetAPI) + else: + self.api = ExecnetAPI() def _initreceive(self, requestqueue=False): if requestqueue: Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Wed Apr 8 19:50:14 2009 @@ -21,6 +21,7 @@ if not spec.chdir and not spec.popen: spec.chdir = defaultchdir self.specs.append(spec) + self.api = py._com.PluginAPI(py.execnet._API) def trace(self, msg): self.notify("trace", "gatewaymanage", msg) @@ -34,7 +35,8 @@ gw = py.execnet.makegateway(spec) self.gateways.append(gw) gw.id = "[%s]" % len(self.gateways) - self.notify("gwmanage_newgateway", gw, gw._rinfo()) + self.api.pyexecnet_gwmanage_newgateway( + gateway=gw, platinfo=gw._rinfo()) def getgateways(self, remote=True, inplacelocal=True): if not self.gateways and self.specs: @@ -79,9 +81,15 @@ rsync.add_target_host(gateway, finished=finished) seen[spec] = gateway if seen: - self.notify("gwmanage_rsyncstart", source=source, gateways=seen.values()) + self.api.pyexecnet_gwmanage_rsyncstart( + source=source, + gateways=seen.values(), + ) rsync.send() - self.notify("gwmanage_rsyncfinish", source=source, gateways=seen.values()) + self.api.pyexecnet_gwmanage_rsyncfinish( + source=source, + gateways=seen.values() + ) else: self.trace("rsync: nothing to do.") Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Wed Apr 8 19:50:14 2009 @@ -20,16 +20,15 @@ for spec in GatewayManager(l, defaultchdir="abc").specs: assert spec.chdir == "abc" - def test_popen_makegateway_events(self, eventrecorder): + def test_popen_makegateway_events(self, _pytest): + rec = _pytest.getcallrecorder(py.execnet._API) hm = GatewayManager(["popen"] * 2) hm.makegateways() - event = eventrecorder.popevent("gwmanage_newgateway") - gw, platinfo = event.args[:2] - assert gw.id == "[1]" - platinfo.executable = gw._rinfo().executable - event = eventrecorder.popevent("gwmanage_newgateway") - gw, platinfo = event.args[:2] - assert gw.id == "[2]" + call = rec.popcall("pyexecnet_gwmanage_newgateway") + assert call.gateway.id == "[1]" + assert call.platinfo.executable == call.gateway._rinfo().executable + call = rec.popcall("pyexecnet_gwmanage_newgateway") + assert call.gateway.id == "[2]" assert len(hm.gateways) == 2 hm.exit() assert not len(hm.gateways) @@ -60,18 +59,17 @@ assert dest.join("dir1", "dir2").check() assert dest.join("dir1", "dir2", 'hello').check() - def test_hostmanage_rsync_same_popen_twice(self, source, dest, eventrecorder): + def test_hostmanage_rsync_same_popen_twice(self, source, dest, _pytest): + rec = _pytest.getcallrecorder(py.execnet._API) hm = GatewayManager(["popen//chdir=%s" %dest] * 2) hm.makegateways() source.ensure("dir1", "dir2", "hello") hm.rsync(source) - event = eventrecorder.popevent("gwmanage_rsyncstart") - source2 = event.kwargs['source'] - gws = event.kwargs['gateways'] - assert source2 == source - assert len(gws) == 1 - assert hm.gateways[0] == gws[0] - event = eventrecorder.popevent("gwmanage_rsyncfinish") + call = rec.popcall("pyexecnet_gwmanage_rsyncstart") + assert call.source == source + assert len(call.gateways) == 1 + assert hm.gateways[0] == call.gateways[0] + call = rec.popcall("pyexecnet_gwmanage_rsyncfinish") def test_multi_chdir_popen_with_path(self, testdir): import os Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Wed Apr 8 19:50:14 2009 @@ -77,18 +77,6 @@ def pyevent__NOP(self, *args, **kwargs): """ the no-operation call. """ - def pyevent__gateway_init(self, gateway): - """ called after a gateway has been initialized. """ - - def pyevent__gateway_exit(self, gateway): - """ called when gateway is being exited. """ - - def pyevent__gwmanage_rsyncstart(self, source, gateways): - """ called before rsyncing a directory to remote gateways takes place. """ - - def pyevent__gwmanage_rsyncfinish(self, source, gateways): - """ called after rsyncing a directory to remote gateways takes place. """ - def pyevent__trace(self, category, msg): """ called for tracing events. """ Modified: py/trunk/py/test/plugin/pytest_execnetcleanup.py ============================================================================== --- py/trunk/py/test/plugin/pytest_execnetcleanup.py (original) +++ py/trunk/py/test/plugin/pytest_execnetcleanup.py Wed Apr 8 19:50:14 2009 @@ -11,12 +11,12 @@ if self._debug: print "[execnetcleanup %0x] %s %s" %(id(self), msg, args) - def pyevent__gateway_init(self, gateway): + def pyexecnet_gateway_init(self, gateway): self.trace("init", gateway) if self._gateways is not None: self._gateways.append(gateway) - def pyevent__gateway_exit(self, gateway): + def pyexecnet_gateway_exit(self, gateway): self.trace("exit", gateway) if self._gateways is not None: self._gateways.remove(gateway) Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Wed Apr 8 19:50:14 2009 @@ -86,18 +86,18 @@ for line in str(excrepr).split("\n"): self.write_line("INTERNALERROR> " + line) - def pyevent__gwmanage_newgateway(self, gateway, rinfo): + def pyexecnet_gwmanage_newgateway(self, gateway, platinfo): #self.write_line("%s instantiated gateway from spec %r" %(gateway.id, gateway.spec._spec)) d = {} - d['version'] = repr_pythonversion(rinfo.version_info) + d['version'] = repr_pythonversion(platinfo.version_info) d['id'] = gateway.id d['spec'] = gateway.spec._spec - d['platform'] = rinfo.platform + d['platform'] = platinfo.platform if self.config.option.verbose: - d['extra'] = "- " + rinfo.executable + d['extra'] = "- " + platinfo.executable else: d['extra'] = "" - d['cwd'] = rinfo.cwd + d['cwd'] = platinfo.cwd infoline = ("%(id)s %(spec)s -- platform %(platform)s, " "Python %(version)s " "cwd: %(cwd)s" @@ -105,14 +105,14 @@ self.write_line(infoline) self.gateway2info[gateway] = infoline - def pyevent__gwmanage_rsyncstart(self, source, gateways): + def pyexecnet_gwmanage_rsyncstart(self, source, gateways): targets = ", ".join([gw.id for gw in gateways]) msg = "rsyncstart: %s -> %s" %(source, targets) if not self.config.option.verbose: msg += " # use --verbose to see rsync progress" self.write_line(msg) - def pyevent__gwmanage_rsyncfinish(self, source, gateways): + def pyexecnet_gwmanage_rsyncfinish(self, source, gateways): targets = ", ".join([gw.id for gw in gateways]) self.write_line("rsyncfinish: %s -> %s" %(source, targets)) @@ -460,17 +460,17 @@ executable = "hello" platform = "xyz" cwd = "qwe" - - rep.pyevent__gwmanage_newgateway(gw1, rinfo) + + rep.pyexecnet_gwmanage_newgateway(gw1, rinfo) linecomp.assert_contains_lines([ "X1*popen*xyz*2.5*" ]) - rep.pyevent__gwmanage_rsyncstart(source="hello", gateways=[gw1, gw2]) + rep.pyexecnet_gwmanage_rsyncstart(source="hello", gateways=[gw1, gw2]) linecomp.assert_contains_lines([ "rsyncstart: hello -> X1, X2" ]) - rep.pyevent__gwmanage_rsyncfinish(source="hello", gateways=[gw1, gw2]) + rep.pyexecnet_gwmanage_rsyncfinish(source="hello", gateways=[gw1, gw2]) linecomp.assert_contains_lines([ "rsyncfinish: hello -> X1, X2" ]) From hpk at codespeak.net Wed Apr 8 21:54:58 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 8 Apr 2009 21:54:58 +0200 (CEST) Subject: [py-svn] r63874 - py/trunk/py/execnet Message-ID: <20090408195458.B16121684C6@codespeak.net> Author: hpk Date: Wed Apr 8 21:54:56 2009 New Revision: 63874 Modified: py/trunk/py/execnet/gwmanage.py Log: remove redundant code Modified: py/trunk/py/execnet/gwmanage.py ============================================================================== --- py/trunk/py/execnet/gwmanage.py (original) +++ py/trunk/py/execnet/gwmanage.py Wed Apr 8 21:54:56 2009 @@ -23,12 +23,6 @@ self.specs.append(spec) self.api = py._com.PluginAPI(py.execnet._API) - def trace(self, msg): - self.notify("trace", "gatewaymanage", msg) - - def notify(self, eventname, *args, **kwargs): - py._com.pyplugins.notify(eventname, *args, **kwargs) - def makegateways(self): assert not self.gateways for spec in self.specs: @@ -90,13 +84,10 @@ source=source, gateways=seen.values() ) - else: - self.trace("rsync: nothing to do.") def exit(self): while self.gateways: gw = self.gateways.pop() - self.trace("exiting gateway %s" % gw) gw.exit() class HostRSync(py.execnet.RSync): From hpk at codespeak.net Thu Apr 9 01:33:51 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 01:33:51 +0200 (CEST) Subject: [py-svn] r63883 - in py/trunk/py: . test test/dist test/dist/testing test/looponfail test/plugin test/testing Message-ID: <20090408233351.37D5016843E@codespeak.net> Author: hpk Date: Thu Apr 9 01:33:48 2009 New Revision: 63883 Modified: py/trunk/py/_com.py py/trunk/py/test/dist/dsession.py py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/dist/testing/test_nodemanage.py py/trunk/py/test/dist/testing/test_txnode.py py/trunk/py/test/dist/txnode.py py/trunk/py/test/looponfail/remote.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest__pytest.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/plugin/pytest_doctest.py py/trunk/py/test/plugin/pytest_eventlog.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_resultlog.py py/trunk/py/test/plugin/pytest_runner.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/pytestplugin.py py/trunk/py/test/runner.py py/trunk/py/test/session.py py/trunk/py/test/testing/test_collect.py py/trunk/py/test/testing/test_pytestplugin.py py/trunk/py/test/testing/test_runner.py py/trunk/py/test/testing/test_session.py Log: * moving many more events to become ordinary plugin hook calls. * bit hackish because the code for handling the old events is also still there ... Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Thu Apr 9 01:33:48 2009 @@ -115,11 +115,11 @@ def register(self, plugin): assert not isinstance(plugin, str) + self.call_each("pytest_plugin_registered", plugin) self._plugins.append(plugin) - self.notify("plugin_registered", plugin) def unregister(self, plugin): - self.notify("plugin_unregistered", plugin) + self.call_each("pytest_plugin_unregistered", plugin) self._plugins.remove(plugin) def getplugins(self): Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Thu Apr 9 01:33:48 2009 @@ -24,20 +24,20 @@ self.shuttingdown = False self.testsfailed = False - def pyevent__itemtestreport(self, rep): + def pytest_itemtestreport(self, rep): if rep.colitem in self.dsession.item2nodes: self.dsession.removeitem(rep.colitem, rep.node) if rep.failed: self.testsfailed = True - def pyevent__collectreport(self, rep): + def pytest_collectreport(self, rep): if rep.passed: self.colitems.extend(rep.result) - def pyevent__testnodeready(self, node): + def pytest_testnodeready(self, node): self.dsession.addnode(node) - def pyevent__testnodedown(self, node, error=None): + def pytest_testnodedown(self, node, error=None): pending = self.dsession.removenode(node) if pending: crashitem = pending[0] @@ -95,8 +95,12 @@ continue loopstate.dowork = True - eventname, args, kwargs = eventcall - self.bus.notify(eventname, *args, **kwargs) + callname, args, kwargs = eventcall + call = getattr(self.config.api, callname, None) + if call is not None: + call(*args, **kwargs) + else: + self.bus.notify(callname, *args, **kwargs) # termination conditions if ((loopstate.testsfailed and self.config.option.exitfirst) or @@ -110,10 +114,9 @@ # once we are in shutdown mode we dont send # events other than HostDown upstream eventname, args, kwargs = self.queue.get() - if eventname == "testnodedown": - node, error = args[0], args[1] - self.bus.notify("testnodedown", node, error) - self.removenode(node) + if eventname == "pytest_testnodedown": + self.config.api.pytest_testnodedown(**kwargs) + self.removenode(kwargs['node']) if not self.node2pending: # finished if loopstate.testsfailed: @@ -174,8 +177,8 @@ if isinstance(next, py.test.collect.Item): senditems.append(next) else: - self.bus.notify("collectionstart", next) - self.queueevent("collectreport", basic_collect_report(next)) + self.bus.notify("collectstart", next) + self.queueevent("pytest_collectreport", basic_collect_report(next)) if self.config.option.dist == "each": self.senditems_each(senditems) else: @@ -197,7 +200,7 @@ pending.extend(sending) for item in sending: self.item2nodes.setdefault(item, []).append(node) - self.bus.notify("itemstart", item, node) + self.config.api.pytest_itemstart(item=item, node=node) tosend[:] = tosend[room:] # update inplace if tosend: # we have some left, give it to the main loop @@ -216,7 +219,7 @@ # "sending same item %r to multiple " # "not implemented" %(item,)) self.item2nodes.setdefault(item, []).append(node) - self.bus.notify("itemstart", item, node) + self.config.api.pytest_itemstart(item=item, node=node) pending.extend(sending) tosend[:] = tosend[room:] # update inplace if not tosend: @@ -239,7 +242,7 @@ longrepr = "!!! Node %r crashed during running of test %r" %(node, item) rep = ItemTestReport(item, when="???", excinfo=longrepr) rep.node = node - self.bus.notify("itemtestreport", rep) + self.config.api.pytest_itemtestreport(rep=rep) def setup(self): """ setup any neccessary resources ahead of the test run. """ Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Thu Apr 9 01:33:48 2009 @@ -80,7 +80,7 @@ session = DSession(modcol.config) session.triggertesting([modcol]) name, args, kwargs = session.queue.get(block=False) - assert name == 'collectreport' + assert name == 'pytest_collectreport' rep, = args assert len(rep.result) == 1 @@ -135,7 +135,7 @@ session.queueevent("anonymous") session.loop_once(loopstate) assert node.sent == [[item]] - session.queueevent("itemtestreport", run(item, node)) + session.queueevent("pytest_itemtestreport", run(item, node)) session.loop_once(loopstate) assert loopstate.shuttingdown assert not loopstate.testsfailed @@ -148,7 +148,7 @@ session.addnode(node) # setup a HostDown event - session.queueevent("testnodedown", node, None) + session.queueevent("pytest_testnodedown", node, None) loopstate = session._initloopstate([item]) loopstate.dowork = False @@ -156,7 +156,7 @@ dumpqueue(session.queue) assert loopstate.exitstatus == outcome.EXIT_NOHOSTS - def test_testnodedown_causes_reschedule_pending(self, testdir, EventRecorder): + def test_testnodedown_causes_reschedule_pending(self, testdir): modcol = testdir.getmodulecol(""" def test_crash(): assert 0 @@ -174,8 +174,8 @@ # have one test pending for a node that goes down session.senditems_load([item1, item2]) node = session.item2nodes[item1] [0] - session.queueevent("testnodedown", node, None) - evrec = EventRecorder(session.bus) + session.queueevent("pytest_testnodedown", node, None) + evrec = testdir.geteventrecorder(session.bus) print session.item2nodes loopstate = session._initloopstate([]) session.loop_once(loopstate) @@ -192,18 +192,18 @@ # setup a session with two nodes session = DSession(item.config) node1 = MockNode() - session.queueevent("testnodeready", node1) + session.queueevent("pytest_testnodeready", node1) loopstate = session._initloopstate([item]) loopstate.dowork = False assert len(session.node2pending) == 0 session.loop_once(loopstate) assert len(session.node2pending) == 1 - def test_event_propagation(self, testdir, EventRecorder): + def test_event_propagation(self, testdir): item = testdir.getitem("def test_func(): pass") session = DSession(item.config) - evrec = EventRecorder(session.bus) + evrec = testdir.geteventrecorder(session.bus) session.queueevent("NOP", 42) session.loop_once(session._initloopstate([])) assert evrec.getcall('NOP') @@ -219,10 +219,10 @@ assert node.sent == [[item]] ev = run(item, node) - session.queueevent("itemtestreport", ev) + session.queueevent("pytest_itemtestreport", rep=ev) session.loop_once(loopstate) assert loopstate.shuttingdown - session.queueevent("testnodedown", node, None) + session.queueevent("pytest_testnodedown", node=node, error=None) session.loop_once(loopstate) dumpqueue(session.queue) return session, loopstate.exitstatus @@ -256,30 +256,30 @@ # run tests ourselves and produce reports ev1 = run(items[0], node) ev2 = run(items[1], node) - session.queueevent("itemtestreport", ev1) # a failing one - session.queueevent("itemtestreport", ev2) + session.queueevent("pytest_itemtestreport", rep=ev1) # a failing one + session.queueevent("pytest_itemtestreport", rep=ev2) # now call the loop loopstate = session._initloopstate(items) session.loop_once(loopstate) assert loopstate.testsfailed assert loopstate.shuttingdown - def test_shuttingdown_filters_events(self, testdir, EventRecorder): + def test_shuttingdown_filters_events(self, testdir): item = testdir.getitem("def test_func(): pass") session = DSession(item.config) node = MockNode() session.addnode(node) loopstate = session._initloopstate([]) loopstate.shuttingdown = True - evrec = EventRecorder(session.bus) - session.queueevent("itemtestreport", run(item, node)) + evrec = testdir.geteventrecorder(session.bus) + session.queueevent("pytest_itemtestreport", rep=run(item, node)) session.loop_once(loopstate) - assert not evrec.getcalls("testnodedown") - session.queueevent("testnodedown", node, None) + assert not evrec.getcalls("pytest_testnodedown") + session.queueevent("pytest_testnodedown", node=node, error=None) session.loop_once(loopstate) - assert evrec.getcall('testnodedown').node == node + assert evrec.getcall('pytest_testnodedown').node == node - def test_filteritems(self, testdir, EventRecorder): + def test_filteritems(self, testdir): modcol = testdir.getmodulecol(""" def test_fail(): assert 0 @@ -292,7 +292,7 @@ dsel = session.filteritems([modcol]) assert dsel == [modcol] items = modcol.collect() - evrec = EventRecorder(session.bus) + evrec = testdir.geteventrecorder(session.bus) remaining = session.filteritems(items) assert remaining == [] @@ -313,13 +313,13 @@ node = MockNode() session.addnode(node) session.senditems_load([item]) - session.queueevent("itemtestreport", run(item, node)) + session.queueevent("pytest_itemtestreport", rep=run(item, node)) loopstate = session._initloopstate([]) session.loop_once(loopstate) assert node._shutdown is True assert loopstate.exitstatus is None, "loop did not wait for testnodedown" assert loopstate.shuttingdown - session.queueevent("testnodedown", node, None) + session.queueevent("pytest_testnodedown", node=node, error=None) session.loop_once(loopstate) assert loopstate.exitstatus == 0 @@ -340,10 +340,10 @@ # node2pending will become empty when the loop sees the report rep = run(item1, node) - session.queueevent("itemtestreport", run(item1, node)) + session.queueevent("pytest_itemtestreport", rep=run(item1, node)) # but we have a collection pending - session.queueevent("collectreport", colreport) + session.queueevent("pytest_collectreport", rep=colreport) loopstate = session._initloopstate([]) session.loop_once(loopstate) @@ -354,7 +354,6 @@ assert loopstate.exitstatus is None, "loop did not care for colitems" def test_dist_some_tests(self, testdir): - from py.__.test.dist.testing.test_txnode import EventQueue p1 = testdir.makepyfile(test_one=""" def test_1(): pass @@ -366,16 +365,16 @@ """) config = testdir.parseconfig('-d', p1, '--tx=popen') dsession = DSession(config) - eq = EventQueue(config.bus) + callrecorder = testdir.geteventrecorder(config.bus).callrecorder dsession.main([config.getfsnode(p1)]) - ev, = eq.geteventargs("itemtestreport") - assert ev.passed - ev, = eq.geteventargs("itemtestreport") - assert ev.skipped - ev, = eq.geteventargs("itemtestreport") - assert ev.failed + rep = callrecorder.popcall("pytest_itemtestreport").rep + assert rep.passed + rep = callrecorder.popcall("pytest_itemtestreport").rep + assert rep.skipped + rep = callrecorder.popcall("pytest_itemtestreport").rep + assert rep.failed # see that the node is really down - node, error = eq.geteventargs("testnodedown") + node = callrecorder.popcall("pytest_testnodedown").node assert node.gateway.spec.popen - eq.geteventargs("testrunfinish") + #XXX eq.geteventargs("pytest_testrunfinish") Modified: py/trunk/py/test/dist/testing/test_nodemanage.py ============================================================================== --- py/trunk/py/test/dist/testing/test_nodemanage.py (original) +++ py/trunk/py/test/dist/testing/test_nodemanage.py Thu Apr 9 01:33:48 2009 @@ -97,14 +97,14 @@ assert gwspec._samefilesystem() assert not gwspec.chdir - def test_setup_DEBUG(self, source, EventRecorder): + def test_setup_DEBUG(self, source, testdir): specs = ["popen"] * 2 source.join("conftest.py").write("rsyncdirs = ['a']") source.ensure('a', dir=1) config = py.test.config._reparse([source, '--debug']) assert config.option.debug nodemanager = NodeManager(config, specs) - sorter = EventRecorder(config.bus, debug=True) + sorter = testdir.geteventrecorder(config.bus) nodemanager.setup_nodes(putevent=[].append) for spec in nodemanager.gwmanager.specs: l = sorter.getcalls("trace") @@ -119,6 +119,6 @@ """) sorter = testdir.inline_run("-d", "--rsyncdir=%s" % testdir.tmpdir, "--tx=%s" % specssh, testdir.tmpdir) - ev = sorter.getfirstnamed("itemtestreport") + ev = sorter.getfirstnamed(pytest_itemtestreport) assert ev.passed Modified: py/trunk/py/test/dist/testing/test_txnode.py ============================================================================== --- py/trunk/py/test/dist/testing/test_txnode.py (original) +++ py/trunk/py/test/dist/testing/test_txnode.py Thu Apr 9 01:33:48 2009 @@ -26,7 +26,9 @@ name, args, kwargs = eventcall assert isinstance(name, str) if name == eventname: - return args + if args: + return args + return kwargs events.append(name) if name == "internalerror": print str(kwargs["excrepr"]) @@ -78,9 +80,9 @@ def test_crash_invalid_item(self, mysetup): node = mysetup.makenode() node.send(123) # invalid item - n, error = mysetup.geteventargs("testnodedown") - assert n is node - assert str(error).find("AttributeError") != -1 + kwargs = mysetup.geteventargs("pytest_testnodedown") + assert kwargs['node'] is node + assert str(kwargs['error']).find("AttributeError") != -1 def test_crash_killed(self, testdir, mysetup): if not hasattr(py.std.os, 'kill'): @@ -92,16 +94,16 @@ """) node = mysetup.makenode(item.config) node.send(item) - n, error = mysetup.geteventargs("testnodedown") - assert n is node - assert str(error).find("Not properly terminated") != -1 + kwargs = mysetup.geteventargs("pytest_testnodedown") + assert kwargs['node'] is node + assert str(kwargs['error']).find("Not properly terminated") != -1 def test_node_down(self, mysetup): node = mysetup.makenode() node.shutdown() - n, error = mysetup.geteventargs("testnodedown") - assert n is node - assert not error + kwargs = mysetup.geteventargs("pytest_testnodedown") + assert kwargs['node'] is node + assert not kwargs['error'] node.callback(node.ENDMARK) excinfo = py.test.raises(IOError, "mysetup.geteventargs('testnodedown', timeout=0.01)") @@ -118,11 +120,11 @@ item = testdir.getitem("def test_func(): pass") node = mysetup.makenode(item.config) node.send(item) - ev, = mysetup.geteventargs("itemtestreport") - assert ev.passed - assert ev.colitem == item - #assert event.item == item - #assert event.item is not item + kwargs = mysetup.geteventargs("pytest_itemtestreport") + rep = kwargs['rep'] + assert rep.passed + print rep + assert rep.colitem == item def test_send_some(self, testdir, mysetup): items = testdir.getitems(""" @@ -138,10 +140,11 @@ for item in items: node.send(item) for outcome in "passed failed skipped".split(): - ev, = mysetup.geteventargs("itemtestreport") - assert getattr(ev, outcome) + kwargs = mysetup.geteventargs("pytest_itemtestreport") + rep = kwargs['rep'] + assert getattr(rep, outcome) node.sendlist(items) for outcome in "passed failed skipped".split(): - ev, = mysetup.geteventargs("itemtestreport") - assert getattr(ev, outcome) + rep = mysetup.geteventargs("pytest_itemtestreport")['rep'] + assert getattr(rep, outcome) Modified: py/trunk/py/test/dist/txnode.py ============================================================================== --- py/trunk/py/test/dist/txnode.py (original) +++ py/trunk/py/test/dist/txnode.py Thu Apr 9 01:33:48 2009 @@ -38,21 +38,21 @@ if not self._down: if not err: err = "Not properly terminated" - self.notify("testnodedown", self, err) + self.notify("pytest_testnodedown", node=self, error=err) self._down = True return eventname, args, kwargs = eventcall if eventname == "slaveready": if self._sendslaveready: self._sendslaveready(self) - self.notify("testnodeready", self) + self.notify("pytest_testnodeready", node=self) elif eventname == "slavefinished": self._down = True - self.notify("testnodedown", self, None) - elif eventname == "itemtestreport": - rep = args[0] + self.notify("pytest_testnodedown", error=None, node=self) + elif eventname == "pytest_itemtestreport": + rep = kwargs['rep'] rep.node = self - self.notify("itemtestreport", rep) + self.notify("pytest_itemtestreport", rep=rep) else: self.notify(eventname, *args, **kwargs) except KeyboardInterrupt: @@ -104,8 +104,8 @@ def sendevent(self, eventname, *args, **kwargs): self.channel.send((eventname, args, kwargs)) - def pyevent__itemtestreport(self, rep): - self.sendevent("itemtestreport", rep) + def pytest_itemtestreport(self, rep): + self.sendevent("pytest_itemtestreport", rep=rep) def run(self): channel = self.channel Modified: py/trunk/py/test/looponfail/remote.py ============================================================================== --- py/trunk/py/test/looponfail/remote.py (original) +++ py/trunk/py/test/looponfail/remote.py Thu Apr 9 01:33:48 2009 @@ -137,10 +137,10 @@ session.shouldclose = channel.isclosed class Failures(list): - def pyevent__itemtestreport(self, rep): + def pytest_itemtestreport(self, rep): if rep.failed: self.append(rep) - pyevent__collectreport = pyevent__itemtestreport + pytest_collectreport = pytest_itemtestreport failreports = Failures() session.bus.register(failreports) Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Thu Apr 9 01:33:48 2009 @@ -68,6 +68,36 @@ """ return processed content for a given doctest""" pytest_doctest_prepare_content.firstresult = True + def pytest_itemstart(self, item, node=None): + """ test item gets collected. """ + + def pytest_itemtestreport(self, rep): + """ test has been run. """ + + def pytest_item_runtest_finished(self, item, excinfo, outerr): + """ test has been run. """ + + def pytest_itemsetupreport(self, rep): + """ a report on running a fixture function. """ + + def pytest_collectstart(self, collector): + """ collector starts collecting. """ + + def pytest_collectreport(self, rep): + """ collector finished collecting. """ + + def pytest_plugin_registered(self, plugin): + """ a new py lib plugin got registered. """ + + def pytest_plugin_unregistered(self, plugin): + """ a py lib plugin got unregistered. """ + + def pytest_testnodeready(self, node): + """ Test Node is ready to operate. """ + + def pytest_testnodedown(self, node, error): + """ Test Node is down. """ + class Events: # Events @@ -83,26 +113,9 @@ def pyevent__internalerror(self, excrepr): """ called for internal errors. """ - def pyevent__itemstart(self, item, node=None): - """ test item gets collected. """ - - def pyevent__itemtestreport(self, rep): - """ test has been run. """ - - def pyevent__item_runtest_finished(self, item, excinfo, outerr): - """ test has been run. """ - - def pyevent__itemsetupreport(self, rep): - """ a report on running a fixture function. """ - def pyevent__deselected(self, items): """ collected items that were deselected (by keyword). """ - def pyevent__collectionstart(self, collector): - """ collector starts collecting. """ - - def pyevent__collectreport(self, rep): - """ collector finished collecting. """ def pyevent__testrunstart(self): """ whole test run starts. """ @@ -115,19 +128,11 @@ The gateway will have an 'id' attribute that is unique within the gateway manager context. """ - def pyevent__testnodeready(self, node): - """ Test Node is ready to operate. """ - - def pyevent__testnodedown(self, node, error): - """ Test Node is down. """ - def pyevent__rescheduleitems(self, items): + def pytest_rescheduleitems(self, items): """ reschedule Items from a node that went down. """ def pyevent__looponfailinfo(self, failreports, rootdirs): """ info for repeating failing tests. """ - def pyevent__plugin_registered(self, plugin): - """ a new py lib plugin got registered. """ - Modified: py/trunk/py/test/plugin/pytest__pytest.py ============================================================================== --- py/trunk/py/test/plugin/pytest__pytest.py (original) +++ py/trunk/py/test/plugin/pytest__pytest.py Thu Apr 9 01:33:48 2009 @@ -21,10 +21,13 @@ def __init__(self, name, locals): assert '_name' not in locals self.__dict__.update(locals) + self.__dict__.pop('self') self._name = name def __repr__(self): - return "" %(self.__dict__,) + d = self.__dict__.copy() + del d['_name'] + return "" %(self._name, d) class CallRecorder: def __init__(self, pyplugins): @@ -47,6 +50,11 @@ for recorder in self._recorders.values(): self._pyplugins.unregister(recorder) + def recordsmethod(self, name): + for apiclass in self._recorders: + if hasattr(apiclass, name): + return True + def _getcallparser(self, method): name = method.__name__ args, varargs, varkw, default = py.std.inspect.getargspec(method) @@ -70,6 +78,22 @@ return call raise ValueError("could not find call %r in %r" %(name, self.calls)) + def getcalls(self, names): + if isinstance(names, str): + names = names.split() + for name in names: + for cls in self._recorders: + if name in vars(cls): + break + else: + raise ValueError("callname %r not found in %r" %( + name, self._recorders.keys())) + l = [] + for call in self.calls: + if call._name in names: + l.append(call) + return l + def test_generic(plugintester): plugintester.apicheck(_pytestPlugin) Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Thu Apr 9 01:33:48 2009 @@ -10,7 +10,7 @@ else: runner = basic_run_report report = runner(item, pdb=pdb) - item.config.pytestplugins.notify("itemtestreport", report) + item.config.api.pytest_itemtestreport(rep=report) return True def pytest_item_makereport(self, item, excinfo, when, outerr): @@ -20,8 +20,9 @@ def pytest_item_runtest_finished(self, item, excinfo, outerr): from py.__.test import runner rep = runner.ItemTestReport(item, excinfo, "execute", outerr) - item.config.pytestplugins.notify("itemtestreport", rep) + item.config.api.pytest_itemtestreport(rep=rep) + # XXX make this access pyfuncitem.args or funcargs def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): pyfuncitem.obj(*args, **kwargs) Modified: py/trunk/py/test/plugin/pytest_doctest.py ============================================================================== --- py/trunk/py/test/plugin/pytest_doctest.py (original) +++ py/trunk/py/test/plugin/pytest_doctest.py Thu Apr 9 01:33:48 2009 @@ -122,7 +122,7 @@ 2 """) sorter = testdir.inline_run(p) - call = sorter.getcall("itemtestreport") + call = sorter.getcall("pytest_itemtestreport") assert call.rep.failed assert call.rep.longrepr # XXX Modified: py/trunk/py/test/plugin/pytest_eventlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_eventlog.py (original) +++ py/trunk/py/test/plugin/pytest_eventlog.py Thu Apr 9 01:33:48 2009 @@ -26,6 +26,7 @@ # plugin tests # =============================================================================== + at py.test.mark.xfail def test_generic(plugintester): plugintester.apicheck(EventlogPlugin) Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Thu Apr 9 01:33:48 2009 @@ -6,8 +6,10 @@ import inspect from py.__.test import runner from py.__.test.config import Config as pytestConfig +from pytest__pytest import CallRecorder import api + class PytesterPlugin: def pytest_funcarg__linecomp(self, pyfuncitem): return LineComp() @@ -19,8 +21,8 @@ tmptestdir = TmpTestdir(pyfuncitem) return tmptestdir - def pytest_funcarg__EventRecorder(self, pyfuncitem): - return EventRecorder + #def pytest_funcarg__EventRecorder(self, pyfuncitem): + # return EventRecorder def pytest_funcarg__eventrecorder(self, pyfuncitem): evrec = EventRecorder(py._com.pyplugins) @@ -74,10 +76,12 @@ if hasattr(self, '_olddir'): self._olddir.chdir() - def geteventrecorder(self, config): - evrec = EventRecorder(config.bus) - self.pyfuncitem.addfinalizer(lambda: config.bus.unregister(evrec)) - return evrec + def geteventrecorder(self, bus): + sorter = EventRecorder(bus) + sorter.callrecorder = CallRecorder(bus) + sorter.callrecorder.start_recording(api.PluginHooks) + self.pyfuncitem.addfinalizer(sorter.callrecorder.finalize) + return sorter def chdir(self): old = self.tmpdir.chdir() @@ -128,7 +132,7 @@ #config = self.parseconfig(*args) config = self.parseconfig(*args) session = config.initsession() - rec = EventRecorder(config.bus) + rec = self.geteventrecorder(config.bus) colitems = [config.getfsnode(arg) for arg in config.args] items = list(session.genitems(colitems)) return items, rec @@ -150,10 +154,10 @@ config = self.parseconfig(*args) config.pytestplugins.do_configure(config) session = config.initsession() - sorter = EventRecorder(config.bus) + sorter = self.geteventrecorder(config.bus) session.main() config.pytestplugins.do_unconfigure(config) - return sorter + return sorter def config_preparse(self): config = self.Config() @@ -306,11 +310,17 @@ if len(names) == 1 and isinstance(names, str): names = names.split() l = [] - for event in self.events: - if event.name in names: - method = self._getcallparser(event.name) - pevent = method(*event.args, **event.kwargs) - l.append(pevent) + for name in names: + if self.callrecorder.recordsmethod("pytest_" + name): + name = "pytest_" + name + if self.callrecorder.recordsmethod(name): + l.extend(self.callrecorder.getcalls(name)) + else: + for event in self.events: + if event.name == name: + method = self._getcallparser(event.name) + pevent = method(*event.args, **event.kwargs) + l.append(pevent) return l def _getcallparser(self, eventname): @@ -328,7 +338,7 @@ # functionality for test reports def getreports(self, names="itemtestreport collectreport"): - names = names.split() + names = [("pytest_" + x) for x in names.split()] l = [] for call in self.getcalls(*names): l.append(call.rep) @@ -386,6 +396,7 @@ def unregister(self): self.pyplugins.unregister(self) + at py.test.mark.xfail def test_eventrecorder(): bus = py._com.PyPlugins() recorder = EventRecorder(bus) @@ -395,7 +406,7 @@ rep = runner.ItemTestReport(None, None) rep.passed = False rep.failed = True - bus.notify("itemtestreport", rep) + bus.notify("pytest_itemtestreport", rep) failures = recorder.getfailures() assert failures == [rep] failures = recorder.getfailures() @@ -404,12 +415,12 @@ rep = runner.ItemTestReport(None, None) rep.passed = False rep.skipped = True - bus.notify("itemtestreport", rep) + bus.notify("pytest_itemtestreport", rep) rep = runner.CollectReport(None, None) rep.passed = False rep.failed = True - bus.notify("itemtestreport", rep) + bus.notify("pytest_itemtestreport", rep) passed, skipped, failed = recorder.listoutcomes() assert not passed and skipped and failed @@ -423,7 +434,7 @@ recorder.clear() assert not recorder.events assert not recorder.getfailures() - bus.notify("itemtestreport", rep) + bus.notify(pytest_itemtestreport, rep) assert not recorder.events assert not recorder.getfailures() Modified: py/trunk/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultlog.py (original) +++ py/trunk/py/test/plugin/pytest_resultlog.py Thu Apr 9 01:33:48 2009 @@ -58,7 +58,7 @@ testpath = generic_path(event.colitem) self.write_log_entry(testpath, shortrepr, longrepr) - def pyevent__itemtestreport(self, rep): + def pytest_itemtestreport(self, rep): code = rep.shortrepr if rep.passed: longrepr = "" @@ -68,7 +68,7 @@ longrepr = str(rep.longrepr.reprcrash.message) self.log_outcome(rep, code, longrepr) - def pyevent__collectreport(self, rep): + def pytest_collectreport(self, rep): if not rep.passed: if rep.failed: code = "F" Modified: py/trunk/py/test/plugin/pytest_runner.py ============================================================================== --- py/trunk/py/test/plugin/pytest_runner.py (original) +++ py/trunk/py/test/plugin/pytest_runner.py Thu Apr 9 01:33:48 2009 @@ -13,7 +13,7 @@ call = item.config.guardedcall(lambda: setupstate.prepare(item)) rep = ItemSetupReport(item, call.excinfo, call.outerr) if call.excinfo: - item.config.pytestplugins.notify("itemsetupreport", rep) + item.config.pytestplugins.notify(pytest_itemsetupreport, rep) else: call = item.config.guardedcall(lambda: item.runtest()) item.config.api.pytest_item_runtest_finished( @@ -21,7 +21,7 @@ call = item.config.guardedcall(lambda: self.teardown_exact(item)) if call.excinfo: rep = ItemSetupReport(item, call.excinfo, call.outerr) - item.config.pytestplugins.notify("itemsetupreport", rep) + item.config.api.pytest_itemsetupreport(rep=rep) def pytest_collector_collect(self, collector): call = item.config.guardedcall(lambda x: collector._memocollect()) @@ -208,9 +208,9 @@ item = testdir.getitem("""def test_func(): pass""") plugin = RunnerPlugin() plugin.pytest_configure(item.config) - sorter = testdir.geteventrecorder(item.config) + sorter = testdir.geteventrecorder(item.config.bus) plugin.pytest_item_setup_and_runtest(item) - rep = sorter.getcall("itemtestreport").rep + rep = sorter.getcall("pytest_itemtestreport").rep assert rep.passed class TestSetupEvents: @@ -223,7 +223,7 @@ def test_func(): pass """) - assert not sorter.getcalls("itemsetupreport") + assert not sorter.getcalls(pytest_itemsetupreport) def test_setup_fails(self, testdir): sorter = testdir.inline_runsource(""" @@ -233,7 +233,7 @@ def test_func(): pass """) - rep = sorter.popcall("itemsetupreport").rep + rep = sorter.popcall(pytest_itemsetupreport).rep assert rep.failed assert not rep.skipped assert rep.excrepr @@ -248,7 +248,7 @@ print "13" raise ValueError(25) """) - rep = evrec.popcall("itemsetupreport").rep + rep = evrec.popcall(pytest_itemsetupreport).rep assert rep.failed assert rep.item == item assert not rep.passed @@ -263,7 +263,7 @@ def test_func(): pass """) - rep = sorter.popcall("itemsetupreport") + rep = sorter.popcall(pytest_itemsetupreport) assert not rep.failed assert rep.skipped assert rep.excrepr Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Thu Apr 9 01:33:48 2009 @@ -116,7 +116,7 @@ targets = ", ".join([gw.id for gw in gateways]) self.write_line("rsyncfinish: %s -> %s" %(source, targets)) - def pyevent__plugin_registered(self, plugin): + def pytest_plugin_registered(self, plugin): if self.config.option.traceconfig: msg = "PLUGIN registered: %s" %(plugin,) # XXX this event may happen during setup/teardown time @@ -124,10 +124,10 @@ # which garbles our output if we use self.write_line self.write_line(msg) - def pyevent__testnodeready(self, node): + def pytest_testnodeready(self, node): self.write_line("%s txnode ready to receive tests" %(node.gateway.id,)) - def pyevent__testnodedown(self, node, error): + def pytest_testnodedown(self, node, error): if error: self.write_line("%s node down, error: %s" %(node.gateway.id, error)) @@ -136,7 +136,7 @@ self.config.option.traceconfig and category.find("config") != -1: self.write_line("[%s] %s" %(category, msg)) - def pyevent__itemstart(self, item, node=None): + def pytest_itemstart(self, item, node=None): if self.config.option.debug: info = item.repr_metainfo() line = info.verboseline(basedir=self.curdir) + " " @@ -154,14 +154,14 @@ #self.write_fspath_result(fspath, "") self.write_ensure_prefix(line, "") - def pyevent__rescheduleitems(self, items): + def pytest_rescheduleitems(self, items): if self.config.option.debug: self.write_sep("!", "RESCHEDULING %s " %(items,)) def pyevent__deselected(self, items): self.stats.setdefault('deselected', []).append(items) - def pyevent__itemtestreport(self, rep): + def pytest_itemtestreport(self, rep): fspath = rep.colitem.fspath cat, letter, word = self.getcategoryletterword(rep) if isinstance(word, tuple): @@ -184,7 +184,7 @@ self._tw.write(" " + line) self.currentfspath = -2 - def pyevent__collectreport(self, rep): + def pytest_collectreport(self, rep): if not rep.passed: if rep.failed: self.stats.setdefault("failed", []).append(rep) @@ -309,14 +309,14 @@ def outindent(self, line): self.out.line(self.indent + str(line)) - def pyevent__collectionstart(self, collector): + def pytest_collectstart(self, collector): self.outindent(collector) self.indent += self.INDENT - def pyevent__itemstart(self, item, node=None): + def pytest_itemstart(self, item, node=None): self.outindent(item) - def pyevent__collectreport(self, rep): + def pytest_collectreport(self, rep): if not rep.passed: self.outindent("!!! %s !!!" % rep.longrepr.reprcrash.message) self._failed.append(rep) @@ -373,7 +373,7 @@ for item in testdir.genitems([modcol]): ev = runner.basic_run_report(item) - rep.config.bus.notify("itemtestreport", ev) + rep.config.api.pytest_itemtestreport(rep=ev) linecomp.assert_contains_lines([ "*test_pass_skip_fail.py .sF" ]) @@ -400,10 +400,10 @@ items = modcol.collect() rep.config.option.debug = True # for item in items: - rep.config.bus.notify("itemstart", item, None) + rep.config.api.pytest_itemstart(item=item, node=None) s = linecomp.stringio.getvalue().strip() assert s.endswith(item.name) - rep.config.bus.notify("itemtestreport", runner.basic_run_report(item)) + rep.config.api.pytest_itemtestreport(rep=runner.basic_run_report(item)) linecomp.assert_contains_lines([ "*test_pass_skip_fail_verbose.py:2: *test_ok*PASS*", @@ -520,8 +520,8 @@ rep.config.bus.notify("testrunstart") rep.config.bus.notify("testrunstart") for item in testdir.genitems([modcol]): - rep.config.bus.notify("itemtestreport", - runner.basic_run_report(item)) + rep.config.api.pytest_itemtestreport( + rep=runner.basic_run_report(item)) rep.config.bus.notify("testrunfinish", exitstatus=1) s = linecomp.stringio.getvalue() if tbopt == "long": @@ -548,7 +548,7 @@ l = list(testdir.genitems([modcol])) assert len(l) == 1 modcol.config.option.debug = True - rep.config.bus.notify("itemstart", l[0]) + rep.config.api.pytest_itemstart(item=l[0]) linecomp.assert_contains_lines([ "*test_show_path_before_running_test.py*" ]) @@ -569,8 +569,8 @@ bus.notify("testrunstart") try: for item in testdir.genitems([modcol]): - bus.notify("itemtestreport", - runner.basic_run_report(item)) + modcol.config.api.pytest_itemtestreport( + rep=runner.basic_run_report(item)) except KeyboardInterrupt: excinfo = py.code.ExceptionInfo() else: @@ -628,17 +628,17 @@ rep = CollectonlyReporter(modcol.config, out=linecomp.stringio) modcol.config.bus.register(rep) indent = rep.indent - rep.config.bus.notify("collectionstart", modcol) + rep.config.api.pytest_collectstart(collector=modcol) linecomp.assert_contains_lines([ "" ]) item = modcol.join("test_func") - rep.config.bus.notify("itemstart", item) + rep.config.api.pytest_itemstart(item=item) linecomp.assert_contains_lines([ " ", ]) - rep.config.bus.notify( "collectreport", - runner.CollectReport(modcol, [], excinfo=None)) + rep.config.api.pytest_collectreport( + rep=runner.CollectReport(modcol, [], excinfo=None)) assert rep.indent == indent def test_collectonly_skipped_module(self, testdir, linecomp): Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Thu Apr 9 01:33:48 2009 @@ -86,14 +86,14 @@ if excinfo is None: excinfo = py.code.ExceptionInfo() excrepr = excinfo.getrepr(funcargs=True, showlocals=True) - return self.notify("internalerror", excrepr) + return self.notify("internalerror", excrepr=excrepr) def do_addoption(self, parser): methods = self.pyplugins.listattr("pytest_addoption", reverse=True) mc = py._com.MultiCall(methods, parser=parser) mc.execute() - def pyevent__plugin_registered(self, plugin): + def pytest_plugin_registered(self, plugin): if hasattr(self, '_config'): self.pyplugins.call_plugin(plugin, "pytest_addoption", parser=self._config._parser) self.pyplugins.call_plugin(plugin, "pytest_configure", config=self._config) Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Thu Apr 9 01:33:48 2009 @@ -100,6 +100,7 @@ class ItemTestReport(BaseReport): failed = passed = skipped = False + # XXX rename colitem to item here def __init__(self, colitem, excinfo=None, when=None, outerr=None): self.colitem = colitem if colitem and when != "setup": Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Thu Apr 9 01:33:48 2009 @@ -34,20 +34,19 @@ colitems[:] = list(next) + colitems continue assert self.bus is next.config.bus - notify = self.bus.notify if isinstance(next, Item): remaining = self.filteritems([next]) if remaining: - notify("itemstart", next) + self.config.api.pytest_itemstart(item=next) yield next else: assert isinstance(next, Collector) - notify("collectionstart", next) + self.config.api.pytest_collectstart(collector=next) rep = basic_collect_report(next) if rep.passed: for x in self.genitems(rep.result, keywordexpr): yield x - notify("collectreport", rep) + self.config.api.pytest_collectreport(rep=rep) if self.shouldstop: break @@ -81,12 +80,12 @@ """ setup any neccessary resources ahead of the test run. """ self.bus.notify("testrunstart") - def pyevent__itemtestreport(self, rep): + def pytest_itemtestreport(self, rep): if rep.failed: self._testsfailed = True if self.config.option.exitfirst: self.shouldstop = True - pyevent__collectreport = pyevent__itemtestreport + pytest_collectreport = pytest_itemtestreport def sessionfinishes(self, exitstatus=0, excinfo=None): """ teardown any resources after a test run. """ Modified: py/trunk/py/test/testing/test_collect.py ============================================================================== --- py/trunk/py/test/testing/test_collect.py (original) +++ py/trunk/py/test/testing/test_collect.py Thu Apr 9 01:33:48 2009 @@ -167,7 +167,7 @@ assert "hello" in wascalled assert "world" in wascalled # make sure the directories do not get double-appended - colreports = sorter.getreports(names="collectreport") + colreports = sorter.getreports("collectreport") names = [rep.colitem.name for rep in colreports] assert names.count("hello") == 1 Modified: py/trunk/py/test/testing/test_pytestplugin.py ============================================================================== --- py/trunk/py/test/testing/test_pytestplugin.py (original) +++ py/trunk/py/test/testing/test_pytestplugin.py Thu Apr 9 01:33:48 2009 @@ -69,12 +69,12 @@ assert plugins.getplugin("plug1").__class__.__name__ == "Plug1Plugin" assert plugins.getplugin("plug2").__class__.__name__ == "Plug2Plugin" - def test_consider_module_import_module(self, testdir, EventRecorder): + def test_consider_module_import_module(self, testdir): mod = py.std.new.module("x") mod.pytest_plugins = "pytest_a" aplugin = testdir.makepyfile(pytest_a="""class APlugin: pass""") plugins = PytestPlugins() - sorter = EventRecorder(plugins) + sorter = testdir.geteventrecorder(plugins) #syspath.prepend(aplugin.dirpath()) py.std.sys.path.insert(0, str(aplugin.dirpath())) plugins.consider_module(mod) Modified: py/trunk/py/test/testing/test_runner.py ============================================================================== --- py/trunk/py/test/testing/test_runner.py (original) +++ py/trunk/py/test/testing/test_runner.py Thu Apr 9 01:33:48 2009 @@ -27,7 +27,7 @@ setup = SetupState() res = setup.do_setup(item) assert not res - rep = evrec.popcall("itemsetupreport").rep + rep = evrec.popcall(pytest_itemsetupreport).rep assert rep.failed assert not rep.skipped assert rep.excrepr @@ -46,10 +46,10 @@ setup = SetupState() res = setup.do_setup(item) assert res - rep = evrec.popcall("itemsetupreport").rep + rep = evrec.popcall(pytest_itemsetupreport).rep assert rep.passed setup.do_teardown(item) - rep = evrec.popcall("itemsetupreport").rep + rep = evrec.popcall(pytest_itemsetupreport).rep assert rep.item == item assert rep.failed assert not rep.passed @@ -67,7 +67,7 @@ evrec = testdir.geteventrecorder(item.config) setup = SetupState() setup.do_setup(item) - rep = evrec.popcall("itemsetupreport").rep + rep = evrec.popcall(pytest_itemsetupreport).rep assert not rep.failed assert rep.skipped assert rep.excrepr @@ -78,7 +78,7 @@ evrec = testdir.geteventrecorder(item.config) setup = SetupState() setup.do_fixture_and_runtest(item) - rep = evrec.popcall("itemtestreport").rep + rep = evrec.popcall(pytest_itemtestreport).rep assert rep.passed def test_runtest_fails(self, testdir): @@ -86,7 +86,7 @@ evrec = testdir.geteventrecorder(item.config) setup = SetupState() setup.do_fixture_and_runtest(item) - event = evrec.popcall("item_runtest_finished") + event = evrec.popcall(pytest_item_runtest_finished) assert event.excinfo 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 Thu Apr 9 01:33:48 2009 @@ -27,7 +27,7 @@ assert failed[2].colitem.name == "test_two" itemstarted = sorter.getcalls("itemstart") assert len(itemstarted) == 4 - colstarted = sorter.getcalls("collectionstart") + colstarted = sorter.getcalls("collectstart") assert len(colstarted) == 1 col = colstarted[0].collector assert isinstance(col, py.test.collect.Module) @@ -199,10 +199,10 @@ ) sorter = testdir.inline_run('--collectonly', p.dirpath()) - itemstarted = sorter.getcalls("itemstart") + itemstarted = sorter.getcalls("pytest_itemstart") assert len(itemstarted) == 3 assert not sorter.getreports("itemtestreport") - started = sorter.getcalls("collectionstart") + started = sorter.getcalls("pytest_collectstart") finished = sorter.getreports("collectreport") assert len(started) == len(finished) assert len(started) == 8 From hpk at codespeak.net Thu Apr 9 01:41:35 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 01:41:35 +0200 (CEST) Subject: [py-svn] r63885 - in py/trunk/py/test: . plugin Message-ID: <20090408234135.ECE7B1684C1@codespeak.net> Author: hpk Date: Thu Apr 9 01:41:35 2009 New Revision: 63885 Modified: py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_execnetcleanup.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/session.py Log: moving two more events Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Thu Apr 9 01:41:35 2009 @@ -98,6 +98,13 @@ def pytest_testnodedown(self, node, error): """ Test Node is down. """ + def pytest_testrunstart(self): + """ whole test run starts. """ + + def pytest_testrunfinish(self, exitstatus, excrepr=None): + """ whole test run finishes. """ + + class Events: # Events @@ -116,19 +123,6 @@ def pyevent__deselected(self, items): """ collected items that were deselected (by keyword). """ - - def pyevent__testrunstart(self): - """ whole test run starts. """ - - def pyevent__testrunfinish(self, exitstatus, excrepr=None): - """ whole test run finishes. """ - - def pyevent__gwmanage_newgateway(self, gateway): - """ execnet gateway manager has instantiated a gateway. - The gateway will have an 'id' attribute that is unique - within the gateway manager context. - """ - def pytest_rescheduleitems(self, items): """ reschedule Items from a node that went down. """ Modified: py/trunk/py/test/plugin/pytest_execnetcleanup.py ============================================================================== --- py/trunk/py/test/plugin/pytest_execnetcleanup.py (original) +++ py/trunk/py/test/plugin/pytest_execnetcleanup.py Thu Apr 9 01:41:35 2009 @@ -21,11 +21,11 @@ if self._gateways is not None: self._gateways.remove(gateway) - def pyevent__testrunstart(self): + def pytest_testrunstart(self): self.trace("testrunstart") self._gateways = [] - def pyevent__testrunfinish(self, exitstatus, excrepr=None): + def pytest_testrunfinish(self, exitstatus, excrepr=None): self.trace("testrunfinish", exitstatus) l = [] for gw in self._gateways: Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Thu Apr 9 01:41:35 2009 @@ -194,7 +194,7 @@ self.stats.setdefault("skipped", []).append(rep) self.write_fspath_result(rep.colitem.fspath, "S") - def pyevent__testrunstart(self): + def pytest_testrunstart(self): self.write_sep("=", "test session starts", bold=True) self._sessionstarttime = py.std.time.time() @@ -219,7 +219,7 @@ for i, testarg in py.builtin.enumerate(self.config.args): self.write_line("test object %d: %s" %(i+1, testarg)) - def pyevent__testrunfinish(self, exitstatus, excrepr=None): + def pytest_testrunfinish(self, exitstatus, excrepr=None): self._tw.line("") if exitstatus in (0, 1, 2): self.summary_failures() @@ -322,7 +322,7 @@ self._failed.append(rep) self.indent = self.indent[:-len(self.INDENT)] - def pyevent__testrunfinish(self, exitstatus, excrepr=None): + def pytest_testrunfinish(self, exitstatus, excrepr=None): if self._failed: self.out.sep("!", "collection failures") for rep in self._failed: @@ -369,7 +369,7 @@ """) rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.config.bus.register(rep) - rep.config.bus.notify("testrunstart") + rep.config.api.pytest_testrunstart() for item in testdir.genitems([modcol]): ev = runner.basic_run_report(item) @@ -377,7 +377,7 @@ linecomp.assert_contains_lines([ "*test_pass_skip_fail.py .sF" ]) - rep.config.bus.notify("testrunfinish", exitstatus=1) + rep.config.api.pytest_testrunfinish(exitstatus=1) linecomp.assert_contains_lines([ " def test_func():", "> assert 0", @@ -396,7 +396,7 @@ """, configargs=("-v",)) rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.config.bus.register(rep) - rep.config.bus.notify("testrunstart") + rep.config.api.pytest_testrunstart() items = modcol.collect() rep.config.option.debug = True # for item in items: @@ -410,7 +410,7 @@ "*test_pass_skip_fail_verbose.py:4: *test_skip*SKIP*", "*test_pass_skip_fail_verbose.py:6: *test_func*FAIL*", ]) - rep.config.bus.notify("testrunfinish", exitstatus=1) + rep.config.api.pytest_testrunfinish(exitstatus=1) linecomp.assert_contains_lines([ " def test_func():", "> assert 0", @@ -421,13 +421,13 @@ modcol = testdir.getmodulecol("import xyz") rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.config.bus.register(rep) - rep.config.bus.notify("testrunstart") + rep.config.api.pytest_testrunstart() l = list(testdir.genitems([modcol])) assert len(l) == 0 linecomp.assert_contains_lines([ "*test_collect_fail.py F*" ]) - rep.config.bus.notify("testrunfinish", exitstatus=1) + rep.config.api.pytest_testrunfinish(exitstatus=1) linecomp.assert_contains_lines([ "> import xyz", "E ImportError: No module named xyz" @@ -517,12 +517,11 @@ """, configargs=("--tb=%s" % tbopt,)) rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep.config.bus.register(rep) - rep.config.bus.notify("testrunstart") - rep.config.bus.notify("testrunstart") + rep.config.api.pytest_testrunstart() for item in testdir.genitems([modcol]): rep.config.api.pytest_itemtestreport( rep=runner.basic_run_report(item)) - rep.config.bus.notify("testrunfinish", exitstatus=1) + rep.config.api.pytest_testrunfinish(exitstatus=1) s = linecomp.stringio.getvalue() if tbopt == "long": print s @@ -566,7 +565,7 @@ rep = TerminalReporter(modcol.config, file=linecomp.stringio) modcol.config.bus.register(rep) bus = modcol.config.bus - bus.notify("testrunstart") + modcol.config.api.pytest_testrunstart() try: for item in testdir.genitems([modcol]): modcol.config.api.pytest_itemtestreport( @@ -578,7 +577,7 @@ s = linecomp.stringio.getvalue() if not verbose: assert s.find("_keyboard_interrupt.py Fs") != -1 - bus.notify("testrunfinish", exitstatus=2, excrepr=excinfo.getrepr()) + modcol.config.api.pytest_testrunfinish(exitstatus=2, excrepr=excinfo.getrepr()) text = linecomp.stringio.getvalue() linecomp.assert_contains_lines([ " def test_foobar():", Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Thu Apr 9 01:41:35 2009 @@ -78,7 +78,7 @@ def sessionstarts(self): """ setup any neccessary resources ahead of the test run. """ - self.bus.notify("testrunstart") + self.config.api.pytest_testrunstart() def pytest_itemtestreport(self, rep): if rep.failed: @@ -89,9 +89,10 @@ def sessionfinishes(self, exitstatus=0, excinfo=None): """ teardown any resources after a test run. """ - self.bus.notify("testrunfinish", - exitstatus=exitstatus, - excrepr=excinfo and excinfo.getrepr() or None) + self.config.api.pytest_testrunfinish( + exitstatus=exitstatus, + excrepr=excinfo and excinfo.getrepr() or None + ) def getinitialitems(self, colitems): if colitems is None: From hpk at codespeak.net Thu Apr 9 01:50:02 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 01:50:02 +0200 (CEST) Subject: [py-svn] r63886 - in py/trunk/py/test: . dist dist/testing looponfail plugin testing Message-ID: <20090408235002.E1C6416848F@codespeak.net> Author: hpk Date: Thu Apr 9 01:50:02 2009 New Revision: 63886 Modified: py/trunk/py/test/dist/dsession.py py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/looponfail/remote.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/session.py py/trunk/py/test/testing/test_genitems.py Log: another few events Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Thu Apr 9 01:50:02 2009 @@ -44,7 +44,7 @@ self.dsession.handle_crashitem(crashitem, node) self.colitems.extend(pending[1:]) - def pyevent__rescheduleitems(self, items): + def pytest_rescheduleitems(self, items): self.colitems.extend(items) self.dowork = False # avoid busywait @@ -204,7 +204,7 @@ tosend[:] = tosend[room:] # update inplace if tosend: # we have some left, give it to the main loop - self.queueevent("rescheduleitems", tosend) + self.queueevent(pytest_rescheduleitems, tosend) def senditems_load(self, tosend): if not tosend: @@ -226,7 +226,7 @@ break if tosend: # we have some left, give it to the main loop - self.queueevent("rescheduleitems", tosend) + self.queueevent("pytest_rescheduleitems", items=tosend) def removeitem(self, item, node): if item not in self.item2nodes: Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Thu Apr 9 01:50:02 2009 @@ -99,9 +99,8 @@ assert session.node2pending[node1] == sent1 assert session.node2pending[node2] == sent2 name, args, kwargs = session.queue.get(block=False) - assert name == "rescheduleitems" - items, = args - assert items == [item] + assert name == "pytest_rescheduleitems" + assert kwargs['items'] == [item] def test_keyboardinterrupt(self, testdir): item = testdir.getitem("def test_func(): pass") @@ -125,7 +124,7 @@ node = MockNode() session.addnode(node) loopstate = session._initloopstate([]) - session.queueevent("rescheduleitems", [item]) + session.queueevent("pytest_rescheduleitems", items=[item]) session.loop_once(loopstate) # check that RescheduleEvents are not immediately # rescheduled if there are no nodes @@ -292,18 +291,18 @@ dsel = session.filteritems([modcol]) assert dsel == [modcol] items = modcol.collect() - evrec = testdir.geteventrecorder(session.bus) + callrecorder = testdir.geteventrecorder(session.bus).callrecorder remaining = session.filteritems(items) assert remaining == [] - event = evrec.getcalls("deselected")[-1] + event = callrecorder.getcalls("pytest_deselected")[-1] assert event.items == items modcol.config.option.keyword = "test_fail" remaining = session.filteritems(items) assert remaining == [items[0]] - event = evrec.getcalls("deselected")[-1] + event = callrecorder.getcalls("pytest_deselected")[-1] assert event.items == [items[1]] def test_testnodedown_shutdown_after_completion(self, testdir): Modified: py/trunk/py/test/looponfail/remote.py ============================================================================== --- py/trunk/py/test/looponfail/remote.py (original) +++ py/trunk/py/test/looponfail/remote.py Thu Apr 9 01:50:02 2009 @@ -147,7 +147,7 @@ DEBUG("SLAVE: starting session.main()") session.main(colitems) - session.bus.notify("looponfailinfo", + session.config.api.pytest_looponfailinfo( failreports=list(failreports), rootdirs=[config.topdir]) channel.send([x.colitem._totrail() for x in failreports]) Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Thu Apr 9 01:50:02 2009 @@ -104,6 +104,14 @@ def pytest_testrunfinish(self, exitstatus, excrepr=None): """ whole test run finishes. """ + def pytest_deselected(self, items): + """ collected items that were deselected (by keyword). """ + + def pytest_rescheduleitems(self, items): + """ reschedule Items from a node that went down. """ + + def pytest_looponfailinfo(self, failreports, rootdirs): + """ info for repeating failing tests. """ class Events: @@ -120,13 +128,4 @@ def pyevent__internalerror(self, excrepr): """ called for internal errors. """ - def pyevent__deselected(self, items): - """ collected items that were deselected (by keyword). """ - - def pytest_rescheduleitems(self, items): - """ reschedule Items from a node that went down. """ - - def pyevent__looponfailinfo(self, failreports, rootdirs): - """ info for repeating failing tests. """ - Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Thu Apr 9 01:50:02 2009 @@ -158,7 +158,7 @@ if self.config.option.debug: self.write_sep("!", "RESCHEDULING %s " %(items,)) - def pyevent__deselected(self, items): + def pytest_deselected(self, items): self.stats.setdefault('deselected', []).append(items) def pytest_itemtestreport(self, rep): @@ -232,7 +232,7 @@ self.summary_deselected() self.summary_stats() - def pyevent__looponfailinfo(self, failreports, rootdirs): + def pytest_looponfailinfo(self, failreports, rootdirs): if failreports: self.write_sep("#", "LOOPONFAILING", red=True) for report in failreports: @@ -495,7 +495,7 @@ """) rep = TerminalReporter(modcol.config, file=linecomp.stringio) reports = [runner.basic_run_report(x) for x in modcol.collect()] - rep.pyevent__looponfailinfo(reports, [modcol.config.topdir]) + rep.pytest_looponfailinfo(reports, [modcol.config.topdir]) linecomp.assert_contains_lines([ "*test_looponfailreport.py:2: assert 0", "*test_looponfailreport.py:4: ValueError*", Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Thu Apr 9 01:50:02 2009 @@ -66,7 +66,7 @@ continue remaining.append(colitem) if deselected: - self.bus.notify("deselected", deselected) + self.config.api.pytest_deselected(items=deselected) if self.config.option.keyword.endswith(":"): self._nomatch = True return remaining Modified: py/trunk/py/test/testing/test_genitems.py ============================================================================== --- py/trunk/py/test/testing/test_genitems.py (original) +++ py/trunk/py/test/testing/test_genitems.py Thu Apr 9 01:50:02 2009 @@ -102,7 +102,7 @@ passed, skipped, failed = sorter.listoutcomes() assert len(passed) == 1 assert passed[0].colitem.name == "test_2" - dlist = sorter.getcalls("deselected") + dlist = sorter.getcalls("pytest_deselected") assert len(dlist) == 1 assert dlist[0].items[0].name == 'test_1' @@ -116,7 +116,7 @@ passed, skipped, failed = sorter.listoutcomes() assert len(passed) == 2 assert not failed - dlist = sorter.getcalls("deselected") + dlist = sorter.getcalls("pytest_deselected") assert len(dlist) == 1 item = dlist[0].items[0] assert item.name == "test_one" From hpk at codespeak.net Thu Apr 9 02:12:10 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 02:12:10 +0200 (CEST) Subject: [py-svn] r63887 - in py/trunk/py/test: . dist dist/testing plugin testing Message-ID: <20090409001210.027681684C5@codespeak.net> Author: hpk Date: Thu Apr 9 02:12:10 2009 New Revision: 63887 Modified: py/trunk/py/test/config.py py/trunk/py/test/dist/nodemanage.py py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/dist/testing/test_nodemanage.py py/trunk/py/test/dist/testing/test_txnode.py py/trunk/py/test/dist/txnode.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_resultdb.py py/trunk/py/test/plugin/pytest_resultlog.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/pytestplugin.py py/trunk/py/test/testing/test_pytestplugin.py Log: move rest of events Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Thu Apr 9 02:12:10 2009 @@ -42,7 +42,7 @@ self.pytestplugins = pytestplugins self._conftest = Conftest(onimport=self._onimportconftest) self._setupstate = SetupState() - self.api = pytestplugins._getapi() + self.api = pytestplugins.api def _onimportconftest(self, conftestmodule): self.trace("loaded conftestmodule %r" %(conftestmodule,)) Modified: py/trunk/py/test/dist/nodemanage.py ============================================================================== --- py/trunk/py/test/dist/nodemanage.py (original) +++ py/trunk/py/test/dist/nodemanage.py Thu Apr 9 02:12:10 2009 @@ -15,7 +15,7 @@ self._nodesready = py.std.threading.Event() def trace(self, msg): - self.config.bus.notify("trace", "nodemanage", msg) + self.config.api.pytest_trace(category="nodemanage", msg=msg) def config_getignores(self): return self.config.getconftest_pathlist("rsyncignore") Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Thu Apr 9 02:12:10 2009 @@ -198,15 +198,6 @@ session.loop_once(loopstate) assert len(session.node2pending) == 1 - def test_event_propagation(self, testdir): - item = testdir.getitem("def test_func(): pass") - session = DSession(item.config) - - evrec = testdir.geteventrecorder(session.bus) - session.queueevent("NOP", 42) - session.loop_once(session._initloopstate([])) - assert evrec.getcall('NOP') - def runthrough(self, item): session = DSession(item.config) node = MockNode() Modified: py/trunk/py/test/dist/testing/test_nodemanage.py ============================================================================== --- py/trunk/py/test/dist/testing/test_nodemanage.py (original) +++ py/trunk/py/test/dist/testing/test_nodemanage.py Thu Apr 9 02:12:10 2009 @@ -104,11 +104,10 @@ config = py.test.config._reparse([source, '--debug']) assert config.option.debug nodemanager = NodeManager(config, specs) - sorter = testdir.geteventrecorder(config.bus) + sorter = testdir.geteventrecorder(config.bus).callrecorder nodemanager.setup_nodes(putevent=[].append) for spec in nodemanager.gwmanager.specs: - l = sorter.getcalls("trace") - print sorter.events + l = sorter.getcalls("pytest_trace") assert l nodemanager.teardown_nodes() Modified: py/trunk/py/test/dist/testing/test_txnode.py ============================================================================== --- py/trunk/py/test/dist/testing/test_txnode.py (original) +++ py/trunk/py/test/dist/testing/test_txnode.py Thu Apr 9 02:12:10 2009 @@ -9,9 +9,6 @@ self.queue = queue bus.register(self) - def pyevent(self, eventname, args, kwargs): - self.queue.put((eventname, args, kwargs)) - def geteventargs(self, eventname, timeout=2.0): events = [] while 1: @@ -30,7 +27,7 @@ return args return kwargs events.append(name) - if name == "internalerror": + if name == "pytest_internalerror": print str(kwargs["excrepr"]) class MySetup: @@ -113,7 +110,7 @@ node = mysetup.makenode(item.config) node.channel.close() py.test.raises(IOError, "node.send(item)") - #ev = self.getcalls("internalerror") + #ev = self.getcalls(pytest_internalerror) #assert ev.excinfo.errisinstance(IOError) def test_send_one(self, testdir, mysetup): Modified: py/trunk/py/test/dist/txnode.py ============================================================================== --- py/trunk/py/test/dist/txnode.py (original) +++ py/trunk/py/test/dist/txnode.py Thu Apr 9 02:12:10 2009 @@ -130,5 +130,5 @@ raise except: er = py.code.ExceptionInfo().getrepr(funcargs=True, showlocals=True) - self.sendevent("internalerror", excrepr=er) + self.sendevent("pytest_internalerror", excrepr=er) raise Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Thu Apr 9 02:12:10 2009 @@ -113,19 +113,9 @@ def pytest_looponfailinfo(self, failreports, rootdirs): """ info for repeating failing tests. """ - -class Events: - # Events - def pyevent(self, eventname, args, kwargs): - """ generically called for each notification event. """ - - def pyevent__NOP(self, *args, **kwargs): - """ the no-operation call. """ - - def pyevent__trace(self, category, msg): - """ called for tracing events. """ - - def pyevent__internalerror(self, excrepr): + def pytest_internalerror(self, excrepr): """ called for internal errors. """ + def pytest_trace(self, category, msg): + """ called for tracing events. """ Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Thu Apr 9 02:12:10 2009 @@ -45,7 +45,6 @@ pm = py.test._PytestPlugins() methods = collectattr(pluginclass) hooks = collectattr(api.PluginHooks) - hooks.update(collectattr(api.Events)) getargs = py.std.inspect.getargs def isgenerichook(name): @@ -78,7 +77,7 @@ if fail: py.test.fail("Plugin API error") -def collectattr(obj, prefixes=("pytest_", "pyevent__")): +def collectattr(obj, prefixes=("pytest_",)): methods = {} for apiname in vars(obj): for prefix in prefixes: Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Thu Apr 9 02:12:10 2009 @@ -285,13 +285,6 @@ self.debug = debug pyplugins.register(self) - def pyevent(self, name, args, kwargs): - if name == "plugin_registered" and args == (self,): - return - if self.debug: - print "[event: %s]: %s **%s" %(name, ", ".join(map(str, args)), kwargs,) - self.events.append(Event(name, args, kwargs)) - def popcall(self, name): for i, event in py.builtin.enumerate(self.events): if event.name == name: Modified: py/trunk/py/test/plugin/pytest_resultdb.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultdb.py (original) +++ py/trunk/py/test/plugin/pytest_resultdb.py Thu Apr 9 02:12:10 2009 @@ -349,7 +349,7 @@ except ValueError: excinfo = py.code.ExceptionInfo() reslog = ResultDB(StringIO.StringIO()) - reslog.pyevent__internalerror(excinfo.getrepr) + reslog.pytest_internalerror(excinfo.getrepr) entry = reslog.logfile.getvalue() entry_lines = entry.splitlines() Modified: py/trunk/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultlog.py (original) +++ py/trunk/py/test/plugin/pytest_resultlog.py Thu Apr 9 02:12:10 2009 @@ -78,7 +78,7 @@ longrepr = str(rep.longrepr.reprcrash) self.log_outcome(rep, code, longrepr) - def pyevent__internalerror(self, excrepr): + def pytest_internalerror(self, excrepr): path = excrepr.reprcrash.path self.write_log_entry(path, '!', str(excrepr)) @@ -214,7 +214,7 @@ except ValueError: excinfo = py.code.ExceptionInfo() reslog = ResultLog(StringIO.StringIO()) - reslog.pyevent__internalerror(excinfo.getrepr()) + reslog.pytest_internalerror(excinfo.getrepr()) entry = reslog.logfile.getvalue() entry_lines = entry.splitlines() Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Thu Apr 9 02:12:10 2009 @@ -82,7 +82,7 @@ else: return "???", dict(red=True) - def pyevent__internalerror(self, excrepr): + def pytest_internalerror(self, excrepr): for line in str(excrepr).split("\n"): self.write_line("INTERNALERROR> " + line) @@ -131,7 +131,7 @@ if error: self.write_line("%s node down, error: %s" %(node.gateway.id, error)) - def pyevent__trace(self, category, msg): + def pytest_trace(self, category, msg): if self.config.option.debug or \ self.config.option.traceconfig and category.find("config") != -1: self.write_line("[%s] %s" %(category, msg)) @@ -437,7 +437,7 @@ modcol = testdir.getmodulecol("def test_one(): pass") rep = TerminalReporter(modcol.config, file=linecomp.stringio) excinfo = py.test.raises(ValueError, "raise ValueError('hello')") - rep.pyevent__internalerror(excinfo.getrepr()) + rep.pytest_internalerror(excinfo.getrepr()) linecomp.assert_contains_lines([ "INTERNALERROR> *raise ValueError*" ]) Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Thu Apr 9 02:12:10 2009 @@ -12,9 +12,9 @@ self.MultiCall = self.pyplugins.MultiCall self._plugins = {} - def _getapi(self): - return py._com.PluginAPI(apiclass=api.PluginHooks, - plugins=self.pyplugins) + self.api = py._com.PluginAPI( + apiclass=api.PluginHooks, + plugins=self.pyplugins) def register(self, plugin): self.pyplugins.register(plugin) @@ -86,7 +86,7 @@ if excinfo is None: excinfo = py.code.ExceptionInfo() excrepr = excinfo.getrepr(funcargs=True, showlocals=True) - return self.notify("internalerror", excrepr=excrepr) + return self.api.pytest_internalerror(excrepr=excrepr) def do_addoption(self, parser): methods = self.pyplugins.listattr("pytest_addoption", reverse=True) Modified: py/trunk/py/test/testing/test_pytestplugin.py ============================================================================== --- py/trunk/py/test/testing/test_pytestplugin.py (original) +++ py/trunk/py/test/testing/test_pytestplugin.py Thu Apr 9 02:12:10 2009 @@ -176,7 +176,7 @@ class A: def pytest_configure(self, config): l.append(self) - def pyevent__hello(self, obj): + def xyz(self, obj): events.append(obj) config.bus.register(A()) @@ -187,7 +187,7 @@ assert len(l) == 2 assert l[0] != l[1] - config.bus.notify("hello", 42) + config.bus.call_each("xyz", obj=42) assert len(events) == 2 assert events == [42,42] From hpk at codespeak.net Thu Apr 9 02:36:09 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 02:36:09 +0200 (CEST) Subject: [py-svn] r63888 - in py/trunk/py: . misc misc/testing test test/dist test/dist/testing test/looponfail test/plugin Message-ID: <20090409003609.4EF9C1684C4@codespeak.net> Author: hpk Date: Thu Apr 9 02:36:07 2009 New Revision: 63888 Modified: py/trunk/py/_com.py py/trunk/py/initpkg.py py/trunk/py/misc/testing/test_com.py py/trunk/py/misc/warn.py py/trunk/py/test/config.py py/trunk/py/test/dist/dsession.py py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/looponfail/remote.py py/trunk/py/test/plugin/pytest__pytest.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_runner.py py/trunk/py/test/pytestplugin.py Log: finally. the event concept is basically gone. now we only have plugin hooks aka plugin calls Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Thu Apr 9 02:36:07 2009 @@ -1,21 +1,5 @@ """ -py lib plugins and events. - -you can write plugins that extend the py lib API. -currently this is mostly used by py.test - -registering a plugin -++++++++++++++++++++++++++++++++++ - -:: - >>> class MyPlugin: - ... def pyevent__plugin_registered(self, plugin): - ... print "registering", plugin.__class__.__name__ - ... - >>> import py - >>> py._com.pyplugins.register(MyPlugin()) - registering MyPlugin - +py lib plugins and plugin call management """ import py @@ -92,7 +76,6 @@ def import_module(self, modspec): # XXX allow modspec to specify version / lookup modpath = modspec - self.notify("importingmodule", modpath) __import__(modpath) def consider_env(self): @@ -155,13 +138,6 @@ return MultiCall(self.listattr(methname, plugins=[plugin]), *args, **kwargs).execute(firstresult=True) - def notify(self, eventname, *args, **kwargs): - #print "notifying", eventname, args, kwargs - MultiCall(self.listattr("pyevent__" + eventname), - *args, **kwargs).execute() - #print "calling anonymous hooks", args, kwargs - MultiCall(self.listattr("pyevent"), eventname, args, kwargs).execute() - class PluginAPI: def __init__(self, apiclass, plugins=None): Modified: py/trunk/py/initpkg.py ============================================================================== --- py/trunk/py/initpkg.py (original) +++ py/trunk/py/initpkg.py Thu Apr 9 02:36:07 2009 @@ -280,5 +280,4 @@ ENVKEY = pkgname.upper() + "_AUTOIMPORT" if ENVKEY in os.environ: for impname in os.environ[ENVKEY].split(","): - py._com.pyplugins.notify("autoimport", impname) __import__(impname) Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Thu Apr 9 02:36:07 2009 @@ -97,21 +97,6 @@ assert not plugins.isregistered(my) assert plugins.getplugins() == [my2] - def test_onregister(self): - plugins = PyPlugins() - l = [] - class MyApi: - def pyevent__plugin_registered(self, plugin): - l.append(plugin) - def pyevent__plugin_unregistered(self, plugin): - l.remove(plugin) - myapi = MyApi() - plugins.register(myapi) - assert len(l) == 1 - assert l[0] is myapi - plugins.unregister(myapi) - assert not l - def test_call_methods(self): plugins = PyPlugins() class api1: @@ -175,21 +160,6 @@ l = list(plugins.listattr('x', reverse=True)) assert l == [43, 42, 41] - def test_notify_anonymous_ordered(self): - plugins = PyPlugins() - l = [] - class api1: - def pyevent__hello(self): - l.append("hellospecific") - class api2: - def pyevent(self, name, args, kwargs): - if name == "hello": - l.append(name + "anonymous") - plugins.register(api1()) - plugins.register(api2()) - plugins.notify('hello') - assert l == ["hellospecific", "helloanonymous"] - def test_consider_env(self, monkeypatch): plugins = PyPlugins() monkeypatch.setitem(os.environ, 'PYLIB', "unknownconsider_env") @@ -201,14 +171,7 @@ mod.pylib = ["xxx nomod"] excinfo = py.test.raises(ImportError, "plugins.consider_module(mod)") mod.pylib = "os" - class Events(list): - def pyevent__importingmodule(self, mod): - self.append(mod) - l = Events() - plugins.register(l) plugins.consider_module(mod) - assert len(l) == 1 - assert l[0] == (mod.pylib) def test_api_and_defaults(): assert isinstance(py._com.pyplugins, PyPlugins) @@ -226,30 +189,6 @@ finally: old.chdir() -class TestPyPluginsEvents: - def test_pyevent__named_dispatch(self): - plugins = PyPlugins() - l = [] - class A: - def pyevent__name(self, x): - l.append(x) - plugins.register(A()) - plugins.notify("name", 13) - assert l == [13] - - def test_pyevent__anonymous_dispatch(self): - plugins = PyPlugins() - l = [] - class A: - def pyevent(self, name, args, kwargs): - if name == "name": - l.extend([args, kwargs]) - - plugins.register(A()) - plugins.notify("name", 13, x=15) - assert l == [(13, ), {'x':15}] - - class TestPluginAPI: def test_happypath(self): plugins = PyPlugins() Modified: py/trunk/py/misc/warn.py ============================================================================== --- py/trunk/py/misc/warn.py (original) +++ py/trunk/py/misc/warn.py Thu Apr 9 02:36:07 2009 @@ -62,7 +62,7 @@ filename = module path = py.path.local(filename) warning = Warning(msg, path, lineno) - self.bus.notify("WARNING", warning) + self.bus.call_each("pyevent__WARNING", warning) # singleton api warner for py lib apiwarner = WarningPlugin(py._com.pyplugins) Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Thu Apr 9 02:36:07 2009 @@ -50,7 +50,7 @@ def trace(self, msg): if getattr(self.option, 'traceconfig', None): - self.bus.notify("trace", "config", msg) + self.api.pytest_trace(category="config", msg=msg) def _processopt(self, opt): if hasattr(opt, 'default') and opt.dest: Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Thu Apr 9 02:36:07 2009 @@ -96,11 +96,9 @@ loopstate.dowork = True callname, args, kwargs = eventcall - call = getattr(self.config.api, callname, None) - if call is not None: + if callname is not None: + call = getattr(self.config.api, callname) call(*args, **kwargs) - else: - self.bus.notify(callname, *args, **kwargs) # termination conditions if ((loopstate.testsfailed and self.config.option.exitfirst) or @@ -177,7 +175,7 @@ if isinstance(next, py.test.collect.Item): senditems.append(next) else: - self.bus.notify("collectstart", next) + self.config.api.pytest_collectstart(collector=next) self.queueevent("pytest_collectreport", basic_collect_report(next)) if self.config.option.dist == "each": self.senditems_each(senditems) Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Thu Apr 9 02:36:07 2009 @@ -129,9 +129,9 @@ # check that RescheduleEvents are not immediately # rescheduled if there are no nodes assert loopstate.dowork == False - session.queueevent("anonymous") + session.queueevent(None) session.loop_once(loopstate) - session.queueevent("anonymous") + session.queueevent(None) session.loop_once(loopstate) assert node.sent == [[item]] session.queueevent("pytest_itemtestreport", run(item, node)) @@ -204,7 +204,7 @@ session.addnode(node) loopstate = session._initloopstate([item]) - session.queueevent("NOP") + session.queueevent(None) session.loop_once(loopstate) assert node.sent == [[item]] Modified: py/trunk/py/test/looponfail/remote.py ============================================================================== --- py/trunk/py/test/looponfail/remote.py (original) +++ py/trunk/py/test/looponfail/remote.py Thu Apr 9 02:36:07 2009 @@ -129,7 +129,7 @@ try: colitem = py.test.collect.Collector._fromtrail(trail, config) except AssertionError, e: - #XXX session.bus.notify of "test disappeared" + #XXX send info for "test disappeared" or so continue colitems.append(colitem) else: Modified: py/trunk/py/test/plugin/pytest__pytest.py ============================================================================== --- py/trunk/py/test/plugin/pytest__pytest.py (original) +++ py/trunk/py/test/plugin/pytest__pytest.py Thu Apr 9 02:36:07 2009 @@ -49,6 +49,7 @@ def finalize(self): for recorder in self._recorders.values(): self._pyplugins.unregister(recorder) + self._recorders.clear() def recordsmethod(self, name): for apiclass in self._recorders: Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Thu Apr 9 02:36:07 2009 @@ -385,21 +385,20 @@ def clear(self): self.events[:] = [] + self.callrecorder.calls[:] = [] def unregister(self): self.pyplugins.unregister(self) + self.callrecorder.finalize() - at py.test.mark.xfail -def test_eventrecorder(): +def test_eventrecorder(testdir): bus = py._com.PyPlugins() - recorder = EventRecorder(bus) - bus.notify("anonymous") - assert recorder.events + recorder = testdir.geteventrecorder(bus) assert not recorder.getfailures() rep = runner.ItemTestReport(None, None) rep.passed = False rep.failed = True - bus.notify("pytest_itemtestreport", rep) + bus.call_each("pytest_itemtestreport", rep=rep) failures = recorder.getfailures() assert failures == [rep] failures = recorder.getfailures() @@ -408,12 +407,12 @@ rep = runner.ItemTestReport(None, None) rep.passed = False rep.skipped = True - bus.notify("pytest_itemtestreport", rep) + bus.call_each("pytest_itemtestreport", rep=rep) rep = runner.CollectReport(None, None) rep.passed = False rep.failed = True - bus.notify("pytest_itemtestreport", rep) + bus.call_each("pytest_itemtestreport", rep=rep) passed, skipped, failed = recorder.listoutcomes() assert not passed and skipped and failed @@ -427,8 +426,7 @@ recorder.clear() assert not recorder.events assert not recorder.getfailures() - bus.notify(pytest_itemtestreport, rep) - assert not recorder.events + bus.call_each("pytest_itemtestreport", rep=rep) assert not recorder.getfailures() class LineComp: Modified: py/trunk/py/test/plugin/pytest_runner.py ============================================================================== --- py/trunk/py/test/plugin/pytest_runner.py (original) +++ py/trunk/py/test/plugin/pytest_runner.py Thu Apr 9 02:36:07 2009 @@ -11,9 +11,9 @@ def pytest_item_setup_and_runtest(self, item): setupstate = item.config._setupstate call = item.config.guardedcall(lambda: setupstate.prepare(item)) - rep = ItemSetupReport(item, call.excinfo, call.outerr) if call.excinfo: - item.config.pytestplugins.notify(pytest_itemsetupreport, rep) + rep = ItemSetupReport(item, call.excinfo, call.outerr) + item.config.api.pytest_itemsetupreport(rep=rep) else: call = item.config.guardedcall(lambda: item.runtest()) item.config.api.pytest_item_runtest_finished( Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Thu Apr 9 02:36:07 2009 @@ -79,9 +79,6 @@ #print "plugins.call_each", args[0], args[1:], kwargs return self.pyplugins.call_each(*args, **kwargs) - def notify(self, eventname, *args, **kwargs): - return self.pyplugins.notify(eventname, *args, **kwargs) - def notify_exception(self, excinfo=None): if excinfo is None: excinfo = py.code.ExceptionInfo() From hpk at codespeak.net Thu Apr 9 02:45:47 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 02:45:47 +0200 (CEST) Subject: [py-svn] r63889 - in py/trunk/py: execnet/testing test/dist/testing test/plugin test/testing Message-ID: <20090409004547.420371684CA@codespeak.net> Author: hpk Date: Thu Apr 9 02:45:46 2009 New Revision: 63889 Modified: py/trunk/py/execnet/testing/test_event.py py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_doctest.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/testing/test_genitems.py py/trunk/py/test/testing/test_pycollect.py Log: renaming/streamlining missing event usage Modified: py/trunk/py/execnet/testing/test_event.py ============================================================================== --- py/trunk/py/execnet/testing/test_event.py (original) +++ py/trunk/py/execnet/testing/test_event.py Thu Apr 9 02:45:46 2009 @@ -3,7 +3,7 @@ from py.__.execnet.gateway import ExecnetAPI class TestExecnetEvents: - def test_popengateway_events(self, _pytest): + def test_popengateway(self, _pytest): rec = _pytest.getcallrecorder(ExecnetAPI) gw = py.execnet.PopenGateway() call = rec.popcall("pyexecnet_gateway_init") Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Thu Apr 9 02:45:46 2009 @@ -254,7 +254,7 @@ assert loopstate.testsfailed assert loopstate.shuttingdown - def test_shuttingdown_filters_events(self, testdir): + def test_shuttingdown_filters(self, testdir): item = testdir.getitem("def test_func(): pass") session = DSession(item.config) node = MockNode() Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Thu Apr 9 02:45:46 2009 @@ -1,5 +1,5 @@ """ -API definitions for pytest plugin hooks and events +API definitions for pytest plugin hooks """ class PluginHooks: @@ -68,6 +68,7 @@ """ return processed content for a given doctest""" pytest_doctest_prepare_content.firstresult = True + def pytest_itemstart(self, item, node=None): """ test item gets collected. """ Modified: py/trunk/py/test/plugin/pytest_doctest.py ============================================================================== --- py/trunk/py/test/plugin/pytest_doctest.py (original) +++ py/trunk/py/test/plugin/pytest_doctest.py Thu Apr 9 02:45:46 2009 @@ -87,8 +87,7 @@ """) for x in (testdir.tmpdir, checkfile): #print "checking that %s returns custom items" % (x,) - items, events = testdir.inline_genitems(x) - print events.events + items, sorter = testdir.inline_genitems(x) assert len(items) == 1 assert isinstance(items[0], DoctestTextfile) @@ -97,7 +96,6 @@ path = testdir.makepyfile(whatever="#") for p in (path, testdir.tmpdir): items, evrec = testdir.inline_genitems(p, '--doctest-modules') - print evrec.events assert len(items) == 1 assert isinstance(items[0], DoctestModule) Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Thu Apr 9 02:45:46 2009 @@ -280,19 +280,10 @@ class EventRecorder(object): def __init__(self, pyplugins, debug=False): # True): - self.events = [] self.pyplugins = pyplugins self.debug = debug pyplugins.register(self) - def popcall(self, name): - for i, event in py.builtin.enumerate(self.events): - if event.name == name: - del self.events[i] - eventparser = self._getcallparser(name) - return eventparser(*event.args, **event.kwargs) - raise KeyError("popevent: %r not found in %r" %(name, self.events)) - def getcall(self, name): l = self.getcalls(name) assert len(l) == 1, (name, l) @@ -308,26 +299,8 @@ name = "pytest_" + name if self.callrecorder.recordsmethod(name): l.extend(self.callrecorder.getcalls(name)) - else: - for event in self.events: - if event.name == name: - method = self._getcallparser(event.name) - pevent = method(*event.args, **event.kwargs) - l.append(pevent) return l - def _getcallparser(self, eventname): - mname = "pyevent__" + eventname - method = getattr(api.Events, mname) - args, varargs, varkw, default = inspect.getargspec(method) - assert args[0] == "self" - args = args[1:] - fspec = inspect.formatargspec(args, varargs, varkw, default) - code = py.code.compile("""def %(mname)s%(fspec)s: - return ParsedCall(locals())""" % locals()) - exec code - return locals()[mname] - # functionality for test reports def getreports(self, names="itemtestreport collectreport"): @@ -384,7 +357,6 @@ assert failed == len(realfailed) def clear(self): - self.events[:] = [] self.callrecorder.calls[:] = [] def unregister(self): @@ -424,7 +396,6 @@ recorder.unregister() recorder.clear() - assert not recorder.events assert not recorder.getfailures() bus.call_each("pytest_itemtestreport", rep=rep) assert not recorder.getfailures() Modified: py/trunk/py/test/testing/test_genitems.py ============================================================================== --- py/trunk/py/test/testing/test_genitems.py (original) +++ py/trunk/py/test/testing/test_genitems.py Thu Apr 9 02:45:46 2009 @@ -10,7 +10,7 @@ pass """) p.copy(p.dirpath(p.purebasename + "2" + ".py")) - items, events = testdir.inline_genitems(p.dirpath()) + items, sorter = testdir.inline_genitems(p.dirpath()) assert len(items) == 4 for numi, i in enumerate(items): for numj, j in enumerate(items): @@ -27,8 +27,8 @@ def test_subdir_conftest_error(self, testdir): tmp = testdir.tmpdir tmp.ensure("sub", "conftest.py").write("raise SyntaxError\n") - items, events = testdir.inline_genitems(tmp) - collectionfailures = events.getfailedcollections() + items, sorter = testdir.inline_genitems(tmp) + collectionfailures = sorter.getfailedcollections() assert len(collectionfailures) == 1 ev = collectionfailures[0] assert ev.longrepr.reprcrash.message.startswith("SyntaxError") @@ -45,7 +45,7 @@ class TestY(TestX): pass ''') - items, events = testdir.inline_genitems(p) + items, sorter = testdir.inline_genitems(p) assert len(items) == 3 assert items[0].name == 'testone' assert items[1].name == 'testmethod_one' Modified: py/trunk/py/test/testing/test_pycollect.py ============================================================================== --- py/trunk/py/test/testing/test_pycollect.py (original) +++ py/trunk/py/test/testing/test_pycollect.py Thu Apr 9 02:45:46 2009 @@ -216,7 +216,7 @@ yield list_append_2 yield assert_order_of_execution """) - sorter = testdir.inline_run(o) # .events_from_cmdline([o]) + sorter = testdir.inline_run(o) passed, skipped, failed = sorter.countoutcomes() assert passed == 4 assert not skipped and not failed From hpk at codespeak.net Thu Apr 9 02:53:46 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 02:53:46 +0200 (CEST) Subject: [py-svn] r63890 - py/trunk/py/test/plugin Message-ID: <20090409005346.243C21684F0@codespeak.net> Author: hpk Date: Thu Apr 9 02:53:45 2009 New Revision: 63890 Modified: py/trunk/py/test/plugin/api.py Log: group hooks Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Thu Apr 9 02:53:45 2009 @@ -37,38 +37,30 @@ """ return custom item/collector for a python object in a module, or None. """ pytest_pymodule_makeitem.firstresult = True - def pytest_itemrun(self, item, pdb=None): - """ run given test item and return test report. """ + def pytest_collectstart(self, collector): + """ collector starts collecting. """ + + def pytest_collectreport(self, rep): + """ collector finished collecting. """ - def pytest_item_runtest_finished(self, item, excinfo, outerr): - """ called in-process after runtest() returned. """ - # ------------------------------------------------------------------------------ # runtest related hooks # ------------------------------------------------------------------------------ + # + def pytest_itemrun(self, item, pdb=None): + """ run given test item and return test report. """ + def pytest_item_runtest_finished(self, item, excinfo, outerr): + """ called in-process after runtest() returned. """ + def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): """ return True if we consumed/did the call to the python function item. """ def pytest_item_makereport(self, item, excinfo, when, outerr): - """ return ItemTestReport event for the given test outcome. """ + """ return ItemTestReport for the given test outcome. """ pytest_item_makereport.firstresult = True - # reporting hooks (invoked from pytest_terminal.py) - def pytest_report_teststatus(self, rep): - """ return shortletter and verbose word. """ - pytest_report_teststatus.firstresult = True - - def pytest_terminal_summary(self, terminalreporter): - """ add additional section in terminal summary reporting. """ - - # doctest hooks (invoked from pytest_terminal.py) - def pytest_doctest_prepare_content(self, content): - """ return processed content for a given doctest""" - pytest_doctest_prepare_content.firstresult = True - - def pytest_itemstart(self, item, node=None): """ test item gets collected. """ @@ -81,11 +73,26 @@ def pytest_itemsetupreport(self, rep): """ a report on running a fixture function. """ - def pytest_collectstart(self, collector): - """ collector starts collecting. """ + # ------------------------------------------------------------------------------ + # reporting hooks (invoked from pytest_terminal.py) + # ------------------------------------------------------------------------------ + def pytest_report_teststatus(self, rep): + """ return shortletter and verbose word. """ + pytest_report_teststatus.firstresult = True - def pytest_collectreport(self, rep): - """ collector finished collecting. """ + def pytest_terminal_summary(self, terminalreporter): + """ add additional section in terminal summary reporting. """ + + # ------------------------------------------------------------------------------ + # doctest hooks + # ------------------------------------------------------------------------------ + def pytest_doctest_prepare_content(self, content): + """ return processed content for a given doctest""" + pytest_doctest_prepare_content.firstresult = True + + # ------------------------------------------------------------------------------ + # misc hooks + # ------------------------------------------------------------------------------ def pytest_plugin_registered(self, plugin): """ a new py lib plugin got registered. """ @@ -118,5 +125,5 @@ """ called for internal errors. """ def pytest_trace(self, category, msg): - """ called for tracing events. """ + """ called for debug info. """ From hpk at codespeak.net Thu Apr 9 15:12:37 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 15:12:37 +0200 (CEST) Subject: [py-svn] r63891 - py/release/1.0.0b1 Message-ID: <20090409131237.1A09716855F@codespeak.net> Author: hpk Date: Thu Apr 9 15:12:36 2009 New Revision: 63891 Added: py/release/1.0.0b1/ - copied from r63360, py/trunk/ Log: adding what got released as 1.0.0b1 From hpk at codespeak.net Thu Apr 9 16:03:21 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 16:03:21 +0200 (CEST) Subject: [py-svn] r63894 - in py/trunk/py: . misc misc/testing test test/dist test/dist/testing test/looponfail test/plugin test/testing Message-ID: <20090409140321.06DEA168565@codespeak.net> Author: hpk Date: Thu Apr 9 16:03:09 2009 New Revision: 63894 Modified: py/trunk/py/__init__.py py/trunk/py/_com.py py/trunk/py/misc/testing/test_com.py py/trunk/py/misc/testing/test_warn.py py/trunk/py/misc/warn.py py/trunk/py/test/cmdline.py py/trunk/py/test/collect.py py/trunk/py/test/config.py py/trunk/py/test/dist/dsession.py py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/dist/testing/test_nodemanage.py py/trunk/py/test/dist/testing/test_txnode.py py/trunk/py/test/dist/txnode.py py/trunk/py/test/looponfail/remote.py py/trunk/py/test/plugin/pytest__pytest.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_resultdb.py py/trunk/py/test/plugin/pytest_resultlog.py py/trunk/py/test/plugin/pytest_runner.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/pycollect.py py/trunk/py/test/pytestplugin.py py/trunk/py/test/session.py py/trunk/py/test/testing/test_collect.py py/trunk/py/test/testing/test_config.py py/trunk/py/test/testing/test_pickling.py py/trunk/py/test/testing/test_pycollect.py py/trunk/py/test/testing/test_pytestplugin.py Log: grand renaming on plugin-related mostly internal objects. Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Thu Apr 9 16:03:09 2009 @@ -54,9 +54,9 @@ exportdefs = { # py lib events and plugins - '_com.PyPlugins' : ('./_com.py', 'PyPlugins'), + '_com.Registry' : ('./_com.py', 'Registry'), '_com.MultiCall' : ('./_com.py', 'MultiCall'), - '_com.pyplugins' : ('./_com.py', 'pyplugins'), + '_com.comregistry' : ('./_com.py', 'comregistry'), '_com.PluginAPI' : ('./_com.py', 'PluginAPI'), # py lib cmdline tools @@ -70,7 +70,7 @@ # helpers for use from test functions or collectors 'test.__doc__' : ('./test/__init__.py', '__doc__'), - 'test._PytestPlugins' : ('./test/pytestplugin.py', 'PytestPlugins'), + 'test._PluginManager' : ('./test/pytestplugin.py', 'PluginManager'), 'test.raises' : ('./test/outcome.py', 'raises'), 'test.mark' : ('./test/outcome.py', 'mark',), 'test.deprecated_call' : ('./test/outcome.py', 'deprecated_call'), @@ -199,6 +199,6 @@ }) import py -py._com.pyplugins.consider_env() +py._com.comregistry.consider_env() Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Thu Apr 9 16:03:09 2009 @@ -62,7 +62,7 @@ self._ex1 = True -class PyPlugins: +class Registry: """ Manage Plugins: Load plugins and manage calls to plugins. """ @@ -71,7 +71,7 @@ def __init__(self, plugins=None): if plugins is None: plugins = [] - self._plugins = plugins + self.plugins = plugins def import_module(self, modspec): # XXX allow modspec to specify version / lookup @@ -99,22 +99,22 @@ def register(self, plugin): assert not isinstance(plugin, str) self.call_each("pytest_plugin_registered", plugin) - self._plugins.append(plugin) + self.plugins.append(plugin) def unregister(self, plugin): self.call_each("pytest_plugin_unregistered", plugin) - self._plugins.remove(plugin) + self.plugins.remove(plugin) def getplugins(self): - return list(self._plugins) + return list(self.plugins) def isregistered(self, plugin): - return plugin in self._plugins + return plugin in self.plugins def listattr(self, attrname, plugins=None, extra=(), reverse=False): l = [] if plugins is None: - plugins = self._plugins + plugins = self.plugins if extra: plugins += list(extra) for plugin in plugins: @@ -143,15 +143,15 @@ def __init__(self, apiclass, plugins=None): self._apiclass = apiclass if plugins is None: - plugins = pyplugins - self._plugins = plugins + plugins = comregistry + self.plugins = plugins for name, method in vars(apiclass).items(): if name[:2] != "__": firstresult = getattr(method, 'firstresult', False) mm = ApiCall(plugins, name, firstresult=firstresult) setattr(self, name, mm) def __repr__(self): - return "" %(self._apiclass, self._plugins) + return "" %(self._apiclass, self.plugins) class ApiCall: def __init__(self, plugins, name, firstresult): @@ -168,4 +168,4 @@ #print "making multicall", self return mc.execute(firstresult=self.firstresult) -pyplugins = PyPlugins() +comregistry = Registry() Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Thu Apr 9 16:03:09 2009 @@ -1,7 +1,7 @@ import py import os -from py._com import PyPlugins, MultiCall +from py._com import Registry, MultiCall from py._com import PluginAPI pytest_plugins = "xfail" @@ -75,13 +75,13 @@ #assert res == 10 -class TestPyPlugins: +class TestRegistry: def test_MultiCall(self): - plugins = PyPlugins() + plugins = Registry() assert hasattr(plugins, "MultiCall") def test_register(self): - plugins = PyPlugins() + plugins = Registry() class MyPlugin: pass my = MyPlugin() @@ -98,7 +98,7 @@ assert plugins.getplugins() == [my2] def test_call_methods(self): - plugins = PyPlugins() + plugins = Registry() class api1: def m(self, __call__, x): return x @@ -121,7 +121,7 @@ assert plugins.call_plugin(api2(), 't') is None def test_call_none_is_no_result(self): - plugins = PyPlugins() + plugins = Registry() class api1: def m(self): return None @@ -135,7 +135,7 @@ assert plugins.call_each('m') == [41] def test_call_noneasresult(self): - plugins = PyPlugins() + plugins = Registry() class api1: def m(self, __call__): return __call__.NONEASRESULT @@ -145,7 +145,7 @@ assert plugins.call_each('m') == [None, None] def test_listattr(self): - plugins = PyPlugins() + plugins = Registry() class api1: x = 41 class api2: @@ -161,12 +161,12 @@ assert l == [43, 42, 41] def test_consider_env(self, monkeypatch): - plugins = PyPlugins() + plugins = Registry() monkeypatch.setitem(os.environ, 'PYLIB', "unknownconsider_env") py.test.raises(ImportError, "plugins.consider_env()") def test_consider_module(self): - plugins = PyPlugins() + plugins = Registry() mod = py.std.new.module("temp") mod.pylib = ["xxx nomod"] excinfo = py.test.raises(ImportError, "plugins.consider_module(mod)") @@ -174,10 +174,10 @@ plugins.consider_module(mod) def test_api_and_defaults(): - assert isinstance(py._com.pyplugins, PyPlugins) + assert isinstance(py._com.comregistry, Registry) def test_subprocess_env(testdir, monkeypatch): - plugins = PyPlugins() + plugins = Registry() old = py.path.local(py.__file__).dirpath().dirpath().chdir() try: monkeypatch.setitem(os.environ, "PYLIB", 'unknownconsider') @@ -191,7 +191,7 @@ class TestPluginAPI: def test_happypath(self): - plugins = PyPlugins() + plugins = Registry() class Api: def hello(self, arg): pass @@ -208,7 +208,7 @@ assert not hasattr(mcm, 'world') def test_firstresult(self): - plugins = PyPlugins() + plugins = Registry() class Api: def hello(self, arg): pass hello.firstresult = True @@ -224,4 +224,4 @@ def test_default_plugins(self): class Api: pass mcm = PluginAPI(apiclass=Api) - assert mcm._plugins == py._com.pyplugins + assert mcm.plugins == py._com.comregistry Modified: py/trunk/py/misc/testing/test_warn.py ============================================================================== --- py/trunk/py/misc/testing/test_warn.py (original) +++ py/trunk/py/misc/testing/test_warn.py Thu Apr 9 16:03:09 2009 @@ -4,9 +4,9 @@ class TestWarningPlugin: def setup_method(self, method): - self.bus = py._com.PyPlugins() - self.wb = WarningPlugin(self.bus) - self.bus.register(self) + self.pluginmanager = py._com.Registry() + self.wb = WarningPlugin(self.pluginmanager) + self.pluginmanager.register(self) self.warnings = [] def pyevent__WARNING(self, warning): @@ -46,4 +46,4 @@ def test_default(): from py.__.misc.warn import APIWARN - assert py._com.pyplugins.isregistered(APIWARN.im_self) + assert py._com.comregistry.isregistered(APIWARN.im_self) Modified: py/trunk/py/misc/warn.py ============================================================================== --- py/trunk/py/misc/warn.py (original) +++ py/trunk/py/misc/warn.py Thu Apr 9 16:03:09 2009 @@ -10,12 +10,12 @@ def __str__(self): return self.msg -# XXX probably only apiwarn() + py._com.pyplugins forwarding +# XXX probably only apiwarn() + py._com.comregistry forwarding # warn_explicit is actually needed class WarningPlugin(object): def __init__(self, bus): - self.bus = bus + self.pluginmanager = bus bus.register(self) def pyevent__WARNING(self, warning): @@ -62,8 +62,8 @@ filename = module path = py.path.local(filename) warning = Warning(msg, path, lineno) - self.bus.call_each("pyevent__WARNING", warning) + self.pluginmanager.call_each("pyevent__WARNING", warning) # singleton api warner for py lib -apiwarner = WarningPlugin(py._com.pyplugins) +apiwarner = WarningPlugin(py._com.comregistry) APIWARN = apiwarner.apiwarn Modified: py/trunk/py/test/cmdline.py ============================================================================== --- py/trunk/py/test/cmdline.py (original) +++ py/trunk/py/test/cmdline.py Thu Apr 9 16:03:09 2009 @@ -11,10 +11,10 @@ config = py.test.config try: config.parse(args) - config.pytestplugins.do_configure(config) + config.pluginmanager.do_configure(config) session = config.initsession() exitstatus = session.main() - config.pytestplugins.do_unconfigure(config) + config.pluginmanager.do_unconfigure(config) raise SystemExit(exitstatus) except config.Error, e: py.std.sys.stderr.write("ERROR: %s\n" %(e.args[0],)) Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Thu Apr 9 16:03:09 2009 @@ -443,7 +443,7 @@ def consider_dir(self, path, usefilters=None): if usefilters is not None: APIWARN("0.99", "usefilters argument not needed") - res = self.config.pytestplugins.call_firstresult( + res = self.config.pluginmanager.call_firstresult( 'pytest_collect_recurse', path=path, parent=self) if res is None or res: return self.config.api.pytest_collect_directory( Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Thu Apr 9 16:03:09 2009 @@ -28,25 +28,24 @@ basetemp = None _sessionclass = None - def __init__(self, pytestplugins=None, topdir=None): + def __init__(self, pluginmanager=None, topdir=None): self.option = CmdOptions() self.topdir = topdir self._parser = parseopt.Parser( usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]", processopt=self._processopt, ) - if pytestplugins is None: - pytestplugins = py.test._PytestPlugins() - assert isinstance(pytestplugins, py.test._PytestPlugins) - self.bus = pytestplugins.pyplugins - self.pytestplugins = pytestplugins + if pluginmanager is None: + pluginmanager = py.test._PluginManager() + assert isinstance(pluginmanager, py.test._PluginManager) + self.pluginmanager = pluginmanager self._conftest = Conftest(onimport=self._onimportconftest) self._setupstate = SetupState() - self.api = pytestplugins.api + self.api = pluginmanager.api def _onimportconftest(self, conftestmodule): self.trace("loaded conftestmodule %r" %(conftestmodule,)) - self.pytestplugins.consider_conftest(conftestmodule) + self.pluginmanager.consider_conftest(conftestmodule) def trace(self, msg): if getattr(self.option, 'traceconfig', None): @@ -76,8 +75,8 @@ def _preparse(self, args): self._conftest.setinitial(args) - self.pytestplugins.consider_env() - self.pytestplugins.do_addoption(self._parser) + self.pluginmanager.consider_env() + self.pluginmanager.do_addoption(self._parser) def parse(self, args): """ parse cmdline arguments into this config object. @@ -108,7 +107,7 @@ # * registering to py lib plugins # * setting py.test.config self.__init__( - pytestplugins=py.test._PytestPlugins(py._com.pyplugins), + pluginmanager=py.test._PluginManager(py._com.comregistry), topdir=py.path.local(), ) # we have to set py.test.config because preparse() @@ -339,6 +338,6 @@ # this is the one per-process instance of py.test configuration config_per_process = Config( - pytestplugins=py.test._PytestPlugins(py._com.pyplugins) + pluginmanager=py.test._PluginManager(py._com.comregistry) ) Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Thu Apr 9 16:03:09 2009 @@ -121,11 +121,11 @@ loopstate.exitstatus = outcome.EXIT_TESTSFAILED else: loopstate.exitstatus = outcome.EXIT_OK - #self.config.bus.unregister(loopstate) + #self.config.pluginmanager.unregister(loopstate) def _initloopstate(self, colitems): loopstate = LoopState(self, colitems) - self.config.bus.register(loopstate) + self.config.pluginmanager.register(loopstate) return loopstate def loop(self, colitems): @@ -140,9 +140,9 @@ except KeyboardInterrupt: exitstatus = outcome.EXIT_INTERRUPTED except: - self.config.pytestplugins.notify_exception() + self.config.pluginmanager.notify_exception() exitstatus = outcome.EXIT_INTERNALERROR - self.config.bus.unregister(loopstate) + self.config.pluginmanager.unregister(loopstate) if exitstatus == 0 and self._testsfailed: exitstatus = outcome.EXIT_TESTSFAILED return exitstatus Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Thu Apr 9 16:03:09 2009 @@ -174,7 +174,7 @@ session.senditems_load([item1, item2]) node = session.item2nodes[item1] [0] session.queueevent("pytest_testnodedown", node, None) - evrec = testdir.geteventrecorder(session.bus) + evrec = testdir.geteventrecorder(session.pluginmanager) print session.item2nodes loopstate = session._initloopstate([]) session.loop_once(loopstate) @@ -261,7 +261,7 @@ session.addnode(node) loopstate = session._initloopstate([]) loopstate.shuttingdown = True - evrec = testdir.geteventrecorder(session.bus) + evrec = testdir.geteventrecorder(session.pluginmanager) session.queueevent("pytest_itemtestreport", rep=run(item, node)) session.loop_once(loopstate) assert not evrec.getcalls("pytest_testnodedown") @@ -282,7 +282,7 @@ dsel = session.filteritems([modcol]) assert dsel == [modcol] items = modcol.collect() - callrecorder = testdir.geteventrecorder(session.bus).callrecorder + callrecorder = testdir.geteventrecorder(session.pluginmanager).callrecorder remaining = session.filteritems(items) assert remaining == [] @@ -355,7 +355,7 @@ """) config = testdir.parseconfig('-d', p1, '--tx=popen') dsession = DSession(config) - callrecorder = testdir.geteventrecorder(config.bus).callrecorder + callrecorder = testdir.geteventrecorder(config.pluginmanager).callrecorder dsession.main([config.getfsnode(p1)]) rep = callrecorder.popcall("pytest_itemtestreport").rep assert rep.passed Modified: py/trunk/py/test/dist/testing/test_nodemanage.py ============================================================================== --- py/trunk/py/test/dist/testing/test_nodemanage.py (original) +++ py/trunk/py/test/dist/testing/test_nodemanage.py Thu Apr 9 16:03:09 2009 @@ -104,7 +104,7 @@ config = py.test.config._reparse([source, '--debug']) assert config.option.debug nodemanager = NodeManager(config, specs) - sorter = testdir.geteventrecorder(config.bus).callrecorder + sorter = testdir.geteventrecorder(config.pluginmanager).callrecorder nodemanager.setup_nodes(putevent=[].append) for spec in nodemanager.gwmanager.specs: l = sorter.getcalls("pytest_trace") Modified: py/trunk/py/test/dist/testing/test_txnode.py ============================================================================== --- py/trunk/py/test/dist/testing/test_txnode.py (original) +++ py/trunk/py/test/dist/testing/test_txnode.py Thu Apr 9 16:03:09 2009 @@ -35,7 +35,7 @@ self.pyfuncitem = pyfuncitem def geteventargs(self, eventname, timeout=2.0): - eq = EventQueue(self.config.bus, self.queue) + eq = EventQueue(self.config.pluginmanager, self.queue) return eq.geteventargs(eventname, timeout=timeout) def makenode(self, config=None): Modified: py/trunk/py/test/dist/txnode.py ============================================================================== --- py/trunk/py/test/dist/txnode.py (original) +++ py/trunk/py/test/dist/txnode.py Thu Apr 9 16:03:09 2009 @@ -61,7 +61,7 @@ except: excinfo = py.code.ExceptionInfo() print "!" * 20, excinfo - self.config.pytestplugins.notify_exception(excinfo) + self.config.pluginmanager.notify_exception(excinfo) def send(self, item): assert item is not None @@ -112,8 +112,8 @@ self.config, basetemp = channel.receive() if basetemp: self.config.basetemp = py.path.local(basetemp) - self.config.pytestplugins.do_configure(self.config) - self.config.pytestplugins.register(self) + self.config.pluginmanager.do_configure(self.config) + self.config.pluginmanager.register(self) self.sendevent("slaveready") try: while 1: @@ -123,9 +123,9 @@ break if isinstance(task, list): for item in task: - item.config.pytestplugins.do_itemrun(item) + item.config.pluginmanager.do_itemrun(item) else: - task.config.pytestplugins.do_itemrun(item=task) + task.config.pluginmanager.do_itemrun(item=task) except KeyboardInterrupt: raise except: Modified: py/trunk/py/test/looponfail/remote.py ============================================================================== --- py/trunk/py/test/looponfail/remote.py (original) +++ py/trunk/py/test/looponfail/remote.py Thu Apr 9 16:03:09 2009 @@ -116,7 +116,7 @@ config.option.looponfail = False config.option.usepdb = False trails = channel.receive() - config.pytestplugins.do_configure(config) + config.pluginmanager.do_configure(config) DEBUG("SLAVE: initsession()") session = config.initsession() # XXX configure the reporter object's terminal writer more directly @@ -143,7 +143,7 @@ pytest_collectreport = pytest_itemtestreport failreports = Failures() - session.bus.register(failreports) + session.pluginmanager.register(failreports) DEBUG("SLAVE: starting session.main()") session.main(colitems) Modified: py/trunk/py/test/plugin/pytest__pytest.py ============================================================================== --- py/trunk/py/test/plugin/pytest__pytest.py (original) +++ py/trunk/py/test/plugin/pytest__pytest.py Thu Apr 9 16:03:09 2009 @@ -8,10 +8,10 @@ def __init__(self, pyfuncitem): self.pyfuncitem = pyfuncitem - def getcallrecorder(self, apiclass, pyplugins=None): - if pyplugins is None: - pyplugins = self.pyfuncitem.config.pytestplugins.pyplugins - callrecorder = CallRecorder(pyplugins) + def getcallrecorder(self, apiclass, comregistry=None): + if comregistry is None: + comregistry = self.pyfuncitem.config.pluginmanager.comregistry + callrecorder = CallRecorder(comregistry) callrecorder.start_recording(apiclass) self.pyfuncitem.addfinalizer(callrecorder.finalize) return callrecorder @@ -30,8 +30,8 @@ return "" %(self._name, d) class CallRecorder: - def __init__(self, pyplugins): - self._pyplugins = pyplugins + def __init__(self, comregistry): + self._comregistry = comregistry self.calls = [] self._recorders = {} @@ -44,11 +44,11 @@ setattr(RecordCalls, name, self._getcallparser(method)) recorder = RecordCalls() self._recorders[apiclass] = recorder - self._pyplugins.register(recorder) + self._comregistry.register(recorder) def finalize(self): for recorder in self._recorders.values(): - self._pyplugins.unregister(recorder) + self._comregistry.unregister(recorder) self._recorders.clear() def recordsmethod(self, name): @@ -99,13 +99,13 @@ plugintester.apicheck(_pytestPlugin) def test_callrecorder_basic(): - pyplugins = py._com.PyPlugins() - rec = CallRecorder(pyplugins) + comregistry = py._com.Registry() + rec = CallRecorder(comregistry) class ApiClass: def xyz(self, arg): pass rec.start_recording(ApiClass) - pyplugins.call_each("xyz", 123) + comregistry.call_each("xyz", 123) call = rec.popcall("xyz") assert call.arg == 123 assert call._name == "xyz" @@ -122,8 +122,8 @@ class Plugin: def xyz(self, arg): return arg + 1 - rec._pyplugins.register(Plugin()) - res = rec._pyplugins.call_firstresult("xyz", 41) + rec._comregistry.register(Plugin()) + res = rec._comregistry.call_firstresult("xyz", 41) assert res == 42 """) sorter.assertoutcome(passed=1) Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Thu Apr 9 16:03:09 2009 @@ -158,7 +158,7 @@ def loadplugins(self, config): for name in config.getvalue("plugin"): print "importing", name - config.pytestplugins.import_plugin(name) + config.pluginmanager.import_plugin(name) def setsession(self, config): val = config.getvalue @@ -177,7 +177,7 @@ def x(*args): config = py.test.config._reparse([tmpdir] + list(args)) try: - config.pytestplugins.do_configure(config) + config.pluginmanager.do_configure(config) except ValueError: return Exception return getattr(config._sessionclass, '__name__', None) @@ -194,13 +194,13 @@ testdir.chdir() config = testdir.parseconfig("-p", "nqweotexistent") py.test.raises(ImportError, - "config.pytestplugins.do_configure(config)" + "config.pluginmanager.do_configure(config)" ) def test_plugin_already_exists(testdir): config = testdir.parseconfig("-p", "default") assert config.option.plugin == ['default'] - config.pytestplugins.do_configure(config) + config.pluginmanager.do_configure(config) class TestDistOptions: Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Thu Apr 9 16:03:09 2009 @@ -27,7 +27,7 @@ class PluginTester(Support): def testdir(self): # XXX import differently, eg. - # FSTester = self.pyfuncitem.config.pytestplugins.getpluginattr("pytester", "FSTester") + # FSTester = self.pyfuncitem.config.pluginmanager.getpluginattr("pytester", "FSTester") from pytest_pytester import TmpTestdir crunner = TmpTestdir(self.pyfuncitem) self.pyfuncitem.addfinalizer(crunner.finalize) @@ -42,7 +42,7 @@ def apicheck(self, pluginclass): print "loading and checking", pluginclass fail = False - pm = py.test._PytestPlugins() + pm = py.test._PluginManager() methods = collectattr(pluginclass) hooks = collectattr(api.PluginHooks) getargs = py.std.inspect.getargs Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Thu Apr 9 16:03:09 2009 @@ -25,8 +25,8 @@ # return EventRecorder def pytest_funcarg__eventrecorder(self, pyfuncitem): - evrec = EventRecorder(py._com.pyplugins) - pyfuncitem.addfinalizer(lambda: evrec.pyplugins.unregister(evrec)) + evrec = EventRecorder(py._com.comregistry) + pyfuncitem.addfinalizer(lambda: evrec.comregistry.unregister(evrec)) return evrec def test_generic(plugintester): @@ -65,10 +65,10 @@ def __repr__(self): return "" % (self.tmpdir,) - def Config(self, pyplugins=None, topdir=None): + def Config(self, comregistry=None, topdir=None): if topdir is None: topdir = self.tmpdir.dirpath() - return pytestConfig(pyplugins, topdir=topdir) + return pytestConfig(comregistry, topdir=topdir) def finalize(self): for p in self._syspathremove: @@ -132,7 +132,7 @@ #config = self.parseconfig(*args) config = self.parseconfig(*args) session = config.initsession() - rec = self.geteventrecorder(config.bus) + rec = self.geteventrecorder(config.pluginmanager) colitems = [config.getfsnode(arg) for arg in config.args] items = list(session.genitems(colitems)) return items, rec @@ -152,20 +152,20 @@ def inline_run(self, *args): config = self.parseconfig(*args) - config.pytestplugins.do_configure(config) + config.pluginmanager.do_configure(config) session = config.initsession() - sorter = self.geteventrecorder(config.bus) + sorter = self.geteventrecorder(config.pluginmanager) session.main() - config.pytestplugins.do_unconfigure(config) + config.pluginmanager.do_unconfigure(config) return sorter def config_preparse(self): config = self.Config() for plugin in self.plugins: if isinstance(plugin, str): - config.pytestplugins.import_plugin(plugin) + config.pluginmanager.import_plugin(plugin) else: - config.pytestplugins.register(plugin) + config.pluginmanager.register(plugin) return config def parseconfig(self, *args): @@ -178,7 +178,7 @@ def parseconfigure(self, *args): config = self.parseconfig(*args) - config.pytestplugins.do_configure(config) + config.pluginmanager.do_configure(config) return config def getitem(self, source, funcname="test_func"): @@ -279,10 +279,10 @@ return "" %(self.__dict__,) class EventRecorder(object): - def __init__(self, pyplugins, debug=False): # True): - self.pyplugins = pyplugins + def __init__(self, comregistry, debug=False): # True): + self.comregistry = comregistry self.debug = debug - pyplugins.register(self) + comregistry.register(self) def getcall(self, name): l = self.getcalls(name) @@ -360,11 +360,11 @@ self.callrecorder.calls[:] = [] def unregister(self): - self.pyplugins.unregister(self) + self.comregistry.unregister(self) self.callrecorder.finalize() def test_eventrecorder(testdir): - bus = py._com.PyPlugins() + bus = py._com.Registry() recorder = testdir.geteventrecorder(bus) assert not recorder.getfailures() rep = runner.ItemTestReport(None, None) Modified: py/trunk/py/test/plugin/pytest_resultdb.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultdb.py (original) +++ py/trunk/py/test/plugin/pytest_resultdb.py Thu Apr 9 16:03:09 2009 @@ -41,12 +41,12 @@ raise config.Error('Unknown --resultdb_format: %s' % config.option.resultdbformat) - config.bus.register(self.resultdb) + config.pluginmanager.register(self.resultdb) def pytest_unconfigure(self, config): if hasattr(self, 'resultdb'): del self.resultdb - #config.bus.unregister(self.resultdb) + #config.pluginmanager.unregister(self.resultdb) class JSONResultArchive(object): Modified: py/trunk/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultlog.py (original) +++ py/trunk/py/test/plugin/pytest_resultlog.py Thu Apr 9 16:03:09 2009 @@ -14,13 +14,13 @@ if resultlog: logfile = open(resultlog, 'w', 1) # line buffered self.resultlog = ResultLog(logfile) - config.bus.register(self.resultlog) + config.pluginmanager.register(self.resultlog) def pytest_unconfigure(self, config): if hasattr(self, 'resultlog'): self.resultlog.logfile.close() del self.resultlog - #config.bus.unregister(self.resultlog) + #config.pluginmanager.unregister(self.resultlog) def generic_path(item): chain = item.listchain() Modified: py/trunk/py/test/plugin/pytest_runner.py ============================================================================== --- py/trunk/py/test/plugin/pytest_runner.py (original) +++ py/trunk/py/test/plugin/pytest_runner.py Thu Apr 9 16:03:09 2009 @@ -208,7 +208,7 @@ item = testdir.getitem("""def test_func(): pass""") plugin = RunnerPlugin() plugin.pytest_configure(item.config) - sorter = testdir.geteventrecorder(item.config.bus) + sorter = testdir.geteventrecorder(item.config.pluginmanager) plugin.pytest_item_setup_and_runtest(item) rep = sorter.getcall("pytest_itemtestreport").rep assert rep.passed Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Thu Apr 9 16:03:09 2009 @@ -15,7 +15,7 @@ name = attr.split("_")[-1] assert hasattr(self.reporter._tw, name), name setattr(self.reporter._tw, name, getattr(config, attr)) - config.bus.register(self.reporter) + config.pluginmanager.register(self.reporter) class TerminalReporter: def __init__(self, config, file=None): @@ -209,7 +209,7 @@ py.path.local(py.__file__).dirpath(), rev)) if self.config.option.traceconfig: plugins = [] - for x in self.config.pytestplugins._plugins: + for x in self.config.pluginmanager.plugins: if isinstance(x, str) and x.startswith("pytest_"): plugins.append(x[7:]) else: @@ -368,7 +368,7 @@ assert 0 """) rep = TerminalReporter(modcol.config, file=linecomp.stringio) - rep.config.bus.register(rep) + rep.config.pluginmanager.register(rep) rep.config.api.pytest_testrunstart() for item in testdir.genitems([modcol]): @@ -395,7 +395,7 @@ assert 0 """, configargs=("-v",)) rep = TerminalReporter(modcol.config, file=linecomp.stringio) - rep.config.bus.register(rep) + rep.config.pluginmanager.register(rep) rep.config.api.pytest_testrunstart() items = modcol.collect() rep.config.option.debug = True # @@ -420,7 +420,7 @@ def test_collect_fail(self, testdir, linecomp): modcol = testdir.getmodulecol("import xyz") rep = TerminalReporter(modcol.config, file=linecomp.stringio) - rep.config.bus.register(rep) + rep.config.pluginmanager.register(rep) rep.config.api.pytest_testrunstart() l = list(testdir.genitems([modcol])) assert len(l) == 0 @@ -516,7 +516,7 @@ g() # --calling-- """, configargs=("--tb=%s" % tbopt,)) rep = TerminalReporter(modcol.config, file=linecomp.stringio) - rep.config.bus.register(rep) + rep.config.pluginmanager.register(rep) rep.config.api.pytest_testrunstart() for item in testdir.genitems([modcol]): rep.config.api.pytest_itemtestreport( @@ -543,7 +543,7 @@ pass """) rep = TerminalReporter(modcol.config, file=linecomp.stringio) - modcol.config.bus.register(rep) + modcol.config.pluginmanager.register(rep) l = list(testdir.genitems([modcol])) assert len(l) == 1 modcol.config.option.debug = True @@ -563,8 +563,8 @@ """, configargs=("-v",)*verbose) #""", configargs=("--showskipsummary",) + ("-v",)*verbose) rep = TerminalReporter(modcol.config, file=linecomp.stringio) - modcol.config.bus.register(rep) - bus = modcol.config.bus + modcol.config.pluginmanager.register(rep) + bus = modcol.config.pluginmanager modcol.config.api.pytest_testrunstart() try: for item in testdir.genitems([modcol]): @@ -625,7 +625,7 @@ pass """) rep = CollectonlyReporter(modcol.config, out=linecomp.stringio) - modcol.config.bus.register(rep) + modcol.config.pluginmanager.register(rep) indent = rep.indent rep.config.api.pytest_collectstart(collector=modcol) linecomp.assert_contains_lines([ @@ -646,7 +646,7 @@ py.test.skip("nomod") """) rep = CollectonlyReporter(modcol.config, out=linecomp.stringio) - modcol.config.bus.register(rep) + modcol.config.pluginmanager.register(rep) cols = list(testdir.genitems([modcol])) assert len(cols) == 0 linecomp.assert_contains_lines(""" @@ -659,7 +659,7 @@ raise ValueError(0) """) rep = CollectonlyReporter(modcol.config, out=linecomp.stringio) - modcol.config.bus.register(rep) + modcol.config.pluginmanager.register(rep) cols = list(testdir.genitems([modcol])) assert len(cols) == 0 linecomp.assert_contains_lines(""" Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Thu Apr 9 16:03:09 2009 @@ -167,7 +167,7 @@ # we assume we are only called once per module mod = self.fspath.pyimport() #print "imported test module", mod - self.config.pytestplugins.consider_module(mod) + self.config.pluginmanager.consider_module(mod) return mod def setup(self): @@ -177,7 +177,7 @@ #print "*" * 20, "INVOKE assertion", self py.magic.invoke(assertion=1) mod = self.obj - self.config.pytestplugins.register(mod) + self.config.pluginmanager.register(mod) if hasattr(mod, 'setup_module'): self.obj.setup_module(mod) @@ -187,7 +187,7 @@ if not self.config.option.nomagic: #print "*" * 20, "revoke assertion", self py.magic.revoke(assertion=1) - self.config.pytestplugins.unregister(self.obj) + self.config.pluginmanager.unregister(self.obj) class Class(PyCollectorMixin, py.test.collect.Collector): @@ -376,8 +376,8 @@ def lookup_onearg(self, argname): prefix = "pytest_funcarg__" - #makerlist = self.config.pytestplugins.listattr(prefix + argname) - value = self.config.pytestplugins.call_firstresult(prefix + argname, pyfuncitem=self) + #makerlist = self.config.pluginmanager.listattr(prefix + argname) + value = self.config.pluginmanager.call_firstresult(prefix + argname, pyfuncitem=self) if value is not None: return value else: @@ -386,8 +386,8 @@ def _raisefuncargerror(self, argname, prefix="pytest_funcarg__"): metainfo = self.repr_metainfo() available = [] - plugins = self.config.pytestplugins._plugins.values() - plugins.extend(self.config.pytestplugins.pyplugins._plugins) + plugins = self.config.pluginmanager.plugins.values() + plugins.extend(self.config.pluginmanager.comregistry.plugins) for plugin in plugins: for name in vars(plugin.__class__): if name.startswith(prefix): Modified: py/trunk/py/test/pytestplugin.py ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pytestplugin.py Thu Apr 9 16:03:09 2009 @@ -4,42 +4,42 @@ import py from py.__.test.plugin import api -class PytestPlugins(object): - def __init__(self, pyplugins=None): - if pyplugins is None: - pyplugins = py._com.PyPlugins() - self.pyplugins = pyplugins - self.MultiCall = self.pyplugins.MultiCall - self._plugins = {} +class PluginManager(object): + def __init__(self, comregistry=None): + if comregistry is None: + comregistry = py._com.Registry() + self.comregistry = comregistry + self.MultiCall = self.comregistry.MultiCall + self.plugins = {} self.api = py._com.PluginAPI( apiclass=api.PluginHooks, - plugins=self.pyplugins) + plugins=self.comregistry) def register(self, plugin): - self.pyplugins.register(plugin) + self.comregistry.register(plugin) def unregister(self, plugin): - self.pyplugins.unregister(plugin) + self.comregistry.unregister(plugin) def isregistered(self, plugin): - return self.pyplugins.isregistered(plugin) + return self.comregistry.isregistered(plugin) def getplugins(self): - return self.pyplugins.getplugins() + return self.comregistry.getplugins() # API for bootstrapping # def getplugin(self, importname): impname, clsname = canonical_names(importname) - return self._plugins[impname] + return self.plugins[impname] def consider_env(self): - for spec in self.pyplugins._envlist("PYTEST_PLUGINS"): + for spec in self.comregistry._envlist("PYTEST_PLUGINS"): self.import_plugin(spec) def consider_conftest(self, conftestmodule): cls = getattr(conftestmodule, 'ConftestPlugin', None) - if cls is not None and cls not in self._plugins: - self._plugins[cls] = True + if cls is not None and cls not in self.plugins: + self.plugins[cls] = True self.register(cls()) self.consider_module(conftestmodule) @@ -54,11 +54,11 @@ def import_plugin(self, spec): assert isinstance(spec, str) modname, clsname = canonical_names(spec) - if modname in self._plugins: + if modname in self.plugins: return mod = importplugin(modname) - plugin = registerplugin(self.pyplugins.register, mod, clsname) - self._plugins[modname] = plugin + plugin = registerplugin(self.comregistry.register, mod, clsname) + self.plugins[modname] = plugin self.consider_module(mod) # # @@ -66,18 +66,18 @@ # # def getfirst(self, attrname): - for x in self.pyplugins.listattr(attrname): + for x in self.comregistry.listattr(attrname): return x def listattr(self, attrname): - return self.pyplugins.listattr(attrname) + return self.comregistry.listattr(attrname) def call_firstresult(self, *args, **kwargs): - return self.pyplugins.call_firstresult(*args, **kwargs) + return self.comregistry.call_firstresult(*args, **kwargs) def call_each(self, *args, **kwargs): #print "plugins.call_each", args[0], args[1:], kwargs - return self.pyplugins.call_each(*args, **kwargs) + return self.comregistry.call_each(*args, **kwargs) def notify_exception(self, excinfo=None): if excinfo is None: @@ -86,18 +86,18 @@ return self.api.pytest_internalerror(excrepr=excrepr) def do_addoption(self, parser): - methods = self.pyplugins.listattr("pytest_addoption", reverse=True) + methods = self.comregistry.listattr("pytest_addoption", reverse=True) mc = py._com.MultiCall(methods, parser=parser) mc.execute() def pytest_plugin_registered(self, plugin): if hasattr(self, '_config'): - self.pyplugins.call_plugin(plugin, "pytest_addoption", parser=self._config._parser) - self.pyplugins.call_plugin(plugin, "pytest_configure", config=self._config) + self.comregistry.call_plugin(plugin, "pytest_addoption", parser=self._config._parser) + self.comregistry.call_plugin(plugin, "pytest_configure", config=self._config) def do_configure(self, config): assert not hasattr(self, '_config') - config.bus.register(self) + config.pluginmanager.register(self) self._config = config config.api.pytest_configure(config=self._config) @@ -105,10 +105,10 @@ config = self._config del self._config config.api.pytest_unconfigure(config=config) - config.bus.unregister(self) + config.pluginmanager.unregister(self) def do_itemrun(self, item, pdb=None): - res = self.pyplugins.call_firstresult("pytest_itemrun", item=item, pdb=pdb) + res = self.comregistry.call_firstresult("pytest_itemrun", item=item, pdb=pdb) if res is None: raise ValueError("could not run %r" %(item,)) Modified: py/trunk/py/test/session.py ============================================================================== --- py/trunk/py/test/session.py (original) +++ py/trunk/py/test/session.py Thu Apr 9 16:03:09 2009 @@ -20,8 +20,8 @@ """ def __init__(self, config): self.config = config - self.bus = config.bus # shortcut - self.bus.register(self) + self.pluginmanager = config.pluginmanager # shortcut + self.pluginmanager.register(self) self._testsfailed = False self._nomatch = False self.shouldstop = False @@ -33,7 +33,7 @@ if isinstance(next, (tuple, list)): colitems[:] = list(next) + colitems continue - assert self.bus is next.config.bus + assert self.pluginmanager is next.config.pluginmanager if isinstance(next, Item): remaining = self.filteritems([next]) if remaining: @@ -120,7 +120,7 @@ exitstatus = outcome.EXIT_INTERRUPTED except: captured_excinfo = py.code.ExceptionInfo() - self.config.pytestplugins.notify_exception(captured_excinfo) + self.config.pluginmanager.notify_exception(captured_excinfo) exitstatus = outcome.EXIT_INTERNALERROR if exitstatus == 0 and self._testsfailed: exitstatus = outcome.EXIT_TESTSFAILED @@ -133,4 +133,4 @@ def runtest(self, item): pdb = self.config.option.usepdb and self.runpdb or None - item.config.pytestplugins.do_itemrun(item, pdb=pdb) + item.config.pluginmanager.do_itemrun(item, pdb=pdb) Modified: py/trunk/py/test/testing/test_collect.py ============================================================================== --- py/trunk/py/test/testing/test_collect.py (original) +++ py/trunk/py/test/testing/test_collect.py Thu Apr 9 16:03:09 2009 @@ -67,7 +67,7 @@ def test_listnames_and__getitembynames(self, testdir): modcol = testdir.getmodulecol("pass", withinit=True) - print modcol.config.pytestplugins.getplugins() + print modcol.config.pluginmanager.getplugins() names = modcol.listnames() print names dircol = modcol.config.getfsnode(modcol.config.topdir) @@ -145,7 +145,7 @@ def pytest_collect_file(self, path, parent): wascalled.append(path) config = testdir.Config() - config.pytestplugins.register(Plugin()) + config.pluginmanager.register(Plugin()) config.parse([tmpdir]) col = config.getfsnode(tmpdir) testdir.makefile(".abc", "xyz") Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Thu Apr 9 16:03:09 2009 @@ -318,7 +318,7 @@ runfiletest(opts + [path]) def test_default_bus(): - assert py.test.config.bus is py._com.pyplugins + assert py.test.config.pluginmanager.comregistry is py._com.comregistry @py.test.mark.todo("test for deprecation") def test_ensuretemp(): Modified: py/trunk/py/test/testing/test_pickling.py ============================================================================== --- py/trunk/py/test/testing/test_pickling.py (original) +++ py/trunk/py/test/testing/test_pickling.py Thu Apr 9 16:03:09 2009 @@ -4,22 +4,22 @@ return ImmutablePickleTransport() def pytest_pyfunc_call(__call__, pyfuncitem, args, kwargs): - # for each function call we patch py._com.pyplugins + # for each function call we patch py._com.comregistry # so that the unpickling of config objects # (which bind to this mechanism) doesn't do harm # usually config objects are no meant to be unpickled in # the same system oldconfig = py.test.config - oldcom = py._com.pyplugins + oldcom = py._com.comregistry print "setting py.test.config to None" py.test.config = None - py._com.pyplugins = py._com.PyPlugins() + py._com.comregistry = py._com.Registry() try: return __call__.execute(firstresult=True) finally: print "setting py.test.config to", oldconfig py.test.config = oldconfig - py._com.pyplugins = oldcom + py._com.comregistry = oldcom class ImmutablePickleTransport: def __init__(self): @@ -195,13 +195,10 @@ from py.__.test.dist.mypickle import PickleChannel channel = PickleChannel(channel) config = channel.receive() - assert py.test.config.pytestplugins.pyplugins == py._com.pyplugins, "pyplugins wrong" - assert py.test.config.bus == py._com.pyplugins, "bus wrong" + assert py.test.config.pluginmanager.comregistry == py._com.comregistry, "comregistry wrong" """) channel = PickleChannel(channel) config = testdir.parseconfig() channel.send(config) - channel.waitclose() # this will raise + channel.waitclose() # this will potentially raise gw.exit() - - Modified: py/trunk/py/test/testing/test_pycollect.py ============================================================================== --- py/trunk/py/test/testing/test_pycollect.py (original) +++ py/trunk/py/test/testing/test_pycollect.py Thu Apr 9 16:03:09 2009 @@ -37,11 +37,11 @@ def test_module_participates_as_plugin(self, testdir): modcol = testdir.getmodulecol("") modcol.setup() - assert modcol.config.pytestplugins.isregistered(modcol.obj) + assert modcol.config.pluginmanager.isregistered(modcol.obj) modcol.teardown() - assert not modcol.config.pytestplugins.isregistered(modcol.obj) + assert not modcol.config.pluginmanager.isregistered(modcol.obj) - def test_module_considers_pytestplugins_at_import(self, testdir): + def test_module_considers_pluginmanager_at_import(self, testdir): modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',") py.test.raises(ImportError, "modcol.obj") @@ -259,7 +259,7 @@ class Provider: def pytest_funcarg__some(self, pyfuncitem): return pyfuncitem.name - item.config.pytestplugins.register(Provider()) + item.config.pluginmanager.register(Provider()) item.setupargs() assert len(item.funcargs) == 1 @@ -268,7 +268,7 @@ class Provider: def pytest_funcarg__other(self, pyfuncitem): return pyfuncitem.name - item.config.pytestplugins.register(Provider()) + item.config.pluginmanager.register(Provider()) item.setupargs() assert len(item.funcargs) == 1 name, value = item.funcargs.popitem() @@ -282,7 +282,7 @@ return pyfuncitem.name def pytest_funcarg__other(self, pyfuncitem): return 42 - item.config.pytestplugins.register(Provider()) + item.config.pluginmanager.register(Provider()) item.setupargs() assert len(item.funcargs) == 2 assert item.funcargs['some'] == "test_func" @@ -295,7 +295,7 @@ def pytest_funcarg__some(self, pyfuncitem): pyfuncitem.addfinalizer(lambda: l.append(42)) return 3 - item.config.pytestplugins.register(Provider()) + item.config.pluginmanager.register(Provider()) item.setupargs() assert len(item.funcargs) == 1 assert item.funcargs['some'] == 3 @@ -317,13 +317,13 @@ """) item1, item2 = testdir.genitems([modcol]) modcol.setup() - assert modcol.config.pytestplugins.isregistered(modcol.obj) + assert modcol.config.pluginmanager.isregistered(modcol.obj) item1.setupargs() assert item1.funcargs['something'] == "test_method" item2.setupargs() assert item2.funcargs['something'] == "test_func" modcol.teardown() - assert not modcol.config.pytestplugins.isregistered(modcol.obj) + assert not modcol.config.pluginmanager.isregistered(modcol.obj) class TestSorting: def test_check_equality_and_cmp_basic(self, testdir): Modified: py/trunk/py/test/testing/test_pytestplugin.py ============================================================================== --- py/trunk/py/test/testing/test_pytestplugin.py (original) +++ py/trunk/py/test/testing/test_pytestplugin.py Thu Apr 9 16:03:09 2009 @@ -1,15 +1,15 @@ import py, os -from py.__.test.pytestplugin import PytestPlugins, canonical_names +from py.__.test.pytestplugin import PluginManager, canonical_names from py.__.test.pytestplugin import registerplugin, importplugin class TestBootstrapping: def test_consider_env_fails_to_import(self, monkeypatch): - plugins = PytestPlugins() + plugins = PluginManager() monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'nonexistingmodule') py.test.raises(ImportError, "plugins.consider_env()") def test_consider_env_plugin_instantiation(self, testdir, monkeypatch): - plugins = PytestPlugins() + plugins = PluginManager() testdir.syspathinsert() testdir.makepyfile(pytest_xy123="class Xy123Plugin: pass") monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123') @@ -27,7 +27,7 @@ p = testdir.makepyfile(""" import py def test_hello(): - plugin = py.test.config.pytestplugins.getplugin('x500') + plugin = py.test.config.pluginmanager.getplugin('x500') assert plugin is not None """) new = str(x500.dirpath()) # "%s:%s" %(x500.dirpath(), os.environ.get('PYTHONPATH', '')) @@ -38,7 +38,7 @@ extra = result.stdout.fnmatch_lines(["*1 passed in*"]) def test_import_plugin_importname(self, testdir): - plugins = PytestPlugins() + plugins = PluginManager() py.test.raises(ImportError, 'plugins.import_plugin("x.y")') py.test.raises(ImportError, 'plugins.import_plugin("pytest_x.y")') @@ -59,7 +59,7 @@ assert plugin2 is plugin1 def test_consider_module(self, testdir): - plugins = PytestPlugins() + plugins = PluginManager() testdir.syspathinsert() testdir.makepyfile(pytest_plug1="class Plug1Plugin: pass") testdir.makepyfile(pytest_plug2="class Plug2Plugin: pass") @@ -73,7 +73,7 @@ mod = py.std.new.module("x") mod.pytest_plugins = "pytest_a" aplugin = testdir.makepyfile(pytest_a="""class APlugin: pass""") - plugins = PytestPlugins() + plugins = PluginManager() sorter = testdir.geteventrecorder(plugins) #syspath.prepend(aplugin.dirpath()) py.std.sys.path.insert(0, str(aplugin.dirpath())) @@ -87,7 +87,7 @@ assert len(l) == 1 def test_consider_conftest(self, testdir): - pp = PytestPlugins() + pp = PluginManager() mod = testdir.makepyfile("class ConftestPlugin: hello = 1").pyimport() pp.consider_conftest(mod) l = [x for x in pp.getplugins() if isinstance(x, mod.ConftestPlugin)] @@ -104,11 +104,11 @@ def test_consider_conftest_deps(self, testdir): mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport() - pp = PytestPlugins() + pp = PluginManager() py.test.raises(ImportError, "pp.consider_conftest(mod)") def test_registry(self): - pp = PytestPlugins() + pp = PluginManager() a1, a2 = object(), object() pp.register(a1) assert pp.isregistered(a1) @@ -150,7 +150,7 @@ """) config = Config() config._conftest.importconftest(p) - print config.pytestplugins.getplugins() + print config.pluginmanager.getplugins() config.parse([]) assert not config.option.test123 @@ -158,7 +158,7 @@ from py.__.test.config import Config config = Config() config.parse([]) - config.pytestplugins.do_configure(config=config) + config.pluginmanager.do_configure(config=config) assert not hasattr(config.option, 'test123') p = testdir.makepyfile(""" class ConftestPlugin: @@ -179,30 +179,30 @@ def xyz(self, obj): events.append(obj) - config.bus.register(A()) + config.pluginmanager.register(A()) assert len(l) == 0 - config.pytestplugins.do_configure(config=config) + config.pluginmanager.do_configure(config=config) assert len(l) == 1 - config.bus.register(A()) # this should lead to a configured() plugin + config.pluginmanager.register(A()) # this should lead to a configured() plugin assert len(l) == 2 assert l[0] != l[1] - config.bus.call_each("xyz", obj=42) + config.pluginmanager.call_each("xyz", obj=42) assert len(events) == 2 assert events == [42,42] - config.pytestplugins.do_unconfigure(config=config) - config.bus.register(A()) + config.pluginmanager.do_unconfigure(config=config) + config.pluginmanager.register(A()) assert len(l) == 2 def test_MultiCall(self): - pp = PytestPlugins() + pp = PluginManager() assert hasattr(pp, 'MultiCall') # lower level API def test_getfirst(self): - plugins = PytestPlugins() + plugins = PluginManager() class My1: x = 1 assert plugins.getfirst("x") is None @@ -210,7 +210,7 @@ assert plugins.getfirst("x") == 1 def test_call_each(self): - plugins = PytestPlugins() + plugins = PluginManager() class My: def method(self, arg): pass @@ -221,7 +221,7 @@ py.test.raises(TypeError, 'plugins.call_each("method", arg=42, s=13)') def test_call_firstresult(self): - plugins = PytestPlugins() + plugins = PluginManager() class My1: def method(self): pass @@ -241,7 +241,7 @@ assert plugins.call_firstresult("method") == True def test_listattr(self): - plugins = PytestPlugins() + plugins = PluginManager() class My2: x = 42 plugins.register(My2()) @@ -267,7 +267,7 @@ return x+0 """) l = [] - call = modcol.config.pytestplugins.setupcall(modcol, "pytest_method", 1) + call = modcol.config.pluginmanager.setupcall(modcol, "pytest_method", 1) assert len(call.methods) == 3 results = call.execute() assert results == [1,2,2] From hpk at codespeak.net Thu Apr 9 16:21:07 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 16:21:07 +0200 (CEST) Subject: [py-svn] r63896 - in py/trunk/py: . misc/testing test test/testing Message-ID: <20090409142107.CF035168565@codespeak.net> Author: hpk Date: Thu Apr 9 16:21:07 2009 New Revision: 63896 Added: py/trunk/py/test/pluginmanager.py - copied, changed from r63894, py/trunk/py/test/pytestplugin.py py/trunk/py/test/testing/test_pluginmanager.py - copied, changed from r63894, py/trunk/py/test/testing/test_pytestplugin.py Removed: py/trunk/py/test/pytestplugin.py py/trunk/py/test/testing/test_pytestplugin.py Modified: py/trunk/py/__init__.py py/trunk/py/_com.py py/trunk/py/misc/testing/test_com.py Log: more renames, killing redundant code Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Thu Apr 9 16:21:07 2009 @@ -70,7 +70,7 @@ # helpers for use from test functions or collectors 'test.__doc__' : ('./test/__init__.py', '__doc__'), - 'test._PluginManager' : ('./test/pytestplugin.py', 'PluginManager'), + 'test._PluginManager' : ('./test/pluginmanager.py', 'PluginManager'), 'test.raises' : ('./test/outcome.py', 'raises'), 'test.mark' : ('./test/outcome.py', 'mark',), 'test.deprecated_call' : ('./test/outcome.py', 'deprecated_call'), @@ -198,7 +198,5 @@ 'compat.subprocess' : ('./compat/subprocess.py', '*'), }) -import py -py._com.comregistry.consider_env() Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Thu Apr 9 16:21:07 2009 @@ -73,29 +73,6 @@ plugins = [] self.plugins = plugins - def import_module(self, modspec): - # XXX allow modspec to specify version / lookup - modpath = modspec - __import__(modpath) - - def consider_env(self): - """ consider ENV variable for loading modules. """ - for spec in self._envlist("PYLIB"): - self.import_module(spec) - - def _envlist(self, varname): - val = py.std.os.environ.get(varname, None) - if val is not None: - return val.split(',') - return () - - def consider_module(self, mod, varname="pylib"): - speclist = getattr(mod, varname, ()) - if not isinstance(speclist, (list, tuple)): - speclist = (speclist,) - for spec in speclist: - self.import_module(spec) - def register(self, plugin): assert not isinstance(plugin, str) self.call_each("pytest_plugin_registered", plugin) @@ -105,12 +82,12 @@ self.call_each("pytest_plugin_unregistered", plugin) self.plugins.remove(plugin) - def getplugins(self): - return list(self.plugins) - def isregistered(self, plugin): return plugin in self.plugins + def __iter__(self): + return iter(self.plugins) + def listattr(self, attrname, plugins=None, extra=(), reverse=False): l = [] if plugins is None: Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Thu Apr 9 16:21:07 2009 @@ -81,21 +81,21 @@ assert hasattr(plugins, "MultiCall") def test_register(self): - plugins = Registry() + registry = Registry() class MyPlugin: pass my = MyPlugin() - plugins.register(my) - assert plugins.getplugins() == [my] + registry.register(my) + assert list(registry) == [my] my2 = MyPlugin() - plugins.register(my2) - assert plugins.getplugins() == [my, my2] + registry.register(my2) + assert list(registry) == [my, my2] - assert plugins.isregistered(my) - assert plugins.isregistered(my2) - plugins.unregister(my) - assert not plugins.isregistered(my) - assert plugins.getplugins() == [my2] + assert registry.isregistered(my) + assert registry.isregistered(my2) + registry.unregister(my) + assert not registry.isregistered(my) + assert list(registry) == [my2] def test_call_methods(self): plugins = Registry() @@ -160,35 +160,9 @@ l = list(plugins.listattr('x', reverse=True)) assert l == [43, 42, 41] - def test_consider_env(self, monkeypatch): - plugins = Registry() - monkeypatch.setitem(os.environ, 'PYLIB', "unknownconsider_env") - py.test.raises(ImportError, "plugins.consider_env()") - - def test_consider_module(self): - plugins = Registry() - mod = py.std.new.module("temp") - mod.pylib = ["xxx nomod"] - excinfo = py.test.raises(ImportError, "plugins.consider_module(mod)") - mod.pylib = "os" - plugins.consider_module(mod) - def test_api_and_defaults(): assert isinstance(py._com.comregistry, Registry) -def test_subprocess_env(testdir, monkeypatch): - plugins = Registry() - old = py.path.local(py.__file__).dirpath().dirpath().chdir() - try: - monkeypatch.setitem(os.environ, "PYLIB", 'unknownconsider') - excinfo = py.test.raises(py.process.cmdexec.Error, """ - py.process.cmdexec('%s -c "import py"') - """ % py.std.sys.executable) - assert str(excinfo.value).find("ImportError") != -1 - assert str(excinfo.value).find("unknownconsider") != -1 - finally: - old.chdir() - class TestPluginAPI: def test_happypath(self): plugins = Registry() Copied: py/trunk/py/test/pluginmanager.py (from r63894, py/trunk/py/test/pytestplugin.py) ============================================================================== --- py/trunk/py/test/pytestplugin.py (original) +++ py/trunk/py/test/pluginmanager.py Thu Apr 9 16:21:07 2009 @@ -24,16 +24,22 @@ return self.comregistry.isregistered(plugin) def getplugins(self): - return self.comregistry.getplugins() + return self.comregistry.plugins # API for bootstrapping # def getplugin(self, importname): impname, clsname = canonical_names(importname) return self.plugins[impname] + + def _envlist(self, varname): + val = py.std.os.environ.get(varname, None) + if val is not None: + return val.split(',') + return () def consider_env(self): - for spec in self.comregistry._envlist("PYTEST_PLUGINS"): + for spec in self._envlist("PYTEST_PLUGINS"): self.import_plugin(spec) def consider_conftest(self, conftestmodule): Deleted: /py/trunk/py/test/pytestplugin.py ============================================================================== --- /py/trunk/py/test/pytestplugin.py Thu Apr 9 16:21:07 2009 +++ (empty file) @@ -1,145 +0,0 @@ -""" -handling py.test plugins. -""" -import py -from py.__.test.plugin import api - -class PluginManager(object): - def __init__(self, comregistry=None): - if comregistry is None: - comregistry = py._com.Registry() - self.comregistry = comregistry - self.MultiCall = self.comregistry.MultiCall - self.plugins = {} - - self.api = py._com.PluginAPI( - apiclass=api.PluginHooks, - plugins=self.comregistry) - - def register(self, plugin): - self.comregistry.register(plugin) - def unregister(self, plugin): - self.comregistry.unregister(plugin) - def isregistered(self, plugin): - return self.comregistry.isregistered(plugin) - - def getplugins(self): - return self.comregistry.getplugins() - - # API for bootstrapping - # - def getplugin(self, importname): - impname, clsname = canonical_names(importname) - return self.plugins[impname] - - def consider_env(self): - for spec in self.comregistry._envlist("PYTEST_PLUGINS"): - self.import_plugin(spec) - - def consider_conftest(self, conftestmodule): - cls = getattr(conftestmodule, 'ConftestPlugin', None) - if cls is not None and cls not in self.plugins: - self.plugins[cls] = True - self.register(cls()) - self.consider_module(conftestmodule) - - def consider_module(self, mod): - attr = getattr(mod, "pytest_plugins", ()) - if attr: - if not isinstance(attr, (list, tuple)): - attr = (attr,) - for spec in attr: - self.import_plugin(spec) - - def import_plugin(self, spec): - assert isinstance(spec, str) - modname, clsname = canonical_names(spec) - if modname in self.plugins: - return - mod = importplugin(modname) - plugin = registerplugin(self.comregistry.register, mod, clsname) - self.plugins[modname] = plugin - self.consider_module(mod) - # - # - # API for interacting with registered and instantiated plugin objects - # - # - def getfirst(self, attrname): - for x in self.comregistry.listattr(attrname): - return x - - def listattr(self, attrname): - return self.comregistry.listattr(attrname) - - def call_firstresult(self, *args, **kwargs): - return self.comregistry.call_firstresult(*args, **kwargs) - - def call_each(self, *args, **kwargs): - #print "plugins.call_each", args[0], args[1:], kwargs - return self.comregistry.call_each(*args, **kwargs) - - def notify_exception(self, excinfo=None): - if excinfo is None: - excinfo = py.code.ExceptionInfo() - excrepr = excinfo.getrepr(funcargs=True, showlocals=True) - return self.api.pytest_internalerror(excrepr=excrepr) - - def do_addoption(self, parser): - methods = self.comregistry.listattr("pytest_addoption", reverse=True) - mc = py._com.MultiCall(methods, parser=parser) - mc.execute() - - def pytest_plugin_registered(self, plugin): - if hasattr(self, '_config'): - self.comregistry.call_plugin(plugin, "pytest_addoption", parser=self._config._parser) - self.comregistry.call_plugin(plugin, "pytest_configure", config=self._config) - - def do_configure(self, config): - assert not hasattr(self, '_config') - config.pluginmanager.register(self) - self._config = config - config.api.pytest_configure(config=self._config) - - def do_unconfigure(self, config): - config = self._config - del self._config - config.api.pytest_unconfigure(config=config) - config.pluginmanager.unregister(self) - - def do_itemrun(self, item, pdb=None): - res = self.comregistry.call_firstresult("pytest_itemrun", item=item, pdb=pdb) - if res is None: - raise ValueError("could not run %r" %(item,)) - -# -# XXX old code to automatically load classes -# -def canonical_names(importspec): - importspec = importspec.lower() - modprefix = "pytest_" - if not importspec.startswith(modprefix): - importspec = modprefix + importspec - clsname = importspec[len(modprefix):].capitalize() + "Plugin" - return importspec, clsname - -def registerplugin(registerfunc, mod, clsname): - pluginclass = getattr(mod, clsname) - plugin = pluginclass() - registerfunc(plugin) - return plugin - -def importplugin(importspec): - try: - return __import__(importspec) - except ImportError, e: - if str(e).find(importspec) == -1: - raise - try: - return __import__("py.__.test.plugin.%s" %(importspec), None, None, '__doc__') - except ImportError, e: - if str(e).find(importspec) == -1: - raise - #print "syspath:", py.std.sys.path - #print "curdir:", py.std.os.getcwd() - return __import__(importspec) # show the original exception Copied: py/trunk/py/test/testing/test_pluginmanager.py (from r63894, py/trunk/py/test/testing/test_pytestplugin.py) ============================================================================== --- py/trunk/py/test/testing/test_pytestplugin.py (original) +++ py/trunk/py/test/testing/test_pluginmanager.py Thu Apr 9 16:21:07 2009 @@ -1,6 +1,6 @@ import py, os -from py.__.test.pytestplugin import PluginManager, canonical_names -from py.__.test.pytestplugin import registerplugin, importplugin +from py.__.test.pluginmanager import PluginManager, canonical_names +from py.__.test.pluginmanager import registerplugin, importplugin class TestBootstrapping: def test_consider_env_fails_to_import(self, monkeypatch): @@ -22,7 +22,7 @@ l3 = len(plugins.getplugins()) assert l2 == l3 - def test_pytestplugin_ENV_startup(self, testdir, monkeypatch): + def test_pluginmanager_ENV_startup(self, testdir, monkeypatch): x500 = testdir.makepyfile(pytest_x500="class X500Plugin: pass") p = testdir.makepyfile(""" import py Deleted: /py/trunk/py/test/testing/test_pytestplugin.py ============================================================================== --- /py/trunk/py/test/testing/test_pytestplugin.py Thu Apr 9 16:21:07 2009 +++ (empty file) @@ -1,274 +0,0 @@ -import py, os -from py.__.test.pytestplugin import PluginManager, canonical_names -from py.__.test.pytestplugin import registerplugin, importplugin - -class TestBootstrapping: - def test_consider_env_fails_to_import(self, monkeypatch): - plugins = PluginManager() - monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'nonexistingmodule') - py.test.raises(ImportError, "plugins.consider_env()") - - def test_consider_env_plugin_instantiation(self, testdir, monkeypatch): - plugins = PluginManager() - testdir.syspathinsert() - testdir.makepyfile(pytest_xy123="class Xy123Plugin: pass") - monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123') - l1 = len(plugins.getplugins()) - plugins.consider_env() - l2 = len(plugins.getplugins()) - assert l2 == l1 + 1 - assert plugins.getplugin('pytest_xy123') - plugins.consider_env() - l3 = len(plugins.getplugins()) - assert l2 == l3 - - def test_pytestplugin_ENV_startup(self, testdir, monkeypatch): - x500 = testdir.makepyfile(pytest_x500="class X500Plugin: pass") - p = testdir.makepyfile(""" - import py - def test_hello(): - plugin = py.test.config.pluginmanager.getplugin('x500') - assert plugin is not None - """) - new = str(x500.dirpath()) # "%s:%s" %(x500.dirpath(), os.environ.get('PYTHONPATH', '')) - monkeypatch.setitem(os.environ, 'PYTHONPATH', new) - monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'pytest_x500') - result = testdir.runpytest(p) - assert result.ret == 0 - extra = result.stdout.fnmatch_lines(["*1 passed in*"]) - - def test_import_plugin_importname(self, testdir): - plugins = PluginManager() - py.test.raises(ImportError, 'plugins.import_plugin("x.y")') - py.test.raises(ImportError, 'plugins.import_plugin("pytest_x.y")') - - reset = testdir.syspathinsert() - pluginname = "pytest_hello" - testdir.makepyfile(**{pluginname: """ - class HelloPlugin: - pass - """}) - plugins.import_plugin("hello") - len1 = len(plugins.getplugins()) - plugins.import_plugin("pytest_hello") - len2 = len(plugins.getplugins()) - assert len1 == len2 - plugin1 = plugins.getplugin("pytest_hello") - assert plugin1.__class__.__name__ == 'HelloPlugin' - plugin2 = plugins.getplugin("hello") - assert plugin2 is plugin1 - - def test_consider_module(self, testdir): - plugins = PluginManager() - testdir.syspathinsert() - testdir.makepyfile(pytest_plug1="class Plug1Plugin: pass") - testdir.makepyfile(pytest_plug2="class Plug2Plugin: pass") - mod = py.std.new.module("temp") - mod.pytest_plugins = ["pytest_plug1", "pytest_plug2"] - plugins.consider_module(mod) - assert plugins.getplugin("plug1").__class__.__name__ == "Plug1Plugin" - assert plugins.getplugin("plug2").__class__.__name__ == "Plug2Plugin" - - def test_consider_module_import_module(self, testdir): - mod = py.std.new.module("x") - mod.pytest_plugins = "pytest_a" - aplugin = testdir.makepyfile(pytest_a="""class APlugin: pass""") - plugins = PluginManager() - sorter = testdir.geteventrecorder(plugins) - #syspath.prepend(aplugin.dirpath()) - py.std.sys.path.insert(0, str(aplugin.dirpath())) - plugins.consider_module(mod) - call = sorter.getcall("plugin_registered") - assert call.plugin.__class__.__name__ == "APlugin" - - # check that it is not registered twice - plugins.consider_module(mod) - l = sorter.getcalls("plugin_registered") - assert len(l) == 1 - - def test_consider_conftest(self, testdir): - pp = PluginManager() - mod = testdir.makepyfile("class ConftestPlugin: hello = 1").pyimport() - pp.consider_conftest(mod) - l = [x for x in pp.getplugins() if isinstance(x, mod.ConftestPlugin)] - assert len(l) == 1 - assert l[0].hello == 1 - - pp.consider_conftest(mod) - l = [x for x in pp.getplugins() if isinstance(x, mod.ConftestPlugin)] - assert len(l) == 1 - - def test_config_sets_conftesthandle_onimport(self, testdir): - config = testdir.parseconfig([]) - assert config._conftest._onimport == config._onimportconftest - - def test_consider_conftest_deps(self, testdir): - mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport() - pp = PluginManager() - py.test.raises(ImportError, "pp.consider_conftest(mod)") - - def test_registry(self): - pp = PluginManager() - a1, a2 = object(), object() - pp.register(a1) - assert pp.isregistered(a1) - pp.register(a2) - assert pp.isregistered(a2) - assert pp.getplugins() == [a1, a2] - pp.unregister(a1) - assert not pp.isregistered(a1) - pp.unregister(a2) - assert not pp.isregistered(a2) - - def test_canonical_names(self): - for name in 'xyz', 'pytest_xyz', 'pytest_Xyz', 'Xyz': - impname, clsname = canonical_names(name) - assert impname == "pytest_xyz" - assert clsname == "XyzPlugin" - - def test_registerplugin(self): - l = [] - registerfunc = l.append - registerplugin(registerfunc, py.io, "TerminalWriter") - assert len(l) == 1 - assert isinstance(l[0], py.io.TerminalWriter) - - def test_importplugin(self): - assert importplugin("py") == py - py.test.raises(ImportError, "importplugin('laksjd.qwe')") - mod = importplugin("pytest_terminal") - assert mod is py.__.test.plugin.pytest_terminal - - -class TestPytestPluginInteractions: - def test_do_option_conftestplugin(self, testdir): - from py.__.test.config import Config - p = testdir.makepyfile(""" - class ConftestPlugin: - def pytest_addoption(self, parser): - parser.addoption('--test123', action="store_true") - """) - config = Config() - config._conftest.importconftest(p) - print config.pluginmanager.getplugins() - config.parse([]) - assert not config.option.test123 - - def test_do_option_postinitialize(self, testdir): - from py.__.test.config import Config - config = Config() - config.parse([]) - config.pluginmanager.do_configure(config=config) - assert not hasattr(config.option, 'test123') - p = testdir.makepyfile(""" - class ConftestPlugin: - def pytest_addoption(self, parser): - parser.addoption('--test123', action="store_true", - default=True) - """) - config._conftest.importconftest(p) - assert config.option.test123 - - def test_configure(self, testdir): - config = testdir.parseconfig() - l = [] - events = [] - class A: - def pytest_configure(self, config): - l.append(self) - def xyz(self, obj): - events.append(obj) - - config.pluginmanager.register(A()) - assert len(l) == 0 - config.pluginmanager.do_configure(config=config) - assert len(l) == 1 - config.pluginmanager.register(A()) # this should lead to a configured() plugin - assert len(l) == 2 - assert l[0] != l[1] - - config.pluginmanager.call_each("xyz", obj=42) - assert len(events) == 2 - assert events == [42,42] - - config.pluginmanager.do_unconfigure(config=config) - config.pluginmanager.register(A()) - assert len(l) == 2 - - def test_MultiCall(self): - pp = PluginManager() - assert hasattr(pp, 'MultiCall') - - # lower level API - - def test_getfirst(self): - plugins = PluginManager() - class My1: - x = 1 - assert plugins.getfirst("x") is None - plugins.register(My1()) - assert plugins.getfirst("x") == 1 - - def test_call_each(self): - plugins = PluginManager() - class My: - def method(self, arg): - pass - plugins.register(My()) - py.test.raises(TypeError, 'plugins.call_each("method")') - l = plugins.call_each("method", arg=42) - assert l == [] - py.test.raises(TypeError, 'plugins.call_each("method", arg=42, s=13)') - - def test_call_firstresult(self): - plugins = PluginManager() - class My1: - def method(self): - pass - class My2: - def method(self): - return True - class My3: - def method(self): - return None - assert plugins.call_firstresult("method") is None - assert plugins.call_firstresult("methodnotexists") is None - plugins.register(My1()) - assert plugins.call_firstresult("method") is None - plugins.register(My2()) - assert plugins.call_firstresult("method") == True - plugins.register(My3()) - assert plugins.call_firstresult("method") == True - - def test_listattr(self): - plugins = PluginManager() - class My2: - x = 42 - plugins.register(My2()) - assert not plugins.listattr("hello") - assert plugins.listattr("x") == [42] - - @py.test.mark(xfail="implement setupcall") - def test_call_setup_participants(self, testdir): - testdir.makepyfile( - conftest=""" - import py - def pytest_method(self, x): - return x+1 - pytest_plugin = "pytest_someplugin", - """ - ) - testdir.makepyfile(pytest_someplugin=""" - def pytest_method(self, x): - return x+1 - """) - modcol = testdir.getmodulecol(""" - def pytest_method(x): - return x+0 - """) - l = [] - call = modcol.config.pluginmanager.setupcall(modcol, "pytest_method", 1) - assert len(call.methods) == 3 - results = call.execute() - assert results == [1,2,2] - From hpk at codespeak.net Thu Apr 9 16:34:54 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 16:34:54 +0200 (CEST) Subject: [py-svn] r63899 - in py/trunk/py: . misc/testing test Message-ID: <20090409143454.4A57C168566@codespeak.net> Author: hpk Date: Thu Apr 9 16:34:53 2009 New Revision: 63899 Modified: py/trunk/py/_com.py py/trunk/py/misc/testing/test_com.py py/trunk/py/test/pluginmanager.py py/trunk/py/test/pycollect.py Log: more consistent naming Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Thu Apr 9 16:34:53 2009 @@ -71,27 +71,27 @@ def __init__(self, plugins=None): if plugins is None: plugins = [] - self.plugins = plugins + self._plugins = plugins def register(self, plugin): assert not isinstance(plugin, str) self.call_each("pytest_plugin_registered", plugin) - self.plugins.append(plugin) + self._plugins.append(plugin) def unregister(self, plugin): self.call_each("pytest_plugin_unregistered", plugin) - self.plugins.remove(plugin) + self._plugins.remove(plugin) def isregistered(self, plugin): - return plugin in self.plugins + return plugin in self._plugins def __iter__(self): - return iter(self.plugins) + return iter(self._plugins) def listattr(self, attrname, plugins=None, extra=(), reverse=False): l = [] if plugins is None: - plugins = self.plugins + plugins = self._plugins if extra: plugins += list(extra) for plugin in plugins: @@ -117,31 +117,31 @@ class PluginAPI: - def __init__(self, apiclass, plugins=None): + def __init__(self, apiclass, registry=None): self._apiclass = apiclass - if plugins is None: - plugins = comregistry - self.plugins = plugins + if registry is None: + registry = comregistry + self.registry = registry for name, method in vars(apiclass).items(): if name[:2] != "__": firstresult = getattr(method, 'firstresult', False) - mm = ApiCall(plugins, name, firstresult=firstresult) + mm = ApiCall(registry, name, firstresult=firstresult) setattr(self, name, mm) def __repr__(self): - return "" %(self._apiclass, self.plugins) + return "" %(self._apiclass, self._plugins) class ApiCall: - def __init__(self, plugins, name, firstresult): - self.plugins = plugins + def __init__(self, registry, name, firstresult): + self.registry = registry self.name = name self.firstresult = firstresult def __repr__(self): mode = self.firstresult and "firstresult" or "each" - return "" %(self.name, mode, self.plugins) + return "" %(self.name, mode, self.registry) def __call__(self, *args, **kwargs): - mc = MultiCall(self.plugins.listattr(self.name), *args, **kwargs) + mc = MultiCall(self.registry.listattr(self.name), *args, **kwargs) #print "making multicall", self return mc.execute(firstresult=self.firstresult) Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Thu Apr 9 16:34:53 2009 @@ -165,37 +165,37 @@ class TestPluginAPI: def test_happypath(self): - plugins = Registry() + registry = Registry() class Api: def hello(self, arg): pass - mcm = PluginAPI(apiclass=Api, plugins=plugins) + mcm = PluginAPI(apiclass=Api, registry=registry) assert hasattr(mcm, 'hello') assert repr(mcm.hello).find("hello") != -1 class Plugin: def hello(self, arg): return arg + 1 - plugins.register(Plugin()) + registry.register(Plugin()) l = mcm.hello(3) assert l == [4] assert not hasattr(mcm, 'world') def test_firstresult(self): - plugins = Registry() + registry = Registry() class Api: def hello(self, arg): pass hello.firstresult = True - mcm = PluginAPI(apiclass=Api, plugins=plugins) + mcm = PluginAPI(apiclass=Api, registry=registry) class Plugin: def hello(self, arg): return arg + 1 - plugins.register(Plugin()) + registry.register(Plugin()) res = mcm.hello(3) assert res == 4 def test_default_plugins(self): class Api: pass mcm = PluginAPI(apiclass=Api) - assert mcm.plugins == py._com.comregistry + assert mcm.registry == py._com.comregistry Modified: py/trunk/py/test/pluginmanager.py ============================================================================== --- py/trunk/py/test/pluginmanager.py (original) +++ py/trunk/py/test/pluginmanager.py Thu Apr 9 16:34:53 2009 @@ -1,5 +1,5 @@ """ -handling py.test plugins. +managing loading and interacting with pytest plugins. """ import py from py.__.test.plugin import api @@ -10,11 +10,11 @@ comregistry = py._com.Registry() self.comregistry = comregistry self.MultiCall = self.comregistry.MultiCall - self.plugins = {} + self.impname2plugin = {} self.api = py._com.PluginAPI( apiclass=api.PluginHooks, - plugins=self.comregistry) + registry=self.comregistry) def register(self, plugin): self.comregistry.register(plugin) @@ -24,13 +24,13 @@ return self.comregistry.isregistered(plugin) def getplugins(self): - return self.comregistry.plugins + return list(self.comregistry) # API for bootstrapping # def getplugin(self, importname): impname, clsname = canonical_names(importname) - return self.plugins[impname] + return self.impname2plugin[impname] def _envlist(self, varname): val = py.std.os.environ.get(varname, None) @@ -44,8 +44,8 @@ def consider_conftest(self, conftestmodule): cls = getattr(conftestmodule, 'ConftestPlugin', None) - if cls is not None and cls not in self.plugins: - self.plugins[cls] = True + if cls is not None and cls not in self.impname2plugin: + self.impname2plugin[cls] = True self.register(cls()) self.consider_module(conftestmodule) @@ -60,11 +60,11 @@ def import_plugin(self, spec): assert isinstance(spec, str) modname, clsname = canonical_names(spec) - if modname in self.plugins: + if modname in self.impname2plugin: return mod = importplugin(modname) plugin = registerplugin(self.comregistry.register, mod, clsname) - self.plugins[modname] = plugin + self.impname2plugin[modname] = plugin self.consider_module(mod) # # Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Thu Apr 9 16:34:53 2009 @@ -386,8 +386,8 @@ def _raisefuncargerror(self, argname, prefix="pytest_funcarg__"): metainfo = self.repr_metainfo() available = [] - plugins = self.config.pluginmanager.plugins.values() - plugins.extend(self.config.pluginmanager.comregistry.plugins) + plugins = list(self.config.pluginmanager.comregistry) + #plugins.extend(self.config.pluginmanager.registry.plugins) for plugin in plugins: for name in vars(plugin.__class__): if name.startswith(prefix): From hpk at codespeak.net Thu Apr 9 16:43:12 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 16:43:12 +0200 (CEST) Subject: [py-svn] r63902 - py/trunk/py/test/plugin Message-ID: <20090409144312.01358168566@codespeak.net> Author: hpk Date: Thu Apr 9 16:43:12 2009 New Revision: 63902 Modified: py/trunk/py/test/plugin/pytest_terminal.py Log: bugfix Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Thu Apr 9 16:43:12 2009 @@ -209,11 +209,16 @@ py.path.local(py.__file__).dirpath(), rev)) if self.config.option.traceconfig: plugins = [] - for x in self.config.pluginmanager.plugins: - if isinstance(x, str) and x.startswith("pytest_"): - plugins.append(x[7:]) + for plugin in self.config.pluginmanager.comregistry: + name = plugin.__class__.__name__ + if name.endswith("Plugin"): + name = name[:-6] + #if name == "Conftest": + # XXX get filename + plugins.append(name) else: - plugins.append(str(x)) # XXX display conftest plugins more nicely + plugins.append(str(plugin)) + plugins = ", ".join(plugins) self.write_line("active plugins: %s" %(plugins,)) for i, testarg in py.builtin.enumerate(self.config.args): From hpk at codespeak.net Thu Apr 9 17:03:59 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 17:03:59 +0200 (CEST) Subject: [py-svn] r63905 - in py/trunk/py: . test test/plugin test/testing Message-ID: <20090409150359.95B3B1684C9@codespeak.net> Author: hpk Date: Thu Apr 9 17:03:58 2009 New Revision: 63905 Modified: py/trunk/py/_com.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/pluginmanager.py py/trunk/py/test/testing/test_pluginmanager.py Log: * have pytest pluginmanager do multicalls for registration * more renaming Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Thu Apr 9 17:03:58 2009 @@ -75,11 +75,9 @@ def register(self, plugin): assert not isinstance(plugin, str) - self.call_each("pytest_plugin_registered", plugin) self._plugins.append(plugin) def unregister(self, plugin): - self.call_each("pytest_plugin_unregistered", plugin) self._plugins.remove(plugin) def isregistered(self, plugin): Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Thu Apr 9 17:03:58 2009 @@ -14,10 +14,25 @@ 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. """ + # ------------------------------------------------------------------------------ + # test Session related hooks + # ------------------------------------------------------------------------------ + + def pytest_testrunstart(self): + """ whole test run starts. """ + + def pytest_testrunfinish(self, exitstatus, excrepr=None): + """ whole test run finishes. """ + + def pytest_deselected(self, items): + """ collected items that were deselected (by keyword). """ + + # ------------------------------------------------------------------------------ # collection hooks @@ -100,30 +115,27 @@ def pytest_plugin_unregistered(self, plugin): """ a py lib plugin got unregistered. """ + def pytest_internalerror(self, excrepr): + """ called for internal errors. """ + + def pytest_trace(self, category, msg): + """ called for debug info. """ + + + + # ------------------------------------------------------------------------------ + # distributed testing + # ------------------------------------------------------------------------------ + def pytest_testnodeready(self, node): """ Test Node is ready to operate. """ def pytest_testnodedown(self, node, error): """ Test Node is down. """ - def pytest_testrunstart(self): - """ whole test run starts. """ - - def pytest_testrunfinish(self, exitstatus, excrepr=None): - """ whole test run finishes. """ - - def pytest_deselected(self, items): - """ collected items that were deselected (by keyword). """ - def pytest_rescheduleitems(self, items): """ reschedule Items from a node that went down. """ def pytest_looponfailinfo(self, failreports, rootdirs): """ info for repeating failing tests. """ - def pytest_internalerror(self, excrepr): - """ called for internal errors. """ - - def pytest_trace(self, category, msg): - """ called for debug info. """ - Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Thu Apr 9 17:03:58 2009 @@ -21,9 +21,6 @@ tmptestdir = TmpTestdir(pyfuncitem) return tmptestdir - #def pytest_funcarg__EventRecorder(self, pyfuncitem): - # return EventRecorder - def pytest_funcarg__eventrecorder(self, pyfuncitem): evrec = EventRecorder(py._com.comregistry) pyfuncitem.addfinalizer(lambda: evrec.comregistry.unregister(evrec)) Modified: py/trunk/py/test/pluginmanager.py ============================================================================== --- py/trunk/py/test/pluginmanager.py (original) +++ py/trunk/py/test/pluginmanager.py Thu Apr 9 17:03:58 2009 @@ -17,9 +17,13 @@ registry=self.comregistry) def register(self, plugin): + self.api.pytest_plugin_registered(plugin=plugin) self.comregistry.register(plugin) + def unregister(self, plugin): + self.api.pytest_plugin_unregistered(plugin=plugin) self.comregistry.unregister(plugin) + def isregistered(self, plugin): return self.comregistry.isregistered(plugin) @@ -63,7 +67,7 @@ if modname in self.impname2plugin: return mod = importplugin(modname) - plugin = registerplugin(self.comregistry.register, mod, clsname) + plugin = registerplugin(self.register, mod, clsname) self.impname2plugin[modname] = plugin self.consider_module(mod) # Modified: py/trunk/py/test/testing/test_pluginmanager.py ============================================================================== --- py/trunk/py/test/testing/test_pluginmanager.py (original) +++ py/trunk/py/test/testing/test_pluginmanager.py Thu Apr 9 17:03:58 2009 @@ -4,22 +4,22 @@ class TestBootstrapping: def test_consider_env_fails_to_import(self, monkeypatch): - plugins = PluginManager() + pluginmanager = PluginManager() monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'nonexistingmodule') - py.test.raises(ImportError, "plugins.consider_env()") + py.test.raises(ImportError, "pluginmanager.consider_env()") def test_consider_env_plugin_instantiation(self, testdir, monkeypatch): - plugins = PluginManager() + pluginmanager = PluginManager() testdir.syspathinsert() testdir.makepyfile(pytest_xy123="class Xy123Plugin: pass") monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123') - l1 = len(plugins.getplugins()) - plugins.consider_env() - l2 = len(plugins.getplugins()) + l1 = len(pluginmanager.getplugins()) + pluginmanager.consider_env() + l2 = len(pluginmanager.getplugins()) assert l2 == l1 + 1 - assert plugins.getplugin('pytest_xy123') - plugins.consider_env() - l3 = len(plugins.getplugins()) + assert pluginmanager.getplugin('pytest_xy123') + pluginmanager.consider_env() + l3 = len(pluginmanager.getplugins()) assert l2 == l3 def test_pluginmanager_ENV_startup(self, testdir, monkeypatch): @@ -38,9 +38,9 @@ extra = result.stdout.fnmatch_lines(["*1 passed in*"]) def test_import_plugin_importname(self, testdir): - plugins = PluginManager() - py.test.raises(ImportError, 'plugins.import_plugin("x.y")') - py.test.raises(ImportError, 'plugins.import_plugin("pytest_x.y")') + pluginmanager = PluginManager() + py.test.raises(ImportError, 'pluginmanager.import_plugin("x.y")') + py.test.raises(ImportError, 'pluginmanager.import_plugin("pytest_x.y")') reset = testdir.syspathinsert() pluginname = "pytest_hello" @@ -48,41 +48,41 @@ class HelloPlugin: pass """}) - plugins.import_plugin("hello") - len1 = len(plugins.getplugins()) - plugins.import_plugin("pytest_hello") - len2 = len(plugins.getplugins()) + pluginmanager.import_plugin("hello") + len1 = len(pluginmanager.getplugins()) + pluginmanager.import_plugin("pytest_hello") + len2 = len(pluginmanager.getplugins()) assert len1 == len2 - plugin1 = plugins.getplugin("pytest_hello") + plugin1 = pluginmanager.getplugin("pytest_hello") assert plugin1.__class__.__name__ == 'HelloPlugin' - plugin2 = plugins.getplugin("hello") + plugin2 = pluginmanager.getplugin("hello") assert plugin2 is plugin1 def test_consider_module(self, testdir): - plugins = PluginManager() + pluginmanager = PluginManager() testdir.syspathinsert() testdir.makepyfile(pytest_plug1="class Plug1Plugin: pass") testdir.makepyfile(pytest_plug2="class Plug2Plugin: pass") mod = py.std.new.module("temp") mod.pytest_plugins = ["pytest_plug1", "pytest_plug2"] - plugins.consider_module(mod) - assert plugins.getplugin("plug1").__class__.__name__ == "Plug1Plugin" - assert plugins.getplugin("plug2").__class__.__name__ == "Plug2Plugin" + pluginmanager.consider_module(mod) + assert pluginmanager.getplugin("plug1").__class__.__name__ == "Plug1Plugin" + assert pluginmanager.getplugin("plug2").__class__.__name__ == "Plug2Plugin" def test_consider_module_import_module(self, testdir): mod = py.std.new.module("x") mod.pytest_plugins = "pytest_a" aplugin = testdir.makepyfile(pytest_a="""class APlugin: pass""") - plugins = PluginManager() - sorter = testdir.geteventrecorder(plugins) + pluginmanager = PluginManager() + sorter = testdir.geteventrecorder(pluginmanager) #syspath.prepend(aplugin.dirpath()) py.std.sys.path.insert(0, str(aplugin.dirpath())) - plugins.consider_module(mod) - call = sorter.getcall("plugin_registered") + pluginmanager.consider_module(mod) + call = sorter.getcall(pluginmanager.api.pytest_plugin_registered.name) assert call.plugin.__class__.__name__ == "APlugin" # check that it is not registered twice - plugins.consider_module(mod) + pluginmanager.consider_module(mod) l = sorter.getcalls("plugin_registered") assert len(l) == 1 @@ -202,26 +202,26 @@ # lower level API def test_getfirst(self): - plugins = PluginManager() + pluginmanager = PluginManager() class My1: x = 1 - assert plugins.getfirst("x") is None - plugins.register(My1()) - assert plugins.getfirst("x") == 1 + assert pluginmanager.getfirst("x") is None + pluginmanager.register(My1()) + assert pluginmanager.getfirst("x") == 1 def test_call_each(self): - plugins = PluginManager() + pluginmanager = PluginManager() class My: def method(self, arg): pass - plugins.register(My()) - py.test.raises(TypeError, 'plugins.call_each("method")') - l = plugins.call_each("method", arg=42) + pluginmanager.register(My()) + py.test.raises(TypeError, 'pluginmanager.call_each("method")') + l = pluginmanager.call_each("method", arg=42) assert l == [] - py.test.raises(TypeError, 'plugins.call_each("method", arg=42, s=13)') + py.test.raises(TypeError, 'pluginmanager.call_each("method", arg=42, s=13)') def test_call_firstresult(self): - plugins = PluginManager() + pluginmanager = PluginManager() class My1: def method(self): pass @@ -231,22 +231,22 @@ class My3: def method(self): return None - assert plugins.call_firstresult("method") is None - assert plugins.call_firstresult("methodnotexists") is None - plugins.register(My1()) - assert plugins.call_firstresult("method") is None - plugins.register(My2()) - assert plugins.call_firstresult("method") == True - plugins.register(My3()) - assert plugins.call_firstresult("method") == True + assert pluginmanager.call_firstresult("method") is None + assert pluginmanager.call_firstresult("methodnotexists") is None + pluginmanager.register(My1()) + assert pluginmanager.call_firstresult("method") is None + pluginmanager.register(My2()) + assert pluginmanager.call_firstresult("method") == True + pluginmanager.register(My3()) + assert pluginmanager.call_firstresult("method") == True def test_listattr(self): - plugins = PluginManager() + pluginmanager = PluginManager() class My2: x = 42 - plugins.register(My2()) - assert not plugins.listattr("hello") - assert plugins.listattr("x") == [42] + pluginmanager.register(My2()) + assert not pluginmanager.listattr("hello") + assert pluginmanager.listattr("x") == [42] @py.test.mark(xfail="implement setupcall") def test_call_setup_participants(self, testdir): From hpk at codespeak.net Thu Apr 9 18:55:12 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 18:55:12 +0200 (CEST) Subject: [py-svn] r63908 - in py/trunk/py: . misc/testing test/dist test/dist/testing test/plugin Message-ID: <20090409165512.203EF16856A@codespeak.net> Author: hpk Date: Thu Apr 9 18:55:11 2009 New Revision: 63908 Modified: py/trunk/py/_com.py py/trunk/py/misc/testing/test_com.py py/trunk/py/test/dist/dsession.py py/trunk/py/test/dist/testing/test_dsession.py py/trunk/py/test/dist/txnode.py py/trunk/py/test/plugin/pytest_terminal.py Log: first step in only allowing keyword arguments to plugin calls Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Thu Apr 9 18:55:11 2009 @@ -139,8 +139,10 @@ return "" %(self.name, mode, self.registry) def __call__(self, *args, **kwargs): - mc = MultiCall(self.registry.listattr(self.name), *args, **kwargs) - #print "making multicall", self + if args: + raise TypeError("only keyword arguments allowed " + "for api call to %r" % self.name) + mc = MultiCall(self.registry.listattr(self.name), **kwargs) return mc.execute(firstresult=self.firstresult) comregistry = Registry() Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Thu Apr 9 18:55:11 2009 @@ -177,10 +177,20 @@ def hello(self, arg): return arg + 1 registry.register(Plugin()) - l = mcm.hello(3) + l = mcm.hello(arg=3) assert l == [4] assert not hasattr(mcm, 'world') + def test_needskeywordargs(self): + registry = Registry() + class Api: + def hello(self, arg): + pass + mcm = PluginAPI(apiclass=Api, registry=registry) + excinfo = py.test.raises(TypeError, "mcm.hello(3)") + assert str(excinfo.value).find("only keyword arguments") != -1 + assert str(excinfo.value).find("hello(self, arg)") + def test_firstresult(self): registry = Registry() class Api: @@ -192,7 +202,7 @@ def hello(self, arg): return arg + 1 registry.register(Plugin()) - res = mcm.hello(3) + res = mcm.hello(arg=3) assert res == 4 def test_default_plugins(self): Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Thu Apr 9 18:55:11 2009 @@ -98,7 +98,8 @@ callname, args, kwargs = eventcall if callname is not None: call = getattr(self.config.api, callname) - call(*args, **kwargs) + assert not args + call(**kwargs) # termination conditions if ((loopstate.testsfailed and self.config.option.exitfirst) or @@ -176,15 +177,15 @@ senditems.append(next) else: self.config.api.pytest_collectstart(collector=next) - self.queueevent("pytest_collectreport", basic_collect_report(next)) + self.queueevent("pytest_collectreport", rep=basic_collect_report(next)) if self.config.option.dist == "each": self.senditems_each(senditems) else: # XXX assert self.config.option.dist == "load" self.senditems_load(senditems) - def queueevent(self, eventname, *args, **kwargs): - self.queue.put((eventname, args, kwargs)) + def queueevent(self, eventname, **kwargs): + self.queue.put((eventname, (), kwargs)) def senditems_each(self, tosend): if not tosend: Modified: py/trunk/py/test/dist/testing/test_dsession.py ============================================================================== --- py/trunk/py/test/dist/testing/test_dsession.py (original) +++ py/trunk/py/test/dist/testing/test_dsession.py Thu Apr 9 18:55:11 2009 @@ -81,7 +81,7 @@ session.triggertesting([modcol]) name, args, kwargs = session.queue.get(block=False) assert name == 'pytest_collectreport' - rep, = args + rep = kwargs['rep'] assert len(rep.result) == 1 def test_triggertesting_item(self, testdir): @@ -134,7 +134,7 @@ session.queueevent(None) session.loop_once(loopstate) assert node.sent == [[item]] - session.queueevent("pytest_itemtestreport", run(item, node)) + session.queueevent("pytest_itemtestreport", rep=run(item, node)) session.loop_once(loopstate) assert loopstate.shuttingdown assert not loopstate.testsfailed @@ -147,7 +147,7 @@ session.addnode(node) # setup a HostDown event - session.queueevent("pytest_testnodedown", node, None) + session.queueevent("pytest_testnodedown", node=node, error=None) loopstate = session._initloopstate([item]) loopstate.dowork = False @@ -173,7 +173,7 @@ # have one test pending for a node that goes down session.senditems_load([item1, item2]) node = session.item2nodes[item1] [0] - session.queueevent("pytest_testnodedown", node, None) + session.queueevent("pytest_testnodedown", node=node, error=None) evrec = testdir.geteventrecorder(session.pluginmanager) print session.item2nodes loopstate = session._initloopstate([]) @@ -191,7 +191,7 @@ # setup a session with two nodes session = DSession(item.config) node1 = MockNode() - session.queueevent("pytest_testnodeready", node1) + session.queueevent("pytest_testnodeready", node=node1) loopstate = session._initloopstate([item]) loopstate.dowork = False assert len(session.node2pending) == 0 Modified: py/trunk/py/test/dist/txnode.py ============================================================================== --- py/trunk/py/test/dist/txnode.py (original) +++ py/trunk/py/test/dist/txnode.py Thu Apr 9 18:55:11 2009 @@ -22,6 +22,7 @@ self._down = False def notify(self, eventname, *args, **kwargs): + assert not args self.putevent((eventname, args, kwargs)) def callback(self, eventcall): Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Thu Apr 9 18:55:11 2009 @@ -61,7 +61,7 @@ self._tw.sep(sep, title, **markup) def getcategoryletterword(self, rep): - res = self.config.api.pytest_report_teststatus(rep) + res = self.config.api.pytest_report_teststatus(rep=rep) if res: return res for cat in 'skipped failed passed ???'.split(): From hpk at codespeak.net Thu Apr 9 20:07:06 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 20:07:06 +0200 (CEST) Subject: [py-svn] r63910 - in py/trunk/py/test: . dist/testing plugin testing Message-ID: <20090409180706.D04CF16843D@codespeak.net> Author: hpk Date: Thu Apr 9 20:07:05 2009 New Revision: 63910 Modified: py/trunk/py/test/config.py py/trunk/py/test/dist/testing/test_txnode.py py/trunk/py/test/plugin/pytest__pytest.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_terminal.py py/trunk/py/test/testing/test_config.py Log: removing old mentionings of "bus". docstrings. Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Thu Apr 9 20:07:05 2009 @@ -22,7 +22,9 @@ """ Test Configuration Error. """ class Config(object): - """ central bus for dealing with configuration/initialization data. """ + """ test configuration object, provides access to config valueso, + the pluginmanager and plugin api. + """ Option = py.compat.optparse.Option # deprecated Error = Error basetemp = None Modified: py/trunk/py/test/dist/testing/test_txnode.py ============================================================================== --- py/trunk/py/test/dist/testing/test_txnode.py (original) +++ py/trunk/py/test/dist/testing/test_txnode.py Thu Apr 9 20:07:05 2009 @@ -3,11 +3,11 @@ from py.__.test.dist.txnode import TXNode class EventQueue: - def __init__(self, bus, queue=None): + def __init__(self, registry, queue=None): if queue is None: queue = py.std.Queue.Queue() self.queue = queue - bus.register(self) + registry.register(self) def geteventargs(self, eventname, timeout=2.0): events = [] Modified: py/trunk/py/test/plugin/pytest__pytest.py ============================================================================== --- py/trunk/py/test/plugin/pytest__pytest.py (original) +++ py/trunk/py/test/plugin/pytest__pytest.py Thu Apr 9 20:07:05 2009 @@ -45,6 +45,7 @@ recorder = RecordCalls() self._recorders[apiclass] = recorder self._comregistry.register(recorder) + self.api = py._com.PluginAPI(apiclass, registry=self._comregistry) def finalize(self): for recorder in self._recorders.values(): Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Thu Apr 9 20:07:05 2009 @@ -73,10 +73,11 @@ if hasattr(self, '_olddir'): self._olddir.chdir() - def geteventrecorder(self, bus): - sorter = EventRecorder(bus) - sorter.callrecorder = CallRecorder(bus) + def geteventrecorder(self, registry): + sorter = EventRecorder(registry) + sorter.callrecorder = CallRecorder(registry) sorter.callrecorder.start_recording(api.PluginHooks) + sorter.api = sorter.callrecorder.api self.pyfuncitem.addfinalizer(sorter.callrecorder.finalize) return sorter @@ -361,13 +362,13 @@ self.callrecorder.finalize() def test_eventrecorder(testdir): - bus = py._com.Registry() - recorder = testdir.geteventrecorder(bus) + registry = py._com.Registry() + recorder = testdir.geteventrecorder(registry) assert not recorder.getfailures() rep = runner.ItemTestReport(None, None) rep.passed = False rep.failed = True - bus.call_each("pytest_itemtestreport", rep=rep) + recorder.api.pytest_itemtestreport(rep=rep) failures = recorder.getfailures() assert failures == [rep] failures = recorder.getfailures() @@ -376,12 +377,12 @@ rep = runner.ItemTestReport(None, None) rep.passed = False rep.skipped = True - bus.call_each("pytest_itemtestreport", rep=rep) + recorder.api.pytest_itemtestreport(rep=rep) rep = runner.CollectReport(None, None) rep.passed = False rep.failed = True - bus.call_each("pytest_itemtestreport", rep=rep) + recorder.api.pytest_itemtestreport(rep=rep) passed, skipped, failed = recorder.listoutcomes() assert not passed and skipped and failed @@ -394,7 +395,7 @@ recorder.unregister() recorder.clear() assert not recorder.getfailures() - bus.call_each("pytest_itemtestreport", rep=rep) + recorder.api.pytest_itemtestreport(rep=rep) assert not recorder.getfailures() class LineComp: Modified: py/trunk/py/test/plugin/pytest_terminal.py ============================================================================== --- py/trunk/py/test/plugin/pytest_terminal.py (original) +++ py/trunk/py/test/plugin/pytest_terminal.py Thu Apr 9 20:07:05 2009 @@ -509,7 +509,7 @@ ]) def test_tb_option(self, testdir, linecomp): - # XXX usage of testdir and event bus + # XXX usage of testdir for tbopt in ["long", "short", "no"]: print 'testing --tb=%s...' % tbopt modcol = testdir.getmodulecol(""" @@ -569,7 +569,6 @@ #""", configargs=("--showskipsummary",) + ("-v",)*verbose) rep = TerminalReporter(modcol.config, file=linecomp.stringio) modcol.config.pluginmanager.register(rep) - bus = modcol.config.pluginmanager modcol.config.api.pytest_testrunstart() try: for item in testdir.genitems([modcol]): Modified: py/trunk/py/test/testing/test_config.py ============================================================================== --- py/trunk/py/test/testing/test_config.py (original) +++ py/trunk/py/test/testing/test_config.py Thu Apr 9 20:07:05 2009 @@ -317,7 +317,7 @@ ['--traceconfig'], ['-v'], ['-v', '-v']): runfiletest(opts + [path]) -def test_default_bus(): +def test_default_registry(): assert py.test.config.pluginmanager.comregistry is py._com.comregistry @py.test.mark.todo("test for deprecation") From hpk at codespeak.net Thu Apr 9 20:40:00 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 20:40:00 +0200 (CEST) Subject: [py-svn] r63911 - in py/trunk/py/test: . plugin testing Message-ID: <20090409184000.8D97A1684D1@codespeak.net> Author: hpk Date: Thu Apr 9 20:39:59 2009 New Revision: 63911 Modified: py/trunk/py/test/collect.py py/trunk/py/test/plugin/api.py py/trunk/py/test/plugin/pytest__pytest.py py/trunk/py/test/pluginmanager.py py/trunk/py/test/testing/test_pluginmanager.py Log: striking unneeded call_each from pluginmanager Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Thu Apr 9 20:39:59 2009 @@ -443,8 +443,7 @@ def consider_dir(self, path, usefilters=None): if usefilters is not None: APIWARN("0.99", "usefilters argument not needed") - res = self.config.pluginmanager.call_firstresult( - 'pytest_collect_recurse', path=path, parent=self) + res = self.config.api.pytest_collect_recurse(path=path, parent=self) if res is None or res: return self.config.api.pytest_collect_directory( path=path, parent=self) Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Thu Apr 9 20:39:59 2009 @@ -44,6 +44,7 @@ """ return True/False to cause/prevent recursion into given directory. return None if you do not want to make the decision. """ + pytest_collect_recurse.firstresult = True def pytest_collect_directory(self, path, parent): """ return Collection node or None. """ Modified: py/trunk/py/test/plugin/pytest__pytest.py ============================================================================== --- py/trunk/py/test/plugin/pytest__pytest.py (original) +++ py/trunk/py/test/plugin/pytest__pytest.py Thu Apr 9 20:39:59 2009 @@ -106,7 +106,7 @@ def xyz(self, arg): pass rec.start_recording(ApiClass) - comregistry.call_each("xyz", 123) + rec.api.xyz(arg=123) call = rec.popcall("xyz") assert call.arg == 123 assert call._name == "xyz" @@ -124,7 +124,7 @@ def xyz(self, arg): return arg + 1 rec._comregistry.register(Plugin()) - res = rec._comregistry.call_firstresult("xyz", 41) - assert res == 42 + res = rec.api.xyz(arg=41) + assert res == [42] """) sorter.assertoutcome(passed=1) Modified: py/trunk/py/test/pluginmanager.py ============================================================================== --- py/trunk/py/test/pluginmanager.py (original) +++ py/trunk/py/test/pluginmanager.py Thu Apr 9 20:39:59 2009 @@ -79,16 +79,12 @@ for x in self.comregistry.listattr(attrname): return x - def listattr(self, attrname): - return self.comregistry.listattr(attrname) + def listattr(self, attrname, plugins=None): + return self.comregistry.listattr(attrname, plugins=plugins) def call_firstresult(self, *args, **kwargs): return self.comregistry.call_firstresult(*args, **kwargs) - def call_each(self, *args, **kwargs): - #print "plugins.call_each", args[0], args[1:], kwargs - return self.comregistry.call_each(*args, **kwargs) - def notify_exception(self, excinfo=None): if excinfo is None: excinfo = py.code.ExceptionInfo() @@ -102,8 +98,12 @@ def pytest_plugin_registered(self, plugin): if hasattr(self, '_config'): - self.comregistry.call_plugin(plugin, "pytest_addoption", parser=self._config._parser) - self.comregistry.call_plugin(plugin, "pytest_configure", config=self._config) + self.call_plugin(plugin, "pytest_addoption", parser=self._config._parser) + self.call_plugin(plugin, "pytest_configure", config=self._config) + + def call_plugin(self, plugin, methname, **kwargs): + return self.MultiCall(self.listattr(methname, plugins=[plugin]), + **kwargs).execute(firstresult=True) def do_configure(self, config): assert not hasattr(self, '_config') Modified: py/trunk/py/test/testing/test_pluginmanager.py ============================================================================== --- py/trunk/py/test/testing/test_pluginmanager.py (original) +++ py/trunk/py/test/testing/test_pluginmanager.py Thu Apr 9 20:39:59 2009 @@ -172,12 +172,9 @@ def test_configure(self, testdir): config = testdir.parseconfig() l = [] - events = [] class A: def pytest_configure(self, config): l.append(self) - def xyz(self, obj): - events.append(obj) config.pluginmanager.register(A()) assert len(l) == 0 @@ -186,11 +183,7 @@ config.pluginmanager.register(A()) # this should lead to a configured() plugin assert len(l) == 2 assert l[0] != l[1] - - config.pluginmanager.call_each("xyz", obj=42) - assert len(events) == 2 - assert events == [42,42] - + config.pluginmanager.do_unconfigure(config=config) config.pluginmanager.register(A()) assert len(l) == 2 @@ -209,16 +202,6 @@ pluginmanager.register(My1()) assert pluginmanager.getfirst("x") == 1 - def test_call_each(self): - pluginmanager = PluginManager() - class My: - def method(self, arg): - pass - pluginmanager.register(My()) - py.test.raises(TypeError, 'pluginmanager.call_each("method")') - l = pluginmanager.call_each("method", arg=42) - assert l == [] - py.test.raises(TypeError, 'pluginmanager.call_each("method", arg=42, s=13)') def test_call_firstresult(self): pluginmanager = PluginManager() From hpk at codespeak.net Thu Apr 9 22:32:06 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 22:32:06 +0200 (CEST) Subject: [py-svn] r63913 - in py/trunk/py: . execnet log log/testing misc misc/testing path test Message-ID: <20090409203206.828131684D1@codespeak.net> Author: hpk Date: Thu Apr 9 22:32:04 2009 New Revision: 63913 Added: py/trunk/py/log/testing/test_warning.py - copied, changed from r63894, py/trunk/py/misc/testing/test_warn.py py/trunk/py/log/warning.py - copied unchanged from r63894, py/trunk/py/misc/warn.py Removed: py/trunk/py/misc/testing/test_warn.py py/trunk/py/misc/warn.py Modified: py/trunk/py/__init__.py py/trunk/py/execnet/register.py py/trunk/py/path/common.py py/trunk/py/test/collect.py py/trunk/py/test/config.py Log: make py.log.APIWARN available Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Thu Apr 9 22:32:04 2009 @@ -179,6 +179,7 @@ # logging API ('producers' and 'consumers' connected via keywords) 'log.__doc__' : ('./log/__init__.py', '__doc__'), + 'log.APIWARN' : ('./log/warning.py', 'APIWARN'), 'log.Producer' : ('./log/producer.py', 'Producer'), 'log.default' : ('./log/producer.py', 'default'), 'log._getstate' : ('./log/producer.py', '_getstate'), Modified: py/trunk/py/execnet/register.py ============================================================================== --- py/trunk/py/execnet/register.py (original) +++ py/trunk/py/execnet/register.py Thu Apr 9 22:32:04 2009 @@ -2,7 +2,6 @@ import os, inspect, socket import sys from py.magic import autopath ; mypath = autopath() -from py.__.misc.warn import APIWARN try: from subprocess import Popen, PIPE @@ -165,7 +164,7 @@ cmdline[i] = "'" + cmdline[i].replace("'", "'\\''") + "'" cmd = 'ssh -C' if identity is not None: - APIWARN("1.0", "pass in 'ssh_config' file instead of identity") + py.log.APIWARN("1.0", "pass in 'ssh_config' file instead of identity") cmd += ' -i %s' % (identity,) if ssh_config is not None: cmd += ' -F %s' % (ssh_config) Copied: py/trunk/py/log/testing/test_warning.py (from r63894, py/trunk/py/misc/testing/test_warn.py) ============================================================================== --- py/trunk/py/misc/testing/test_warn.py (original) +++ py/trunk/py/log/testing/test_warning.py Thu Apr 9 22:32:04 2009 @@ -1,5 +1,5 @@ import py -from py.__.misc.warn import WarningPlugin +from py.__.log.warning import WarningPlugin mypath = py.magic.autopath() class TestWarningPlugin: @@ -45,5 +45,4 @@ assert warning.msg == "xxx (since version 3.0)" def test_default(): - from py.__.misc.warn import APIWARN - assert py._com.comregistry.isregistered(APIWARN.im_self) + assert py._com.comregistry.isregistered(py.log.APIWARN.im_self) Deleted: /py/trunk/py/misc/testing/test_warn.py ============================================================================== --- /py/trunk/py/misc/testing/test_warn.py Thu Apr 9 22:32:04 2009 +++ (empty file) @@ -1,49 +0,0 @@ -import py -from py.__.misc.warn import WarningPlugin -mypath = py.magic.autopath() - -class TestWarningPlugin: - def setup_method(self, method): - self.pluginmanager = py._com.Registry() - self.wb = WarningPlugin(self.pluginmanager) - self.pluginmanager.register(self) - self.warnings = [] - - def pyevent__WARNING(self, warning): - self.warnings.append(warning) - - def test_event_generation(self): - self.wb.warn("hello") - assert len(self.warnings) == 1 - - def test_location(self): - self.wb.warn("again") - warning = self.warnings[0] - lno = self.test_location.im_func.func_code.co_firstlineno + 1 - assert warning.lineno == lno - assert warning.path == mypath - locstr = "%s:%d: " %(mypath, lno+1,) - assert repr(warning).startswith(locstr) - assert str(warning) == warning.msg - - def test_stacklevel(self): - def f(): - self.wb.warn("x", stacklevel=2) - # 3 - # 4 - f() - lno = self.test_stacklevel.im_func.func_code.co_firstlineno + 5 - warning = self.warnings[0] - assert warning.lineno == lno - - def test_forwarding_to_warnings_module(self): - py.test.deprecated_call(self.wb.warn, "x") - - def test_apiwarn(self): - self.wb.apiwarn("3.0", "xxx") - warning = self.warnings[0] - assert warning.msg == "xxx (since version 3.0)" - -def test_default(): - from py.__.misc.warn import APIWARN - assert py._com.comregistry.isregistered(APIWARN.im_self) Deleted: /py/trunk/py/misc/warn.py ============================================================================== --- /py/trunk/py/misc/warn.py Thu Apr 9 22:32:04 2009 +++ (empty file) @@ -1,69 +0,0 @@ -import py, sys - -class Warning(py.std.exceptions.DeprecationWarning): - def __init__(self, msg, path, lineno): - self.msg = msg - self.path = path - self.lineno = lineno - def __repr__(self): - return "%s:%d: %s" %(self.path, self.lineno+1, self.msg) - def __str__(self): - return self.msg - -# XXX probably only apiwarn() + py._com.comregistry forwarding -# warn_explicit is actually needed - -class WarningPlugin(object): - def __init__(self, bus): - self.pluginmanager = bus - bus.register(self) - - def pyevent__WARNING(self, warning): - # forward to python warning system - py.std.warnings.warn_explicit(warning, category=Warning, - filename=str(warning.path), - lineno=warning.lineno, - registry=py.std.warnings.__dict__.setdefault( - "__warningsregistry__", {}) - ) - - def apiwarn(self, startversion, msg, stacklevel=1): - # below is mostly COPIED from python2.4/warnings.py's def warn() - # Get context information - msg = "%s (since version %s)" %(msg, startversion) - self.warn(msg, stacklevel=stacklevel+1) - - def warn(self, msg, stacklevel=1): - try: - caller = sys._getframe(stacklevel) - except ValueError: - globals = sys.__dict__ - lineno = 1 - else: - globals = caller.f_globals - lineno = caller.f_lineno - if '__name__' in globals: - module = globals['__name__'] - else: - module = "" - filename = globals.get('__file__') - if filename: - fnl = filename.lower() - if fnl.endswith(".pyc") or fnl.endswith(".pyo"): - filename = filename[:-1] - else: - if module == "__main__": - try: - filename = sys.argv[0] - except AttributeError: - # embedded interpreters don't have sys.argv, see bug #839151 - filename = '__main__' - if not filename: - filename = module - path = py.path.local(filename) - warning = Warning(msg, path, lineno) - self.pluginmanager.call_each("pyevent__WARNING", warning) - -# singleton api warner for py lib -apiwarner = WarningPlugin(py._com.comregistry) -APIWARN = apiwarner.apiwarn Modified: py/trunk/py/path/common.py ============================================================================== --- py/trunk/py/path/common.py (original) +++ py/trunk/py/path/common.py Thu Apr 9 22:32:04 2009 @@ -5,7 +5,6 @@ from __future__ import generators import os, sys import py -from py.__.misc.warn import APIWARN def checktype(pathinstance, kw): names = ('local', 'svnwc', 'svnurl', 'py', ) @@ -22,7 +21,7 @@ kwargs-specified specification. """ def __init__(self, **kwargs): - APIWARN("0.9.0", + py.log.APIWARN("0.9.0", "py.path.checker is deprecated, construct " "calls to pathobj.check() instead", ) Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Thu Apr 9 22:32:04 2009 @@ -4,7 +4,6 @@ that is usually built iteratively. """ import py -from py.__.misc.warn import APIWARN from py.__.test.outcome import Skipped def configproperty(name): @@ -442,7 +441,7 @@ def consider_dir(self, path, usefilters=None): if usefilters is not None: - APIWARN("0.99", "usefilters argument not needed") + py.log.APIWARN("0.99", "usefilters argument not needed") res = self.config.api.pytest_collect_recurse(path=path, parent=self) if res is None or res: return self.config.api.pytest_collect_directory( @@ -479,13 +478,13 @@ """ execute this test item.""" def warnoldcollect(): - APIWARN("1.0", + py.log.APIWARN("1.0", "implement collector.collect() instead of " "collector.run() and collector.join()", stacklevel=2) def warnoldtestrun(): - APIWARN("1.0", + py.log.APIWARN("1.0", "implement item.runtest() instead of " "item.run() and item.execute()", stacklevel=2) Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Thu Apr 9 22:32:04 2009 @@ -2,7 +2,6 @@ from conftesthandle import Conftest from py.__.test import parseopt -from py.__.misc.warn import APIWARN from py.__.test.runner import SetupState def ensuretemp(string, dir=1): @@ -181,7 +180,7 @@ """ add a named group of options to the current testing session. This function gets invoked during testing session initialization. """ - APIWARN("1.0", "define plugins to add options", stacklevel=2) + py.log.APIWARN("1.0", "define plugins to add options", stacklevel=2) group = self._parser.addgroup(groupname) for opt in specs: group._addoption_instance(opt) From hpk at codespeak.net Thu Apr 9 22:34:29 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 22:34:29 +0200 (CEST) Subject: [py-svn] r63914 - in py/trunk/py: . execnet log log/testing path test Message-ID: <20090409203429.74B4616841C@codespeak.net> Author: hpk Date: Thu Apr 9 22:34:28 2009 New Revision: 63914 Modified: py/trunk/py/__init__.py py/trunk/py/execnet/register.py py/trunk/py/log/testing/test_warning.py py/trunk/py/log/warning.py py/trunk/py/path/common.py py/trunk/py/test/collect.py py/trunk/py/test/config.py Log: py.log._apiwarn is fine enough because it's something of an py lib internal api deprecation mechanism atm. Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Thu Apr 9 22:34:28 2009 @@ -179,7 +179,7 @@ # logging API ('producers' and 'consumers' connected via keywords) 'log.__doc__' : ('./log/__init__.py', '__doc__'), - 'log.APIWARN' : ('./log/warning.py', 'APIWARN'), + 'log._apiwarn' : ('./log/warning.py', '_apiwarn'), 'log.Producer' : ('./log/producer.py', 'Producer'), 'log.default' : ('./log/producer.py', 'default'), 'log._getstate' : ('./log/producer.py', '_getstate'), Modified: py/trunk/py/execnet/register.py ============================================================================== --- py/trunk/py/execnet/register.py (original) +++ py/trunk/py/execnet/register.py Thu Apr 9 22:34:28 2009 @@ -164,7 +164,7 @@ cmdline[i] = "'" + cmdline[i].replace("'", "'\\''") + "'" cmd = 'ssh -C' if identity is not None: - py.log.APIWARN("1.0", "pass in 'ssh_config' file instead of identity") + py.log._apiwarn("1.0", "pass in 'ssh_config' file instead of identity") cmd += ' -i %s' % (identity,) if ssh_config is not None: cmd += ' -F %s' % (ssh_config) Modified: py/trunk/py/log/testing/test_warning.py ============================================================================== --- py/trunk/py/log/testing/test_warning.py (original) +++ py/trunk/py/log/testing/test_warning.py Thu Apr 9 22:34:28 2009 @@ -45,4 +45,4 @@ assert warning.msg == "xxx (since version 3.0)" def test_default(): - assert py._com.comregistry.isregistered(py.log.APIWARN.im_self) + assert py._com.comregistry.isregistered(py.log._apiwarn.im_self) Modified: py/trunk/py/log/warning.py ============================================================================== --- py/trunk/py/log/warning.py (original) +++ py/trunk/py/log/warning.py Thu Apr 9 22:34:28 2009 @@ -66,4 +66,4 @@ # singleton api warner for py lib apiwarner = WarningPlugin(py._com.comregistry) -APIWARN = apiwarner.apiwarn +_apiwarn = apiwarner.apiwarn Modified: py/trunk/py/path/common.py ============================================================================== --- py/trunk/py/path/common.py (original) +++ py/trunk/py/path/common.py Thu Apr 9 22:34:28 2009 @@ -21,7 +21,7 @@ kwargs-specified specification. """ def __init__(self, **kwargs): - py.log.APIWARN("0.9.0", + py.log._apiwarn("0.9.0", "py.path.checker is deprecated, construct " "calls to pathobj.check() instead", ) Modified: py/trunk/py/test/collect.py ============================================================================== --- py/trunk/py/test/collect.py (original) +++ py/trunk/py/test/collect.py Thu Apr 9 22:34:28 2009 @@ -441,7 +441,7 @@ def consider_dir(self, path, usefilters=None): if usefilters is not None: - py.log.APIWARN("0.99", "usefilters argument not needed") + py.log._apiwarn("0.99", "usefilters argument not needed") res = self.config.api.pytest_collect_recurse(path=path, parent=self) if res is None or res: return self.config.api.pytest_collect_directory( @@ -478,13 +478,13 @@ """ execute this test item.""" def warnoldcollect(): - py.log.APIWARN("1.0", + py.log._apiwarn("1.0", "implement collector.collect() instead of " "collector.run() and collector.join()", stacklevel=2) def warnoldtestrun(): - py.log.APIWARN("1.0", + py.log._apiwarn("1.0", "implement item.runtest() instead of " "item.run() and item.execute()", stacklevel=2) Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Thu Apr 9 22:34:28 2009 @@ -180,7 +180,7 @@ """ add a named group of options to the current testing session. This function gets invoked during testing session initialization. """ - py.log.APIWARN("1.0", "define plugins to add options", stacklevel=2) + py.log._apiwarn("1.0", "define plugins to add options", stacklevel=2) group = self._parser.addgroup(groupname) for opt in specs: group._addoption_instance(opt) From hpk at codespeak.net Thu Apr 9 23:04:52 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 23:04:52 +0200 (CEST) Subject: [py-svn] r63916 - in py/trunk/py/log: . testing Message-ID: <20090409210452.2F80216847C@codespeak.net> Author: hpk Date: Thu Apr 9 23:04:51 2009 New Revision: 63916 Modified: py/trunk/py/log/testing/test_warning.py py/trunk/py/log/warning.py Log: KISS: remove interaction of warnings with plugin mechanism, it's not needed. Modified: py/trunk/py/log/testing/test_warning.py ============================================================================== --- py/trunk/py/log/testing/test_warning.py (original) +++ py/trunk/py/log/testing/test_warning.py Thu Apr 9 23:04:51 2009 @@ -1,48 +1,28 @@ import py -from py.__.log.warning import WarningPlugin mypath = py.magic.autopath() -class TestWarningPlugin: - def setup_method(self, method): - self.pluginmanager = py._com.Registry() - self.wb = WarningPlugin(self.pluginmanager) - self.pluginmanager.register(self) - self.warnings = [] +def test_forwarding_to_warnings_module(): + py.test.deprecated_call(py.log._apiwarn, "1.3", "..") - def pyevent__WARNING(self, warning): - self.warnings.append(warning) - - def test_event_generation(self): - self.wb.warn("hello") - assert len(self.warnings) == 1 - - def test_location(self): - self.wb.warn("again") - warning = self.warnings[0] - lno = self.test_location.im_func.func_code.co_firstlineno + 1 - assert warning.lineno == lno - assert warning.path == mypath - locstr = "%s:%d: " %(mypath, lno+1,) - assert repr(warning).startswith(locstr) - assert str(warning) == warning.msg - - def test_stacklevel(self): - def f(): - self.wb.warn("x", stacklevel=2) - # 3 - # 4 - f() - lno = self.test_stacklevel.im_func.func_code.co_firstlineno + 5 - warning = self.warnings[0] - assert warning.lineno == lno - - def test_forwarding_to_warnings_module(self): - py.test.deprecated_call(self.wb.warn, "x") - - def test_apiwarn(self): - self.wb.apiwarn("3.0", "xxx") - warning = self.warnings[0] - assert warning.msg == "xxx (since version 3.0)" - -def test_default(): - assert py._com.comregistry.isregistered(py.log._apiwarn.im_self) +def test_apiwarn_functional(): + capture = py.io.StdCapture() + py.log._apiwarn("x.y.z", "something") + out, err = capture.reset() + print "out", out + print "err", err + assert err.find("x.y.z") != -1 + lno = test_apiwarn_functional.func_code.co_firstlineno + 2 + exp = "%s:%s" % (mypath, lno) + assert err.find(exp) != -1 + +def test_stacklevel(): + def f(): + py.log._apiwarn("x", "some", stacklevel=2) + # 3 + # 4 + capture = py.io.StdCapture() + f() + out, err = capture.reset() + lno = test_stacklevel.func_code.co_firstlineno + 6 + warning = str(err) + assert warning.find(":%s" % lno) != -1 Modified: py/trunk/py/log/warning.py ============================================================================== --- py/trunk/py/log/warning.py (original) +++ py/trunk/py/log/warning.py Thu Apr 9 23:04:51 2009 @@ -10,60 +10,45 @@ def __str__(self): return self.msg -# XXX probably only apiwarn() + py._com.comregistry forwarding -# warn_explicit is actually needed +def _apiwarn(startversion, msg, stacklevel=1): + # below is mostly COPIED from python2.4/warnings.py's def warn() + # Get context information + msg = "%s (since version %s)" %(msg, startversion) + warn(msg, stacklevel=stacklevel+1) -class WarningPlugin(object): - def __init__(self, bus): - self.pluginmanager = bus - bus.register(self) - - def pyevent__WARNING(self, warning): - # forward to python warning system - py.std.warnings.warn_explicit(warning, category=Warning, - filename=str(warning.path), - lineno=warning.lineno, - registry=py.std.warnings.__dict__.setdefault( - "__warningsregistry__", {}) - ) - - def apiwarn(self, startversion, msg, stacklevel=1): - # below is mostly COPIED from python2.4/warnings.py's def warn() - # Get context information - msg = "%s (since version %s)" %(msg, startversion) - self.warn(msg, stacklevel=stacklevel+1) +def warn(msg, stacklevel=1): + try: + caller = sys._getframe(stacklevel) + except ValueError: + globals = sys.__dict__ + lineno = 1 + else: + globals = caller.f_globals + lineno = caller.f_lineno + if '__name__' in globals: + module = globals['__name__'] + else: + module = "" + filename = globals.get('__file__') + if filename: + fnl = filename.lower() + if fnl.endswith(".pyc") or fnl.endswith(".pyo"): + filename = filename[:-1] + else: + if module == "__main__": + try: + filename = sys.argv[0] + except AttributeError: + # embedded interpreters don't have sys.argv, see bug #839151 + filename = '__main__' + if not filename: + filename = module + path = py.path.local(filename) + warning = Warning(msg, path, lineno) + py.std.warnings.warn_explicit(warning, category=Warning, + filename=str(warning.path), + lineno=warning.lineno, + registry=py.std.warnings.__dict__.setdefault( + "__warningsregistry__", {}) + ) - def warn(self, msg, stacklevel=1): - try: - caller = sys._getframe(stacklevel) - except ValueError: - globals = sys.__dict__ - lineno = 1 - else: - globals = caller.f_globals - lineno = caller.f_lineno - if '__name__' in globals: - module = globals['__name__'] - else: - module = "" - filename = globals.get('__file__') - if filename: - fnl = filename.lower() - if fnl.endswith(".pyc") or fnl.endswith(".pyo"): - filename = filename[:-1] - else: - if module == "__main__": - try: - filename = sys.argv[0] - except AttributeError: - # embedded interpreters don't have sys.argv, see bug #839151 - filename = '__main__' - if not filename: - filename = module - path = py.path.local(filename) - warning = Warning(msg, path, lineno) - self.pluginmanager.call_each("pyevent__WARNING", warning) - -# singleton api warner for py lib -apiwarner = WarningPlugin(py._com.comregistry) -_apiwarn = apiwarner.apiwarn From hpk at codespeak.net Thu Apr 9 23:09:54 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 9 Apr 2009 23:09:54 +0200 (CEST) Subject: [py-svn] r63917 - in py/trunk/py: . misc/testing Message-ID: <20090409210954.B17F4168458@codespeak.net> Author: hpk Date: Thu Apr 9 23:09:54 2009 New Revision: 63917 Modified: py/trunk/py/_com.py py/trunk/py/misc/testing/test_com.py Log: remove now unused code Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Thu Apr 9 23:09:54 2009 @@ -101,10 +101,6 @@ l.reverse() return l - def call_each(self, methname, *args, **kwargs): - """ return call object for executing a plugin call. """ - return MultiCall(self.listattr(methname), *args, **kwargs).execute() - def call_firstresult(self, methname, *args, **kwargs): """ return first non-None result of a plugin method. """ return MultiCall(self.listattr(methname), *args, **kwargs).execute(firstresult=True) Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Thu Apr 9 23:09:54 2009 @@ -97,29 +97,6 @@ assert not registry.isregistered(my) assert list(registry) == [my2] - def test_call_methods(self): - plugins = Registry() - class api1: - def m(self, __call__, x): - return x - class api2: - def m(self, __call__, x, y=33): - return y - plugins.register(api1()) - plugins.register(api2()) - res = plugins.call_firstresult("m", x=5) - assert plugins.call_firstresult("notexist") is None - - assert res == 33 - reslist = plugins.call_each("m", x=5) - assert len(reslist) == 2 - assert 5 in reslist - assert 33 in reslist - assert plugins.call_each("notexist") == [] - - assert plugins.call_plugin(api1(), 'm', x=12) == 12 - assert plugins.call_plugin(api2(), 't') is None - def test_call_none_is_no_result(self): plugins = Registry() class api1: @@ -132,7 +109,6 @@ plugins.register(api1()) plugins.register(api2()) assert plugins.call_firstresult('m') == 41 - assert plugins.call_each('m') == [41] def test_call_noneasresult(self): plugins = Registry() @@ -142,7 +118,6 @@ plugins.register(api1()) plugins.register(api1()) assert plugins.call_firstresult('m') is None - assert plugins.call_each('m') == [None, None] def test_listattr(self): plugins = Registry() From mzeidler at codespeak.net Fri Apr 10 13:12:08 2009 From: mzeidler at codespeak.net (mzeidler at codespeak.net) Date: Fri, 10 Apr 2009 13:12:08 +0200 (CEST) Subject: [py-svn] r63932 - py/trunk/contrib/pytest_twisted Message-ID: <20090410111208.26CDB16856B@codespeak.net> Author: mzeidler Date: Fri Apr 10 13:12:04 2009 New Revision: 63932 Modified: py/trunk/contrib/pytest_twisted/__init__.py Log: * Fixed broken setup_method()-mechanism. * Added --twisted-logging option. * Added example test-script to the plugin-test. Modified: py/trunk/contrib/pytest_twisted/__init__.py ============================================================================== --- py/trunk/contrib/pytest_twisted/__init__.py (original) +++ py/trunk/contrib/pytest_twisted/__init__.py Fri Apr 10 13:12:04 2009 @@ -1,15 +1,16 @@ """ +Notes: twisted's asynchrone behavior may have influence on the order of test-functions + TODO: - + switching on this plugin breaks 'setup_method' mechanism which then is not previously called before the test function. + credits to Ralf Schmitt See: http://twistedmatrix.com/pipermail/twisted-python/2007-February/014872.html - + add option for twisted logging - + write tests + + get test to work """ import os import sys import py + try: from twisted.internet.defer import Deferred except ImportError: @@ -22,10 +23,10 @@ "http://pypi.python.org/pypi/greenlet" sys.exit(0) -DIR_CUR = str(py.path.local()) - def _start_twisted_logging(): + """Enables twisted internal logging""" + class Logger(object): """late-bound sys.stdout""" def write(self, msg): @@ -34,31 +35,38 @@ def flush(self): sys.stdout.flush() # sys.stdout will be changed by py.test later. - import twisted.python.log twisted.python.log.startLogging(Logger(), setStdout=0) -def _run_twisted(): - """greenlet: run twisted mainloop""" +def _run_twisted(logging=False): + """Start twisted mainloop and initialize recursive calling of doit().""" + from twisted.internet import reactor, defer from twisted.python import log, failure - failure.Failure.cleanFailure = lambda *args: None # make twisted copy traceback... - _start_twisted_logging() # XXX: add py.test option - - def doit(val): + # make twisted copy traceback... + failure.Failure.cleanFailure = lambda *args: None + if logging: + _start_twisted_logging() + # recursively called for each test-function/method due done() + def doit(val): # val always None + # switch context to wait that wrapper() passes back to test-method res = gr_tests.switch(val) if res is None: reactor.stop() return - + def done(res): - reactor.callLater(0.0, doit, None) + reactor.callLater(0.0, doit, None) # recursive call of doit() def err(res): reactor.callLater(0.0, doit, res) + # the test-function *may* return a deferred + # here the test-function will actually been called + # done() is finalizing a test-process by assureing recursive envoking + # of doit() defer.maybeDeferred(res).addCallback(done).addErrback(err) - + # initialy preparing the calling of doit() and starting the reactor reactor.callLater(0.0, doit, None) reactor.run() @@ -67,27 +75,42 @@ """Allows to test twisted applications with pytest.""" def pytest_addoption(self, parser): - parser.addoption("--twisted", dest="twisted", - help="Allows to test twisted applications with pytest.") + #parser.addoption("--twisted", dest="twisted", + # help="Allows to test twisted applications with pytest.") + + group = parser.addgroup('twisted options') + group.addoption('-T', action='store_true', default=False, + dest = 'twisted', + help="Allows to test twisted applications.") + group.addoption('--twisted-logging', action='store', default=False, + dest='twisted_logging', + help="switch on twisted internal logging") + self.twisted = False def pytest_configure(self, config): - twisted = config.getvalue("twisted") + twisted = config.getvalue("twisted") + twisted_logging = config.getvalue("twisted_logging") if twisted: - print "Twisted plugin switched on" - gr_twisted.switch() + self.twisted = True + gr_twisted.switch(twisted_logging) def pytest_unconfigure(self, config): - gr_twisted.switch(None) + if self.twisted: + gr_twisted.switch(None) def pytest_pyfunc_call(self, pyfuncitem, *args, **kwargs): - def wrapper(func): - res = func.obj() - if isinstance(res, Deferred): - res = gr_twisted.switch(func.obj) - if res: - res.raiseException() - return res - pyfuncitem = wrapper(pyfuncitem) + if self.twisted: + def wrapper(func): + """ + wrapper just to pass back (injecting) the test-function into + doit() by using a greenlet switch. + """ + if hasattr(func, 'obj'): + # XXX: what about **kwargs? + res = gr_twisted.switch(lambda: func.obj(*args)) + if res: + res.raiseException() + pyfuncitem = wrapper(pyfuncitem) gr_twisted = greenlet(_run_twisted) @@ -97,19 +120,87 @@ # plugin tests # =============================================================================== -# XXX: write test -''' def test_generic(plugintester): - plugintester.apicheck(EventlogPlugin) + plugintester.apicheck(TwistedPlugin) testdir = plugintester.testdir() - testdir.makepyfile(""" + testdir.makepyfile(''' def test_pass(): pass - """) - testdir.runpytest("--twisted") - s = testdir.tmpdir.join("event.log").read() - assert s.find("TestrunStart") != -1 - assert s.find("ItemTestReport") != -1 - assert s.find("TestrunFinish") != -1 -''' + from twisted.internet import defer, reactor + from twisted.python import failure + from twisted.python import log + + + def test_no_deferred(): + assert True is True + + def test_deferred(): + log.msg("test_deferred() called") + d = defer.Deferred() + def done(): + log.msg("test_deferred.done() CALLBACK DONE") + d.callback(None) + + reactor.callLater(2.5, done) + log.msg("test_deferred() returning deferred: %r" % (d,)) + return d + + def test_deferred2(): + log.msg("test_deferred2() called") + d = defer.Deferred() + def done(): + log.msg("test_deferred2.done() CALLBACK DONE") + d.callback(None) + + reactor.callLater(2.5, done) + log.msg("test_deferred2() returning deferred: %r" % (d,)) + return d + + def test_deferred4(): + log.msg("test_deferred4() called") + from twisted.web.client import getPage + def printContents(contents): + assert contents == "" + + deferred = getPage('http://twistedmatrix.com/') + deferred.addCallback(printContents) + return deferred + + def test_deferred3(): + log.msg("test_deferred3() called") + d = defer.Deferred() + def done(): + log.msg("test_deferred3.done() CALLBACK DONE") + d.callback(None) + + reactor.callLater(2.5, done) + log.msg("test_deferred3() returning deferred: %r" % (d,)) + return d + + class TestTwistedSetupMethod: + def setup_method(self, method): + log.msg("TestTwistedSetupMethod.setup_method() called") + + def test_deferred(self): + log.msg("TestTwistedSetupMethod.test_deferred() called") + d = defer.Deferred() + def done(): + log.msg("TestTwistedSetupMethod.test_deferred() CALLBACK DONE") + d.callback(None) + + reactor.callLater(2.5, done) + log.msg("TestTwistedSetupMethod.test_deferred() returning deferred: %r" % (d,)) + return d + + + def test_defer_fail(): + def fun(): + log.msg("provoking NameError") + rsdfg + return defer.maybeDeferred(fun) + ''') + testdir.runpytest("-T") + # XXX: what to do? + # s = testdir.tmpdir.join("event.log").read() + # assert s.find("TestrunFinish") != -1 From mzeidler at codespeak.net Fri Apr 10 15:02:36 2009 From: mzeidler at codespeak.net (mzeidler at codespeak.net) Date: Fri, 10 Apr 2009 15:02:36 +0200 (CEST) Subject: [py-svn] r63938 - py/trunk/contrib/pytest_coverage Message-ID: <20090410130236.E7757169DF6@codespeak.net> Author: mzeidler Date: Fri Apr 10 15:02:36 2009 New Revision: 63938 Modified: py/trunk/contrib/pytest_coverage/__init__.py Log: Changed methods pyevent_* to pytest_* due to the changed plugin-API. Modified: py/trunk/contrib/pytest_coverage/__init__.py ============================================================================== --- py/trunk/contrib/pytest_coverage/__init__.py (original) +++ py/trunk/contrib/pytest_coverage/__init__.py Fri Apr 10 15:02:36 2009 @@ -314,13 +314,15 @@ dest = os.path.join(dir_annotate_output, r) shutil.copy(src, dest) - def pyevent__collectionstart(self, collector): + def pytest_collectstart(self, collector): if isinstance(collector, py.__.test.pycollect.Module): COVERAGE_MODULES.update(getattr(collector.obj, 'COVERAGE_MODULES', [])) - def pyevent__testrunstart(self): + def pytest_testrunstart(self): + print "self.coverage", self.coverage if hasattr(self, 'coverage'): + print "START coverage" self.coverage.erase() self.coverage.start() From hpk at codespeak.net Sun Apr 12 09:48:37 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 12 Apr 2009 09:48:37 +0200 (CEST) Subject: [py-svn] r63989 - in py/trunk: . doc Message-ID: <20090412074837.7B7B416845A@codespeak.net> Author: hpk Date: Sun Apr 12 09:48:35 2009 New Revision: 63989 Removed: py/trunk/doc/links.txt Modified: py/trunk/TODO.txt py/trunk/doc/index.txt Log: small doc cleanup Modified: py/trunk/TODO.txt ============================================================================== --- py/trunk/TODO.txt (original) +++ py/trunk/TODO.txt Sun Apr 12 09:48:35 2009 @@ -1,14 +1,6 @@ Things to do for 1.0.0 ========================= -- 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 -------------- Modified: py/trunk/doc/index.txt ============================================================================== --- py/trunk/doc/index.txt (original) +++ py/trunk/doc/index.txt Sun Apr 12 09:48:35 2009 @@ -51,7 +51,6 @@ log misc coding-style - links contact download releases Deleted: /py/trunk/doc/links.txt ============================================================================== --- /py/trunk/doc/links.txt Sun Apr 12 09:48:35 2009 +++ (empty file) @@ -1,33 +0,0 @@ - -===== -Links -===== - -Some links to ongoing discussions and comments about pylib and technics/concepts pylib uses. - -* `Discussion `_ - about site-packages. That's why pylib autopath and py.__.misc.dynpkg are a good idea ;-) - - -* `Pyinotify `_ uses code from pypy autopath functions. - -* `Testing (WSGI) Applications with Paste `_ and py.test. "This has been written with py.test in mind." Paste uses py.test. - - -* `Agile Testing `_ by Grig Gheorghiu - - * `Slides from 'py library overview' presentation at SoCal Piggies meeting - `_ - - * `Python unit testing part 3: the py.test tool and library - `_ - - * `greenlets and py.xml - `_ - - * `Keyword-based logging with the py library - `_ - - - - From hpk at codespeak.net Sun Apr 12 10:08:03 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 12 Apr 2009 10:08:03 +0200 (CEST) Subject: [py-svn] r63990 - in py/trunk/doc: . announce Message-ID: <20090412080803.367F016845A@codespeak.net> Author: hpk Date: Sun Apr 12 10:08:02 2009 New Revision: 63990 Added: py/trunk/doc/announce/ py/trunk/doc/announce/release-0.9.0.txt - copied unchanged from r63989, py/trunk/doc/release-0.9.0.txt py/trunk/doc/announce/release-0.9.2.txt - copied unchanged from r63989, py/trunk/doc/release-0.9.2.txt py/trunk/doc/announce/release-1.0.0.txt - copied unchanged from r63989, py/trunk/doc/release-1.0.0.txt py/trunk/doc/test-funcargs.txt Removed: py/trunk/doc/release-0.9.0.txt py/trunk/doc/release-0.9.2.txt py/trunk/doc/release-1.0.0.txt Modified: py/trunk/doc/releases.txt py/trunk/doc/test-ext.txt Log: * adding XXXed funcargs docs, moving release announcements to own subdir Deleted: /py/trunk/doc/release-0.9.0.txt ============================================================================== --- /py/trunk/doc/release-0.9.0.txt Sun Apr 12 10:08:02 2009 +++ (empty file) @@ -1,7 +0,0 @@ -py lib 1.0.0: XXX -====================================================================== - -Welcome to the 1.0.0 py lib release - a library aiming to -support agile and test-driven python development on various levels. - -XXX Deleted: /py/trunk/doc/release-0.9.2.txt ============================================================================== --- /py/trunk/doc/release-0.9.2.txt Sun Apr 12 10:08:02 2009 +++ (empty file) @@ -1,27 +0,0 @@ -py lib 0.9.2: bugfix release -============================= - -Welcome to the 0.9.2 py lib and py.test release - -mainly fixing Windows issues, providing better -packaging and integration with setuptools. - -Here is a quick summary of what the py lib provides: - -* py.test: cross-project testing tool with many advanced features -* py.execnet: ad-hoc code distribution to SSH, Socket and local sub processes -* py.magic.greenlet: micro-threads on standard CPython ("stackless-light") -* py.path: path abstractions over local and subversion files -* rich documentation of py's exported API -* tested against Linux, Win32, OSX, works on python 2.3-2.6 - -See here for more information: - -Pypi pages: http://pypi.python.org/pypi/py/ - -Download/Install: http://codespeak.net/py/0.9.2/download.html - -Documentation/API: http://codespeak.net/py/0.9.2/index.html - -best and have fun, - -holger krekel Deleted: /py/trunk/doc/release-1.0.0.txt ============================================================================== --- /py/trunk/doc/release-1.0.0.txt Sun Apr 12 10:08:02 2009 +++ (empty file) @@ -1,24 +0,0 @@ -py lib 1.0.0: distributed testing and dynamic code deployment -=============================================================== - -XXX draft - -Welcome to the 1.0.0 py lib release - a python library aiming -to support agile and test-driven development. - -It passes tests against Linux, OSX and Win32, on Python -2.3, 2.4, 2.5 and 2.6. - -Main API/Tool Features: - -* py.test: cross-project testing tool with many advanced features -* py.execnet: ad-hoc code distribution to SSH, Socket and local sub processes -* py.code: support for dynamically running and debugging python code -* py.path: path abstractions over local and subversion files - -Download/Install: http://codespeak.net/py/1.0.0/download.html -Documentation/API: http://codespeak.net/py/1.0.0/index.html - -best, -holger - Modified: py/trunk/doc/releases.txt ============================================================================== --- py/trunk/doc/releases.txt (original) +++ py/trunk/doc/releases.txt Sun Apr 12 10:08:02 2009 @@ -7,6 +7,6 @@ .. toctree:: :maxdepth: 1 - release-1.0.0 - release-0.9.2 - release-0.9.0 + announce/release-1.0.0 + announce/release-0.9.2 + announce/release-0.9.0 Modified: py/trunk/doc/test-ext.txt ============================================================================== --- py/trunk/doc/test-ext.txt (original) +++ py/trunk/doc/test-ext.txt Sun Apr 12 10:08:02 2009 @@ -110,6 +110,11 @@ Plugin hooks and events ======================================= -See definitions at +.. automodule:: py.__.test.plugin.api + +.. autoclass:: PluginHooks + :members: + +.. autoclass:: Events + :members: -http://codespeak.net/svn/py/trunk/py/test/plugin/api.py Added: py/trunk/doc/test-funcargs.txt ============================================================================== --- (empty file) +++ py/trunk/doc/test-funcargs.txt Sun Apr 12 10:08:02 2009 @@ -0,0 +1,160 @@ + +===================================== +Python test function arguments +===================================== + +py.test enables a new way to separate test configuration +and test setup from actual test code in test functions. +When it runs a test functions it will lookup function +arguments by name and provide a value. +Here is a simple example for such a test function: + + def test_function(mysetup): + # work with mysetup + +To provide a value py.test looks for a ``pytest_funcargs`` +dictionary in the test module, for example:: + + class MySetup: + def __init__(self, pyfuncitem): + self.pyfuncitem = pyfuncitem + pytest_funcargs = {'mysetup': MySetup} + +This is already enough to run the test. Of course +up until now our ``mysetup`` does not provide +much value. But it is now easy to add new +methods on the ``MySetup`` class that have +full access to the test collection process. + +Plugins can register their funcargs via +the config object, usually upon initial configure:: + + class ConftestPlugin: + def pytest_configure(self, config): + config.register_funcargs(mysetup=MySetup) + +If you provide a "funcarg" from a plugin you can +easily make methods depend on command line options +or environment settings. Here is a complete +example that allows to run tests involving +an SSH connection if an ssh host is specified:: + + class ConftestPlugin: + def pytest_addoption(self, parser): + parser.addoption("--ssh", action="store", default=None, + help="specify ssh host to run tests with") + + def pytest_configure(self, config): + config.register_funcargs(mysetup=MySetup) + + class MySetup: + def __init__(self, pyfuncitem): + self.pyfuncitem = pyfuncitem + def ssh_gateway(self): + host = pyfuncitem.config.option.ssh + if host is None: + py.test.skip("specify ssh host with --ssh to run this test") + return py.execnet.SshGateway(host) + +Now any test functions can use the "mysetup" object, for example:: + + class TestClass: + def test_function(self, mysetup): + ssh_gw = mysetup.ssh_gateway() + # work with ssh_gw + +Without specifying a command line option the output looks like this:: + + ... + + +Lookup rules +====================== + +In order to run this test function a value for the +``mysetup`` needs to be found. Here is how py.test +finds a matching provider function: + +1. see if there is a ``pytest_funcargs`` dictionary + which maps ``mysetup`` to a provider function. + if so, call the provider function. + +XXX + + + +example +===================== + +You can run a test file ``test_some.py`` with this content: + + pytest_funcargs = {'myarg': (lambda pyfuncitem: 42)} + + def test_something(myarg): + assert myarg == 42 + +You can also put this on a class: + + class TestClass: + pytest_funcargs = {'myarg': (lambda pyfuncitem: 42)} + + def test_something(self, myarg): + assert myarg == 42 + +To separate funcarg setup you can also put a funcarg +definition into a conftest.py:: + + pytest_funcargs = {'myarg': decorate_myarg} + def decorate_myarg(pyfuncitem): + result = pyfuncitem.call_next_provider() + return result + 1 + +for registering funcargs from a plugin, talk to the +test configuration object like this:: + + class MyPlugin: + def pytest_configure(self, config): + config.register_funcargs( + myarg=decorate_myarg + ) + +a local helper funcarg for doing acceptance tests maybe +by running shell commands could look like this:: + + class MyPlugin: + def pytest_option(self, parser): + group = parser.addgroup("myproject acceptance tests") + group.addoption("-A", dest="acceptance", action="store_true", + help="run (slow) acceptance tests") + + def pytest_configure(self, config): + config.register_funcargs(accept=AcceptFuncarg) + + class AcceptFuncarg: + def __init__(self, pyfuncitem): + if not pyfuncitem.config.option.acceptance: + py.test.skip("specify -A to run acceptance tests") + self.tmpdir = pyfuncitem.config.maketempdir(pyfuncitem.name) + self._old = self.tmpdir.chdir() + pyfuncitem.addfinalizer(self.finalize) + + def run(self): + return py.process.cmdexec("echo hello") + + def finalize(self): + self._old.chdir() + # cleanup any other resources + +and the actual test function example: + + def test_some_acceptance_aspect(accept): + accept.tmpdir.mkdir("somesub") + result = accept.run() + assert result + +for registering funcargs from a plugin, talk to the +test configuration object like this:: + + XXX + + From hpk at codespeak.net Sun Apr 12 10:21:50 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 12 Apr 2009 10:21:50 +0200 (CEST) Subject: [py-svn] r63991 - py/trunk Message-ID: <20090412082150.6D3C8168477@codespeak.net> Author: hpk Date: Sun Apr 12 10:21:47 2009 New Revision: 63991 Added: py/trunk/.hgignore Log: adding ignore file Added: py/trunk/.hgignore ============================================================================== --- (empty file) +++ py/trunk/.hgignore Sun Apr 12 10:21:47 2009 @@ -0,0 +1,11 @@ + +# Automatically generated by `hgimportsvn` +syntax:glob +.svn +.hgsvn + +# These lines are suggested according to the svn:ignore property +# Feel free to enable them by uncommenting them +syntax:glob +*.pyc +*.pyo From hpk at codespeak.net Sun Apr 12 22:58:34 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 12 Apr 2009 22:58:34 +0200 (CEST) Subject: [py-svn] r63996 - py/trunk/doc Message-ID: <20090412205834.8CB8516845A@codespeak.net> Author: hpk Date: Sun Apr 12 22:58:32 2009 New Revision: 63996 Modified: py/trunk/doc/download.txt py/trunk/doc/impl-test.txt Log: here it comes to svn Modified: py/trunk/doc/download.txt ============================================================================== --- py/trunk/doc/download.txt (original) +++ py/trunk/doc/download.txt Sun Apr 12 22:58:32 2009 @@ -2,7 +2,6 @@ Downloading ============== - "easy_install py" =================================================== Modified: py/trunk/doc/impl-test.txt ============================================================================== --- py/trunk/doc/impl-test.txt (original) +++ py/trunk/doc/impl-test.txt Sun Apr 12 22:58:32 2009 @@ -4,7 +4,6 @@ XXX REVIEW and remove the below XXX - Customizing the testing process =============================== From hpk at codespeak.net Mon Apr 13 01:05:11 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 13 Apr 2009 01:05:11 +0200 (CEST) Subject: [py-svn] r64000 - in py/trunk: . py Message-ID: <20090412230511.2A0E2168538@codespeak.net> Author: hpk Date: Mon Apr 13 01:05:08 2009 New Revision: 64000 Removed: py/trunk/ez_setup.py Modified: py/trunk/MANIFEST py/trunk/py/__init__.py py/trunk/setup.py Log: can't bear the setuptools problems anymore. trying to switch away from it. Modified: py/trunk/MANIFEST ============================================================================== --- py/trunk/MANIFEST (original) +++ py/trunk/MANIFEST Mon Apr 13 01:05:08 2009 @@ -1,3 +1,4 @@ +.hgignore CHANGELOG LICENSE MANIFEST @@ -125,11 +126,12 @@ py/log/testing/__init__.py py/log/testing/test_log.py py/log/testing/test_logger.py +py/log/testing/test_warning.py +py/log/warning.py py/magic/__init__.py py/magic/assertion.py py/magic/autopath.py py/magic/exprinfo.py -py/magic/greenlet.py py/magic/invoke.py py/magic/patch.py py/magic/testing/__init__.py @@ -164,8 +166,6 @@ py/misc/testing/test_std.py py/misc/testing/test_svnlook.py py/misc/testing/test_terminal.py -py/misc/testing/test_warn.py -py/misc/warn.py py/path/__init__.py py/path/common.py py/path/gateway/TODO.txt @@ -248,7 +248,6 @@ py/test/dist/testing/test_nodemanage.py py/test/dist/testing/test_txnode.py py/test/dist/txnode.py -py/test/event.py py/test/looponfail/__init__.py py/test/looponfail/remote.py py/test/looponfail/testing/__init__.py @@ -258,7 +257,9 @@ py/test/outcome.py py/test/parseopt.py py/test/plugin/__init__.py +py/test/plugin/api.py py/test/plugin/conftest.py +py/test/plugin/pytest__pytest.py py/test/plugin/pytest_default.py py/test/plugin/pytest_doctest.py py/test/plugin/pytest_eventlog.py @@ -266,6 +267,7 @@ py/test/plugin/pytest_figleaf.py py/test/plugin/pytest_iocapture.py py/test/plugin/pytest_monkeypatch.py +py/test/plugin/pytest_pdb.py py/test/plugin/pytest_plugintester.py py/test/plugin/pytest_pocoo.py py/test/plugin/pytest_pylint.py @@ -273,12 +275,13 @@ py/test/plugin/pytest_restdoc.py py/test/plugin/pytest_resultdb.py py/test/plugin/pytest_resultlog.py +py/test/plugin/pytest_runner.py py/test/plugin/pytest_terminal.py py/test/plugin/pytest_tmpdir.py py/test/plugin/pytest_unittest.py py/test/plugin/pytest_xfail.py +py/test/pluginmanager.py py/test/pycollect.py -py/test/pytestplugin.py py/test/runner.py py/test/session.py py/test/testing/__init__.py @@ -298,12 +301,13 @@ py/test/testing/test_outcome.py py/test/testing/test_parseopt.py py/test/testing/test_pickling.py +py/test/testing/test_pluginmanager.py py/test/testing/test_pycollect.py -py/test/testing/test_pytestplugin.py py/test/testing/test_recording.py +py/test/testing/test_runner.py py/test/testing/test_runner_functional.py py/test/testing/test_session.py -py/test/testing/test_setup_nested.py +py/test/testing/test_setup_functional.py py/test/testing/test_traceback.py py/test/web/__init__.py py/test/web/exception.py Deleted: /py/trunk/ez_setup.py ============================================================================== --- /py/trunk/ez_setup.py Mon Apr 13 01:05:08 2009 +++ (empty file) @@ -1,272 +0,0 @@ -#!python -"""Bootstrap setuptools installation - -If you want to use setuptools in your package's setup.py, just include this -file in the same directory with it, and add this to the top of your setup.py:: - - from ez_setup import use_setuptools - use_setuptools() - -If you want to require a specific version of setuptools, set a download -mirror, or use an alternate download directory, you can do so by supplying -the appropriate options to ``use_setuptools()``. - -This file can also be run as a script to install or upgrade setuptools. -""" -import sys -DEFAULT_VERSION = "0.6c8" -DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] - -md5_data = { - 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', - 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', - 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', - 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', - 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', - 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', - 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', - 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', - 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', - 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', - 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', - 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', - 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', - 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', - 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', - 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', - 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', - 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', - 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', - 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', - 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', - 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', - 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', - 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', - 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', - 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', - 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', - 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', - 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', - 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', -} - -import sys, os - -def _validate_md5(egg_name, data): - if egg_name in md5_data: - from md5 import md5 - digest = md5(data).hexdigest() - if digest != md5_data[egg_name]: - print >>sys.stderr, ( - "md5 validation of %s failed! (Possible download problem?)" - % egg_name - ) - sys.exit(2) - return data - - -def use_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, - download_delay=15 -): - """Automatically find/download setuptools and make it available on sys.path - - `version` should be a valid setuptools version number that is available - as an egg for download under the `download_base` URL (which should end with - a '/'). `to_dir` is the directory where setuptools will be downloaded, if - it is not already available. If `download_delay` is specified, it should - be the number of seconds that will be paused before initiating a download, - should one be required. If an older version of setuptools is installed, - this routine will print a message to ``sys.stderr`` and raise SystemExit in - an attempt to abort the calling script. - """ - was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules - def do_download(): - egg = download_setuptools(version, download_base, to_dir, download_delay) - sys.path.insert(0, egg) - import setuptools; setuptools.bootstrap_install_from = egg - try: - import pkg_resources - except ImportError: - return do_download() - try: - pkg_resources.require("setuptools>="+version); return - except pkg_resources.VersionConflict, e: - if was_imported: - print >>sys.stderr, ( - "The required version of setuptools (>=%s) is not available, and\n" - "can't be installed while this script is running. Please install\n" - " a more recent version first, using 'easy_install -U setuptools'." - "\n\n(Currently using %r)" - ) % (version, e.args[0]) - sys.exit(2) - else: - del pkg_resources, sys.modules['pkg_resources'] # reload ok - return do_download() - except pkg_resources.DistributionNotFound: - return do_download() - -def download_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, - delay = 15 -): - """Download setuptools from a specified location and return its filename - - `version` should be a valid setuptools version number that is available - as an egg for download under the `download_base` URL (which should end - with a '/'). `to_dir` is the directory where the egg will be downloaded. - `delay` is the number of seconds to pause before an actual download attempt. - """ - import urllib2, shutil - egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) - url = download_base + egg_name - saveto = os.path.join(to_dir, egg_name) - src = dst = None - if not os.path.exists(saveto): # Avoid repeated downloads - try: - from distutils import log - if delay: - log.warn(""" ---------------------------------------------------------------------------- -This script requires setuptools version %s to run (even to display -help). I will attempt to download it for you (from -%s), but -you may need to enable firewall access for this script first. -I will start the download in %d seconds. - -(Note: if this machine does not have network access, please obtain the file - - %s - -and place it in this directory before rerunning this script.) ----------------------------------------------------------------------------""", - version, download_base, delay, url - ); from time import sleep; sleep(delay) - log.warn("Downloading %s", url) - src = urllib2.urlopen(url) - # Read/write all in one block, so we don't create a corrupt file - # if the download is interrupted. - data = _validate_md5(egg_name, src.read()) - dst = open(saveto,"wb"); dst.write(data) - finally: - if src: src.close() - if dst: dst.close() - return os.path.realpath(saveto) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -def main(argv, version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" - try: - import setuptools - except ImportError: - egg = None - try: - egg = download_setuptools(version, delay=0) - sys.path.insert(0,egg) - from setuptools.command.easy_install import main - return main(list(argv)+[egg]) # we're done here - finally: - if egg and os.path.exists(egg): - os.unlink(egg) - else: - if setuptools.__version__ == '0.0.1': - print >>sys.stderr, ( - "You have an obsolete version of setuptools installed. Please\n" - "remove it from your system entirely before rerunning this script." - ) - sys.exit(2) - - req = "setuptools>="+version - import pkg_resources - try: - pkg_resources.require(req) - except pkg_resources.VersionConflict: - try: - from setuptools.command.easy_install import main - except ImportError: - from easy_install import main - main(list(argv)+[download_setuptools(delay=0)]) - sys.exit(0) # try to force an exit - else: - if argv: - from setuptools.command.easy_install import main - main(argv) - else: - print "Setuptools version",version,"or greater has been installed." - print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' - -def update_md5(filenames): - """Update our built-in md5 registry""" - - import re - from md5 import md5 - - for name in filenames: - base = os.path.basename(name) - f = open(name,'rb') - md5_data[base] = md5(f.read()).hexdigest() - f.close() - - data = [" %r: %r,\n" % it for it in md5_data.items()] - data.sort() - repl = "".join(data) - - import inspect - srcfile = inspect.getsourcefile(sys.modules[__name__]) - f = open(srcfile, 'rb'); src = f.read(); f.close() - - match = re.search("\nmd5_data = {\n([^}]+)}", src) - if not match: - print >>sys.stderr, "Internal error!" - sys.exit(2) - - src = src[:match.start(1)] + repl + src[match.end(1):] - f = open(srcfile,'w') - f.write(src) - f.close() - - -if __name__=='__main__': - if len(sys.argv)>2 and sys.argv[1]=='--md5update': - update_md5(sys.argv[2:]) - else: - main(sys.argv[1:]) - - - - - Modified: py/trunk/py/__init__.py ============================================================================== --- py/trunk/py/__init__.py (original) +++ py/trunk/py/__init__.py Mon Apr 13 01:05:08 2009 @@ -21,7 +21,7 @@ """ from initpkg import initpkg -version = "1.0.0b1" +version = "1.0.0b2" initpkg(__name__, description = "pylib and py.test: agile development and test support library", Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Mon Apr 13 01:05:08 2009 @@ -1,15 +1,13 @@ """ setup file for 'py' package based on: - https://codespeak.net/svn/py/trunk, revision=63545 + https://codespeak.net/svn/py/trunk, revision=63999 autogenerated by gensetup.py """ import os, sys -import ez_setup -ez_setup.use_setuptools() -from setuptools import setup, Extension +from distutils.core import setup, Extension long_description = """ @@ -37,21 +35,22 @@ name='py', description='pylib and py.test: agile development and test support library', long_description = long_description, - version='1.0.0b1', + version='1.0.0b2', url='http://pylib.org', - download_url='http://codespeak.net/py/1.0.0b1/download.html', + download_url='http://codespeak.net/py/1.0.0b2/download.html', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others', author_email='holger at merlinux.eu, py-dev at codespeak.net', - entry_points={'console_scripts': ['py.cleanup = py.cmdline:pycleanup', - 'py.countloc = py.cmdline:pycountloc', - 'py.lookup = py.cmdline:pylookup', - 'py.rest = py.cmdline:pyrest', - 'py.svnwcrevert = py.cmdline:pysvnwcrevert', - 'py.test = py.cmdline:pytest', - 'py.which = py.cmdline:pywhich']}, + py_modules=['_findpy'], + scripts=['py/bin/py.cleanup', + 'py/bin/py.countloc', + 'py/bin/py.lookup', + 'py/bin/py.rest', + 'py/bin/py.svnwcrevert', + 'py/bin/py.test', + 'py/bin/py.which'], classifiers=['Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', @@ -147,7 +146,6 @@ 'rest/testing/data/part1.txt', 'rest/testing/data/part2.txt', 'rest/testing/data/tocdepth.rst2pdfconfig']}, - zip_safe=False, ) if __name__ == '__main__': From fijal at codespeak.net Mon Apr 13 01:20:26 2009 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Apr 2009 01:20:26 +0200 (CEST) Subject: [py-svn] r64002 - py/dist/py/code Message-ID: <20090412232026.BD1BC16851E@codespeak.net> Author: fijal Date: Mon Apr 13 01:20:26 2009 New Revision: 64002 Modified: py/dist/py/code/frame.py Log: if code.fullsource is None (which can happen), expr is also empty Modified: py/dist/py/code/frame.py ============================================================================== --- py/dist/py/code/frame.py (original) +++ py/dist/py/code/frame.py Mon Apr 13 01:20:26 2009 @@ -13,6 +13,8 @@ self.raw = frame def statement(self): + if self.code.fullsource is None: + return py.code.Source("") return self.code.fullsource.getstatement(self.lineno) statement = property(statement, None, None, "statement this frame is at") From fijal at codespeak.net Mon Apr 13 01:23:33 2009 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Apr 2009 01:23:33 +0200 (CEST) Subject: [py-svn] r64003 - py/dist/py/code/testing Message-ID: <20090412232333.651A116852B@codespeak.net> Author: fijal Date: Mon Apr 13 01:23:32 2009 New Revision: 64003 Modified: py/dist/py/code/testing/test_frame.py Log: A test for 64002 Modified: py/dist/py/code/testing/test_frame.py ============================================================================== --- py/dist/py/code/testing/test_frame.py (original) +++ py/dist/py/code/testing/test_frame.py Mon Apr 13 01:23:32 2009 @@ -9,6 +9,14 @@ source, lineno = f.code.fullsource, f.lineno assert source[lineno].startswith(" return sys._getframe(0)") +def test_getstatement_empty_fullsource(): + def func(): + return sys._getframe(0) + f = func() + f = py.code.Frame(f) + f.code.fullsource = None + assert f.statement(13) == py.code.Source("") + def test_code_from_func(): co = py.code.Code(test_frame_getsourcelineno_myself) assert co.firstlineno From fijal at codespeak.net Mon Apr 13 01:25:55 2009 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Apr 2009 01:25:55 +0200 (CEST) Subject: [py-svn] r64004 - py/dist/py/code/testing Message-ID: <20090412232555.AE4C3168545@codespeak.net> Author: fijal Date: Mon Apr 13 01:25:52 2009 New Revision: 64004 Modified: py/dist/py/code/testing/test_frame.py Log: sorry, fix the test Modified: py/dist/py/code/testing/test_frame.py ============================================================================== --- py/dist/py/code/testing/test_frame.py (original) +++ py/dist/py/code/testing/test_frame.py Mon Apr 13 01:25:52 2009 @@ -14,8 +14,12 @@ return sys._getframe(0) f = func() f = py.code.Frame(f) - f.code.fullsource = None - assert f.statement(13) == py.code.Source("") + prop = f.code.__class__.fullsource + try: + f.code.__class__.fullsource = None + assert f.statement == py.code.Source("") + finally: + f.code.__class__.fullsource = prop def test_code_from_func(): co = py.code.Code(test_frame_getsourcelineno_myself) From fijal at codespeak.net Mon Apr 13 01:39:00 2009 From: fijal at codespeak.net (fijal at codespeak.net) Date: Mon, 13 Apr 2009 01:39:00 +0200 (CEST) Subject: [py-svn] r64007 - in py/trunk/py/code: . testing Message-ID: <20090412233900.CBF55168568@codespeak.net> Author: fijal Date: Mon Apr 13 01:39:00 2009 New Revision: 64007 Modified: py/trunk/py/code/frame.py py/trunk/py/code/testing/test_frame.py Log: port 64001-64004 to trunk Modified: py/trunk/py/code/frame.py ============================================================================== --- py/trunk/py/code/frame.py (original) +++ py/trunk/py/code/frame.py Mon Apr 13 01:39:00 2009 @@ -13,6 +13,8 @@ self.raw = frame def statement(self): + if self.code.fullsource is None: + return py.code.Source("") return self.code.fullsource.getstatement(self.lineno) statement = property(statement, None, None, "statement this frame is at") Modified: py/trunk/py/code/testing/test_frame.py ============================================================================== --- py/trunk/py/code/testing/test_frame.py (original) +++ py/trunk/py/code/testing/test_frame.py Mon Apr 13 01:39:00 2009 @@ -9,6 +9,18 @@ source, lineno = f.code.fullsource, f.lineno assert source[lineno].startswith(" return sys._getframe(0)") +def test_getstatement_empty_fullsource(): + def func(): + return sys._getframe(0) + f = func() + f = py.code.Frame(f) + prop = f.code.__class__.fullsource + try: + f.code.__class__.fullsource = None + assert f.statement == py.code.Source("") + finally: + f.code.__class__.fullsource = prop + def test_code_from_func(): co = py.code.Code(test_frame_getsourcelineno_myself) assert co.firstlineno From hpk at codespeak.net Mon Apr 13 14:54:32 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 13 Apr 2009 14:54:32 +0200 (CEST) Subject: [py-svn] r64027 - in py/trunk/py/test: . testing Message-ID: <20090413125432.F0B57168557@codespeak.net> Author: hpk Date: Mon Apr 13 14:54:32 2009 New Revision: 64027 Modified: py/trunk/py/test/parseopt.py py/trunk/py/test/testing/test_parseopt.py Log: make parser.getgroup create a group if it doesn't exist Modified: py/trunk/py/test/parseopt.py ============================================================================== --- py/trunk/py/test/parseopt.py (original) +++ py/trunk/py/test/parseopt.py Mon Apr 13 14:54:32 2009 @@ -38,11 +38,11 @@ self._groups.append(group) return group - def getgroup(self, name): + def getgroup(self, name, description=""): for group in self._groups: if group.name == name: return group - raise ValueError("group %r not found" %(name,)) + return self.addgroup(name, description) def addoption(self, *opts, **attrs): """ add an optparse-style option. """ Modified: py/trunk/py/test/testing/test_parseopt.py ============================================================================== --- py/trunk/py/test/testing/test_parseopt.py (original) +++ py/trunk/py/test/testing/test_parseopt.py Mon Apr 13 14:54:32 2009 @@ -18,7 +18,15 @@ py.test.raises(ValueError, parser.addgroup, "hello") group2 = parser.getgroup("hello") assert group2 is group - py.test.raises(ValueError, parser.getgroup, 'something') + + def test_getgroup_addsgroup(self): + parser = parseopt.Parser() + group = parser.getgroup("hello", description="desc") + assert group.name == "hello" + assert group.description == "desc" + group2 = parser.getgroup("hello") + assert group2 is group + def test_group_addoption(self): group = parseopt.OptionGroup("hello") From hpk at codespeak.net Mon Apr 13 14:54:59 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 13 Apr 2009 14:54:59 +0200 (CEST) Subject: [py-svn] r64028 - in py/trunk: doc py/test/plugin Message-ID: <20090413125459.383D8169E4E@codespeak.net> Author: hpk Date: Mon Apr 13 14:54:58 2009 New Revision: 64028 Modified: py/trunk/doc/path.txt py/trunk/py/test/plugin/pytest_restdoc.py Log: use pygments for syntax-coloring python code and console Modified: py/trunk/doc/path.txt ============================================================================== --- py/trunk/doc/path.txt (original) +++ py/trunk/doc/path.txt Mon Apr 13 14:54:58 2009 @@ -20,7 +20,9 @@ number of modules. Example usage, here we use the :api:`py.test.ensuretemp()` function to create -a :api:`py.path.local` object for us (which wraps a directory):: +a :api:`py.path.local` object for us (which wraps a directory): + +.. sourcecode:: pycon >>> import py >>> temppath = py.test.ensuretemp('py.path_documentation') @@ -46,7 +48,9 @@ versioning, and both in a way more user-friendly manner than existing other solutions. -Some example usage of :api:`py.path.svnurl`:: +Some example usage of :api:`py.path.svnurl`: + +.. sourcecode:: pycon .. >>> import py .. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk') @@ -59,7 +63,9 @@ >>> time.strftime('%Y-%m-%d', time.gmtime(firstentry.date)) '2004-10-02' -Example usage of :api:`py.path.svnwc`:: +Example usage of :api:`py.path.svnwc`: + +.. sourcecode:: pycon .. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk') >>> temp = py.test.ensuretemp('py.path_documentation') @@ -98,7 +104,7 @@ Search for a particular string inside all files with a .txt extension in a specific directory. -:: +.. sourcecode:: pycon >>> dirpath = temppath.ensure('testdir', dir=True) >>> dirpath.join('textfile1.txt').write('foo bar baz') @@ -120,7 +126,9 @@ This example shows the :api:`py.path` features to deal with filesystem paths Note that the filesystem is never touched, all operations are performed on a string level (so the paths -don't have to exist, either):: +don't have to exist, either): + +.. sourcecode:: pycon >>> p1 = py.path.local('/foo/bar') >>> p2 = p1.join('baz/qux') @@ -153,7 +161,9 @@ ....................... Now we will show a bit about the powerful 'check()' method on paths, which -allows you to check whether a file exists, what type it is, etc.:: +allows you to check whether a file exists, what type it is, etc.: + +.. sourcecode:: pycon >>> file1 = temppath.join('file1') >>> file1.check() # does it exist? @@ -177,7 +187,9 @@ ....................... As an example of 'uncommon' methods, we'll show how to read and write -properties in an :api:`py.path.svnwc` instance:: +properties in an :api:`py.path.svnwc` instance: + +.. sourcecode:: pycon .. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk') >>> wc.propget('foo') @@ -195,7 +207,9 @@ ....................... Some uncommon functionality can also be provided as extensions, such as SVN -authentication:: +authentication: + +.. sourcecode:: pycon .. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk') >>> auth = py.path.SvnAuth('anonymous', 'user', cache_auth=False, Modified: py/trunk/py/test/plugin/pytest_restdoc.py ============================================================================== --- py/trunk/py/test/plugin/pytest_restdoc.py (original) +++ py/trunk/py/test/plugin/pytest_restdoc.py Mon Apr 13 14:54:58 2009 @@ -96,6 +96,47 @@ toctree_directive.options = {'maxdepth': int, 'glob': directives.flag, 'hidden': directives.flag} directives.register_directive('toctree', toctree_directive) + self.register_pygments() + + def register_pygments(self): + # taken from pygments-main/external/rst-directive.py + try: + from pygments.formatters import HtmlFormatter + except ImportError: + def pygments_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + return [] + else: + # The default formatter + DEFAULT = HtmlFormatter(noclasses=True) + # Add name -> formatter pairs for every variant you want to use + VARIANTS = { + # 'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=True), + } + + from docutils import nodes + from docutils.parsers.rst import directives + + from pygments import highlight + from pygments.lexers import get_lexer_by_name, TextLexer + + def pygments_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + try: + lexer = get_lexer_by_name(arguments[0]) + except ValueError: + # no lexer found - use the text one instead of an exception + lexer = TextLexer() + # take an arbitrary option if more than one is given + formatter = options and VARIANTS[options.keys()[0]] or DEFAULT + parsed = highlight(u'\n'.join(content), lexer, formatter) + return [nodes.raw('', parsed, format='html')] + + pygments_directive.arguments = (1, 0, 1) + pygments_directive.content = 1 + pygments_directive.options = dict([(key, directives.flag) for key in VARIANTS]) + + directives.register_directive('sourcecode', pygments_directive) def resolve_linkrole(self, name, text, check=True): apigen_relpath = self.project.apigen_relpath From hpk at codespeak.net Mon Apr 13 15:58:28 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 13 Apr 2009 15:58:28 +0200 (CEST) Subject: [py-svn] r64029 - in py: attic attic/sphinx-support attic/sphinx-support/_templates trunk/doc trunk/doc/_static trunk/doc/_templates trunk/doc/announce trunk/doc/test Message-ID: <20090413135828.07422169E82@codespeak.net> Author: hpk Date: Mon Apr 13 15:58:26 2009 New Revision: 64029 Added: py/attic/conf.py - copied unchanged from r64028, py/trunk/doc/conf.py py/attic/roles.py - copied unchanged from r64028, py/trunk/doc/roles.py py/attic/sphinx-support/ py/attic/sphinx-support/Makefile - copied unchanged from r64028, py/trunk/doc/Makefile py/attic/sphinx-support/_templates/ - copied from r64028, py/trunk/doc/_templates/ py/attic/sphinx-support/make.bat - copied unchanged from r64028, py/trunk/doc/make.bat py/trunk/doc/announce/releases.txt - copied unchanged from r64028, py/trunk/doc/releases.txt py/trunk/doc/test/ (props changed) py/trunk/doc/test/attic.txt - copied, changed from r64028, py/trunk/doc/impl-test.txt py/trunk/doc/test/config.txt - copied unchanged from r64028, py/trunk/doc/test-config.txt py/trunk/doc/test/dist.txt - copied, changed from r64028, py/trunk/doc/test-dist.txt py/trunk/doc/test/examples.txt - copied unchanged from r64028, py/trunk/doc/test-examples.txt py/trunk/doc/test/ext.txt - copied, changed from r64028, py/trunk/doc/test-ext.txt py/trunk/doc/test/features.txt - copied, changed from r64028, py/trunk/doc/test-features.txt py/trunk/doc/test/funcargs.txt - copied unchanged from r64028, py/trunk/doc/test-funcargs.txt py/trunk/doc/test/plugins.txt - copied unchanged from r64028, py/trunk/doc/test-plugins.txt py/trunk/doc/test/quickstart.txt - copied, changed from r64028, py/trunk/doc/test-quickstart.txt py/trunk/doc/test/statemanage.txt - copied unchanged from r64028, py/trunk/doc/test-statemanage.txt py/trunk/doc/test/test.txt - copied, changed from r64028, py/trunk/doc/test.txt Removed: py/trunk/doc/Makefile py/trunk/doc/__init__.py py/trunk/doc/_static/ py/trunk/doc/_templates/ py/trunk/doc/coding-style.txt py/trunk/doc/conf.py py/trunk/doc/impl-test.txt py/trunk/doc/make.bat py/trunk/doc/releases.txt py/trunk/doc/roles.py py/trunk/doc/test-config.txt py/trunk/doc/test-dist.txt py/trunk/doc/test-examples.txt py/trunk/doc/test-ext.txt py/trunk/doc/test-features.txt py/trunk/doc/test-funcargs.txt py/trunk/doc/test-plugins.txt py/trunk/doc/test-quickstart.txt py/trunk/doc/test-statemanage.txt py/trunk/doc/test.txt Modified: py/trunk/doc/announce/ (props changed) py/trunk/doc/bin.txt py/trunk/doc/confrest.py py/trunk/doc/conftest.py py/trunk/doc/contact.txt py/trunk/doc/index.txt py/trunk/doc/xml.txt Log: * move test docs into their own subdir * don't use sphinx for now, put the support code to attic * remove some old docs Deleted: /py/trunk/doc/Makefile ============================================================================== --- /py/trunk/doc/Makefile Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,94 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " htmlall to force make all standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf _build/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html - @echo - @echo "Build finished. The HTML pages are in _build/html." - -htmlall: - $(SPHINXBUILD) -a -b html $(ALLSPHINXOPTS) _build/html - @echo - @echo "Build finished. The HTML pages are in _build/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml - @echo - @echo "Build finished. The HTML pages are in _build/dirhtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in _build/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in _build/qthelp, like this:" - @echo "# qcollectiongenerator _build/qthelp/py lib.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile _build/qthelp/py lib.qhc" - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex - @echo - @echo "Build finished; the LaTeX files are in _build/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes - @echo - @echo "The overview file is in _build/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in _build/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in _build/doctest/output.txt." Deleted: /py/trunk/doc/__init__.py ============================================================================== --- /py/trunk/doc/__init__.py Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1 +0,0 @@ -# Modified: py/trunk/doc/bin.txt ============================================================================== --- py/trunk/doc/bin.txt (original) +++ py/trunk/doc/bin.txt Mon Apr 13 15:58:26 2009 @@ -15,7 +15,7 @@ The ``py.test`` executable is the main entry point into the py-lib testing tool, see the `py.test documentation`_. -.. _`py.test documentation`: test.html +.. _`py.test documentation`: test/test.html ``py.cleanup`` ============== Deleted: /py/trunk/doc/coding-style.txt ============================================================================== --- /py/trunk/doc/coding-style.txt Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,65 +0,0 @@ -===================================================== -Coding Style for the Py lib and friendly applications -===================================================== - - -Honour PEP 8: Style Guide for Python Code ------------------------------------------ - -First of all, if you haven't already read it, read the `PEP 8 -Style Guide for Python Code`_ which, if in doubt, serves as -the default coding-style for the py lib. - -Documentation and Testing -------------------------- - -- generally we want to drive and interweave coding of - documentation, tests and real code as much as possible. - Without good documentation others may never know about - your latest and greatest feature. - -naming ------- - -- directories, modules and namespaces are always **lowercase** - -- classes and especially Exceptions are most often **CamelCase** - -- types, i.e. very widely usable classes like the ``py.path`` - family are all lower case. - -- never use plural names in directory and file names - -- functions/methods are lowercase and ``_`` - separated if - you really need to separate at all - -- it's appreciated if you manage to name files in a directory - so that tab-completion on the shell level is as easy as possible. - - -committing ----------- - -- adding features requires adding appropriate tests. - -- bug fixes should be encoded in a test before being fixed. - -- write telling log messages because several people - will read your diffs, and we plan to have a search facility - over the py lib's subversion repository. - -- if you add ``.txt`` or ``.py`` files to the repository then - please make sure you have ``svn:eol-style`` set to native. - which allows checkin/checkout in native line-ending format. - -Miscellaneous -------------- - -- Tests are the insurance that your code will be maintained - further and survives major releases. - -- Try to put the tests close to the tested code, don't - overload directories with names. - -.. _`PEP 8 Style Guide for Python Code`: http://www.python.org/peps/pep-0008.html -.. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev Deleted: /py/trunk/doc/conf.py ============================================================================== --- /py/trunk/doc/conf.py Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,195 +0,0 @@ -# -*- coding: utf-8 -*- -# -# py lib documentation build configuration file, created by -# sphinx-quickstart on Wed Apr 1 19:13:13 2009. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) -sys.path.append(os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'roles'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.txt' - -# The encoding of source files. -#source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'py lib' -copyright = u'2009, Holger Krekel and others' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '1.0' -# The full version, including alpha/beta/rc tags. -release = '1.0.0b1' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'pylib' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -html_theme_path = ['_templates'] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -html_logo = "img/pylib.png" - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_use_modindex = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -html_show_sourcelink = False - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'pylibdoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'pylib.tex', u'py lib Documentation', - u'Holger Krekel', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True Modified: py/trunk/doc/confrest.py ============================================================================== --- py/trunk/doc/confrest.py (original) +++ py/trunk/doc/confrest.py Mon Apr 13 15:58:26 2009 @@ -22,6 +22,9 @@ self._root = html.html(self.head, self.body) self.fill() + def a_href(self, name, url): + return html.a(name, class_="menu", href=url) + def a_docref(self, name, relhtmlpath): docpath = self.project.docpath return html.a(name, class_="menu", @@ -39,6 +42,8 @@ self.a_docref("index", "index.html"), #self.a_apigenref("api", "api/index.html"), #self.a_apigenref("source", "source/index.html"), + #self.a_href("source", "http://bitbucket.org/hpk42/py-trunk/src/"), + self.a_href("issues", "http://bitbucket.org/hpk42/py-trunk/issues/"), self.a_docref("contact", "contact.html"), self.a_docref("download", "download.html"), ] Modified: py/trunk/doc/conftest.py ============================================================================== --- py/trunk/doc/conftest.py (original) +++ py/trunk/doc/conftest.py Mon Apr 13 15:58:26 2009 @@ -1,3 +1,5 @@ #XXX make work: excludedirs = ['_build'] +import py +py.test.importorskip("pygments") pytest_plugins = ['pytest_restdoc'] rsyncdirs = ['.'] Modified: py/trunk/doc/contact.txt ============================================================================== --- py/trunk/doc/contact.txt (original) +++ py/trunk/doc/contact.txt Mon Apr 13 15:58:26 2009 @@ -32,7 +32,6 @@ or the mailing list and ask questions, get involved. .. _FOAF: http://en.wikipedia.org/wiki/FOAF -.. _`coding style`: coding-style.html .. _us: http://codespeak.net/mailman/listinfo/py-dev .. _codespeak: http://codespeak.net/ .. _`py-dev`: Deleted: /py/trunk/doc/impl-test.txt ============================================================================== --- /py/trunk/doc/impl-test.txt Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,74 +0,0 @@ -=============================================== -ATTIC documentation -=============================================== - -XXX REVIEW and remove the below XXX - -Customizing the testing process -=============================== - -writing conftest.py files ------------------------------------ - -You may put conftest.py files containing project-specific -configuration in your project's root directory, it's usually -best to put it just into the same directory level as your -topmost ``__init__.py``. In fact, ``py.test`` performs -an "upwards" search starting from the directory that you specify -to be tested and will lookup configuration values right-to-left. -You may have options that reside e.g. in your home directory -but note that project specific settings will be considered -first. There is a flag that helps you debugging your -conftest.py configurations:: - - py.test --traceconfig - - -customizing the collecting and running process ------------------------------------------------ - -To introduce different test items you can create -one or more ``conftest.py`` files in your project. -When the collection process traverses directories -and modules the default collectors will produce -custom Collectors and Items if they are found -in a local ``conftest.py`` file. - - -Customizing the collection process in a module ----------------------------------------------- - -If you have a module where you want to take responsibility for -collecting your own test Items and possibly even for executing -a test then you can provide `generative tests`_ that yield -callables and possibly arguments as a tuple. This is especially -useful for calling application test machinery with different -parameter sets but counting each of the calls as a separate -tests. - -.. _`generative tests`: test-features.html#generative-tests - -The other extension possibility is about -specifying a custom test ``Item`` class which -is responsible for setting up and executing an underlying -test. Or you can extend the collection process for a whole -directory tree by putting Items in a ``conftest.py`` configuration file. -The collection process dynamically consults the *chain of conftest.py* -modules to determine collectors and items at ``Directory``, ``Module``, -``Class``, ``Function`` or ``Generator`` level respectively. - -Customizing execution of Items and Functions ----------------------------------------------------- - -- ``py.test.collect.Function`` test items control execution - of a test function through its ``function.runtest()`` method. - This method is responsible for performing setup and teardown - ("Test Fixtures") for a test Function. - -- ``Function.execute(target, *args)`` methods are invoked by - the default ``Function.run()`` to actually execute a python - function with the given (usually empty set of) arguments. - -.. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev - - Modified: py/trunk/doc/index.txt ============================================================================== --- py/trunk/doc/index.txt (original) +++ py/trunk/doc/index.txt Mon Apr 13 15:58:26 2009 @@ -30,7 +30,7 @@ .. _`py.io`: io.html .. _`py.path`: path.html .. _`py.code`: code.html -.. _`py.test`: test.html +.. _`py.test`: test/test.html .. _`py lib scripts`: bin.html .. _`py.xml`: xml.html .. _`miscellaneous features`: misc.html Deleted: /py/trunk/doc/make.bat ============================================================================== --- /py/trunk/doc/make.bat Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,112 +0,0 @@ - at ECHO OFF - -REM Command file for Sphinx documentation - -set SPHINXBUILD=sphinx-build -set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (_build\*) do rmdir /q /s %%i - del /q /s _build\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html - echo. - echo.Build finished. The HTML pages are in _build/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% _build/dirhtml - echo. - echo.Build finished. The HTML pages are in _build/dirhtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in _build/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in _build/qthelp, like this: - echo.^> qcollectiongenerator _build\qthelp\py lib.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile _build\qthelp\py lib.ghc - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex - echo. - echo.Build finished; the LaTeX files are in _build/latex. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes - echo. - echo.The overview file is in _build/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck - echo. - echo.Link check complete; look for any errors in the above output ^ -or in _build/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% _build/doctest - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in _build/doctest/output.txt. - goto end -) - -:end Deleted: /py/trunk/doc/releases.txt ============================================================================== --- /py/trunk/doc/releases.txt Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,12 +0,0 @@ -============= -Release notes -============= - -Contents: - -.. toctree:: - :maxdepth: 1 - - announce/release-1.0.0 - announce/release-0.9.2 - announce/release-0.9.0 Deleted: /py/trunk/doc/roles.py ============================================================================== --- /py/trunk/doc/roles.py Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,5 +0,0 @@ -from docutils.nodes import literal - -def setup(app): - app.add_generic_role('api', literal) - app.add_generic_role('source', literal) Deleted: /py/trunk/doc/test-config.txt ============================================================================== --- /py/trunk/doc/test-config.txt Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,23 +0,0 @@ -Test configuration -======================== - -test options and values ------------------------------ - -You can see all available command line options by running:: - - py.test -h - -py.test will lookup values of options in this order: - -* option value supplied at command line -* content of environment variable ``PYTEST_OPTION_NAME=...`` -* ``name = ...`` setting in the nearest ``conftest.py`` file. - -The name of an option usually is the one you find -in the longform of the option, i.e. the name -behind the ``--`` double-dash. - -IOW, you can set default values for options per project, per -home-directoray, per shell session or per test-run. - Deleted: /py/trunk/doc/test-dist.txt ============================================================================== --- /py/trunk/doc/test-dist.txt Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,125 +0,0 @@ -.. _`distribute tests across machines`: - -=================== -Distributed testing -=================== - -``py.test`` can ad-hoc distribute test runs to multiple CPUs or remote -machines. This allows to speed up development or to use special resources -of remote machines. Before running tests remotely, ``py.test`` efficiently -synchronizes your program source code to the remote place. All test results -are reported back and displayed to your local test session. You may -specify different Python versions and interpreters. - -Synchronisation and running of tests only requires -a bare Python installation on the remote side. No -special software is installed - this is realized -by use of the **zero installation** `py.execnet`_ mechanisms. - -Speed up test runs by sending tests to multiple CPUs ----------------------------------------------------------- - -To send tests to multiple CPUs, type:: - - py.test -n NUM - -Especially for longer running tests or tests requiring -a lot of IO this can lead to considerable speed ups. - - -Running tests in a Python subprocess ----------------------------------------- - -To instantiate a python2.4 sub process and send tests to it, you may type:: - - py.test -d --tx popen//python=python2.4 - -This will start a subprocess which is run with the "python2.4" -Python interpreter, found in your system binary lookup path. - -If you prefix the --tx option value like this:: - - --tx 3*popen//python=python2.4 - -then three subprocesses would be created and tests -will be load-balanced across these three processes. - - -Sending tests to remote SSH accounts ------------------------------------------------ - -Suppose you have a package ``mypkg`` which contains some -tests that you can successfully run locally. And you -have a ssh-reachable machine ``myhost``. Then -you can ad-hoc distribute your tests by typing:: - - py.test -d --tx ssh=myhostpopen --rsyncdir mypkg mypkg - -This will synchronize your ``mypkg`` package directory -to an remote ssh account and then locally collect tests -and send them to remote places for execution. - -You can specify multiple ``--rsyncdir`` directories -to be sent to the remote side. - - -Sending tests to remote Socket Servers ----------------------------------------- - -Download the single-module `socketserver.py`_ Python program -and run it like this:: - - python socketserver.py - -It will tell you that it starts listening on the default -port. You can now on your home machine specify this -new socket host with something like this:: - - py.test -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg - - -.. _`atonce`: - -Running tests on many platforms at once -------------------------------------------------------------- - -The basic command to run tests on multiple platforms is:: - - py.test --dist=each --tx=spec1 --tx=spec2 - -If you specify a windows host, an OSX host and a Linux -environment this command will send each tests to all -platforms - and report back failures from all platforms -at once. The provided specifications strings -use the `xspec syntax`_. - -.. _`xspec syntax`: execnet.html#xspec - -.. _`socketserver.py`: http://codespeak.net/svn/py/dist/py/execnet/script/socketserver.py -.. _`py.execnet`: execnet.html - -Specifying test exec environments in a conftest.py -------------------------------------------------------------- - -Instead of specifying command line options, you can -put options values in a ``conftest.py`` file like this:: - - pytest_option_tx = ['ssh=myhost//python=python2.5', 'popen//python=python2.5'] - pytest_option_dist = True - -Any commandline ``--tx`` specifictions will add to the list of available execution -environments. - -Specifying "rsync" dirs in a conftest.py -------------------------------------------------------------- - -In your ``mypkg/conftest.py`` you may specify directories to synchronise -or to exclude:: - - rsyncdirs = ['.', '../plugins'] - rsyncignore = ['_cache'] - -These directory specifications are relative to the directory -where the ``conftest.py`` is found. - - Deleted: /py/trunk/doc/test-examples.txt ============================================================================== --- /py/trunk/doc/test-examples.txt Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,54 +0,0 @@ - -Working Examples -================ - -managing state at module, class and method level ------------------------------------------------------------- - -Here is a working example for what goes on when you setup modules, -classes and methods:: - - # [[from py/documentation/example/pytest/test_setup_flow_example.py]] - - def setup_module(module): - module.TestStateFullThing.classcount = 0 - - class TestStateFullThing: - def setup_class(cls): - cls.classcount += 1 - - def teardown_class(cls): - cls.classcount -= 1 - - def setup_method(self, method): - self.id = eval(method.func_name[5:]) - - def test_42(self): - assert self.classcount == 1 - assert self.id == 42 - - def test_23(self): - assert self.classcount == 1 - assert self.id == 23 - - def teardown_module(module): - assert module.TestStateFullThing.classcount == 0 - -For this example the control flow happens as follows:: - - import test_setup_flow_example - setup_module(test_setup_flow_example) - setup_class(TestStateFullThing) - instance = TestStateFullThing() - setup_method(instance, instance.test_42) - instance.test_42() - setup_method(instance, instance.test_23) - instance.test_23() - teardown_class(TestStateFullThing) - teardown_module(test_setup_flow_example) - -Note that ``setup_class(TestStateFullThing)`` is called and not -``TestStateFullThing.setup_class()`` which would require you -to insert ``setup_class = classmethod(setup_class)`` to make -your setup function callable. Did we mention that lazyness -is a virtue? Deleted: /py/trunk/doc/test-ext.txt ============================================================================== --- /py/trunk/doc/test-ext.txt Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,120 +0,0 @@ - -=============== -Writing plugins -=============== - -Learning by examples -===================== - -XXX - -adding custom options ----------------------- - -py.test supports adding of standard optparse_ Options. -A plugin may implement the ``addoption`` hook for registering -custom options:: - - class ConftestPlugin: - def pytest_addoption(self, parser): - parser.addoption("-M", "--myopt", action="store", - help="specify string to set myopt") - - def pytest_configure(self, config): - if config.option.myopt: - # do action based on option value - -.. _optparse: http://docs.python.org/library/optparse.html - -Setting default values for test options -======================================= - -You can see all available command line options by running:: - - py.test -h - -py.test will lookup values of options in this order: - -* option value supplied at command line -* content of environment variable ``PYTEST_OPTION_NAME=...`` -* ``name = ...`` setting in the nearest ``conftest.py`` file. - -The name of an option usually is the one you find -in the longform of the option, i.e. the name -behind the ``--`` double-dash. - -IOW, you can set default values for options per project, per -home-directoray, per shell session or per test-run. - -.. _`collection process`: - -Test Collection process -====================================================== - -The collecting process is iterative so that distribution -and execution of tests can start as soon as the first test -item is collected. Collection nodes with children are -called "Collectors" and terminal nodes are called "Items". -Here is an example of such a tree, generated with the -command ``py.test --collectonly py/xmlobj``:: - - - - - - - - - - - - - - - - -By default all directories not starting with a dot are traversed, -looking for ``test_*.py`` and ``*_test.py`` files. Those Python -files are imported under their `package name`_. - -The Module collector looks for test functions -and test classes and methods. Test functions and methods -are prefixed ``test`` by default. Test classes must -start with a capitalized ``Test`` prefix. - -.. _`package name`: - -constructing the package name for test modules -------------------------------------------------- - -Test modules are imported under their fully qualified -name. Given a filesystem ``fspath`` it is constructed as follows: - -* walk the directories up to the last one that contains - an ``__init__.py`` file. - -* perform ``sys.path.insert(0, basedir)``. - -* import the root package as ``root`` - -* determine the fully qualified name for ``fspath`` by either: - - * calling ``root.__pkg__.getimportname(fspath)`` if the - ``__pkg__`` exists.` or - - * otherwise use the relative path of the module path to - the base dir and turn slashes into dots and strike - the trailing ``.py``. - - -Plugin hooks and events -======================================= - -.. automodule:: py.__.test.plugin.api - -.. autoclass:: PluginHooks - :members: - -.. autoclass:: Events - :members: - Deleted: /py/trunk/doc/test-features.txt ============================================================================== --- /py/trunk/doc/test-features.txt Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,279 +0,0 @@ -================================================== -Features -================================================== - -py.test is a standalone-tool that collects and runs tests for -your Python application and modules. py.test works across -linux, windows and osx and on Python 2.3 - Python 2.6. - -It aims to support *unit-tests* and *functional tests* written -in Python and is used in projects that run more than 10000 -tests regularly. - -py.test presents a clean and powerful command line interface -and strives to generally make testing a fun effort. - -automatically collects and executes tests -=============================================== - -py.test discovers tests automatically by inspect specified -directories or files. By default, it collects all python -modules a leading ``test_`` or trailing ``_test`` filename. -From each test module every function with a leading ``test_`` -or class with a leading ``Test`` name is collected. - -.. _`generative tests`: -.. _`collection process`: test-ext.html#collection-process - -load-balance tests to multiple CPUs -=================================== - -For large test suites you can distribute your -tests to multiple CPUs by issuing for example:: - - py.test -n 3 - -Read more on `distributed testing`_. - -.. _`distributed testing`: test-dist.html - -Distribute tests across machines -=================================== - -py.test supports the sending of tests to -remote ssh-accounts or socket servers. -It can `ad-hoc run your test on multiple -platforms one a single test run`. Ad-hoc -means that there are **no installation -requirements whatsoever** on the remote side. - -.. _`ad-hoc run your test on multiple platforms one a single test run`: test-dist.html#atonce - -extensive debugging support -=================================== - -testing starts immediately --------------------------- - -Testing starts as soon as the first ``test item`` -is collected. The collection process is iterative -and does not need to complete before your first -test items are executed. - -support for modules containing tests --------------------------------------- - -As ``py.test`` operates as a separate cmdline -tool you can easily have a command line utility and -some tests in the same file. - -debug with the ``print`` statement ----------------------------------- - -By default, ``py.test`` catches text written to stdout/stderr during -the execution of each individual test. This output will only be -displayed however if the test fails; you will not see it -otherwise. This allows you to put debugging print statements in your -code without being overwhelmed by all the output that might be -generated by tests that do not fail. - -Each failing test that produced output during the running of the test -will have its output displayed in the ``recorded stdout`` section. - -The catching of stdout/stderr output can be disabled using the -``--nocapture`` option to the ``py.test`` tool. Any output will -in this case be displayed as soon as it is generated. - -test execution order --------------------------------- - -Tests usually run in the order in which they appear in the files. -However, tests should not rely on running one after another, as -this prevents more advanced usages: running tests -distributedly or selectively, or in "looponfailing" mode, -will cause them to run in random order. - -assert with the ``assert`` statement ----------------------------------------- - -``py.test`` allows to use the standard python -``assert statement`` for verifying expectations -and values in Python tests. For example, you can -write the following in your tests:: - - assert hasattr(x, 'attribute') - -to state that your object has a certain ``attribute``. In case this -assertion fails you will see the value of ``x``. Intermediate -values are computed by executing the assert expression a second time. -If you execute code with side effects, e.g. read from a file like this:: - - assert f.read() != '...' - -then you may get a warning from pytest if that assertions -first failed and then succeeded. - -asserting expected exceptions ----------------------------------------- - -In order to write assertions about exceptions, you use -one of two forms:: - - py.test.raises(Exception, func, *args, **kwargs) - py.test.raises(Exception, "func(*args, **kwargs)") - -both of which execute the given function with args and kwargs and -asserts that the given ``Exception`` is raised. The reporter will -provide you with helpful output in case of failures such as *no -exception* or *wrong exception*. - -useful tracebacks, recursion detection --------------------------------------- - -A lot of care is taken to present nice tracebacks in case of test -failure. Try:: - - py.test py/doc/example/pytest/failure_demo.py - -to see a variety of tracebacks, each representing a different -failure situation. - -``py.test`` uses the same order for presenting tracebacks as Python -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 --------------------------- - -Test classes are recognized by their leading ``Test`` name. Unlike -``unitest.py``, you don't need to inherit from some base class to make -them be found by the test runner. Besides being easier, it also allows -you to write test classes that subclass from application level -classes. - -testing for deprecated APIs ------------------------------- - -In your tests you can use ``py.test.deprecated_call(func, *args, **kwargs)`` -to test that a particular function call triggers a DeprecationWarning. -This is useful for testing phasing out of old APIs in your projects. - - -advanced test selection / skipping -========================================================= - -dynamically skipping tests -------------------------------- - -If you want to skip tests you can use ``py.test.skip`` within -test or setup functions. Example:: - - py.test.skip("message") - -You can also use a helper to skip on a failing import:: - - docutils = py.test.importorskip("docutils") - -or to skip if a library does not have the right version:: - - docutils = py.test.importorskip("docutils", minversion="0.3") - -The version will be read from the module's ``__version__`` attribute. - -.. _`selection by keyword`: - -selecting/unselecting tests by keyword ---------------------------------------------- - -Pytest's keyword mechanism provides a powerful way to -group and selectively run tests in your test code base. -You can selectively run tests by specifiying a keyword -on the command line. Examples:: - - py.test -k test_simple - py.test -k "-test_simple" - -will run all tests matching (or not matching) the -"test_simple" keyword. Note that you need to quote -the keyword if "-" is recognized as an indicator -for a commandline option. Lastly, you may use:: - - py.test. -k "test_simple:" - -which will run all tests after the expression has *matched once*, i.e. -all tests that are seen after a test that matches the "test_simple" -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.mark(webtest=True) - def test_send_http(): - ... - -disabling a test class ----------------------- - -If you want to disable a complete test class you -can set the class-level attribute ``disabled``. -For example, in order to avoid running some tests on Win32:: - - class TestPosixOnly: - disabled = sys.platform == 'win32' - - def test_xxx(self): - ... - -generative tests: yielding parametrized tests -==================================================== - -*Generative tests* are test methods that are *generator functions* which -``yield`` callables and their arguments. This is most useful for running a -test function multiple times against different parameters. Example:: - - def test_generative(): - for x in (42,17,49): - yield check, x - - def check(arg): - assert arg % 7 == 0 # second generated tests fails! - -Note that ``test_generative()`` will cause three tests -to get run, notably ``check(42)``, ``check(17)`` and ``check(49)`` -of which the middle one will obviously fail. - -To make it easier to distinguish the generated tests it is possible to specify an explicit name for them, like for example:: - - def test_generative(): - for x in (42,17,49): - yield "case %d" % x, check, x - -extensible plugin system -========================================= - -py.test itself consists of many plugins -and you can easily write new `py.test plugins`_ -for these purposes: - -* reporting extensions -* customizing collection and run of tests -* running non-python tests -* managing test state setup - -.. _`py.test plugins`: test-plugins.html - -.. _`reStructured Text`: http://docutils.sourceforge.net -.. _`Python debugger`: http://docs.python.org/lib/module-pdb.html - - -.. _nose: http://somethingaboutorange.com/mrl/projects/nose/ Deleted: /py/trunk/doc/test-funcargs.txt ============================================================================== --- /py/trunk/doc/test-funcargs.txt Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,160 +0,0 @@ - -===================================== -Python test function arguments -===================================== - -py.test enables a new way to separate test configuration -and test setup from actual test code in test functions. -When it runs a test functions it will lookup function -arguments by name and provide a value. -Here is a simple example for such a test function: - - def test_function(mysetup): - # work with mysetup - -To provide a value py.test looks for a ``pytest_funcargs`` -dictionary in the test module, for example:: - - class MySetup: - def __init__(self, pyfuncitem): - self.pyfuncitem = pyfuncitem - pytest_funcargs = {'mysetup': MySetup} - -This is already enough to run the test. Of course -up until now our ``mysetup`` does not provide -much value. But it is now easy to add new -methods on the ``MySetup`` class that have -full access to the test collection process. - -Plugins can register their funcargs via -the config object, usually upon initial configure:: - - class ConftestPlugin: - def pytest_configure(self, config): - config.register_funcargs(mysetup=MySetup) - -If you provide a "funcarg" from a plugin you can -easily make methods depend on command line options -or environment settings. Here is a complete -example that allows to run tests involving -an SSH connection if an ssh host is specified:: - - class ConftestPlugin: - def pytest_addoption(self, parser): - parser.addoption("--ssh", action="store", default=None, - help="specify ssh host to run tests with") - - def pytest_configure(self, config): - config.register_funcargs(mysetup=MySetup) - - class MySetup: - def __init__(self, pyfuncitem): - self.pyfuncitem = pyfuncitem - def ssh_gateway(self): - host = pyfuncitem.config.option.ssh - if host is None: - py.test.skip("specify ssh host with --ssh to run this test") - return py.execnet.SshGateway(host) - -Now any test functions can use the "mysetup" object, for example:: - - class TestClass: - def test_function(self, mysetup): - ssh_gw = mysetup.ssh_gateway() - # work with ssh_gw - -Without specifying a command line option the output looks like this:: - - ... - - -Lookup rules -====================== - -In order to run this test function a value for the -``mysetup`` needs to be found. Here is how py.test -finds a matching provider function: - -1. see if there is a ``pytest_funcargs`` dictionary - which maps ``mysetup`` to a provider function. - if so, call the provider function. - -XXX - - - -example -===================== - -You can run a test file ``test_some.py`` with this content: - - pytest_funcargs = {'myarg': (lambda pyfuncitem: 42)} - - def test_something(myarg): - assert myarg == 42 - -You can also put this on a class: - - class TestClass: - pytest_funcargs = {'myarg': (lambda pyfuncitem: 42)} - - def test_something(self, myarg): - assert myarg == 42 - -To separate funcarg setup you can also put a funcarg -definition into a conftest.py:: - - pytest_funcargs = {'myarg': decorate_myarg} - def decorate_myarg(pyfuncitem): - result = pyfuncitem.call_next_provider() - return result + 1 - -for registering funcargs from a plugin, talk to the -test configuration object like this:: - - class MyPlugin: - def pytest_configure(self, config): - config.register_funcargs( - myarg=decorate_myarg - ) - -a local helper funcarg for doing acceptance tests maybe -by running shell commands could look like this:: - - class MyPlugin: - def pytest_option(self, parser): - group = parser.addgroup("myproject acceptance tests") - group.addoption("-A", dest="acceptance", action="store_true", - help="run (slow) acceptance tests") - - def pytest_configure(self, config): - config.register_funcargs(accept=AcceptFuncarg) - - class AcceptFuncarg: - def __init__(self, pyfuncitem): - if not pyfuncitem.config.option.acceptance: - py.test.skip("specify -A to run acceptance tests") - self.tmpdir = pyfuncitem.config.maketempdir(pyfuncitem.name) - self._old = self.tmpdir.chdir() - pyfuncitem.addfinalizer(self.finalize) - - def run(self): - return py.process.cmdexec("echo hello") - - def finalize(self): - self._old.chdir() - # cleanup any other resources - -and the actual test function example: - - def test_some_acceptance_aspect(accept): - accept.tmpdir.mkdir("somesub") - result = accept.run() - assert result - -for registering funcargs from a plugin, talk to the -test configuration object like this:: - - XXX - - Deleted: /py/trunk/doc/test-plugins.txt ============================================================================== --- /py/trunk/doc/test-plugins.txt Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,71 +0,0 @@ -================ -Included plugins -================ - -Many of py.test's features are implemented as a plugin. - -Included plugins -================ - -You can find the source code of all default plugins in -http://codespeak.net/svn/py/trunk/py/test/plugin/ - -plugins that add reporting asepcts ------------------------------------ - -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 - -plugins for adding new test types ------------------------------------ - -pytest_unittest: run traditional unittest TestCase instances - -pytest_doctest: run doctests in python modules or .txt files - -pytest_restdoc: provide RestructuredText syntax and link checking - -plugins for python test functions ------------------------------------ - -pytest_xfail: provides "expected to fail" test marker - -pytest_tmpdir: provide temporary directories to test functions - -pytest_plugintester: generic plugin apichecks, support for functional plugin tests - -pytest_apigen: tracing values of function/method calls when running tests - -Loading plugins and specifying dependencies -============================================ - -py.test loads and configures plugins at tool startup: - -* by reading the ``PYTEST_PLUGINS`` environment variable - and importing the comma-separated list of plugin names. - -* by loading all plugins specified via one or more ``-p name`` - command line options. - -* by loading all plugins specified via a ``pytest_plugins`` - variable in ``conftest.py`` files or test modules. - -example: ensure a plugin is loaded ------------------------------------ - -If you create a ``conftest.py`` file with the following content:: - - pytest_plugins = "pytest_myextension", - -then all tests in that directory and below it will run with -an instantiated "pytest_myextension". Here is how instantiation -takes place: - -* the module ``pytest_extension`` will be imported and - and its contained `ExtensionPlugin`` class will - be instantiated. A plugin module may specify its - dependencies via another ``pytest_plugins`` definition. - Deleted: /py/trunk/doc/test-quickstart.txt ============================================================================== --- /py/trunk/doc/test-quickstart.txt Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,59 +0,0 @@ -.. _`setuptools installation`: http://pypi.python.org/pypi/setuptools - - -================== -Quickstart -================== - -This document assumes basic python knowledge. If you have a -`setuptools installation`_, install ``py.test`` by typing:: - - easy_install -U py - -For alternative installation methods please see the download_ page. - -You should now have a ``py.test`` command line tool and can -look at its documented cmdline options via this command:: - - py.test -h - -Writing and running a test -========================== - -``py.test`` is the command line tool to run tests. -Let's write a first test module by putting the following -test function into a ``test_sample.py`` file:: - - # content of test_sample.py - def test_answer(): - assert 42 == 43 - -Now you can run the test by passing it as an argument:: - - py.test test_sample.py - -What does happen here? ``py.test`` looks for functions and -methods in the module that start with ``test_``. It then -executes those tests. Assertions about test outcomes are -done via the standard ``assert`` statement. - -You can also use ``py.test`` to run all tests in a directory structure by -invoking it without any arguments:: - - py.test - -This will automatically collect and run any Python module whose filenames -start with ``test_`` or ends with ``_test`` from the directory and any -subdirectories, starting with the current directory, and run them. Each -Python test module is inspected for test methods starting with ``test_``. - -.. Organising your tests -.. --------------------------- - -Please refer to `features`_ for a walk through the basic features. - - -.. _download: download.html -.. _features: test-features.html - - Deleted: /py/trunk/doc/test-statemanage.txt ============================================================================== --- /py/trunk/doc/test-statemanage.txt Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,65 +0,0 @@ -================= -Managing state -================= - -funcargs: provding arguments for test functions -=========================================================== - -XXX write docs - -Managing test state across test modules, classes and methods -============================================================ - -Often you want to create some files, database connections or other -state in order to run tests in a certain environment. With -``py.test`` there are three scopes for which you can provide hooks to -manage such state. Again, ``py.test`` will detect these hooks in -modules on a name basis. The following module-level hooks will -automatically be called by the session:: - - def setup_module(module): - """ setup up any state specific to the execution - of the given module. - """ - - def teardown_module(module): - """ teardown any state that was previously setup - with a setup_module method. - """ - -The following hooks are available for test classes:: - - def setup_class(cls): - """ setup up any state specific to the execution - of the given class (which usually contains tests). - """ - - def teardown_class(cls): - """ teardown any state that was previously setup - with a call to setup_class. - """ - - def setup_method(self, method): - """ setup up any state tied to the execution of the given - method in a class. setup_method is invoked for every - test method of a class. - """ - - def teardown_method(self, method): - """ teardown any state that was previously setup - with a setup_method call. - """ - -The last two hooks, ``setup_method`` and ``teardown_method``, are -equivalent to ``setUp`` and ``tearDown`` in the Python standard -library's ``unitest`` module. - -All setup/teardown methods are optional. You could have a -``setup_module`` but no ``teardown_module`` and the other way round. - -Note that while the test session guarantees that for every ``setup`` a -corresponding ``teardown`` will be invoked (if it exists) it does -*not* guarantee that any ``setup`` is called only happens once. For -example, the session might decide to call the ``setup_module`` / -``teardown_module`` pair more than once during the execution of a test -module. Deleted: /py/trunk/doc/test.txt ============================================================================== --- /py/trunk/doc/test.txt Mon Apr 13 15:58:26 2009 +++ (empty file) @@ -1,40 +0,0 @@ -======= -py.test -======= - -* rapidly collect and run tests -* use unit- or doctests, functional or integration tests -* distribute tests to multiple environments -* local or global plugins for custom test types - -quickstart_: for getting started immediately. - -features_: a walk through basic features and usage. - -plugins_: using available plugins. - -extend_: writing plugins and advanced configuration. - -`distributed testing`_ how to distribute test runs to other machines and platforms. - -.. _quickstart: test-quickstart.html -.. _features: test-features.html -.. _plugins: test-plugins.html -.. _extend: test-ext.html -.. _`distributed testing`: test-dist.html - - -Contents: - -.. toctree:: - :maxdepth: 2 - - test-quickstart - test-features - test-plugins - test-ext - test-dist - test-config - test-statemanage - test-examples - impl-test Copied: py/trunk/doc/test/attic.txt (from r64028, py/trunk/doc/impl-test.txt) ============================================================================== --- py/trunk/doc/impl-test.txt (original) +++ py/trunk/doc/test/attic.txt Mon Apr 13 15:58:26 2009 @@ -46,7 +46,7 @@ parameter sets but counting each of the calls as a separate tests. -.. _`generative tests`: test-features.html#generative-tests +.. _`generative tests`: features.html#generative-tests The other extension possibility is about specifying a custom test ``Item`` class which Copied: py/trunk/doc/test/dist.txt (from r64028, py/trunk/doc/test-dist.txt) ============================================================================== --- py/trunk/doc/test-dist.txt (original) +++ py/trunk/doc/test/dist.txt Mon Apr 13 15:58:26 2009 @@ -93,10 +93,10 @@ at once. The provided specifications strings use the `xspec syntax`_. -.. _`xspec syntax`: execnet.html#xspec +.. _`xspec syntax`: ../execnet.html#xspec .. _`socketserver.py`: http://codespeak.net/svn/py/dist/py/execnet/script/socketserver.py -.. _`py.execnet`: execnet.html +.. _`py.execnet`: ../execnet.html Specifying test exec environments in a conftest.py ------------------------------------------------------------- Copied: py/trunk/doc/test/ext.txt (from r64028, py/trunk/doc/test-ext.txt) ============================================================================== --- py/trunk/doc/test-ext.txt (original) +++ py/trunk/doc/test/ext.txt Mon Apr 13 15:58:26 2009 @@ -110,11 +110,4 @@ Plugin hooks and events ======================================= -.. automodule:: py.__.test.plugin.api - -.. autoclass:: PluginHooks - :members: - -.. autoclass:: Events - :members: - +XXX Copied: py/trunk/doc/test/features.txt (from r64028, py/trunk/doc/test-features.txt) ============================================================================== --- py/trunk/doc/test-features.txt (original) +++ py/trunk/doc/test/features.txt Mon Apr 13 15:58:26 2009 @@ -1,18 +1,26 @@ ================================================== -Features +py.test Features ================================================== -py.test is a standalone-tool that collects and runs tests for -your Python application and modules. py.test works across -linux, windows and osx and on Python 2.3 - Python 2.6. - -It aims to support *unit-tests* and *functional tests* written -in Python and is used in projects that run more than 10000 -tests regularly. +py.test is an extensible tool for running all kinds +of tests one one or more machines. It supports a variety +of testing methods for your Python application and modules, +including unit, functional, integration and doc-testing. + +It is used in projects that run more than 10000 tests +daily as well as single-python-module projects. py.test presents a clean and powerful command line interface and strives to generally make testing a fun effort. +py.test 1.0 works across linux, windows and osx +and on Python 2.3 - Python 2.6. + +More detailed feature list: + +.. contents:: + :depth: 1 + automatically collects and executes tests =============================================== @@ -23,7 +31,13 @@ or class with a leading ``Test`` name is collected. .. _`generative tests`: -.. _`collection process`: test-ext.html#collection-process +.. _`collection process`: ext.html#collection-process + +Rapidly write integration, functional, unit tests +=================================================== + +py.test provides + load-balance tests to multiple CPUs =================================== @@ -35,7 +49,7 @@ Read more on `distributed testing`_. -.. _`distributed testing`: test-dist.html +.. _`distributed testing`: dist.html Distribute tests across machines =================================== @@ -47,7 +61,7 @@ means that there are **no installation requirements whatsoever** on the remote side. -.. _`ad-hoc run your test on multiple platforms one a single test run`: test-dist.html#atonce +.. _`ad-hoc run your test on multiple platforms one a single test run`: dist.html#atonce extensive debugging support =================================== @@ -270,7 +284,7 @@ * running non-python tests * managing test state setup -.. _`py.test plugins`: test-plugins.html +.. _`py.test plugins`: plugins.html .. _`reStructured Text`: http://docutils.sourceforge.net .. _`Python debugger`: http://docs.python.org/lib/module-pdb.html Copied: py/trunk/doc/test/quickstart.txt (from r64028, py/trunk/doc/test-quickstart.txt) ============================================================================== --- py/trunk/doc/test-quickstart.txt (original) +++ py/trunk/doc/test/quickstart.txt Mon Apr 13 15:58:26 2009 @@ -53,7 +53,7 @@ Please refer to `features`_ for a walk through the basic features. -.. _download: download.html -.. _features: test-features.html +.. _download: ../download.html +.. _features: features.html Copied: py/trunk/doc/test/test.txt (from r64028, py/trunk/doc/test.txt) ============================================================================== --- py/trunk/doc/test.txt (original) +++ py/trunk/doc/test/test.txt Mon Apr 13 15:58:26 2009 @@ -5,7 +5,7 @@ * rapidly collect and run tests * use unit- or doctests, functional or integration tests * distribute tests to multiple environments -* local or global plugins for custom test types +* local or global plugins for custom test scenarios and types quickstart_: for getting started immediately. @@ -17,24 +17,10 @@ `distributed testing`_ how to distribute test runs to other machines and platforms. -.. _quickstart: test-quickstart.html -.. _features: test-features.html -.. _plugins: test-plugins.html -.. _extend: test-ext.html -.. _`distributed testing`: test-dist.html - - -Contents: - -.. toctree:: - :maxdepth: 2 - - test-quickstart - test-features - test-plugins - test-ext - test-dist - test-config - test-statemanage - test-examples - impl-test +.. _quickstart: quickstart.html +.. _features: features.html +.. _plugins: plugins.html +.. _extend: ext.html +.. _`distributed testing`: dist.html + + Modified: py/trunk/doc/xml.txt ============================================================================== --- py/trunk/doc/xml.txt (original) +++ py/trunk/doc/xml.txt Mon Apr 13 15:58:26 2009 @@ -166,4 +166,4 @@ your Tags. Hum, it's probably harder to explain this than to actually code it :-) -.. _`py.test`: test.html +.. _`py.test`: test/test.html From hpk at codespeak.net Tue Apr 14 22:36:47 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 14 Apr 2009 22:36:47 +0200 (CEST) Subject: [py-svn] r64076 - in py/trunk: . doc doc/test example/funcarg example/funcarg/mysetup py py/misc/testing py/test py/test/dist/testing py/test/plugin py/test/testing Message-ID: <20090414203647.2A5A1169F33@codespeak.net> Author: hpk Date: Tue Apr 14 22:36:45 2009 New Revision: 64076 Added: py/trunk/doc/test/xunit_setup.txt py/trunk/example/funcarg/ py/trunk/example/funcarg/mysetup/ py/trunk/example/funcarg/mysetup/conftest.py py/trunk/example/funcarg/mysetup/myapp.py py/trunk/example/funcarg/mysetup/test_sample.py py/trunk/py/test/testing/test_funcargs.py Removed: py/trunk/doc/test/statemanage.txt Modified: py/trunk/.hgignore py/trunk/doc/path.txt py/trunk/doc/test/config.txt py/trunk/doc/test/ext.txt py/trunk/doc/test/features.txt py/trunk/doc/test/funcargs.txt py/trunk/py/_com.py py/trunk/py/conftest.py py/trunk/py/misc/testing/test_com.py py/trunk/py/test/dist/testing/test_nodemanage.py py/trunk/py/test/dist/testing/test_txnode.py py/trunk/py/test/plugin/pytest__pytest.py py/trunk/py/test/plugin/pytest_default.py py/trunk/py/test/plugin/pytest_iocapture.py py/trunk/py/test/plugin/pytest_monkeypatch.py py/trunk/py/test/plugin/pytest_plugintester.py py/trunk/py/test/plugin/pytest_pytester.py py/trunk/py/test/plugin/pytest_restdoc.py py/trunk/py/test/plugin/pytest_resultdb.py py/trunk/py/test/plugin/pytest_resultlog.py py/trunk/py/test/plugin/pytest_tmpdir.py py/trunk/py/test/pluginmanager.py py/trunk/py/test/pycollect.py py/trunk/py/test/testing/conftest.py py/trunk/py/test/testing/test_pickling.py py/trunk/py/test/testing/test_pycollect.py py/trunk/py/test/testing/test_traceback.py Log: merging new funcargs docs and code. doc/test/funcargs.txt now pretty much describes how funcargs are to work, including examples. Modified: py/trunk/.hgignore ============================================================================== --- py/trunk/.hgignore (original) +++ py/trunk/.hgignore Tue Apr 14 22:36:45 2009 @@ -9,3 +9,4 @@ syntax:glob *.pyc *.pyo +*.swp Modified: py/trunk/doc/path.txt ============================================================================== --- py/trunk/doc/path.txt (original) +++ py/trunk/doc/path.txt Tue Apr 14 22:36:45 2009 @@ -11,6 +11,8 @@ Path implementations provided by :api:`py.path` =============================================== +.. _`local`: + :api:`py.path.local` -------------------- Modified: py/trunk/doc/test/config.txt ============================================================================== --- py/trunk/doc/test/config.txt (original) +++ py/trunk/doc/test/config.txt Tue Apr 14 22:36:45 2009 @@ -21,3 +21,26 @@ IOW, you can set default values for options per project, per home-directoray, per shell session or per test-run. + +.. _`basetemp`: + +per-testrun temporary directories +------------------------------------------- + +``py.test`` runs provide means to create per-test session +temporary (sub) directories. You can create such directories +like this: + +.. sourcecode: python + + import py + basetemp = py.test.config.ensuretemp() + basetemp_subdir = py.test.config.ensuretemp("subdir") + +By default, ``py.test`` creates a ``pytest-NUMBER`` directory +and will keep around the directories of the last three +test runs. You can also set the base temporary directory +with the `--basetemp`` option. When distributing +tests on the same machine, ``py.test`` takes care to +pass around the basetemp directory such that all temporary +files land below the same basetemp directory. Modified: py/trunk/doc/test/ext.txt ============================================================================== --- py/trunk/doc/test/ext.txt (original) +++ py/trunk/doc/test/ext.txt Tue Apr 14 22:36:45 2009 @@ -1,7 +1,20 @@ +====================================== +Writing plugins and extensions +====================================== + + +.. _`local plugin`: + +Local Plugins +================================== + +You can easily specify a project-specific or "local" +plugin by defining a ``ConftestPlugin`` in a ``conftest.py`` +file like this:: + + class ConftestPlugin: + """ my local plugin. """ -=============== -Writing plugins -=============== Learning by examples ===================== Modified: py/trunk/doc/test/features.txt ============================================================================== --- py/trunk/doc/test/features.txt (original) +++ py/trunk/doc/test/features.txt Tue Apr 14 22:36:45 2009 @@ -36,8 +36,19 @@ Rapidly write integration, functional, unit tests =================================================== -py.test provides +XXX +funcargs and xUnit style setups +=================================================== + +py.test provides powerful means for managing test +state and fixtures. Apart from the `traditional +xUnit style setup`_ for unittests it features the +simple and powerful `funcargs mechanism`_ for handling +both complex and simple test scenarious. + +.. _`funcargs mechanism`: funcargs.html +.. _`traditional xUnit style setup`: xunit_setup.html load-balance tests to multiple CPUs =================================== Modified: py/trunk/doc/test/funcargs.txt ============================================================================== --- py/trunk/doc/test/funcargs.txt (original) +++ py/trunk/doc/test/funcargs.txt Tue Apr 14 22:36:45 2009 @@ -1,142 +1,314 @@ +====================================================== +**funcargs**: powerful and simple test setup +====================================================== -===================================== -Python test function arguments -===================================== - -py.test enables a new way to separate test configuration -and test setup from actual test code in test functions. -When it runs a test functions it will lookup function -arguments by name and provide a value. -Here is a simple example for such a test function: - - def test_function(mysetup): - # work with mysetup - -To provide a value py.test looks for a ``pytest_funcargs`` -dictionary in the test module, for example:: +In version 1.0 py.test introduces a new mechanism for setting up test +state for use by Python test functions. It is particularly useful +for functional and integration testing but also for unit testing. +Using funcargs you can easily: - class MySetup: - def __init__(self, pyfuncitem): - self.pyfuncitem = pyfuncitem - pytest_funcargs = {'mysetup': MySetup} - -This is already enough to run the test. Of course -up until now our ``mysetup`` does not provide -much value. But it is now easy to add new -methods on the ``MySetup`` class that have -full access to the test collection process. +* write self-contained, simple to read and debug test functions +* cleanly encapsulate glue code between your app and your tests +* setup test state depending on command line options or environment + +Using the funcargs mechanism will increase readability +and allow for easier refactoring of your application +and its test suites. + +.. contents:: Contents: + :depth: 2 + +The basic funcarg request/provide mechanism +============================================= + +To use funcargs you only need to specify +a named argument for your test function: + +.. sourcecode:: python + + def test_function(myarg): + # use myarg + +For each test function that requests this ``myarg`` +argument a matching so called funcarg provider +will be invoked. A Funcarg provider for ``myarg`` +is written down liks this: + +.. sourcecode:: python + + def pytest_funcarg__myarg(self, request): + # return value for myarg here + +Such a provider method can live on a test class, +test module or on a local or global plugin. +The method is recognized by the ``pytest_funcarg__`` +prefix and is correlated to the argument +name which follows this prefix. The passed in +``request`` object allows to interact +with test configuration, test collection +and test running aspects. + +.. _`request object`: + +funcarg request objects +------------------------ + +Request objects encapsulate a request for a function argument from a +specific test function. Request objects provide access to command line +options, the underlying python function and allow interaction +with other providers and the test running process. + +Attributes of request objects +++++++++++++++++++++++++++++++++++++++++ + +``request.argname``: name of the requested function argument + +``request.function``: python function object requesting the argument + +``request.fspath``: filesystem path of containing module + +``request.config``: access to command line opts and general config + +finalizing after test function executed +++++++++++++++++++++++++++++++++++++++++ + +Request objects allow to **register a finalizer method** which is +called after a test function has finished running. +This is useful for tearing down or cleaning up +test state. Here is a basic example for providing +a ``myfile`` object that will be closed upon test +function finish: + +.. sourcecode:: python + + def pytest_funcarg__myfile(self, request): + # ... create and open a "myfile" object ... + request.addfinalizer(lambda: myfile.close()) + return myfile + +a unique temporary directory +++++++++++++++++++++++++++++++++++++++++ + +request objects allow to create unique temporary +directories. These directories will be created +as subdirectories under the `per-testsession +temporary directory`_. Each request object +receives its own unique subdirectory whose +basenames starts with the name of the function +that triggered the funcarg request. You +can further work with the provided `py.path.local`_ +object to e.g. create subdirs or config files:: + + def pytest_funcarg__mysetup(self, request): + tmpdir = request.maketempdir() + tmpdir.mkdir("mysubdir") + tmpdir.join("config.ini").write("[default") + return tmpdir -Plugins can register their funcargs via -the config object, usually upon initial configure:: +Note that you do not need to perform finalization, +i.e. remove the temporary directory as this is +part of the global management of the base temporary +directory. + +.. _`per-testsession temporary directory`: config.html#basetemp + +decorating/adding to existing funcargs +++++++++++++++++++++++++++++++++++++++++ + +If you want to **decorate a function argument** that is +provided elsewhere you can ask the request object +to provide the "next" value: + +.. sourcecode:: python + + def pytest_funcarg__myfile(self, request): + myfile = request.call_next_provider() + # do something extra + return myfile + +This will raise a ``request.Error`` exception if there +is no next provider left. See the `decorator example`_ +for a use of this method. + + +.. _`lookup order`: + +Order of funcarg provider lookup +---------------------------------------- + +For any funcarg argument request here is the +lookup order for provider methods: + +1. test class (if we are executing a method) +2. test module +3. local plugins +4. global plugins + + +Using multiple funcargs +---------------------------------------- + +A test function may receive more than one +function arguments. For each of the +function arguments a lookup of a +matching provider will be performed. + + +Funcarg Examples +===================== + +Example: basic application specific setup +----------------------------------------------------- + +Here is a basic useful example for handling application +specific setup. The goal is to have one place where +we have the glue code for bootstrapping and configuring +application objects and allow test modules and +test functions to stay ignorant of involved details. +Let's start with the using side and consider a simple +test function living in a test file ``test_sample.py``: + +.. sourcecode:: python + + def test_answer(mysetup): + app = mysetup.myapp() + answer = app.question() + assert answer == 42 + +To run this test py.test looks up and calls a provider to obtain the +required ``mysetup`` function argument. The test function simply +interacts with the provided application specific setup. + +To provide the ``mysetup`` function argument we write down +a provider method in a `local plugin`_ by putting the +following code into a local ``conftest.py``: + +.. sourcecode:: python + + from myapp import MyApp class ConftestPlugin: - def pytest_configure(self, config): - config.register_funcargs(mysetup=MySetup) + def pytest_funcarg__mysetup(self, request): + return MySetup() + + class MySetup: + def myapp(self): + return MyApp() + +The ``pytest_funcarg__mysetup`` method is called to +provide a value for the requested ``mysetup`` test function argument. +To complete the example we put a pseudo MyApp object +into ``myapp.py``: + +.. sourcecode:: python + + class MyApp: + def question(self): + return 6 * 9 + +You can now run the test with ``py.test test_sample.py`` which will +show this failure: + +.. sourcecode:: python + + def test_answer(mysetup): + app = mysetup.myapp() + answer = app.question() + > assert answer == 42 + E assert 54 == 42 + +If you are confused as to that the question or answer is +here, please visit here_. + +.. _here: http://uncyclopedia.wikia.com/wiki/The_Hitchhiker's_Guide_to_the_Galaxy + +.. _`local plugin`: ext.html#local-plugin + + +Example: specifying funcargs in test modules or classes +--------------------------------------------------------- + +.. sourcecode:: python + + def pytest_funcarg__mysetup(request): + result = request.call_next_provider() + result.extra = "..." + return result + +You can also put such a function into a test class like this: + +.. sourcecode:: python + + class TestClass: + def pytest_funcarg__mysetup(self, request): + # ... + # + + +Example: command line option for providing SSH-host +----------------------------------------------------------- If you provide a "funcarg" from a plugin you can easily make methods depend on command line options or environment settings. Here is a complete example that allows to run tests involving -an SSH connection if an ssh host is specified:: +an SSH connection if an ssh host is specified: + +.. sourcecode:: python class ConftestPlugin: def pytest_addoption(self, parser): parser.addoption("--ssh", action="store", default=None, help="specify ssh host to run tests with") - def pytest_configure(self, config): - config.register_funcargs(mysetup=MySetup) + pytest_funcarg__mysetup = MySetupFuncarg - class MySetup: - def __init__(self, pyfuncitem): - self.pyfuncitem = pyfuncitem + class MySetupFuncarg: + def __init__(self, request): + self.request = request def ssh_gateway(self): - host = pyfuncitem.config.option.ssh + host = self.request.config.option.ssh if host is None: py.test.skip("specify ssh host with --ssh to run this test") return py.execnet.SshGateway(host) -Now any test functions can use the "mysetup" object, for example:: +Now any test functions can use the "mysetup.ssh_gateway()" method like this: + +.. sourcecode:: python class TestClass: def test_function(self, mysetup): ssh_gw = mysetup.ssh_gateway() # work with ssh_gw - -Without specifying a command line option the output looks like this:: + +Running this without the command line will yield this run result:: ... -Lookup rules -====================== +.. _`accept example`: -In order to run this test function a value for the -``mysetup`` needs to be found. Here is how py.test -finds a matching provider function: +example: specifying and selecting acceptance tests +-------------------------------------------------------------- -1. see if there is a ``pytest_funcargs`` dictionary - which maps ``mysetup`` to a provider function. - if so, call the provider function. +.. sourcecode:: python -XXX - - - -example -===================== - -You can run a test file ``test_some.py`` with this content: - - pytest_funcargs = {'myarg': (lambda pyfuncitem: 42)} - - def test_something(myarg): - assert myarg == 42 - -You can also put this on a class: - - class TestClass: - pytest_funcargs = {'myarg': (lambda pyfuncitem: 42)} - - def test_something(self, myarg): - assert myarg == 42 - -To separate funcarg setup you can also put a funcarg -definition into a conftest.py:: - - pytest_funcargs = {'myarg': decorate_myarg} - def decorate_myarg(pyfuncitem): - result = pyfuncitem.call_next_provider() - return result + 1 - -for registering funcargs from a plugin, talk to the -test configuration object like this:: - - class MyPlugin: - def pytest_configure(self, config): - config.register_funcargs( - myarg=decorate_myarg - ) - -a local helper funcarg for doing acceptance tests maybe -by running shell commands could look like this:: - - class MyPlugin: + class ConftestPlugin: def pytest_option(self, parser): - group = parser.addgroup("myproject acceptance tests") + group = parser.getgroup("myproject") group.addoption("-A", dest="acceptance", action="store_true", help="run (slow) acceptance tests") - def pytest_configure(self, config): - config.register_funcargs(accept=AcceptFuncarg) + def pytest_funcarg__accept(self, request): + return AcceptFuncarg(request) class AcceptFuncarg: - def __init__(self, pyfuncitem): - if not pyfuncitem.config.option.acceptance: + def __init__(self, request): + if not request.config.option.acceptance: py.test.skip("specify -A to run acceptance tests") - self.tmpdir = pyfuncitem.config.maketempdir(pyfuncitem.name) + self.tmpdir = request.config.maketempdir(request.argname) self._old = self.tmpdir.chdir() - pyfuncitem.addfinalizer(self.finalize) + request.addfinalizer(self.finalize) def run(self): return py.process.cmdexec("echo hello") @@ -144,17 +316,72 @@ def finalize(self): self._old.chdir() # cleanup any other resources + and the actual test function example: +.. sourcecode:: python + def test_some_acceptance_aspect(accept): accept.tmpdir.mkdir("somesub") result = accept.run() assert result - -for registering funcargs from a plugin, talk to the -test configuration object like this:: + +That's it! This test will get automatically skipped with +an appropriate message if you just run ``py.test``:: + + ... OUTPUT of py.test on this example ... + + +.. _`decorator example`: + +example: decorating/extending a funcarg in a TestClass +-------------------------------------------------------------- + +For larger scale setups it's sometimes useful to decorare +a funcarg just for a particular test module or even +a particular test class. We can extend the `accept example`_ +by putting this in our test class: + +.. sourcecode:: python + + class TestSpecialAcceptance: + def pytest_funcarg__accept(self, request): + arg = request.call_next_provider() + # create a special layout in our tempdir + arg.tmpdir.mkdir("special") + return arg + + def test_sometest(self, accept): + assert accept.tmpdir.join("special").check() + +According to the `lookup order`_ our class-specific provider will +be invoked first. Here, we just ask our request object to +call the next provider and decorate its result. This simple +mechanism allows us to stay ignorant of how/where the +function argument is provided. + +Note that we make use here of `py.path.local`_ objects +that provide uniform access to the local filesystem. + +.. _`py.path.local`: ../path.html#local + +Questions and Answers +================================== + +.. _`why pytest_pyfuncarg__ methods?`: + +Why ``pytest_funcarg__*`` methods? +------------------------------------ - XXX +When experimenting with funcargs we also considered an explicit +registration mechanism, i.e. calling a register method e.g. on the +config object. But lacking a good use case for this indirection and +flexibility we decided to go for `Convention over Configuration`_ +and allow to directly specify the provider. It has the +positive implication that you should be able to +"grep" for ``pytest_funcarg__MYARG`` and will find all +providing sites (usually exactly one). +.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration Deleted: /py/trunk/doc/test/statemanage.txt ============================================================================== --- /py/trunk/doc/test/statemanage.txt Tue Apr 14 22:36:45 2009 +++ (empty file) @@ -1,65 +0,0 @@ -================= -Managing state -================= - -funcargs: provding arguments for test functions -=========================================================== - -XXX write docs - -Managing test state across test modules, classes and methods -============================================================ - -Often you want to create some files, database connections or other -state in order to run tests in a certain environment. With -``py.test`` there are three scopes for which you can provide hooks to -manage such state. Again, ``py.test`` will detect these hooks in -modules on a name basis. The following module-level hooks will -automatically be called by the session:: - - def setup_module(module): - """ setup up any state specific to the execution - of the given module. - """ - - def teardown_module(module): - """ teardown any state that was previously setup - with a setup_module method. - """ - -The following hooks are available for test classes:: - - def setup_class(cls): - """ setup up any state specific to the execution - of the given class (which usually contains tests). - """ - - def teardown_class(cls): - """ teardown any state that was previously setup - with a call to setup_class. - """ - - def setup_method(self, method): - """ setup up any state tied to the execution of the given - method in a class. setup_method is invoked for every - test method of a class. - """ - - def teardown_method(self, method): - """ teardown any state that was previously setup - with a setup_method call. - """ - -The last two hooks, ``setup_method`` and ``teardown_method``, are -equivalent to ``setUp`` and ``tearDown`` in the Python standard -library's ``unitest`` module. - -All setup/teardown methods are optional. You could have a -``setup_module`` but no ``teardown_module`` and the other way round. - -Note that while the test session guarantees that for every ``setup`` a -corresponding ``teardown`` will be invoked (if it exists) it does -*not* guarantee that any ``setup`` is called only happens once. For -example, the session might decide to call the ``setup_module`` / -``teardown_module`` pair more than once during the execution of a test -module. Added: py/trunk/doc/test/xunit_setup.txt ============================================================================== --- (empty file) +++ py/trunk/doc/test/xunit_setup.txt Tue Apr 14 22:36:45 2009 @@ -0,0 +1,73 @@ +==================================== +xUnit style setup +==================================== + +.. _`funcargs`: funcargs.html +.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit + +Note: + + Since version 1.0 py.test offers funcargs_ for both + simple and complex test setup needs. Especially + for functional and integration, but also for unit testing, it is + highly recommended that you use this new method. + +Python, Java and other languages have a tradition +of using xUnit_ style testing. This typically +involves the call of a ``setup`` method before +a test function is run and ``teardown`` after +it finishes. With ``py.test`` there are three +scopes for which you can provide setup/teardown +hooks to provide test fixtures: per-module, per-class +and per-method/function. ``py.test`` will +discover and call according methods automatically. +All setup/teardown methods are optional. + +The following methods are called at module level if they exist: + +.. sourcecode:: python + + def setup_module(module): + """ setup up any state specific to the execution + of the given module. + """ + + def teardown_module(module): + """ teardown any state that was previously setup + with a setup_module method. + """ + +The following hooks are available for test classes: + +.. sourcecode:: python + + def setup_class(cls): + """ setup up any state specific to the execution + of the given class (which usually contains tests). + """ + + def teardown_class(cls): + """ teardown any state that was previously setup + with a call to setup_class. + """ + + def setup_method(self, method): + """ setup up any state tied to the execution of the given + method in a class. setup_method is invoked for every + test method of a class. + """ + + def teardown_method(self, method): + """ teardown any state that was previously setup + with a setup_method call. + """ + +The last two hooks, ``setup_method`` and ``teardown_method``, are +equivalent to ``setUp`` and ``tearDown`` in the Python standard +library's `unittest.py module`_. + +Note that it possible that setup/teardown pairs are invoked multiple +times per testing process. + +.. _`unittest.py module`: http://docs.python.org/library/unittest.html + Added: py/trunk/example/funcarg/mysetup/conftest.py ============================================================================== --- (empty file) +++ py/trunk/example/funcarg/mysetup/conftest.py Tue Apr 14 22:36:45 2009 @@ -0,0 +1,10 @@ + +from myapp import MyApp + +class ConftestPlugin: + def pytest_funcarg__mysetup(self, request): + return MySetup() + +class MySetup: + def myapp(self): + return MyApp() Added: py/trunk/example/funcarg/mysetup/myapp.py ============================================================================== --- (empty file) +++ py/trunk/example/funcarg/mysetup/myapp.py Tue Apr 14 22:36:45 2009 @@ -0,0 +1,5 @@ + +class MyApp: + def question(self): + return 6 * 9 + Added: py/trunk/example/funcarg/mysetup/test_sample.py ============================================================================== --- (empty file) +++ py/trunk/example/funcarg/mysetup/test_sample.py Tue Apr 14 22:36:45 2009 @@ -0,0 +1,5 @@ + +def test_answer(mysetup): + app = mysetup.myapp() + answer = app.question() + assert answer == 42 Modified: py/trunk/py/_com.py ============================================================================== --- py/trunk/py/_com.py (original) +++ py/trunk/py/_com.py Tue Apr 14 22:36:45 2009 @@ -90,9 +90,7 @@ l = [] if plugins is None: plugins = self._plugins - if extra: - plugins += list(extra) - for plugin in plugins: + for plugin in list(plugins) + list(extra): try: l.append(getattr(plugin, attrname)) except AttributeError: Modified: py/trunk/py/conftest.py ============================================================================== --- py/trunk/py/conftest.py (original) +++ py/trunk/py/conftest.py Tue Apr 14 22:36:45 2009 @@ -5,10 +5,10 @@ import py class PylibTestconfigPlugin: - def pytest_funcarg__specssh(self, pyfuncitem): - return getspecssh(pyfuncitem.config) - def pytest_funcarg__specsocket(self, pyfuncitem): - return getsocketspec(pyfuncitem.config) + def pytest_funcarg__specssh(self, request): + return getspecssh(request.config) + def pytest_funcarg__specsocket(self, request): + return getsocketspec(request.config) def pytest_addoption(self, parser): group = parser.addgroup("pylib", "py lib testing options") Modified: py/trunk/py/misc/testing/test_com.py ============================================================================== --- py/trunk/py/misc/testing/test_com.py (original) +++ py/trunk/py/misc/testing/test_com.py Tue Apr 14 22:36:45 2009 @@ -135,6 +135,12 @@ l = list(plugins.listattr('x', reverse=True)) assert l == [43, 42, 41] + class api4: + x = 44 + l = list(plugins.listattr('x', extra=(api4,))) + assert l == range(41, 45) + assert len(list(plugins)) == 3 # otherwise extra added + def test_api_and_defaults(): assert isinstance(py._com.comregistry, Registry) Modified: py/trunk/py/test/dist/testing/test_nodemanage.py ============================================================================== --- py/trunk/py/test/dist/testing/test_nodemanage.py (original) +++ py/trunk/py/test/dist/testing/test_nodemanage.py Tue Apr 14 22:36:45 2009 @@ -1,28 +1,25 @@ - -""" RSync filter test -""" - import py from py.__.test.dist.nodemanage import NodeManager -def pytest_funcarg__source(pyfuncitem): - return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source") -def pytest_funcarg__dest(pyfuncitem): - dest = py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest") - return dest +class pytest_funcarg__mysetup: + def __init__(self, request): + basetemp = request.maketempdir() + basetemp = basetemp.mkdir(request.function.__name__) + self.source = basetemp.mkdir("source") + self.dest = basetemp.mkdir("dest") class TestNodeManager: @py.test.mark.xfail("consider / forbid implicit rsyncdirs?") - def test_rsync_roots_no_roots(self, source, dest): - source.ensure("dir1", "file1").write("hello") + def test_rsync_roots_no_roots(self, mysetup): + mysetup.source.ensure("dir1", "file1").write("hello") config = py.test.config._reparse([source]) - nodemanager = NodeManager(config, ["popen//chdir=%s" % dest]) + nodemanager = NodeManager(config, ["popen//chdir=%s" % mysetup.dest]) assert nodemanager.config.topdir == source == config.topdir nodemanager.rsync_roots() p, = nodemanager.gwmanager.multi_exec("import os ; channel.send(os.getcwd())").receive_each() p = py.path.local(p) print "remote curdir", p - assert p == dest.join(config.topdir.basename) + assert p == mysetup.dest.join(config.topdir.basename) assert p.join("dir1").check() assert p.join("dir1", "file1").check() @@ -33,8 +30,9 @@ nodemanager.setup_nodes([].append) nodemanager.wait_nodesready(timeout=2.0) - def test_popen_rsync_subdir(self, testdir, source, dest): - dir1 = source.mkdir("dir1") + def test_popen_rsync_subdir(self, testdir, mysetup): + source, dest = mysetup.source, mysetup.dest + dir1 = mysetup.source.mkdir("dir1") dir2 = dir1.mkdir("dir2") dir2.ensure("hello") for rsyncroot in (dir1, source): @@ -53,7 +51,8 @@ assert dest.join("dir1", "dir2", 'hello').check() nodemanager.gwmanager.exit() - def test_init_rsync_roots(self, source, dest): + def test_init_rsync_roots(self, mysetup): + source, dest = mysetup.source, mysetup.dest dir2 = source.ensure("dir1", "dir2", dir=1) source.ensure("dir1", "somefile", dir=1) dir2.ensure("hello") @@ -68,7 +67,8 @@ assert not dest.join("dir1").check() assert not dest.join("bogus").check() - def test_rsyncignore(self, source, dest): + def test_rsyncignore(self, mysetup): + source, dest = mysetup.source, mysetup.dest dir2 = source.ensure("dir1", "dir2", dir=1) dir5 = source.ensure("dir5", "dir6", "bogus") dirf = source.ensure("dir5", "file") @@ -86,7 +86,8 @@ assert dest.join("dir5","file").check() assert not dest.join("dir6").check() - def test_optimise_popen(self, source, dest): + def test_optimise_popen(self, mysetup): + source, dest = mysetup.source, mysetup.dest specs = ["popen"] * 3 source.join("conftest.py").write("rsyncdirs = ['a']") source.ensure('a', dir=1) @@ -97,7 +98,8 @@ assert gwspec._samefilesystem() assert not gwspec.chdir - def test_setup_DEBUG(self, source, testdir): + def test_setup_DEBUG(self, mysetup, testdir): + source = mysetup.source specs = ["popen"] * 2 source.join("conftest.py").write("rsyncdirs = ['a']") source.ensure('a', dir=1) Modified: py/trunk/py/test/dist/testing/test_txnode.py ============================================================================== --- py/trunk/py/test/dist/testing/test_txnode.py (original) +++ py/trunk/py/test/dist/testing/test_txnode.py Tue Apr 14 22:36:45 2009 @@ -31,8 +31,8 @@ print str(kwargs["excrepr"]) class MySetup: - def __init__(self, pyfuncitem): - self.pyfuncitem = pyfuncitem + def __init__(self, request): + self.request = request def geteventargs(self, eventname, timeout=2.0): eq = EventQueue(self.config.pluginmanager, self.queue) @@ -55,17 +55,11 @@ print "exiting:", gw gw.exit() -def pytest_funcarg__mysetup(pyfuncitem): - mysetup = MySetup(pyfuncitem) +def pytest_funcarg__mysetup(request): + mysetup = MySetup(request) #pyfuncitem.addfinalizer(mysetup.finalize) return mysetup -def pytest_funcarg__testdir(__call__, pyfuncitem): - # decorate to make us always change to testdir - testdir = __call__.execute(firstresult=True) - testdir.chdir() - return testdir - def test_node_hash_equality(mysetup): node = mysetup.makenode() node2 = mysetup.makenode() Modified: py/trunk/py/test/plugin/pytest__pytest.py ============================================================================== --- py/trunk/py/test/plugin/pytest__pytest.py (original) +++ py/trunk/py/test/plugin/pytest__pytest.py Tue Apr 14 22:36:45 2009 @@ -1,19 +1,19 @@ import py class _pytestPlugin: - def pytest_funcarg___pytest(self, pyfuncitem): - return PytestArg(pyfuncitem) + def pytest_funcarg___pytest(self, request): + return PytestArg(request) class PytestArg: - def __init__(self, pyfuncitem): - self.pyfuncitem = pyfuncitem + def __init__(self, request): + self.request = request def getcallrecorder(self, apiclass, comregistry=None): if comregistry is None: - comregistry = self.pyfuncitem.config.pluginmanager.comregistry + comregistry = self.request.config.pluginmanager.comregistry callrecorder = CallRecorder(comregistry) callrecorder.start_recording(apiclass) - self.pyfuncitem.addfinalizer(callrecorder.finalize) + self.request.addfinalizer(callrecorder.finalize) return callrecorder Modified: py/trunk/py/test/plugin/pytest_default.py ============================================================================== --- py/trunk/py/test/plugin/pytest_default.py (original) +++ py/trunk/py/test/plugin/pytest_default.py Tue Apr 14 22:36:45 2009 @@ -22,7 +22,6 @@ rep = runner.ItemTestReport(item, excinfo, "execute", outerr) item.config.api.pytest_itemtestreport(rep=rep) - # XXX make this access pyfuncitem.args or funcargs def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): pyfuncitem.obj(*args, **kwargs) Modified: py/trunk/py/test/plugin/pytest_iocapture.py ============================================================================== --- py/trunk/py/test/plugin/pytest_iocapture.py (original) +++ py/trunk/py/test/plugin/pytest_iocapture.py Tue Apr 14 22:36:45 2009 @@ -2,14 +2,14 @@ class IocapturePlugin: """ capture sys.stdout/sys.stderr / fd1/fd2. """ - def pytest_funcarg__stdcapture(self, pyfuncitem): + def pytest_funcarg__stdcapture(self, request): capture = Capture(py.io.StdCapture) - pyfuncitem.addfinalizer(capture.finalize) + request.addfinalizer(capture.finalize) return capture - def pytest_funcarg__stdcapturefd(self, pyfuncitem): + def pytest_funcarg__stdcapturefd(self, request): capture = Capture(py.io.StdCaptureFD) - pyfuncitem.addfinalizer(capture.finalize) + request.addfinalizer(capture.finalize) return capture class Capture: Modified: py/trunk/py/test/plugin/pytest_monkeypatch.py ============================================================================== --- py/trunk/py/test/plugin/pytest_monkeypatch.py (original) +++ py/trunk/py/test/plugin/pytest_monkeypatch.py Tue Apr 14 22:36:45 2009 @@ -2,9 +2,9 @@ class MonkeypatchPlugin: """ setattr-monkeypatching with automatical reversal after test. """ - def pytest_funcarg__monkeypatch(self, pyfuncitem): + def pytest_funcarg__monkeypatch(self, request): monkeypatch = MonkeyPatch() - pyfuncitem.addfinalizer(monkeypatch.finalize) + request.addfinalizer(monkeypatch.finalize) return monkeypatch notset = object() Modified: py/trunk/py/test/plugin/pytest_plugintester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_plugintester.py (original) +++ py/trunk/py/test/plugin/pytest_plugintester.py Tue Apr 14 22:36:45 2009 @@ -6,37 +6,23 @@ class PlugintesterPlugin: """ test support code for testing pytest plugins. """ - def pytest_funcarg__plugintester(self, pyfuncitem): - pt = PluginTester(pyfuncitem) - pyfuncitem.addfinalizer(pt.finalize) - return pt - -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 pytest_funcarg__plugintester(self, request): + return PluginTester(request) - def finalize(self): - """ called after test function finished execution""" +class PluginTester: + def __init__(self, request): + self.request = request -class PluginTester(Support): def testdir(self): - # XXX import differently, eg. - # FSTester = self.pyfuncitem.config.pluginmanager.getpluginattr("pytester", "FSTester") from pytest_pytester import TmpTestdir - crunner = TmpTestdir(self.pyfuncitem) - self.pyfuncitem.addfinalizer(crunner.finalize) + crunner = TmpTestdir(self.request) + self.request.addfinalizer(crunner.finalize) # - for colitem in self.pyfuncitem.listchain(): - if isinstance(colitem, py.test.collect.Module) and \ - colitem.name.startswith("pytest_"): - crunner.plugins.append(colitem.fspath.purebasename) - break + #for colitem in self.request.listchain(): + # if isinstance(colitem, py.test.collect.Module) and \ + # colitem.name.startswith("pytest_"): + # crunner.plugins.append(colitem.fspath.purebasename) + # break return crunner def apicheck(self, pluginclass): Modified: py/trunk/py/test/plugin/pytest_pytester.py ============================================================================== --- py/trunk/py/test/plugin/pytest_pytester.py (original) +++ py/trunk/py/test/plugin/pytest_pytester.py Tue Apr 14 22:36:45 2009 @@ -11,19 +11,19 @@ class PytesterPlugin: - def pytest_funcarg__linecomp(self, pyfuncitem): + def pytest_funcarg__linecomp(self, request): return LineComp() - def pytest_funcarg__LineMatcher(self, pyfuncitem): + def pytest_funcarg__LineMatcher(self, request): return LineMatcher - def pytest_funcarg__testdir(self, pyfuncitem): - tmptestdir = TmpTestdir(pyfuncitem) + def pytest_funcarg__testdir(self, request): + tmptestdir = TmpTestdir(request) return tmptestdir - def pytest_funcarg__eventrecorder(self, pyfuncitem): + def pytest_funcarg__eventrecorder(self, request): evrec = EventRecorder(py._com.comregistry) - pyfuncitem.addfinalizer(lambda: evrec.comregistry.unregister(evrec)) + request.addfinalizer(lambda: evrec.comregistry.unregister(evrec)) return evrec def test_generic(plugintester): @@ -38,11 +38,11 @@ self.stderr = LineMatcher(errlines) class TmpTestdir: - def __init__(self, pyfuncitem): - self.pyfuncitem = pyfuncitem + def __init__(self, request): + self.request = request # XXX remove duplication with tmpdir plugin - basetmp = pyfuncitem.config.ensuretemp("testdir") - name = pyfuncitem.name + basetmp = request.config.ensuretemp("testdir") + name = request.function.__name__ for i in range(100): try: tmpdir = basetmp.mkdir(name + str(i)) @@ -57,7 +57,7 @@ self._syspathremove = [] self.chdir() # always chdir assert hasattr(self, '_olddir') - self.pyfuncitem.addfinalizer(self.finalize) + self.request.addfinalizer(self.finalize) def __repr__(self): return "" % (self.tmpdir,) @@ -78,7 +78,7 @@ sorter.callrecorder = CallRecorder(registry) sorter.callrecorder.start_recording(api.PluginHooks) sorter.api = sorter.callrecorder.api - self.pyfuncitem.addfinalizer(sorter.callrecorder.finalize) + self.request.addfinalizer(sorter.callrecorder.finalize) return sorter def chdir(self): @@ -90,7 +90,7 @@ items = kwargs.items() if args: source = "\n".join(map(str, args)) - basename = self.pyfuncitem.name + basename = self.request.function.__name__ items.insert(0, (basename, source)) ret = None for name, value in items: @@ -139,7 +139,7 @@ # used from runner functional tests item = self.getitem(source) # the test class where we are called from wants to provide the runner - testclassinstance = self.pyfuncitem.obj.im_self + testclassinstance = self.request.function.im_self runner = testclassinstance.getrunner() return runner(item, **runnerargs) @@ -200,7 +200,7 @@ return self.config.getfsnode(path) def getmodulecol(self, source, configargs=(), withinit=False): - kw = {self.pyfuncitem.name: py.code.Source(source).strip()} + kw = {self.request.function.__name__: py.code.Source(source).strip()} path = self.makepyfile(**kw) if withinit: self.makepyfile(__init__ = "#") @@ -455,3 +455,10 @@ return extralines + + +def test_parseconfig(testdir): + config1 = testdir.parseconfig() + config2 = testdir.parseconfig() + assert config2 != config1 + assert config1 != py.test.config Modified: py/trunk/py/test/plugin/pytest_restdoc.py ============================================================================== --- py/trunk/py/test/plugin/pytest_restdoc.py (original) +++ py/trunk/py/test/plugin/pytest_restdoc.py Tue Apr 14 22:36:45 2009 @@ -382,10 +382,15 @@ "resolve_linkrole('source', 'py/foo/bar.py')") -def pytest_funcarg__testdir(__call__, pyfuncitem): - testdir = __call__.execute(firstresult=True) +def pytest_funcarg__testdir(request): + testdir = request.call_next_provider() testdir.makepyfile(confrest="from py.__.misc.rest import Project") testdir.plugins.append(RestdocPlugin()) + count = 0 + for p in testdir.plugins: + if isinstance(p, RestdocPlugin): + count += 1 + assert count < 2 return testdir class TestDoctest: Modified: py/trunk/py/test/plugin/pytest_resultdb.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultdb.py (original) +++ py/trunk/py/test/plugin/pytest_resultdb.py Tue Apr 14 22:36:45 2009 @@ -1,4 +1,3 @@ -import uuid import py from pytest_resultlog import ResultLog @@ -65,7 +64,7 @@ self._flush() def append_data(self, data): - runid = uuid.uuid4() + runid = py.std.uuid.uuid4() for item in data: item = item.copy() item['runid'] = str(runid) @@ -100,7 +99,7 @@ def append_data(self, data): flat_data = [] - runid = uuid.uuid4() + runid = py.std.uuid.uuid4() for item in data: item = item.copy() item['runid'] = str(runid) Modified: py/trunk/py/test/plugin/pytest_resultlog.py ============================================================================== --- py/trunk/py/test/plugin/pytest_resultlog.py (original) +++ py/trunk/py/test/plugin/pytest_resultlog.py Tue Apr 14 22:36:45 2009 @@ -157,6 +157,7 @@ # ignorant regarding formatting details. def getresultlog(self, testdir, arg): resultlog = testdir.tmpdir.join("resultlog") + testdir.plugins.append("resultlog") args = ["--resultlog=%s" % resultlog] + [arg] testdir.runpytest(*args) return filter(None, resultlog.readlines(cr=0)) @@ -166,7 +167,6 @@ ok = testdir.makepyfile(test_collection_ok="") skip = testdir.makepyfile(test_collection_skip="import py ; py.test.skip('hello')") fail = testdir.makepyfile(test_collection_fail="XXX") - lines = self.getresultlog(testdir, ok) assert not lines @@ -226,6 +226,7 @@ def test_generic(plugintester, LineMatcher): plugintester.apicheck(ResultlogPlugin) testdir = plugintester.testdir() + testdir.plugins.append("resultlog") testdir.makepyfile(""" import py def test_pass(): Modified: py/trunk/py/test/plugin/pytest_tmpdir.py ============================================================================== --- py/trunk/py/test/plugin/pytest_tmpdir.py (original) +++ py/trunk/py/test/plugin/pytest_tmpdir.py Tue Apr 14 22:36:45 2009 @@ -13,9 +13,9 @@ """ provide temporary directories to test functions and methods. """ - def pytest_funcarg__tmpdir(self, pyfuncitem): - name = pyfuncitem.name - return pyfuncitem.config.mktemp(name, numbered=True) + def pytest_funcarg__tmpdir(self, request): + name = request.function.__name__ + return request.config.mktemp(name, numbered=True) # =============================================================================== # @@ -29,7 +29,7 @@ def test_funcarg(testdir): item = testdir.getitem("def test_func(tmpdir): pass") plugin = TmpdirPlugin() - p = plugin.pytest_funcarg__tmpdir(item) + p = plugin.pytest_funcarg__tmpdir(item.getrequest("tmpdir")) assert p.check() bn = p.basename.strip("0123456789-") assert bn.endswith("test_func") Modified: py/trunk/py/test/pluginmanager.py ============================================================================== --- py/trunk/py/test/pluginmanager.py (original) +++ py/trunk/py/test/pluginmanager.py Tue Apr 14 22:36:45 2009 @@ -18,6 +18,7 @@ def register(self, plugin): self.api.pytest_plugin_registered(plugin=plugin) + import types self.comregistry.register(plugin) def unregister(self, plugin): @@ -79,8 +80,8 @@ for x in self.comregistry.listattr(attrname): return x - def listattr(self, attrname, plugins=None): - return self.comregistry.listattr(attrname, plugins=plugins) + def listattr(self, attrname, plugins=None, extra=()): + return self.comregistry.listattr(attrname, plugins=plugins, extra=extra) def call_firstresult(self, *args, **kwargs): return self.comregistry.call_firstresult(*args, **kwargs) Modified: py/trunk/py/test/pycollect.py ============================================================================== --- py/trunk/py/test/pycollect.py (original) +++ py/trunk/py/test/pycollect.py Tue Apr 14 22:36:45 2009 @@ -177,7 +177,7 @@ #print "*" * 20, "INVOKE assertion", self py.magic.invoke(assertion=1) mod = self.obj - self.config.pluginmanager.register(mod) + #self.config.pluginmanager.register(mod) if hasattr(mod, 'setup_module'): self.obj.setup_module(mod) @@ -187,7 +187,7 @@ if not self.config.option.nomagic: #print "*" * 20, "revoke assertion", self py.magic.revoke(assertion=1) - self.config.pluginmanager.unregister(self.obj) + #self.config.pluginmanager.unregister(self.obj) class Class(PyCollectorMixin, py.test.collect.Collector): @@ -365,38 +365,15 @@ for i, argname in py.builtin.enumerate(argnames): if i < startindex: continue + request = self.getrequest(argname) try: - self.funcargs[argname] = self.lookup_onearg(argname) - except LookupError, e: + self.funcargs[argname] = request.call_next_provider() + except request.Error: numdefaults = len(funcobj.func_defaults or ()) if i + numdefaults >= len(argnames): - continue # continue # seems that our args have defaults + continue # our args have defaults XXX issue warning? else: - raise - - def lookup_onearg(self, argname): - prefix = "pytest_funcarg__" - #makerlist = self.config.pluginmanager.listattr(prefix + argname) - value = self.config.pluginmanager.call_firstresult(prefix + argname, pyfuncitem=self) - if value is not None: - return value - else: - self._raisefuncargerror(argname, prefix) - - def _raisefuncargerror(self, argname, prefix="pytest_funcarg__"): - metainfo = self.repr_metainfo() - available = [] - plugins = list(self.config.pluginmanager.comregistry) - #plugins.extend(self.config.pluginmanager.registry.plugins) - for plugin in plugins: - for name in vars(plugin.__class__): - if name.startswith(prefix): - name = name[len(prefix):] - if name not in available: - available.append(name) - msg = "funcargument %r not found for: %s" %(argname,metainfo.verboseline()) - msg += "\n available funcargs: %s" %(", ".join(available),) - raise LookupError(msg) + request._raiselookupfailed() def __eq__(self, other): try: @@ -410,6 +387,70 @@ def __ne__(self, other): return not self == other + def getrequest(self, argname): + return FuncargRequest(pyfuncitem=self, argname=argname) + + +class FuncargRequest: + _argprefix = "pytest_funcarg__" -# DEPRECATED -#from py.__.test.plugin.pytest_doctest import DoctestFile + class Error(LookupError): + """ error on performing funcarg request. """ + + def __init__(self, pyfuncitem, argname): + # XXX make pyfuncitem _pyfuncitem + self._pyfuncitem = pyfuncitem + self.argname = argname + self.function = pyfuncitem.obj + self.config = pyfuncitem.config + self.fspath = pyfuncitem.fspath + self._plugins = self._getplugins() + self._methods = self.config.pluginmanager.listattr( + plugins=self._plugins, + attrname=self._argprefix + str(argname) + ) + + def __repr__(self): + return "" %(self.argname, self._pyfuncitem) + + + def _getplugins(self): + plugins = [] + current = self._pyfuncitem + while not isinstance(current, Module): + current = current.parent + if isinstance(current, (Instance, Module)): + plugins.insert(0, current.obj) + return self.config.pluginmanager.getplugins() + plugins + + def call_next_provider(self): + if not self._methods: + raise self.Error("no provider methods left") + nextmethod = self._methods.pop() + return nextmethod(request=self) + + def addfinalizer(self, finalizer): + self._pyfuncitem.addfinalizer(finalizer) + + def maketempdir(self): + basetemp = self.config.getbasetemp() + tmp = py.path.local.make_numbered_dir( + prefix=self.function.__name__ + "_", + keep=0, rootdir=basetemp) + return tmp + + def _raiselookupfailed(self): + available = [] + for plugin in self._plugins: + for name in vars(plugin.__class__): + if name.startswith(self._argprefix): + name = name[len(self._argprefix):] + if name not in available: + available.append(name) + metainfo = self._pyfuncitem.repr_metainfo() + msg = "funcargument %r not found for: %s" %(self.argname,metainfo.verboseline()) + msg += "\n available funcargs: %s" %(", ".join(available),) + raise LookupError(msg) + + + Modified: py/trunk/py/test/testing/conftest.py ============================================================================== --- py/trunk/py/test/testing/conftest.py (original) +++ py/trunk/py/test/testing/conftest.py Tue Apr 14 22:36:45 2009 @@ -1,2 +1,3 @@ pytest_plugins = "pytest_xfail", "pytest_pytester", "pytest_tmpdir" + Added: py/trunk/py/test/testing/test_funcargs.py ============================================================================== --- (empty file) +++ py/trunk/py/test/testing/test_funcargs.py Tue Apr 14 22:36:45 2009 @@ -0,0 +1,130 @@ +import py + +class TestFuncargs: + def test_funcarg_lookupfails(self, testdir): + testdir.makeconftest(""" + class ConftestPlugin: + def pytest_funcarg__xyzsomething(self, request): + return 42 + """) + item = testdir.getitem("def test_func(some): pass") + exc = py.test.raises(LookupError, "item.setupargs()") + s = str(exc.value) + assert s.find("xyzsomething") != -1 + + def test_funcarg_lookup_default(self, testdir): + item = testdir.getitem("def test_func(some, other=42): pass") + class Provider: + def pytest_funcarg__some(self, request): + return request.function.__name__ + item.config.pluginmanager.register(Provider()) + item.setupargs() + assert len(item.funcargs) == 1 + + def test_funcarg_lookup_default_gets_overriden(self, testdir): + item = testdir.getitem("def test_func(some=42, other=13): pass") + class Provider: + def pytest_funcarg__other(self, request): + return request.function.__name__ + item.config.pluginmanager.register(Provider()) + item.setupargs() + assert len(item.funcargs) == 1 + name, value = item.funcargs.popitem() + assert name == "other" + assert value == item.name + + def test_funcarg_basic(self, testdir): + item = testdir.getitem("def test_func(some, other): pass") + class Provider: + def pytest_funcarg__some(self, request): + return request.function.__name__ + def pytest_funcarg__other(self, request): + return 42 + item.config.pluginmanager.register(Provider()) + item.setupargs() + assert len(item.funcargs) == 2 + assert item.funcargs['some'] == "test_func" + assert item.funcargs['other'] == 42 + + def test_funcarg_lookup_modulelevel(self, testdir): + modcol = testdir.getmodulecol(""" + def pytest_funcarg__something(request): + return request.function.__name__ + + class TestClass: + def test_method(self, something): + pass + def test_func(something): + pass + """) + item1, item2 = testdir.genitems([modcol]) + item1.setupargs() + assert item1.funcargs['something'] == "test_method" + item2.setupargs() + assert item2.funcargs['something'] == "test_func" + +class TestRequest: + def test_request_attributes(self, testdir): + item = testdir.getitem(""" + def pytest_funcarg__something(request): pass + def test_func(something): pass + """) + req = item.getrequest("other") + assert req.argname == "other" + assert req.function == item.obj + assert req.function.__name__ == "test_func" + assert req.config == item.config + assert repr(req).find(req.function.__name__) != -1 + + def test_request_contains_funcargs_methods(self, testdir): + modcol = testdir.getmodulecol(""" + def pytest_funcarg__something(request): + pass + class TestClass: + def pytest_funcarg__something(self, request): + pass + def test_method(self, something): + pass + """) + item1, = testdir.genitems([modcol]) + assert item1.name == "test_method" + methods = item1.getrequest("something")._methods + assert len(methods) == 2 + method1, method2 = methods + assert not hasattr(method1, 'im_self') + assert method2.im_self is not None + + def test_request_call_next_provider(self, testdir): + item = testdir.getitem(""" + def pytest_funcarg__something(request): pass + def test_func(something): pass + """) + req = item.getrequest("something") + val = req.call_next_provider() + assert val is None + py.test.raises(req.Error, "req.call_next_provider()") + + def test_request_addfinalizer(self, testdir): + item = testdir.getitem(""" + def pytest_funcarg__something(request): pass + def test_func(something): pass + """) + req = item.getrequest("something") + l = [1] + req.addfinalizer(l.pop) + item.teardown() + + def test_request_maketemp(self, testdir): + item = testdir.getitem("def test_func(): pass") + req = item.getrequest("xxx") + tmpdir = req.maketempdir() + tmpdir2 = req.maketempdir() + assert tmpdir != tmpdir2 + assert tmpdir.basename.startswith("test_func") + assert tmpdir2.basename.startswith("test_func") + + def test_request_getmodulepath(self, testdir): + modcol = testdir.getmodulecol("def test_somefunc(): pass") + item, = testdir.genitems([modcol]) + req = item.getrequest("hello") + assert req.fspath == modcol.fspath Modified: py/trunk/py/test/testing/test_pickling.py ============================================================================== --- py/trunk/py/test/testing/test_pickling.py (original) +++ py/trunk/py/test/testing/test_pickling.py Tue Apr 14 22:36:45 2009 @@ -1,31 +1,27 @@ import py -def pytest_funcarg__pickletransport(pyfuncitem): - return ImmutablePickleTransport() - -def pytest_pyfunc_call(__call__, pyfuncitem, args, kwargs): - # for each function call we patch py._com.comregistry - # so that the unpickling of config objects - # (which bind to this mechanism) doesn't do harm - # usually config objects are no meant to be unpickled in - # the same system +def setglobals(request): oldconfig = py.test.config oldcom = py._com.comregistry print "setting py.test.config to None" py.test.config = None py._com.comregistry = py._com.Registry() - try: - return __call__.execute(firstresult=True) - finally: + def resetglobals(): print "setting py.test.config to", oldconfig py.test.config = oldconfig py._com.comregistry = oldcom + request.addfinalizer(resetglobals) + +def pytest_funcarg__testdir(request): + setglobals(request) + return request.call_next_provider() class ImmutablePickleTransport: - def __init__(self): + def __init__(self, request): from py.__.test.dist.mypickle import ImmutablePickler self.p1 = ImmutablePickler(uneven=0) self.p2 = ImmutablePickler(uneven=1) + setglobals(request) def p1_to_p2(self, obj): return self.p2.loads(self.p1.dumps(obj)) @@ -39,6 +35,8 @@ return p2config class TestImmutablePickling: + pytest_funcarg__pickletransport = ImmutablePickleTransport + def test_pickle_config(self, testdir, pickletransport): config1 = testdir.parseconfig() assert config1.topdir == testdir.tmpdir Modified: py/trunk/py/test/testing/test_pycollect.py ============================================================================== --- py/trunk/py/test/testing/test_pycollect.py (original) +++ py/trunk/py/test/testing/test_pycollect.py Tue Apr 14 22:36:45 2009 @@ -34,13 +34,6 @@ x = l.pop() assert x is None - def test_module_participates_as_plugin(self, testdir): - modcol = testdir.getmodulecol("") - modcol.setup() - assert modcol.config.pluginmanager.isregistered(modcol.obj) - modcol.teardown() - assert not modcol.config.pluginmanager.isregistered(modcol.obj) - def test_module_considers_pluginmanager_at_import(self, testdir): modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',") py.test.raises(ImportError, "modcol.obj") @@ -243,88 +236,6 @@ assert f1 == f1_b assert not f1 != f1_b - def test_funcarg_lookupfails(self, testdir): - testdir.makeconftest(""" - class ConftestPlugin: - def pytest_funcarg__something(self, pyfuncitem): - return 42 - """) - item = testdir.getitem("def test_func(some): pass") - exc = py.test.raises(LookupError, "item.setupargs()") - s = str(exc.value) - assert s.find("something") != -1 - - def test_funcarg_lookup_default(self, testdir): - item = testdir.getitem("def test_func(some, other=42): pass") - class Provider: - def pytest_funcarg__some(self, pyfuncitem): - return pyfuncitem.name - item.config.pluginmanager.register(Provider()) - item.setupargs() - assert len(item.funcargs) == 1 - - def test_funcarg_lookup_default_gets_overriden(self, testdir): - item = testdir.getitem("def test_func(some=42, other=13): pass") - class Provider: - def pytest_funcarg__other(self, pyfuncitem): - return pyfuncitem.name - item.config.pluginmanager.register(Provider()) - item.setupargs() - assert len(item.funcargs) == 1 - name, value = item.funcargs.popitem() - assert name == "other" - assert value == item.name - - def test_funcarg_basic(self, testdir): - item = testdir.getitem("def test_func(some, other): pass") - class Provider: - def pytest_funcarg__some(self, pyfuncitem): - return pyfuncitem.name - def pytest_funcarg__other(self, pyfuncitem): - return 42 - item.config.pluginmanager.register(Provider()) - item.setupargs() - assert len(item.funcargs) == 2 - assert item.funcargs['some'] == "test_func" - assert item.funcargs['other'] == 42 - - def test_funcarg_addfinalizer(self, testdir): - item = testdir.getitem("def test_func(some): pass") - l = [] - class Provider: - def pytest_funcarg__some(self, pyfuncitem): - pyfuncitem.addfinalizer(lambda: l.append(42)) - return 3 - item.config.pluginmanager.register(Provider()) - item.setupargs() - assert len(item.funcargs) == 1 - assert item.funcargs['some'] == 3 - assert len(l) == 0 - item.teardown() - assert len(l) == 1 - assert l[0] == 42 - - def test_funcarg_lookup_modulelevel(self, testdir): - modcol = testdir.getmodulecol(""" - def pytest_funcarg__something(pyfuncitem): - return pyfuncitem.name - - class TestClass: - def test_method(self, something): - pass - def test_func(something): - pass - """) - item1, item2 = testdir.genitems([modcol]) - modcol.setup() - assert modcol.config.pluginmanager.isregistered(modcol.obj) - item1.setupargs() - assert item1.funcargs['something'] == "test_method" - item2.setupargs() - assert item2.funcargs['something'] == "test_func" - modcol.teardown() - assert not modcol.config.pluginmanager.isregistered(modcol.obj) - class TestSorting: def test_check_equality_and_cmp_basic(self, testdir): modcol = testdir.getmodulecol(""" Modified: py/trunk/py/test/testing/test_traceback.py ============================================================================== --- py/trunk/py/test/testing/test_traceback.py (original) +++ py/trunk/py/test/testing/test_traceback.py Tue Apr 14 22:36:45 2009 @@ -10,7 +10,7 @@ def test_traceback_argsetup(self, testdir): testdir.makeconftest(""" class ConftestPlugin: - def pytest_funcarg__hello(self, pyfuncitem): + def pytest_funcarg__hello(self, request): raise ValueError("xyz") """) p = testdir.makepyfile("def test(hello): pass") From hpk at codespeak.net Tue Apr 14 23:04:58 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 14 Apr 2009 23:04:58 +0200 (CEST) Subject: [py-svn] r64077 - in py/trunk: . py/execnet/testing Message-ID: <20090414210458.27D09169F66@codespeak.net> Author: hpk Date: Tue Apr 14 23:04:57 2009 New Revision: 64077 Modified: py/trunk/CHANGELOG py/trunk/MANIFEST py/trunk/py/execnet/testing/test_gwmanage.py py/trunk/setup.py Log: sync from hg-trunk Modified: py/trunk/CHANGELOG ============================================================================== --- py/trunk/CHANGELOG (original) +++ py/trunk/CHANGELOG Tue Apr 14 23:04:57 2009 @@ -3,13 +3,20 @@ Changes between 0.9.2 and 1.0 (UNRELEASED) ============================================= +* introduced new "funcarg" setup method, + see doc/test/funcarg.txt + +* introduced plugin architecuture and many + new py.test plugins, see + doc/test/plugins.txt + * teardown_method is now guaranteed to get called after a test method has run. * new method: py.test.importorskip(mod,minversion) will either import or call py.test.skip() -* revised internal py.test architecture +* completely revised internal py.test architecture * new py.process.ForkedFunc object allowing to fork execution of a function to a sub process Modified: py/trunk/MANIFEST ============================================================================== --- py/trunk/MANIFEST (original) +++ py/trunk/MANIFEST Tue Apr 14 23:04:57 2009 @@ -4,7 +4,6 @@ MANIFEST README.txt TODO.txt -ez_setup.py py/LICENSE py/__init__.py py/_com.py @@ -297,6 +296,7 @@ py/test/testing/test_config.py py/test/testing/test_conftesthandle.py py/test/testing/test_deprecated_api.py +py/test/testing/test_funcargs.py py/test/testing/test_genitems.py py/test/testing/test_outcome.py py/test/testing/test_parseopt.py Modified: py/trunk/py/execnet/testing/test_gwmanage.py ============================================================================== --- py/trunk/py/execnet/testing/test_gwmanage.py (original) +++ py/trunk/py/execnet/testing/test_gwmanage.py Tue Apr 14 23:04:57 2009 @@ -33,7 +33,8 @@ hm.exit() assert not len(hm.gateways) - def test_popens_rsync(self, source): + def test_popens_rsync(self, mysetup): + source = mysetup.source hm = GatewayManager(["popen"] * 2) hm.makegateways() assert len(hm.gateways) == 2 @@ -45,7 +46,8 @@ hm.exit() assert not len(hm.gateways) - def test_rsync_popen_with_path(self, source, dest): + def test_rsync_popen_with_path(self, mysetup): + source, dest = mysetup.source, mysetup.dest hm = GatewayManager(["popen//chdir=%s" %dest] * 1) hm.makegateways() source.ensure("dir1", "dir2", "hello") @@ -59,7 +61,8 @@ assert dest.join("dir1", "dir2").check() assert dest.join("dir1", "dir2", 'hello').check() - def test_hostmanage_rsync_same_popen_twice(self, source, dest, _pytest): + def test_hostmanage_rsync_same_popen_twice(self, mysetup, _pytest): + source, dest = mysetup.source, mysetup.dest rec = _pytest.getcallrecorder(py.execnet._API) hm = GatewayManager(["popen//chdir=%s" %dest] * 2) hm.makegateways() @@ -109,13 +112,15 @@ assert l[0].startswith(curwd) assert l[0].endswith("hello") -def pytest_funcarg__source(pyfuncitem): - return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source") -def pytest_funcarg__dest(pyfuncitem): - return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest") +class pytest_funcarg__mysetup: + def __init__(self, request): + tmp = request.maketempdir() + self.source = tmp.mkdir("source") + self.dest = tmp.mkdir("dest") class TestHRSync: - def test_hrsync_filter(self, source, dest): + def test_hrsync_filter(self, mysetup): + source, dest = mysetup.source, mysetup.dest source.ensure("dir", "file.txt") source.ensure(".svn", "entries") source.ensure(".somedotfile", "moreentries") @@ -129,7 +134,8 @@ assert 'file.txt' in basenames assert 'somedir' in basenames - def test_hrsync_one_host(self, source, dest): + def test_hrsync_one_host(self, mysetup): + source, dest = mysetup.source, mysetup.dest gw = py.execnet.makegateway("popen//chdir=%s" % dest) finished = [] rsync = HostRSync(source) Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Tue Apr 14 23:04:57 2009 @@ -1,7 +1,7 @@ """ setup file for 'py' package based on: - https://codespeak.net/svn/py/trunk, revision=63999 + https://codespeak.net/svn/py/trunk, revision=64076 autogenerated by gensetup.py """ From hpk at codespeak.net Wed Apr 15 12:11:40 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 15 Apr 2009 12:11:40 +0200 (CEST) Subject: [py-svn] r64091 - py/trunk Message-ID: <20090415101140.E57251684C9@codespeak.net> Author: hpk Date: Wed Apr 15 12:11:39 2009 New Revision: 64091 Added: py/trunk/_findpy.py (contents, props changed) Modified: py/trunk/MANIFEST py/trunk/setup.py Log: adding missing file Modified: py/trunk/MANIFEST ============================================================================== --- py/trunk/MANIFEST (original) +++ py/trunk/MANIFEST Wed Apr 15 12:11:39 2009 @@ -4,6 +4,7 @@ MANIFEST README.txt TODO.txt +_findpy.py py/LICENSE py/__init__.py py/_com.py Added: py/trunk/_findpy.py ============================================================================== --- (empty file) +++ py/trunk/_findpy.py Wed Apr 15 12:11:39 2009 @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +# +# find and import a version of 'py' +# +import sys +import os +from os.path import dirname as opd, exists, join, basename, abspath + +def searchpy(current): + while 1: + last = current + initpy = join(current, '__init__.py') + if not exists(initpy): + pydir = join(current, 'py') + # recognize py-package and ensure it is importable + if exists(pydir) and exists(join(pydir, '__init__.py')): + #for p in sys.path: + # if p == current: + # return True + if current != sys.path[0]: # if we are already first, then ok + print >>sys.stderr, "inserting into sys.path:", current + sys.path.insert(0, current) + return True + current = opd(current) + if last == current: + return False + +if not searchpy(abspath(os.curdir)): + if not searchpy(opd(abspath(sys.argv[0]))): + if not searchpy(opd(__file__)): + pass # let's hope it is just on sys.path + +import py + +if __name__ == '__main__': + print "py lib is at", py.__file__ Modified: py/trunk/setup.py ============================================================================== --- py/trunk/setup.py (original) +++ py/trunk/setup.py Wed Apr 15 12:11:39 2009 @@ -1,7 +1,7 @@ """ setup file for 'py' package based on: - https://codespeak.net/svn/py/trunk, revision=64076 + https://codespeak.net/svn/py/trunk, revision=64088 autogenerated by gensetup.py """ From arigo at codespeak.net Thu Apr 16 16:21:21 2009 From: arigo at codespeak.net (arigo at codespeak.net) Date: Thu, 16 Apr 2009 16:21:21 +0200 (CEST) Subject: [py-svn] r64170 - py/dist/py/test Message-ID: <20090416142121.8B87F169E03@codespeak.net> Author: arigo Date: Thu Apr 16 16:21:21 2009 New Revision: 64170 Modified: py/dist/py/test/runner.py Log: Temporary checkin. Don't force importing py.execnet when running py.test normally. Useful when we run on top of pypy-c and don't have the "thread" module. Modified: py/dist/py/test/runner.py ============================================================================== --- py/dist/py/test/runner.py (original) +++ py/dist/py/test/runner.py Thu Apr 16 16:21:21 2009 @@ -10,7 +10,6 @@ from py.__.test import event from py.__.test.outcome import Exit -from py.__.test.dist.mypickle import ImmutablePickler import py.__.test.custompdb class RobustRun(object): @@ -95,6 +94,7 @@ def forked_run_report(item, pdb=None): EXITSTATUS_TESTEXIT = 4 + from py.__.test.dist.mypickle import ImmutablePickler ipickle = ImmutablePickler(uneven=0) ipickle.selfmemoize(item.config) def runforked(): From afa at codespeak.net Fri Apr 17 11:52:15 2009 From: afa at codespeak.net (afa at codespeak.net) Date: Fri, 17 Apr 2009 11:52:15 +0200 (CEST) Subject: [py-svn] r64232 - py/dist/py/path/local Message-ID: <20090417095215.EF273169E90@codespeak.net> Author: afa Date: Fri Apr 17 11:52:15 2009 New Revision: 64232 Modified: py/dist/py/path/local/local.py Log: On Windows, have sysfind() also look at the current directory, if '.' is not in the PATH. This better matches what you get from the command prompt (or with os.system()), and should fix the buildbot. Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Fri Apr 17 11:52:15 2009 @@ -514,6 +514,8 @@ else: if iswin32: paths = py.std.os.environ['Path'].split(';') + if '' not in paths and '.' not in paths: + paths.append('.') try: systemroot = os.environ['SYSTEMROOT'] except KeyError: From afa at codespeak.net Fri Apr 17 13:18:34 2009 From: afa at codespeak.net (afa at codespeak.net) Date: Fri, 17 Apr 2009 13:18:34 +0200 (CEST) Subject: [py-svn] r64253 - py/dist/py/path/local/testing Message-ID: <20090417111834.2DBD1169EAF@codespeak.net> Author: afa Date: Fri Apr 17 13:18:33 2009 New Revision: 64253 Modified: py/dist/py/path/local/testing/test_local.py Log: Add a test for previous change: on Windows, sysfind() also tries the current directory. Modified: py/dist/py/path/local/testing/test_local.py ============================================================================== --- py/dist/py/path/local/testing/test_local.py (original) +++ py/dist/py/path/local/testing/test_local.py Fri Apr 17 13:18:33 2009 @@ -199,6 +199,17 @@ assert x.check(file=1) assert py.path.local.sysfind('jaksdkasldqwe') is None + def test_sysfind_in_currentdir(self): + cmd = py.path.local.sysfind('cmd') + root = cmd.new(dirname='', basename='') # c:\ in most installations + + old = root.chdir() + try: + x = py.path.local.sysfind(cmd.relto(root)) + assert x.check(file=1) + finally: + old.chdir() + class TestExecution(LocalSetup): disabled = py.std.sys.platform == 'win32' From hpk at codespeak.net Fri Apr 17 13:30:56 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 17 Apr 2009 13:30:56 +0200 (CEST) Subject: [py-svn] r64259 - in py/trunk/py/path/local: . testing Message-ID: <20090417113056.4819E169EB9@codespeak.net> Author: hpk Date: Fri Apr 17 13:30:55 2009 New Revision: 64259 Modified: py/trunk/py/path/local/local.py py/trunk/py/path/local/testing/test_win.py Log: porting afa's 64232 and 64253 to trunk Modified: py/trunk/py/path/local/local.py ============================================================================== --- py/trunk/py/path/local/local.py (original) +++ py/trunk/py/path/local/local.py Fri Apr 17 13:30:55 2009 @@ -514,6 +514,8 @@ else: if iswin32: paths = py.std.os.environ['Path'].split(';') + if '' not in paths and '.' not in paths: + paths.append('.') try: systemroot = os.environ['SYSTEMROOT'] except KeyError: Modified: py/trunk/py/path/local/testing/test_win.py ============================================================================== --- py/trunk/py/path/local/testing/test_win.py (original) +++ py/trunk/py/path/local/testing/test_win.py Fri Apr 17 13:30:55 2009 @@ -42,4 +42,15 @@ assert t1 == str(self.root) + '\\a_path' t1 = self.root.join('dir/a_path') assert t1 == str(self.root) + '\\dir\\a_path' - + + def test_sysfind_in_currentdir(self): + cmd = py.path.local.sysfind('cmd') + root = cmd.new(dirname='', basename='') # c:\ in most installations + + old = root.chdir() + try: + x = py.path.local.sysfind(cmd.relto(root)) + assert x.check(file=1) + finally: + old.chdir() + From hpk at codespeak.net Fri Apr 17 13:40:13 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 17 Apr 2009 13:40:13 +0200 (CEST) Subject: [py-svn] r64263 - py/trunk/py/log Message-ID: <20090417114013.0D5D4169EBC@codespeak.net> Author: hpk Date: Fri Apr 17 13:40:13 2009 New Revision: 64263 Modified: py/trunk/py/log/consumer.py Log: fix failing test on windows Modified: py/trunk/py/log/consumer.py ============================================================================== --- py/trunk/py/log/consumer.py (original) +++ py/trunk/py/log/consumer.py Fri Apr 17 13:40:13 2009 @@ -49,7 +49,7 @@ for priority in "LOG_EMERG LOG_ALERT LOG_CRIT LOG_ERR LOG_WARNING LOG_NOTICE LOG_INFO LOG_DEBUG".split(): try: exec("%s = py.std.syslog.%s" % (priority, priority)) - except AttributeError: + except ImportError: pass def __init__(self, priority = None): From hpk at codespeak.net Fri Apr 17 14:00:13 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 17 Apr 2009 14:00:13 +0200 (CEST) Subject: [py-svn] r64266 - in py/trunk: . doc/test py/test/plugin Message-ID: <20090417120013.3C518169E70@codespeak.net> Author: hpk Date: Fri Apr 17 14:00:12 2009 New Revision: 64266 Modified: py/trunk/.hgignore py/trunk/_findpy.py py/trunk/doc/test/funcargs.txt py/trunk/py/test/plugin/api.py Log: merging hg/bitbucket py-trunk Modified: py/trunk/.hgignore ============================================================================== --- py/trunk/.hgignore (original) +++ py/trunk/.hgignore Fri Apr 17 14:00:12 2009 @@ -10,3 +10,4 @@ *.pyc *.pyo *.swp +*.html Modified: py/trunk/_findpy.py ============================================================================== --- py/trunk/_findpy.py (original) +++ py/trunk/_findpy.py Fri Apr 17 14:00:12 2009 @@ -1,7 +1,9 @@ #!/usr/bin/env python # -# find and import a version of 'py' +# try to find and import a nearby version of the 'py' package. +# otherwise use the system global default +# XXX turn this into a developer-only thing? # import sys import os Modified: py/trunk/doc/test/funcargs.txt ============================================================================== --- py/trunk/doc/test/funcargs.txt (original) +++ py/trunk/doc/test/funcargs.txt Fri Apr 17 14:00:12 2009 @@ -131,7 +131,7 @@ for a use of this method. -.. _`lookup order`: +.. _`funcarg lookup order`: Order of funcarg provider lookup ---------------------------------------- @@ -154,30 +154,35 @@ matching provider will be performed. -Funcarg Examples -===================== +Funcarg Tutorial Examples +============================ -Example: basic application specific setup ------------------------------------------------------ +tutorial example: the "test/app-specific" setup pattern +--------------------------------------------------------- -Here is a basic useful example for handling application -specific setup. The goal is to have one place where -we have the glue code for bootstrapping and configuring -application objects and allow test modules and -test functions to stay ignorant of involved details. -Let's start with the using side and consider a simple -test function living in a test file ``test_sample.py``: +Here is a basic useful step-wise example for handling application +specific test setup. The goal is to have one place where we have the +glue code for bootstrapping and configuring application objects and allow +test modules and test functions to stay ignorant of involved details. + +step 1: use and implement a test/app-specific "mysetup" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Let's write a simple test function living in a test file +``test_sample.py`` that uses a ``mysetup`` funcarg for accessing test +specific setup. .. sourcecode:: python + # ./test_sample.py def test_answer(mysetup): app = mysetup.myapp() answer = app.question() assert answer == 42 -To run this test py.test looks up and calls a provider to obtain the -required ``mysetup`` function argument. The test function simply -interacts with the provided application specific setup. +To run this test py.test needs to find and call a provider to +obtain the required ``mysetup`` function argument. The test +function interacts with the provided application specific setup. To provide the ``mysetup`` function argument we write down a provider method in a `local plugin`_ by putting the @@ -185,6 +190,7 @@ .. sourcecode:: python + # ./conftest.py from myapp import MyApp class ConftestPlugin: @@ -195,13 +201,14 @@ def myapp(self): return MyApp() -The ``pytest_funcarg__mysetup`` method is called to -provide a value for the requested ``mysetup`` test function argument. -To complete the example we put a pseudo MyApp object -into ``myapp.py``: +py.test finds the ``pytest_funcarg__mysetup`` method by +name, see `funcarg lookup order`_ for more on this mechanism. + +To run the example we put a pseudo MyApp object into ``myapp.py``: .. sourcecode:: python + # ./myapp.py class MyApp: def question(self): return 6 * 9 @@ -217,42 +224,19 @@ > assert answer == 42 E assert 54 == 42 -If you are confused as to that the question or answer is -here, please visit here_. +If you are confused as to what the concrete question or answers +mean actually, please visit here_ :) .. _here: http://uncyclopedia.wikia.com/wiki/The_Hitchhiker's_Guide_to_the_Galaxy - .. _`local plugin`: ext.html#local-plugin -Example: specifying funcargs in test modules or classes ---------------------------------------------------------- - -.. sourcecode:: python - - def pytest_funcarg__mysetup(request): - result = request.call_next_provider() - result.extra = "..." - return result - -You can also put such a function into a test class like this: - -.. sourcecode:: python - - class TestClass: - def pytest_funcarg__mysetup(self, request): - # ... - # +step 2: adding a command line option +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -Example: command line option for providing SSH-host ------------------------------------------------------------ - -If you provide a "funcarg" from a plugin you can -easily make methods depend on command line options -or environment settings. Here is a complete -example that allows to run tests involving -an SSH connection if an ssh host is specified: +If you provide a "funcarg" from a plugin you can easily make methods +depend on command line options or environment settings. Let's write a +local plugin that adds a command line option to ``py.test`` invocations: .. sourcecode:: python @@ -266,25 +250,44 @@ class MySetupFuncarg: def __init__(self, request): self.request = request - def ssh_gateway(self): + def getsshconnection(self): host = self.request.config.option.ssh if host is None: py.test.skip("specify ssh host with --ssh to run this test") return py.execnet.SshGateway(host) -Now any test functions can use the "mysetup.ssh_gateway()" method like this: +Now any test functions can use the ``mysetup.getsshconnection()`` method like this: .. sourcecode:: python class TestClass: def test_function(self, mysetup): - ssh_gw = mysetup.ssh_gateway() - # work with ssh_gw + conn = mysetup.getsshconnection() + # work with conn Running this without the command line will yield this run result:: - ... + XXX fill in + + +Example: specifying funcargs in test modules or classes +--------------------------------------------------------- + +.. sourcecode:: python + def pytest_funcarg__mysetup(request): + result = request.call_next_provider() + result.extra = "..." + return result + +You can put such a function into a test class like this: + +.. sourcecode:: python + + class TestClass: + def pytest_funcarg__mysetup(self, request): + # ... + # .. _`accept example`: @@ -355,7 +358,7 @@ def test_sometest(self, accept): assert accept.tmpdir.join("special").check() -According to the `lookup order`_ our class-specific provider will +According to the `funcarg lookup order`_ our class-specific provider will be invoked first. Here, we just ask our request object to call the next provider and decorate its result. This simple mechanism allows us to stay ignorant of how/where the Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Fri Apr 17 14:00:12 2009 @@ -67,9 +67,6 @@ def pytest_itemrun(self, item, pdb=None): """ run given test item and return test report. """ - def pytest_item_runtest_finished(self, item, excinfo, outerr): - """ called in-process after runtest() returned. """ - def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): """ return True if we consumed/did the call to the python function item. """ From hpk at codespeak.net Fri Apr 17 20:32:30 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Fri, 17 Apr 2009 20:32:30 +0200 (CEST) Subject: [py-svn] r64306 - in py/trunk: contrib/pytest_twisted py/test py/test/dist py/test/plugin py/test/testing Message-ID: <20090417183230.66804169E6F@codespeak.net> Author: hpk Date: Fri Apr 17 20:32:26 2009 New Revision: 64306 Added: py/trunk/py/test/testing/test_api.py Modified: py/trunk/contrib/pytest_twisted/__init__.py py/trunk/py/test/config.py py/trunk/py/test/dist/dsession.py py/trunk/py/test/plugin/api.py py/trunk/py/test/pluginmanager.py py/trunk/py/test/runner.py py/trunk/py/test/testing/acceptance_test.py py/trunk/py/test/testing/test_pluginmanager.py Log: merging hg py-trunk to svn py-trunk. Modified: py/trunk/contrib/pytest_twisted/__init__.py ============================================================================== --- py/trunk/contrib/pytest_twisted/__init__.py (original) +++ py/trunk/contrib/pytest_twisted/__init__.py Fri Apr 17 20:32:26 2009 @@ -100,18 +100,13 @@ def pytest_pyfunc_call(self, pyfuncitem, *args, **kwargs): if self.twisted: - def wrapper(func): - """ - wrapper just to pass back (injecting) the test-function into - doit() by using a greenlet switch. - """ - if hasattr(func, 'obj'): - # XXX: what about **kwargs? - res = gr_twisted.switch(lambda: func.obj(*args)) - if res: - res.raiseException() - pyfuncitem = wrapper(pyfuncitem) - + # XXX1 kwargs? + # XXX2 we want to delegate actual call to next plugin + # (which may want to produce test coverage, etc.) + res = gr_twisted.switch(lambda: pyfuncitem.obj(*args)) + if res: + res.raiseException() + return True # indicates that we performed the function call gr_twisted = greenlet(_run_twisted) gr_tests = greenlet.getcurrent() Modified: py/trunk/py/test/config.py ============================================================================== --- py/trunk/py/test/config.py (original) +++ py/trunk/py/test/config.py Fri Apr 17 20:32:26 2009 @@ -76,6 +76,7 @@ def _preparse(self, args): self._conftest.setinitial(args) + self.pluginmanager.consider_preparse(args) self.pluginmanager.consider_env() self.pluginmanager.do_addoption(self._parser) Modified: py/trunk/py/test/dist/dsession.py ============================================================================== --- py/trunk/py/test/dist/dsession.py (original) +++ py/trunk/py/test/dist/dsession.py Fri Apr 17 20:32:26 2009 @@ -203,7 +203,7 @@ tosend[:] = tosend[room:] # update inplace if tosend: # we have some left, give it to the main loop - self.queueevent(pytest_rescheduleitems, tosend) + self.queueevent("pytest_rescheduleitems", items=tosend) def senditems_load(self, tosend): if not tosend: Modified: py/trunk/py/test/plugin/api.py ============================================================================== --- py/trunk/py/test/plugin/api.py (original) +++ py/trunk/py/test/plugin/api.py Fri Apr 17 20:32:26 2009 @@ -69,6 +69,7 @@ def pytest_pyfunc_call(self, pyfuncitem, args, kwargs): """ return True if we consumed/did the call to the python function item. """ + pytest_pyfunc_call.firstresult = True def pytest_item_makereport(self, item, excinfo, when, outerr): """ return ItemTestReport for the given test outcome. """ Modified: py/trunk/py/test/pluginmanager.py ============================================================================== --- py/trunk/py/test/pluginmanager.py (original) +++ py/trunk/py/test/pluginmanager.py Fri Apr 17 20:32:26 2009 @@ -47,6 +47,11 @@ for spec in self._envlist("PYTEST_PLUGINS"): self.import_plugin(spec) + def consider_preparse(self, args): + for opt1,opt2 in zip(args, args[1:]): + if opt1 == "-p": + self.import_plugin(opt2) + def consider_conftest(self, conftestmodule): cls = getattr(conftestmodule, 'ConftestPlugin', None) if cls is not None and cls not in self.impname2plugin: Modified: py/trunk/py/test/runner.py ============================================================================== --- py/trunk/py/test/runner.py (original) +++ py/trunk/py/test/runner.py Fri Apr 17 20:32:26 2009 @@ -53,8 +53,6 @@ excinfo = py.code.ExceptionInfo() return CollectReport(collector, res, excinfo, outerr) -from cPickle import Pickler, Unpickler - def forked_run_report(item, pdb=None): EXITSTATUS_TESTEXIT = 4 from py.__.test.dist.mypickle import ImmutablePickler Modified: py/trunk/py/test/testing/acceptance_test.py ============================================================================== --- py/trunk/py/test/testing/acceptance_test.py (original) +++ py/trunk/py/test/testing/acceptance_test.py Fri Apr 17 20:32:26 2009 @@ -17,6 +17,23 @@ '*ERROR: hello' ]) + def test_config_preparse_plugin_option(self, testdir): + testdir.makepyfile(pytest_xyz=""" + class XyzPlugin: + def pytest_addoption(self, parser): + parser.addoption("--xyz", dest="xyz", action="store") + """) + testdir.makepyfile(test_one=""" + import py + def test_option(): + assert py.test.config.option.xyz == "123" + """) + result = testdir.runpytest("-p", "xyz", "--xyz=123") + assert result.ret == 0 + assert result.stdout.fnmatch_lines([ + '*1 passed*', + ]) + def test_basetemp(self, testdir): mytemp = testdir.tmpdir.mkdir("mytemp") p = testdir.makepyfile(""" Added: py/trunk/py/test/testing/test_api.py ============================================================================== --- (empty file) +++ py/trunk/py/test/testing/test_api.py Fri Apr 17 20:32:26 2009 @@ -0,0 +1,14 @@ + +class TestPyfuncHooks: + def test_pyfunc_call(self, testdir): + item = testdir.getitem("def test_func(): raise ValueError") + config = item.config + class MyPlugin1: + def pytest_pyfunc_call(self, pyfuncitem, *args, **kwargs): + raise ValueError + class MyPlugin2: + def pytest_pyfunc_call(self, pyfuncitem, *args, **kwargs): + return True + config.pluginmanager.register(MyPlugin1()) + config.pluginmanager.register(MyPlugin2()) + config.api.pytest_pyfunc_call(pyfuncitem=item) Modified: py/trunk/py/test/testing/test_pluginmanager.py ============================================================================== --- py/trunk/py/test/testing/test_pluginmanager.py (original) +++ py/trunk/py/test/testing/test_pluginmanager.py Fri Apr 17 20:32:26 2009 @@ -8,6 +8,12 @@ monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'nonexistingmodule') py.test.raises(ImportError, "pluginmanager.consider_env()") + def test_preparse_args(self, monkeypatch): + pluginmanager = PluginManager() + py.test.raises(ImportError, """ + pluginmanager.consider_preparse(["xyz", "-p", "hello123"]) + """) + def test_consider_env_plugin_instantiation(self, testdir, monkeypatch): pluginmanager = PluginManager() testdir.syspathinsert() From py-svn at codespeak.net Fri Apr 24 23:41:30 2009 From: py-svn at codespeak.net (Tarra Strop) Date: Fri, 24 Apr 2009 23:41:30 +0200 Subject: [py-svn] Confirm Delivery Address Message-ID: <000601c9c525$6e18a320$1b291753@specialxp> When buying pillules, better to enjoy our fast delivery, than staying in line http://wibglyqr.sowgugob.cn/ -------------- next part -------------- An HTML attachment was scrubbed... URL: From py-svn at codespeak.net Thu Apr 30 08:34:03 2009 From: py-svn at codespeak.net (Eunice Schilder) Date: Thu, 30 Apr 2009 09:34:03 +0300 Subject: [py-svn] Instant payment notification Message-ID: <20090430063331.A5A3F169E40@codespeak.net> Your body is damaged by illness? The products you need for the prices you want http://ksfeskvj.tobsuxog.cn/ -------------- next part -------------- An HTML attachment was scrubbed... URL: