Merge lp:~jtv/launchpad/bug-536797 into lp:launchpad

Proposed by Jeroen T. Vermeulen
Status: Merged
Merged at revision: not available
Proposed branch: lp:~jtv/launchpad/bug-536797
Merge into: lp:launchpad
Diff against target: 814 lines (+356/-89)
23 files modified
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+2/-1)
lib/canonical/launchpad/security.py (+52/-2)
lib/canonical/launchpad/webapp/configure.zcml (+7/-0)
lib/canonical/launchpad/webapp/tales.py (+27/-0)
lib/lp/buildmaster/interfaces/buildfarmbranchjob.py (+21/-0)
lib/lp/buildmaster/interfaces/buildfarmjob.py (+0/-1)
lib/lp/buildmaster/interfaces/buildqueue.py (+4/-0)
lib/lp/buildmaster/model/buildqueue.py (+12/-2)
lib/lp/buildmaster/tests/test_buildqueue.py (+36/-7)
lib/lp/code/interfaces/sourcepackagerecipebuild.py (+2/-7)
lib/lp/soyuz/browser/build.py (+0/-1)
lib/lp/soyuz/browser/builder.py (+2/-8)
lib/lp/soyuz/browser/configure.zcml (+31/-1)
lib/lp/soyuz/doc/build-estimated-dispatch-time.txt (+3/-4)
lib/lp/soyuz/interfaces/buildfarmbuildjob.py (+21/-0)
lib/lp/soyuz/interfaces/buildpackagejob.py (+4/-4)
lib/lp/soyuz/templates/builder-index.pt (+5/-47)
lib/lp/soyuz/templates/buildfarmbranchjob-current.pt (+11/-0)
lib/lp/soyuz/templates/buildfarmbuildjob-current.pt (+8/-0)
lib/lp/soyuz/templates/buildfarmjob-current.pt (+10/-0)
lib/lp/soyuz/templates/buildqueue-current.pt (+24/-0)
lib/lp/translations/model/translationtemplatesbuildjob.py (+5/-4)
lib/lp/translations/stories/buildfarm/xx-build-summary.txt (+69/-0)
To merge this branch: bzr merge lp:~jtv/launchpad/bug-536797
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Canonical Launchpad Engineering ui Pending
Review via email: mp+21439@code.launchpad.net

Commit message

Minimal support for non-Soyuz build-farm jobs in builder UI.

Description of the change

= Bug 536797 =

The Builder UI is not very good at dealing with non-Soyuz build-farm jobs. Most build-farm job types have an IBuildBase associated with them, but not all. (It's not required by the IBuildFarmJob interface either).

This branch, after discussion with al-maisan, bigjools, noodles, wgrant, and probably others introduces a bit more UI support for coping with different types of build-farm job. It does this by introducing a few new interfaces: IBuildFarmBuildJob represents an IBuildFarmJob that also has an IBuild reference, and IBuildFarmBranchJob represents one that has an IBranch reference. These inherit from IBuildFarmJob so that zope can pick the most specific applicable interface to render in the UI.

You'll see a bunch of new templates: buildqueue-current.pt summarizes the IBuildQueue a Builder is currently working on. This then renders the associated IBuildQueue.specific_job, which can be one of several IBuildFarmJob-implementing types. These are differentiated along the lines of the IBuildFarmJob / IBuildFarmBuildJob / IBuildFarmBranchJob interface hierarchy. For the existing Soyuz-style build jobs this doesn't change the UI at all, apart from an icon that renders in a slightly different place.

I also added a link formatter for IBuildBase, to help cut down on complicated logic in the TAL.

In the same vein you'll see the access check for showing a job's log tail moved into security.py. Doing this in the TAL became too complicated with the different build types. It's quite possible that the current check is too strict; it may make sense to show a build's log if either its branch or its build is accessible. I think experience will have to show this. Soyuz is not the kind of thing you perfect all in one branch. In any case it's hard to exercise just now: our jobs that generate translation templates don't support private branches.

A BuildQueue's "current build duration" moves from the view into the model, which seems more appropriate and lets it reuse a "fake clock" fixture that's already there. I did replace some use of datetime.utcnow() with datetime.now(timezone('UTC')); the former returns timezone-unaware times which breaks several tests.

You'll note that some of the files I introduced deserve to be in better places. I filed a separate bug about this, because I've just about hit the limit of what I can do in one branch, and a step like that would need broader discussion.

To test (runs a lot of tests, but many exercise one change or another):
{{{
./bin/test -vv -t buildmaster -t soyuz -t 'translations.*build'
}}}

To Q/A, first off, browser around the /builders page. Then follow the instructions on https://dev.launchpad.net/Translations/BuildTemplatesOnLocalBuildFarm to create a TranslationTemplatesBuildJob, and look again. Now you'll see a different summary of the currently running job, since it has no associated IBuild.

I found no lint. I'm firing off an EC2 run to make sure I didn't miss any tests.

Jeroen

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

Hi Jeroen,

Thanks for the nice agrarian branch.

Running your test command (was that just 'make check' in disguise?) I
got a failure on test_dispatchBuildToSlave.

Jeroen I tried on two different occasions to follow your instructions
in order to exercise the new UI. Both times were very time consuming
and ultimately failed. I give up. I approve the code but not the UI
-- you needed to get a separate UI review anyway but as a code
reviewer I like to have a once-over.

> === modified file 'lib/canonical/launchpad/security.py'
> --- lib/canonical/launchpad/security.py 2010-03-12 08:59:36 +0000
> +++ lib/canonical/launchpad/security.py 2010-03-16 11:13:21 +0000
> @@ -1505,6 +1508,41 @@
> return auth_spr.checkUnauthenticated()
>
>
> +class ViewBuildFarmJob(AuthorizationBase):
> + """Permission to view an `IBuildFarmJob`.
> +
> + This permission is based entirely on permission to view the
> + associated `IBuild` and/or `IBranch`.
> + """
> + permission = 'launchpad.View'
> + usedfor = IBuildFarmJob
> +
> + def _getBuildPermission(self):
> + return ViewBuildRecord(self.obj.build)
> +
> + def _checkBranchVisibility(self, user=None):
> + """Is the user free to view any branches associated with this job?"""
> + if not IBuildFarmBranchJob.providedBy(self.obj):
> + return True
> + else:
> + return self.obj.branch.visibleByUser(user)

I don't like the inverted test (our coding standards recommend against
having a 'not' test if both paths are present) and the fact a return
value of True has dual meanings.

> + def checkAuthenticated(self, user):
> + if not self._checkBranchVisibility(user):
> + return False
> +
> + if IBuildFarmBuildJob.providedBy(self.obj):
> + if not self._getBuildPermission().checkAuthenticated(user):
> + return False
> +
> + return True

Could you write a single method that provides checkAuthenticated and
checkUnauthenticated for your two possiblities? A nice little wrapper
would make the real methods more readable.

> + def checkUnauthenticated(self):
> + if not self._checkBranchVisibility():
> + return False
> + return self._getBuildPermission().checkUnauthenticated()

Again I find this implementation confusing.

> +
> class AdminQuestion(AdminByAdminsTeam):
> permission = 'launchpad.Admin'
> usedfor = IQuestion

> === modified file 'lib/canonical/launchpad/webapp/tales.py'
> --- lib/canonical/launchpad/webapp/tales.py 2010-03-10 12:50:18 +0000
> +++ lib/canonical/launchpad/webapp/tales.py 2010-03-16 11:13:21 +0000
> @@ -1514,6 +1514,33 @@
> return url
>
>
> +class BuildBaseFormatterAPI(ObjectFormatterAPI):
> + """Adapter providing fmt support for `IBuildBase` objects."""

You have plenty of room so please spell out formatter.

> + def _composeArchiveReference(self, archive):
> + if archive.is_ppa:
> + return " [%s/%s]" % (
> + cgi.escape(archive.owner.name), cgi.escape(archive.name))
> + else:
> + return ""
> +
> + def icon(self, view_name):
> + if not check_permi...

review: Approve (code)
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (11.1 KiB)

Hi Brad,

This is a really good review. Thanks for that! It does show that I was past my saturation point for this branch, doesn't it?

> Running your test command (was that just 'make check' in disguise?) I
> got a failure on test_dispatchBuildToSlave.

Hmm... my EC2 test came through clean though, as do my manual runs. You don't happen to have a log of this failure somewhere, do you? I wouldn't normally ask, but IIRC you were the one who wrote the retest tool, so you may have a more reliable habit of keeping them.

> Jeroen I tried on two different occasions to follow your instructions
> in order to exercise the new UI. Both times were very time consuming
> and ultimately failed. I give up. I approve the code but not the UI
> -- you needed to get a separate UI review anyway but as a code
> reviewer I like to have a once-over.

Alright. Sorry to put you to the trouble; this is indeed very hard work and it's been slowing me down no end. If you have any notes at all about what made it hard, please let me know so I can improve the documentation. I believe it's important to us to make this accessible to encourage development.

> > === modified file 'lib/canonical/launchpad/security.py'
> > --- lib/canonical/launchpad/security.py 2010-03-12 08:59:36 +0000
> > +++ lib/canonical/launchpad/security.py 2010-03-16 11:13:21 +0000
> > @@ -1505,6 +1508,41 @@
> > return auth_spr.checkUnauthenticated()
> >
> >
> > +class ViewBuildFarmJob(AuthorizationBase):
> > + """Permission to view an `IBuildFarmJob`.
> > +
> > + This permission is based entirely on permission to view the
> > + associated `IBuild` and/or `IBranch`.
> > + """
> > + permission = 'launchpad.View'
> > + usedfor = IBuildFarmJob
> > +
> > + def _getBuildPermission(self):
> > + return ViewBuildRecord(self.obj.build)
> > +
> > + def _checkBranchVisibility(self, user=None):
> > + """Is the user free to view any branches associated with this
> job?"""
> > + if not IBuildFarmBranchJob.providedBy(self.obj):
> > + return True
> > + else:
> > + return self.obj.branch.visibleByUser(user)
>
> I don't like the inverted test (our coding standards recommend against
> having a 'not' test if both paths are present) and the fact a return
> value of True has dual meanings.

You're right, of course. This is a missing final cleanup after various refactorings. I put the interface checks in separate "get {IBranch,IBuildBase} for this job if it has one" methods to isolate the complexity.

> > + def checkAuthenticated(self, user):
> > + if not self._checkBranchVisibility(user):
> > + return False
> > +
> > + if IBuildFarmBuildJob.providedBy(self.obj):
> > + if not self._getBuildPermission().checkAuthenticated(user):
> > + return False
> > +
> > + return True
>
> Could you write a single method that provides checkAuthenticated and
> checkUnauthenticated for your two possiblities? A nice little wrapper
> would make the real methods more readable.

