source: main/waeup.sirp/trunk/src/waeup/sirp/testing.py @ 6534

Last change on this file since 6534 was 6466, checked in by uli, 14 years ago

Use other replacement for dates.

  • Property svn:eol-style set to native
File size: 8.8 KB
Line 
1"""Testing support for :mod:`waeup.sirp`.
2"""
3import grok
4import doctest
5import os.path
6import re
7import warnings
8import zope.component
9import waeup.sirp
10from zope.app.testing.functional import (
11    ZCMLLayer, FunctionalTestSetup, getRootFolder, sync, FunctionalTestCase)
12from zope.component import getGlobalSiteManager
13from zope.security.testing import addCheckerPublic
14from zope.testing import renormalizing
15from zope.testing.cleanup import cleanUp
16
17ftesting_zcml = os.path.join(
18    os.path.dirname(waeup.sirp.__file__), 'ftesting.zcml')
19FunctionalLayer = ZCMLLayer(ftesting_zcml, __name__, 'FunctionalLayer',
20                            allow_teardown=True)
21
22def setUpZope(test=None):
23    """Initialize a Zope-compatible environment.
24
25    Currently, we only initialize the event machinery.
26    """
27    zope.component.eventtesting.setUp(test)
28
29def cleanUpZope(test=None):
30    """Clean up Zope-related registrations.
31
32    Cleans up all registrations and the like.
33    """
34    cleanUp()
35
36def maybe_grok():
37    """Try to grok the :mod:`waeup.sirp` package.
38
39    For many tests, even simple ones, we want the components defined
40    somewhere in the :mod:`waeup.sirp` package being registered. While
41    grokking the complete package can become expensive when done many
42    times, we only want to grok if it did not happen
43    before. Furthermore regrokking the whole package makes no sense if
44    done already.
45
46    :func:`maybe_grok` checks whether any eventhandlers are already
47    registered and does nothing in that case.
48
49    The grokking of :mod:`waeup.sirp` is done with warnings disabled.
50
51    Returns ``True`` if grokking was done, ``False`` else.
52
53    .. The following samples should go into Sphinx docs directly....
54
55    Sample
56    ******
57
58    Usage with plain Python testrunners
59    -----------------------------------
60
61    Together with the :func:`setUpZope` and :func:`cleanUpZope`
62    functions we then can do unittests with all components registered
63    and the event dispatcher in place like this::
64
65      import unittest2 as unittest # Want Python 2.7 features
66      from waeup.sirp.testing import (
67        maybe_grok, setUpZope, cleanUpZope,
68        )
69      from waeup.sirp.app import University
70
71      class MyTestCase(unittest.TestCase):
72
73          @classmethod
74          def setUpClass(cls):
75              grokked = maybe_grok()
76              if grokked:
77                  setUpZope(None)
78              return
79
80          @classmethod
81          def tearDownClass(cls):
82              cleanUpZope(None)
83
84          def setUp(self):
85              pass
86
87          def tearDown(self):
88              pass
89
90          def test_jambdata_in_site(self):
91              u = University()
92              self.assertTrue('jambdata' in u.keys())
93              return
94
95    Here the component registration is done only once for the whole
96    :class:`unittest.TestCase` and no ZODB is needed. That means
97    inside the tests you can expect to have all :mod:`waeup.sirp`
98    components (utilities, adapter, events) registered but as objects
99    have here still have no place inside a ZODB things like 'browsing'
100    won't work out of the box. The benefit is the faster test
101    setup/teardown.
102
103    .. note:: This works only with the default Python testrunners.
104
105         If you use the Zope testrunner (from :mod:`zope.testing`)
106         then you have to use appropriate layers like the
107         :class:`waeup.sirp.testing.WAeUPSIRPUnitTestLayer`.
108
109    Usage with :mod:`zope.testing` testrunners
110    ------------------------------------------
111
112    If you use the standard Zope testrunner, classmethods like
113    `setUpClass` are not executed. Instead you have to use a layer
114    like the one defined in this module.
115
116    .. seealso:: :class:`waeup.sirp.testing.WAeUPSIRPUnitTestLayer`
117
118    """
119    gsm =  getGlobalSiteManager()
120    # If there are any event handlers registered already, we assume
121    # that waeup.sirp was grokked already. There might be a batter
122    # check, though.
123    if len(list(gsm.registeredHandlers())) > 0:
124        return False
125    # Register the zope.Public permission, normally done via ZCML setup.
126    addCheckerPublic()
127    warnings.simplefilter('ignore') # disable (erraneous) warnings
128    grok.testing.grok('waeup.sirp')
129    warnings.simplefilter('default') # reenable warnings
130    return True
131
132
133class WAeUPSIRPUnitTestLayer(object):
134    """A layer for tests that groks `waeup.sirp`.
135
136    A Zope test layer that registers all :mod:`waeup.sirp` components
137    before attached tests are run and cleans this registrations up
138    afterwards. Also basic (non-waeup.sirp) components like the event
139    dispatcher machinery are registered, set up and cleaned up.
140
141    This layer does not provide a complete ZODB setup (and is
142    therefore much faster than complete functional setups) but does
143    only the registrations (which also takes some time, so running
144    this layer is slower than test cases that need none or only a
145    few registrations).
146
147    The registrations are done for all tests the layer is attached to
148    once before all these tests are run (and torn down once
149    afterwards).
150
151    To make use of this layer, you have to write a
152    :mod:`unittest.TestCase` class that provides an attribute called
153    ``layer`` with this class as value like this::
154
155      import unittest
156      from waeup.sirp.testing import WAeUPSIRPUnitTestLayer
157
158      class MyTestCase(unittest.TestCase):
159
160          layer = WAeUPSIRPUnitTestLayer
161
162          # per-test setups and real tests go here...
163          def test_foo(self):
164              self.assertEqual(1, 1)
165              return
166
167    """
168    @classmethod
169    def setUp(cls):
170        #setUpZope(None)
171        grokked = maybe_grok()
172        if grokked:
173            pass
174            #setUpZope(None)
175        return
176
177    @classmethod
178    def tearDown(cls):
179        cleanUpZope(None)
180
181#: This extended :class:`doctest.OutputChecker` allows the following
182#: additional matches when looking for output diffs:
183#:
184#: `N.NNN seconds`
185#:    matches strings like ``12.123 seconds``
186#:
187#: `HTTPError:`
188#:    matches ``httperror_seek_wrapper:``. This string is output by some
189#:    virtual browsers you might use in functional browser tests to signal
190#:    HTTP error state.
191#:
192#: `1034h`
193#:    is ignored. This sequence of control chars is output by some
194#:    (buggy) testrunners at beginning of output.
195#:
196#: `<10-DIGITS>`
197#:    matches a sequence of 10 digits. Useful when checking accesscode
198#:    numbers if you don't know the exact (random) code.
199#:
200#: `<YYYY-MM-DD hh:mm:ss>`
201#:    matches any date and time like `2011-05-01 12:01:32`.
202#:
203#: `<DATE-AND-TIME>`
204#:    same like ``<YYYY-MM-DD hh:mm:ss>`` but shorter.
205checker = renormalizing.RENormalizing([
206    # Relevant normalizers from zope.testing.testrunner.tests:
207    (re.compile(r'\d+[.]\d\d\d seconds'), 'N.NNN seconds'),
208    # Our own one to work around
209    # http://reinout.vanrees.org/weblog/2009/07/16/invisible-test-diff.html:
210    (re.compile(r'.*1034h'), ''),
211    (re.compile(r'httperror_seek_wrapper:'), 'HTTPError:' ),
212    (re.compile('[\d]{10}'), '<10-DIGITS>'),
213    (re.compile('\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d'), '<YYYY-MM-DD hh:mm:ss>'),
214    (re.compile('\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d'), '<DATETIME>'),
215    ])
216
217def setUp(test):
218    FunctionalTestSetup().setUp()
219
220def tearDown(test):
221    FunctionalTestSetup().tearDown()
222
223def doctestsuite_for_module(dotted_path):
224    """Create a doctest suite for the module at `dotted_path`.
225    """
226    test = doctest.DocTestSuite(
227        dotted_path,
228        setUp = setUp,
229        tearDown = tearDown,
230        checker = checker,
231        extraglobs = dict(
232            getRootFolder=getRootFolder,
233            sync=sync,),
234        optionflags = (doctest.ELLIPSIS +
235                       doctest.NORMALIZE_WHITESPACE +
236                       doctest.REPORT_NDIFF),
237        )
238    test.layer = FunctionalLayer
239    return test
240
241optionflags = (
242    doctest.REPORT_NDIFF + doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE)
243
244class FunctionalTestCase(FunctionalTestCase):
245    """A test case that supports checking output diffs in doctest style.
246    """
247
248    def assertMatches(self, want, got, checker=checker,
249                      optionflags=optionflags):
250        """Assert that the multiline string `want` matches `got`.
251
252        In `want` you can use shortcuts like ``...`` as in regular doctests.
253
254        If no special `checker` is passed, we use an extended
255        :class:`doctest.OutputChecker` as defined in
256        :mod:`waeup.sirp.testing`.
257
258        If optional `optionflags` are not given, use ``REPORT_NDIFF``,
259        ``ELLIPSIS``, and ``NORMALIZE_WHITESPACE``.
260
261        .. seealso:: :data:`waeup.sirp.testing.optionflags`
262
263        .. seealso:: :data:`waeup.sirp.testing.checker`
264        """
265        if checker.check_output(want, got, optionflags):
266            return
267        diff = checker.output_difference(
268            doctest.Example('', want), got, optionflags)
269        self.fail(diff)
Note: See TracBrowser for help on using the repository browser.