[py-svn] commit/pytest: 2 new changesets

Bitbucket commits-noreply at bitbucket.org
Thu Jul 7 06:20:34 CEST 2011


2 new changesets in pytest:

http://bitbucket.org/hpk42/pytest/changeset/606d81cbbd31/
changeset:   606d81cbbd31
user:        gutworth
date:        2011-07-07 06:24:04
summary:     simplify rewrite-on-import

Use load_module on the import hook to load the rewritten module. This allows the
removal of the complicated code related to copying pyc files in and out of the
cache location. It also plays more nicely with parallel py.test processes like
the ones found in xdist.
affected #:  3 files (1.5 KB)

--- a/_pytest/assertion/__init__.py	Wed Jul 06 20:25:54 2011 +0200
+++ b/_pytest/assertion/__init__.py	Wed Jul 06 23:24:04 2011 -0500
@@ -28,7 +28,6 @@
     def __init__(self, config, mode):
         self.mode = mode
         self.trace = config.trace.root.get("assertion")
-        self.pycs = []
 
 def pytest_configure(config):
     mode = config.getvalue("assertmode")
@@ -62,8 +61,6 @@
     config._assertstate.trace("configured with mode set to %r" % (mode,))
 
 def pytest_unconfigure(config):
-    if config._assertstate.mode == "rewrite":
-        rewrite._drain_pycs(config._assertstate)
     hook = config._assertstate.hook
     if hook is not None:
         sys.meta_path.remove(hook)
@@ -77,8 +74,6 @@
         hook.set_session(session)
 
 def pytest_sessionfinish(session):
-    if session.config._assertstate.mode == "rewrite":
-        rewrite._drain_pycs(session.config._assertstate)
     hook = session.config._assertstate.hook
     if hook is not None:
         hook.session = None


--- a/_pytest/assertion/rewrite.py	Wed Jul 06 20:25:54 2011 +0200
+++ b/_pytest/assertion/rewrite.py	Wed Jul 06 23:24:04 2011 -0500
@@ -8,6 +8,7 @@
 import os
 import struct
 import sys
+import types
 
 import py
 from _pytest.assertion import util
@@ -22,14 +23,11 @@
     del ver
 
 class AssertionRewritingHook(object):
-    """Import hook which rewrites asserts.
-
-    Note this hook doesn't load modules itself. It uses find_module to write a
-    fake pyc, so the normal import system will find it.
-    """
+    """Import hook which rewrites asserts."""
 
     def __init__(self):
         self.session = None
+        self.modules = {}
 
     def set_session(self, session):
         self.fnpats = session.config.getini("python_files")
@@ -77,37 +75,53 @@
                     return None
             finally:
                 self.session = sess
-        # This looks like a test file, so rewrite it. This is the most magical
-        # part of the process: load the source, rewrite the asserts, and write a
-        # fake pyc, so that it'll be loaded when the module is imported. This is
-        # complicated by the fact we cache rewritten pycs.
-        pyc = _compute_pyc_location(fn_pypath)
-        state.pycs.append(pyc)
-        cache_fn = fn_pypath.basename[:-3] + "." + PYTEST_TAG + ".pyc"
-        cache = py.path.local(fn_pypath.dirname).join("__pycache__", cache_fn)
-        if _use_cached_pyc(fn_pypath, cache):
+        # The requested module looks like a test file, so rewrite it. This is
+        # the most magical part of the process: load the source, rewrite the
+        # asserts, and load the rewritten source. We also cache the rewritten
+        # module code in a special pyc. We must be aware of the possibility of
+        # concurrent py.test processes rewriting and loading pycs. To avoid
+        # tricky race conditions, we maintain the following invariant: The
+        # cached pyc is always a complete, valid pyc. Operations on it must be
+        # atomic. POSIX's atomic rename comes in handy.
+        cache_dir = os.path.join(fn_pypath.dirname, "__pycache__")
+        py.path.local(cache_dir).ensure(dir=True)
+        cache_name = fn_pypath.basename[:-3] + "." + PYTEST_TAG + ".pyc"
+        pyc = os.path.join(cache_dir, cache_name)
+        co = _read_pyc(fn_pypath, pyc)
+        if co is None:
+            state.trace("rewriting %r" % (fn,))
+            co = _make_rewritten_pyc(state, fn_pypath, pyc)
+            if co is None:
+                # Probably a SyntaxError in the module.
+                return None
+        else:
             state.trace("found cached rewritten pyc for %r" % (fn,))