TBH I still don't see why these security classes don't work like that in the first place. What cou...

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Went over this with wgrant, and (IIRC) noodles & bigjools. The conclusion was that the status icon for regular builds is now in the wrong place; even though it's the build status, it's shown in the UI as the builder status. And since I was traveling, wgrant has already landed a fix.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-03-06 04:57:40 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-03-17 06:01:34 +0000
@@ -322,7 +322,8 @@
322 IStructuralSubscriptionTarget)322 IStructuralSubscriptionTarget)
323323
324patch_reference_property(324patch_reference_property(
325 ISourcePackageRelease, 'source_package_recipe_build', ISourcePackageRecipeBuild)325 ISourcePackageRelease, 'source_package_recipe_build',
326 ISourcePackageRecipeBuild)
326327
327# IHasBugs328# IHasBugs
328patch_plain_parameter_type(329patch_plain_parameter_type(
329330
=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2010-03-16 10:47:00 +0000
+++ lib/canonical/launchpad/security.py 2010-03-17 06:01:34 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Security policies for using content objects."""4"""Security policies for using content objects."""
@@ -32,8 +32,9 @@
32from lp.bugs.interfaces.bugnomination import IBugNomination32from lp.bugs.interfaces.bugnomination import IBugNomination
33from lp.bugs.interfaces.bugsubscription import IBugSubscription33from lp.bugs.interfaces.bugsubscription import IBugSubscription
34from lp.bugs.interfaces.bugtracker import IBugTracker34from lp.bugs.interfaces.bugtracker import IBugTracker
35from lp.soyuz.interfaces.build import IBuild
36from lp.buildmaster.interfaces.builder import IBuilder, IBuilderSet35from lp.buildmaster.interfaces.builder import IBuilder, IBuilderSet
36from lp.buildmaster.interfaces.buildfarmbranchjob import IBuildFarmBranchJob
37from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
37from lp.code.interfaces.codeimport import ICodeImport38from lp.code.interfaces.codeimport import ICodeImport
38from lp.code.interfaces.codeimportjob import (39from lp.code.interfaces.codeimportjob import (
39 ICodeImportJobSet, ICodeImportJobWorkflow)40 ICodeImportJobSet, ICodeImportJobWorkflow)
@@ -70,6 +71,8 @@
70from lp.soyuz.interfaces.packageset import IPackageset, IPackagesetSet71from lp.soyuz.interfaces.packageset import IPackageset, IPackagesetSet
71from lp.translations.interfaces.pofile import IPOFile72from lp.translations.interfaces.pofile import IPOFile
72from lp.translations.interfaces.potemplate import IPOTemplate73from lp.translations.interfaces.potemplate import IPOTemplate
74from lp.soyuz.interfaces.build import IBuild
75from lp.soyuz.interfaces.buildfarmbuildjob import IBuildFarmBuildJob
73from lp.soyuz.interfaces.publishing import (76from lp.soyuz.interfaces.publishing import (
74 IBinaryPackagePublishingHistory, IPublishingEdit,77 IBinaryPackagePublishingHistory, IPublishingEdit,
75 ISourcePackagePublishingHistory)78 ISourcePackagePublishingHistory)
@@ -1508,6 +1511,53 @@
1508 return auth_spr.checkUnauthenticated()1511 return auth_spr.checkUnauthenticated()
15091512
15101513
1514class ViewBuildFarmJob(AuthorizationBase):
1515 """Permission to view an `IBuildFarmJob`.
1516
1517 This permission is based entirely on permission to view the
1518 associated `IBuild` and/or `IBranch`.
1519 """
1520 permission = 'launchpad.View'
1521 usedfor = IBuildFarmJob
1522
1523 def _getBranch(self):
1524 """Get `IBranch` associated with this job, if any."""
1525 if IBuildFarmBranchJob.providedBy(self.obj):
1526 return self.obj.branch
1527 else:
1528 return None
1529
1530 def _getBuild(self):
1531 """Get `IBuildBase` associated with this job, if any."""
1532 if IBuildFarmBuildJob.providedBy(self.obj):
1533 return self.obj.build
1534 else:
1535 return None
1536
1537 def _checkBuildPermission(self, user=None):
1538 """Check access to `IBuildBase` for this job."""
1539 permission = ViewBuildRecord(self.obj.build)
1540 if user is None:
1541 return permission.checkUnauthenticated()
1542 else:
1543 return permission.checkAuthenticated(user)
1544
1545 def _checkAccess(self, user=None):
1546 """Unified access check for anonymous and authenticated users."""
1547 branch = self._getBranch()
1548 if branch is not None and not branch.visibleByUser(user):
1549 return False
1550
1551 build = self._getBuild()
1552 if build is not None and not self._checkBuildPermission(user):
1553 return False
1554
1555 return True
1556
1557 checkAuthenticated = _checkAccess
1558 checkUnauthenticated = _checkAccess
1559
1560
1511class AdminQuestion(AdminByAdminsTeam):1561class AdminQuestion(AdminByAdminsTeam):
1512 permission = 'launchpad.Admin'1562 permission = 'launchpad.Admin'
1513 usedfor = IQuestion1563 usedfor = IQuestion
15141564
=== modified file 'lib/canonical/launchpad/webapp/configure.zcml'
--- lib/canonical/launchpad/webapp/configure.zcml 2010-02-17 11:13:06 +0000
+++ lib/canonical/launchpad/webapp/configure.zcml 2010-03-17 06:01:34 +0000
@@ -426,6 +426,13 @@
426 />426 />
427427
428 <adapter428 <adapter
429 for="lp.buildmaster.interfaces.buildbase.IBuildBase"
430 provides="zope.traversing.interfaces.IPathAdapter"
431 factory="canonical.launchpad.webapp.tales.BuildBaseFormatterAPI"
432 name="fmt"
433 />
434
435 <adapter
429 for="lp.code.interfaces.codeimportmachine.ICodeImportMachine"436 for="lp.code.interfaces.codeimportmachine.ICodeImportMachine"
430 provides="zope.traversing.interfaces.IPathAdapter"437 provides="zope.traversing.interfaces.IPathAdapter"
431 factory="canonical.launchpad.webapp.tales.CodeImportMachineFormatterAPI"438 factory="canonical.launchpad.webapp.tales.CodeImportMachineFormatterAPI"
432439
=== modified file 'lib/canonical/launchpad/webapp/tales.py'
--- lib/canonical/launchpad/webapp/tales.py 2010-03-10 12:50:18 +0000
+++ lib/canonical/launchpad/webapp/tales.py 2010-03-17 06:01:34 +0000
@@ -1514,6 +1514,33 @@
1514 return url1514 return url
15151515
15161516
1517class BuildBaseFormatterAPI(ObjectFormatterAPI):
1518 """Adapter providing fmt support for `IBuildBase` objects."""
1519 def _composeArchiveReference(self, archive):
1520 if archive.is_ppa:
1521 return " [%s/%s]" % (
1522 cgi.escape(archive.owner.name), cgi.escape(archive.name))
1523 else:
1524 return ""
1525
1526 def icon(self, view_name):
1527 if not check_permission('launchpad.View', self._context):
1528 return '<img src="/@@/processing" alt="[build]" />'
1529
1530 return BuildImageDisplayAPI(self._context).icon()
1531
1532 def link(self, view_name, rootsite=None):
1533 icon = self.icon(view_name)
1534 build = self._context
1535 if not check_permission('launchpad.View', build):
1536 return '%s private source' % icon
1537
1538 url = self.url(view_name=view_name, rootsite=rootsite)
1539 title = cgi.escape(build.title)
1540 archive = self._composeArchiveReference(build.archive)
1541 return '<a href="%s">%s%s</a>%s' % (url, icon, title, archive)
1542
1543
1517class CodeImportMachineFormatterAPI(CustomizableFormatter):1544class CodeImportMachineFormatterAPI(CustomizableFormatter):
1518 """Adapter providing fmt support for CodeImport objects"""1545 """Adapter providing fmt support for CodeImport objects"""
15191546
15201547
=== added file 'lib/lp/buildmaster/interfaces/buildfarmbranchjob.py'
--- lib/lp/buildmaster/interfaces/buildfarmbranchjob.py 1970-01-01 00:00:00 +0000
+++ lib/lp/buildmaster/interfaces/buildfarmbranchjob.py 2010-03-17 06:01:34 +0000
@@ -0,0 +1,21 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Interface for `IBuildFarmJob`s that are also `IBranchJob`s."""
5
6__metaclass__ = type
7__all__ = [
8 'IBuildFarmBranchJob'
9 ]
10
11from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
12from lp.code.interfaces.branchjob import IBranchJob
13
14
15class IBuildFarmBranchJob(IBuildFarmJob, IBranchJob):
16 """An `IBuildFarmJob` that's also an `IBranchJob`.
17
18 Use this interface for `IBuildFarmJob` implementations that do not
19 have a "build" attribute but do implement `IBranchJob`, so that the
20 UI can render appropriate status information.
21 """
022
=== modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py'
--- lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-01-30 05:27:48 +0000
+++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-03-17 06:01:34 +0000
@@ -91,7 +91,6 @@
91 "return None."))91 "return None."))
9292
9393
94
95class ISpecificBuildFarmJobClass(Interface):94class ISpecificBuildFarmJobClass(Interface):
96 """Class interface provided by `IBuildFarmJob` classes.95 """Class interface provided by `IBuildFarmJob` classes.
9796
9897
=== modified file 'lib/lp/buildmaster/interfaces/buildqueue.py'
--- lib/lp/buildmaster/interfaces/buildqueue.py 2010-03-05 13:52:32 +0000
+++ lib/lp/buildmaster/interfaces/buildqueue.py 2010-03-17 06:01:34 +0000
@@ -72,6 +72,10 @@
72 title=_("Estimated Job Duration"), required=True,72 title=_("Estimated Job Duration"), required=True,
73 description=_("Estimated job duration interval."))73 description=_("Estimated job duration interval."))
7474
75 current_build_duration = Timedelta(
76 title=_("Current build duration"), required=False,
77 description=_("Time spent building so far."))
78
75 def manualScore(value):79 def manualScore(value):
76 """Manually set a score value to a queue item and lock it."""80 """Manually set a score value to a queue item and lock it."""
7781
7882
=== modified file 'lib/lp/buildmaster/model/buildqueue.py'
--- lib/lp/buildmaster/model/buildqueue.py 2010-03-08 12:53:59 +0000
+++ lib/lp/buildmaster/model/buildqueue.py 2010-03-17 06:01:34 +0000
@@ -14,6 +14,7 @@
14from collections import defaultdict14from collections import defaultdict
15from datetime import datetime, timedelta15from datetime import datetime, timedelta
16import logging16import logging
17import pytz
1718
18from sqlobject import (19from sqlobject import (
19 StringCol, ForeignKey, BoolCol, IntCol, IntervalCol, SQLObjectNotFound)20 StringCol, ForeignKey, BoolCol, IntCol, IntervalCol, SQLObjectNotFound)
@@ -118,6 +119,15 @@
118 """See `IBuildQueue`."""119 """See `IBuildQueue`."""
119 return self.job.date_started120 return self.job.date_started
120121
122 @property
123 def current_build_duration(self):
124 """See `IBuildQueue`."""
125 date_started = self.date_started
126 if date_started is None:
127 return None
128 else:
129 return self._now() - date_started
130
121 def destroySelf(self):131 def destroySelf(self):
122 """Remove this record and associated job/specific_job."""132 """Remove this record and associated job/specific_job."""
123 job = self.job133 job = self.job
@@ -464,8 +474,8 @@
464474
465 @staticmethod475 @staticmethod
466 def _now():476 def _now():
467 """Provide utcnow() while allowing test code to monkey-patch this."""477 """Return current time (UTC). Overridable for test purposes."""
468 return datetime.utcnow()478 return datetime.now(pytz.UTC)
469479
470480
471class BuildQueueSet(object):481class BuildQueueSet(object):
472482
=== modified file 'lib/lp/buildmaster/tests/test_buildqueue.py'
--- lib/lp/buildmaster/tests/test_buildqueue.py 2010-03-10 21:30:57 +0000
+++ lib/lp/buildmaster/tests/test_buildqueue.py 2010-03-17 06:01:34 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
3# pylint: disable-msg=C03243# pylint: disable-msg=C0324
44
@@ -6,13 +6,14 @@
66
7from datetime import datetime, timedelta7from datetime import datetime, timedelta
8from pytz import utc8from pytz import utc
9from unittest import TestLoader
910
10from zope.component import getUtility11from zope.component import getUtility
11from zope.interface.verify import verifyObject12from zope.interface.verify import verifyObject
1213
13from canonical.launchpad.webapp.interfaces import (14from canonical.launchpad.webapp.interfaces import (
14 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)15 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
15from canonical.testing import LaunchpadZopelessLayer16from canonical.testing import LaunchpadZopelessLayer, ZopelessDatabaseLayer
1617
17from lp.buildmaster.interfaces.buildbase import BuildStatus18from lp.buildmaster.interfaces.buildbase import BuildStatus
18from lp.buildmaster.interfaces.builder import IBuilderSet19from lp.buildmaster.interfaces.builder import IBuilderSet
@@ -28,6 +29,7 @@
28from lp.soyuz.model.build import Build29from lp.soyuz.model.build import Build
29from lp.soyuz.tests.test_publishing import SoyuzTestPublisher30from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
30from lp.testing import TestCaseWithFactory31from lp.testing import TestCaseWithFactory
32from lp.testing.fakemethod import FakeMethod
3133
3234
33def find_job(test, name, processor='386'):35def find_job(test, name, processor='386'):
@@ -140,10 +142,12 @@
140 This avoids spurious test failures.142 This avoids spurious test failures.
141 """143 """
142 # Use the date/time the job started if available.144 # Use the date/time the job started if available.
143 time_stamp = buildqueue.job.date_started145 if buildqueue.job.date_started:
144 if not time_stamp:146 time_stamp = buildqueue.job.date_started
145 time_stamp = datetime.now(utc)147 else:
146 buildqueue._now = lambda: time_stamp148 time_stamp = buildqueue._now()
149
150 buildqueue._now = FakeMethod(result=time_stamp)
147 return time_stamp151 return time_stamp
148152
149153
@@ -209,7 +213,6 @@
209 "An active build job must have a builder.")213 "An active build job must have a builder.")
210214
211215
212
213class TestBuildQueueBase(TestCaseWithFactory):216class TestBuildQueueBase(TestCaseWithFactory):
214 """Setup the test publisher and some builders."""217 """Setup the test publisher and some builders."""
215218
@@ -605,6 +608,7 @@
605 # fact that no builders capable of running the job are available.608 # fact that no builders capable of running the job are available.
606 check_mintime_to_builder(self, job, 0)609 check_mintime_to_builder(self, job, 0)
607610
611
608class MultiArchBuildsBase(TestBuildQueueBase):612class MultiArchBuildsBase(TestBuildQueueBase):
609 """Set up a test environment with builds and multiple processors."""613 """Set up a test environment with builds and multiple processors."""
610 def setUp(self):614 def setUp(self):
@@ -805,6 +809,27 @@
805 check_mintime_to_builder(self, apg_job, 0)809 check_mintime_to_builder(self, apg_job, 0)
806810
807811
812class TestBuildQueueDuration(TestCaseWithFactory):
813 layer = ZopelessDatabaseLayer
814
815 def _makeBuildQueue(self):
816 """Produce a `BuildQueue` object to test."""
817 return self.factory.makeSourcePackageRecipeBuildJob()
818
819 def test_current_build_duration_not_started(self):
820 buildqueue = self._makeBuildQueue()
821 self.assertEqual(None, buildqueue.current_build_duration)
822
823 def test_current_build_duration(self):
824 buildqueue = self._makeBuildQueue()
825 now = buildqueue._now()
826 buildqueue._now = FakeMethod(result=now)
827 age = timedelta(minutes=3)
828 buildqueue.job.date_started = now - age
829
830 self.assertEqual(age, buildqueue.current_build_duration)
831
832
808class TestJobClasses(TestCaseWithFactory):833class TestJobClasses(TestCaseWithFactory):
809 """Tests covering build farm job type classes."""834 """Tests covering build farm job type classes."""
810 layer = LaunchpadZopelessLayer835 layer = LaunchpadZopelessLayer
@@ -1301,3 +1326,7 @@
1301 assign_to_builder(self, 'xxr-daptup', 1, None)1326 assign_to_builder(self, 'xxr-daptup', 1, None)
1302 postgres_build, postgres_job = find_job(self, 'postgres', '386')1327 postgres_build, postgres_job = find_job(self, 'postgres', '386')
1303 check_estimate(self, postgres_job, 120)1328 check_estimate(self, postgres_job, 120)
1329
1330
1331def test_suite():
1332 return TestLoader().loadTestsFromName(__name__)
13041333
=== modified file 'lib/lp/code/interfaces/sourcepackagerecipebuild.py'
--- lib/lp/code/interfaces/sourcepackagerecipebuild.py 2010-02-19 06:34:18 +0000
+++ lib/lp/code/interfaces/sourcepackagerecipebuild.py 2010-03-17 06:01:34 +0000
@@ -21,7 +21,7 @@
21from canonical.launchpad import _21from canonical.launchpad import _
2222
23from lp.buildmaster.interfaces.buildbase import IBuildBase23from lp.buildmaster.interfaces.buildbase import IBuildBase
24from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob24from lp.soyuz.interfaces.buildfarmbuildjob import IBuildFarmBuildJob
25from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe25from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe
26from lp.registry.interfaces.person import IPerson26from lp.registry.interfaces.person import IPerson
27from lp.registry.interfaces.distroseries import IDistroSeries27from lp.registry.interfaces.distroseries import IDistroSeries
@@ -79,18 +79,13 @@
79 """79 """
8080
8181
82class ISourcePackageRecipeBuildJob(IBuildFarmJob):82class ISourcePackageRecipeBuildJob(IBuildFarmBuildJob):
83 """A read-only interface for recipe build jobs."""83 """A read-only interface for recipe build jobs."""
8484
85 job = Reference(85 job = Reference(
86 IJob, title=_("Job"), required=True, readonly=True,86 IJob, title=_("Job"), required=True, readonly=True,
87 description=_("Data common to all job types."))87 description=_("Data common to all job types."))
8888
89 build = Reference(
90 ISourcePackageRecipeBuild, title=_("Build"),
91 required=True, readonly=True,
92 description=_("Build record associated with this job."))
93
9489
95class ISourcePackageRecipeBuildJobSource(Interface):90class ISourcePackageRecipeBuildJobSource(Interface):
96 """A utility of this interface used to create _things_."""91 """A utility of this interface used to create _things_."""
9792
=== modified file 'lib/lp/soyuz/browser/build.py'
--- lib/lp/soyuz/browser/build.py 2010-03-10 13:28:55 +0000
+++ lib/lp/soyuz/browser/build.py 2010-03-17 06:01:34 +0000
@@ -525,4 +525,3 @@
525 @property525 @property
526 def no_results(self):526 def no_results(self):
527 return self.form_submitted and not self.complete_builds527 return self.form_submitted and not self.complete_builds
528
529528
=== modified file 'lib/lp/soyuz/browser/builder.py'
--- lib/lp/soyuz/browser/builder.py 2010-03-08 05:30:13 +0000
+++ lib/lp/soyuz/browser/builder.py 2010-03-17 06:01:34 +0000
@@ -18,9 +18,7 @@
18 'BuilderView',18 'BuilderView',
19 ]19 ]
2020
21import datetime
22import operator21import operator
23import pytz
2422
25from zope.component import getUtility23from zope.component import getUtility
26from zope.event import notify24from zope.event import notify
@@ -235,14 +233,10 @@
235233
236 @property234 @property
237 def current_build_duration(self):235 def current_build_duration(self):
238 """Return the delta representing the duration of the current job."""236 if self.context.currentjob is None:
239 if (self.context.currentjob is None or
240 self.context.currentjob.date_started is None):
241 return None237 return None
242 else:238 else:
243 UTC = pytz.timezone('UTC')239 return self.context.currentjob.current_build_duration
244 date_started = self.context.currentjob.date_started
245 return datetime.datetime.now(UTC) - date_started
246240
247 @property241 @property
248 def page_title(self):242 def page_title(self):
249243
=== modified file 'lib/lp/soyuz/browser/configure.zcml'
--- lib/lp/soyuz/browser/configure.zcml 2010-03-08 05:30:13 +0000
+++ lib/lp/soyuz/browser/configure.zcml 2010-03-17 06:01:34 +0000
@@ -1,4 +1,4 @@
1<!-- Copyright 2009 Canonical Ltd. This software is licensed under the1<!-- Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2 GNU Affero General Public License version 3 (see the file LICENSE).2 GNU Affero General Public License version 3 (see the file LICENSE).
3-->3-->
44
@@ -798,4 +798,34 @@
798 path_expression="string:+dependency/${dependency/id}"798 path_expression="string:+dependency/${dependency/id}"
799 attribute_to_parent="archive"799 attribute_to_parent="archive"
800 />800 />
801
802 <!-- XXX JeroenVermeulen 2010-03-13 bug=539395: This is buildmaster, not soyuz -->
803 <browser:page
804 for="lp.buildmaster.interfaces.buildqueue.IBuildQueue"
805 name="+current"
806 class="canonical.launchpad.webapp.publisher.LaunchpadView"
807 facet="overview"
808 permission="launchpad.View"
809 template="../templates/buildqueue-current.pt"/>
810 <browser:page
811 for="lp.buildmaster.interfaces.buildqueue.IBuildFarmJob"
812 name="+current"
813 class="canonical.launchpad.webapp.publisher.LaunchpadView"
814 facet="overview"
815 permission="launchpad.View"
816 template="../templates/buildfarmjob-current.pt"/>
817 <browser:page
818 for="lp.buildmaster.interfaces.buildfarmbranchjob.IBuildFarmBranchJob"
819 name="+current"
820 class="canonical.launchpad.webapp.publisher.LaunchpadView"
821 facet="overview"
822 permission="launchpad.View"
823 template="../templates/buildfarmbranchjob-current.pt"/>
824 <browser:page
825 for="lp.soyuz.interfaces.buildfarmbuildjob.IBuildFarmBuildJob"
826 name="+current"
827 class="canonical.launchpad.webapp.publisher.LaunchpadView"
828 facet="overview"
829 permission="launchpad.View"
830 template="../templates/buildfarmbuildjob-current.pt"/>
801</configure>831</configure>
802832
=== modified file 'lib/lp/soyuz/doc/build-estimated-dispatch-time.txt'
--- lib/lp/soyuz/doc/build-estimated-dispatch-time.txt 2010-03-06 04:57:40 +0000
+++ lib/lp/soyuz/doc/build-estimated-dispatch-time.txt 2010-03-17 06:01:34 +0000
@@ -56,7 +56,6 @@
5656
57 >>> from datetime import datetime57 >>> from datetime import datetime
58 >>> import pytz58 >>> import pytz
59 >>> UTC = pytz.timezone('UTC')
60 >>> bob_the_builder = builder_set.get(1)59 >>> bob_the_builder = builder_set.get(1)
61 >>> cur_bqueue = bob_the_builder.currentjob60 >>> cur_bqueue = bob_the_builder.currentjob
62 >>> from lp.soyuz.interfaces.build import IBuildSet61 >>> from lp.soyuz.interfaces.build import IBuildSet
@@ -76,7 +75,7 @@
76 >>> from zope.security.proxy import removeSecurityProxy75 >>> from zope.security.proxy import removeSecurityProxy
77 >>> cur_bqueue.lastscore = 111176 >>> cur_bqueue.lastscore = 1111
78 >>> cur_bqueue.setDateStarted(77 >>> cur_bqueue.setDateStarted(
79 ... datetime(2008, 4, 1, 10, 45, 39, tzinfo=UTC))78 ... datetime(2008, 4, 1, 10, 45, 39, tzinfo=pytz.UTC))
80 >>> print cur_bqueue.date_started79 >>> print cur_bqueue.date_started
81 2008-04-01 10:45:39+00:0080 2008-04-01 10:45:39+00:00
8281
@@ -89,7 +88,7 @@
89The estimated start time for the pending job is either now or lies88The estimated start time for the pending job is either now or lies
90in the future.89in the future.
9190
92 >>> now = datetime.utcnow()91 >>> now = datetime.now(pytz.UTC)
93 >>> def job_start_estimate(build):92 >>> def job_start_estimate(build):
94 ... return build.buildqueue_record.getEstimatedJobStartTime()93 ... return build.buildqueue_record.getEstimatedJobStartTime()
95 >>> estimate = job_start_estimate(alsa_build)94 >>> estimate = job_start_estimate(alsa_build)
@@ -147,7 +146,7 @@
147Since the 'iceweasel' build has a higher score (666) than the 'pmount'146Since the 'iceweasel' build has a higher score (666) than the 'pmount'
148build (66) its estimated dispatch time is essentially "now".147build (66) its estimated dispatch time is essentially "now".
149148
150 >>> now = datetime.utcnow()149 >>> now = datetime.now(pytz.UTC)
151 >>> estimate = job_start_estimate(iceweasel_build)150 >>> estimate = job_start_estimate(iceweasel_build)
152 >>> estimate > now151 >>> estimate > now
153 True152 True
154153
=== added file 'lib/lp/soyuz/interfaces/buildfarmbuildjob.py'
--- lib/lp/soyuz/interfaces/buildfarmbuildjob.py 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/interfaces/buildfarmbuildjob.py 2010-03-17 06:01:34 +0000
@@ -0,0 +1,21 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Interface to support UI for most build-farm jobs."""
5
6__metaclass__ = type
7__all__ = [
8 'IBuildFarmBuildJob'
9 ]
10
11from canonical.launchpad import _
12from lazr.restful.fields import Reference
13from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
14from lp.soyuz.interfaces.build import IBuild
15
16
17class IBuildFarmBuildJob(IBuildFarmJob):
18 """An `IBuildFarmJob` with an `IBuild` reference."""
19 build = Reference(
20 IBuild, title=_("Build"), required=True, readonly=True,
21 description=_("Build record associated with this job."))
022
=== modified file 'lib/lp/soyuz/interfaces/buildpackagejob.py'
--- lib/lp/soyuz/interfaces/buildpackagejob.py 2010-01-20 22:09:26 +0000
+++ lib/lp/soyuz/interfaces/buildpackagejob.py 2010-03-17 06:01:34 +0000
@@ -15,12 +15,12 @@
1515
16from canonical.launchpad import _16from canonical.launchpad import _
17from lazr.restful.fields import Reference17from lazr.restful.fields import Reference
18from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
19from lp.services.job.interfaces.job import IJob18from lp.services.job.interfaces.job import IJob
20from lp.soyuz.interfaces.build import IBuild19from lp.soyuz.interfaces.build import IBuild
2120from lp.soyuz.interfaces.buildfarmbuildjob import IBuildFarmBuildJob
2221
23class IBuildPackageJob(IBuildFarmJob):22
23class IBuildPackageJob(IBuildFarmBuildJob):
24 """A read-only interface for build package jobs."""24 """A read-only interface for build package jobs."""
2525
26 id = Int(title=_('ID'), required=True, readonly=True)26 id = Int(title=_('ID'), required=True, readonly=True)
2727
=== modified file 'lib/lp/soyuz/templates/builder-index.pt'
--- lib/lp/soyuz/templates/builder-index.pt 2009-11-12 16:19:18 +0000
+++ lib/lp/soyuz/templates/builder-index.pt 2010-03-17 06:01:34 +0000
@@ -104,32 +104,10 @@
104 </tal:buildernok>104 </tal:buildernok>
105 </tal:no_job>105 </tal:no_job>
106106
107 <tal:comment replace="nothing">107 <tal:job-header condition="job">
108 In the very near future, 'job' will not just be a Build job.
109 The template needs to cope with that as and when new job types are
110 added.
111 </tal:comment>
112 <tal:job condition="job">
113 <span class="sortkey" tal:content="job/id" />108 <span class="sortkey" tal:content="job/id" />
114 <tal:build define="build job/specific_job/build">109 <tal:specific-job replace="structure job/specific_job/@@+current" />
115 <tal:visible condition="build/required:launchpad.View">110 </tal:job-header>
116 <tal:icon replace="structure build/image:icon" />
117 Building
118 <a tal:attributes="href build/fmt:url"
119 tal:content="build/title"
120 >i386 build of mozilla-firefox 0.9 in ubuntu hoary RELEASE</a>
121 <tal:ppa condition="build/archive/is_ppa"
122 define="ppa build/archive;">
123 <span tal:replace="string: [${ppa/owner/name}/${ppa/name}]"
124 >[cprov/ppa]</span>
125 </tal:ppa>
126 </tal:visible>
127 <tal:restricted condition="not: build/required:launchpad.View">
128 <img src="/@@/processing" alt="[building]" />
129 Building private source
130 </tal:restricted>
131 </tal:build>
132 </tal:job>
133111
134 </metal:macro>112 </metal:macro>
135113
@@ -147,7 +125,7 @@
147 </span>125 </span>
148 Current status</h2>126 Current status</h2>
149127
150 <p tal:define="builder context">128 <p tal:define="builder context" id="current-build-summary">
151 <metal:summary use-macro="template/macros/status-summary" />129 <metal:summary use-macro="template/macros/status-summary" />
152 </p>130 </p>
153131
@@ -156,27 +134,7 @@
156 context/failnotes/fmt:text-to-html" />134 context/failnotes/fmt:text-to-html" />
157 </tal:buildernok>135 </tal:buildernok>
158136
159 <tal:job condition="job">137 <tal:job condition="job" replace="structure job/@@+current" />
160 <p class="sprite">Started
161 <span tal:attributes="title job/job/date_started/fmt:datetime"
162 tal:content="view/current_build_duration/fmt:exactduration"
163 /> ago.</p>
164 <tal:visible
165 define="build job/specific_job/build"
166 condition="build/required:launchpad.View">
167 <tal:logtail condition="job/logtail">
168 <h3>Buildlog</h3>
169 <div tal:content="structure job/logtail/fmt:text-to-html"
170 id="buildlog-tail" class="logtail">
171 Things are crashing and burning all over the place.
172 </div>
173 <p class="discreet" tal:condition="view/user">
174 Updated on
175 <span tal:replace="structure view/user/fmt:local-time"/>
176 </p>
177 </tal:logtail>
178 </tal:visible>
179 </tal:job>
180 </div>138 </div>
181139
182 </metal:macro>140 </metal:macro>
183141
=== added file 'lib/lp/soyuz/templates/buildfarmbranchjob-current.pt'
--- lib/lp/soyuz/templates/buildfarmbranchjob-current.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/templates/buildfarmbranchjob-current.pt 2010-03-17 06:01:34 +0000
@@ -0,0 +1,11 @@
1<tal:root
2 xmlns:tal="http://xml.zope.org/namespaces/tal"
3 xmlns:metal="http://xml.zope.org/namespaces/metal"
4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
5 omit-tag="">
6
7 <img src="/@@/processing" alt="[building]" />
8 Working on
9 <tal:jobtype replace="context/__class__/__name__" />
10 for branch <tal:branch replace="structure context/branch/fmt:link" />.
11</tal:root>
012
=== added file 'lib/lp/soyuz/templates/buildfarmbuildjob-current.pt'
--- lib/lp/soyuz/templates/buildfarmbuildjob-current.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/templates/buildfarmbuildjob-current.pt 2010-03-17 06:01:34 +0000
@@ -0,0 +1,8 @@
1<tal:root
2 xmlns:tal="http://xml.zope.org/namespaces/tal"
3 xmlns:metal="http://xml.zope.org/namespaces/metal"
4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
5 omit-tag="">
6
7 Building <tal:build replace="structure context/build/fmt:link" />
8</tal:root>
09
=== added file 'lib/lp/soyuz/templates/buildfarmjob-current.pt'
--- lib/lp/soyuz/templates/buildfarmjob-current.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/templates/buildfarmjob-current.pt 2010-03-17 06:01:34 +0000
@@ -0,0 +1,10 @@
1<tal:root
2 xmlns:tal="http://xml.zope.org/namespaces/tal"
3 xmlns:metal="http://xml.zope.org/namespaces/metal"
4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
5 omit-tag="">
6
7 <img src="/@@/processing" alt="[building]" />
8 Working on
9 <tal:jobtype replace="context/__class__/__name__" />.
10</tal:root>
011
=== added file 'lib/lp/soyuz/templates/buildqueue-current.pt'
--- lib/lp/soyuz/templates/buildqueue-current.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/templates/buildqueue-current.pt 2010-03-17 06:01:34 +0000
@@ -0,0 +1,24 @@
1<tal:root
2 xmlns:tal="http://xml.zope.org/namespaces/tal"
3 xmlns:metal="http://xml.zope.org/namespaces/metal"
4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
5 omit-tag="">
6
7 <p class="sprite">Started
8 <span
9 tal:attributes="title context/job/date_started/fmt:datetime"
10 tal:content="context/current_build_duration/fmt:exactduration" />
11 ago.
12 </p>
13 <tal:logtail condition="context/specific_job/required:launchpad.View">
14 <h2>Buildlog</h2>
15 <div tal:content="structure context/logtail/fmt:text-to-html"
16 id="buildlog-tail"
17 class="logtail">
18 Things are crashing and burning all over the place.
19 </div>
20 <p class="discreet" tal:condition="view/user">
21 Updated on <tal:date replace="structure view/user/fmt:local-time" />
22 </p>
23 </tal:logtail>
24</tal:root>
025
=== modified file 'lib/lp/translations/model/translationtemplatesbuildjob.py'
--- lib/lp/translations/model/translationtemplatesbuildjob.py 2010-03-06 00:21:57 +0000
+++ lib/lp/translations/model/translationtemplatesbuildjob.py 2010-03-17 06:01:34 +0000
@@ -19,10 +19,11 @@
19 DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE, MASTER_FLAVOR)19 DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE, MASTER_FLAVOR)
2020
21from lp.buildmaster.interfaces.buildfarmjob import (21from lp.buildmaster.interfaces.buildfarmjob import (
22 BuildFarmJobType, IBuildFarmJob, ISpecificBuildFarmJobClass)22 BuildFarmJobType, ISpecificBuildFarmJobClass)
23from lp.buildmaster.model.buildfarmjob import BuildFarmJob23from lp.buildmaster.model.buildfarmjob import BuildFarmJob
24from lp.buildmaster.model.buildqueue import BuildQueue24from lp.buildmaster.model.buildqueue import BuildQueue
25from lp.code.interfaces.branchjob import IBranchJob, IRosettaUploadJobSource25from lp.code.interfaces.branchjob import IRosettaUploadJobSource
26from lp.buildmaster.interfaces.buildfarmbranchjob import IBuildFarmBranchJob
26from lp.code.model.branchjob import BranchJob, BranchJobDerived, BranchJobType27from lp.code.model.branchjob import BranchJob, BranchJobDerived, BranchJobType
27from lp.translations.interfaces.translationtemplatesbuildjob import (28from lp.translations.interfaces.translationtemplatesbuildjob import (
28 ITranslationTemplatesBuildJobSource)29 ITranslationTemplatesBuildJobSource)
@@ -34,7 +35,7 @@
3435
35 Implementation-wise, this is actually a `BranchJob`.36 Implementation-wise, this is actually a `BranchJob`.
36 """37 """
37 implements(IBranchJob, IBuildFarmJob)38 implements(IBuildFarmBranchJob)
3839
39 class_job_type = BranchJobType.TRANSLATION_TEMPLATES_BUILD40 class_job_type = BranchJobType.TRANSLATION_TEMPLATES_BUILD
4041
@@ -101,7 +102,7 @@
101 """See `ITranslationTemplatesBuildJobSource`."""102 """See `ITranslationTemplatesBuildJobSource`."""
102 store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)103 store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
103104
104 # We don't have any JSON metadata for this BranchJob type.105 # Pass public HTTP URL for the branch.
105 metadata = {'branch_url': branch.composePublicURL()}106 metadata = {'branch_url': branch.composePublicURL()}
106 branch_job = BranchJob(107 branch_job = BranchJob(
107 branch, BranchJobType.TRANSLATION_TEMPLATES_BUILD, metadata)108 branch, BranchJobType.TRANSLATION_TEMPLATES_BUILD, metadata)
108109
=== added directory 'lib/lp/translations/stories/buildfarm'
=== added file 'lib/lp/translations/stories/buildfarm/xx-build-summary.txt'
--- lib/lp/translations/stories/buildfarm/xx-build-summary.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/stories/buildfarm/xx-build-summary.txt 2010-03-17 06:01:34 +0000
@@ -0,0 +1,69 @@
1= TranslationTemplatesBuildJob Build Summary =
2
3The builders UI can show TranslationTemplateBuildJobs, although they
4look a little different from Soyuz-style jobs.
5
6== Setup ==
7
8Create a builder working on a TranslationTemplatesBuildJob for a branch.
9
10 >>> from zope.component import getUtility
11 >>> from canonical.launchpad.interfaces.launchpad import (
12 ... ILaunchpadCelebrities)
13 >>> from canonical.launchpad.interfaces.librarian import (
14 ... ILibraryFileAliasSet)
15 >>> from canonical.launchpad.scripts.logger import QuietFakeLogger
16 >>> from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
17 >>> from lp.testing.fakemethod import FakeMethod
18 >>> from lp.translations.interfaces.translations import (
19 ... TranslationsBranchImportMode)
20
21 >>> class FakeSlave:
22 ... resume = FakeMethod(result=('stdout', 'stderr', 0))
23 ... build = FakeMethod()
24 ... cacheFile = FakeMethod()
25
26 >>> login(ANONYMOUS)
27 >>> owner_email = factory.getUniqueString() + '@example.com'
28 >>> owner = factory.makePerson(email=owner_email, password='test')
29
30 >>> productseries = factory.makeProductSeries(owner=owner)
31 >>> product = productseries.product
32 >>> product.official_rosetta = True
33 >>> branch = factory.makeProductBranch(product=product, owner=owner)
34 >>> branch_url = branch.unique_name
35
36 >>> productseries.branch = factory.makeBranch()
37 >>> productseries.translations_autoimport_mode = (
38 ... TranslationsBranchImportMode.IMPORT_TEMPLATES)
39 >>> specific_job = factory.makeTranslationTemplatesBuildJob(branch=branch)
40 >>> buildqueue = getUtility(IBuildQueueSet).getByJob(specific_job.job)
41
42 >>> fake_chroot = getUtility(ILibraryFileAliasSet)[1]
43 >>> ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
44 >>> unused = ubuntu.currentseries.nominatedarchindep.addOrUpdateChroot(
45 ... fake_chroot)
46
47 >>> builder = factory.makeBuilder(vm_host=factory.getUniqueString())
48 >>> builder.setSlaveForTesting(FakeSlave())
49 >>> builder.startBuild(buildqueue, QuietFakeLogger())
50
51 >>> builder_page = canonical_url(builder)
52 >>> logout()
53
54Helper: find the current build's summary on a browser's current page.
55
56 >>> def find_build_summary(browser):
57 ... return find_tag_by_id(browser.contents, 'current-build-summary')
58
59
60== Show summary ==
61
62The job's summary shows that what type of job this is. It also links
63to the branch.
64
65 >>> user_browser.open(builder_page)
66 >>> print extract_text(find_build_summary(user_browser))
67 Working on TranslationTemplatesBuildJob for branch ...
68
69 >>> user_browser.getLink(branch_url).click()