1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
|
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Infrastructure for setting up doctests."""
__metaclass__ = type
__all__ = [
'default_optionflags',
'LayeredDocFileSuite',
'setUp',
'setGlobs',
'stop',
'strip_prefix',
'tearDown',
]
import doctest
from functools import partial
import logging
import os
import pdb
import pprint
import sys
import transaction
from zope.component import getUtility
from zope.testing.loggingsupport import Handler
from lp.services.config import config
from lp.services.database.sqlbase import flush_database_updates
from lp.services.webapp.interfaces import ILaunchBag
from lp.services.webapp.testing import verifyObject
from lp.testing import (
ANONYMOUS,
launchpadlib_credentials_for,
launchpadlib_for,
login,
login_person,
logout,
oauth_access_token_for,
reset_logging,
)
from lp.testing.factory import LaunchpadObjectFactory
from lp.testing.views import (
create_initialized_view,
create_view,
)
default_optionflags = (doctest.REPORT_NDIFF |
doctest.NORMALIZE_WHITESPACE |
doctest.ELLIPSIS)
def strip_prefix(path):
"""Return path with the Launchpad tree root removed."""
prefix = config.root
if not prefix.endswith(os.path.sep):
prefix += os.path.sep
if path.startswith(prefix):
return path[len(prefix):]
else:
return path
class FilePrefixStrippingDocTestParser(doctest.DocTestParser):
"""A DocTestParser that strips a prefix from doctests."""
def get_doctest(self, string, globs, name, filename, lineno):
filename = strip_prefix(filename)
return doctest.DocTestParser.get_doctest(
self, string, globs, name, filename, lineno)
default_parser = FilePrefixStrippingDocTestParser()
class StdoutHandler(Handler):
"""A logging handler that prints log messages to sys.stdout.
This causes log messages to become part of the output captured by
doctest, making the test cover the logging behaviour of the code
being run.
"""
def emit(self, record):
Handler.emit(self, record)
print >> sys.stdout, '%s:%s:%s' % (
record.levelname, record.name, self.format(record))
def LayeredDocFileSuite(*paths, **kw):
"""Create a DocFileSuite, optionally applying a layer to it.
In addition to the standard DocFileSuite arguments, the following
optional keyword arguments are accepted:
:param stdout_logging: If True, log messages are sent to the
doctest's stdout (defaults to True).
:param stdout_logging_level: The logging level for the above.
:param layer: A Zope test runner layer to apply to the tests (by
default no layer is applied).
"""
kw.setdefault('optionflags', default_optionflags)
kw.setdefault('parser', default_parser)
# Make sure that paths are resolved relative to our caller
kw['package'] = doctest._normalize_module(kw.get('package'))
# Set stdout_logging keyword argument to True to make
# logging output be sent to stdout, forcing doctests to deal with it.
stdout_logging = kw.pop('stdout_logging', True)
stdout_logging_level = kw.pop('stdout_logging_level', logging.INFO)
if stdout_logging:
kw_setUp = kw.get('setUp')
def setUp(test):
if kw_setUp is not None:
kw_setUp(test)
log = StdoutHandler('')
log.setLoggerLevel(stdout_logging_level)
log.install()
test.globs['log'] = log
# Store as instance attribute so we can uninstall it.
test._stdout_logger = log
kw['setUp'] = setUp
kw_tearDown = kw.get('tearDown')
def tearDown(test):
if kw_tearDown is not None:
kw_tearDown(test)
reset_logging()
test._stdout_logger.uninstall()
kw['tearDown'] = tearDown
layer = kw.pop('layer', None)
suite = doctest.DocFileSuite(*paths, **kw)
if layer is not None:
suite.layer = layer
for test in suite:
# doctest._module_relative_path() does not normalize paths. To make
# test selection simpler and reporting easier to read, normalize here.
test._dt_test.filename = os.path.normpath(test._dt_test.filename)
# doctest.DocFileTest insists on using the basename of the file as the
# test ID. This causes conflicts when two doctests have the same
# filename, so we patch the id() method on the test cases.
test.id = partial(lambda test: test._dt_test.filename, test)
return suite
def ordered_dict_as_string(dict):
"""Return the contents of a dict as an ordered string.
The output will be ordered by key, so {'z': 1, 'a': 2, 'c': 3} will
be printed as {'a': 2, 'c': 3, 'z': 1}.
We do this because dict ordering is not guaranteed.
"""
# XXX 2008-06-25 gmb:
# Once we move to Python 2.5 we won't need this, since dict
# ordering is guaranteed when __str__() is called.
item_string = '%r: %r'
item_strings = []
for key, value in sorted(dict.items()):
item_strings.append(item_string % (key, value))
return '{%s}' % ', '.join(
"%r: %r" % (key, value) for key, value in sorted(dict.items()))
def stop():
# Temporarily restore the real stdout.
old_stdout = sys.stdout
sys.stdout = sys.__stdout__
try:
pdb.set_trace()
finally:
sys.stdout = old_stdout
def setGlobs(test):
"""Add the common globals for testing system documentation."""
test.globs['ANONYMOUS'] = ANONYMOUS
test.globs['login'] = login
test.globs['login_person'] = login_person
test.globs['logout'] = logout
test.globs['ILaunchBag'] = ILaunchBag
test.globs['getUtility'] = getUtility
test.globs['transaction'] = transaction
test.globs['flush_database_updates'] = flush_database_updates
test.globs['create_view'] = create_view
test.globs['create_initialized_view'] = create_initialized_view
test.globs['factory'] = LaunchpadObjectFactory()
test.globs['ordered_dict_as_string'] = ordered_dict_as_string
test.globs['verifyObject'] = verifyObject
test.globs['pretty'] = pprint.PrettyPrinter(width=1).pformat
test.globs['stop'] = stop
test.globs['launchpadlib_for'] = launchpadlib_for
test.globs['launchpadlib_credentials_for'] = launchpadlib_credentials_for
test.globs['oauth_access_token_for'] = oauth_access_token_for
def setUp(test):
"""Setup the common globals and login for testing system documentation."""
setGlobs(test)
# Set up an anonymous interaction.
login(ANONYMOUS)
def tearDown(test):
"""Tear down the common system documentation test."""
logout()
|