-            _atomic_copy(cache, pyc)
-        else:
-            state.trace("rewriting %r" % (fn,))
-            _make_rewritten_pyc(state, fn_pypath, pyc)
-            # Try cache it in the __pycache__ directory.
-            _cache_pyc(state, pyc, cache)
-        return None
+        self.modules[name] = co, pyc
+        return self
 
-def _drain_pycs(state):
-    for pyc in state.pycs:
+    def load_module(self, name):
+        co, pyc = self.modules.pop(name)
+        # I wish I could just call imp.load_compiled here, but __file__ has to
+        # be set properly. In Python 3.2+, this all would be handled correctly
+        # by load_compiled.
+        mod = sys.modules[name] = imp.new_module(name)
         try:
-            pyc.remove()
-        except py.error.ENOENT:
-            state.trace("couldn't find pyc: %r" % (pyc,))
-        else:
-            state.trace("removed pyc: %r" % (pyc,))
-    del state.pycs[:]
+            mod.__file__ = co.co_filename
+            # Normally, this attribute is 3.2+.
+            mod.__cached__ = pyc
+            exec co in mod.__dict__
+        except:
+            del sys.modules[name]
+            raise
+        return sys.modules[name]
 
 def _write_pyc(co, source_path, pyc):
+    # Technically, we don't have to have the same pyc format as (C)Python, since
+    # these "pycs" should never be seen by builtin import. However, there's
+    # little reason deviate, and I hope sometime to be able to use
+    # imp.load_compiled to load them. (See the comment in load_module above.)
     mtime = int(source_path.mtime())
-    fp = pyc.open("wb")
+    fp = open(pyc, "wb")
     try:
         fp.write(imp.get_magic())
         fp.write(struct.pack("<l", mtime))
@@ -116,6 +130,11 @@
         fp.close()
 
 def _make_rewritten_pyc(state, fn, pyc):
+    """Try to rewrite *fn* and dump the rewritten code to *pyc*.
+
+    Return the code object of the rewritten module on success. Return None if
+    there are problems parsing or compiling the module.
+    """
     try:
         source = fn.read("rb")
     except EnvironmentError:
@@ -134,44 +153,40 @@
         # assertion rewriting, but I don't know of a fast way to tell.
         state.trace("failed to compile: %r" % (fn,))
         return None
-    _write_pyc(co, fn, pyc)
+    # Dump the code object into a file specific to this process.
+    proc_pyc = pyc + "." + str(os.getpid())
+    _write_pyc(co, fn, proc_pyc)
+    # Atomically replace the pyc.
+    os.rename(proc_pyc, pyc)
+    return co
 
-def _compute_pyc_location(source_path):
-    if hasattr(imp, "cache_from_source"):
-        # Handle PEP 3147 pycs.
-        pyc = py.path.local(imp.cache_from_source(str(source_path)))
-        pyc.ensure()
-    else:
-        pyc = source_path + "c"
-    return pyc
+def _read_pyc(source, pyc):
+    """Possibly read a py.test pyc containing rewritten code.
 
-def _use_cached_pyc(source, cache):
+    Return rewritten code if successful or None if not.
+    """
     try:
-        mtime = int(source.mtime())
-        fp = cache.open("rb")
+        fp = open(pyc, "rb")
+    except IOError:
+        return None
+    try:
         try:
+            mtime = int(source.mtime())
             data = fp.read(8)
-        finally:
-            fp.close()
-    except EnvironmentError:
-        return False
-    # Check for invalid or out of date pyc file.
-    return (len(data) == 8 and
-            data[:4] == imp.get_magic() and
-            struct.unpack("<l", data[4:])[0] == mtime)
-
-def _cache_pyc(state, pyc, cache):
-    try:
-        cache.dirpath().ensure(dir=True)
-        _atomic_copy(pyc, cache)
-    except EnvironmentError:
-        state.trace("failed to cache %r as %r" % (pyc, cache))
-
-def _atomic_copy(orig, to):
-    """An atomic copy (at least on POSIX platforms)"""
-    temp = py.path.local(orig.strpath + str(os.getpid()))
-    orig.copy(temp)
-    temp.rename(to)
+        except EnvironmentError:
+            return None
+        # Check for invalid or out of date pyc file.
+        if (len(data) != 8 or
+            data[:4] != imp.get_magic() or
+            struct.unpack("<l", data[4:])[0] != mtime):
+            return None
+        co = marshal.load(fp)
+        if not isinstance(co, types.CodeType):
+            # That's interesting....
+            return None
+        return co
+    finally:
+        fp.close()
 
 
 def rewrite_asserts(mod):


--- a/testing/test_assertion.py	Wed Jul 06 20:25:54 2011 +0200
+++ b/testing/test_assertion.py	Wed Jul 06 23:24:04 2011 -0500
@@ -246,11 +246,3 @@
     result.stderr.fnmatch_lines([
         "*WARNING*assert statements are not executed*",
     ])
-
-def test_load_fake_pyc(testdir):
-    rewrite = pytest.importorskip("_pytest.assertion.rewrite")
-    path = testdir.makepyfile(a_random_module="x = 'hello'")
-    co = compile("x = 'bye'", str(path), "exec")
-    rewrite._write_pyc(co, path, rewrite._compute_pyc_location(path))
-    mod = path.pyimport()
-    assert mod.x == "bye"


http://bitbucket.org/hpk42/pytest/changeset/90e2c16de745/
changeset:   90e2c16de745
user:        gutworth
date:        2011-07-07 06:24:54
summary:     merge heads
affected #:  8 files (2.5 KB)

--- a/doc/_templates/localtoc.html	Wed Jul 06 23:24:04 2011 -0500
+++ b/doc/_templates/localtoc.html	Wed Jul 06 23:24:54 2011 -0500
@@ -16,9 +16,9 @@
 <table><tr><td>
-        <a href="{{ pathto('index') }}">index</a>
+        <a href="{{ pathto('index') }}">home</a></td><td>
-        <a href="{{ pathto('features') }}">features</a>
+        <a href="{{ pathto('contents') }}">contents</a></td></tr><tr><td><a href="{{ pathto('getting-started') }}">install</a></td><td>


--- a/doc/conf.py	Wed Jul 06 23:24:04 2011 -0500
+++ b/doc/conf.py	Wed Jul 06 23:24:54 2011 -0500
@@ -38,7 +38,7 @@
 #source_encoding = 'utf-8-sig'
 
 # The master toctree document.
-master_doc = 'index'
+master_doc = 'contents'
 
 # General information about the project.
 project = u'pytest'
@@ -135,7 +135,7 @@
 
 # Custom sidebar templates, maps document names to template names.
 #html_sidebars = {}
-#html_sidebars = {'*': 'sidebar.html'}
+#html_sidebars = {'index': 'indexsidebar.html'}
 
 # Additional templates that should be rendered to pages, maps page names to
 # template names.


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/contents.txt	Wed Jul 06 23:24:54 2011 -0500
@@ -0,0 +1,24 @@
+
+.. _toc:
+
+Table of Contents
+========================
+
+.. toctree::
+   :maxdepth: 2
+
+   overview
+   example/index
+   apiref
+   plugins
+   talks
+   develop
+   announce/index
+
+.. toctree::
+   :hidden:
+
+   changelog.txt
+   naming20.txt
+   example/attic
+


