Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/tags/cherrypy-3.0.0/cherrypy/test/test_tools.py

Revision 1551 (checked in by fumanchu, 2 years ago)

Cleaner test_tools.

  • Property svn:eol-style set to native
Line 
1 """Test the various means of instantiating and invoking tools."""
2
3 import gzip, StringIO
4 import time
5 timeout = 0.2
6
7 import types
8 from cherrypy.test import test
9 test.prefer_parent_path()
10
11 import cherrypy
12 from cherrypy import tools
13
14
15 europoundUnicode = u'\x80\xa3'
16
17 def setup_server():
18    
19     # Put check_access in a custom toolbox with its own namespace
20     myauthtools = cherrypy._cptools.Toolbox("myauth")
21    
22     def check_access(default=False):
23         if not getattr(cherrypy.request, "userid", default):
24             raise cherrypy.HTTPError(401)
25     myauthtools.check_access = cherrypy.Tool('before_request_body', check_access)
26    
27     def numerify():
28         def number_it(body):
29             for chunk in body:
30                 for k, v in cherrypy.request.numerify_map:
31                     chunk = chunk.replace(k, v)
32                 yield chunk
33         cherrypy.response.body = number_it(cherrypy.response.body)
34    
35     class NumTool(cherrypy.Tool):
36         def _setup(self):
37             def makemap():
38                 m = self._merged_args().get("map", {})
39                 cherrypy.request.numerify_map = m.items()
40             cherrypy.request.hooks.attach('on_start_resource', makemap)
41            
42             def critical():
43                 cherrypy.request.error_response = cherrypy.HTTPError(502).set_response
44             critical.failsafe = True
45            
46             cherrypy.request.hooks.attach('on_start_resource', critical)
47             cherrypy.request.hooks.attach(self._point, self.callable)
48    
49     tools.numerify = NumTool('before_finalize', numerify)
50    
51     # It's not mandatory to inherit from cherrypy.Tool.
52     class NadsatTool:
53        
54         def __init__(self):
55             self.ended = {}
56             self._name = "nadsat"
57        
58         def nadsat(self):
59             def nadsat_it_up(body):
60                 for chunk in body:
61                     chunk = chunk.replace("good", "horrorshow")
62                     chunk = chunk.replace("piece", "lomtick")
63                     yield chunk
64             cherrypy.response.body = nadsat_it_up(cherrypy.response.body)
65         nadsat.priority = 0
66        
67         def cleanup(self):
68             # This runs after the request has been completely written out.
69             cherrypy.response.body = "razdrez"
70             id = cherrypy.request.params.get("id")
71             if id:
72                 self.ended[id] = True
73         cleanup.failsafe = True
74        
75         def _setup(self):
76             cherrypy.request.hooks.attach('before_finalize', self.nadsat)
77             cherrypy.request.hooks.attach('on_end_request', self.cleanup)
78     tools.nadsat = NadsatTool()
79    
80     def pipe_body():
81         cherrypy.request.process_request_body = False
82         clen = int(cherrypy.request.headers['Content-Length'])
83         cherrypy.request.body = cherrypy.request.rfile.read(clen)
84    
85     class Root:
86         def index(self):
87             return "Howdy earth!"
88         index.exposed = True
89        
90         def euro(self):
91             hooks = list(cherrypy.request.hooks['before_finalize'])
92             hooks.sort()
93             assert [x.callback.__name__ for x in hooks] == ['encode', 'gzip']
94             assert [x.priority for x in hooks] == [70, 80]
95             yield u"Hello,"
96             yield u"world"
97             yield europoundUnicode
98         euro.exposed = True
99        
100         # Bare hooks
101         def pipe(self):
102             return cherrypy.request.body
103         pipe.exposed = True
104         pipe._cp_config = {'hooks.before_request_body': pipe_body}
105        
106         # Multiple decorators; include kwargs just for fun.
107         # Note that encode must run before gzip.
108         def decorated_euro(self, *vpath):
109             yield u"Hello,"
110             yield u"world"
111             yield europoundUnicode
112         decorated_euro.exposed = True
113         decorated_euro = tools.gzip(compress_level=6)(decorated_euro)
114         decorated_euro = tools.encode(errors='ignore')(decorated_euro)
115    
116     root = Root()
117    
118    
119     class TestType(type):
120         """Metaclass which automatically exposes all functions in each subclass,
121         and adds an instance of the subclass as an attribute of root.
122         """
123         def __init__(cls, name, bases, dct):
124             type.__init__(name, bases, dct)
125             for value in dct.itervalues():
126                 if isinstance(value, types.FunctionType):
127                     value.exposed = True
128             setattr(root, name.lower(), cls())
129     class Test(object):
130         __metaclass__ = TestType
131    
132    
133     # METHOD ONE:
134     # Declare Tools in _cp_config
135     class Demo(Test):
136        
137         _cp_config = {"tools.nadsat.on": True}
138        
139         def index(self, id=None):
140             return "A good piece of cherry pie"
141        
142         def ended(self, id):
143             return repr(tools.nadsat.ended[id])
144        
145         def err(self, id=None):
146             raise ValueError()
147        
148         def errinstream(self, id=None):
149             raise ValueError()
150             yield "confidential"
151        
152         # METHOD TWO: decorator using Tool()
153         # We support Python 2.3, but the @-deco syntax would look like this:
154         # @tools.check_access()
155         def restricted(self):
156             return "Welcome!"
157         restricted = myauthtools.check_access()(restricted)
158         userid = restricted
159        
160         def err_in_onstart(self):
161             return "success!"
162        
163         def stream(self, id=None):
164             for x in xrange(100000000):
165                 yield str(x)
166         stream._cp_config = {'response.stream': True}
167    
168    
169     cherrypy.config.update({'environment': 'test_suite'})
170    
171     conf = {
172         # METHOD THREE:
173         # Declare Tools in detached config
174         '/demo': {
175             'tools.numerify.on': True,
176             'tools.numerify.map': {"pie": "3.14159"},
177         },
178         '/demo/restricted': {
179             'request.show_tracebacks': False,
180         },
181         '/demo/userid': {
182             'request.show_tracebacks': False,
183             'myauth.check_access.default': True,
184         },
185         '/demo/errinstream': {
186             'response.stream': True,
187         },
188         '/demo/err_in_onstart': {
189             # Because this isn't a dict, on_start_resource will error.
190             'tools.numerify.map': "pie->3.14159"
191         },
192         # Combined tools
193         '/euro': {
194             'tools.gzip.on': True,
195             'tools.encode.on': True,
196         },
197         # Priority specified in config
198         '/decorated_euro/subpath': {
199             'tools.gzip.priority': 10,
200         },
201     }
202     cherrypy.tree.mount(root, config=conf)
203
204
205 #                             Client-side code                             #
206
207 from cherrypy.test import helper
208
209
210 class ToolTests(helper.CPWebCase):
211    
212     def testHookErrors(self):
213         self.getPage("/demo/?id=1")
214         # If body is "razdrez", then on_end_request is being called too early.
215         self.assertBody("A horrorshow lomtick of cherry 3.14159")
216         # If this fails, then on_end_request isn't being called at all.
217         time.sleep(0.1)
218         self.getPage("/demo/ended/1")
219         self.assertBody("True")
220        
221         valerr = '\n    raise ValueError()\nValueError'
222         self.getPage("/demo/err?id=3")
223         # If body is "razdrez", then on_end_request is being called too early.
224         self.assertErrorPage(502, pattern=valerr)
225         # If this fails, then on_end_request isn't being called at all.
226         time.sleep(0.1)
227         self.getPage("/demo/ended/3")
228         self.assertBody("True")
229        
230         # If body is "razdrez", then on_end_request is being called too early.
231         self.getPage("/demo/errinstream?id=5")
232         # Because this error is raised after the response body has
233         # started, the status should not change to an error status.
234         self.assertStatus("200 OK")
235         self.assertBody("Unrecoverable error in the server.")
236         # If this fails, then on_end_request isn't being called at all.
237         time.sleep(0.1)
238         self.getPage("/demo/ended/5")
239         self.assertBody("True")
240        
241         # Test the "__call__" technique (compile-time decorator).
242         self.getPage("/demo/restricted")
243         self.assertErrorPage(401)
244        
245         # Test compile-time decorator with kwargs from config.
246         self.getPage("/demo/userid")
247         self.assertBody("Welcome!")
248    
249     def testEndRequestOnDrop(self):
250         old_timeout = None
251         try:
252             httpserver = cherrypy.server.httpservers.keys()[0]
253             old_timeout = httpserver.timeout
254         except (AttributeError, IndexError):
255             print "skipped ",
256             return
257        
258         try:
259             httpserver.timeout = timeout
260            
261             # Test that on_end_request is called even if the client drops.
262             self.persistent = True
263             try:
264                 conn = self.HTTP_CONN
265                 conn.putrequest("GET", "/demo/stream?id=9", skip_host=True)
266                 conn.putheader("Host", self.HOST)
267                 conn.endheaders()
268                 # Skip the rest of the request and close the conn. This will
269                 # cause the server's active socket to error, which *should*
270                 # result in the request being aborted, and request.close being
271                 # called all the way up the stack (including WSGI middleware),
272                 # eventually calling our on_end_request hook.
273             finally:
274                 self.persistent = False
275             time.sleep(timeout * 2)
276             # Test that the on_end_request hook was called.
277             self.getPage("/demo/ended/9")
278             self.assertBody("True")
279         finally:
280             if old_timeout is not None:
281                 httpserver.timeout = old_timeout
282    
283     def testGuaranteedHooks(self):
284         # The 'critical' on_start_resource hook is 'failsafe' (guaranteed
285         # to run even if there are failures in other on_start methods).
286         # This is NOT true of the other hooks.
287         # Here, we have set up a failure in NumerifyTool.numerify_map,
288         # but our 'critical' hook should run and set the error to 502.
289         self.getPage("/demo/err_in_onstart")
290         self.assertErrorPage(502)
291         self.assertInBody("AttributeError: 'str' object has no attribute 'items'")
292    
293     def testCombinedTools(self):
294         expectedResult = (u"Hello,world" + europoundUnicode).encode('utf-8')
295         zbuf = StringIO.StringIO()
296         zfile = gzip.GzipFile(mode='wb', fileobj=zbuf, compresslevel=9)
297         zfile.write(expectedResult)
298         zfile.close()
299        
300         self.getPage("/euro", headers=[("Accept-Encoding", "gzip"),
301                                         ("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7")])
302         self.assertInBody(zbuf.getvalue()[:3])
303        
304         zbuf = StringIO.StringIO()
305         zfile = gzip.GzipFile(mode='wb', fileobj=zbuf, compresslevel=6)
306         zfile.write(expectedResult)
307         zfile.close()
308        
309         self.getPage("/decorated_euro", headers=[("Accept-Encoding", "gzip")])
310         self.assertInBody(zbuf.getvalue()[:3])
311        
312         # This should break because gzip's priority was lowered in conf.
313         # Of course, we don't want breakage in production apps,
314         # but it proves the priority was changed.
315         self.getPage("/decorated_euro/subpath",
316                      headers=[("Accept-Encoding", "gzip")])
317         self.assertErrorPage(500, pattern='UnicodeEncodeError')
318    
319     def testBareHooks(self):
320         content = "bit of a pain in me gulliver"
321         self.getPage("/pipe",
322                      headers=[("Content-Length", len(content)),
323                               ("Content-Type", "text/plain")],
324                      method="POST", body=content)
325         self.assertBody(content)
326
327
328 if __name__ == '__main__':
329     setup_server()
330     helper.testmain()
331
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets