Merge lp:~gmb/launchpad/jobbifiy-bug-heat-calculations-509193 into lp:launchpad/db-devel

Proposed by Graham Binns
Status: Merged
Merged at revision: not available
Proposed branch: lp:~gmb/launchpad/jobbifiy-bug-heat-calculations-509193
Merge into: lp:launchpad/db-devel
Diff against target: 570 lines (+483/-1)
11 files modified
cronscripts/calculate-bug-heat.py (+33/-0)
database/schema/security.cfg (+8/-0)
lib/canonical/config/schema-lazr.conf (+9/-0)
lib/lp/bugs/configure.zcml (+11/-0)
lib/lp/bugs/interfaces/bugjob.py (+66/-0)
lib/lp/bugs/model/bugheat.py (+54/-0)
lib/lp/bugs/model/bugjob.py (+148/-0)
lib/lp/bugs/scripts/bugheat.py (+3/-1)
lib/lp/bugs/tests/test_bugheat.py (+95/-0)
lib/lp/bugs/tests/test_bugjob.py (+55/-0)
lib/lp/services/job/interfaces/job.py (+1/-0)
To merge this branch: bzr merge lp:~gmb/launchpad/jobbifiy-bug-heat-calculations-509193
Reviewer Review Type Date Requested Status
Paul Hummer (community) code Approve
Review via email: mp+17831@code.launchpad.net

Commit message

Add infrastructure to allow the Bug Tracker to use the Jobs system for task. Also add specific infrastructure for using the Jobs system to update bug heat.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

This branch adds the necessary interfaces and implementations to the bug
tracker to:

 a. Allow us to use the Jobs system for Bug-related tasks generally and
 b. Allow us to use the Jobs system to update bug heat specifically.

A lot of the work for the Jobs system has been cribbed from how it's
used in Codehosting, but we've pared it down to make sure that we don't
repeat stuff that's defined elsewhere. A full list of the changes we've
made is below.

We've also added a cronscript to run the CalculateBugHeatJobs.

== List of changes ==

=== cronscripts/calculate-bug-heat.py ===

 - We've added a cronscript to run pending CalculateBugHeatJobs.

=== database/schema/security.cfg ===

 - We've added permissions for the CalculateBugHeatJob runner.

=== lib/canonical/config/schema-lazr.conf ===

 - We've added config options for the CalculateBugHeatJob runner.

=== lib/lp/bugs/configure.zcml ===

 - We've added ZCML configuration for the CalculateBugHeatJobs.

=== lib/lp/bugs/interfaces/bugjob.py ===

 - We've added the following interfaces:
   - IBugJob: A base Job interface for Bugs.
   - ICalculateBugHeatJob: A base interface for bug heat calculation
     job.
   - ICalculateBugHeatJobSource: An interface for acquiring
     ICalculateBugHeatJobs.

=== lib/lp/bugs/model/bugheat.py ===

 - We've added implementation for CalculateBugHeatJob. This uses a
   BugHeatCalculator to calculate the bug heat and then calls
   Bug.setHeat() to set the bug's heat.

=== lib/lp/bugs/model/bugjob.py ===

 - We've added the following classes (cribbed largely from Codehosting):
   - BugJob: Provides a base class for all bug jobs.
   - BugJobDerived: Provides an intermediate class for deriving from
     BugJob, so that a lot of the job-generic work can be done here
     rather than others having to reproduce more boilerplate.

=== lib/lp/bugs/scripts/bugheat.py ===

 - We've added BugHeatCalculator to the __all__ for this module in order
   to fix some lint.

=== lib/lp/bugs/tests/test_bugheat.py ===

 - We've added unit tests for the CalculateBugHeatJob class.

=== lib/lp/bugs/tests/test_bugjob.py ===

 - We've added unit tests for the generic BugJob classes.

=== lib/lp/services/job/interfaces/job.py ===

 - We've added IJobSource to the __all__ for this module in order to
   fix some lint.

Revision history for this message
Paul Hummer (rockstar) wrote :
Download full text (4.5 KiB)

Hi Graham-

  This branch looks good. I have a few comments.

=== added file 'cronscripts/calculate-bug-heat.py'
--- cronscripts/calculate-bug-heat.py 1970-01-01 00:00:00 +0000
+++ cronscripts/calculate-bug-heat.py 2010-01-21 18:28:18 +0000
@@ -0,0 +1,33 @@
+#!/usr/bin/python2.5
+#
+# Copyright 2009 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+# pylint: disable-msg=W0403
+

It's 2010 now. :)

=== added file 'lib/lp/bugs/model/bugjob.py'
--- lib/lp/bugs/model/bugjob.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/model/bugjob.py 2010-01-21 18:28:18 +0000
@@ -0,0 +1,158 @@
+# Copyright 2009 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Job classes related to BugJobs are in here."""
+
+__metaclass__ = type
+__all__ = [
+ 'BugJob',
+ ]
+
+import simplejson
+
+from sqlobject import SQLObjectNotFound
+from storm.base import Storm
+from storm.expr import And
+from storm.locals import Int, Reference, Unicode
+
+from zope.component import getUtility
+from zope.interface import implements
+from zope.security.proxy import removeSecurityProxy
+
+from canonical.database.enumcol import EnumCol
+from canonical.launchpad.webapp.interfaces import (
+ DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE, MASTER_FLAVOR)
+
+from lazr.delegates import delegates
+
+from lp.bugs.interfaces.bugjob import BugJobType, IBugJob
+from lp.bugs.model.bug import Bug
+from lp.services.job.model.job import Job
+from lp.services.job.runner import BaseRunnableJob
+
+
+class BugJob(Storm):
+ """Base class for jobs related to Bugs."""
+
+ implements(IBugJob)
+
+ __storm_table__ = 'BugJob'
+
+ id = Int(primary=True)
+
+ job_id = Int(name='job')
+ job = Reference(job_id, Job.id)
+
+ bug_id = Int(name='bug')
+ bug = Reference(bug_id, Bug.id)
+
+ job_type = EnumCol(enum=BugJobType, notNull=True)
+
+ _json_data = Unicode('json_data')
+
+ @property
+ def metadata(self):
+ return simplejson.loads(self._json_data)
+
+ def __init__(self, bug, job_type, metadata):
+ """Constructor.
+
+ :param bug: The proposal this job relates to.
+ :param job_type: The BugJobType of this job.
+ :param metadata: The type-specific variables, as a JSON-compatible
+ dict.
+ """
+ Storm.__init__(self)
+ json_data = simplejson.dumps(metadata)
+ self.job = Job()
+ self.bug = bug
+ self.job_type = job_type
+ # XXX AaronBentley 2009-01-29 bug=322819: This should be a bytestring,
+ # but the DB representation is unicode.
+ self._json_data = json_data.decode('utf-8')
+
+ @classmethod
+ def get(cls, key):
+ """Return the instance of this class whose key is supplied."""
+ store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
+ instance = store.get(cls, key)
+ if instance is None:
+ raise SQLObjectNotFound(
+ 'No occurrence of %s has key %s' % (cls.__name__, key))
+ return instance
+
+
+class BugJobDerived(BaseRunnableJob):
+ """Inte...

Read more...

review: Needs Information (code)
Revision history for this message
Graham Binns (gmb) wrote :
Download full text (5.5 KiB)

On Thu, Jan 21, 2010 at 07:17:01PM -0000, Paul Hummer wrote:
> Review: Needs Information code
> Hi Graham-
>
> This branch looks good. I have a few comments.
>
> === added file 'cronscripts/calculate-bug-heat.py'
> --- cronscripts/calculate-bug-heat.py 1970-01-01 00:00:00 +0000
> +++ cronscripts/calculate-bug-heat.py 2010-01-21 18:28:18 +0000
> @@ -0,0 +1,33 @@
> +#!/usr/bin/python2.5
> +#
> +# Copyright 2009 Canonical Ltd. This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +# pylint: disable-msg=W0403
> +
>
> It's 2010 now. :)
>

Erk. Fixed.

> === added file 'lib/lp/bugs/model/bugjob.py'
> --- lib/lp/bugs/model/bugjob.py 1970-01-01 00:00:00 +0000
> +++ lib/lp/bugs/model/bugjob.py 2010-01-21 18:28:18 +0000
> @@ -0,0 +1,158 @@
> +# Copyright 2009 Canonical Ltd. This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +"""Job classes related to BugJobs are in here."""
> +
> +__metaclass__ = type
> +__all__ = [
> + 'BugJob',
> + ]
> +
> +import simplejson
> +
> +from sqlobject import SQLObjectNotFound
> +from storm.base import Storm
> +from storm.expr import And
> +from storm.locals import Int, Reference, Unicode
> +
> +from zope.component import getUtility
> +from zope.interface import implements
> +from zope.security.proxy import removeSecurityProxy
> +
> +from canonical.database.enumcol import EnumCol
> +from canonical.launchpad.webapp.interfaces import (
> + DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE, MASTER_FLAVOR)
> +
> +from lazr.delegates import delegates
> +
> +from lp.bugs.interfaces.bugjob import BugJobType, IBugJob
> +from lp.bugs.model.bug import Bug
> +from lp.services.job.model.job import Job
> +from lp.services.job.runner import BaseRunnableJob
> +
> +
> +class BugJob(Storm):
> + """Base class for jobs related to Bugs."""
> +
> + implements(IBugJob)
> +
> + __storm_table__ = 'BugJob'
> +
> + id = Int(primary=True)
> +
> + job_id = Int(name='job')
> + job = Reference(job_id, Job.id)
> +
> + bug_id = Int(name='bug')
> + bug = Reference(bug_id, Bug.id)
> +
> + job_type = EnumCol(enum=BugJobType, notNull=True)
> +
> + _json_data = Unicode('json_data')
> +
> + @property
> + def metadata(self):
> + return simplejson.loads(self._json_data)
> +
> + def __init__(self, bug, job_type, metadata):
> + """Constructor.
> +
> + :param bug: The proposal this job relates to.
> + :param job_type: The BugJobType of this job.
> + :param metadata: The type-specific variables, as a JSON-compatible
> + dict.
> + """
> + Storm.__init__(self)
> + json_data = simplejson.dumps(metadata)
> + self.job = Job()
> + self.bug = bug
> + self.job_type = job_type
> + # XXX AaronBentley 2009-01-29 bug=322819: This should be a bytestring,
> + # but the DB representation is unicode.
> + self._json_data = json_data.decode('utf-8')
> +
> + @classmethod
> + def get(cls, key):
> + """Return the instance of this class whose key is supplied."""
> + store = getUtility(ISt...

Read more...

Revision history for this message
Paul Hummer (rockstar) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'cronscripts/calculate-bug-heat.py'
--- cronscripts/calculate-bug-heat.py 1970-01-01 00:00:00 +0000
+++ cronscripts/calculate-bug-heat.py 2010-01-21 20:48:17 +0000
@@ -0,0 +1,33 @@
1#!/usr/bin/python2.5
2#
3# Copyright 2010 Canonical Ltd. This software is licensed under the
4# GNU Affero General Public License version 3 (see the file LICENSE).
5
6# pylint: disable-msg=W0403
7
8"""Calculate bug heat."""
9
10__metaclass__ = type
11
12import _pythonpath
13
14from canonical.launchpad.webapp import errorlog
15
16from lp.services.job.runner import JobCronScript
17from lp.bugs.interfaces.bugjob import ICalculateBugHeatJobSource
18
19
20class RunCalculateBugHeat(JobCronScript):
21 """Run BranchScanJob jobs."""
22
23 config_name = 'calculate_bug_heat'
24 source_interface = ICalculateBugHeatJobSource
25
26 def main(self):
27 errorlog.globalErrorUtility.configure(self.config_name)
28 return super(RunCalculateBugHeat, self).main()
29
30
31if __name__ == '__main__':
32 script = RunCalculateBugHeat()
33 script.lock_and_run()
034
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2010-01-21 00:44:55 +0000
+++ database/schema/security.cfg 2010-01-21 20:48:17 +0000
@@ -144,6 +144,7 @@
144public.bugattachment = SELECT, INSERT, UPDATE, DELETE144public.bugattachment = SELECT, INSERT, UPDATE, DELETE
145public.bugbranch = SELECT, INSERT, UPDATE, DELETE145public.bugbranch = SELECT, INSERT, UPDATE, DELETE
146public.bugcve = SELECT, INSERT, DELETE146public.bugcve = SELECT, INSERT, DELETE
147public.bugjob = SELECT, INSERT, UPDATE, DELETE
147public.bugnomination = SELECT, UPDATE148public.bugnomination = SELECT, UPDATE
148public.bugnotification = SELECT, INSERT, UPDATE, DELETE149public.bugnotification = SELECT, INSERT, UPDATE, DELETE
149public.bugnotificationattachment = SELECT, INSERT150public.bugnotificationattachment = SELECT, INSERT
@@ -1867,3 +1868,10 @@
1867[modified-branches]1868[modified-branches]
1868type=user1869type=user
1869public.branch = SELECT1870public.branch = SELECT
1871
1872[calculate-bug-heat]
1873type=user
1874groups=script,read
1875public.bug = SELECT, UPDATE
1876public.job = SELECT, UPDATE, DELETE
1877public.bugjob = SELECT, DELETE
18701878
=== modified file 'lib/canonical/config/schema-lazr.conf'
--- lib/canonical/config/schema-lazr.conf 2010-01-21 11:07:52 +0000
+++ lib/canonical/config/schema-lazr.conf 2010-01-21 20:48:17 +0000
@@ -29,6 +29,15 @@
29base_url: http://ftpmaster.internal/29base_url: http://ftpmaster.internal/
3030
3131
32[calculate_bug_heat]
33# The database user which will be used by this process.
34# datatype: string
35dbuser: calculate-bug-heat
36oops_prefix: none
37error_dir: none
38copy_to_zlog: false
39
40
32[branchscanner]41[branchscanner]
33# The database user which will be used by this process.42# The database user which will be used by this process.
34# datatype: string43# datatype: string
3544
=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml 2010-01-11 12:00:48 +0000
+++ lib/lp/bugs/configure.zcml 2010-01-21 20:48:17 +0000
@@ -895,4 +895,15 @@
895 for="lp.bugs.interfaces.bugtarget.IBugTarget"895 for="lp.bugs.interfaces.bugtarget.IBugTarget"
896 factory="lp.bugs.browser.bugtarget.BugsVHostBreadcrumb"896 factory="lp.bugs.browser.bugtarget.BugsVHostBreadcrumb"
897 permission="zope.Public"/>897 permission="zope.Public"/>
898
899 <!-- CalculateBugHeatJobs -->
900 <class class="lp.bugs.model.bugheat.CalculateBugHeatJob">
901 <allow interface="lp.bugs.interfaces.bugjob.ICalculateBugHeatJob"/>
902 </class>
903 <securedutility
904 component="lp.bugs.model.bugheat.CalculateBugHeatJob"
905 provides="lp.bugs.interfaces.bugjob.ICalculateBugHeatJobSource">
906 <allow
907 interface="lp.bugs.interfaces.bugjob.ICalculateBugHeatJobSource"/>
908 </securedutility>
898</configure>909</configure>
899910
=== added file 'lib/lp/bugs/interfaces/bugjob.py'
--- lib/lp/bugs/interfaces/bugjob.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/interfaces/bugjob.py 2010-01-21 20:48:17 +0000
@@ -0,0 +1,66 @@
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"""Interfaces for using the Jobs system for Bugs."""
5
6__metaclass__ = type
7__all__ = [
8 'BugJobType',
9 'IBugJob',
10 'ICalculateBugHeatJob',
11 'ICalculateBugHeatJobSource',
12 ]
13
14from zope.interface import Attribute, Interface
15from zope.schema import Int, Object
16
17from canonical.launchpad import _
18
19from lazr.enum import DBEnumeratedType, DBItem
20from lp.bugs.interfaces.bug import IBug
21from lp.services.job.interfaces.job import IJob, IJobSource, IRunnableJob
22
23
24class BugJobType(DBEnumeratedType):
25 """Values that IBugJob.job_type can take."""
26
27 UPDATE_HEAT = DBItem(0, """
28 Update the heat for a bug.
29
30 This job recalculates the heat for a Bug.
31 """)
32
33
34class IBugJob(Interface):
35 """A Job related to a Bug."""
36
37 id = Int(
38 title=_('DB ID'), required=True, readonly=True,
39 description=_("The tracking number for this job."))
40
41 bug = Object(
42 title=_('The Bug this job is about'),
43 schema=IBug, required=True)
44
45 job = Object(title=_('The common Job attributes'), schema=IJob,
46 required=True)
47
48 metadata = Attribute('A dict of data about the job.')
49
50 def destroySelf():
51 """Destroy this object."""
52
53
54class IBugJobSource(IJobSource):
55 """An interface for acquiring IBugJobs."""
56
57 def create(bug):
58 """Create a new IBugJob for a bug."""
59
60
61class ICalculateBugHeatJob(IRunnableJob):
62 """A Job to calculate bug heat."""
63
64
65class ICalculateBugHeatJobSource(IBugJobSource):
66 """Interface for acquiring CalculateBugHeatJobs."""
067
=== added file 'lib/lp/bugs/model/bugheat.py'
--- lib/lp/bugs/model/bugheat.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/model/bugheat.py 2010-01-21 20:48:17 +0000
@@ -0,0 +1,54 @@
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"""Job classes related to BugJobs are in here."""
5
6__metaclass__ = type
7__all__ = [
8 'CalculateBugHeatJob',
9 ]
10
11from zope.component import getUtility
12from zope.interface import classProvides, implements
13
14from canonical.launchpad.webapp.interfaces import (
15 DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE)
16
17from lp.bugs.interfaces.bugjob import (
18 BugJobType, ICalculateBugHeatJob, ICalculateBugHeatJobSource)
19from lp.bugs.model.bugjob import BugJob, BugJobDerived
20from lp.bugs.scripts.bugheat import BugHeatCalculator
21from lp.services.job.model.job import Job
22
23
24class CalculateBugHeatJob(BugJobDerived):
25 """A Job to calculate bug heat."""
26 implements(ICalculateBugHeatJob)
27
28 class_job_type = BugJobType.UPDATE_HEAT
29 classProvides(ICalculateBugHeatJobSource)
30
31 def run(self):
32 """See `IRunnableJob`."""
33 calculator = BugHeatCalculator(self.bug)
34 calculated_heat = calculator.getBugHeat()
35 self.bug.setHeat(calculated_heat)
36
37 @classmethod
38 def create(cls, bug):
39 """See `ICalculateBugHeatJobSource`."""
40 # If there's already a job for the bug, don't create a new one.
41 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
42 job_for_bug = store.find(
43 BugJob,
44 BugJob.bug == bug,
45 BugJob.job_type == cls.class_job_type,
46 BugJob.job == Job.id,
47 Job.id.is_in(Job.ready_jobs)
48 ).any()
49
50 if job_for_bug is not None:
51 return cls(job_for_bug)
52 else:
53 return super(CalculateBugHeatJob, cls).create(bug)
54
055
=== added file 'lib/lp/bugs/model/bugjob.py'
--- lib/lp/bugs/model/bugjob.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/model/bugjob.py 2010-01-21 20:48:17 +0000
@@ -0,0 +1,148 @@
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"""Job classes related to BugJobs are in here."""
5
6__metaclass__ = type
7__all__ = [
8 'BugJob',
9 'BugJobDerived',
10 ]
11
12import simplejson
13
14from sqlobject import SQLObjectNotFound
15from storm.base import Storm
16from storm.expr import And
17from storm.locals import Int, Reference, Unicode
18
19from zope.component import getUtility
20from zope.interface import classProvides, implements
21from zope.security.proxy import removeSecurityProxy
22
23from canonical.database.enumcol import EnumCol
24from canonical.launchpad.webapp.interfaces import (
25 DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE, MASTER_FLAVOR)
26
27from lazr.delegates import delegates
28
29from lp.bugs.interfaces.bugjob import BugJobType, IBugJob, IBugJobSource
30from lp.bugs.model.bug import Bug
31from lp.services.job.model.job import Job
32from lp.services.job.runner import BaseRunnableJob
33
34
35class BugJob(Storm):
36 """Base class for jobs related to Bugs."""
37
38 implements(IBugJob)
39
40 __storm_table__ = 'BugJob'
41
42 id = Int(primary=True)
43
44 job_id = Int(name='job')
45 job = Reference(job_id, Job.id)
46
47 bug_id = Int(name='bug')
48 bug = Reference(bug_id, Bug.id)
49
50 job_type = EnumCol(enum=BugJobType, notNull=True)
51
52 _json_data = Unicode('json_data')
53
54 @property
55 def metadata(self):
56 return simplejson.loads(self._json_data)
57
58 def __init__(self, bug, job_type, metadata):
59 """Constructor.
60
61 :param bug: The proposal this job relates to.
62 :param job_type: The BugJobType of this job.
63 :param metadata: The type-specific variables, as a JSON-compatible
64 dict.
65 """
66 Storm.__init__(self)
67 json_data = simplejson.dumps(metadata)
68 self.job = Job()
69 self.bug = bug
70 self.job_type = job_type
71 # XXX AaronBentley 2009-01-29 bug=322819: This should be a bytestring,
72 # but the DB representation is unicode.
73 self._json_data = json_data.decode('utf-8')
74
75 @classmethod
76 def get(cls, key):
77 """Return the instance of this class whose key is supplied."""
78 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
79 instance = store.get(cls, key)
80 if instance is None:
81 raise SQLObjectNotFound(
82 'No occurrence of %s has key %s' % (cls.__name__, key))
83 return instance
84
85
86class BugJobDerived(BaseRunnableJob):
87 """Intermediate class for deriving from BugJob."""
88 delegates(IBugJob)
89 classProvides(IBugJobSource)
90
91 def __init__(self, job):
92 self.context = job
93
94 # We need to define __eq__ and __ne__ here to prevent the security
95 # proxy from mucking up our comparisons in tests and elsewhere.
96
97 def __eq__(self, job):
98 return (
99 self.__class__ is removeSecurityProxy(job.__class__)
100 and self.job == job.job)
101
102 def __ne__(self, job):
103 return not (self == job)
104
105 @classmethod
106 def create(cls, bug):
107 """See `IBugJob`."""
108 # If there's already a job for the bug, don't create a new one.
109 job = BugJob(bug, cls.class_job_type, {})
110 return cls(job)
111
112 @classmethod
113 def get(cls, job_id):
114 """Get a job by id.
115
116 :return: the BugJob with the specified id, as the current
117 BugJobDerived subclass.
118 :raises: SQLObjectNotFound if there is no job with the specified id,
119 or its job_type does not match the desired subclass.
120 """
121 job = BugJob.get(job_id)
122 if job.job_type != cls.class_job_type:
123 raise SQLObjectNotFound(
124 'No object found with id %d and type %s' % (job_id,
125 cls.class_job_type.title))
126 return cls(job)
127
128 @classmethod
129 def iterReady(cls):
130 """Iterate through all ready BugJobs."""
131 store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
132 jobs = store.find(
133 BugJob,
134 And(BugJob.job_type == cls.class_job_type,
135 BugJob.job == Job.id,
136 Job.id.is_in(Job.ready_jobs),
137 BugJob.bug == Bug.id))
138 return (cls(job) for job in jobs)
139
140 def getOopsVars(self):
141 """See `IRunnableJob`."""
142 vars = BaseRunnableJob.getOopsVars(self)
143 vars.extend([
144 ('bug_id', self.context.bug.id),
145 ('bug_job_id', self.context.id),
146 ('bug_job_type', self.context.job_type.title),
147 ])
148 return vars
0149
=== modified file 'lib/lp/bugs/scripts/bugheat.py'
--- lib/lp/bugs/scripts/bugheat.py 2010-01-12 16:41:23 +0000
+++ lib/lp/bugs/scripts/bugheat.py 2010-01-21 20:48:17 +0000
@@ -4,7 +4,9 @@
4"""The innards of the Bug Heat cronscript."""4"""The innards of the Bug Heat cronscript."""
55
6__metaclass__ = type6__metaclass__ = type
7__all__ = []7__all__ = [
8 'BugHeatCalculator',
9 ]
810
911
10from zope.component import getUtility12from zope.component import getUtility
1113
=== added file 'lib/lp/bugs/tests/test_bugheat.py'
--- lib/lp/bugs/tests/test_bugheat.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/tests/test_bugheat.py 2010-01-21 20:48:17 +0000
@@ -0,0 +1,95 @@
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"""Tests for BugJobs."""
5
6__metaclass__ = type
7
8import transaction
9import unittest
10
11from zope.component import getUtility
12
13from canonical.launchpad.scripts.tests import run_script
14from canonical.testing import LaunchpadZopelessLayer
15
16from lp.bugs.interfaces.bugjob import ICalculateBugHeatJobSource
17from lp.bugs.model.bugheat import CalculateBugHeatJob
18from lp.bugs.scripts.bugheat import BugHeatCalculator
19from lp.testing import TestCaseWithFactory
20
21
22class CalculateBugHeatJobTestCase(TestCaseWithFactory):
23 """Test case for CalculateBugHeatJob."""
24
25 layer = LaunchpadZopelessLayer
26
27 def setUp(self):
28 super(CalculateBugHeatJobTestCase, self).setUp()
29 self.bug = self.factory.makeBug()
30
31 def test_run(self):
32 # CalculateBugHeatJob.run() sets calculates and sets the heat
33 # for a bug.
34 job = CalculateBugHeatJob.create(self.bug)
35 bug_heat_calculator = BugHeatCalculator(self.bug)
36
37 job.run()
38 self.assertEqual(
39 bug_heat_calculator.getBugHeat(), self.bug.heat)
40
41 def test_utility(self):
42 # CalculateBugHeatJobSource is a utility for acquiring
43 # CalculateBugHeatJobs.
44 utility = getUtility(ICalculateBugHeatJobSource)
45 self.assertTrue(
46 ICalculateBugHeatJobSource.providedBy(utility))
47
48 def test_create_only_creates_one(self):
49 # If there's already a CalculateBugHeatJob for a bug,
50 # CalculateBugHeatJob.create() won't create a new one.
51 job = CalculateBugHeatJob.create(self.bug)
52
53 # There will now be one job in the queue.
54 self.assertEqual(
55 1, len(list(CalculateBugHeatJob.iterReady())))
56
57 new_job = CalculateBugHeatJob.create(self.bug)
58
59 # The two jobs will in fact be the same job.
60 self.assertEqual(job, new_job)
61
62 # And the queue will still have a length of 1.
63 self.assertEqual(
64 1, len(list((CalculateBugHeatJob.iterReady()))))
65
66 def test_cronscript_succeeds(self):
67 # The calculate-bug-heat cronscript will run all pending
68 # CalculateBugHeatJobs.
69 CalculateBugHeatJob.create(self.bug)
70 transaction.commit()
71
72 retcode, stdout, stderr = run_script(
73 'cronscripts/calculate-bug-heat.py', [],
74 expect_returncode=0)
75 self.assertEqual('', stdout)
76 self.assertIn(
77 'INFO Ran 1 ICalculateBugHeatJobSource jobs.\n', stderr)
78
79 def test_getOopsVars(self):
80 # BugJobDerived.getOopsVars() returns the variables to be used
81 # when logging an OOPS for a bug job. We test this using
82 # CalculateBugHeatJob because BugJobDerived doesn't let us
83 # create() jobs.
84 job = CalculateBugHeatJob.create(self.bug)
85 vars = job.getOopsVars()
86
87 # The Bug ID, BugJob ID and BugJob type will be returned by
88 # getOopsVars().
89 self.assertIn(('bug_id', self.bug.id), vars)
90 self.assertIn(('bug_job_id', job.context.id), vars)
91 self.assertIn(('bug_job_type', job.context.job_type.title), vars)
92
93
94def test_suite():
95 return unittest.TestLoader().loadTestsFromName(__name__)
096
=== added file 'lib/lp/bugs/tests/test_bugjob.py'
--- lib/lp/bugs/tests/test_bugjob.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/tests/test_bugjob.py 2010-01-21 20:48:17 +0000
@@ -0,0 +1,55 @@
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"""Tests for BugJobs."""
5
6__metaclass__ = type
7
8import unittest
9
10from canonical.testing import LaunchpadZopelessLayer
11
12from lp.bugs.interfaces.bugjob import BugJobType
13from lp.bugs.model.bugjob import BugJob, BugJobDerived
14from lp.testing import TestCaseWithFactory
15
16
17class BugJobTestCase(TestCaseWithFactory):
18 """Test case for basic BugJob gubbins."""
19
20 layer = LaunchpadZopelessLayer
21
22 def test_instantiate(self):
23 # BugJob.__init__() instantiates a BugJob instance.
24 bug = self.factory.makeBug()
25
26 metadata = ('some', 'arbitrary', 'metadata')
27 bug_job = BugJob(
28 bug, BugJobType.UPDATE_HEAT, metadata)
29
30 self.assertEqual(bug, bug_job.bug)
31 self.assertEqual(BugJobType.UPDATE_HEAT, bug_job.job_type)
32
33 # When we actually access the BugJob's metadata it gets
34 # unserialized from JSON, so the representation returned by
35 # bug_heat.metadata will be different from what we originally
36 # passed in.
37 metadata_expected = [u'some', u'arbitrary', u'metadata']
38 self.assertEqual(metadata_expected, bug_job.metadata)
39
40
41class BugJobDerivedTestCase(TestCaseWithFactory):
42 """Test case for the BugJobDerived class."""
43
44 layer = LaunchpadZopelessLayer
45
46 def test_create_explodes(self):
47 # BugJobDerived.create() will blow up because it needs to be
48 # subclassed to work properly.
49 bug = self.factory.makeBug()
50 self.assertRaises(
51 AttributeError, BugJobDerived.create, bug)
52
53
54def test_suite():
55 return unittest.TestLoader().loadTestsFromName(__name__)
056
=== modified file 'lib/lp/services/job/interfaces/job.py'
--- lib/lp/services/job/interfaces/job.py 2010-01-14 23:33:30 +0000
+++ lib/lp/services/job/interfaces/job.py 2010-01-21 20:48:17 +0000
@@ -9,6 +9,7 @@
99
10__all__ = [10__all__ = [
11 'IJob',11 'IJob',
12 'IJobSource',
12 'IRunnableJob',13 'IRunnableJob',
13 'JobStatus',14 'JobStatus',
14 'LeaseHeld',15 'LeaseHeld',

Subscribers

People subscribed via source and target branches

to status/vote changes: