Merge lp:~bac/launchpad/lplib-testing into lp:launchpad

Proposed by Brad Crittenden
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~bac/launchpad/lplib-testing
Merge into: lp:launchpad
Prerequisite: lp:~bac/launchpad/bug-569101
Diff against target: 617 lines (+456/-30)
7 files modified
lib/canonical/launchpad/security.py (+5/-0)
lib/canonical/launchpad/testing/systemdocs.py (+8/-3)
lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled (+374/-0)
lib/lp/registry/stories/webservice/xx-project-registry.txt (+18/-0)
lib/lp/registry/tests/test_doc.py (+9/-3)
lib/lp/services/testing/__init__.py (+40/-23)
lib/lp/testing/_webservice.py (+2/-1)
To merge this branch: bzr merge lp:~bac/launchpad/lplib-testing
Reviewer Review Type Date Requested Status
Curtis Hovey (community) Approve
Review via email: mp+24040@code.launchpad.net

Commit message

Changes to support launchpadlib testing, allow milestones to be retrieved by anonymous lplib user.

Description of the change

= Summary =

Attempts to convert a webservice test to a launchpadlib test discovered
many problems that are fixed in this jumbled up branch.

== Proposed fix ==

=== modified file 'lib/canonical/launchpad/security.py'
 * The only production bug of the lot. Allow milestones to be accessed
anonymously via launchpadlib.

=== modified file 'lib/canonical/launchpad/testing/systemdocs.py'
 * Export the new launchpadlib helper methods to doctests instead of
just pagetests.

=== added file
'lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled'
 * New launchpadlib test file. It is disabled because there are
outstanding issues with the test environment and the tests will not pass
as written. I need to file a bug for the issues and put an XXX into the
beginning of this file. The biggest issue is logged in users with
permission are not allowed to see non-public data or make changes.

=== modified file 'lib/lp/services/testing/__init__.py'
 * Refactor to expose the new method 'build_doctest_suite' which is used
to register a subset of all tests. This is used in the registry test
registration.

=== modified file 'lib/lp/testing/_webservice.py'
 * Silly formatting fixes.

=== modified file 'lib/lp/registry/tests/test_doc.py'
 * Allow the discovery of tests in lp/registry/doc/launchpadlib

== Pre-implementation notes ==

Chats with Curtis, Leonard, Gary and Francis.

== Implementation details ==

Nothing interesting.

== Tests ==

Hmm, none really at this time. The test that proves milestones are now
accessible is in the launchpadlib test.

I'll add a web service test to show that milestones are retrievable by
anonymous users.

== Demo and Q/A ==

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/canonical/launchpad/security.py
  lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled
  lib/lp/registry/tests/test_doc.py
  lib/canonical/launchpad/testing/systemdocs.py
  lib/lp/testing/_webservice.py
  lib/lp/services/testing/__init__.py

== Pylint notices ==

lib/canonical/launchpad/testing/systemdocs.py
    6: [W0105] String statement has no effect

lib/lp/services/testing/__init__.py
    31: [W0102, build_doctest_suite] Dangerous default value {} as argument
    60: [W0102, build_test_suite] Dangerous default value {} as argument

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

Here is the missing test: http://pastebin.ubuntu.com/421231/

Revision history for this message
Curtis Hovey (sinzui) wrote :

Thanks for salvaging your branch. The milestone fix and test harness enhancement is much appreciated.

I think you are missing punctuation:
    # Add doctests using default setup/teardown

review: Approve
Revision history for this message
Robert Collins (lifeless) wrote :