--- a/doc/features.txt	Wed Jul 06 23:24:04 2011 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-
-
-Feature Overview
-=============================================
-
-
-- **a mature full-featured testing tool**
-
- - runs on Posix/Windows, Python 2.4-3.2, PyPy and Jython
- - continuously `tested on many Python interpreters <http://hudson.testrun.org/view/pytest/job/pytest/>`_
- - used in :ref:`many projects and organisations <projects>`, in test
-   suites ranging from 10 to 10s of thousands of tests
- - has :ref:`comprehensive documentation <toc>`
- - comes with :ref:`tested examples <examples>`
- - supports :ref:`good integration practises <goodpractises>`
-
-- **provides no-boilerplate testing**
-
- - makes it :ref:`easy to get started <getstarted>`,
- - refined :ref:`usage options <usage>`
- - :ref:`assert with the assert statement`
- - helpful :ref:`traceback and failing assertion reporting <tbreportdemo>`
- - allows :ref:`print debugging <printdebugging>` and :ref:`the
-   capturing of standard output during test execution <captures>`
- - supports :pep:`8` compliant coding styles in tests
-
-- **supports functional testing and complex test setups**
-
- - advanced :ref:`skip and xfail`
- - generic :ref:`marking and test selection <mark>`
- - can :ref:`distribute tests to multiple CPUs <xdistcpu>` through :ref:`xdist plugin <xdist>`
- - can :ref:`continuously re-run failing tests <looponfailing>`
- - many :ref:`builtin helpers <pytest helpers>`
- - flexible :ref:`Python test discovery`
- - unique :ref:`dependency injection through funcargs <funcargs>`
- - :ref:`parametrized test functions <parametrized test functions>`
-
-- **integrates many common testing methods**
-
- - can integrate ``nose``, ``unittest.py`` and ``doctest.py`` style
-   tests, including running testcases made for Django and trial
- - supports extended :ref:`xUnit style setup <xunitsetup>`
- - supports domain-specific :ref:`non-python tests`
- - supports the generation of testing coverage reports
- - `Javascript unit- and functional testing`_
-
-- **extensive plugin and customization system**
-
- - all collection, reporting, running aspects are delegated to hook functions
- - customizations can be per-directory, per-project or per PyPI released plugins
- - it is easy to add command line options or do other kind of add-ons and customizations.
-
-.. _`Javascript unit- and functional testing`: http://pypi.python.org/pypi/oejskit
-
-.. _`easy`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html
-
-


--- a/doc/getting-started.txt	Wed Jul 06 23:24:04 2011 -0500
+++ b/doc/getting-started.txt	Wed Jul 06 23:24:54 2011 -0500
@@ -66,7 +66,7 @@
     report intermediate values of the assert expression freeing
     you from the need to learn the many names of `JUnit legacy methods`_.
 
-.. _`the JUnit legacy methods`: http://docs.python.org/library/unittest.html#test-cases
+.. _`JUnit legacy methods`: http://docs.python.org/library/unittest.html#test-cases
 
 .. _`assert statement`: http://docs.python.org/reference/simple_stmts.html#the-assert-statement
 


--- a/doc/index.txt	Wed Jul 06 23:24:04 2011 -0500
+++ b/doc/index.txt	Wed Jul 06 23:24:54 2011 -0500
@@ -1,24 +1,56 @@
 
-.. _toc:
 
-Table of Contents
-========================
+Welcome to pytest / features
+=============================================
 
-.. toctree::
-   :maxdepth: 2
+- **a mature full-featured testing tool**
 
