Merge lp:~flacoste/launchpad/bug-801233 into lp:launchpad
- bug-801233
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Francis J. Lacoste | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 13501 | ||||
Proposed branch: | lp:~flacoste/launchpad/bug-801233 | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
1343 lines (+617/-83) 25 files modified
lib/canonical/launchpad/configure.zcml (+1/-0) lib/lp/app/browser/launchpad.py (+5/-1) lib/lp/buildmaster/configure.zcml (+1/-0) lib/lp/buildmaster/interfaces/builder.py (+36/-19) lib/lp/buildmaster/model/builder.py (+13/-10) lib/lp/buildmaster/security.py (+19/-0) lib/lp/buildmaster/tests/test_webservice.py (+68/-0) lib/lp/services/webservice/configure.zcml (+15/-0) lib/lp/services/webservice/json.py (+24/-0) lib/lp/services/webservice/tests/test_json.py (+30/-0) lib/lp/services/webservice/wadl-to-refhtml.xsl (+126/-6) lib/lp/soyuz/browser/configure.zcml (+7/-5) lib/lp/soyuz/browser/processor.py (+8/-14) lib/lp/soyuz/browser/tests/test_processor.py (+41/-0) lib/lp/soyuz/configure.zcml (+7/-0) lib/lp/soyuz/interfaces/processor.py (+34/-4) lib/lp/soyuz/interfaces/webservice.py (+2/-6) lib/lp/soyuz/model/processor.py (+21/-0) lib/lp/soyuz/security.py (+26/-0) lib/lp/soyuz/tests/test_packagecloner.py (+1/-4) lib/lp/soyuz/tests/test_processor.py (+90/-7) lib/lp/testing/__init__.py (+11/-0) lib/lp/testing/factory.py (+6/-3) lib/lp/testing/publication.py (+11/-3) lib/lp/testing/tests/test_publication.py (+14/-1) |
||||
To merge this branch: | bzr merge lp:~flacoste/launchpad/bug-801233 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brad Crittenden (community) | code | Approve | |
Review via email:
|
Commit message
[r=bac][bug=801233] Export IBuilderSet.
Description of the change
= Summary =
This is a massive Yak shaving branch! The goal is to write an API script which
will allow copy rebuilds to be manually allocated a number of builders. To do
this, we need to find dynamically the number of available builders for a
particular processor. That information isn't currently available over the
API.
== Proposed fix ==
It exports IBuilder.processor, IBuilderSet.
IBuilderSet.
shaving along the way. An important one worth mentioning here: I changed the
URL of IProcessor. They were living under their family and their DB id was
part of th URL. Since IProcessor name are globally unique, I added a
/+processors collection and their URL point their using their name. That will
make it easier to get processor by crafting the URL.
== Pre-implementation notes ==
No pre-implementation done.
== Implementation details ==
Here are the numerous yak shaved along the way:
* Removed getBuildersByArch() which wasn't used anywhere.
* getBuildQueueSize() returns a dict containing datetime.timedelta object.
simplejson didn't know how to serialize those, so I hooked up a very
simple one which simply str() it. That was added into
lp.services.
* Collections check that launchpad.View is available on the entry. So I had
to define adapters for IBuilder, IProcessor and IProcessorFamily.
* Since there is no web page for IProcessor nor IProcessorFamily, I changed
pulish_web_link to False.
Everything else should be straightforward, let me know if you have any
questions.
== Tests ==
./bin/test -vvt
'lp.soyuz.
== Demo and Q/A ==
This will be QA-ed by checking the exported attribute and methods on
qastaging.
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/canonical
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
./lib/lp/
174: Line exceeds 78 characters.
261: Line exceeds 78 characters.
219: W291 trailing whitespace
284: E301 expected 1 blank line, found 0
302: E301 expected 1 blank line, found 0
373: E301 expected 1 blank line, found 0
517: E301 expected 1 blank line, found 0
519: E301 expected 1 blank line, found 0
608: E301 expected 1 blank line, found 0
683: E301 expected 1 blank line, found 0
686: E301 expected 1 blank line, found 0
./lib/lp/
48: Line exceeds 78 characters.
55: Line exceeds 78 characters.
57: Line exceeds 78 characters.
123: Line exceeds 78 characters.
88: E202 whitespace before ')'
123: E501 line too long (123 characters)
123: E202 whitespace before ')'
./lib/lp/
87: E301 expected 1 blank line, found 0
./lib/lp/
18: E302 expected 2 blank lines, found 1
22: E302 expected 2 blank lines, found 1
./lib/lp/
36: E302 expected 2 blank lines, found 1
./lib/lp/
27: E303 too many blank lines (2)
30: W291 trailing whitespace
33: W391 blank line at end of file
./lib/lp/
49: E301 expected 1 blank line, found 0
58: W291 trailing whitespace
79: E301 expected 1 blank line, found 0
101: E301 expected 1 blank line, found 0
./lib/lp/
139: Line exceeds 78 characters.
210: Line exceeds 78 characters.
249: W293 blank line contains whitespace
256: W293 blank line contains whitespace
./lib/lp/
13: E302 expected 2 blank lines, found 1
41: W391 blank line at end of file
I'll fix the white-space lint post-review.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francis J. Lacoste (flacoste) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Brad Crittenden (bac) wrote : | # |
Hi Francis,
This is a really good follow up branch to the changes I made. Thanks for making the processor traversal by name.
I only found a few things to note:
* As we discussed on IRC the docstring for getBuildQueueSizes make the wadl generator unhappy.
* In lib/lp/
* In lib/lp/
* Some of the lint is real, including new code with super long lines such as in test_default_
* in ProcessorSet getAll and getByName why don't you use the storeOf(self)?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francis J. Lacoste (flacoste) wrote : | # |
On 11-07-11 03:43 PM, Brad Crittenden wrote:
> Review: Approve code
> Hi Francis,
>
> This is a really good follow up branch to the changes I made. Thanks for making the processor traversal by name.
>
> I only found a few things to note:
>
> * As we discussed on IRC the docstring for getBuildQueueSizes make the wadl generator unhappy.
Yeah, this was a simple matter of using the rst literal block directive
to make it happy. (::)
This introduces a few other drive-bys:
* Turned out that our WADL to HTML conversion excludes the RST
parameters table (because we don't want the :param name: documentation
to be redundant or misleading with the web-service specific
documentation.) So I added support for the :return: (and :raise:)
parameters for custom methods (when appropriate). This will make the
return value of a couple other JSON-returning method documentated.
* Got bored and tired of seeing so many Unkonwn URL entry messages, so I
fixed a bunch. Not all of them are fixed. Will eventually chase the
others, but it's still an improvement!
>
> * In lib/lp/
Done.
>
> * In lib/lp/
>
Done.
> * Some of the lint is real, including new code with super long lines such as in test_default_
Branch is now lint-free!
>
> * in ProcessorSet getAll and getByName why don't you use the storeOf(self)?
Because self isn't a DB instance (and thus doesn't have a store).
(That's usually the case in *Set object.)
Thanks for the review!
--
Francis J. Lacoste
<email address hidden>
Preview Diff
1 | === modified file 'lib/canonical/launchpad/configure.zcml' | |||
2 | --- lib/canonical/launchpad/configure.zcml 2011-02-23 21:15:23 +0000 | |||
3 | +++ lib/canonical/launchpad/configure.zcml 2011-07-21 19:34:45 +0000 | |||
4 | @@ -27,6 +27,7 @@ | |||
5 | 27 | <include package="lp.coop.answersbugs" /> | 27 | <include package="lp.coop.answersbugs" /> |
6 | 28 | <include package="lp.code" /> | 28 | <include package="lp.code" /> |
7 | 29 | <include package="lp.soyuz" /> | 29 | <include package="lp.soyuz" /> |
8 | 30 | <include package="lp.services.webservice" /> | ||
9 | 30 | <include package="lp.translations" /> | 31 | <include package="lp.translations" /> |
10 | 31 | <include package="lp.testopenid" /> | 32 | <include package="lp.testopenid" /> |
11 | 32 | <include package="lp.blueprints" /> | 33 | <include package="lp.blueprints" /> |
12 | 33 | 34 | ||
13 | === modified file 'lib/lp/app/browser/launchpad.py' | |||
14 | --- lib/lp/app/browser/launchpad.py 2011-06-30 11:28:59 +0000 | |||
15 | +++ lib/lp/app/browser/launchpad.py 2011-07-21 19:34:45 +0000 | |||
16 | @@ -146,7 +146,10 @@ | |||
17 | 146 | from lp.services.worlddata.interfaces.language import ILanguageSet | 146 | from lp.services.worlddata.interfaces.language import ILanguageSet |
18 | 147 | from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet | 147 | from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet |
19 | 148 | from lp.soyuz.interfaces.packageset import IPackagesetSet | 148 | from lp.soyuz.interfaces.packageset import IPackagesetSet |
21 | 149 | from lp.soyuz.interfaces.processor import IProcessorFamilySet | 149 | from lp.soyuz.interfaces.processor import ( |
22 | 150 | IProcessorFamilySet, | ||
23 | 151 | IProcessorSet, | ||
24 | 152 | ) | ||
25 | 150 | from lp.testopenid.interfaces.server import ITestOpenIDApplication | 153 | from lp.testopenid.interfaces.server import ITestOpenIDApplication |
26 | 151 | from lp.translations.interfaces.translationgroup import ITranslationGroupSet | 154 | from lp.translations.interfaces.translationgroup import ITranslationGroupSet |
27 | 152 | from lp.translations.interfaces.translationimportqueue import ( | 155 | from lp.translations.interfaces.translationimportqueue import ( |
28 | @@ -617,6 +620,7 @@ | |||
29 | 617 | 'people': IPersonSet, | 620 | 'people': IPersonSet, |
30 | 618 | 'pillars': IPillarNameSet, | 621 | 'pillars': IPillarNameSet, |
31 | 619 | '+processor-families': IProcessorFamilySet, | 622 | '+processor-families': IProcessorFamilySet, |
32 | 623 | '+processors': IProcessorSet, | ||
33 | 620 | 'projects': IProductSet, | 624 | 'projects': IProductSet, |
34 | 621 | 'projectgroups': IProjectGroupSet, | 625 | 'projectgroups': IProjectGroupSet, |
35 | 622 | 'sourcepackagenames': ISourcePackageNameSet, | 626 | 'sourcepackagenames': ISourcePackageNameSet, |
36 | 623 | 627 | ||
37 | === modified file 'lib/lp/buildmaster/configure.zcml' | |||
38 | --- lib/lp/buildmaster/configure.zcml 2010-11-19 13:25:25 +0000 | |||
39 | +++ lib/lp/buildmaster/configure.zcml 2011-07-21 19:34:45 +0000 | |||
40 | @@ -10,6 +10,7 @@ | |||
41 | 10 | xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc" | 10 | xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc" |
42 | 11 | i18n_domain="launchpad"> | 11 | i18n_domain="launchpad"> |
43 | 12 | <include package=".browser"/> | 12 | <include package=".browser"/> |
44 | 13 | <authorizations module=".security" /> | ||
45 | 13 | 14 | ||
46 | 14 | <!-- Builder --> | 15 | <!-- Builder --> |
47 | 15 | <class | 16 | <class |
48 | 16 | 17 | ||
49 | === modified file 'lib/lp/buildmaster/interfaces/builder.py' | |||
50 | --- lib/lp/buildmaster/interfaces/builder.py 2011-02-24 15:30:54 +0000 | |||
51 | +++ lib/lp/buildmaster/interfaces/builder.py 2011-07-21 19:34:45 +0000 | |||
52 | @@ -27,6 +27,12 @@ | |||
53 | 27 | exported, | 27 | exported, |
54 | 28 | operation_parameters, | 28 | operation_parameters, |
55 | 29 | operation_returns_entry, | 29 | operation_returns_entry, |
56 | 30 | operation_returns_collection_of, | ||
57 | 31 | operation_for_version, | ||
58 | 32 | ) | ||
59 | 33 | from lazr.restful.fields import ( | ||
60 | 34 | Reference, | ||
61 | 35 | ReferenceChoice, | ||
62 | 30 | ) | 36 | ) |
63 | 31 | from zope.interface import ( | 37 | from zope.interface import ( |
64 | 32 | Attribute, | 38 | Attribute, |
65 | @@ -34,7 +40,6 @@ | |||
66 | 34 | ) | 40 | ) |
67 | 35 | from zope.schema import ( | 41 | from zope.schema import ( |
68 | 36 | Bool, | 42 | Bool, |
69 | 37 | Choice, | ||
70 | 38 | Field, | 43 | Field, |
71 | 39 | Int, | 44 | Int, |
72 | 40 | Text, | 45 | Text, |
73 | @@ -45,6 +50,7 @@ | |||
74 | 45 | from lp.app.validators.name import name_validator | 50 | from lp.app.validators.name import name_validator |
75 | 46 | from lp.app.validators.url import builder_url_validator | 51 | from lp.app.validators.url import builder_url_validator |
76 | 47 | from lp.registry.interfaces.role import IHasOwner | 52 | from lp.registry.interfaces.role import IHasOwner |
77 | 53 | from lp.soyuz.interfaces.processor import IProcessor | ||
78 | 48 | from lp.services.fields import ( | 54 | from lp.services.fields import ( |
79 | 49 | Description, | 55 | Description, |
80 | 50 | PersonChoice, | 56 | PersonChoice, |
81 | @@ -106,10 +112,12 @@ | |||
82 | 106 | 112 | ||
83 | 107 | id = Attribute("Builder identifier") | 113 | id = Attribute("Builder identifier") |
84 | 108 | 114 | ||
86 | 109 | processor = Choice( | 115 | processor = exported(ReferenceChoice( |
87 | 110 | title=_('Processor'), required=True, vocabulary='Processor', | 116 | title=_('Processor'), required=True, vocabulary='Processor', |
88 | 117 | schema=IProcessor, | ||
89 | 111 | description=_('Build Slave Processor, used to identify ' | 118 | description=_('Build Slave Processor, used to identify ' |
91 | 112 | 'which jobs can be built by this device.')) | 119 | 'which jobs can be built by this device.')), |
92 | 120 | as_of='devel', readonly=True) | ||
93 | 113 | 121 | ||
94 | 114 | owner = exported(PersonChoice( | 122 | owner = exported(PersonChoice( |
95 | 115 | title=_('Owner'), required=True, vocabulary='ValidOwner', | 123 | title=_('Owner'), required=True, vocabulary='ValidOwner', |
96 | @@ -128,7 +136,8 @@ | |||
97 | 128 | 136 | ||
98 | 129 | title = exported(Title( | 137 | title = exported(Title( |
99 | 130 | title=_('Title'), required=True, | 138 | title=_('Title'), required=True, |
101 | 131 | description=_('The builder slave title. Should be just a few words.'))) | 139 | description=_( |
102 | 140 | 'The builder slave title. Should be just a few words.'))) | ||
103 | 132 | 141 | ||
104 | 133 | description = exported(Description( | 142 | description = exported(Description( |
105 | 134 | title=_('Description'), required=False, | 143 | title=_('Description'), required=False, |
106 | @@ -199,8 +208,8 @@ | |||
107 | 199 | :param file_sha1: The file's sha1, which is how the file is addressed | 208 | :param file_sha1: The file's sha1, which is how the file is addressed |
108 | 200 | in the slave XMLRPC protocol. Specially, the file_sha1 'buildlog' | 209 | in the slave XMLRPC protocol. Specially, the file_sha1 'buildlog' |
109 | 201 | will cause the build log to be retrieved and gzipped. | 210 | will cause the build log to be retrieved and gzipped. |
112 | 202 | :param filename: The name of the file to be given to the librarian file | 211 | :param filename: The name of the file to be given to the librarian |
113 | 203 | alias. | 212 | file alias. |
114 | 204 | :param private: True if the build is for a private archive. | 213 | :param private: True if the build is for a private archive. |
115 | 205 | :return: A Deferred that calls back with a librarian file alias. | 214 | :return: A Deferred that calls back with a librarian file alias. |
116 | 206 | """ | 215 | """ |
117 | @@ -238,14 +247,14 @@ | |||
118 | 238 | 247 | ||
119 | 239 | def updateStatus(logger=None): | 248 | def updateStatus(logger=None): |
120 | 240 | """Update the builder's status by probing it. | 249 | """Update the builder's status by probing it. |
122 | 241 | 250 | ||
123 | 242 | :return: A Deferred that fires when the dialog with the slave is | 251 | :return: A Deferred that fires when the dialog with the slave is |
124 | 243 | finished. It does not have a return value. | 252 | finished. It does not have a return value. |
125 | 244 | """ | 253 | """ |
126 | 245 | 254 | ||
127 | 246 | def cleanSlave(): | 255 | def cleanSlave(): |
128 | 247 | """Clean any temporary files from the slave. | 256 | """Clean any temporary files from the slave. |
130 | 248 | 257 | ||
131 | 249 | :return: A Deferred that fires when the dialog with the slave is | 258 | :return: A Deferred that fires when the dialog with the slave is |
132 | 250 | finished. It does not have a return value. | 259 | finished. It does not have a return value. |
133 | 251 | """ | 260 | """ |
134 | @@ -385,26 +394,34 @@ | |||
135 | 385 | def getBuilders(): | 394 | def getBuilders(): |
136 | 386 | """Return all active configured builders.""" | 395 | """Return all active configured builders.""" |
137 | 387 | 396 | ||
141 | 388 | def getBuildersByArch(arch): | 397 | @export_read_operation() |
142 | 389 | """Return all configured builders for a given DistroArchSeries.""" | 398 | @operation_for_version('devel') |
140 | 390 | |||
143 | 391 | def getBuildQueueSizes(): | 399 | def getBuildQueueSizes(): |
144 | 392 | """Return the number of pending builds for each processor. | 400 | """Return the number of pending builds for each processor. |
145 | 393 | 401 | ||
146 | 394 | :return: a dict of tuples with the queue size and duration for | 402 | :return: a dict of tuples with the queue size and duration for |
155 | 395 | each processor and virtualisation. For example: | 403 | each processor and virtualisation. For example:: |
156 | 396 | { | 404 | |
157 | 397 | 'virt': { | 405 | { |
158 | 398 | '386': (1, datetime.timedelta(0, 60)), | 406 | 'virt': { |
159 | 399 | 'amd64': (2, datetime.timedelta(0, 30)), | 407 | '386': (1, datetime.timedelta(0, 60)), |
160 | 400 | }, | 408 | 'amd64': (2, datetime.timedelta(0, 30)), |
161 | 401 | 'nonvirt':... | 409 | }, |
162 | 402 | } | 410 | 'nonvirt':... |
163 | 411 | } | ||
164 | 403 | 412 | ||
165 | 404 | The tuple contains the size of the queue, as an integer, | 413 | The tuple contains the size of the queue, as an integer, |
166 | 405 | and the sum of the jobs 'estimated_duration' in queue, | 414 | and the sum of the jobs 'estimated_duration' in queue, |
167 | 406 | as a timedelta or None for empty queues. | 415 | as a timedelta or None for empty queues. |
168 | 407 | """ | 416 | """ |
169 | 408 | 417 | ||
170 | 418 | @operation_parameters( | ||
171 | 419 | processor=Reference( | ||
172 | 420 | title=_("Processor"), required=True, schema=IProcessor), | ||
173 | 421 | virtualized=Bool( | ||
174 | 422 | title=_("Virtualized"), required=False, default=True)) | ||
175 | 423 | @operation_returns_collection_of(IBuilder) | ||
176 | 424 | @export_read_operation() | ||
177 | 425 | @operation_for_version('devel') | ||
178 | 409 | def getBuildersForQueue(processor, virtualized): | 426 | def getBuildersForQueue(processor, virtualized): |
179 | 410 | """Return all builders for given processor/virtualization setting.""" | 427 | """Return all builders for given processor/virtualization setting.""" |
180 | 411 | 428 | ||
181 | === modified file 'lib/lp/buildmaster/model/builder.py' | |||
182 | --- lib/lp/buildmaster/model/builder.py 2011-02-01 18:14:57 +0000 | |||
183 | +++ lib/lp/buildmaster/model/builder.py 2011-07-21 19:34:45 +0000 | |||
184 | @@ -171,7 +171,8 @@ | |||
185 | 171 | 171 | ||
186 | 172 | :param builder_url: The URL of the slave buildd machine, | 172 | :param builder_url: The URL of the slave buildd machine, |
187 | 173 | e.g. http://localhost:8221 | 173 | e.g. http://localhost:8221 |
189 | 174 | :param vm_host: If the slave is virtual, specify its host machine here. | 174 | :param vm_host: If the slave is virtual, specify its host machine |
190 | 175 | here. | ||
191 | 175 | :param reactor: Used by tests to override the Twisted reactor. | 176 | :param reactor: Used by tests to override the Twisted reactor. |
192 | 176 | :param proxy: Used By tests to override the xmlrpc.Proxy. | 177 | :param proxy: Used By tests to override the xmlrpc.Proxy. |
193 | 177 | """ | 178 | """ |
194 | @@ -216,7 +217,7 @@ | |||
195 | 216 | def getFile(self, sha_sum, file_to_write): | 217 | def getFile(self, sha_sum, file_to_write): |
196 | 217 | """Fetch a file from the builder. | 218 | """Fetch a file from the builder. |
197 | 218 | 219 | ||
199 | 219 | :param sha_sum: The sha of the file (which is also its name on the | 220 | :param sha_sum: The sha of the file (which is also its name on the |
200 | 220 | builder) | 221 | builder) |
201 | 221 | :param file_to_write: A file name or file-like object to write | 222 | :param file_to_write: A file name or file-like object to write |
202 | 222 | the file to | 223 | the file to |
203 | @@ -258,7 +259,8 @@ | |||
204 | 258 | resume_command = config.builddmaster.vm_resume_command % { | 259 | resume_command = config.builddmaster.vm_resume_command % { |
205 | 259 | 'vm_host': self._vm_host} | 260 | 'vm_host': self._vm_host} |
206 | 260 | # Twisted API requires string but the configuration provides unicode. | 261 | # Twisted API requires string but the configuration provides unicode. |
208 | 261 | resume_argv = [term.encode('utf-8') for term in resume_command.split()] | 262 | resume_argv = [ |
209 | 263 | term.encode('utf-8') for term in resume_command.split()] | ||
210 | 262 | d = defer.Deferred() | 264 | d = defer.Deferred() |
211 | 263 | p = ProcessWithTimeout( | 265 | p = ProcessWithTimeout( |
212 | 264 | d, config.builddmaster.socket_timeout, clock=clock) | 266 | d, config.builddmaster.socket_timeout, clock=clock) |
213 | @@ -281,6 +283,7 @@ | |||
214 | 281 | def sendFileToSlave(self, sha1, url, username="", password=""): | 283 | def sendFileToSlave(self, sha1, url, username="", password=""): |
215 | 282 | """Helper to send the file at 'url' with 'sha1' to this builder.""" | 284 | """Helper to send the file at 'url' with 'sha1' to this builder.""" |
216 | 283 | d = self.ensurepresent(sha1, url, username, password) | 285 | d = self.ensurepresent(sha1, url, username, password) |
217 | 286 | |||
218 | 284 | def check_present((present, info)): | 287 | def check_present((present, info)): |
219 | 285 | if not present: | 288 | if not present: |
220 | 286 | raise CannotFetchFile(url, info) | 289 | raise CannotFetchFile(url, info) |
221 | @@ -299,6 +302,7 @@ | |||
222 | 299 | """ | 302 | """ |
223 | 300 | d = self._with_timeout(self._server.callRemote( | 303 | d = self._with_timeout(self._server.callRemote( |
224 | 301 | 'build', buildid, builder_type, chroot_sha1, filemap, args)) | 304 | 'build', buildid, builder_type, chroot_sha1, filemap, args)) |
225 | 305 | |||
226 | 302 | def got_fault(failure): | 306 | def got_fault(failure): |
227 | 303 | failure.trap(xmlrpclib.Fault) | 307 | failure.trap(xmlrpclib.Fault) |
228 | 304 | raise BuildSlaveFailure(failure.value) | 308 | raise BuildSlaveFailure(failure.value) |
229 | @@ -370,6 +374,7 @@ | |||
230 | 370 | d = builder.cleanSlave() | 374 | d = builder.cleanSlave() |
231 | 371 | else: | 375 | else: |
232 | 372 | d = builder.requestAbort() | 376 | d = builder.requestAbort() |
233 | 377 | |||
234 | 373 | def log_rescue(ignored): | 378 | def log_rescue(ignored): |
235 | 374 | if logger: | 379 | if logger: |
236 | 375 | logger.info( | 380 | logger.info( |
237 | @@ -514,8 +519,10 @@ | |||
238 | 514 | logger.info("Resuming %s (%s)" % (self.name, self.url)) | 519 | logger.info("Resuming %s (%s)" % (self.name, self.url)) |
239 | 515 | 520 | ||
240 | 516 | d = self.slave.resume() | 521 | d = self.slave.resume() |
241 | 522 | |||
242 | 517 | def got_resume_ok((stdout, stderr, returncode)): | 523 | def got_resume_ok((stdout, stderr, returncode)): |
243 | 518 | return stdout, stderr | 524 | return stdout, stderr |
244 | 525 | |||
245 | 519 | def got_resume_bad(failure): | 526 | def got_resume_bad(failure): |
246 | 520 | stdout, stderr, code = failure.value | 527 | stdout, stderr, code = failure.value |
247 | 521 | raise CannotResumeHost( | 528 | raise CannotResumeHost( |
248 | @@ -605,6 +612,7 @@ | |||
249 | 605 | def slaveStatus(self): | 612 | def slaveStatus(self): |
250 | 606 | """See IBuilder.""" | 613 | """See IBuilder.""" |
251 | 607 | d = self.slave.status() | 614 | d = self.slave.status() |
252 | 615 | |||
253 | 608 | def got_status(status_sentence): | 616 | def got_status(status_sentence): |
254 | 609 | status = {'builder_status': status_sentence[0]} | 617 | status = {'builder_status': status_sentence[0]} |
255 | 610 | 618 | ||
256 | @@ -680,9 +688,11 @@ | |||
257 | 680 | if not self.builderok: | 688 | if not self.builderok: |
258 | 681 | return defer.succeed(False) | 689 | return defer.succeed(False) |
259 | 682 | d = self.slaveStatusSentence() | 690 | d = self.slaveStatusSentence() |
260 | 691 | |||
261 | 683 | def catch_fault(failure): | 692 | def catch_fault(failure): |
262 | 684 | failure.trap(xmlrpclib.Fault, socket.error) | 693 | failure.trap(xmlrpclib.Fault, socket.error) |
263 | 685 | return False | 694 | return False |
264 | 695 | |||
265 | 686 | def check_available(status): | 696 | def check_available(status): |
266 | 687 | return status[0] == BuilderStatus.IDLE | 697 | return status[0] == BuilderStatus.IDLE |
267 | 688 | return d.addCallbacks(check_available, catch_fault) | 698 | return d.addCallbacks(check_available, catch_fault) |
268 | @@ -896,13 +906,6 @@ | |||
269 | 896 | return Builder.selectBy( | 906 | return Builder.selectBy( |
270 | 897 | active=True, orderBy=['virtualized', 'processor', 'name']) | 907 | active=True, orderBy=['virtualized', 'processor', 'name']) |
271 | 898 | 908 | ||
272 | 899 | def getBuildersByArch(self, arch): | ||
273 | 900 | """See IBuilderSet.""" | ||
274 | 901 | return Builder.select('builder.processor = processor.id ' | ||
275 | 902 | 'AND processor.family = %d' | ||
276 | 903 | % arch.processorfamily.id, | ||
277 | 904 | clauseTables=("Processor",)) | ||
278 | 905 | |||
279 | 906 | def getBuildQueueSizes(self): | 909 | def getBuildQueueSizes(self): |
280 | 907 | """See `IBuilderSet`.""" | 910 | """See `IBuilderSet`.""" |
281 | 908 | store = getUtility(IStoreSelector).get(MAIN_STORE, SLAVE_FLAVOR) | 911 | store = getUtility(IStoreSelector).get(MAIN_STORE, SLAVE_FLAVOR) |
282 | 909 | 912 | ||
283 | === added file 'lib/lp/buildmaster/security.py' | |||
284 | --- lib/lp/buildmaster/security.py 1970-01-01 00:00:00 +0000 | |||
285 | +++ lib/lp/buildmaster/security.py 2011-07-21 19:34:45 +0000 | |||
286 | @@ -0,0 +1,19 @@ | |||
287 | 1 | # Copyright 2011 Canonical Ltd. This software is licensed under the | ||
288 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
289 | 3 | |||
290 | 4 | """Security adapters for the buildmaster package.""" | ||
291 | 5 | |||
292 | 6 | __metaclass__ = type | ||
293 | 7 | __all__ = [ | ||
294 | 8 | 'ViewBuilder', | ||
295 | 9 | ] | ||
296 | 10 | |||
297 | 11 | from lp.app.security import AnonymousAuthorization | ||
298 | 12 | from lp.buildmaster.interfaces.builder import ( | ||
299 | 13 | IBuilder, | ||
300 | 14 | ) | ||
301 | 15 | |||
302 | 16 | |||
303 | 17 | class ViewBuilder(AnonymousAuthorization): | ||
304 | 18 | """Anyone can view a `IBuilder`.""" | ||
305 | 19 | usedfor = IBuilder | ||
306 | 0 | 20 | ||
307 | === added file 'lib/lp/buildmaster/tests/test_webservice.py' | |||
308 | --- lib/lp/buildmaster/tests/test_webservice.py 1970-01-01 00:00:00 +0000 | |||
309 | +++ lib/lp/buildmaster/tests/test_webservice.py 2011-07-21 19:34:45 +0000 | |||
310 | @@ -0,0 +1,68 @@ | |||
311 | 1 | # Copyright 2011 Canonical Ltd. This software is licensed under the | ||
312 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
313 | 3 | |||
314 | 4 | """Tests for the builders webservice .""" | ||
315 | 5 | |||
316 | 6 | __metaclass__ = type | ||
317 | 7 | |||
318 | 8 | from canonical.testing.layers import DatabaseFunctionalLayer | ||
319 | 9 | from canonical.launchpad.testing.pages import LaunchpadWebServiceCaller | ||
320 | 10 | from lp.testing import ( | ||
321 | 11 | api_url, | ||
322 | 12 | logout, | ||
323 | 13 | TestCaseWithFactory, | ||
324 | 14 | ) | ||
325 | 15 | |||
326 | 16 | |||
327 | 17 | class TestBuildersCollection(TestCaseWithFactory): | ||
328 | 18 | layer = DatabaseFunctionalLayer | ||
329 | 19 | |||
330 | 20 | def setUp(self): | ||
331 | 21 | super(TestBuildersCollection, self).setUp() | ||
332 | 22 | self.webservice = LaunchpadWebServiceCaller() | ||
333 | 23 | |||
334 | 24 | def test_getBuildQueueSizes(self): | ||
335 | 25 | logout() | ||
336 | 26 | results = self.webservice.named_get( | ||
337 | 27 | '/builders', 'getBuildQueueSizes', api_version='devel') | ||
338 | 28 | self.assertEquals( | ||
339 | 29 | ['nonvirt', 'virt'], sorted(results.jsonBody().keys())) | ||
340 | 30 | |||
341 | 31 | def test_getBuildersForQueue(self): | ||
342 | 32 | g1 = self.factory.makeProcessorFamily('g1').processors[0] | ||
343 | 33 | quantum = self.factory.makeProcessorFamily('quantum').processors[0] | ||
344 | 34 | self.factory.makeBuilder( | ||
345 | 35 | processor=quantum, name='quantum_builder1') | ||
346 | 36 | self.factory.makeBuilder( | ||
347 | 37 | processor=quantum, name='quantum_builder2') | ||
348 | 38 | self.factory.makeBuilder( | ||
349 | 39 | processor=quantum, name='quantum_builder3', virtualized=False) | ||
350 | 40 | self.factory.makeBuilder( | ||
351 | 41 | processor=g1, name='g1_builder', virtualized=False) | ||
352 | 42 | |||
353 | 43 | logout() | ||
354 | 44 | results = self.webservice.named_get( | ||
355 | 45 | '/builders', 'getBuildersForQueue', | ||
356 | 46 | processor=api_url(quantum), virtualized=True, | ||
357 | 47 | api_version='devel').jsonBody() | ||
358 | 48 | self.assertEquals( | ||
359 | 49 | ['quantum_builder1', 'quantum_builder2'], | ||
360 | 50 | sorted(builder['name'] for builder in results['entries'])) | ||
361 | 51 | |||
362 | 52 | |||
363 | 53 | class TestBuilderEntry(TestCaseWithFactory): | ||
364 | 54 | layer = DatabaseFunctionalLayer | ||
365 | 55 | |||
366 | 56 | def setUp(self): | ||
367 | 57 | super(TestBuilderEntry, self).setUp() | ||
368 | 58 | self.webservice = LaunchpadWebServiceCaller() | ||
369 | 59 | |||
370 | 60 | def test_exports_processor(self): | ||
371 | 61 | processor_family = self.factory.makeProcessorFamily('s1') | ||
372 | 62 | builder = self.factory.makeBuilder( | ||
373 | 63 | processor=processor_family.processors[0]) | ||
374 | 64 | |||
375 | 65 | logout() | ||
376 | 66 | entry = self.webservice.get( | ||
377 | 67 | api_url(builder), api_version='devel').jsonBody() | ||
378 | 68 | self.assertEndsWith(entry['processor_link'], '/+processors/s1') | ||
379 | 0 | 69 | ||
380 | === added file 'lib/lp/services/webservice/configure.zcml' | |||
381 | --- lib/lp/services/webservice/configure.zcml 1970-01-01 00:00:00 +0000 | |||
382 | +++ lib/lp/services/webservice/configure.zcml 2011-07-21 19:34:45 +0000 | |||
383 | @@ -0,0 +1,15 @@ | |||
384 | 1 | <!-- Copyright 2011 Canonical Ltd. This software is licensed under the | ||
385 | 2 | GNU Affero General Public License version 3 (see the file LICENSE). | ||
386 | 3 | --> | ||
387 | 4 | |||
388 | 5 | <configure | ||
389 | 6 | xmlns="http://namespaces.zope.org/zope" | ||
390 | 7 | xmlns:i18n="http://namespaces.zope.org/i18n" | ||
391 | 8 | i18n_domain="launchpad"> | ||
392 | 9 | |||
393 | 10 | <adapter | ||
394 | 11 | provides="lazr.restful.interfaces.IJSONPublishable" | ||
395 | 12 | for="zope.interface.common.idatetime.ITimeDelta" | ||
396 | 13 | factory="lp.services.webservice.json.StrJSONSerializer" | ||
397 | 14 | permission="zope.Public"/> | ||
398 | 15 | </configure> | ||
399 | 0 | 16 | ||
400 | === added file 'lib/lp/services/webservice/json.py' | |||
401 | --- lib/lp/services/webservice/json.py 1970-01-01 00:00:00 +0000 | |||
402 | +++ lib/lp/services/webservice/json.py 2011-07-21 19:34:45 +0000 | |||
403 | @@ -0,0 +1,24 @@ | |||
404 | 1 | # Copyright 2011 Canonical Ltd. This software is licensed under the | ||
405 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
406 | 3 | |||
407 | 4 | """Additional JSON serializer for the web service.""" | ||
408 | 5 | |||
409 | 6 | __metaclass__ = type | ||
410 | 7 | __all__ = [ | ||
411 | 8 | 'StrJSONSerializer', | ||
412 | 9 | ] | ||
413 | 10 | |||
414 | 11 | |||
415 | 12 | from lazr.restful.interfaces import IJSONPublishable | ||
416 | 13 | from zope.interface import implements | ||
417 | 14 | |||
418 | 15 | |||
419 | 16 | class StrJSONSerializer: | ||
420 | 17 | """Simple JSON serializer that simply str() it's context. """ | ||
421 | 18 | implements(IJSONPublishable) | ||
422 | 19 | |||
423 | 20 | def __init__(self, context): | ||
424 | 21 | self.context = context | ||
425 | 22 | |||
426 | 23 | def toDataForJSON(self, media_type): | ||
427 | 24 | return str(self.context) | ||
428 | 0 | 25 | ||
429 | === added directory 'lib/lp/services/webservice/tests' | |||
430 | === added file 'lib/lp/services/webservice/tests/__init__.py' | |||
431 | === added file 'lib/lp/services/webservice/tests/test_json.py' | |||
432 | --- lib/lp/services/webservice/tests/test_json.py 1970-01-01 00:00:00 +0000 | |||
433 | +++ lib/lp/services/webservice/tests/test_json.py 2011-07-21 19:34:45 +0000 | |||
434 | @@ -0,0 +1,30 @@ | |||
435 | 1 | # Copyright 2011 Canonical Ltd. This software is licensed under the | ||
436 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
437 | 3 | |||
438 | 4 | """Tests for the JSON serializer.""" | ||
439 | 5 | |||
440 | 6 | __metaclass__ = type | ||
441 | 7 | |||
442 | 8 | from datetime import timedelta | ||
443 | 9 | |||
444 | 10 | from canonical.testing.layers import FunctionalLayer | ||
445 | 11 | from lazr.restful.interfaces import IJSONPublishable | ||
446 | 12 | from lp.services.webservice.json import StrJSONSerializer | ||
447 | 13 | from lp.testing import TestCase | ||
448 | 14 | |||
449 | 15 | |||
450 | 16 | class TestStrJSONSerializer(TestCase): | ||
451 | 17 | layer = FunctionalLayer | ||
452 | 18 | |||
453 | 19 | def test_toDataForJSON(self): | ||
454 | 20 | serializer = StrJSONSerializer( | ||
455 | 21 | timedelta(days=2, hours=2, seconds=5)) | ||
456 | 22 | self.assertEquals( | ||
457 | 23 | '2 days, 2:00:05', | ||
458 | 24 | serializer.toDataForJSON('application/json')) | ||
459 | 25 | |||
460 | 26 | def test_timedelta_users_StrJSONSerializer(self): | ||
461 | 27 | delta = timedelta(seconds=5) | ||
462 | 28 | serializer = IJSONPublishable(delta) | ||
463 | 29 | self.assertEquals('0:00:05', | ||
464 | 30 | serializer.toDataForJSON('application/json')) | ||
465 | 0 | 31 | ||
466 | === modified file 'lib/lp/services/webservice/wadl-to-refhtml.xsl' | |||
467 | --- lib/lp/services/webservice/wadl-to-refhtml.xsl 2011-01-13 19:45:32 +0000 | |||
468 | +++ lib/lp/services/webservice/wadl-to-refhtml.xsl 2011-07-21 19:34:45 +0000 | |||
469 | @@ -165,9 +165,16 @@ | |||
470 | 165 | <xsl:call-template name="resource-uri-doc"> | 165 | <xsl:call-template name="resource-uri-doc"> |
471 | 166 | <xsl:with-param name="url"> | 166 | <xsl:with-param name="url"> |
472 | 167 | <xsl:choose> | 167 | <xsl:choose> |
476 | 168 | <xsl:when test="@id = 'has_milestones' | 168 | <xsl:when test=" |
477 | 169 | or @id = 'bug_target' | 169 | @id = 'bug_link_target' |
478 | 170 | or @id = 'has_bugs'"> | 170 | or @id = 'bug_target' |
479 | 171 | or @id = 'has_bugs' | ||
480 | 172 | or @id = 'has_milestones' | ||
481 | 173 | or @id = 'object_with_translation_imports' | ||
482 | 174 | or @id = 'question_target' | ||
483 | 175 | or @id = 'specification_target' | ||
484 | 176 | or @id = 'structural_subscription_target' | ||
485 | 177 | "> | ||
486 | 171 | <em>depends on the underlying entry</em> | 178 | <em>depends on the underlying entry</em> |
487 | 172 | </xsl:when> | 179 | </xsl:when> |
488 | 173 | <xsl:otherwise> | 180 | <xsl:otherwise> |
489 | @@ -264,10 +271,18 @@ | |||
490 | 264 | <xsl:text>/+build/</xsl:text> | 271 | <xsl:text>/+build/</xsl:text> |
491 | 265 | <var><id></var> | 272 | <var><id></var> |
492 | 266 | </xsl:when> | 273 | </xsl:when> |
493 | 274 | <xsl:when test="@id = 'builder'"> | ||
494 | 275 | <xsl:text>/builders/</xsl:text> | ||
495 | 276 | <var><builder.name></var> | ||
496 | 277 | </xsl:when> | ||
497 | 267 | <xsl:when test="@id = 'cve'"> | 278 | <xsl:when test="@id = 'cve'"> |
498 | 268 | <xsl:text>/bugs/cve/</xsl:text> | 279 | <xsl:text>/bugs/cve/</xsl:text> |
499 | 269 | <var><sequence></var> | 280 | <var><sequence></var> |
500 | 270 | </xsl:when> | 281 | </xsl:when> |
501 | 282 | <xsl:when test="@id = 'country'"> | ||
502 | 283 | <xsl:text>/+countries/</xsl:text> | ||
503 | 284 | <var><iso3166code2></var> | ||
504 | 285 | </xsl:when> | ||
505 | 271 | <xsl:when test="@id = 'distribution_source_package'"> | 286 | <xsl:when test="@id = 'distribution_source_package'"> |
506 | 272 | <xsl:text>/</xsl:text> | 287 | <xsl:text>/</xsl:text> |
507 | 273 | <var><distribution.name></var> | 288 | <var><distribution.name></var> |
508 | @@ -294,6 +309,15 @@ | |||
509 | 294 | <xsl:text>/+email/</xsl:text> | 309 | <xsl:text>/+email/</xsl:text> |
510 | 295 | <var><email></var> | 310 | <var><email></var> |
511 | 296 | </xsl:when> | 311 | </xsl:when> |
512 | 312 | <xsl:when test="@id = 'gpg_key'"> | ||
513 | 313 | <xsl:text>/</xsl:text> | ||
514 | 314 | <var><person.name></var> | ||
515 | 315 | <xsl:text>/+gpg-keys/</xsl:text> | ||
516 | 316 | <var><keyid></var> | ||
517 | 317 | </xsl:when> | ||
518 | 318 | <xsl:when test="@id = 'hwdb'"> | ||
519 | 319 | <xsl:text>/+hwdb</xsl:text> | ||
520 | 320 | </xsl:when> | ||
521 | 297 | <xsl:when test="@id = 'h_w_device'"> | 321 | <xsl:when test="@id = 'h_w_device'"> |
522 | 298 | <xsl:text>/+hwdb/+device/</xsl:text> | 322 | <xsl:text>/+hwdb/+device/</xsl:text> |
523 | 299 | <var><id></var> | 323 | <var><id></var> |
524 | @@ -360,6 +384,17 @@ | |||
525 | 360 | <xsl:text>/~</xsl:text> | 384 | <xsl:text>/~</xsl:text> |
526 | 361 | <var><name></var> | 385 | <var><name></var> |
527 | 362 | </xsl:when> | 386 | </xsl:when> |
528 | 387 | <xsl:when test="@id = 'pillars'"> | ||
529 | 388 | <xsl:text>/pillars</xsl:text> | ||
530 | 389 | </xsl:when> | ||
531 | 390 | <xsl:when test="@id = 'processor'"> | ||
532 | 391 | <xsl:text>/+processors/</xsl:text> | ||
533 | 392 | <var><processor.name></var> | ||
534 | 393 | </xsl:when> | ||
535 | 394 | <xsl:when test="@id = 'processor_family'"> | ||
536 | 395 | <xsl:text>/+processor-families/</xsl:text> | ||
537 | 396 | <var><processor_family.name></var> | ||
538 | 397 | </xsl:when> | ||
539 | 363 | <xsl:when test="@id = 'product_release'"> | 398 | <xsl:when test="@id = 'product_release'"> |
540 | 364 | <xsl:text>/</xsl:text> | 399 | <xsl:text>/</xsl:text> |
541 | 365 | <var><product.name></var> | 400 | <var><product.name></var> |
542 | @@ -398,6 +433,20 @@ | |||
543 | 398 | <xsl:text>/</xsl:text> | 433 | <xsl:text>/</xsl:text> |
544 | 399 | <var><name></var> | 434 | <var><name></var> |
545 | 400 | </xsl:when> | 435 | </xsl:when> |
546 | 436 | <xsl:when test="@id = 'question'"> | ||
547 | 437 | <xsl:text>/</xsl:text> | ||
548 | 438 | <var><target.name></var> | ||
549 | 439 | <xsl:text>/+question/</xsl:text> | ||
550 | 440 | <var ><question.id></var> | ||
551 | 441 | </xsl:when> | ||
552 | 442 | <xsl:when test="@id = 'question_message'"> | ||
553 | 443 | <xsl:text>/</xsl:text> | ||
554 | 444 | <var><target.name></var> | ||
555 | 445 | <xsl:text>/+question/</xsl:text> | ||
556 | 446 | <var ><question.id></var> | ||
557 | 447 | <xsl:text>/messages/</xsl:text> | ||
558 | 448 | <var ><message.index></var> | ||
559 | 449 | </xsl:when> | ||
560 | 401 | <xsl:when test="@id = 'source_package'"> | 450 | <xsl:when test="@id = 'source_package'"> |
561 | 402 | <xsl:text>/</xsl:text> | 451 | <xsl:text>/</xsl:text> |
562 | 403 | <var><distribution.name></var> | 452 | <var><distribution.name></var> |
563 | @@ -414,6 +463,34 @@ | |||
564 | 414 | <xsl:text>/+sourcepub/</xsl:text> | 463 | <xsl:text>/+sourcepub/</xsl:text> |
565 | 415 | <var><id></var> | 464 | <var><id></var> |
566 | 416 | </xsl:when> | 465 | </xsl:when> |
567 | 466 | <xsl:when test="@id = 'specification'"> | ||
568 | 467 | <xsl:text>/</xsl:text> | ||
569 | 468 | <var><target.name></var> | ||
570 | 469 | <xsl:text>/+spec/</xsl:text> | ||
571 | 470 | <var ><specification.name></var> | ||
572 | 471 | </xsl:when> | ||
573 | 472 | <xsl:when test="@id = 'specification_branch'"> | ||
574 | 473 | <xsl:text>/</xsl:text> | ||
575 | 474 | <var><target.name></var> | ||
576 | 475 | <xsl:text>/+spec/</xsl:text> | ||
577 | 476 | <var ><specification.name></var> | ||
578 | 477 | <xsl:text>/+branch/</xsl:text> | ||
579 | 478 | <var ><branch.unique_name[1:]></var> | ||
580 | 479 | </xsl:when> | ||
581 | 480 | <xsl:when test="@id = 'specification_subscription'"> | ||
582 | 481 | <xsl:text>/</xsl:text> | ||
583 | 482 | <var><target.name></var> | ||
584 | 483 | <xsl:text>/+spec/</xsl:text> | ||
585 | 484 | <var ><specification.name></var> | ||
586 | 485 | <xsl:text>/+subscription/</xsl:text> | ||
587 | 486 | <var ><person.name></var> | ||
588 | 487 | </xsl:when> | ||
589 | 488 | <xsl:when test="@id = 'ssh_key'"> | ||
590 | 489 | <xsl:text>/</xsl:text> | ||
591 | 490 | <var><person.name></var> | ||
592 | 491 | <xsl:text>/+ssh-keys/</xsl:text> | ||
593 | 492 | <var><keyid></var> | ||
594 | 493 | </xsl:when> | ||
595 | 417 | <xsl:when test="@id = 'team_membership'"> | 494 | <xsl:when test="@id = 'team_membership'"> |
596 | 418 | <xsl:text>/~</xsl:text> | 495 | <xsl:text>/~</xsl:text> |
597 | 419 | <var><team.name></var> | 496 | <var><team.name></var> |
598 | @@ -864,6 +941,12 @@ | |||
599 | 864 | <xsl:apply-templates select="wadl:doc"/> | 941 | <xsl:apply-templates select="wadl:doc"/> |
600 | 865 | <xsl:apply-templates select="wadl:request"/> | 942 | <xsl:apply-templates select="wadl:request"/> |
601 | 866 | <xsl:apply-templates select="wadl:response"/> | 943 | <xsl:apply-templates select="wadl:response"/> |
602 | 944 | <xsl:if test="not(wadl:response)"> | ||
603 | 945 | <xsl:apply-templates select="wadl:doc//html:th[ | ||
604 | 946 | node() = 'return:' | ||
605 | 947 | ]"/> | ||
606 | 948 | </xsl:if> | ||
607 | 949 | <xsl:call-template name="error-documentation"/> | ||
608 | 867 | </xsl:when> | 950 | </xsl:when> |
609 | 868 | <xsl:otherwise> | 951 | <xsl:otherwise> |
610 | 869 | <p><em>Missing documentation.</em></p> | 952 | <p><em>Missing documentation.</em></p> |
611 | @@ -910,6 +993,39 @@ | |||
612 | 910 | </p> | 993 | </p> |
613 | 911 | </xsl:template> | 994 | </xsl:template> |
614 | 912 | 995 | ||
615 | 996 | <!-- Documentation of the custom method return type. --> | ||
616 | 997 | <xsl:template match="wadl:doc//html:th[node() = 'return:']"> | ||
617 | 998 | <h6>Response (application/json)</h6> | ||
618 | 999 | <xsl:choose> | ||
619 | 1000 | <xsl:when test="following-sibling::html:td/text()"> | ||
620 | 1001 | <p><xsl:apply-templates select="following-sibling::html:td" | ||
621 | 1002 | mode="copy"/></p> | ||
622 | 1003 | </xsl:when> | ||
623 | 1004 | <xsl:otherwise> | ||
624 | 1005 | <xsl:apply-templates select="following-sibling::html:td" | ||
625 | 1006 | mode="copy"/> | ||
626 | 1007 | </xsl:otherwise> | ||
627 | 1008 | </xsl:choose> | ||
628 | 1009 | </xsl:template> | ||
629 | 1010 | |||
630 | 1011 | <!-- Documentation of the error raised by the operation. --> | ||
631 | 1012 | <xsl:template name="error-documentation"> | ||
632 | 1013 | <xsl:if test="wadl:doc//html:th[node() = 'raise:']"> | ||
633 | 1014 | <h6>Errors</h6> | ||
634 | 1015 | <ul> | ||
635 | 1016 | <xsl:apply-templates | ||
636 | 1017 | select="wadl:doc//html:th[node() = 'raise:']"/> | ||
637 | 1018 | </ul> | ||
638 | 1019 | </xsl:if> | ||
639 | 1020 | </xsl:template> | ||
640 | 1021 | |||
641 | 1022 | <xsl:template match="wadl:doc//html:th[node() = 'raise:']"> | ||
642 | 1023 | <li> | ||
643 | 1024 | <xsl:apply-templates select="following-sibling::html:td" | ||
644 | 1025 | mode="copy"/> | ||
645 | 1026 | </li> | ||
646 | 1027 | </xsl:template> | ||
647 | 1028 | |||
648 | 913 | <!-- Documentation for request parameter. --> | 1029 | <!-- Documentation for request parameter. --> |
649 | 914 | <xsl:template match="wadl:param"> | 1030 | <xsl:template match="wadl:param"> |
650 | 915 | <tr> | 1031 | <tr> |
651 | @@ -1032,11 +1148,15 @@ | |||
652 | 1032 | mode="representation-type"/> | 1148 | mode="representation-type"/> |
653 | 1033 | </xsl:template> | 1149 | </xsl:template> |
654 | 1034 | 1150 | ||
657 | 1035 | <!-- Omit docutils parameter lists in methods since they are redundant | 1151 | <!-- Omit docutils parameter table in methods. The parameter names |
658 | 1036 | or misleading with the one we give. --> | 1152 | description is either redundant or misleading with the one we |
659 | 1153 | give. | ||
660 | 1154 | |||
661 | 1155 | We process the return and raise parameters separately. | ||
662 | 1156 | --> | ||
663 | 1037 | <xsl:template match="wadl:method//html:table[ | 1157 | <xsl:template match="wadl:method//html:table[ |
664 | 1038 | contains(@class, 'field-list')]" | 1158 | contains(@class, 'field-list')]" |
666 | 1039 | mode="copy"/> | 1159 | mode="copy" /> |
667 | 1040 | 1160 | ||
668 | 1041 | <!-- Output the mediaType attribute of a representation --> | 1161 | <!-- Output the mediaType attribute of a representation --> |
669 | 1042 | <xsl:template match="wadl:representation[@mediaType]" | 1162 | <xsl:template match="wadl:representation[@mediaType]" |
670 | 1043 | 1163 | ||
671 | === modified file 'lib/lp/soyuz/browser/configure.zcml' | |||
672 | --- lib/lp/soyuz/browser/configure.zcml 2011-06-23 13:42:38 +0000 | |||
673 | +++ lib/lp/soyuz/browser/configure.zcml 2011-07-21 19:34:45 +0000 | |||
674 | @@ -32,18 +32,20 @@ | |||
675 | 32 | path_expression="string:+binarypub" | 32 | path_expression="string:+binarypub" |
676 | 33 | attribute_to_parent="archive" | 33 | attribute_to_parent="archive" |
677 | 34 | urldata="lp.soyuz.browser.publishing.BinaryPublicationURL"/> | 34 | urldata="lp.soyuz.browser.publishing.BinaryPublicationURL"/> |
680 | 35 | <browser:url | 35 | <browser:url for="lp.soyuz.interfaces.processor.IProcessorFamilySet" |
679 | 36 | for="lp.soyuz.interfaces.processor.IProcessorFamilySet" | ||
681 | 37 | path_expression="string:+processor-families" | 36 | path_expression="string:+processor-families" |
682 | 38 | parent_utility="canonical.launchpad.webapp.interfaces.ILaunchpadRoot"/> | 37 | parent_utility="canonical.launchpad.webapp.interfaces.ILaunchpadRoot"/> |
683 | 39 | <browser:url | 38 | <browser:url |
684 | 40 | for="lp.soyuz.interfaces.processor.IProcessorFamily" | 39 | for="lp.soyuz.interfaces.processor.IProcessorFamily" |
685 | 41 | path_expression="string:${name}" | 40 | path_expression="string:${name}" |
686 | 42 | parent_utility="lp.soyuz.interfaces.processor.IProcessorFamilySet" /> | 41 | parent_utility="lp.soyuz.interfaces.processor.IProcessorFamilySet" /> |
687 | 42 | <browser:url for="lp.soyuz.interfaces.processor.IProcessorSet" | ||
688 | 43 | path_expression="string:+processors" | ||
689 | 44 | parent_utility="canonical.launchpad.webapp.interfaces.ILaunchpadRoot"/> | ||
690 | 43 | <browser:url | 45 | <browser:url |
691 | 44 | for="lp.soyuz.interfaces.processor.IProcessor" | 46 | for="lp.soyuz.interfaces.processor.IProcessor" |
694 | 45 | path_expression="string:${id}" | 47 | path_expression="string:${name}" |
695 | 46 | attribute_to_parent="family" /> | 48 | parent_utility="lp.soyuz.interfaces.processor.IProcessorSet" /> |
696 | 47 | </facet> | 49 | </facet> |
697 | 48 | <browser:navigation | 50 | <browser:navigation |
698 | 49 | module="lp.soyuz.browser.binarypackagerelease" | 51 | module="lp.soyuz.browser.binarypackagerelease" |
699 | @@ -233,7 +235,7 @@ | |||
700 | 233 | <browser:navigation | 235 | <browser:navigation |
701 | 234 | module="lp.soyuz.browser.processor" | 236 | module="lp.soyuz.browser.processor" |
702 | 235 | classes=" | 237 | classes=" |
704 | 236 | ProcessorFamilySetNavigation ProcessorFamilyNavigation"/> | 238 | ProcessorFamilySetNavigation ProcessorSetNavigation"/> |
705 | 237 | <browser:url | 239 | <browser:url |
706 | 238 | for="lp.soyuz.interfaces.archive.IPPA" | 240 | for="lp.soyuz.interfaces.archive.IPPA" |
707 | 239 | path_expression="string:+archive" | 241 | path_expression="string:+archive" |
708 | 240 | 242 | ||
709 | === modified file 'lib/lp/soyuz/browser/processor.py' | |||
710 | --- lib/lp/soyuz/browser/processor.py 2011-06-23 16:11:42 +0000 | |||
711 | +++ lib/lp/soyuz/browser/processor.py 2011-07-21 19:34:45 +0000 | |||
712 | @@ -8,15 +8,15 @@ | |||
713 | 8 | 8 | ||
714 | 9 | __all__ = [ | 9 | __all__ = [ |
715 | 10 | 'ProcessorFamilySetNavigation', | 10 | 'ProcessorFamilySetNavigation', |
717 | 11 | 'ProcessorFamilyNavigation', | 11 | 'ProcessorSetNavigation', |
718 | 12 | ] | 12 | ] |
719 | 13 | 13 | ||
720 | 14 | 14 | ||
721 | 15 | from canonical.launchpad.webapp import Navigation | 15 | from canonical.launchpad.webapp import Navigation |
722 | 16 | from lp.app.errors import NotFoundError | 16 | from lp.app.errors import NotFoundError |
723 | 17 | from lp.soyuz.interfaces.processor import ( | 17 | from lp.soyuz.interfaces.processor import ( |
724 | 18 | IProcessorFamily, | ||
725 | 19 | IProcessorFamilySet, | 18 | IProcessorFamilySet, |
726 | 19 | IProcessorSet, | ||
727 | 20 | ) | 20 | ) |
728 | 21 | 21 | ||
729 | 22 | 22 | ||
730 | @@ -32,15 +32,9 @@ | |||
731 | 32 | return family | 32 | return family |
732 | 33 | 33 | ||
733 | 34 | 34 | ||
746 | 35 | class ProcessorFamilyNavigation(Navigation): | 35 | class ProcessorSetNavigation(Navigation): |
747 | 36 | """IProcessorFamily navigation.""" | 36 | """IProcessorFamilySet navigation.""" |
748 | 37 | 37 | usedfor = IProcessorSet | |
749 | 38 | usedfor = IProcessorFamily | 38 | |
750 | 39 | 39 | def traverse(self, name): | |
751 | 40 | def traverse(self, id_): | 40 | return self.context.getByName(name) |
740 | 41 | id_ = int(id_) | ||
741 | 42 | processors = self.processors | ||
742 | 43 | for p in processors: | ||
743 | 44 | if p.id == id_: | ||
744 | 45 | return p | ||
745 | 46 | raise NotFoundError(id_) | ||
752 | 47 | 41 | ||
753 | === added file 'lib/lp/soyuz/browser/tests/test_processor.py' | |||
754 | --- lib/lp/soyuz/browser/tests/test_processor.py 1970-01-01 00:00:00 +0000 | |||
755 | +++ lib/lp/soyuz/browser/tests/test_processor.py 2011-07-21 19:34:45 +0000 | |||
756 | @@ -0,0 +1,41 @@ | |||
757 | 1 | # Copyright 2011 Canonical Ltd. This software is licensed under the | ||
758 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
759 | 3 | |||
760 | 4 | """Tests for process navigation.""" | ||
761 | 5 | |||
762 | 6 | __metaclass__ = type | ||
763 | 7 | |||
764 | 8 | from canonical.testing.layers import DatabaseFunctionalLayer | ||
765 | 9 | from canonical.launchpad.webapp.publisher import canonical_url | ||
766 | 10 | from lp.testing import TestCaseWithFactory | ||
767 | 11 | from lp.testing.publication import test_traverse | ||
768 | 12 | |||
769 | 13 | |||
770 | 14 | class TestProcessorNavigation(TestCaseWithFactory): | ||
771 | 15 | layer = DatabaseFunctionalLayer | ||
772 | 16 | |||
773 | 17 | def test_processor_family_url(self): | ||
774 | 18 | family = self.factory.makeProcessorFamily('quantum') | ||
775 | 19 | self.assertEquals( | ||
776 | 20 | '/+processor-families/quantum', | ||
777 | 21 | canonical_url(family, force_local_path=True)) | ||
778 | 22 | |||
779 | 23 | def test_processor_url(self): | ||
780 | 24 | family = self.factory.makeProcessorFamily('quantum') | ||
781 | 25 | quantum = family.processors[0] | ||
782 | 26 | self.assertEquals( | ||
783 | 27 | '/+processors/quantum', | ||
784 | 28 | canonical_url(quantum, force_local_path=True)) | ||
785 | 29 | |||
786 | 30 | def test_processor_family_navigation(self): | ||
787 | 31 | family = self.factory.makeProcessorFamily('quantum') | ||
788 | 32 | obj, view, request = test_traverse( | ||
789 | 33 | 'http://api.launchpad.dev/devel/+processor-families/quantum') | ||
790 | 34 | self.assertEquals(family, obj) | ||
791 | 35 | |||
792 | 36 | def test_processor_navigation(self): | ||
793 | 37 | family = self.factory.makeProcessorFamily('quantum') | ||
794 | 38 | obj, view, request = test_traverse( | ||
795 | 39 | 'http://api.launchpad.dev/' | ||
796 | 40 | 'devel/+processors/quantum') | ||
797 | 41 | self.assertEquals(family.processors[0], obj) | ||
798 | 0 | 42 | ||
799 | === modified file 'lib/lp/soyuz/configure.zcml' | |||
800 | --- lib/lp/soyuz/configure.zcml 2011-07-12 14:23:40 +0000 | |||
801 | +++ lib/lp/soyuz/configure.zcml 2011-07-21 19:34:45 +0000 | |||
802 | @@ -11,6 +11,7 @@ | |||
803 | 11 | i18n_domain="launchpad"> | 11 | i18n_domain="launchpad"> |
804 | 12 | <include | 12 | <include |
805 | 13 | package=".browser"/> | 13 | package=".browser"/> |
806 | 14 | <authorizations module=".security" /> | ||
807 | 14 | 15 | ||
808 | 15 | <!-- PackageCloner --> | 16 | <!-- PackageCloner --> |
809 | 16 | 17 | ||
810 | @@ -375,6 +376,12 @@ | |||
811 | 375 | <allow | 376 | <allow |
812 | 376 | interface="lp.soyuz.interfaces.processor.IProcessorFamilySet"/> | 377 | interface="lp.soyuz.interfaces.processor.IProcessorFamilySet"/> |
813 | 377 | </securedutility> | 378 | </securedutility> |
814 | 379 | <securedutility | ||
815 | 380 | class="lp.soyuz.model.processor.ProcessorSet" | ||
816 | 381 | provides="lp.soyuz.interfaces.processor.IProcessorSet"> | ||
817 | 382 | <allow | ||
818 | 383 | interface="lp.soyuz.interfaces.processor.IProcessorSet"/> | ||
819 | 384 | </securedutility> | ||
820 | 378 | <adapter | 385 | <adapter |
821 | 379 | for="lp.soyuz.interfaces.distroarchseriesbinarypackagerelease.IDistroArchSeriesBinaryPackageRelease" | 386 | for="lp.soyuz.interfaces.distroarchseriesbinarypackagerelease.IDistroArchSeriesBinaryPackageRelease" |
822 | 380 | provides="canonical.launchpad.webapp.interfaces.IBreadcrumb" | 387 | provides="canonical.launchpad.webapp.interfaces.IBreadcrumb" |
823 | 381 | 388 | ||
824 | === modified file 'lib/lp/soyuz/interfaces/processor.py' | |||
825 | --- lib/lp/soyuz/interfaces/processor.py 2011-06-23 16:11:42 +0000 | |||
826 | +++ lib/lp/soyuz/interfaces/processor.py 2011-07-21 19:34:45 +0000 | |||
827 | @@ -11,6 +11,8 @@ | |||
828 | 11 | 'IProcessor', | 11 | 'IProcessor', |
829 | 12 | 'IProcessorFamily', | 12 | 'IProcessorFamily', |
830 | 13 | 'IProcessorFamilySet', | 13 | 'IProcessorFamilySet', |
831 | 14 | 'IProcessorSet', | ||
832 | 15 | 'ProcessorNotFound', | ||
833 | 14 | ] | 16 | ] |
834 | 15 | 17 | ||
835 | 16 | from zope.interface import ( | 18 | from zope.interface import ( |
836 | @@ -38,6 +40,12 @@ | |||
837 | 38 | CollectionField, | 40 | CollectionField, |
838 | 39 | Reference, | 41 | Reference, |
839 | 40 | ) | 42 | ) |
840 | 43 | from lp.app.errors import NameLookupFailed | ||
841 | 44 | |||
842 | 45 | |||
843 | 46 | class ProcessorNotFound(NameLookupFailed): | ||
844 | 47 | """Exception raised when a processor name isn't found.""" | ||
845 | 48 | _message_prefix = 'No such processor' | ||
846 | 41 | 49 | ||
847 | 42 | 50 | ||
848 | 43 | class IProcessor(Interface): | 51 | class IProcessor(Interface): |
849 | @@ -49,7 +57,7 @@ | |||
850 | 49 | # the WADL generation work it must be back-dated to the earliest version. | 57 | # the WADL generation work it must be back-dated to the earliest version. |
851 | 50 | # Note that individual attributes and methods can and must truthfully set | 58 | # Note that individual attributes and methods can and must truthfully set |
852 | 51 | # 'devel' as their version. | 59 | # 'devel' as their version. |
854 | 52 | export_as_webservice_entry(publish_web_link=True, as_of='beta') | 60 | export_as_webservice_entry(publish_web_link=False, as_of='beta') |
855 | 53 | id = Attribute("The Processor ID") | 61 | id = Attribute("The Processor ID") |
856 | 54 | family = exported( | 62 | family = exported( |
857 | 55 | Reference( | 63 | Reference( |
858 | @@ -84,7 +92,7 @@ | |||
859 | 84 | # 'devel' as their version. | 92 | # 'devel' as their version. |
860 | 85 | export_as_webservice_entry( | 93 | export_as_webservice_entry( |
861 | 86 | plural_name='processor_families', | 94 | plural_name='processor_families', |
863 | 87 | publish_web_link=True, | 95 | publish_web_link=False, |
864 | 88 | as_of='beta') | 96 | as_of='beta') |
865 | 89 | 97 | ||
866 | 90 | id = Attribute("The ProcessorFamily ID") | 98 | id = Attribute("The ProcessorFamily ID") |
867 | @@ -123,14 +131,36 @@ | |||
868 | 123 | """ | 131 | """ |
869 | 124 | 132 | ||
870 | 125 | 133 | ||
871 | 134 | class IProcessorSet(Interface): | ||
872 | 135 | """Operations related to Processor instances.""" | ||
873 | 136 | export_as_webservice_collection(IProcessor) | ||
874 | 137 | |||
875 | 138 | @operation_parameters( | ||
876 | 139 | name=TextLine(required=True)) | ||
877 | 140 | @operation_returns_entry(IProcessor) | ||
878 | 141 | @export_read_operation() | ||
879 | 142 | @operation_for_version('devel') | ||
880 | 143 | def getByName(name): | ||
881 | 144 | """Return the IProcessor instance with the matching name. | ||
882 | 145 | |||
883 | 146 | :param name: The name to look for. | ||
884 | 147 | :raise ProcessorNotFound: if there is no processor with that name. | ||
885 | 148 | :return: A `IProcessor` instance if found | ||
886 | 149 | """ | ||
887 | 150 | |||
888 | 151 | @collection_default_content() | ||
889 | 152 | def getAll(): | ||
890 | 153 | """Return all the `IProcessor` known to Launchpad.""" | ||
891 | 154 | |||
892 | 155 | |||
893 | 126 | class IProcessorFamilySet(Interface): | 156 | class IProcessorFamilySet(Interface): |
894 | 127 | """Operations related to ProcessorFamily instances.""" | 157 | """Operations related to ProcessorFamily instances.""" |
895 | 128 | 158 | ||
897 | 129 | export_as_webservice_collection(Interface) | 159 | export_as_webservice_collection(IProcessorFamily) |
898 | 130 | 160 | ||
899 | 131 | @operation_parameters( | 161 | @operation_parameters( |
900 | 132 | name=TextLine(required=True)) | 162 | name=TextLine(required=True)) |
902 | 133 | @operation_returns_entry(Interface) | 163 | @operation_returns_entry(IProcessorFamily) |
903 | 134 | @export_read_operation() | 164 | @export_read_operation() |
904 | 135 | @operation_for_version('devel') | 165 | @operation_for_version('devel') |
905 | 136 | def getByName(name): | 166 | def getByName(name): |
906 | 137 | 167 | ||
907 | === modified file 'lib/lp/soyuz/interfaces/webservice.py' | |||
908 | --- lib/lp/soyuz/interfaces/webservice.py 2011-06-23 18:26:26 +0000 | |||
909 | +++ lib/lp/soyuz/interfaces/webservice.py 2011-07-21 19:34:45 +0000 | |||
910 | @@ -35,6 +35,7 @@ | |||
911 | 35 | 'IProcessor', | 35 | 'IProcessor', |
912 | 36 | 'IProcessorFamily', | 36 | 'IProcessorFamily', |
913 | 37 | 'IProcessorFamilySet', | 37 | 'IProcessorFamilySet', |
914 | 38 | 'IProcessorSet', | ||
915 | 38 | 'ISourcePackagePublishingHistory', | 39 | 'ISourcePackagePublishingHistory', |
916 | 39 | 'IncompatibleArguments', | 40 | 'IncompatibleArguments', |
917 | 40 | 'InsufficientUploadRights', | 41 | 'InsufficientUploadRights', |
918 | @@ -96,6 +97,7 @@ | |||
919 | 96 | IProcessor, | 97 | IProcessor, |
920 | 97 | IProcessorFamily, | 98 | IProcessorFamily, |
921 | 98 | IProcessorFamilySet, | 99 | IProcessorFamilySet, |
922 | 100 | IProcessorSet, | ||
923 | 99 | ) | 101 | ) |
924 | 100 | from lp.soyuz.interfaces.publishing import ( | 102 | from lp.soyuz.interfaces.publishing import ( |
925 | 101 | IBinaryPackagePublishingHistory, | 103 | IBinaryPackagePublishingHistory, |
926 | @@ -105,7 +107,6 @@ | |||
927 | 105 | 107 | ||
928 | 106 | from canonical.launchpad.components.apihelpers import ( | 108 | from canonical.launchpad.components.apihelpers import ( |
929 | 107 | patch_collection_property, | 109 | patch_collection_property, |
930 | 108 | patch_entry_return_type, | ||
931 | 109 | patch_plain_parameter_type, | 110 | patch_plain_parameter_type, |
932 | 110 | patch_reference_property, | 111 | patch_reference_property, |
933 | 111 | ) | 112 | ) |
934 | @@ -115,15 +116,10 @@ | |||
935 | 115 | from canonical.launchpad.interfaces import _schema_circular_imports | 116 | from canonical.launchpad.interfaces import _schema_circular_imports |
936 | 116 | _schema_circular_imports | 117 | _schema_circular_imports |
937 | 117 | 118 | ||
938 | 118 | from lazr.restful.declarations import LAZR_WEBSERVICE_EXPORTED | ||
939 | 119 | IProcessorFamilySet.queryTaggedValue( | ||
940 | 120 | LAZR_WEBSERVICE_EXPORTED)['collection_entry_schema'] = IProcessorFamily | ||
941 | 121 | |||
942 | 122 | # IProcessor | 119 | # IProcessor |
943 | 123 | patch_reference_property( | 120 | patch_reference_property( |
944 | 124 | IProcessor, 'family', IProcessorFamily) | 121 | IProcessor, 'family', IProcessorFamily) |
945 | 125 | 122 | ||
946 | 126 | patch_entry_return_type(IProcessorFamilySet, 'getByName', IProcessorFamily) | ||
947 | 127 | patch_collection_property( | 123 | patch_collection_property( |
948 | 128 | IArchive, 'enabled_restricted_families', IProcessorFamily) | 124 | IArchive, 'enabled_restricted_families', IProcessorFamily) |
949 | 129 | patch_plain_parameter_type( | 125 | patch_plain_parameter_type( |
950 | 130 | 126 | ||
951 | === modified file 'lib/lp/soyuz/model/processor.py' | |||
952 | --- lib/lp/soyuz/model/processor.py 2010-11-12 02:15:28 +0000 | |||
953 | +++ lib/lp/soyuz/model/processor.py 2011-07-21 19:34:45 +0000 | |||
954 | @@ -25,6 +25,8 @@ | |||
955 | 25 | IProcessor, | 25 | IProcessor, |
956 | 26 | IProcessorFamily, | 26 | IProcessorFamily, |
957 | 27 | IProcessorFamilySet, | 27 | IProcessorFamilySet, |
958 | 28 | IProcessorSet, | ||
959 | 29 | ProcessorNotFound, | ||
960 | 28 | ) | 30 | ) |
961 | 29 | 31 | ||
962 | 30 | 32 | ||
963 | @@ -42,6 +44,24 @@ | |||
964 | 42 | return "<Processor %r>" % self.title | 44 | return "<Processor %r>" % self.title |
965 | 43 | 45 | ||
966 | 44 | 46 | ||
967 | 47 | class ProcessorSet: | ||
968 | 48 | """See `IProcessorSet`.""" | ||
969 | 49 | implements(IProcessorSet) | ||
970 | 50 | |||
971 | 51 | def getByName(self, name): | ||
972 | 52 | """See `IProcessorSet`.""" | ||
973 | 53 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) | ||
974 | 54 | processor = store.find(Processor, Processor.name == name).one() | ||
975 | 55 | if processor is None: | ||
976 | 56 | raise ProcessorNotFound(name) | ||
977 | 57 | return processor | ||
978 | 58 | |||
979 | 59 | def getAll(self): | ||
980 | 60 | """See `IProcessorSet`.""" | ||
981 | 61 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) | ||
982 | 62 | return store.find(Processor) | ||
983 | 63 | |||
984 | 64 | |||
985 | 45 | class ProcessorFamily(SQLBase): | 65 | class ProcessorFamily(SQLBase): |
986 | 46 | implements(IProcessorFamily) | 66 | implements(IProcessorFamily) |
987 | 47 | _table = 'ProcessorFamily' | 67 | _table = 'ProcessorFamily' |
988 | @@ -64,6 +84,7 @@ | |||
989 | 64 | 84 | ||
990 | 65 | class ProcessorFamilySet: | 85 | class ProcessorFamilySet: |
991 | 66 | implements(IProcessorFamilySet) | 86 | implements(IProcessorFamilySet) |
992 | 87 | |||
993 | 67 | def getByName(self, name): | 88 | def getByName(self, name): |
994 | 68 | """Please see `IProcessorFamilySet`.""" | 89 | """Please see `IProcessorFamilySet`.""" |
995 | 69 | # Please note that ProcessorFamily.name is unique i.e. the database | 90 | # Please note that ProcessorFamily.name is unique i.e. the database |
996 | 70 | 91 | ||
997 | === added file 'lib/lp/soyuz/security.py' | |||
998 | --- lib/lp/soyuz/security.py 1970-01-01 00:00:00 +0000 | |||
999 | +++ lib/lp/soyuz/security.py 2011-07-21 19:34:45 +0000 | |||
1000 | @@ -0,0 +1,26 @@ | |||
1001 | 1 | # Copyright 2011 Canonical Ltd. This software is licensed under the | ||
1002 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
1003 | 3 | |||
1004 | 4 | """Security adapters for the soyuz module.""" | ||
1005 | 5 | |||
1006 | 6 | __metaclass__ = type | ||
1007 | 7 | __all__ = [ | ||
1008 | 8 | 'ViewProcessor', | ||
1009 | 9 | 'ViewProcessorFamily', | ||
1010 | 10 | ] | ||
1011 | 11 | |||
1012 | 12 | from lp.app.security import AnonymousAuthorization | ||
1013 | 13 | from lp.soyuz.interfaces.processor import ( | ||
1014 | 14 | IProcessor, | ||
1015 | 15 | IProcessorFamily, | ||
1016 | 16 | ) | ||
1017 | 17 | |||
1018 | 18 | |||
1019 | 19 | class ViewProcessor(AnonymousAuthorization): | ||
1020 | 20 | """Anyone can view an `IProcessor`.""" | ||
1021 | 21 | usedfor = IProcessor | ||
1022 | 22 | |||
1023 | 23 | |||
1024 | 24 | class ViewProcessorFamily(AnonymousAuthorization): | ||
1025 | 25 | """Anyone can view an `IProcessorFamily`.""" | ||
1026 | 26 | usedfor = IProcessorFamily | ||
1027 | 0 | 27 | ||
1028 | === modified file 'lib/lp/soyuz/tests/test_packagecloner.py' | |||
1029 | --- lib/lp/soyuz/tests/test_packagecloner.py 2011-06-23 09:31:34 +0000 | |||
1030 | +++ lib/lp/soyuz/tests/test_packagecloner.py 2011-07-21 19:34:45 +0000 | |||
1031 | @@ -344,7 +344,6 @@ | |||
1032 | 344 | # This is a processor family without a DAS in the source, so | 344 | # This is a processor family without a DAS in the source, so |
1033 | 345 | # we expect no builds. | 345 | # we expect no builds. |
1034 | 346 | family = self.factory.makeProcessorFamily(name="armel") | 346 | family = self.factory.makeProcessorFamily(name="armel") |
1035 | 347 | self.factory.makeProcessor(family=family, name="armel") | ||
1036 | 348 | proc_families = [family] | 347 | proc_families = [family] |
1037 | 349 | copy_archive, distroseries = self.makeCopyArchive( | 348 | copy_archive, distroseries = self.makeCopyArchive( |
1038 | 350 | [package_info], proc_families=proc_families) | 349 | [package_info], proc_families=proc_families) |
1039 | @@ -357,7 +356,6 @@ | |||
1040 | 357 | # One of these processor families has a DAS in the source, so | 356 | # One of these processor families has a DAS in the source, so |
1041 | 358 | # we expect one set of builds | 357 | # we expect one set of builds |
1042 | 359 | family = self.factory.makeProcessorFamily(name="armel") | 358 | family = self.factory.makeProcessorFamily(name="armel") |
1043 | 360 | self.factory.makeProcessor(family=family, name="armel") | ||
1044 | 361 | proc_families = [family, ProcessorFamilySet().getByName("x86")] | 359 | proc_families = [family, ProcessorFamilySet().getByName("x86")] |
1045 | 362 | copy_archive, distroseries = self.makeCopyArchive( | 360 | copy_archive, distroseries = self.makeCopyArchive( |
1046 | 363 | [package_info], proc_families=proc_families) | 361 | [package_info], proc_families=proc_families) |
1047 | @@ -399,7 +397,6 @@ | |||
1048 | 399 | copy_archive, distroseries, proc_families=proc_families) | 397 | copy_archive, distroseries, proc_families=proc_families) |
1049 | 400 | self.checkBuilds(copy_archive, [package_info, package_info]) | 398 | self.checkBuilds(copy_archive, [package_info, package_info]) |
1050 | 401 | 399 | ||
1051 | 402 | |||
1052 | 403 | def diffArchives(self, target_archive, target_distroseries, | 400 | def diffArchives(self, target_archive, target_distroseries, |
1053 | 404 | source_archive=None, source_distroseries=None): | 401 | source_archive=None, source_distroseries=None): |
1054 | 405 | """Run a packageSetDiff of two archives.""" | 402 | """Run a packageSetDiff of two archives.""" |
1055 | @@ -423,6 +420,7 @@ | |||
1056 | 423 | expected_changed_tuples = [(e.name, e.version) | 420 | expected_changed_tuples = [(e.name, e.version) |
1057 | 424 | for e in expected_changed] | 421 | for e in expected_changed] |
1058 | 425 | expected_new_tuples = [(e.name, e.version) for e in expected_new] | 422 | expected_new_tuples = [(e.name, e.version) for e in expected_new] |
1059 | 423 | |||
1060 | 426 | def get_tuples(source_keys): | 424 | def get_tuples(source_keys): |
1061 | 427 | tuples = [] | 425 | tuples = [] |
1062 | 428 | for source_key in source_keys: | 426 | for source_key in source_keys: |
1063 | @@ -503,7 +501,6 @@ | |||
1064 | 503 | [package_infos[0]], [package_infos[1]], diff, | 501 | [package_infos[0]], [package_infos[1]], diff, |
1065 | 504 | distroseries.distribution.main_archive) | 502 | distroseries.distribution.main_archive) |
1066 | 505 | 503 | ||
1067 | 506 | |||
1068 | 507 | def mergeCopy(self, target_archive, target_distroseries, | 504 | def mergeCopy(self, target_archive, target_distroseries, |
1069 | 508 | source_archive=None, source_distroseries=None): | 505 | source_archive=None, source_distroseries=None): |
1070 | 509 | if source_distroseries is None: | 506 | if source_distroseries is None: |
1071 | 510 | 507 | ||
1072 | === modified file 'lib/lp/soyuz/tests/test_processor.py' | |||
1073 | --- lib/lp/soyuz/tests/test_processor.py 2010-10-04 19:50:45 +0000 | |||
1074 | +++ lib/lp/soyuz/tests/test_processor.py 2011-07-21 19:34:45 +0000 | |||
1075 | @@ -5,13 +5,29 @@ | |||
1076 | 5 | 5 | ||
1077 | 6 | from zope.component import getUtility | 6 | from zope.component import getUtility |
1078 | 7 | 7 | ||
1080 | 8 | from canonical.testing.layers import LaunchpadZopelessLayer | 8 | from canonical.launchpad.webapp.interfaces import ( |
1081 | 9 | DEFAULT_FLAVOR, | ||
1082 | 10 | IStoreSelector, | ||
1083 | 11 | MAIN_STORE, | ||
1084 | 12 | ) | ||
1085 | 13 | from canonical.launchpad.testing.pages import LaunchpadWebServiceCaller | ||
1086 | 14 | from canonical.testing.layers import ( | ||
1087 | 15 | DatabaseFunctionalLayer, | ||
1088 | 16 | LaunchpadZopelessLayer, | ||
1089 | 17 | ) | ||
1090 | 18 | |||
1091 | 9 | from lp.soyuz.interfaces.processor import ( | 19 | from lp.soyuz.interfaces.processor import ( |
1092 | 10 | IProcessor, | 20 | IProcessor, |
1093 | 11 | IProcessorFamily, | 21 | IProcessorFamily, |
1094 | 12 | IProcessorFamilySet, | 22 | IProcessorFamilySet, |
1097 | 13 | ) | 23 | IProcessorSet, |
1098 | 14 | from lp.testing import TestCaseWithFactory | 24 | ProcessorNotFound, |
1099 | 25 | ) | ||
1100 | 26 | from lp.testing import ( | ||
1101 | 27 | ExpectedException, | ||
1102 | 28 | logout, | ||
1103 | 29 | TestCaseWithFactory, | ||
1104 | 30 | ) | ||
1105 | 15 | 31 | ||
1106 | 16 | 32 | ||
1107 | 17 | class ProcessorFamilyTests(TestCaseWithFactory): | 33 | class ProcessorFamilyTests(TestCaseWithFactory): |
1108 | @@ -29,16 +45,83 @@ | |||
1109 | 29 | """Test adding a new Processor to a ProcessorFamily.""" | 45 | """Test adding a new Processor to a ProcessorFamily.""" |
1110 | 30 | family = getUtility(IProcessorFamilySet).new("avr", "Atmel AVR", | 46 | family = getUtility(IProcessorFamilySet).new("avr", "Atmel AVR", |
1111 | 31 | "The Modified Harvard architecture 8-bit RISC processors.") | 47 | "The Modified Harvard architecture 8-bit RISC processors.") |
1113 | 32 | proc = family.addProcessor("avr2001", "The 2001 AVR", "Fast as light.") | 48 | proc = family.addProcessor( |
1114 | 49 | "avr2001", "The 2001 AVR", "Fast as light.") | ||
1115 | 33 | self.assertProvides(proc, IProcessor) | 50 | self.assertProvides(proc, IProcessor) |
1116 | 34 | self.assertEquals(family, proc.family) | 51 | self.assertEquals(family, proc.family) |
1117 | 35 | 52 | ||
1118 | 36 | def test_get_restricted(self): | 53 | def test_get_restricted(self): |
1119 | 37 | """Test retrieving all restricted processors.""" | 54 | """Test retrieving all restricted processors.""" |
1120 | 38 | family_set = getUtility(IProcessorFamilySet) | 55 | family_set = getUtility(IProcessorFamilySet) |
1122 | 39 | normal_family = getUtility(IProcessorFamilySet).new("avr", "Atmel AVR", | 56 | normal_family = getUtility(IProcessorFamilySet).new( |
1123 | 57 | "avr", "Atmel AVR", | ||
1124 | 40 | "The Modified Harvard architecture 8-bit RISC processors.") | 58 | "The Modified Harvard architecture 8-bit RISC processors.") |
1127 | 41 | restricted_family = getUtility(IProcessorFamilySet).new("5051", "5051", | 59 | restricted_family = getUtility(IProcessorFamilySet).new( |
1128 | 42 | "Another small processor family", restricted=True) | 60 | "5051", "5051", "Another small processor family", |
1129 | 61 | restricted=True) | ||
1130 | 43 | self.assertFalse(normal_family in family_set.getRestricted()) | 62 | self.assertFalse(normal_family in family_set.getRestricted()) |
1131 | 44 | self.assertTrue(restricted_family in family_set.getRestricted()) | 63 | self.assertTrue(restricted_family in family_set.getRestricted()) |
1132 | 64 | |||
1133 | 65 | |||
1134 | 66 | class ProcessorSetTests(TestCaseWithFactory): | ||
1135 | 67 | layer = DatabaseFunctionalLayer | ||
1136 | 68 | |||
1137 | 69 | def test_getByName(self): | ||
1138 | 70 | processor_set = getUtility(IProcessorSet) | ||
1139 | 71 | q1 = self.factory.makeProcessorFamily(name='q1') | ||
1140 | 72 | self.assertEquals(q1.processors[0], processor_set.getByName('q1')) | ||
1141 | 73 | |||
1142 | 74 | def test_getByName_not_found(self): | ||
1143 | 75 | processor_set = getUtility(IProcessorSet) | ||
1144 | 76 | with ExpectedException(ProcessorNotFound, 'No such processor.*'): | ||
1145 | 77 | processor_set.getByName('q1') | ||
1146 | 78 | |||
1147 | 79 | def test_getAll(self): | ||
1148 | 80 | processor_set = getUtility(IProcessorSet) | ||
1149 | 81 | # Make it easy to filter out sample data | ||
1150 | 82 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) | ||
1151 | 83 | store.execute("UPDATE Processor SET name = 'sample_data_' || name") | ||
1152 | 84 | self.factory.makeProcessorFamily(name='q1') | ||
1153 | 85 | self.factory.makeProcessorFamily(name='i686') | ||
1154 | 86 | self.factory.makeProcessorFamily(name='g4') | ||
1155 | 87 | self.assertEquals( | ||
1156 | 88 | ['g4', 'i686', 'q1'], | ||
1157 | 89 | sorted( | ||
1158 | 90 | processor.name for processor in processor_set.getAll() | ||
1159 | 91 | if not processor.name.startswith('sample_data_'))) | ||
1160 | 92 | |||
1161 | 93 | |||
1162 | 94 | class ProcessorSetWebServiceTests(TestCaseWithFactory): | ||
1163 | 95 | layer = DatabaseFunctionalLayer | ||
1164 | 96 | |||
1165 | 97 | def setUp(self): | ||
1166 | 98 | super(ProcessorSetWebServiceTests, self).setUp() | ||
1167 | 99 | self.webservice = LaunchpadWebServiceCaller() | ||
1168 | 100 | |||
1169 | 101 | def test_getByName(self): | ||
1170 | 102 | self.factory.makeProcessorFamily(name='transmeta') | ||
1171 | 103 | logout() | ||
1172 | 104 | |||
1173 | 105 | processor = self.webservice.named_get( | ||
1174 | 106 | '/+processors', 'getByName', name='transmeta', | ||
1175 | 107 | api_version='devel', | ||
1176 | 108 | ).jsonBody() | ||
1177 | 109 | self.assertEquals('transmeta', processor['name']) | ||
1178 | 110 | |||
1179 | 111 | def test_default_collection(self): | ||
1180 | 112 | # Make it easy to filter out sample data | ||
1181 | 113 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) | ||
1182 | 114 | store.execute("UPDATE Processor SET name = 'sample_data_' || name") | ||
1183 | 115 | self.factory.makeProcessorFamily(name='q1') | ||
1184 | 116 | self.factory.makeProcessorFamily(name='i686') | ||
1185 | 117 | self.factory.makeProcessorFamily(name='g4') | ||
1186 | 118 | |||
1187 | 119 | logout() | ||
1188 | 120 | |||
1189 | 121 | collection = self.webservice.get( | ||
1190 | 122 | '/+processors?ws.size=10', api_version='devel').jsonBody() | ||
1191 | 123 | self.assertEquals( | ||
1192 | 124 | ['g4', 'i686', 'q1'], | ||
1193 | 125 | sorted( | ||
1194 | 126 | processor['name'] for processor in collection['entries'] | ||
1195 | 127 | if not processor['name'].startswith('sample_data_'))) | ||
1196 | 45 | 128 | ||
1197 | === modified file 'lib/lp/testing/__init__.py' | |||
1198 | --- lib/lp/testing/__init__.py 2011-07-18 14:07:48 +0000 | |||
1199 | +++ lib/lp/testing/__init__.py 2011-07-21 19:34:45 +0000 | |||
1200 | @@ -618,6 +618,17 @@ | |||
1201 | 618 | self._unfoldEmailHeader(expected), | 618 | self._unfoldEmailHeader(expected), |
1202 | 619 | self._unfoldEmailHeader(observed)) | 619 | self._unfoldEmailHeader(observed)) |
1203 | 620 | 620 | ||
1204 | 621 | def assertStartsWith(self, s, prefix): | ||
1205 | 622 | if not s.startswith(prefix): | ||
1206 | 623 | raise AssertionError( | ||
1207 | 624 | 'string %r does not start with %r' % (s, prefix)) | ||
1208 | 625 | |||
1209 | 626 | def assertEndsWith(self, s, suffix): | ||
1210 | 627 | """Asserts that s ends with suffix.""" | ||
1211 | 628 | if not s.endswith(suffix): | ||
1212 | 629 | raise AssertionError( | ||
1213 | 630 | 'string %r does not end with %r' % (s, suffix)) | ||
1214 | 631 | |||
1215 | 621 | 632 | ||
1216 | 622 | class TestCaseWithFactory(TestCase): | 633 | class TestCaseWithFactory(TestCase): |
1217 | 623 | 634 | ||
1218 | 624 | 635 | ||
1219 | === modified file 'lib/lp/testing/factory.py' | |||
1220 | --- lib/lp/testing/factory.py 2011-07-19 13:51:36 +0000 | |||
1221 | +++ lib/lp/testing/factory.py 2011-07-21 19:34:45 +0000 | |||
1222 | @@ -894,6 +894,9 @@ | |||
1223 | 894 | restricted=False): | 894 | restricted=False): |
1224 | 895 | """Create a new processor family. | 895 | """Create a new processor family. |
1225 | 896 | 896 | ||
1226 | 897 | A default processor for the family will be created with the | ||
1227 | 898 | same name as the family. | ||
1228 | 899 | |||
1229 | 897 | :param name: Name of the family (e.g. x86) | 900 | :param name: Name of the family (e.g. x86) |
1230 | 898 | :param title: Optional title of the family | 901 | :param title: Optional title of the family |
1231 | 899 | :param description: Optional extended description | 902 | :param description: Optional extended description |
1232 | @@ -906,11 +909,11 @@ | |||
1233 | 906 | description = "Description of the %s processor family" % name | 909 | description = "Description of the %s processor family" % name |
1234 | 907 | if title is None: | 910 | if title is None: |
1235 | 908 | title = "%s and compatible processors." % name | 911 | title = "%s and compatible processors." % name |
1238 | 909 | family = getUtility(IProcessorFamilySet).new(name, title, description, | 912 | family = getUtility(IProcessorFamilySet).new( |
1239 | 910 | restricted=restricted) | 913 | name, title, description, restricted=restricted) |
1240 | 911 | # Make sure there's at least one processor in the family, so that | 914 | # Make sure there's at least one processor in the family, so that |
1241 | 912 | # other things can have a default processor. | 915 | # other things can have a default processor. |
1243 | 913 | self.makeProcessor(family=family) | 916 | self.makeProcessor(name=name, family=family) |
1244 | 914 | return family | 917 | return family |
1245 | 915 | 918 | ||
1246 | 916 | def makeProductRelease(self, milestone=None, product=None, | 919 | def makeProductRelease(self, milestone=None, product=None, |
1247 | 917 | 920 | ||
1248 | === modified file 'lib/lp/testing/publication.py' | |||
1249 | --- lib/lp/testing/publication.py 2010-10-03 15:30:06 +0000 | |||
1250 | +++ lib/lp/testing/publication.py 2011-07-21 19:34:45 +0000 | |||
1251 | @@ -20,6 +20,7 @@ | |||
1252 | 20 | from zope.interface import providedBy | 20 | from zope.interface import providedBy |
1253 | 21 | from zope.publisher.interfaces.browser import IDefaultSkin | 21 | from zope.publisher.interfaces.browser import IDefaultSkin |
1254 | 22 | from zope.security.management import restoreInteraction | 22 | from zope.security.management import restoreInteraction |
1255 | 23 | from zope.security.proxy import removeSecurityProxy | ||
1256 | 23 | 24 | ||
1257 | 24 | from canonical.launchpad.interfaces.launchpad import IOpenLaunchBag | 25 | from canonical.launchpad.interfaces.launchpad import IOpenLaunchBag |
1258 | 25 | import canonical.launchpad.layers as layers | 26 | import canonical.launchpad.layers as layers |
1259 | @@ -30,6 +31,7 @@ | |||
1260 | 30 | ) | 31 | ) |
1261 | 31 | from canonical.launchpad.webapp.servers import ProtocolErrorPublication | 32 | from canonical.launchpad.webapp.servers import ProtocolErrorPublication |
1262 | 32 | 33 | ||
1263 | 34 | |||
1264 | 33 | # Defines an helper function that returns the appropriate | 35 | # Defines an helper function that returns the appropriate |
1265 | 34 | # IRequest and IPublication. | 36 | # IRequest and IPublication. |
1266 | 35 | def get_request_and_publication(host='localhost', port=None, | 37 | def get_request_and_publication(host='localhost', port=None, |
1267 | @@ -119,9 +121,15 @@ | |||
1268 | 119 | getUtility(IOpenLaunchBag).clear() | 121 | getUtility(IOpenLaunchBag).clear() |
1269 | 120 | app = publication.getApplication(request) | 122 | app = publication.getApplication(request) |
1270 | 121 | view = request.traverse(app) | 123 | view = request.traverse(app) |
1274 | 122 | # Since the last traversed object is the view, the second last should be | 124 | # Find the object from the view instead on relying that it stays |
1275 | 123 | # the object that the view is on. | 125 | # in the traversed_objects stack. That doesn't apply to the web |
1276 | 124 | obj = request.traversed_objects[-2] | 126 | # service for example. |
1277 | 127 | try: | ||
1278 | 128 | obj = removeSecurityProxy(view).context | ||
1279 | 129 | except AttributeError: | ||
1280 | 130 | # But sometime the view didn't store the context... | ||
1281 | 131 | # Use the last traversed object in these cases. | ||
1282 | 132 | obj = request.traversed_objects[-2] | ||
1283 | 125 | 133 | ||
1284 | 126 | restoreInteraction() | 134 | restoreInteraction() |
1285 | 127 | 135 | ||
1286 | 128 | 136 | ||
1287 | === modified file 'lib/lp/testing/tests/test_publication.py' | |||
1288 | --- lib/lp/testing/tests/test_publication.py 2010-10-04 19:50:45 +0000 | |||
1289 | +++ lib/lp/testing/tests/test_publication.py 2011-07-21 19:34:45 +0000 | |||
1290 | @@ -23,6 +23,7 @@ | |||
1291 | 23 | from canonical.launchpad.webapp.publisher import get_current_browser_request | 23 | from canonical.launchpad.webapp.publisher import get_current_browser_request |
1292 | 24 | from canonical.launchpad.webapp.servers import LaunchpadTestRequest | 24 | from canonical.launchpad.webapp.servers import LaunchpadTestRequest |
1293 | 25 | from canonical.testing.layers import DatabaseFunctionalLayer | 25 | from canonical.testing.layers import DatabaseFunctionalLayer |
1294 | 26 | from lazr.restful import EntryResource | ||
1295 | 26 | from lp.testing import ( | 27 | from lp.testing import ( |
1296 | 27 | ANONYMOUS, | 28 | ANONYMOUS, |
1297 | 28 | login, | 29 | login, |
1298 | @@ -45,15 +46,17 @@ | |||
1299 | 45 | """ | 46 | """ |
1300 | 46 | # This method is completely out of control. Thanks, Zope. | 47 | # This method is completely out of control. Thanks, Zope. |
1301 | 47 | name = '+' + self.factory.getUniqueString() | 48 | name = '+' + self.factory.getUniqueString() |
1302 | 49 | |||
1303 | 48 | class new_class(simple): | 50 | class new_class(simple): |
1304 | 49 | def __init__(self, context, request): | 51 | def __init__(self, context, request): |
1305 | 52 | self.context = context | ||
1306 | 50 | view_callable() | 53 | view_callable() |
1307 | 51 | required = {} | 54 | required = {} |
1308 | 52 | for n in ('browserDefault', '__call__', 'publishTraverse'): | 55 | for n in ('browserDefault', '__call__', 'publishTraverse'): |
1309 | 53 | required[n] = CheckerPublic | 56 | required[n] = CheckerPublic |
1310 | 54 | defineChecker(new_class, Checker(required)) | 57 | defineChecker(new_class, Checker(required)) |
1311 | 55 | getSiteManager().registerAdapter( | 58 | getSiteManager().registerAdapter( |
1313 | 56 | new_class, (ILaunchpadRoot, IDefaultBrowserLayer), Interface, | 59 | new_class, (ILaunchpadRoot, IDefaultBrowserLayer), Interface, |
1314 | 57 | name) | 60 | name) |
1315 | 58 | self.addCleanup( | 61 | self.addCleanup( |
1316 | 59 | getSiteManager().unregisterAdapter, new_class, | 62 | getSiteManager().unregisterAdapter, new_class, |
1317 | @@ -74,6 +77,7 @@ | |||
1318 | 74 | # traversal in the sense of get_current_browser_request. | 77 | # traversal in the sense of get_current_browser_request. |
1319 | 75 | login(ANONYMOUS) | 78 | login(ANONYMOUS) |
1320 | 76 | requests = [] | 79 | requests = [] |
1321 | 80 | |||
1322 | 77 | def record_current_request(): | 81 | def record_current_request(): |
1323 | 78 | requests.append(get_current_browser_request()) | 82 | requests.append(get_current_browser_request()) |
1324 | 79 | context, view, request = test_traverse( | 83 | context, view, request = test_traverse( |
1325 | @@ -96,9 +100,18 @@ | |||
1326 | 96 | person = self.factory.makePerson() | 100 | person = self.factory.makePerson() |
1327 | 97 | login_person(person) | 101 | login_person(person) |
1328 | 98 | users = [] | 102 | users = [] |
1329 | 103 | |||
1330 | 99 | def record_user(): | 104 | def record_user(): |
1331 | 100 | users.append(getUtility(ILaunchBag).user) | 105 | users.append(getUtility(ILaunchBag).user) |
1332 | 101 | context, view, request = test_traverse( | 106 | context, view, request = test_traverse( |
1333 | 102 | self.registerViewCallable(record_user)) | 107 | self.registerViewCallable(record_user)) |
1334 | 103 | self.assertEqual(1, len(users)) | 108 | self.assertEqual(1, len(users)) |
1335 | 104 | self.assertEqual(person, users[0]) | 109 | self.assertEqual(person, users[0]) |
1336 | 110 | |||
1337 | 111 | def test_webservice_traverse(self): | ||
1338 | 112 | login(ANONYMOUS) | ||
1339 | 113 | product = self.factory.makeProduct() | ||
1340 | 114 | context, view, request = test_traverse( | ||
1341 | 115 | 'http://api.launchpad.dev/devel/' + product.name) | ||
1342 | 116 | self.assertEqual(product, context) | ||
1343 | 117 | self.assertIsInstance(view, EntryResource) |
Another peculiar I forgot about: the test_traverse() helper wasn't working for API requests, I fixed. that.