I'm just curious, does this then glue lplib to the test instance of
launchpad ? Could this be adapted to let third party authors test
their code with lplib against a local launchpad [started up by the
test environment] ?

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2010-04-20 08:01:41 +0000
+++ lib/canonical/launchpad/security.py 2010-04-25 14:29:32 +0000
@@ -375,6 +375,11 @@
375 usedfor = IDistributionMirror375 usedfor = IDistributionMirror
376376
377377
378class ViewMilestone(AnonymousAuthorization):
379 """Anyone can view an IMilestone."""
380 usedfor = IMilestone
381
382
378class EditSpecificationBranch(AuthorizationBase):383class EditSpecificationBranch(AuthorizationBase):
379384
380 usedfor = ISpecificationBranch385 usedfor = ISpecificationBranch
381386
=== modified file 'lib/canonical/launchpad/testing/systemdocs.py'
--- lib/canonical/launchpad/testing/systemdocs.py 2010-03-24 10:15:57 +0000
+++ lib/canonical/launchpad/testing/systemdocs.py 2010-04-25 14:29:32 +0000
@@ -1,10 +1,10 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Infrastructure for setting up doctests."""
5
4from __future__ import with_statement6from __future__ import with_statement
57
6"""Infrastructure for setting up doctests."""
7
8__metaclass__ = type8__metaclass__ = type
9__all__ = [9__all__ = [
10 'default_optionflags',10 'default_optionflags',
@@ -34,7 +34,9 @@
34from canonical.launchpad.interfaces import ILaunchBag34from canonical.launchpad.interfaces import ILaunchBag
35from canonical.launchpad.webapp.testing import verifyObject35from canonical.launchpad.webapp.testing import verifyObject
36from canonical.testing import reset_logging36from canonical.testing import reset_logging
37from lp.testing import ANONYMOUS, login, login_person, logout37from lp.testing import (
38 ANONYMOUS, launchpadlib_credentials_for, launchpadlib_for, login,
39 login_person, logout, oauth_access_token_for)
38from lp.testing.factory import LaunchpadObjectFactory40from lp.testing.factory import LaunchpadObjectFactory
39from lp.testing.views import create_view, create_initialized_view41from lp.testing.views import create_view, create_initialized_view
4042
@@ -202,6 +204,9 @@
202 test.globs['pretty'] = pprint.PrettyPrinter(width=1).pformat204 test.globs['pretty'] = pprint.PrettyPrinter(width=1).pformat
203 test.globs['stop'] = stop205 test.globs['stop'] = stop
204 test.globs['with_statement'] = with_statement206 test.globs['with_statement'] = with_statement
207 test.globs['launchpadlib_for'] = launchpadlib_for
208 test.globs['launchpadlib_credentials_for'] = launchpadlib_credentials_for
209 test.globs['oauth_access_token_for'] = oauth_access_token_for
205210
206211
207def setUp(test):212def setUp(test):
208213
=== added directory 'lib/lp/registry/doc/launchpadlib'
=== added file 'lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled'
--- lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled 2010-04-25 14:29:32 +0000
@@ -0,0 +1,374 @@
1# XXX bug=569189, bac 2010-04-23
2# This test is disabled due to a bug which prevents authenticated
3# users from writing data or reading any data with a permission
4# attached.
5
6==============
7Project Groups
8==============
9
10
11Project group collection
12------------------------
13
14It is possible to get a batched list of all the project groups.
15
16 >>> from launchpadlib.launchpad import Launchpad
17 >>> from lp.testing._login import logout
18 >>> logout()
19 >>> lp_anon = Launchpad.login_anonymously('launchpadlib test',
20 ... 'http://api.launchpad.dev/')
21 >>> len(lp_anon.project_groups)
22 7
23
24 >>> project_groups = sorted(lp_anon.project_groups, key=lambda X: X.name)
25 >>> project_groups[0].self_link
26 u'http://.../aaa'
27
28 >>> for project_group in project_groups:
29 ... print "%s (%s)" % (project_group.name, project_group.display_name)
30 aaa (the Test Project)
31 apache (Apache)
32 gimp (the GiMP Project)
33 gnome (GNOME)
34 iso-codes-project (iso-codes)
35 launchpad-mirrors (Launchpad SCM Mirrors)
36 mozilla (the Mozilla Project)
37
38It's possible to search the list and get a subset of the project groups.
39
40 >>> lp_anon = Launchpad.login_anonymously('launchpadlib test',
41 ... 'http://api.launchpad.dev/')
42 >>> project_groups = lp_anon.project_groups.search(text="Apache")
43 >>> for project_group in project_groups:
44 ... print project_group.display_name
45 Apache
46
47Searching without providing a search string is the same as getting all
48the project groups.
49
50 >>> project_groups = lp_anon.project_groups.search()
51 >>> project_groups = sorted(project_groups, key=lambda X: X.name)
52 >>> for project_group in project_groups:
53 ... print "%s (%s)" % (project_group.name, project_group.display_name)
54 aaa (the Test Project)
55 apache (Apache)
56 gimp (the GiMP Project)
57 gnome (GNOME)
58 iso-codes-project (iso-codes)
59 launchpad-mirrors (Launchpad SCM Mirrors)
60 mozilla (the Mozilla Project)
61
62Project group object
63--------------------
64
65An individual project group can be accessed using dictionary-like syntax.
66
67 >>> mozilla = lp_anon.project_groups['mozilla']
68
69A project group supplies many attributes, collections, operations.
70
71 >>> from operator import attrgetter
72 >>> def pprint_object(obj):
73 ... groups = ['lp_attributes',
74 ... 'lp_collections',
75 ... 'lp_entries']
76 ... items = []
77 ... for group in groups:
78 ... items.extend(attrgetter(group)(obj))
79 ... items.remove('http_etag')
80 ... for item in sorted(items):
81 ... value = attrgetter(item)(obj)
82 ... print "%s: %s" % (item, value)
83
84The project group object has a large set of properties that can be
85accessed directly.
86
87 >>> pprint_object(mozilla)
88 active: True
89 active_milestones: <lazr.restfulclient.resource.Collection object...>
90 all_milestones: <lazr.restfulclient.resource.Collection object ...>
91 bug_reporting_guidelines: None
92 bug_tracker: None
93 date_created: 2004-09-24 20:58:02.177698+00:00
94 description: The Mozilla Project produces several internet applications ...
95 display_name: the Mozilla Project
96 driver: None
97 freshmeat_project: None
98 homepage_content: None
99 homepage_url: http://www.mozilla.org/
100 icon: <lazr.restfulclient.resource.HostedFile object ...>
101 logo: <lazr.restfulclient.resource.HostedFile object ...>
102 mugshot: <lazr.restfulclient.resource.HostedFile object ...>
103 name: mozilla
104 official_bug_tags: []
105 owner: http://.../~name12
106 projects: <lazr.restfulclient.resource.Collection object ...>
107 registrant: http://.../~name12
108 resource_type_link: http://.../#project_group
109 reviewed: False
110 self_link: http://.../mozilla
111 sourceforge_project: None
112 summary: The Mozilla Project is the largest open source web browser...
113 title: The Mozilla Project
114 wiki_url: None
115
116The milestones can be accessed through the active_milestones
117collection and the all_milestones collection.
118
119 >>> def print_collection(collection, attrs=None):
120 ... items = [str(item) for item in collection]
121 ... for item in sorted(items):
122 ... print item
123
124 >>> print_collection(sorted(mozilla.active_milestones))
125 http://api.launchpad.dev/.../mozilla/+milestone/1.0
126
127 >>> print_collection(sorted(mozilla.all_milestones))
128 http://.../mozilla/+milestone/0.8
129 http://.../mozilla/+milestone/0.9
130 http://.../mozilla/+milestone/0.9.1
131 http://.../mozilla/+milestone/0.9.2
132 http://.../mozilla/+milestone/1.0
133 http://.../mozilla/+milestone/1.0.0
134
135
136An individual milestone can be retrieved. None is returned if it
137doesn't exist.
138
139 >>> print mozilla.getMilestone(name="1.0")
140 http://.../mozilla/+milestone/1.0
141
142 >>> print mozilla.getMilestone(name="fnord")
143 None
144
145
146Project objects
147---------------
148
149The Launchpad 'projects' collection is used to select an individual
150project, which has a large number of attributes. Some of the
151attributes are marked as 'redacted' as they are only visible to
152project administrators.
153
154 >>> firefox = lp_anon.projects['firefox']
155 >>> pprint_object(firefox)
156 active: True
157 active_milestones: <lazr.restfulclient.resource.Collection object...>
158 all_milestones: <lazr.restfulclient.resource.Collection object...>
159 brand: <lazr.restfulclient.resource.HostedFile object...>
160 bug_reporting_guidelines: None
161 bug_tracker: None
162 commercial_subscription: None
163 commercial_subscription_is_due: True
164 date_created: 2004-09-24 20:58:02.185708+00:00
165 description: The Mozilla Firefox web browser
166 development_focus: http://.../firefox/trunk
167 display_name: Mozilla Firefox
168 download_url: None
169 driver: None
170 freshmeat_project: None
171 homepage_url: None
172 icon: <lazr.restfulclient.resource.HostedFile object...>
173 is_permitted: ...redacted
174 license_approved: ...redacted
175 license_info: None
176 license_reviewed: ...redacted
177 licenses: []
178 logo: <lazr.restfulclient.resource.HostedFile object...>
179 name: firefox
180 official_bug_tags: []
181 owner: http://.../~name12
182 programming_language: None
183 project_group: http://.../mozilla
184 qualifies_for_free_hosting: False
185 registrant: http://.../~name12
186 releases: <lazr.restfulclient.resource.Collection object...>
187 remote_product: None
188 resource_type_link: http://.../#project
189 reviewer_whiteboard: ...redacted
190 screenshots_url: None
191 self_link: http://.../firefox
192 series: <lazr.restfulclient.resource.Collection object...>
193 sourceforge_project: None
194 summary: The Mozilla Firefox web browser
195 title: Mozilla Firefox
196 translation_focus: None
197 wiki_url: None
198
199Getting a Launchpad object based on an administrator's credentials
200allows the previously redacted attributes to be seen.
201
202 >>> lp_mark = launchpadlib_for(
203 ... 'launchpadlib test', 'mark', 'READ_PRIVATE')
204 >>> print lp_mark.me.name
205 mark
206 >>> firefox = lp_mark.projects['firefox']
207 >>> print firefox.license_reviewed
208 False
209
210In Launchpad project names may not have uppercase letters in their
211name. As a convenience, requests for projects using the wrong case
212return the correct project.
213
214 >>> firefox = lp_anon.projects['Firefox']
215 >>> print firefox.title
216 Mozilla Firefox
217
218The milestones can be accessed through the active_milestones
219collection and the all_milestones collection.
220
221 >>> # This should not be needed but using the object fetched above causes a
222 >>> # 301-Moved Permanently exception.
223 >>> firefox = lp_anon.projects['firefox']
224 >>> print_collection(sorted(firefox.active_milestones))
225 http://api.launchpad.dev/.../firefox/+milestone/1.0
226
227 >>> print_collection(sorted(firefox.all_milestones))
228 http://.../firefox/+milestone/0.9
229 http://.../firefox/+milestone/0.9.1
230 http://.../firefox/+milestone/0.9.2
231 http://.../firefox/+milestone/1.0
232 http://.../firefox/+milestone/1.0.0
233
234An individual milestone can be retrieved. None is returned if it
235doesn't exist.
236
237 >>> print firefox.getMilestone(name="1.0")
238 http://.../firefox/+milestone/1.0
239
240 >>> print firefox.getMilestone(name="fnord")
241 None
242
243A list of series can be accessed through the series_collection_link.
244
245 >>> print_collection(firefox.series)
246 http://.../firefox/1.0
247 http://.../firefox/trunk
248
249"getSeries" returns the series for the given name.
250
251 >>> series = firefox.getSeries(name="1.0")
252 >>> print series.self_link
253 http://.../firefox/1.0
254
255A list of releases can be accessed through the releases_collection_link.
256
257 >>> print_collection(firefox.releases)
258 http://.../firefox/1.0/1.0.0
259 http://.../firefox/trunk/0.9
260 http://.../firefox/trunk/0.9.1
261 http://.../firefox/trunk/0.9.2
262
263"getRelease" returns the release for the given version.
264
265 >>> release = firefox.getRelease(version="0.9.1")
266 >>> print release.self_link
267 http://.../firefox/trunk/0.9.1
268
269The development focus series can be accessed through the
270development_focus attribute.
271
272 >>> print firefox.development_focus.self_link
273 http://.../firefox/trunk
274
275Attributes can be edited, but not by the anonymous user.
276
277 >>> mark = lp_anon.people['mark']
278 >>> firefox.driver = mark
279 >>> firefox.lp_save()
280 Traceback (most recent call last):
281 ...
282 HTTPError: HTTP Error 401: Unauthorized...
283
284A project administrator can modify attributes on the project.
285
286 >>> mark = lp_mark.people['mark']
287 >>> firefox = lp_mark.projects['firefox']
288 >>> firefox.driver = mark
289 >>> firefox.homepage_url = 'http://sf.net/firefox'
290 >>> firefox.lp_save()
291
292 >>> print firefox.driver.self_link
293 http://.../~mark
294 >>> print firefox.homepage_url
295 http://sf.net/firefox
296
297Changing the owner of a project can change other attributes as well.
298
299 >>> # Create a product with a series and release.
300 >>> login('test@canonical.com')
301 >>> test_project_owner = factory.makePerson(name='test-project-owner')
302 >>> test_project = factory.makeProduct(
303 ... name='test-project', owner=test_project_owner)
304 >>> test_series = factory.makeProductSeries(
305 ... product=test_project, name='test-series',
306 ... owner=test_project_owner)
307 >>> test_milestone = factory.makeMilestone(
308 ... product=test_project, name='test-milestone',
309 ... productseries=test_series)
310 >>> test_project_release = factory.makeProductRelease(
311 ... product=test_project, milestone=test_milestone)
312 >>> logout()
313
314 >>> nopriv = lp_mark.people['no-priv']
315 >>> test_project = lp_mark.projects['test-project']
316 >>> test_project.owner = nopriv
317 >>> test_project.lp_save()
318 >>> print test_project.owner.self_link
319 http://.../~name12
320 >>> test_series = test_project.getSeries(name="test-series")
321 >>> print test_series.owner.self_link
322 http://.../~name12
323
324 >>> release = test_project.getMilestone(name='test-milestone')
325 >>> print release.owner.self_link
326 http://.../~name12
327
328Read-only attributes cannot be changed.
329
330 >>> firefox.registrant = nopriv
331 >>> firefox.lp_save()
332 Traceback (most recent call last):
333 ...
334 HTTPError: HTTP Error 400: Bad Request
335 ...
336 registrant_link: You tried to modify a read-only attribute...
337
338"get_timeline" returns a list of dictionaries, corresponding to each
339milestone and release.
340
341 >>> print pretty(firefox.get_timeline())
342 [{u'is_development_focus': False,
343 u'landmarks': [{u'code_name': u'First Stable Release',
344 u'date': u'2004-06-28',
345 u'name': u'1.0.0',
346 u'type': u'release',
347 u'uri': u'/firefox/1.0/1.0.0'}],
348 u'name': u'1.0',
349 u'status': u'Active Development',
350 u'uri': u'/firefox/1.0'},
351 {u'is_development_focus': True,
352 u'landmarks': [{u'code_name': None,
353 u'date': u'2056-10-16',
354 u'name': u'1.0',
355 u'type': u'milestone',
356 u'uri': u'/firefox/+milestone/1.0'},
357 {u'code_name': u'One (secure) Tree Hill',
358 u'date': u'2004-10-15',
359 u'name': u'0.9.2',
360 u'type': u'release',
361 u'uri': u'/firefox/trunk/0.9.2'},
362 {u'code_name': u'One Tree Hill (v2)',
363 u'date': u'2004-10-15',
364 u'name': u'0.9.1',
365 u'type': u'release',
366 u'uri': u'/firefox/trunk/0.9.1'},
367 {u'code_name': u'One Tree Hill',
368 u'date': u'2004-10-15',
369 u'name': u'0.9',
370 u'type': u'release',
371 u'uri': u'/firefox/trunk/0.9'}],
372 u'name': u'trunk',
373 u'status': u'Active Development',
374 u'uri': u'/firefox/trunk'}]
0375
=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
--- lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-04-23 03:02:31 +0000
+++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-04-25 14:29:32 +0000
@@ -108,6 +108,24 @@
108 http://.../mozilla/+milestone/0.9.2108 http://.../mozilla/+milestone/0.9.2
109 http://.../mozilla/+milestone/1.0.0109 http://.../mozilla/+milestone/1.0.0
110110
111The milestones can also be accessed anonymously.
112
113 >>> response = anon_webservice.get(
114 ... mozilla['active_milestones_collection_link'])
115 >>> active_milestones = response.jsonBody()
116 >>> print_self_link_of_entries(active_milestones)
117 http://.../mozilla/+milestone/1.0
118
119 >>> response = anon_webservice.get(
120 ... mozilla['all_milestones_collection_link'])
121 >>> all_milestones = response.jsonBody()
122 >>> print_self_link_of_entries(all_milestones)
123 http://.../mozilla/+milestone/0.8
124 http://.../mozilla/+milestone/0.9
125 http://.../mozilla/+milestone/0.9.1
126 http://.../mozilla/+milestone/0.9.2
127 http://.../mozilla/+milestone/1.0.0
128
111"getMilestone" returns a milestone for the given name, or None if there129"getMilestone" returns a milestone for the given name, or None if there
112is no milestone for the given name.130is no milestone for the given name.
113131
114132
=== modified file 'lib/lp/registry/tests/test_doc.py'
--- lib/lp/registry/tests/test_doc.py 2009-10-01 14:48:09 +0000
+++ lib/lp/registry/tests/test_doc.py 2010-04-25 14:29:32 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""4"""
@@ -16,7 +16,7 @@
16 LaunchpadZopelessLayer)16 LaunchpadZopelessLayer)
1717
18from lp.registry.tests import mailinglists_helper18from lp.registry.tests import mailinglists_helper
19from lp.services.testing import build_test_suite19from lp.services.testing import build_doctest_suite, build_test_suite
2020
2121
22here = os.path.dirname(os.path.realpath(__file__))22here = os.path.dirname(os.path.realpath(__file__))
@@ -28,6 +28,7 @@
28 DatabaseLayer.force_dirty_database()28 DatabaseLayer.force_dirty_database()
29 tearDown(test)29 tearDown(test)
3030
31
31def mailingListXMLRPCInternalSetUp(test):32def mailingListXMLRPCInternalSetUp(test):
32 setUp(test)33 setUp(test)
33 # Use the direct API view instance, not retrieved through the component34 # Use the direct API view instance, not retrieved through the component
@@ -182,4 +183,9 @@
182183
183184
184def test_suite():185def test_suite():
185 return build_test_suite(here, special, layer=DatabaseFunctionalLayer)186 suite = build_test_suite(here, special, layer=DatabaseFunctionalLayer)
187 launchpadlib_path = os.path.join(os.path.pardir, 'doc', 'launchpadlib')
188 lplib_suite = build_doctest_suite(here, launchpadlib_path,
189 layer=DatabaseFunctionalLayer)
190 suite.addTest(lplib_suite)
191 return suite
186192
=== modified file 'lib/lp/services/testing/__init__.py'
--- lib/lp/services/testing/__init__.py 2010-01-12 21:03:16 +0000
+++ lib/lp/services/testing/__init__.py 2010-04-25 14:29:32 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""4"""
@@ -10,6 +10,12 @@
10 doc/ - Contains doctests10 doc/ - Contains doctests
11"""11"""
1212
13__metaclass__ = type
14__all__ = [
15 'build_doctest_suite',
16 'build_test_suite',
17 ]
18
13import logging19import logging
14import os20import os
15import unittest21import unittest
@@ -20,14 +26,43 @@
20from canonical.launchpad.testing.systemdocs import (26from canonical.launchpad.testing.systemdocs import (
21 LayeredDocFileSuite, setUp, tearDown)27 LayeredDocFileSuite, setUp, tearDown)
22from canonical.testing import DatabaseFunctionalLayer28from canonical.testing import DatabaseFunctionalLayer
23from canonical.launchpad.testing.systemdocs import strip_prefix29
30
31def build_doctest_suite(base_dir, tests_path, special_tests={},
32 layer=DatabaseFunctionalLayer,
33 setUp=setUp, tearDown=tearDown,
34 package=None):
35 """Build the doc test suite."""
36 suite = unittest.TestSuite()
37 # Tests are run relative to the calling module, not this module.
38 if package is None:
39 package = doctest._normalize_module(None)
40 testsdir = os.path.abspath(
41 os.path.normpath(os.path.join(base_dir, tests_path)))
42
43 if os.path.exists(testsdir):
44 # Add doctests using default setup/teardown.
45 filenames = [filename
46 for filename in os.listdir(testsdir)
47 if (filename.endswith('.txt')
48 and filename not in special_tests)]
49 # Sort the list to give a predictable order.
50 filenames.sort()
51 for filename in filenames:
52 path = os.path.join(tests_path, filename)
53 one_test = LayeredDocFileSuite(
54 path, package=package, setUp=setUp, tearDown=tearDown,
55 layer=layer, stdout_logging_level=logging.WARNING)
56 suite.addTest(one_test)
57 return suite
58
2459
25def build_test_suite(base_dir, special_tests={},60def build_test_suite(base_dir, special_tests={},
26 layer=DatabaseFunctionalLayer,61 layer=DatabaseFunctionalLayer,
27 setUp=setUp, tearDown=tearDown):62 setUp=setUp, tearDown=tearDown):
28 """Build a test suite from a directory containing test files.63 """Build a test suite from a directory containing test files.
2964
30 The parent's 'stories' subdirectory will be checked for pagetests and 65 The parent's 'stories' subdirectory will be checked for pagetests and
31 the parent's 'doc' subdirectory will be checked for doctests.66 the parent's 'doc' subdirectory will be checked for doctests.
3267
33 :param base_dir: The tests subdirectory that.68 :param base_dir: The tests subdirectory that.
@@ -63,24 +98,6 @@
63 suite.addTest(special_suite)98 suite.addTest(special_suite)
6499
65 tests_path = os.path.join(os.path.pardir, 'doc')100 tests_path = os.path.join(os.path.pardir, 'doc')
66 testsdir = os.path.abspath(101 suite.addTest(build_doctest_suite(base_dir, tests_path, special_tests,
67 os.path.normpath(os.path.join(base_dir, tests_path))102 layer, setUp, tearDown, package))
68 )
69
70 if os.path.exists(testsdir):
71 # Add doctests using default setup/teardown
72 filenames = [filename
73 for filename in os.listdir(testsdir)
74 if (filename.endswith('.txt')
75 and filename not in special_tests)]
76 # Sort the list to give a predictable order.
77 filenames.sort()
78 for filename in filenames:
79 path = os.path.join(tests_path, filename)
80 one_test = LayeredDocFileSuite(
81 path, package=package, setUp=setUp, tearDown=tearDown,
82 layer=layer, stdout_logging_level=logging.WARNING
83 )
84 suite.addTest(one_test)
85
86 return suite103 return suite
87104
=== modified file 'lib/lp/testing/_webservice.py'
--- lib/lp/testing/_webservice.py 2010-04-15 20:37:59 +0000
+++ lib/lp/testing/_webservice.py 2010-04-25 14:29:32 +0000
@@ -8,7 +8,7 @@
8__all__ = [8__all__ = [
9 'launchpadlib_credentials_for',9 'launchpadlib_credentials_for',
10 'launchpadlib_for',10 'launchpadlib_for',
11 'oauth_access_token_for'11 'oauth_access_token_for',
12 ]12 ]
1313
14from zope.component import getUtility14from zope.component import getUtility
@@ -22,6 +22,7 @@
2222
23from lp.testing._login import login, logout23from lp.testing._login import login, logout
2424
25
25def oauth_access_token_for(consumer_name, person, permission, context=None):26def oauth_access_token_for(consumer_name, person, permission, context=None):
26 """Find or create an OAuth access token for the given person.27 """Find or create an OAuth access token for the given person.
27 :param consumer_name: An OAuth consumer name.28 :param consumer_name: An OAuth consumer name.