-   overview
-   example/index
-   apiref
-   plugins
-   talks
-   develop
-   announce/index
+ - runs on Posix/Windows, Python 2.4-3.2, PyPy and Jython
+ - continuously `tested on many Python interpreters <http://hudson.testrun.org/view/pytest/job/pytest/>`_
+ - used in :ref:`many projects and organisations <projects>`, in test
+   suites ranging from 10 to 10s of thousands of tests
+ - has :ref:`comprehensive documentation <toc>`
+ - comes with :ref:`tested examples <examples>`
+ - supports :ref:`good integration practises <goodpractises>`
 
-.. toctree::
-   :hidden:
+- **provides no-boilerplate testing**
 
-   changelog.txt
-   naming20.txt
-   example/attic
+ - makes it :ref:`easy to get started <getstarted>`,
+ - refined :ref:`usage options <usage>`
+ - :ref:`assert with the assert statement`
+ - helpful :ref:`traceback and failing assertion reporting <tbreportdemo>`
+ - allows :ref:`print debugging <printdebugging>` and :ref:`the
+   capturing of standard output during test execution <captures>`
+ - supports :pep:`8` compliant coding styles in tests
 
+- **supports functional testing and complex test setups**
+
+ - advanced :ref:`skip and xfail`
+ - generic :ref:`marking and test selection <mark>`
+ - can :ref:`distribute tests to multiple CPUs <xdistcpu>` through :ref:`xdist plugin <xdist>`
+ - can :ref:`continuously re-run failing tests <looponfailing>`
+ - many :ref:`builtin helpers <pytest helpers>`
+ - flexible :ref:`Python test discovery`
+ - unique :ref:`dependency injection through funcargs <funcargs>`
+ - :ref:`parametrized test functions <parametrized test functions>`
+
+- **integrates many common testing methods**
+
+ - can integrate ``nose``, ``unittest.py`` and ``doctest.py`` style
+   tests, including running testcases made for Django and trial
+ - supports extended :ref:`xUnit style setup <xunitsetup>`
+ - supports domain-specific :ref:`non-python tests`
+ - supports the generation of testing coverage reports
+ - `Javascript unit- and functional testing`_
+
+- **extensive plugin and customization system**
+
+ - all collection, reporting, running aspects are delegated to hook functions
+ - customizations can be per-directory, per-project or per PyPI released plugins
+ - it is easy to add command line options or do other kind of add-ons and customizations.
+
+.. _`Javascript unit- and functional testing`: http://pypi.python.org/pypi/oejskit
+
+.. _`easy`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html
+
+


--- a/doc/overview.txt	Wed Jul 06 23:24:04 2011 -0500
+++ b/doc/overview.txt	Wed Jul 06 23:24:54 2011 -0500
@@ -5,7 +5,7 @@
 .. toctree::
    :maxdepth: 2
 
-   features.txt
+   index.txt
    getting-started.txt
    usage.txt
    goodpractises.txt


--- a/setup.py	Wed Jul 06 23:24:04 2011 -0500
+++ b/setup.py	Wed Jul 06 23:24:54 2011 -0500
@@ -29,7 +29,9 @@
         author='Holger Krekel, Benjamin Peterson, Ronny Pfannschmidt, Floris Bruynooghe and others',
         author_email='holger at merlinux.eu',
         entry_points= make_entry_points(),
-        install_requires=['py>=1.4.4.dev2'],
+        # the following should be enabled for release
+        #install_requires=['py>=1.4.4'],
+        install_requires=['py>=1.4.3'],
         classifiers=['Development Status :: 5 - Production/Stable',
                      'Intended Audience :: Developers',
                      'License :: OSI Approved :: MIT License',
@@ -67,4 +69,4 @@
     return {'console_scripts': l}
 
 if __name__ == '__main__':
-    main()
\ No newline at end of file
+    main()

Repository URL: https://bitbucket.org/hpk42/pytest/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.



More information about the pytest-commit mailing list