[Zope3-checkins] SVN: zope.testing/trunk/ - Added context-manager support to ``zope.testing.setupstack``

Jim Fulton jim at zope.com
Sat Jan 28 21:12:59 UTC 2012


Log message for revision 124225:
  - Added context-manager support to ``zope.testing.setupstack``
  
  - Added the ``wait_until`` module, which makes it easier to deal with
    non-deterministic timing issues.
  
  - Renamed ``zope.testing.renormalizing.RENormalizing`` to
    ``zope.testing.renormalizing.OutputChecker``. The old name is an
    alias.
  
  - Updated tests to run with Python 3.
  
  - More clearly labeled which features were supported by Python 3.
  
  - Reorganized documentation.
  

Changed:
  U   zope.testing/trunk/CHANGES.txt
  U   zope.testing/trunk/README.txt
  U   zope.testing/trunk/setup.py
  U   zope.testing/trunk/src/zope/testing/formparser.txt
  U   zope.testing/trunk/src/zope/testing/loggingsupport.py
  A   zope.testing/trunk/src/zope/testing/loggingsupport.txt
  U   zope.testing/trunk/src/zope/testing/renormalizing/__init__.py
  A   zope.testing/trunk/src/zope/testing/renormalizing.txt
  U   zope.testing/trunk/src/zope/testing/setupstack.py
  U   zope.testing/trunk/src/zope/testing/setupstack.txt
  U   zope.testing/trunk/src/zope/testing/tests.py
  A   zope.testing/trunk/src/zope/testing/wait_until.py
  A   zope.testing/trunk/src/zope/testing/wait_until.txt

-=-
Modified: zope.testing/trunk/CHANGES.txt
===================================================================
--- zope.testing/trunk/CHANGES.txt	2012-01-27 21:57:17 UTC (rev 124224)
+++ zope.testing/trunk/CHANGES.txt	2012-01-28 21:12:57 UTC (rev 124225)
@@ -1,11 +1,22 @@
 zope.testing Changelog
 **********************
 
-4.0.1 (unreleased)
+4.1.0 (unreleased)
 ==================
 
-- None yet.
+- Added context-manager support to ``zope.testing.setupstack``
 
+- Added the ``wait_until`` module, which makes it easier to deal with
+  non-deterministic timing issues.
+
+- Renamed ``zope.testing.renormalizing.RENormalizing`` to
+  ``zope.testing.renormalizing.OutputChecker``. The old name is an
+  alias.
+
+- Updated tests to run with Python 3.
+
+- More clearly labeled which features were supported by Python 3.
+
 4.0.0 (2011-11-09)
 ==================
 

Modified: zope.testing/trunk/README.txt
===================================================================
--- zope.testing/trunk/README.txt	2012-01-27 21:57:17 UTC (rev 124224)
+++ zope.testing/trunk/README.txt	2012-01-28 21:12:57 UTC (rev 124225)
@@ -4,65 +4,55 @@
 
 .. contents::
 
-This package provides a number of testing frameworks.  It includes a
-flexible test runner, and supports both doctest and unittest.
+This package provides a number of testing frameworks.
 
-cleanup.py
+cleanup
   Provides a mixin class for cleaning up after tests that
   make global changes.
 
-doctest.py
-  Enhanced version of python's standard doctest.py.
-  Better test count (one per block instead of one per docstring).
-  See doctest.txt.
-
-  (We need to merge this with the standard doctest module.)
-
-doctestunit.py
-  Provides a pprint function that always sorts dictionary entries
-  (pprint.pprint from the standard library doesn't sort very short ones,
-  sometimes causing test failures when the internal order changes).
-
-formparser.py
+formparser
   An HTML parser that extracts form information.
 
+  **Python 2 only**
+
   This is intended to support functional tests that need to extract
   information from HTML forms returned by the publisher.
 
   See formparser.txt.
 
-loggingsupport.py
+loggingsupport
   Support for testing logging code
 
   If you want to test that your code generates proper log output, you
   can create and install a handler that collects output.
 
-loghandler.py
+loghandler
   Logging handler for tests that check logging output.
 
-module.py
+module
   Lets a doctest pretend to be a Python module.
 
   See module.txt.
 
-renormalizing.py
+renormalizing
   Regular expression pattern normalizing output checker.
   Useful for doctests.
 
-server.py
+server
   Provides a simple HTTP server compatible with the zope.app.testing
   functional testing API.  Lets you interactively play with the system
   under test.  Helpful in debugging functional doctest failures.
 
-setupstack.py
+  **Python 2 only**
+
+setupstack
   A simple framework for automating doctest set-up and tear-down.
   See setupstack.txt.
 
-testrunner
-  The test runner package.  This is typically wrapped by a test.py script that
-  sets up options to run a particular set of tests.
+wait_until
+  A small utility for dealing with timing non-determinism
+  See wait_until.txt.
 
-
 Getting started
 ***************
 

Modified: zope.testing/trunk/setup.py
===================================================================
--- zope.testing/trunk/setup.py	2012-01-27 21:57:17 UTC (rev 124224)
+++ zope.testing/trunk/setup.py	2012-01-28 21:12:57 UTC (rev 124225)
@@ -38,18 +38,20 @@
 chapters = '\n'.join([
     open(os.path.join('src', 'zope', 'testing', name)).read()
     for name in (
-        'formparser.txt',
-        'setupstack.txt',
+    'formparser.txt',
+    'loggingsupport.txt',
+    'renormalizing.txt',
+    'setupstack.txt',
+    'wait_until.txt',
     )])
 
 long_description=(
     open('README.txt').read()
     + '\n' +
-    open('CHANGES.txt').read()
-    + '\n' +
     'Detailed Documentation\n'
     '**********************\n'
     + '\n' + chapters
+    + '\n' + open('CHANGES.txt').read()
     )
 
 setup(
@@ -76,10 +78,10 @@
         "Topic :: Software Development :: Libraries :: Python Modules",
         "Topic :: Software Development :: Testing",
         ],
-    
-    packages=["zope", 
-              "zope.testing", 
-              "zope.testing.doctest", 
+
+    packages=["zope",
+              "zope.testing",
+              "zope.testing.doctest",
               "zope.testing.renormalizing"],
     package_dir = {'': 'src'},
     namespace_packages=['zope',],

Modified: zope.testing/trunk/src/zope/testing/formparser.txt
===================================================================
--- zope.testing/trunk/src/zope/testing/formparser.txt	2012-01-27 21:57:17 UTC (rev 124224)
+++ zope.testing/trunk/src/zope/testing/formparser.txt	2012-01-28 21:12:57 UTC (rev 124225)
@@ -5,6 +5,9 @@
 be extracted in order to re-submit it as part of a subsequent request.
 The `zope.testing.formparser` module can be used for this purpose.
 
+NOTE
+   formparser doesn't support Python 3.
+
 The scanner is implemented using the `FormParser` class.  The
 constructor arguments are the page data containing the form and
 (optionally) the URL from which the page was retrieved:

Modified: zope.testing/trunk/src/zope/testing/loggingsupport.py
===================================================================
--- zope.testing/trunk/src/zope/testing/loggingsupport.py	2012-01-27 21:57:17 UTC (rev 124224)
+++ zope.testing/trunk/src/zope/testing/loggingsupport.py	2012-01-28 21:12:57 UTC (rev 124225)
@@ -11,64 +11,6 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""Support for testing logging code
-
-If you want to test that your code generates proper log output, you
-can create and install a handler that collects output:
-
-  >>> handler = InstalledHandler('foo.bar')
-
-The handler is installed into loggers for all of the names passed. In
-addition, the logger level is set to 1, which means, log
-everything. If you want to log less than everything, you can provide a
-level keyword argument.  The level setting effects only the named
-loggers.
-
-  >>> handler_with_levels = InstalledHandler('baz', level=logging.WARNING)
-
-Then, any log output is collected in the handler:
-
-  >>> logging.getLogger('foo.bar').exception('eek')
-  >>> logging.getLogger('foo.bar').info('blah blah')
-
-  >>> for record in handler.records:
-  ...     print record.name, record.levelname
-  ...     print ' ', record.getMessage()
-  foo.bar ERROR
-    eek
-  foo.bar INFO
-    blah blah
-
-A similar effect can be gotten by just printing the handler:
-
-  >>> print handler
-  foo.bar ERROR
-    eek
-  foo.bar INFO
-    blah blah
-
-After checking the log output, you need to uninstall the handler:
-
-  >>> handler.uninstall()
-  >>> handler_with_levels.uninstall()
-
-At which point, the handler won't get any more log output.
-Let's clear the handler:
-
-  >>> handler.clear()
-  >>> handler.records
-  []
-
-And then log something:
-
-  >>> logging.getLogger('foo.bar').info('blah')
-
-and, sure enough, we still have no output:
-
-  >>> handler.records
-  []
-"""
-
 import logging
 
 class Handler(logging.Handler):

Added: zope.testing/trunk/src/zope/testing/loggingsupport.txt
===================================================================
--- zope.testing/trunk/src/zope/testing/loggingsupport.txt	                        (rev 0)
+++ zope.testing/trunk/src/zope/testing/loggingsupport.txt	2012-01-28 21:12:57 UTC (rev 124225)
@@ -0,0 +1,59 @@
+Support for testing logging code
+================================
+
+If you want to test that your code generates proper log output, you
+can create and install a handler that collects output:
+
+  >>> from zope.testing.loggingsupport import InstalledHandler
+  >>> handler = InstalledHandler('foo.bar')
+
+The handler is installed into loggers for all of the names passed. In
+addition, the logger level is set to 1, which means, log
+everything. If you want to log less than everything, you can provide a
+level keyword argument.  The level setting effects only the named
+loggers.
+
+  >>> import logging
+  >>> handler_with_levels = InstalledHandler('baz', level=logging.WARNING)
+
+Then, any log output is collected in the handler:
+
+  >>> logging.getLogger('foo.bar').exception('eek')
+  >>> logging.getLogger('foo.bar').info('blah blah')
+
+  >>> for record in handler.records:
+  ...     print_(record.name, record.levelname)
+  ...     print_(' ', record.getMessage())
+  foo.bar ERROR
+    eek
+  foo.bar INFO
+    blah blah
+
+A similar effect can be gotten by just printing the handler:
+
+  >>> print_(handler)
+  foo.bar ERROR
+    eek
+  foo.bar INFO
+    blah blah
+
+After checking the log output, you need to uninstall the handler:
+
+  >>> handler.uninstall()
+  >>> handler_with_levels.uninstall()
+
+At which point, the handler won't get any more log output.
+Let's clear the handler:
+
+  >>> handler.clear()
+  >>> handler.records
+  []
+
+And then log something:
+
+  >>> logging.getLogger('foo.bar').info('blah')
+
+and, sure enough, we still have no output:
+
+  >>> handler.records
+  []


Property changes on: zope.testing/trunk/src/zope/testing/loggingsupport.txt
___________________________________________________________________
Added: svn:eol-style
   + native

Modified: zope.testing/trunk/src/zope/testing/renormalizing/__init__.py
===================================================================
--- zope.testing/trunk/src/zope/testing/renormalizing/__init__.py	2012-01-27 21:57:17 UTC (rev 124224)
+++ zope.testing/trunk/src/zope/testing/renormalizing/__init__.py	2012-01-28 21:12:57 UTC (rev 124225)
@@ -24,244 +24,9 @@
 # is not available on Python 2.4 which we still support.
 #
 ##############################################################################
-r"""Regular expression pattern normalizing output checker
-
-The pattern-normalizing output checker extends the default output checker with
-an option to normalize expected and actual output.
-
-You specify a sequence of patterns and replacements.  The replacements are
-applied to the expected and actual outputs before calling the default outputs
-checker.  Let's look at an example.  In this example, we have some times and
-addresses:
-
-    >>> want = '''\
-    ... <object object at 0xb7f14438>
-    ... completed in 1.234 seconds.
-    ... <BLANKLINE>
-    ... <object object at 0xb7f14440>
-    ... completed in 123.234 seconds.
-    ... <BLANKLINE>
-    ... <object object at 0xb7f14448>
-    ... completed in .234 seconds.
-    ... <BLANKLINE>
-    ... <object object at 0xb7f14450>
-    ... completed in 1.234 seconds.
-    ... <BLANKLINE>
-    ... '''
-
-    >>> got = '''\
-    ... <object object at 0xb7f14458>
-    ... completed in 1.235 seconds.
-    ...
-    ... <object object at 0xb7f14460>
-    ... completed in 123.233 seconds.
-    ...
-    ... <object object at 0xb7f14468>
-    ... completed in .231 seconds.
-    ...
-    ... <object object at 0xb7f14470>
-    ... completed in 1.23 seconds.
-    ...
-    ... '''
-
-We may wish to consider these two strings to match, even though they differ in
-actual addresses and times.  The default output checker will consider them
-different:
-
-    >>> doctest.OutputChecker().check_output(want, got, 0)
-    False
-
-We'll use the RENormalizing to normalize both the wanted and gotten strings to
-ignore differences in times and addresses:
-
-    >>> import re
-    >>> checker = RENormalizing([
-    ...    (re.compile('[0-9]*[.][0-9]* seconds'), '<SOME NUMBER OF> seconds'),
-    ...    (re.compile('at 0x[0-9a-f]+'), 'at <SOME ADDRESS>'),
-    ...    ])
-
-    >>> checker.check_output(want, got, 0)
-    True
-
-Usual OutputChecker options work as expected:
-
-    >>> want_ellided = '''\
-    ... <object object at 0xb7f14438>
-    ... completed in 1.234 seconds.
-    ... ...
-    ... <object object at 0xb7f14450>
-    ... completed in 1.234 seconds.
-    ... <BLANKLINE>
-    ... '''
-
-    >>> checker.check_output(want_ellided, got, 0)
-    False
-
-    >>> checker.check_output(want_ellided, got, doctest.ELLIPSIS)
-    True
-
-When we get differencs, we output them with normalized text:
-
-    >>> source = '''\
-    ... >>> do_something()
-    ... <object object at 0xb7f14438>
-    ... completed in 1.234 seconds.
-    ... ...
-    ... <object object at 0xb7f14450>
-    ... completed in 1.234 seconds.
-    ... <BLANKLINE>
-    ... '''
-
-    >>> example = doctest.Example(source, want_ellided)
-
-    >>> print checker.output_difference(example, got, 0)
-    Expected:
-        <object object at <SOME ADDRESS>>
-        completed in <SOME NUMBER OF> seconds.
-        ...
-        <object object at <SOME ADDRESS>>
-        completed in <SOME NUMBER OF> seconds.
-        <BLANKLINE>
-    Got:
-        <object object at <SOME ADDRESS>>
-        completed in <SOME NUMBER OF> seconds.
-        <BLANKLINE>
-        <object object at <SOME ADDRESS>>
-        completed in <SOME NUMBER OF> seconds.
-        <BLANKLINE>
-        <object object at <SOME ADDRESS>>
-        completed in <SOME NUMBER OF> seconds.
-        <BLANKLINE>
-        <object object at <SOME ADDRESS>>
-        completed in <SOME NUMBER OF> seconds.
-        <BLANKLINE>
-    <BLANKLINE>
-
-    >>> print checker.output_difference(example, got,
-    ...                                 doctest.REPORT_NDIFF)
-    Differences (ndiff with -expected +actual):
-        - <object object at <SOME ADDRESS>>
-        - completed in <SOME NUMBER OF> seconds.
-        - ...
-          <object object at <SOME ADDRESS>>
-          completed in <SOME NUMBER OF> seconds.
-          <BLANKLINE>
-        + <object object at <SOME ADDRESS>>
-        + completed in <SOME NUMBER OF> seconds.
-        + <BLANKLINE>
-        + <object object at <SOME ADDRESS>>
-        + completed in <SOME NUMBER OF> seconds.
-        + <BLANKLINE>
-        + <object object at <SOME ADDRESS>>
-        + completed in <SOME NUMBER OF> seconds.
-        + <BLANKLINE>
-    <BLANKLINE>
-
-    If the wanted text is empty, however, we don't transform the actual output.
-    This is usful when writing tests.  We leave the expected output empty, run
-    the test, and use the actual output as expected, after reviewing it.
-
-    >>> source = '''\
-    ... >>> do_something()
-    ... '''
-
-    >>> example = doctest.Example(source, '\n')
-    >>> print checker.output_difference(example, got, 0)
-    Expected:
-    <BLANKLINE>
-    Got:
-        <object object at 0xb7f14458>
-        completed in 1.235 seconds.
-        <BLANKLINE>
-        <object object at 0xb7f14460>
-        completed in 123.233 seconds.
-        <BLANKLINE>
-        <object object at 0xb7f14468>
-        completed in .231 seconds.
-        <BLANKLINE>
-        <object object at 0xb7f14470>
-        completed in 1.23 seconds.
-        <BLANKLINE>
-    <BLANKLINE>
-
-If regular expressions aren't expressive enough, you can use arbitrary Python
-callables to transform the text.  For example, suppose you want to ignore
-case during comparison:
-
-    >>> checker = RENormalizing([
-    ...    lambda s: s.lower(),
-    ...    lambda s: s.replace('<blankline>', '<BLANKLINE>'),
-    ...    ])
-
-    >>> want = '''\
-    ... Usage: thundermonkey [options] [url]
-    ... <BLANKLINE>
-    ... Options:
-    ...     -h    display this help message
-    ... '''
-
-    >>> got = '''\
-    ... usage: thundermonkey [options] [URL]
-    ...
-    ... options:
-    ...     -h    Display this help message
-    ... '''
-
-    >>> checker.check_output(want, got, 0)
-    True
-
-Suppose we forgot that <BLANKLINE> must be in upper case:
-
-    >>> checker = RENormalizing([
-    ...    lambda s: s.lower(),
-    ...    ])
-
-    >>> checker.check_output(want, got, 0)
-    False
-
-The difference would show us that:
-
-    >>> source = '''\
-    ... >>> print_help_message()
-    ... ''' + want
-    >>> example = doctest.Example(source, want)
-    >>> print checker.output_difference(example, got,
-    ...                                 doctest.REPORT_NDIFF)
-    Differences (ndiff with -expected +actual):
-          usage: thundermonkey [options] [url]
-        - <blankline>
-        + <BLANKLINE>
-          options:
-              -h    display this help message
-    <BLANKLINE>
-
-
-It is possible to combine RENormalizing checkers for easy reuse:
-
-    >>> address_and_time_checker = RENormalizing([
-    ...    (re.compile('[0-9]*[.][0-9]* seconds'), '<SOME NUMBER OF> seconds'),
-    ...    (re.compile('at 0x[0-9a-f]+'), 'at <SOME ADDRESS>'),
-    ...    ])
-    >>> lowercase_checker = RENormalizing([
-    ...    lambda s: s.lower(),
-    ...    ])
-    >>> combined_checker = address_and_time_checker + lowercase_checker
-    >>> len(combined_checker.transformers)
-    3
-
-Combining a checker with something else does not work:
-
-    >>> lowercase_checker + 5 #doctest: +ELLIPSIS
-    Traceback (most recent call last):
-        ...
-    TypeError: unsupported operand type(s) for +: ...
-
-"""
-
 import doctest
 
-
-class RENormalizing(doctest.OutputChecker):
+class OutputChecker(doctest.OutputChecker):
     """Pattern-normalizing outout checker
     """
 
@@ -315,3 +80,6 @@
         example.want = original
 
         return result
+
+RENormalizing = OutputChecker
+

Added: zope.testing/trunk/src/zope/testing/renormalizing.txt
===================================================================
--- zope.testing/trunk/src/zope/testing/renormalizing.txt	                        (rev 0)
+++ zope.testing/trunk/src/zope/testing/renormalizing.txt	2012-01-28 21:12:57 UTC (rev 124225)
@@ -0,0 +1,236 @@
+Regular expression pattern normalizing output checker
+=====================================================
+
+The pattern-normalizing output checker extends the default output checker with
+an option to normalize expected and actual output.
+
+You specify a sequence of patterns and replacements.  The replacements are
+applied to the expected and actual outputs before calling the default outputs
+checker.  Let's look at an example.  In this example, we have some times and
+addresses:
+
+    >>> want = '''\
+    ... <object object at 0xb7f14438>
+    ... completed in 1.234 seconds.
+    ... <BLANKLINE>
+    ... <object object at 0xb7f14440>
+    ... completed in 123.234 seconds.
+    ... <BLANKLINE>
+    ... <object object at 0xb7f14448>
+    ... completed in .234 seconds.
+    ... <BLANKLINE>
+    ... <object object at 0xb7f14450>
+    ... completed in 1.234 seconds.
+    ... <BLANKLINE>
+    ... '''
+
+    >>> got = '''\
+    ... <object object at 0xb7f14458>
+    ... completed in 1.235 seconds.
+    ...
+    ... <object object at 0xb7f14460>
+    ... completed in 123.233 seconds.
+    ...
+    ... <object object at 0xb7f14468>
+    ... completed in .231 seconds.
+    ...
+    ... <object object at 0xb7f14470>
+    ... completed in 1.23 seconds.
+    ...
+    ... '''
+
+We may wish to consider these two strings to match, even though they differ in
+actual addresses and times.  The default output checker will consider them
+different:
+
+    >>> import doctest
+    >>> doctest.OutputChecker().check_output(want, got, 0)
+    False
+
+We'll use the zope.testing.renormalizing.OutputChecker to normalize both the
+wanted and gotten strings to ignore differences in times and
+addresses:
+
+    >>> import re
+    >>> from zope.testing.renormalizing import OutputChecker
+    >>> checker = OutputChecker([
+    ...    (re.compile('[0-9]*[.][0-9]* seconds'), '<SOME NUMBER OF> seconds'),
+    ...    (re.compile('at 0x[0-9a-f]+'), 'at <SOME ADDRESS>'),
+    ...    ])
+
+    >>> checker.check_output(want, got, 0)
+    True
+
+Usual OutputChecker options work as expected:
+
+    >>> want_ellided = '''\
+    ... <object object at 0xb7f14438>
+    ... completed in 1.234 seconds.
+    ... ...
+    ... <object object at 0xb7f14450>
+    ... completed in 1.234 seconds.
+    ... <BLANKLINE>
+    ... '''
+
+    >>> checker.check_output(want_ellided, got, 0)
+    False
+
+    >>> checker.check_output(want_ellided, got, doctest.ELLIPSIS)
+    True
+
+When we get differencs, we output them with normalized text:
+
+    >>> source = '''\
+    ... >>> do_something()
+    ... <object object at 0xb7f14438>
+    ... completed in 1.234 seconds.
+    ... ...
+    ... <object object at 0xb7f14450>
+    ... completed in 1.234 seconds.
+    ... <BLANKLINE>
+    ... '''
+
+    >>> example = doctest.Example(source, want_ellided)
+
+    >>> print_(checker.output_difference(example, got, 0))
+    Expected:
+        <object object at <SOME ADDRESS>>
+        completed in <SOME NUMBER OF> seconds.
+        ...
+        <object object at <SOME ADDRESS>>
+        completed in <SOME NUMBER OF> seconds.
+        <BLANKLINE>
+    Got:
+        <object object at <SOME ADDRESS>>
+        completed in <SOME NUMBER OF> seconds.
+        <BLANKLINE>
+        <object object at <SOME ADDRESS>>
+        completed in <SOME NUMBER OF> seconds.
+        <BLANKLINE>
+        <object object at <SOME ADDRESS>>
+        completed in <SOME NUMBER OF> seconds.
+        <BLANKLINE>
+        <object object at <SOME ADDRESS>>
+        completed in <SOME NUMBER OF> seconds.
+        <BLANKLINE>
+    <BLANKLINE>
+
+    >>> print_(checker.output_difference(example, got,
+    ...                                 doctest.REPORT_NDIFF))
+    Differences (ndiff with -expected +actual):
+        - <object object at <SOME ADDRESS>>
+        - completed in <SOME NUMBER OF> seconds.
+        - ...
+          <object object at <SOME ADDRESS>>
+          completed in <SOME NUMBER OF> seconds.
+          <BLANKLINE>
+        + <object object at <SOME ADDRESS>>
+        + completed in <SOME NUMBER OF> seconds.
+        + <BLANKLINE>
+        + <object object at <SOME ADDRESS>>
+        + completed in <SOME NUMBER OF> seconds.
+        + <BLANKLINE>
+        + <object object at <SOME ADDRESS>>
+        + completed in <SOME NUMBER OF> seconds.
+        + <BLANKLINE>
+    <BLANKLINE>
+
+    If the wanted text is empty, however, we don't transform the actual output.
+    This is usful when writing tests.  We leave the expected output empty, run
+    the test, and use the actual output as expected, after reviewing it.
+
+    >>> source = '''\
+    ... >>> do_something()
+    ... '''
+
+    >>> example = doctest.Example(source, '\n')
+    >>> print_(checker.output_difference(example, got, 0))
+    Expected:
+    <BLANKLINE>
+    Got:
+        <object object at 0xb7f14458>
+        completed in 1.235 seconds.
+        <BLANKLINE>
+        <object object at 0xb7f14460>
+        completed in 123.233 seconds.
+        <BLANKLINE>
+        <object object at 0xb7f14468>
+        completed in .231 seconds.
+        <BLANKLINE>
+        <object object at 0xb7f14470>
+        completed in 1.23 seconds.
+        <BLANKLINE>
+    <BLANKLINE>
+
+If regular expressions aren't expressive enough, you can use arbitrary Python
+callables to transform the text.  For example, suppose you want to ignore
+case during comparison:
+
+    >>> checker = OutputChecker([
+    ...    lambda s: s.lower(),
+    ...    lambda s: s.replace('<blankline>', '<BLANKLINE>'),
+    ...    ])
+
+    >>> want = '''\
+    ... Usage: thundermonkey [options] [url]
+    ... <BLANKLINE>
+    ... Options:
+    ...     -h    display this help message
+    ... '''
+
+    >>> got = '''\
+    ... usage: thundermonkey [options] [URL]
+    ...
+    ... options:
+    ...     -h    Display this help message
+    ... '''
+
+    >>> checker.check_output(want, got, 0)
+    True
+
+Suppose we forgot that <BLANKLINE> must be in upper case:
+
+    >>> checker = OutputChecker([
+    ...    lambda s: s.lower(),
+    ...    ])
+
+    >>> checker.check_output(want, got, 0)
+    False
+
+The difference would show us that:
+
+    >>> source = '''\
+    ... >>> print_help_message()
+    ... ''' + want
+    >>> example = doctest.Example(source, want)
+    >>> print_(checker.output_difference(example, got,
+    ...                                 doctest.REPORT_NDIFF))
+    Differences (ndiff with -expected +actual):
+          usage: thundermonkey [options] [url]
+        - <blankline>
+        + <BLANKLINE>
+          options:
+              -h    display this help message
+    <BLANKLINE>
+
+
+It is possible to combine OutputChecker checkers for easy reuse:
+
+    >>> address_and_time_checker = OutputChecker([
+    ...    (re.compile('[0-9]*[.][0-9]* seconds'), '<SOME NUMBER OF> seconds'),
+    ...    (re.compile('at 0x[0-9a-f]+'), 'at <SOME ADDRESS>'),
+    ...    ])
+    >>> lowercase_checker = OutputChecker([
+    ...    lambda s: s.lower(),
+    ...    ])
+    >>> combined_checker = address_and_time_checker + lowercase_checker
+    >>> len(combined_checker.transformers)
+    3
+
+Combining a checker with something else does not work:
+
+    >>> lowercase_checker + 5 #doctest: +ELLIPSIS
+    Traceback (most recent call last):
+        ...
+    TypeError: unsupported operand type(s) for +: ...
+


Property changes on: zope.testing/trunk/src/zope/testing/renormalizing.txt
___________________________________________________________________
Added: svn:eol-style
   + native

Modified: zope.testing/trunk/src/zope/testing/setupstack.py
===================================================================
--- zope.testing/trunk/src/zope/testing/setupstack.py	2012-01-27 21:57:17 UTC (rev 124224)
+++ zope.testing/trunk/src/zope/testing/setupstack.py	2012-01-28 21:12:57 UTC (rev 124225)
@@ -20,14 +20,22 @@
 
 key = '__' + __name__
 
+def globs(test):
+    try:
+        return test.globs
+    except AttributeError:
+        return test.__dict__
+
 def register(test, function, *args, **kw):
-    stack = test.globs.get(key)
+    tglobs = globs(test)
+    stack = tglobs.get(key)
     if stack is None:
-        stack = test.globs[key] = []
+        stack = tglobs[key] = []
     stack.append((function, args, kw))
 
 def tearDown(test):
-    stack = test.globs.get(key)
+    tglobs = globs(test)
+    stack = tglobs.get(key)
     while stack:
         f, p, k = stack.pop()
         f(*p, **k)
@@ -50,4 +58,9 @@
             dname = os.path.join(path, dname)
             os.rmdir(dname)
     os.rmdir(path)
-    
+
+def context_manager(test, manager):
+    result = manager.__enter__()
+    register(test, manager.__exit__, None, None, None)
+    return result
+

Modified: zope.testing/trunk/src/zope/testing/setupstack.txt
===================================================================
--- zope.testing/trunk/src/zope/testing/setupstack.txt	2012-01-27 21:57:17 UTC (rev 124224)
+++ zope.testing/trunk/src/zope/testing/setupstack.txt	2012-01-28 21:12:57 UTC (rev 124225)
@@ -1,5 +1,5 @@
-Stack-based test doctest setUp and tearDown
-============================================
+Stack-based test setUp and tearDown
+===================================
 
 Writing doctest setUp and tearDown functions can be a bit tedious,
 especially when setUp/tearDown functions are combined.
@@ -109,3 +109,63 @@
 
     >>> os.path.exists(os.path.join(setupstack_cwd, 'Data.fs'))
     False
+
+Context-manager support
+-----------------------
+
+You can leverage context managers using the ``contextmanager`` method.
+The result of calling the content manager's __enter__ method will be
+returned. The context-manager's __exit__ method will be called as part
+of test tear down:
+
+    >>> class Manager(object):
+    ...     def __enter__(self):
+    ...         print_('enter')
+    ...         return 42
+    ...     def __exit__(self, *args):
+    ...         print_('exit', args)
+
+    >>> manager = Manager()
+    >>> test = Test()
+
+    >>> zope.testing.setupstack.context_manager(test, manager)
+    enter
+    42
+
+    >>> zope.testing.setupstack.tearDown(test)
+    exit (None, None, None)
+
+globs
+-----
+
+Doctests have ``globs`` attributes used to hold test globals.
+``setupstack`` was originally designed to work with doctests, but can
+now work with either doctests, or other test objects, as long as the
+test objects have either a ``globs`` attribute or a ``__dict__``
+attribute.  The ``zope.testing.setupstack.globs`` function is used to
+get the globals for a test object:
+
+    >>> zope.testing.setupstack.globs(test) is test.globs
+    True
+
+Here, because the test object had a ``globs`` attribute, it was
+returned. Because we used the test object above, it has a setupstack:
+
+    >>> '__zope.testing.setupstack' in test.globs
+    True
+
+If we remove the ``globs`` attribute, the object's instance dictionary
+will be used:
+
+    >>> del test.globs
+    >>> zope.testing.setupstack.globs(test) is test.__dict__
+    True
+    >>> zope.testing.setupstack.context_manager(test, manager)
+    enter
+    42
+
+    >>> '__zope.testing.setupstack' in test.__dict__
+    True
+
+The ``globs`` function is used internally, but can also be used by
+setup code to support either doctests or other test objects.

Modified: zope.testing/trunk/src/zope/testing/tests.py
===================================================================
--- zope.testing/trunk/src/zope/testing/tests.py	2012-01-27 21:57:17 UTC (rev 124224)
+++ zope.testing/trunk/src/zope/testing/tests.py	2012-01-28 21:12:57 UTC (rev 124225)
@@ -12,26 +12,28 @@
 """Tests for the testing framework.
 """
 
+import doctest
 import sys
 import re
 import unittest
 import warnings
 from zope.testing import renormalizing
 
-# Yes, it is deprecated, but we want to run tests on it here.
-warnings.filterwarnings("ignore", "zope.testing.doctest is deprecated",
-                        DeprecationWarning, __name__, 0)
+if sys.version < '3':
+    # Yes, it is deprecated, but we want to run tests on it here.
+    warnings.filterwarnings("ignore", "zope.testing.doctest is deprecated",
+                            DeprecationWarning, __name__, 0)
 
-from zope.testing import doctest
+    from zope.testing import doctest
 
+def print_(*args):
+    sys.stdout.write(' '.join(map(str, args))+'\n')
 
+def setUp(test):
+    test.globs['print_'] = print_
+
 def test_suite():
     suite = unittest.TestSuite((
-        doctest.DocTestSuite('zope.testing.loggingsupport'),
-        doctest.DocTestSuite('zope.testing.renormalizing'),
-        doctest.DocTestSuite('zope.testing.server'),
-        doctest.DocFileSuite('doctest.txt'),
-        doctest.DocFileSuite('formparser.txt'),
         doctest.DocFileSuite(
             'module.txt',
             # when this test is run in isolation, the error message shows the
@@ -41,9 +43,21 @@
             checker=renormalizing.RENormalizing([
                 (re.compile('No module named zope.testing.unlikelymodulename'),
                  'No module named unlikelymodulename')])),
-        doctest.DocFileSuite('setupstack.txt'),
+        doctest.DocFileSuite('loggingsupport.txt', setUp=setUp),
+        doctest.DocFileSuite('renormalizing.txt', setUp=setUp),
+        doctest.DocFileSuite('setupstack.txt', setUp=setUp),
+        doctest.DocFileSuite(
+            'wait_until.txt', setUp=setUp,
+            checker=renormalizing.RENormalizing([
+                (re.compile('zope.testing.wait_until.TimeOutWaitingFor: '),
+                 'TimeOutWaitingFor: '),
+                ])
+            ),
         ))
 
     if sys.version < '3':
+        suite.addTests(doctest.DocFileSuite('doctest.txt'))
         suite.addTests(doctest.DocFileSuite('unicode.txt'))
+        suite.addTests(doctest.DocTestSuite('zope.testing.server'))
+        suite.addTests(doctest.DocFileSuite('formparser.txt'))
     return suite

Added: zope.testing/trunk/src/zope/testing/wait_until.py
===================================================================
--- zope.testing/trunk/src/zope/testing/wait_until.py	                        (rev 0)
+++ zope.testing/trunk/src/zope/testing/wait_until.py	2012-01-28 21:12:57 UTC (rev 124225)
@@ -0,0 +1,68 @@
+##############################################################################
+#
+# Copyright Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+import time
+
+class WaitUntil:
+
+    class TimeOutWaitingFor(Exception):
+        "A test condition timed out"
+
+    timeout = 9
+    wait = .01
+
+    def __init__(self,
+                 timeout=None, wait=None, exception=None,
+                 getnow=(lambda : time.time), getsleep=(lambda : time.sleep)):
+
+        if timeout is not None:
+            self.timeout = timeout
+
+        if wait is not None:
+            self.wait = wait
+
+        if exception is not None:
+            self.TimeOutWaitingFor = exception
+
+        self.getnow = getnow
+        self.getsleep = getsleep
+
+    def __call__(self, func=None, timeout=None, wait=None, message=None):
+        if func is None:
+            return lambda func: self(func, timeout, wait, message)
+
+        if func():
+            return
+
+        now = self.getnow()
+        sleep = self.getsleep()
+        if timeout is None:
+            timeout = self.timeout
+        if wait is None:
+            wait = self.wait
+        wait = float(wait)
+
+        deadline = now() + timeout
+        while 1:
+            sleep(wait)
+            if func():
+                return
+            if now() > deadline:
+                raise self.TimeOutWaitingFor(
+                    message or
+                    getattr(func, '__doc__') or
+                    getattr(func, '__name__')
+                    )
+
+wait_until = WaitUntil()


Property changes on: zope.testing/trunk/src/zope/testing/wait_until.py
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: zope.testing/trunk/src/zope/testing/wait_until.txt
===================================================================
--- zope.testing/trunk/src/zope/testing/wait_until.txt	                        (rev 0)
+++ zope.testing/trunk/src/zope/testing/wait_until.txt	2012-01-28 21:12:57 UTC (rev 124225)
@@ -0,0 +1,170 @@
+Wait until a condition holds (or until a time out)
+==================================================
+
+Often, in tests, you need to wait until some condition holds.  This
+may be because you're testing interaction with an external system or
+testing threaded (threads, processes, greenlet's, etc.) interactions.
+
+You can add sleeps to your tests, but it's often hard to know how
+long to sleep.
+
+``zope.testing.wait_until`` provides a convenient way to wait until
+some condition holds.  It will test a condition and, when true,
+return.  It will sleep a short time between tests.
+
+Here's a silly example, that illustrates it's use:
+
+    >>> from zope.testing.wait_until import wait_until
+    >>> wait_until(lambda : True)
+
+Since the condition we passed is always True, it returned
+immediately.  If the condition doesn't hold, then we'll get a timeout:
+
+    >>> wait_until((lambda : False), timeout=.01)
+    Traceback (most recent call last):
+    ...
+    TimeOutWaitingFor: <lambda>
+
+``wait_until`` has some keyword options:
+
+timeout
+   How long, in seconds, to wait for the condition to hold
+
+   Defaults to 9 seconds.
+
+wait
+   How long to wait between calls.
+
+   Defaults to .01 seconds.
+
+message
+   A message (or other data) to pass to the timeout exception.
+
+   This defaults to ``None``.  If this is false, then the callable's
+   doc string or ``__name__`` is used.
+
+``wait_until`` can be used as a decorator:
+
+    >>> @wait_until
+    ... def ok():
+    ...     return True
+
+    >>> @wait_until(timeout=.01)
+    ... def no_way():
+    ...     pass
+    Traceback (most recent call last):
+    ...
+    TimeOutWaitingFor: no_way
+
+    >>> @wait_until(timeout=.01)
+    ... def no_way():
+    ...     "never true"
+    Traceback (most recent call last):
+    ...
+    TimeOutWaitingFor: never true
+
+.. more tests
+
+    >>> import time
+    >>> now = time.time()
+    >>> @wait_until(timeout=.01, message='dang')
+    ... def no_way():
+    ...     "never true"
+    Traceback (most recent call last):
+    ...
+    TimeOutWaitingFor: dang
+
+    >>> .01 < (time.time() - now) < .03
+    True
+
+
+Customization
+-------------
+
+``wait_until`` is an instance of ``WaitUntil``.  With ``WaitUntil``,
+you can create you're own custom ``wait_until`` utilities.  For
+example, if you're testing something that uses getevent, you'd want to
+use gevent's sleep function:
+
+    >>> import zope.testing.wait_until
+    >>> wait_until = zope.testing.wait_until.WaitUntil(
+    ...    getsleep=lambda : gevent.sleep)
+
+WaitUntil takes a number of customization parameters:
+
+exception
+  Timeout exception class
+
+getnow
+  Function used to get a function for getting the current time.
+
+  Default: lambda : time.time
+
+getsleep
+  Function used to get a sleep function.
+
+  Default: lambda : time.sleep
+
+timeout
+  Default timeout
+
+  Default: 9
+
+wait
+  Default time to wait between attempts
+
+  Default: .01
+
+
+.. more tests
+
+    >>> def mysleep(t):
+    ...     print_('mysleep', t)
+    ...     time.sleep(t)
+
+    >>> def mynow():
+    ...     print_('mynow')
+    ...     return time.time()
+
+    >>> wait_until = zope.testing.wait_until.WaitUntil(
+    ...    getnow=(lambda : mynow), getsleep=(lambda : mysleep),
+    ...    exception=ValueError, timeout=.02, wait=.0001)
+
+    >>> @wait_until
+    ... def _(state=[]):
+    ...     if len(state) > 1:
+    ...        return True
+    ...     state.append(0)
+    mynow
+    mysleep 0.0001
+    mynow
+    mysleep 0.0001
+
+    >>> @wait_until(wait=.002)
+    ... def _(state=[]):
+    ...     if len(state) > 1:
+    ...        return True
+    ...     state.append(0)
+    mynow
+    mysleep 0.002
+    mynow
+    mysleep 0.002
+
+    >>> @wait_until(timeout=0)
+    ... def _(state=[]):
+    ...     if len(state) > 1:
+    ...        return True
+    ...     state.append(0)
+    Traceback (most recent call last):
+    ...
+    ValueError: _
+
+    >>> wait_until = zope.testing.wait_until.WaitUntil(timeout=0)
+    >>> @wait_until(timeout=0)
+    ... def _(state=[]):
+    ...     if len(state) > 1:
+    ...        return True
+    ...     state.append(0)
+    Traceback (most recent call last):
+    ...
+    TimeOutWaitingFor: _


Property changes on: zope.testing/trunk/src/zope/testing/wait_until.txt
___________________________________________________________________
Added: svn:eol-style
   + native



More information about the Zope3-Checkins mailing list