Merge lp:~leonardr/lazr.restful/make-test-class-public into lp:lazr.restful

Proposed by Leonard Richardson
Status: Merged
Approved by: Edwin Grubbs
Approved revision: 128
Merged at revision: not available
Proposed branch: lp:~leonardr/lazr.restful/make-test-class-public
Merge into: lp:lazr.restful
Prerequisite: lp:~leonardr/lazr.restful/refactor-tag-request-with-version
Diff against target: 377 lines (+147/-135)
2 files modified
src/lazr/restful/testing/webservice.py (+138/-3)
src/lazr/restful/tests/test_webservice.py (+9/-132)
To merge this branch: bzr merge lp:~leonardr/lazr.restful/make-test-class-public
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code Approve
Review via email: mp+20574@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

lazr.restful's unit test file test_webservice.py used to have a class called WebServiceTestCase that would set up a dummy web service and run some unit tests against it. Over time, the amount of setup work necessary increased, to the point where WSTC was basically setting up a fully functional web service.

When integrating a new lazr.restful version into Launchpad, I noticed that Launchpad also had unit tests that ran against a web service, and that I could save myself a lot of trouble by writing unit tests that subclassed WebServiceTestCase. I did this, but I pledged to come back and move WSTC (and its dependent classes) into lazr.restful.testing.webservice, so that Launchpad wouldn't be importing classes from a lazr.restful unit test module.

This branch does basically that, and nothing else. The one fix I made in passing was to change SimpleWebServiceConfiguration.createRequest to use the utility function tag_request_with_version_name(), replacing code that was basically a copy of tag_request_with_version_name. I also had to change the WADL unit tests, because I fleshed out the doctest strings of IGenericEntry and IGenericConfiguration.

Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (9.1 KiB)

Hi Leonard,

The changes look good, but I got these errors when running the tests under python2.6. There were even more problems under python2.5.

-Edwin

Running lazr.restful.example.base.tests.test_integration.WSGILayer tests:
  Set up lazr.restful.example.base.tests.test_integration.FunctionalLayer in 0.328 seconds.
  Set up lazr.restful.example.base.tests.test_integration.WSGILayer in 0.000 seconds.

Failure in test /home/egrubbs/canonical/launchpadlib/refactor-tag-request-with-version/src/lazr/restful/example/base/tests/field.txt
Traceback (most recent call last):
  File "/usr/lib/python2.5/unittest.py", line 260, in run
    testMethod()
  File "/usr/lib/python2.5/doctest.py", line 2128, in runTest
    raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for field.txt
  File "/home/egrubbs/canonical/launchpadlib/refactor-tag-request-with-version/src/lazr/restful/example/base/tests/field.txt", line 0

----------------------------------------------------------------------
File "/home/egrubbs/canonical/launchpadlib/refactor-tag-request-with-version/src/lazr/restful/example/base/tests/field.txt", line 55, in field.txt
Failed example:
    print new_value
Differences (ndiff with -expected +actual):
    - "http://.../cookbooks/The%20Joy%20of%20Cooking"
    ? ^^^^
    + "http:\/\/cookbooks.dev\/devel\/cookbooks\/The%20Joy%20of%20Cooking"
    ? + ^ +++++++++++++++++++++++
----------------------------------------------------------------------
File "/home/egrubbs/canonical/launchpadlib/refactor-tag-request-with-version/src/lazr/restful/example/base/tests/field.txt", line 58, in field.txt
Failed example:
    print webservice(link_field_url, 'PATCH', new_value)
Differences (ndiff with -expected +actual):
      HTTP/1.1 209 Content Returned
    - ...
    + Status: 209 Content Returned
    + Content-Length: 68
      Content-Type: application/json
    - ...
    + X-Powered-By: Zope (www.zope.org), Python (www.python.org)
      <BLANKLINE>
    - "http://cookbooks.dev/.../cookbooks/The%20Joy%20of%20Cooking"
    ? ^^^
    + "http:\/\/cookbooks.dev\/devel\/cookbooks\/The%20Joy%20of%20Cooking"
    ? + + + ^^^^^^ +

  Ran 9 tests with 1 failures and 0 errors in 2.391 seconds.
