Merge lp:~wgrant/launchpad/move-rescueiflost-tests into lp:launchpad
- move-rescueiflost-tests
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Michael Hudson-Doyle |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~wgrant/launchpad/move-rescueiflost-tests |
Merge into: | lp:launchpad |
Prerequisite: | lp:~wgrant/launchpad/more-buildmaster-cleanup |
Diff against target: |
702 lines (+204/-284) 6 files modified
lib/lp/buildmaster/doc/builder.txt (+135/-93) lib/lp/buildmaster/doc/buildfarmjobbehavior.txt (+8/-0) lib/lp/buildmaster/tests/test_manager.py (+3/-3) lib/lp/code/tests/test_recipebuilder.py (+4/-4) lib/lp/soyuz/doc/buildd-slavescanner.txt (+3/-84) lib/lp/soyuz/tests/soyuzbuilddhelpers.py (+51/-100) |
To merge this branch: | bzr merge lp:~wgrant/launchpad/move-rescueiflost-tests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Nelson (community) | code | Approve | |
Brad Crittenden (community) | Needs Information | ||
Review via email: mp+22737@code.launchpad.net |
Commit message
Move the rescueIfLost tests out of buildd-
Description of the change
This branch moves buildd-
It replaces a tonne of mock slaves with a couple of configurable ones, and fixes a couple of other tests around the tree that used the condemned mocks. It also replaces with a test of IdleBuildBehavi
I also rewrote the start of builder.txt, since its style offended me.
William Grant (wgrant) wrote : | # |
On Tue, 2010-04-06 at 15:51 +0000, Brad Crittenden wrote:
> Review: Needs Information
> Hi William,
>
> This branch looks great. As we discussed on IRC please run it past a Soyuz team member and I'll approve it.
Thanks for the review, Brad.
> > === modified file 'lib/lp/
> > --- lib/lp/
> > +++ lib/lp/
> > @@ -2,36 +2,34 @@
> > Builder Class
> > =============
> >
> > -This test aims to meet the requirements of
> > -<https:/
> > -which represents the Buildd Slave entity.
> > -
> > -Need auxiliar methods from zope toolchain:
> > +The Builder class represents a slave machine in the build farm. These
> > +slaves are used to execute untrusted code -- for example when building
> > +packages.
> > +
> > +There are several builders in the sample data. Let's examine the first.
> > +
> > + >>> from lp.buildmaster.
> > + >>> builder = Builder.get(1)
> > +
> > +As expected, it implements IBuilder.
>
> Nice changes.
>
> > +Builders can take on different behaviors depending on the type of build
> > +they are currently processing. Each builder provides an attribute
> > +(current_
> > +for the current build is delegated. In the sample data, bob's current
> > +behavior is the behavior for dealing with binary packages
>
> How about:
> "behavior is dealing with binary packages."
> (Note full-stop.)
>
> > >>> from zope.security.proxy import isinstance
> > >>> from lp.soyuz.
> > @@ -40,85 +38,77 @@
> > ... builder.
> > True
> >
> > -Confirm we can get the slave xmlrpc interface
> > +A builder has an XML-RPC proxy in the 'slave' attribute, which allows
> > +us to easily call methods on the slave machines.
> >
> > >>> s = builder.slave
> >
> > -Confirm that the urlbase is correct in that slave. (If the protocol changes,
> > -this may change too)
> > +The base URL of the proxy matches the builder's URL.
> >
> > >>> s.urlbase == builder.url
> > True
> >
> > -Check if the instance corresponds to the declared interface:
> > -
> > - >>> verifyObject(
> > - True
> > -
> >
> > BuilderSet
> > ==========
> >
> > -Now perform the tests for the Builder ContentSet class, BuilderSet.
> > -
> > -Check if it can be imported:
> > -
> > +Builders and groups thereof are managed through a utility, IBuilderSet.
> > +
> > + >>> from zope.component import getUtility
> > >>> from lp.buildmaster.
> > -
> > -Check we can use the set as a utility:
> > -
> > >>> builderset = getUtility(
> > -
> > -Check if the instance returned as utility corresponds to its
> > -respective interface:
> > -
> > >>> verifyObject(
> > True
> >
> > -Check if the instance is iterable:
> > +Iterating over a BuilderSet yields all registered builders.
...
Michael Nelson (michael.nelson) wrote : | # |
I *think* Jelmer is actually working on refactoring a lot of buildd-slavescanner into unit-tests (but I can't see the branch), so it'd be worthwhile chatting with him about it (he might just handle the conflicts). Other than that, I'm all for it :)
Preview Diff
1 | === modified file 'lib/lp/buildmaster/doc/builder.txt' | |||
2 | --- lib/lp/buildmaster/doc/builder.txt 2010-03-24 10:24:22 +0000 | |||
3 | +++ lib/lp/buildmaster/doc/builder.txt 2010-04-09 00:40:18 +0000 | |||
4 | @@ -2,36 +2,34 @@ | |||
5 | 2 | Builder Class | 2 | Builder Class |
6 | 3 | ============= | 3 | ============= |
7 | 4 | 4 | ||
13 | 5 | This test aims to meet the requirements of | 5 | The Builder class represents a slave machine in the build farm. These |
14 | 6 | <https://launchpad.canonical.com/BasicTestCoverage> for the Builder class, | 6 | slaves are used to execute untrusted code -- for example when building |
15 | 7 | which represents the Buildd Slave entity. | 7 | packages. |
16 | 8 | 8 | ||
17 | 9 | Need auxiliar methods from zope toolchain: | 9 | There are several builders in the sample data. Let's examine the first. |
18 | 10 | |||
19 | 11 | >>> from lp.buildmaster.model.builder import Builder | ||
20 | 12 | >>> builder = Builder.get(1) | ||
21 | 13 | |||
22 | 14 | As expected, it implements IBuilder. | ||
23 | 10 | 15 | ||
24 | 11 | >>> from canonical.launchpad.webapp.testing import verifyObject | 16 | >>> from canonical.launchpad.webapp.testing import verifyObject |
25 | 12 | >>> from zope.component import getUtility | ||
26 | 13 | |||
27 | 14 | Importing Builder content class and its interface: | ||
28 | 15 | |||
29 | 16 | >>> from lp.buildmaster.model.builder import Builder | ||
30 | 17 | >>> from lp.buildmaster.interfaces.builder import IBuilder | 17 | >>> from lp.buildmaster.interfaces.builder import IBuilder |
37 | 18 | 18 | >>> verifyObject(IBuilder, builder) | |
38 | 19 | Get an instance of Builder from the current sampledata: | 19 | True |
33 | 20 | |||
34 | 21 | >>> builder = Builder.get(1) | ||
35 | 22 | |||
36 | 23 | Test some attributes: | ||
39 | 24 | 20 | ||
40 | 25 | >>> print builder.name | 21 | >>> print builder.name |
41 | 26 | bob | 22 | bob |
50 | 27 | 23 | >>> print builder.builderok | |
51 | 28 | >>> builder.builderok = True | 24 | True |
52 | 29 | >>> builder.failnotes = None | 25 | >>> print builder.failnotes |
53 | 30 | 26 | None | |
54 | 31 | A builder provides a current_build_behavior attribute to which all the | 27 | |
55 | 32 | build-type specific behavior for the current build is delegated. For | 28 | Builders can take on different behaviors depending on the type of build |
56 | 33 | our sample data, the behavior is specifically a behavior for dealing | 29 | they are currently processing. Each builder provides an attribute |
57 | 34 | with binary packages | 30 | (current_build_behavior) to which all the build-type specific behavior |
58 | 31 | for the current build is delegated. In the sample data, bob's current | ||
59 | 32 | behavior is dealing with binary packages. | ||
60 | 35 | 33 | ||
61 | 36 | >>> from zope.security.proxy import isinstance | 34 | >>> from zope.security.proxy import isinstance |
62 | 37 | >>> from lp.soyuz.model.binarypackagebuildbehavior import ( | 35 | >>> from lp.soyuz.model.binarypackagebuildbehavior import ( |
63 | @@ -40,86 +38,78 @@ | |||
64 | 40 | ... builder.current_build_behavior, BinaryPackageBuildBehavior) | 38 | ... builder.current_build_behavior, BinaryPackageBuildBehavior) |
65 | 41 | True | 39 | True |
66 | 42 | 40 | ||
68 | 43 | Confirm we can get the slave xmlrpc interface | 41 | A builder has an XML-RPC proxy in the 'slave' attribute, which allows |
69 | 42 | us to easily call methods on the slave machines. | ||
70 | 44 | 43 | ||
71 | 45 | >>> s = builder.slave | 44 | >>> s = builder.slave |
72 | 46 | 45 | ||
75 | 47 | Confirm that the urlbase is correct in that slave. (If the protocol changes, | 46 | The base URL of the proxy matches the builder's URL. |
74 | 48 | this may change too) | ||
76 | 49 | 47 | ||
77 | 50 | >>> s.urlbase == builder.url | 48 | >>> s.urlbase == builder.url |
78 | 51 | True | 49 | True |
79 | 52 | 50 | ||
80 | 53 | Check if the instance corresponds to the declared interface: | ||
81 | 54 | |||
82 | 55 | >>> verifyObject(IBuilder, builder) | ||
83 | 56 | True | ||
84 | 57 | |||
85 | 58 | 51 | ||
86 | 59 | BuilderSet | 52 | BuilderSet |
87 | 60 | ========== | 53 | ========== |
88 | 61 | 54 | ||
93 | 62 | Now perform the tests for the Builder ContentSet class, BuilderSet. | 55 | Builders and groups thereof are managed through a utility, IBuilderSet. |
94 | 63 | 56 | ||
95 | 64 | Check if it can be imported: | 57 | >>> from zope.component import getUtility |
92 | 65 | |||
96 | 66 | >>> from lp.buildmaster.interfaces.builder import IBuilderSet | 58 | >>> from lp.buildmaster.interfaces.builder import IBuilderSet |
97 | 67 | |||
98 | 68 | Check we can use the set as a utility: | ||
99 | 69 | |||
100 | 70 | >>> builderset = getUtility(IBuilderSet) | 59 | >>> builderset = getUtility(IBuilderSet) |
101 | 71 | |||
102 | 72 | Check if the instance returned as utility corresponds to its | ||
103 | 73 | respective interface: | ||
104 | 74 | |||
105 | 75 | >>> verifyObject(IBuilderSet, builderset) | 60 | >>> verifyObject(IBuilderSet, builderset) |
106 | 76 | True | 61 | True |
107 | 77 | 62 | ||
109 | 78 | Check if the instance is iterable: | 63 | Iterating over a BuilderSet yields all registered builders. |
110 | 79 | 64 | ||
111 | 80 | >>> for b in builderset: | 65 | >>> for b in builderset: |
114 | 81 | ... b.id | 66 | ... print b.name |
115 | 82 | 1 | 67 | bob |
116 | 68 | frog | ||
117 | 69 | |||
118 | 70 | count() return the number of builders registered: | ||
119 | 71 | |||
120 | 72 | >>> builderset.count() | ||
121 | 83 | 2 | 73 | 2 |
122 | 84 | 74 | ||
129 | 85 | Check if the __getitem__ method: | 75 | Builders can be retrieved by name. |
130 | 86 | 76 | ||
131 | 87 | >>> builderset['bob'].name | 77 | >>> print builderset['bob'].name |
132 | 88 | u'bob' | 78 | bob |
133 | 89 | 79 | >>> print builderset['bad'] | |
134 | 90 | Check now the specific method in the utility as new(): | 80 | None |
135 | 81 | |||
136 | 82 | And also by ID. | ||
137 | 83 | |||
138 | 84 | >>> print builderset.get(2).name | ||
139 | 85 | frog | ||
140 | 86 | >>> print builderset.get(100).name | ||
141 | 87 | Traceback (most recent call last): | ||
142 | 88 | ... | ||
143 | 89 | SQLObjectNotFound: Object not found | ||
144 | 90 | |||
145 | 91 | The 'new' method will create a new builder in the database. | ||
146 | 91 | 92 | ||
147 | 92 | >>> bnew = builderset.new(1, 'http://dummy.com:8221/', 'dummy', | 93 | >>> bnew = builderset.new(1, 'http://dummy.com:8221/', 'dummy', |
148 | 93 | ... 'Dummy Title', 'eh ?', 1) | 94 | ... 'Dummy Title', 'eh ?', 1) |
149 | 94 | >>> bnew.name | 95 | >>> bnew.name |
150 | 95 | u'dummy' | 96 | u'dummy' |
151 | 96 | 97 | ||
178 | 97 | Check get() which returns a correspondent Builder instance to a given id: | 98 | 'getBuilders' returns builders with the 'active' flag set, ordered by |
179 | 98 | 99 | virtualization status, architecture, then name. | |
180 | 99 | >>> builderset.get(bnew.id).name | 100 | |
181 | 100 | u'dummy' | 101 | >>> for b in builderset.getBuilders(): |
182 | 101 | 102 | ... print b.name | |
183 | 102 | Or raises an SQLObjectNotFound exception: | 103 | bob |
184 | 103 | 104 | dummy | |
185 | 104 | >>> builderset.get(100) | 105 | frog |
186 | 105 | Traceback (most recent call last): | 106 | >>> login('foo.bar@canonical.com') |
187 | 106 | ... | 107 | >>> bnew.active = False |
188 | 107 | SQLObjectNotFound: Object not found | 108 | >>> login(ANONYMOUS) |
189 | 108 | 109 | >>> for b in builderset.getBuilders(): | |
190 | 109 | count() return the number of builder instance we have stored: | 110 | ... print b.name |
191 | 110 | 111 | bob | |
192 | 111 | >>> builderset.count() | 112 | frog |
167 | 112 | 3 | ||
168 | 113 | |||
169 | 114 | getBuilder() method returns all the builders available. It seems the | ||
170 | 115 | same than the own instance but we have plans to turn it aware of some | ||
171 | 116 | attributes of builder instance as: builderok and trust. | ||
172 | 117 | |||
173 | 118 | >>> for b in builderset.getBuilders(): | ||
174 | 119 | ... b.name | ||
175 | 120 | u'bob' | ||
176 | 121 | u'dummy' | ||
177 | 122 | u'frog' | ||
193 | 123 | 113 | ||
194 | 124 | 'getBuildQueueSizeForProcessor' returns the number of pending builds | 114 | 'getBuildQueueSizeForProcessor' returns the number of pending builds |
195 | 125 | for a given Processor. The callsites can also control which build-farm | 115 | for a given Processor. The callsites can also control which build-farm |
196 | @@ -246,24 +236,76 @@ | |||
197 | 246 | A fictitious i386 variant is rejected, since there are no DASes with that | 236 | A fictitious i386 variant is rejected, since there are no DASes with that |
198 | 247 | tag. | 237 | tag. |
199 | 248 | 238 | ||
206 | 249 | >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave | 239 | >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave |
207 | 250 | >>> bob.setSlaveForTesting(OkSlave('i387')) | 240 | >>> bob.setSlaveForTesting(OkSlave('i387')) |
208 | 251 | >>> bob.checkSlaveArchitecture() | 241 | >>> bob.checkSlaveArchitecture() |
209 | 252 | Traceback (most recent call last): | 242 | Traceback (most recent call last): |
210 | 253 | ... | 243 | ... |
211 | 254 | BuildDaemonError: Bad slave architecture tag: i387 (registered family: x86) | 244 | BuildDaemonError: Bad slave architecture tag: i387 (registered family: x86) |
212 | 255 | 245 | ||
213 | 256 | hppa isn't in the x86 family, so it too is rejected. | 246 | hppa isn't in the x86 family, so it too is rejected. |
214 | 257 | 247 | ||
221 | 258 | >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave | 248 | >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave |
222 | 259 | >>> bob.setSlaveForTesting(OkSlave('hppa')) | 249 | >>> bob.setSlaveForTesting(OkSlave('hppa')) |
223 | 260 | >>> bob.checkSlaveArchitecture() | 250 | >>> bob.checkSlaveArchitecture() |
224 | 261 | Traceback (most recent call last): | 251 | Traceback (most recent call last): |
225 | 262 | ... | 252 | ... |
226 | 263 | BuildDaemonError: Bad slave architecture tag: hppa (registered family: x86) | 253 | BuildDaemonError: Bad slave architecture tag: hppa (registered family: x86) |
227 | 264 | 254 | ||
228 | 265 | But i386, a real x86 variant, passes without objection. | 255 | But i386, a real x86 variant, passes without objection. |
229 | 266 | 256 | ||
233 | 267 | >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave | 257 | >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave |
234 | 268 | >>> bob.setSlaveForTesting(OkSlave('i386')) | 258 | >>> bob.setSlaveForTesting(OkSlave('i386')) |
235 | 269 | >>> bob.checkSlaveArchitecture() | 259 | >>> bob.checkSlaveArchitecture() |
236 | 260 | |||
237 | 261 | |||
238 | 262 | Rescuing lost slaves | ||
239 | 263 | ==================== | ||
240 | 264 | |||
241 | 265 | Builder.rescueIfLost() checks the build ID reported in the slave status | ||
242 | 266 | against the database. If it isn't building what we think it should be, | ||
243 | 267 | the current build will be aborted and the slave cleaned in preparation | ||
244 | 268 | for a new task. The decision about the slave's correctness is left up | ||
245 | 269 | to IBuildFarmJobBehavior.verifySlaveBuildID -- for these examples we | ||
246 | 270 | will use a special behavior that just checks if the slave ID is 'good'. | ||
247 | 271 | |||
248 | 272 | >>> import logging | ||
249 | 273 | >>> from lp.buildmaster.interfaces.builder import CorruptBuildID | ||
250 | 274 | >>> from lp.soyuz.tests.soyuzbuilddhelpers import ( | ||
251 | 275 | ... BuildingSlave, MockBuilder, OkSlave, WaitingSlave) | ||
252 | 276 | |||
253 | 277 | >>> class TestBuildBehavior: | ||
254 | 278 | ... def verifySlaveBuildID(self, build_id): | ||
255 | 279 | ... if build_id != 'good': | ||
256 | 280 | ... raise CorruptBuildID('Bad value') | ||
257 | 281 | |||
258 | 282 | >>> def rescue_slave_if_lost(slave): | ||
259 | 283 | ... builder = MockBuilder('mock', slave, TestBuildBehavior()) | ||
260 | 284 | ... builder.rescueIfLost(logging.getLogger()) | ||
261 | 285 | |||
262 | 286 | An idle slave is not rescued. | ||
263 | 287 | |||
264 | 288 | >>> rescue_slave_if_lost(OkSlave()) | ||
265 | 289 | |||
266 | 290 | Slaves building or having built the correct build are not rescued | ||
267 | 291 | either. | ||
268 | 292 | |||
269 | 293 | >>> rescue_slave_if_lost(BuildingSlave(build_id='good')) | ||
270 | 294 | >>> rescue_slave_if_lost(WaitingSlave(build_id='good')) | ||
271 | 295 | |||
272 | 296 | But if a slave is building the wrong ID, it is declared lost and | ||
273 | 297 | an abort is attempted. MockSlave prints out a message when it is aborted | ||
274 | 298 | or cleaned. | ||
275 | 299 | |||
276 | 300 | >>> rescue_slave_if_lost(BuildingSlave(build_id='bad')) | ||
277 | 301 | Aborting slave | ||
278 | 302 | WARNING:root:Builder 'mock' rescued from 'bad': 'Bad value' | ||
279 | 303 | |||
280 | 304 | Slaves having completed an incorrect build are also declared lost, | ||
281 | 305 | but there's no need to abort a completed build. Such builders are | ||
282 | 306 | instead simply cleaned, ready for the next build. | ||
283 | 307 | |||
284 | 308 | >>> rescue_slave_if_lost(WaitingSlave(build_id='bad')) | ||
285 | 309 | Cleaning slave | ||
286 | 310 | WARNING:root:Builder 'mock' rescued from 'bad': 'Bad value' | ||
287 | 311 | |||
288 | 270 | 312 | ||
289 | === modified file 'lib/lp/buildmaster/doc/buildfarmjobbehavior.txt' | |||
290 | --- lib/lp/buildmaster/doc/buildfarmjobbehavior.txt 2010-01-13 20:19:43 +0000 | |||
291 | +++ lib/lp/buildmaster/doc/buildfarmjobbehavior.txt 2010-04-09 00:40:18 +0000 | |||
292 | @@ -111,3 +111,11 @@ | |||
293 | 111 | ... | 111 | ... |
294 | 112 | BuildBehaviorMismatch: Builder was idle when asked to log the start of a | 112 | BuildBehaviorMismatch: Builder was idle when asked to log the start of a |
295 | 113 | build. | 113 | build. |
296 | 114 | |||
297 | 115 | If a slave is working on a job while we think it is idle, it will always be | ||
298 | 116 | aborted. | ||
299 | 117 | |||
300 | 118 | >>> bob.current_build_behavior.verifySlaveBuildID('foo') | ||
301 | 119 | Traceback (most recent call last): | ||
302 | 120 | ... | ||
303 | 121 | CorruptBuildID: No job assigned to builder | ||
304 | 114 | 122 | ||
305 | === modified file 'lib/lp/buildmaster/tests/test_manager.py' | |||
306 | --- lib/lp/buildmaster/tests/test_manager.py 2010-04-08 04:00:30 +0000 | |||
307 | +++ lib/lp/buildmaster/tests/test_manager.py 2010-04-09 00:40:18 +0000 | |||
308 | @@ -31,7 +31,7 @@ | |||
309 | 31 | from lp.buildmaster.tests.harness import BuilddManagerTestSetup | 31 | from lp.buildmaster.tests.harness import BuilddManagerTestSetup |
310 | 32 | from lp.registry.interfaces.distribution import IDistributionSet | 32 | from lp.registry.interfaces.distribution import IDistributionSet |
311 | 33 | from lp.soyuz.interfaces.build import IBuildSet | 33 | from lp.soyuz.interfaces.build import IBuildSet |
313 | 34 | from lp.soyuz.tests.soyuzbuilddhelpers import SaneBuildingSlave | 34 | from lp.soyuz.tests.soyuzbuilddhelpers import BuildingSlave |
314 | 35 | from lp.soyuz.tests.test_publishing import SoyuzTestPublisher | 35 | from lp.soyuz.tests.test_publishing import SoyuzTestPublisher |
315 | 36 | 36 | ||
316 | 37 | 37 | ||
317 | @@ -671,7 +671,7 @@ | |||
318 | 671 | self.assertTrue(builder.builderok) | 671 | self.assertTrue(builder.builderok) |
319 | 672 | 672 | ||
320 | 673 | job = getUtility(IBuildQueueSet).get(job.id) | 673 | job = getUtility(IBuildQueueSet).get(job.id) |
322 | 674 | self.assertBuildingJob(job, builder, logtail='Doing something ...') | 674 | self.assertBuildingJob(job, builder, logtail='This is a build log') |
323 | 675 | 675 | ||
324 | 676 | def testScanUpdatesBuildingJobs(self): | 676 | def testScanUpdatesBuildingJobs(self): |
325 | 677 | # The job assigned to a broken builder is rescued. | 677 | # The job assigned to a broken builder is rescued. |
326 | @@ -682,7 +682,7 @@ | |||
327 | 682 | 682 | ||
328 | 683 | login('foo.bar@canonical.com') | 683 | login('foo.bar@canonical.com') |
329 | 684 | builder.builderok = True | 684 | builder.builderok = True |
331 | 685 | builder.setSlaveForTesting(SaneBuildingSlave()) | 685 | builder.setSlaveForTesting(BuildingSlave(build_id='8-1')) |
332 | 686 | transaction.commit() | 686 | transaction.commit() |
333 | 687 | login(ANONYMOUS) | 687 | login(ANONYMOUS) |
334 | 688 | 688 | ||
335 | 689 | 689 | ||
336 | === modified file 'lib/lp/code/tests/test_recipebuilder.py' | |||
337 | --- lib/lp/code/tests/test_recipebuilder.py 2010-03-25 18:34:07 +0000 | |||
338 | +++ lib/lp/code/tests/test_recipebuilder.py 2010-04-09 00:40:18 +0000 | |||
339 | @@ -20,8 +20,8 @@ | |||
340 | 20 | SourcePackageRecipeBuild) | 20 | SourcePackageRecipeBuild) |
341 | 21 | from lp.soyuz.adapters.archivedependencies import get_sources_list_for_building | 21 | from lp.soyuz.adapters.archivedependencies import get_sources_list_for_building |
342 | 22 | from lp.soyuz.model.processor import ProcessorFamilySet | 22 | from lp.soyuz.model.processor import ProcessorFamilySet |
345 | 23 | from lp.soyuz.tests.soyuzbuilddhelpers import (MockBuilder, | 23 | from lp.soyuz.tests.soyuzbuilddhelpers import ( |
346 | 24 | SaneBuildingSlave,) | 24 | MockBuilder, OkSlave) |
347 | 25 | from lp.soyuz.tests.test_binarypackagebuildbehavior import ( | 25 | from lp.soyuz.tests.test_binarypackagebuildbehavior import ( |
348 | 26 | BaseTestVerifySlaveBuildID) | 26 | BaseTestVerifySlaveBuildID) |
349 | 27 | from lp.soyuz.tests.test_publishing import ( | 27 | from lp.soyuz.tests.test_publishing import ( |
350 | @@ -84,7 +84,7 @@ | |||
351 | 84 | # VerifyBuildRequest won't raise any exceptions when called with a | 84 | # VerifyBuildRequest won't raise any exceptions when called with a |
352 | 85 | # valid builder set. | 85 | # valid builder set. |
353 | 86 | job = self.makeJob() | 86 | job = self.makeJob() |
355 | 87 | builder = MockBuilder("bob-de-bouwer", SaneBuildingSlave()) | 87 | builder = MockBuilder("bob-de-bouwer", OkSlave()) |
356 | 88 | job.setBuilder(builder) | 88 | job.setBuilder(builder) |
357 | 89 | logger = BufferLogger() | 89 | logger = BufferLogger() |
358 | 90 | job.verifyBuildRequest(logger) | 90 | job.verifyBuildRequest(logger) |
359 | @@ -136,7 +136,7 @@ | |||
360 | 136 | # dispatchBuildToSlave will fail when there is not chroot tarball | 136 | # dispatchBuildToSlave will fail when there is not chroot tarball |
361 | 137 | # available for the distroseries to build for. | 137 | # available for the distroseries to build for. |
362 | 138 | job = self.makeJob() | 138 | job = self.makeJob() |
364 | 139 | builder = MockBuilder("bob-de-bouwer", SaneBuildingSlave()) | 139 | builder = MockBuilder("bob-de-bouwer", OkSlave()) |
365 | 140 | processorfamily = ProcessorFamilySet().getByProcessorName('386') | 140 | processorfamily = ProcessorFamilySet().getByProcessorName('386') |
366 | 141 | builder.processor = processorfamily.processors[0] | 141 | builder.processor = processorfamily.processors[0] |
367 | 142 | job.setBuilder(builder) | 142 | job.setBuilder(builder) |
368 | 143 | 143 | ||
369 | === modified file 'lib/lp/soyuz/doc/buildd-slavescanner.txt' | |||
370 | --- lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-04-08 15:29:39 +0000 | |||
371 | +++ lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-04-09 00:40:18 +0000 | |||
372 | @@ -32,90 +32,8 @@ | |||
373 | 32 | Import MockBuilder and a series of MockSlaves to be used in this test. | 32 | Import MockBuilder and a series of MockSlaves to be used in this test. |
374 | 33 | 33 | ||
375 | 34 | >>> from lp.soyuz.tests.soyuzbuilddhelpers import ( | 34 | >>> from lp.soyuz.tests.soyuzbuilddhelpers import ( |
460 | 35 | ... AbortedSlave, AbortingSlave, BuildingSlave, InsaneWaitingSlave, | 35 | ... AbortedSlave, AbortingSlave, BuildingSlave, |
461 | 36 | ... LostBuildingBrokenSlave, LostBuildingSlave, LostWaitingSlave, | 36 | ... LostBuildingBrokenSlave, MockBuilder, OkSlave, WaitingSlave) |
378 | 37 | ... MockBuilder, OkSlave, SaneBuildingSlave, SaneWaitingSlave, | ||
379 | 38 | ... WaitingSlave) | ||
380 | 39 | |||
381 | 40 | Let's play with a Builder method designed to rescue build slaves | ||
382 | 41 | that are processing unknown jobs. In real conditions, this situation | ||
383 | 42 | only happens if the slave is processing deleted or modified BuildQueue | ||
384 | 43 | entry, since Build entries are never removed. It might be caused by | ||
385 | 44 | exceptions in slavescanner or queuebuilder scripts. | ||
386 | 45 | |||
387 | 46 | When we figured this situation out, the procedure to rescue is to | ||
388 | 47 | request the slave XMLRPC method 'clean', reseting the slave completely. | ||
389 | 48 | |||
390 | 49 | We figured out if the building information is correct and sane by | ||
391 | 50 | checking the job identifier field from status message information, | ||
392 | 51 | which consists of "<Build.id>-<BuildQueue.id>". | ||
393 | 52 | |||
394 | 53 | First let's emulate a sane and a lost slave. The SaneSlave returns a | ||
395 | 54 | job identifier that exists in our sampledata, but the LostSlave | ||
396 | 55 | returns a completely bogus one. | ||
397 | 56 | |||
398 | 57 | The the mock slave.clean() method is modified to print a message for | ||
399 | 58 | testing purposes. | ||
400 | 59 | |||
401 | 60 | Initializing the sane_builder. It was not rescued, since the job | ||
402 | 61 | identifier is sane (Build.id == 8 and BuildQueue.id == 1 exist): | ||
403 | 62 | |||
404 | 63 | >>> sanebuilding_builder = MockBuilder( | ||
405 | 64 | ... 'Sane Building Slave', SaneBuildingSlave()) | ||
406 | 65 | >>> sanebuilding_builder.rescueIfLost(logger) is None | ||
407 | 66 | True | ||
408 | 67 | |||
409 | 68 | A sane WAITING slave: | ||
410 | 69 | |||
411 | 70 | >>> sanewaiting_builder = MockBuilder( | ||
412 | 71 | ... 'Sane Waiting Slave', SaneWaitingSlave()) | ||
413 | 72 | >>> sanewaiting_builder.rescueIfLost(logger) is None | ||
414 | 73 | True | ||
415 | 74 | |||
416 | 75 | A insane WAITING slave, with wrong BuildQueue/Build relation: | ||
417 | 76 | |||
418 | 77 | >>> insanewaiting_builder = MockBuilder( | ||
419 | 78 | ... 'Insane Waiting Slave', InsaneWaitingSlave()) | ||
420 | 79 | >>> insanewaiting_builder.rescueIfLost(logger) | ||
421 | 80 | WARNING:root:Builder 'Insane Waiting Slave' rescued from '7-1': 'Job build entry mismatch' | ||
422 | 81 | |||
423 | 82 | It was rescued because the BuildQueue.id == 1 isn't related to | ||
424 | 83 | Build.id == 7, so this pair relation is wrong. | ||
425 | 84 | |||
426 | 85 | Let's test slaves with job identifier pointing non-existent | ||
427 | 86 | Build/BuildQueue entries. first a lost slave in status 'BUILDING': | ||
428 | 87 | |||
429 | 88 | >>> lostbuilding_builder = MockBuilder( | ||
430 | 89 | ... 'Lost Building Slave', LostBuildingSlave()) | ||
431 | 90 | >>> lostbuilding_builder.rescueIfLost(logger) | ||
432 | 91 | WARNING:root:Builder 'Lost Building Slave' rescued from '1000-10000': | ||
433 | 92 | 'Build 1000 is not available: 'Object not found'' | ||
434 | 93 | |||
435 | 94 | Then a lost slave in status 'WAITING': | ||
436 | 95 | |||
437 | 96 | >>> lostwaiting_builder = MockBuilder( | ||
438 | 97 | ... 'Lost Waiting Slave', LostWaitingSlave()) | ||
439 | 98 | >>> lostwaiting_builder.rescueIfLost(logger) | ||
440 | 99 | WARNING:root:Builder 'Lost Waiting Slave' rescued from '1000-10000': | ||
441 | 100 | 'Build 1000 is not available: 'Object not found'' | ||
442 | 101 | |||
443 | 102 | Both got rescued, as expected. | ||
444 | 103 | |||
445 | 104 | A BUILDING or WAITING slave without a build assigned in the DB will also be rescued. | ||
446 | 105 | |||
447 | 106 | >>> from lp.buildmaster.model.buildfarmjobbehavior import IdleBuildBehavior | ||
448 | 107 | |||
449 | 108 | >>> lostbuilding_builder = MockBuilder( | ||
450 | 109 | ... 'Lost Building Slave', LostBuildingSlave()) | ||
451 | 110 | >>> lostbuilding_builder.current_build_behavior = IdleBuildBehavior() | ||
452 | 111 | >>> lostbuilding_builder.rescueIfLost(logger) | ||
453 | 112 | WARNING:root:Builder 'Lost Building Slave' rescued from '1000-10000': 'No job assigned to builder' | ||
454 | 113 | |||
455 | 114 | >>> lostwaiting_builder = MockBuilder( | ||
456 | 115 | ... 'Lost Waiting Slave', LostWaitingSlave()) | ||
457 | 116 | >>> lostwaiting_builder.current_build_behavior = IdleBuildBehavior() | ||
458 | 117 | >>> lostwaiting_builder.rescueIfLost(logger) | ||
459 | 118 | WARNING:root:Builder 'Lost Waiting Slave' rescued from '1000-10000': 'No job assigned to builder' | ||
462 | 119 | 37 | ||
463 | 120 | Slave-scanner will deactivate a 'lost-building' builder that could not | 38 | Slave-scanner will deactivate a 'lost-building' builder that could not |
464 | 121 | be aborted appropriately. | 39 | be aborted appropriately. |
465 | @@ -124,6 +42,7 @@ | |||
466 | 124 | ... 'Lost Building Broken Slave', LostBuildingBrokenSlave()) | 42 | ... 'Lost Building Broken Slave', LostBuildingBrokenSlave()) |
467 | 125 | 43 | ||
468 | 126 | >>> lostbuilding_builder.updateStatus(logger) | 44 | >>> lostbuilding_builder.updateStatus(logger) |
469 | 45 | Aborting slave | ||
470 | 127 | WARNING:root:Lost Building Broken Slave (http://fake:0000) marked as failed due to: <Fault 8002: 'Could not abort'> | 46 | WARNING:root:Lost Building Broken Slave (http://fake:0000) marked as failed due to: <Fault 8002: 'Could not abort'> |
471 | 128 | Traceback (most recent call last): | 47 | Traceback (most recent call last): |
472 | 129 | ... | 48 | ... |
473 | 130 | 49 | ||
474 | === modified file 'lib/lp/soyuz/tests/soyuzbuilddhelpers.py' | |||
475 | --- lib/lp/soyuz/tests/soyuzbuilddhelpers.py 2010-04-03 03:38:55 +0000 | |||
476 | +++ lib/lp/soyuz/tests/soyuzbuilddhelpers.py 2010-04-09 00:40:18 +0000 | |||
477 | @@ -7,11 +7,6 @@ | |||
478 | 7 | 7 | ||
479 | 8 | __all__ = [ | 8 | __all__ = [ |
480 | 9 | 'MockBuilder', | 9 | 'MockBuilder', |
481 | 10 | 'SaneBuildingSlave', | ||
482 | 11 | 'SaneWaitingSlave', | ||
483 | 12 | 'InsaneWaitingSlave', | ||
484 | 13 | 'LostBuildingSlave', | ||
485 | 14 | 'LostWaitingSlave', | ||
486 | 15 | 'LostBuildingBrokenSlave', | 10 | 'LostBuildingBrokenSlave', |
487 | 16 | 'BrokenSlave', | 11 | 'BrokenSlave', |
488 | 17 | 'OkSlave', | 12 | 'OkSlave', |
489 | @@ -36,9 +31,12 @@ | |||
490 | 36 | class MockBuilder: | 31 | class MockBuilder: |
491 | 37 | """Emulates a IBuilder class.""" | 32 | """Emulates a IBuilder class.""" |
492 | 38 | 33 | ||
494 | 39 | current_build_behavior = BinaryPackageBuildBehavior(None) | 34 | def __init__(self, name, slave, behavior=None): |
495 | 35 | if behavior is None: | ||
496 | 36 | self.current_build_behavior = BinaryPackageBuildBehavior(None) | ||
497 | 37 | else: | ||
498 | 38 | self.current_build_behavior = behavior | ||
499 | 40 | 39 | ||
500 | 41 | def __init__(self, name, slave): | ||
501 | 42 | self.slave = slave | 40 | self.slave = slave |
502 | 43 | self.builderok = True | 41 | self.builderok = True |
503 | 44 | self.manual = False | 42 | self.manual = False |
504 | @@ -58,9 +56,11 @@ | |||
505 | 58 | return self.current_build_behavior.verifySlaveBuildID(slave_build_id) | 56 | return self.current_build_behavior.verifySlaveBuildID(slave_build_id) |
506 | 59 | 57 | ||
507 | 60 | def cleanSlave(self): | 58 | def cleanSlave(self): |
508 | 59 | print 'Cleaning slave' | ||
509 | 61 | return self.slave.clean() | 60 | return self.slave.clean() |
510 | 62 | 61 | ||
511 | 63 | def requestAbort(self): | 62 | def requestAbort(self): |
512 | 63 | print 'Aborting slave' | ||
513 | 64 | return self.slave.abort() | 64 | return self.slave.abort() |
514 | 65 | 65 | ||
515 | 66 | def resumeSlave(self, logger): | 66 | def resumeSlave(self, logger): |
516 | @@ -79,88 +79,6 @@ | |||
517 | 79 | updateBuilderStatus(self, logger) | 79 | updateBuilderStatus(self, logger) |
518 | 80 | 80 | ||
519 | 81 | 81 | ||
520 | 82 | class SaneBuildingSlave: | ||
521 | 83 | """A mock slave that is currently building build 8 and buildqueue 1.""" | ||
522 | 84 | |||
523 | 85 | def status(self): | ||
524 | 86 | return ('BuilderStatus.BUILDING', '8-1', 'Doing something ...') | ||
525 | 87 | |||
526 | 88 | def clean(self): | ||
527 | 89 | print 'Rescuing SaneSlave' | ||
528 | 90 | |||
529 | 91 | def echo(self, *args): | ||
530 | 92 | return args | ||
531 | 93 | |||
532 | 94 | def info(self): | ||
533 | 95 | return ['1.0', 'i386', ['debian']] | ||
534 | 96 | |||
535 | 97 | def build(self, buildid, builder_type, chroot_sha1, filemap, args): | ||
536 | 98 | return ('BuildStatus.Building', buildid) | ||
537 | 99 | |||
538 | 100 | |||
539 | 101 | class SaneWaitingSlave: | ||
540 | 102 | """A mock slave that is currently waiting. | ||
541 | 103 | |||
542 | 104 | Uses build 8 and buildqueue 1. | ||
543 | 105 | """ | ||
544 | 106 | |||
545 | 107 | def status(self): | ||
546 | 108 | return ('BuilderStatus.WAITING', 'BuildStatus.OK', '8-1') | ||
547 | 109 | |||
548 | 110 | def clean(self): | ||
549 | 111 | print 'Rescuing SaneSlave' | ||
550 | 112 | |||
551 | 113 | |||
552 | 114 | class InsaneWaitingSlave: | ||
553 | 115 | """A mock slave waiting with a bogus Build/BuildQueue relation.""" | ||
554 | 116 | |||
555 | 117 | def status(self): | ||
556 | 118 | return ('BuilderStatus.WAITING', 'BuildStatus.OK', '7-1') | ||
557 | 119 | |||
558 | 120 | def clean(self): | ||
559 | 121 | pass | ||
560 | 122 | |||
561 | 123 | |||
562 | 124 | class LostBuildingSlave: | ||
563 | 125 | """A mock slave building bogus Build/BuildQueue IDs.""" | ||
564 | 126 | |||
565 | 127 | def status(self): | ||
566 | 128 | return ('BuilderStatus.BUILDING', '1000-10000') | ||
567 | 129 | |||
568 | 130 | def abort(self): | ||
569 | 131 | pass | ||
570 | 132 | |||
571 | 133 | |||
572 | 134 | class LostWaitingSlave: | ||
573 | 135 | """A mock slave waiting with bogus Build/BuildQueue IDs.""" | ||
574 | 136 | |||
575 | 137 | def status(self): | ||
576 | 138 | return ('BuilderStatus.WAITING', 'BuildStatus.OK', '1000-10000') | ||
577 | 139 | |||
578 | 140 | def clean(self): | ||
579 | 141 | pass | ||
580 | 142 | |||
581 | 143 | |||
582 | 144 | class LostBuildingBrokenSlave: | ||
583 | 145 | """A mock slave building bogus Build/BuildQueue IDs that can't be aborted. | ||
584 | 146 | |||
585 | 147 | When 'aborted' it raises an xmlrpclib.Fault(8002, 'Could not abort') | ||
586 | 148 | """ | ||
587 | 149 | |||
588 | 150 | def status(self): | ||
589 | 151 | return ('BuilderStatus.BUILDING', '1000-10000') | ||
590 | 152 | |||
591 | 153 | def abort(self): | ||
592 | 154 | raise xmlrpclib.Fault(8002, "Could not abort") | ||
593 | 155 | |||
594 | 156 | |||
595 | 157 | class BrokenSlave: | ||
596 | 158 | """A mock slave that reports that it is broken.""" | ||
597 | 159 | |||
598 | 160 | def status(self): | ||
599 | 161 | raise xmlrpclib.Fault(8001, "Broken slave") | ||
600 | 162 | |||
601 | 163 | |||
602 | 164 | class OkSlave: | 82 | class OkSlave: |
603 | 165 | """An idle mock slave that prints information about itself. | 83 | """An idle mock slave that prints information about itself. |
604 | 166 | 84 | ||
605 | @@ -196,9 +114,15 @@ | |||
606 | 196 | def fetchlogtail(self, size): | 114 | def fetchlogtail(self, size): |
607 | 197 | return 'BOGUS' | 115 | return 'BOGUS' |
608 | 198 | 116 | ||
609 | 117 | def echo(self, *args): | ||
610 | 118 | return args | ||
611 | 119 | |||
612 | 199 | def clean(self): | 120 | def clean(self): |
613 | 200 | pass | 121 | pass |
614 | 201 | 122 | ||
615 | 123 | def abort(self): | ||
616 | 124 | pass | ||
617 | 125 | |||
618 | 202 | def info(self): | 126 | def info(self): |
619 | 203 | return ('1.0', self.arch_tag, 'debian') | 127 | return ('1.0', self.arch_tag, 'debian') |
620 | 204 | 128 | ||
621 | @@ -223,9 +147,13 @@ | |||
622 | 223 | class BuildingSlave(OkSlave): | 147 | class BuildingSlave(OkSlave): |
623 | 224 | """A mock slave that looks like it's currently building.""" | 148 | """A mock slave that looks like it's currently building.""" |
624 | 225 | 149 | ||
625 | 150 | def __init__(self, build_id='1-1'): | ||
626 | 151 | super(BuildingSlave, self).__init__() | ||
627 | 152 | self.build_id = build_id | ||
628 | 153 | |||
629 | 226 | def status(self): | 154 | def status(self): |
630 | 227 | buildlog = xmlrpclib.Binary("This is a build log") | 155 | buildlog = xmlrpclib.Binary("This is a build log") |
632 | 228 | return ('BuilderStatus.BUILDING', '1-1', buildlog) | 156 | return ('BuilderStatus.BUILDING', self.build_id, buildlog) |
633 | 229 | 157 | ||
634 | 230 | def getFile(self, sum): | 158 | def getFile(self, sum): |
635 | 231 | if sum == "buildlog": | 159 | if sum == "buildlog": |
636 | @@ -234,24 +162,19 @@ | |||
637 | 234 | return s | 162 | return s |
638 | 235 | 163 | ||
639 | 236 | 164 | ||
640 | 237 | class AbortedSlave(OkSlave): | ||
641 | 238 | """A mock slave that looks like it's aborted.""" | ||
642 | 239 | |||
643 | 240 | def status(self): | ||
644 | 241 | return ('BuilderStatus.ABORTED', '1-1') | ||
645 | 242 | |||
646 | 243 | |||
647 | 244 | class WaitingSlave(OkSlave): | 165 | class WaitingSlave(OkSlave): |
648 | 245 | """A mock slave that looks like it's currently waiting.""" | 166 | """A mock slave that looks like it's currently waiting.""" |
649 | 246 | 167 | ||
651 | 247 | def __init__(self, state, dependencies=None): | 168 | def __init__(self, state='BuildStatus.OK', dependencies=None, |
652 | 169 | build_id='1-1'): | ||
653 | 248 | super(WaitingSlave, self).__init__() | 170 | super(WaitingSlave, self).__init__() |
654 | 249 | self.state = state | 171 | self.state = state |
655 | 250 | self.dependencies = dependencies | 172 | self.dependencies = dependencies |
656 | 173 | self.build_id = build_id | ||
657 | 251 | 174 | ||
658 | 252 | def status(self): | 175 | def status(self): |
661 | 253 | return ('BuilderStatus.WAITING', self.state, '1-1', {}, | 176 | return ('BuilderStatus.WAITING', self.state, self.build_id, {}, |
662 | 254 | self.dependencies ) | 177 | self.dependencies) |
663 | 255 | 178 | ||
664 | 256 | def getFile(self, sum): | 179 | def getFile(self, sum): |
665 | 257 | if sum == "buildlog": | 180 | if sum == "buildlog": |
666 | @@ -259,8 +182,36 @@ | |||
667 | 259 | s.headers = {'content-length':19} | 182 | s.headers = {'content-length':19} |
668 | 260 | return s | 183 | return s |
669 | 261 | 184 | ||
670 | 185 | |||
671 | 262 | class AbortingSlave(OkSlave): | 186 | class AbortingSlave(OkSlave): |
672 | 263 | """A mock slave that looks like it's in the process of aborting.""" | 187 | """A mock slave that looks like it's in the process of aborting.""" |
673 | 264 | 188 | ||
674 | 265 | def status(self): | 189 | def status(self): |
675 | 266 | return ('BuilderStatus.ABORTING', '1-1') | 190 | return ('BuilderStatus.ABORTING', '1-1') |
676 | 191 | |||
677 | 192 | |||
678 | 193 | class AbortedSlave(OkSlave): | ||
679 | 194 | """A mock slave that looks like it's aborted.""" | ||
680 | 195 | |||
681 | 196 | def status(self): | ||
682 | 197 | return ('BuilderStatus.ABORTED', '1-1') | ||
683 | 198 | |||
684 | 199 | |||
685 | 200 | class LostBuildingBrokenSlave: | ||
686 | 201 | """A mock slave building bogus Build/BuildQueue IDs that can't be aborted. | ||
687 | 202 | |||
688 | 203 | When 'aborted' it raises an xmlrpclib.Fault(8002, 'Could not abort') | ||
689 | 204 | """ | ||
690 | 205 | |||
691 | 206 | def status(self): | ||
692 | 207 | return ('BuilderStatus.BUILDING', '1000-10000') | ||
693 | 208 | |||
694 | 209 | def abort(self): | ||
695 | 210 | raise xmlrpclib.Fault(8002, "Could not abort") | ||
696 | 211 | |||
697 | 212 | |||
698 | 213 | class BrokenSlave: | ||
699 | 214 | """A mock slave that reports that it is broken.""" | ||
700 | 215 | |||
701 | 216 | def status(self): | ||
702 | 217 | raise xmlrpclib.Fault(8001, "Broken slave") |
Hi William,
This branch looks great. As we discussed on IRC please run it past a Soyuz team member and I'll approve it.
> === modified file 'lib/lp/ buildmaster/ doc/builder. txt' buildmaster/ doc/builder. txt 2010-03-24 10:24:22 +0000 buildmaster/ doc/builder. txt 2010-04-03 04:39:18 +0000 /launchpad. canonical. com/BasicTestCo verage> for the Builder class, model.builder import Builder
> --- lib/lp/
> +++ lib/lp/
> @@ -2,36 +2,34 @@
> Builder Class
> =============
>
> -This test aims to meet the requirements of
> -<https:/
> -which represents the Buildd Slave entity.
> -
> -Need auxiliar methods from zope toolchain:
> +The Builder class represents a slave machine in the build farm. These
> +slaves are used to execute untrusted code -- for example when building
> +packages.
> +
> +There are several builders in the sample data. Let's examine the first.
> +
> + >>> from lp.buildmaster.
> + >>> builder = Builder.get(1)
> +
> +As expected, it implements IBuilder.
Nice changes.
> +Builders can take on different behaviors depending on the type of build build_behavior) to which all the build-type specific behavior
> +they are currently processing. Each builder provides an attribute
> +(current_
> +for the current build is delegated. In the sample data, bob's current
> +behavior is the behavior for dealing with binary packages
How about:
"behavior is dealing with binary packages."
(Note full-stop.)
> >>> from zope.security.proxy import isinstance model.binarypac kagebuildbehavi or import ( current_ build_behavior, BinaryPackageBu ildBehavior) IBuilder, builder) interfaces. builder import IBuilderSet IBuilderSet) IBuilderSet, builderset)
> >>> from lp.soyuz.
> @@ -40,85 +38,77 @@
> ... builder.
> True
>
> -Confirm we can get the slave xmlrpc interface
> +A builder has an XML-RPC proxy in the 'slave' attribute, which allows
> +us to easily call methods on the slave machines.
>
> >>> s = builder.slave
>
> -Confirm that the urlbase is correct in that slave. (If the protocol changes,
> -this may change too)
> +The base URL of the proxy matches the builder's URL.
>
> >>> s.urlbase == builder.url
> True
>
> -Check if the instance corresponds to the declared interface:
> -
> - >>> verifyObject(
> - True
> -
>
> BuilderSet
> ==========
>
> -Now perform the tests for the Builder ContentSet class, BuilderSet.
> -
> -Check if it can be imported:
> -
> +Builders and groups thereof are managed through a utility, IBuilderSet.
> +
> + >>> from zope.component import getUtility
> >>> from lp.buildmaster.
> -
> -Check we can use the set as a utility:
> -
> >>> builderset = getUtility(
> -
> -Check if the instance returned as utility corresponds to its
> -respective interface:
> -
> >>> verifyObject(
> True
>
> -Check if the instance is iterable:
> +Iterating over a BuilderSet yields all registered builders.
>
> >>> for b in builderset:
> - ... b.id
> - 1
> + ... print b.name
> + bob
> + frog
> +
> +count() return the number of builder instance we have stored:
typo: instances
> +
> + >>> builderset.count()
> 2
>
> -Check if the __getitem__ method:
> -
...