Running lazr.restful.example.multiversion.tests.test_integration.WSGILayer tests:
  Tear down lazr.restful.example.base.tests.test_integration.WSGILayer in 0.000 seconds.
  Tear down lazr.restful.example.base.tests.test_integration.FunctionalLayer in 0.000 seconds.
  Set up lazr.restful.example.multiversion.tests.test_integration.FunctionalLayer in 0.078 seconds.
  Set up lazr.restful.example.multiversion.tests.test_integration.WSGILayer in 0.000 seconds.
  Ran 3 tests with 0 failures and 0 errors in 0.406 seconds.
Running lazr.restful.example.wsgi.tests.test_integration.WSGILayer tests:
  Tear down lazr.restful.example.multiversion.tests.test_integration.WSGILayer in 0.001 seconds.
  Tear down lazr.restful.example.multiversion.tests.test_integration.FunctionalLayer in 0.000 seconds.
  Set up lazr.restful.example.wsgi.tests.test_integration.FunctionalLayer in 0.069 se...

Read more...

review: Needs Fixing
Revision history for this message
Leonard Richardson (leonardr) wrote :

I think you're running the tests on an old version of simplejson. Can you run some code to see what version of simplejson you have? Here's what I get:

$ bin/py
>>> import simplejson
>>> simplejson.__version__
'2.0.9'

Also try making this change to setup.py, rerun bin/buildout, and see if the problem goes away.

=== modified file 'setup.py'
--- setup.py 2009-10-07 18:54:39 +0000
+++ setup.py 2010-03-04 14:03:26 +0000
@@ -64,7 +64,7 @@
         'lazr.uri',
         'martian==0.11',
         'setuptools',
- 'simplejson',
+ 'simplejson>=2.0.9',
         'van.testing',
         'wsgiref',
         'zope.app.pagetemplate',

Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Hi Leonard,

Updating simplejson fixed it.

merge-approved

-Edwin

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/lazr/restful/testing/webservice.py'
2--- src/lazr/restful/testing/webservice.py 2010-03-03 13:05:13 +0000
3+++ src/lazr/restful/testing/webservice.py 2010-03-03 17:07:18 +0000
4@@ -9,7 +9,11 @@
5 'ExampleWebServicePublication',
6 'FakeRequest',
7 'FakeResponse',
8+ 'IGenericEntry',
9+ 'IGenericCollection',
10 'pprint_entry',
11+ 'WebServiceTestCase',
12+ 'WebServiceTestConfiguration'
13 'WebServiceTestPublication',
14 'WebServiceTestRequest',
15 'TestPublication',
16@@ -19,25 +23,39 @@
17 import os
18 import traceback
19 import simplejson
20+import sys
21+from types import ModuleType
22 import urllib
23+import unittest
24 from urlparse import urljoin
25 import wsgi_intercept
26
27-from zope.component import adapts, getUtility, queryMultiAdapter
28-from zope.interface import implements
29+from zope.component import (
30+ adapts, getGlobalSiteManager, getUtility, queryMultiAdapter)
31+from zope.configuration import xmlconfig
32+from zope.interface import alsoProvides, implements, Interface
33 from zope.publisher.browser import BrowserRequest
34 from zope.publisher.interfaces.http import IHTTPApplicationRequest
35 from zope.publisher.paste import Application
36 from zope.proxy import ProxyBase
37+from zope.schema import TextLine
38 from zope.security.checker import ProxyFactory
39+from zope.testing.cleanup import CleanUp
40 from zope.traversing.browser.interfaces import IAbsoluteURL
41
42+from lazr.restful.declarations import (
43+ collection_default_content, exported,
44+ export_as_webservice_collection, export_as_webservice_entry,
45+ export_read_operation, operation_parameters)
46 from lazr.restful.interfaces import (
47 IServiceRootResource, IWebServiceClientRequest,
48 IWebServiceConfiguration, IWebServiceLayer, IWebServiceVersion)
49 from lazr.restful.publisher import (
50 WebServicePublicationMixin, WebServiceRequestTraversal)
51-from lazr.restful.simple import Publication, Request
52+from lazr.restful.simple import (
53+ BaseWebServiceConfiguration, Publication, Request,
54+ RootResourceAbsoluteURL, ServiceRootResource)
55+from lazr.restful.utils import tag_request_with_version_name
56
57 from lazr.uri import URI
58
59@@ -385,6 +403,121 @@
60 raise ValueError(self.body)
61
62
63+class WebServiceTestConfiguration(BaseWebServiceConfiguration):
64+ """A simple configuration for tests that need a running web service."""
65+ implements(IWebServiceConfiguration)
66+ show_tracebacks = False
67+ active_versions = ['1.0', '2.0']
68+ hostname = "webservice_test"
69+ last_version_with_mutator_named_operations = None
70+
71+ def createRequest(self, body_instream, environ):
72+ request = Request(body_instream, environ)
73+ request.setPublication(
74+ WebServiceTestPublication(getUtility(IServiceRootResource)))
75+ tag_request_with_version_name(request, '2.0')
76+ return request
77+
78+
79+class IWebServiceTestRequest10(IWebServiceClientRequest):
80+ """A marker interface for requests to the '1.0' web service."""
81+
82+
83+class IWebServiceTestRequest20(IWebServiceClientRequest):
84+ """A marker interface for requests to the '2.0' web service."""
85+
86+
87+class WebServiceTestCase(CleanUp, unittest.TestCase):
88+ """A test case for web service operations."""
89+
90+ testmodule_objects = []
91+
92+ def setUp(self):
93+ """Set the component registry with the given model."""
94+ super(WebServiceTestCase, self).setUp()
95+
96+ # Register a simple configuration object.
97+ webservice_configuration = WebServiceTestConfiguration()
98+ sm = getGlobalSiteManager()
99+ sm.registerUtility(webservice_configuration)
100+
101+ # Register IWebServiceVersions for the
102+ # '1.0' and '2.0' web service versions.
103+ alsoProvides(IWebServiceTestRequest10, IWebServiceVersion)
104+ sm.registerUtility(
105+ IWebServiceTestRequest10, IWebServiceVersion, name='1.0')
106+ alsoProvides(IWebServiceTestRequest20, IWebServiceVersion)
107+ sm.registerUtility(
108+ IWebServiceTestRequest20, IWebServiceVersion, name='2.0')
109+
110+ # Register a service root resource
111+ service_root = ServiceRootResource()
112+ sm.registerUtility(service_root, IServiceRootResource)
113+
114+ # Register an IAbsoluteURL adapter for the service root resource.
115+ sm.registerAdapter(
116+ RootResourceAbsoluteURL,
117+ [IServiceRootResource, IWebServiceClientRequest], IAbsoluteURL)
118+
119+ # Build a test module that exposes the given resource interfaces.
120+ testmodule = ModuleType('testmodule')
121+ for interface in self.testmodule_objects:
122+ setattr(testmodule, interface.__name__, interface)
123+ sys.modules['lazr.restful.testmodule'] = testmodule
124+
125+ # Register the test module in the ZCML configuration: adapter
126+ # classes will be built automatically.
127+ xmlconfig.string("""
128+ <configure
129+ xmlns="http://namespaces.zope.org/zope"
130+ xmlns:webservice="http://namespaces.canonical.com/webservice">
131+ <include package="zope.component" file="meta.zcml" />
132+ <include package="zope.security" file="meta.zcml"/>
133+ <include package="lazr.restful" file="meta.zcml" />
134+ <include package="lazr.restful" file="configure.zcml" />
135+
136+ <adapter for="*"
137+ factory="zope.traversing.adapters.DefaultTraversable"
138+ provides="zope.traversing.interfaces.ITraversable" />
139+
140+ <webservice:register module="lazr.restful.testmodule" />
141+ </configure>
142+ """)
143+
144+
145+class IGenericEntry(Interface):
146+ """A simple, reusable entry interface for use in tests.
147+
148+ The entry publishes one field and one named operation.
149+ """
150+ export_as_webservice_entry()
151+
152+ # pylint: disable-msg=E0213
153+ a_field = exported(
154+ TextLine(
155+ title=u'A "field"',
156+ description=u'The only field that can be <> 0 in the entry.'))
157+
158+ @operation_parameters(
159+ message=TextLine(title=u'Message to say'))
160+ @export_read_operation()
161+ def greet(message):
162+ """Print an appropriate greeting based on the message.
163+
164+ :param message: This will be included in the greeting.
165+ """
166+
167+
168+class IGenericCollection(Interface):
169+ """A simple collection containing `IGenericEntry`, for use in tests."""
170+ export_as_webservice_collection(IGenericEntry)
171+
172+ # pylint: disable-msg=E0211
173+ @collection_default_content()
174+ def getAll():
175+ """Returns all the entries."""
176+
177+
178 class DummyRootResource:
179 """A root resource that does nothing."""
180 implements(IServiceRootResource)
181@@ -403,3 +536,5 @@
182 def __str__(self):
183 return "http://dummy"
184 __call__ = __str__
185+
186+
187
188=== modified file 'src/lazr/restful/tests/test_webservice.py'
189--- src/lazr/restful/tests/test_webservice.py 2010-03-03 13:05:13 +0000
190+++ src/lazr/restful/tests/test_webservice.py 2010-03-03 17:07:18 +0000
191@@ -6,18 +6,14 @@
192
193 from cStringIO import StringIO
194 from operator import attrgetter
195-import sys
196-from types import ModuleType
197 import unittest
198
199 from zope.component import getGlobalSiteManager, getUtility
200-from zope.configuration import xmlconfig
201-from zope.interface import alsoProvides, implements, Interface
202+from zope.interface import implements, Interface
203 from zope.publisher.browser import TestRequest
204 from zope.schema import Date, Datetime, TextLine
205 from zope.security.management import (
206 endInteraction, newInteraction, queryInteraction)
207-from zope.testing.cleanup import CleanUp
208 from zope.traversing.browser.interfaces import IAbsoluteURL
209
210 from lazr.restful.fields import Reference
211@@ -25,15 +21,11 @@
212 ICollection, IEntry, IEntryResource, IResourceGETOperation,
213 IServiceRootResource, IWebServiceConfiguration,
214 IWebServiceClientRequest, IWebServiceVersion)
215-from lazr.restful import (
216- EntryResource, ServiceRootResource, ResourceGETOperation)
217-from lazr.restful.simple import BaseWebServiceConfiguration, Request
218-from lazr.restful.declarations import (
219- collection_default_content, exported, export_as_webservice_collection,
220- export_as_webservice_entry, export_read_operation, operation_parameters)
221-from lazr.restful.simple import RootResourceAbsoluteURL
222+from lazr.restful import EntryResource, ResourceGETOperation
223+from lazr.restful.declarations import exported, export_as_webservice_entry
224 from lazr.restful.testing.webservice import (
225- create_web_service_request, WebServiceTestPublication)
226+ create_web_service_request, IGenericCollection, IGenericEntry,
227+ WebServiceTestCase, WebServiceTestPublication)
228 from lazr.restful.testing.tales import test_tales
229 from lazr.restful.utils import (
230 get_current_browser_request, get_current_web_service_request,
231@@ -69,125 +61,10 @@
232 IResourceGETOperation, name=name)
233
234
235-class IGenericEntry(Interface):
236- """A simple, reusable entry interface.
237-
238- This is the description of the entry.
239- """
240- export_as_webservice_entry()
241-
242- # pylint: disable-msg=E0213
243- a_field = exported(
244- TextLine(
245- title=u'A "field"',
246- description=u'The only field that can be <> 0 in the entry.'))
247-
248- @operation_parameters(
249- message=TextLine(title=u'Message to say'))
250- @export_read_operation()
251- def greet(message):
252- """Print an appropriate greeting based on the message.
253-
254- :param message: This will be included in the greeting.
255- """
256-
257-
258-class IGenericCollection(Interface):
259- """A simple collection containing `IGenericEntry`."""
260- export_as_webservice_collection(IGenericEntry)
261-
262- # pylint: disable-msg=E0211
263- @collection_default_content()
264- def getAll():
265- """Returns all the entries."""
266-
267-
268-class IWebServiceRequest10(IWebServiceClientRequest):
269- """A marker interface for requests to the '1.0' web service."""
270-
271-
272-class IWebServiceRequest20(IWebServiceClientRequest):
273- """A marker interface for requests to the '2.0' web service."""
274-
275-
276-class SimpleWebServiceConfiguration(BaseWebServiceConfiguration):
277- implements(IWebServiceConfiguration)
278- show_tracebacks = False
279- active_versions = ['1.0', '2.0']
280- hostname = "webservice_test"
281- last_version_with_mutator_named_operations = None
282-
283- def createRequest(self, body_instream, environ):
284- request = Request(body_instream, environ)
285- request.version = '2.0'
286- alsoProvides(request, IWebServiceRequest20)
287- request.setPublication(WebServiceTestPublication(
288- getUtility(IServiceRootResource)))
289- request.annotations[request.VERSION_ANNOTATION] = '2.0'
290- return request
291-
292-
293-class WebServiceTestCase(CleanUp, unittest.TestCase):
294- """A test case for web service operations."""
295-
296- testmodule_objects = []
297-
298- def setUp(self):
299- """Set the component registry with the given model."""
300- super(WebServiceTestCase, self).setUp()
301-
302- # Register a simple configuration object.
303- webservice_configuration = SimpleWebServiceConfiguration()
304- sm = getGlobalSiteManager()
305- sm.registerUtility(webservice_configuration)
306-
307- # Register IWebServiceVersions for the
308- # '1.0' and '2.0' web service versions.
309- alsoProvides(IWebServiceRequest10, IWebServiceVersion)
310- sm.registerUtility(
311- IWebServiceRequest10, IWebServiceVersion, name='1.0')
312- alsoProvides(IWebServiceRequest20, IWebServiceVersion)
313- sm.registerUtility(
314- IWebServiceRequest20, IWebServiceVersion, name='2.0')
315-
316- # Register a service root resource
317- service_root = ServiceRootResource()
318- sm.registerUtility(service_root, IServiceRootResource)
319-
320- # Register an IAbsoluteURL adapter for the service root resource.
321- sm.registerAdapter(
322- RootResourceAbsoluteURL,
323- [IServiceRootResource, IWebServiceClientRequest], IAbsoluteURL)
324-
325- # Build a test module that exposes the given resource interfaces.
326- testmodule = ModuleType('testmodule')
327- for interface in self.testmodule_objects:
328- setattr(testmodule, interface.__name__, interface)
329- sys.modules['lazr.restful.testmodule'] = testmodule
330-
331- # Register the test module in the ZCML configuration: adapter
332- # classes will be built automatically.
333- xmlconfig.string("""
334- <configure
335- xmlns="http://namespaces.zope.org/zope"
336- xmlns:webservice="http://namespaces.canonical.com/webservice">
337- <include package="zope.component" file="meta.zcml" />
338- <include package="zope.security" file="meta.zcml"/>
339- <include package="lazr.restful" file="meta.zcml" />
340- <include package="lazr.restful" file="configure.zcml" />
341-
342- <adapter for="*"
343- factory="zope.traversing.adapters.DefaultTraversable"
344- provides="zope.traversing.interfaces.ITraversable" />
345-
346- <webservice:register module="lazr.restful.testmodule" />
347- </configure>
348- """)
349-
350-
351 class IHas_getitem(Interface):
352 pass
353
354+
355 class Has_getitem:
356 implements(IHas_getitem)
357 def __getitem__(self, item):
358@@ -320,8 +197,8 @@
359 'entry/wadl_entry:doc', entry=entry).splitlines()
360 self.assertEquals([
361 '<wadl:doc xmlns="http://www.w3.org/1999/xhtml">',
362- '<p>A simple, reusable entry interface.</p>',
363- '<p>This is the description of the entry.</p>',
364+ '<p>A simple, reusable entry interface for use in tests.</p>',
365+ '<p>The entry publishes one field and one named operation.</p>',
366 '',
367 '</wadl:doc>'], doclines)
368
369@@ -339,7 +216,7 @@
370 ).splitlines()
371 self.assertEquals([
372 '<wadl:doc xmlns="http://www.w3.org/1999/xhtml">',
373- 'A simple collection containing IGenericEntry.',
374+ 'A simple collection containing IGenericEntry, for use in tests.',
375 '</wadl:doc>'], doclines)
376
377 def test_field_wadl_doc (self):

Subscribers

People subscribed via source and target branches