PageRenderTime 42ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/master/buildbot/test/unit/test_schedulers_triggerable.py

https://gitlab.com/murder187ss/buildbot
Python | 333 lines | 239 code | 53 blank | 41 comment | 11 complexity | 537a4c7e3200e5e8af2e1f102c81ffa4 MD5 | raw file
  1. # This file is part of Buildbot. Buildbot is free software: you can
  2. # redistribute it and/or modify it under the terms of the GNU General Public
  3. # License as published by the Free Software Foundation, version 2.
  4. #
  5. # This program is distributed in the hope that it will be useful, but WITHOUT
  6. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  7. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  8. # details.
  9. #
  10. # You should have received a copy of the GNU General Public License along with
  11. # this program; if not, write to the Free Software Foundation, Inc., 51
  12. # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  13. #
  14. # Copyright Buildbot Team Members
  15. from future.utils import itervalues
  16. import mock
  17. from twisted.internet import defer
  18. from twisted.internet import task
  19. from twisted.python import log
  20. from twisted.trial import unittest
  21. from buildbot.process import properties
  22. from buildbot.schedulers import triggerable
  23. from buildbot.test.fake import fakedb
  24. from buildbot.test.util import interfaces
  25. from buildbot.test.util import scheduler
  26. from buildbot.test.util.decorators import flaky
  27. class TriggerableInterfaceTest(unittest.TestCase, interfaces.InterfaceTests):
  28. def test_interface(self):
  29. self.assertInterfacesImplemented(triggerable.Triggerable)
  30. class Triggerable(scheduler.SchedulerMixin, unittest.TestCase):
  31. OBJECTID = 33
  32. def setUp(self):
  33. # Necessary to get an assertable submitted_at time.
  34. self.now = 946684799
  35. self.clock = task.Clock()
  36. self.clock.advance(self.now)
  37. self.clock_patch = mock.patch('buildbot.test.fake.fakedb.reactor.seconds', self.clock.seconds)
  38. self.clock_patch.start()
  39. self.setUpScheduler()
  40. self.subscription = None
  41. def tearDown(self):
  42. self.tearDownScheduler()
  43. self.clock_patch.stop()
  44. def makeScheduler(self, overrideBuildsetMethods=False, **kwargs):
  45. self.master.db.insertTestData([fakedb.Builder(id=77, name='b')])
  46. sched = self.attachScheduler(
  47. triggerable.Triggerable(name='n', builderNames=['b'], **kwargs),
  48. self.OBJECTID, overrideBuildsetMethods=overrideBuildsetMethods)
  49. sched._updateWaiters._reactor = self.clock
  50. return sched
  51. @defer.inlineCallbacks
  52. def assertTriggeredBuildset(self, idsDeferred, waited_for, properties={}, sourcestamps=None):
  53. bsid, brids = yield idsDeferred
  54. properties.update({u'scheduler': ('n', u'Scheduler')})
  55. self.assertEqual(
  56. self.master.db.buildsets.buildsets[bsid]['properties'],
  57. properties,
  58. )
  59. buildset = yield self.master.db.buildsets.getBuildset(bsid)
  60. from datetime import datetime
  61. from buildbot.util import UTC
  62. ssids = buildset.pop('sourcestamps')
  63. self.assertEqual(
  64. buildset,
  65. {
  66. 'bsid': bsid,
  67. 'complete': False,
  68. 'complete_at': None,
  69. 'external_idstring': None,
  70. 'reason': u"The Triggerable scheduler named 'n' triggered this build",
  71. 'results': -1,
  72. 'submitted_at': datetime(1999, 12, 31, 23, 59, 59, tzinfo=UTC),
  73. 'parent_buildid': None,
  74. 'parent_relationship': None,
  75. }
  76. )
  77. actual_sourcestamps = yield defer.gatherResults([
  78. self.master.db.sourcestamps.getSourceStamp(ssid)
  79. for ssid in ssids
  80. ])
  81. self.assertEqual(len(sourcestamps), len(actual_sourcestamps))
  82. for expected_ss, actual_ss in zip(sourcestamps, actual_sourcestamps):
  83. actual_ss = actual_ss.copy()
  84. # We don't care if the actual sourcestamp has *more* attributes than expected.
  85. for key in list(actual_ss.keys()):
  86. if key not in expected_ss:
  87. del actual_ss[key]
  88. self.assertEqual(expected_ss, actual_ss)
  89. for brid in itervalues(brids):
  90. buildrequest = yield self.master.db.buildrequests.getBuildRequest(brid)
  91. self.assertEqual(
  92. buildrequest,
  93. {
  94. 'buildrequestid': brid,
  95. 'buildername': u'b',
  96. 'builderid': 77,
  97. 'buildsetid': bsid,
  98. 'claimed': False,
  99. 'claimed_at': None,
  100. 'complete': False,
  101. 'complete_at': None,
  102. 'claimed_by_masterid': None,
  103. 'priority': 0,
  104. 'results': -1,
  105. 'submitted_at': datetime(1999, 12, 31, 23, 59, 59, tzinfo=UTC),
  106. 'waited_for': waited_for
  107. }
  108. )
  109. def sendCompletionMessage(self, bsid, results=3):
  110. self.master.mq.callConsumer(('buildsets', str(bsid), 'complete'),
  111. dict(
  112. bsid=bsid,
  113. submitted_at=100,
  114. complete=True,
  115. complete_at=200,
  116. external_idstring=None,
  117. reason=u'triggering',
  118. results=results,
  119. sourcestamps=[],
  120. parent_buildid=None,
  121. parent_relationship=None,
  122. ))
  123. # tests
  124. # NOTE: these tests take advantage of the fact that all of the fake
  125. # scheduler operations are synchronous, and thus do not return a Deferred.
  126. # The Deferred from trigger() is completely processed before this test
  127. # method returns.
  128. def test_constructor_no_reason(self):
  129. sched = self.makeScheduler()
  130. self.assertEqual(sched.reason, "The Triggerable scheduler named 'n' triggered this build")
  131. def test_constructor_explicit_reason(self):
  132. sched = self.makeScheduler(reason="Because I said so")
  133. self.assertEqual(sched.reason, "Because I said so")
  134. @flaky(bugNumber=3339)
  135. def test_trigger(self):
  136. sched = self.makeScheduler(codebases={'cb': {'repository': 'r'}})
  137. # no subscription should be in place yet
  138. self.assertEqual(sched.master.mq.qrefs, [])
  139. # trigger the scheduler, exercising properties while we're at it
  140. waited_for = True
  141. set_props = properties.Properties()
  142. set_props.setProperty('pr', 'op', 'test')
  143. ss = {'revision': 'myrev',
  144. 'branch': 'br',
  145. 'project': 'p',
  146. 'repository': 'r',
  147. 'codebase': 'cb'}
  148. idsDeferred, d = sched.trigger(waited_for, sourcestamps=[ss], set_props=set_props)
  149. self.clock.advance(0) # let the debounced function fire
  150. self.assertTriggeredBuildset(
  151. idsDeferred,
  152. waited_for,
  153. properties={u'pr': ('op', u'test')},
  154. sourcestamps=[
  155. dict(branch='br', project='p', repository='r',
  156. codebase='cb', revision='myrev'),
  157. ])
  158. # set up a boolean so that we can know when the deferred fires
  159. self.fired = False
  160. @d.addCallback
  161. def fired(xxx_todo_changeme):
  162. (result, brids) = xxx_todo_changeme
  163. self.assertEqual(result, 3) # from sendCompletionMessage
  164. self.assertEqual(brids, {77: 1000})
  165. self.fired = True
  166. d.addErrback(log.err)
  167. # check that the scheduler has subscribed to buildset changes, but
  168. # not fired yet
  169. self.assertEqual(
  170. [q.filter for q in sched.master.mq.qrefs],
  171. [('buildsets', None, 'complete',)])
  172. self.assertFalse(self.fired)
  173. # pretend a non-matching buildset is complete
  174. self.sendCompletionMessage(27)
  175. # scheduler should not have reacted
  176. self.assertEqual(
  177. [q.filter for q in sched.master.mq.qrefs],
  178. [('buildsets', None, 'complete',)])
  179. self.assertFalse(self.fired)
  180. # pretend the matching buildset is complete
  181. self.sendCompletionMessage(200)
  182. self.clock.advance(0) # let the debounced function fire
  183. # scheduler should have reacted
  184. self.assertEqual(
  185. [q.filter for q in sched.master.mq.qrefs],
  186. [])
  187. self.assertTrue(self.fired)
  188. return d
  189. def test_trigger_overlapping(self):
  190. sched = self.makeScheduler(codebases={'cb': {'repository': 'r'}})
  191. # no subscription should be in place yet
  192. self.assertEqual(sched.master.mq.qrefs, [])
  193. waited_for = False
  194. def makeSS(rev):
  195. return {'revision': rev, 'branch': 'br', 'project': 'p',
  196. 'repository': 'r', 'codebase': 'cb'}
  197. # trigger the scheduler the first time
  198. idsDeferred, d = sched.trigger(waited_for, [makeSS('myrev1')]) # triggers bsid 200
  199. self.assertTriggeredBuildset(
  200. idsDeferred,
  201. waited_for,
  202. sourcestamps=[
  203. dict(branch='br', project='p', repository='r',
  204. codebase='cb', revision='myrev1'),
  205. ])
  206. d.addCallback(lambda res_brids: self.assertEqual(res_brids[0], 11)
  207. and self.assertEqual(res_brids[1], {77: 1000}))
  208. waited_for = True
  209. # and the second time
  210. idsDeferred, d = sched.trigger(waited_for, [makeSS('myrev2')]) # triggers bsid 201
  211. self.clock.advance(0) # let the debounced function fire
  212. self.assertTriggeredBuildset(
  213. idsDeferred,
  214. waited_for,
  215. sourcestamps=[
  216. dict(branch='br', project='p', repository='r',
  217. codebase='cb', revision='myrev2'),
  218. ])
  219. d.addCallback(lambda res_brids1: self.assertEqual(res_brids1[0], 22)
  220. and self.assertEqual(res_brids1[1], {77: 1001}))
  221. # check that the scheduler has subscribed to buildset changes
  222. self.assertEqual(
  223. [q.filter for q in sched.master.mq.qrefs],
  224. [('buildsets', None, 'complete',)])
  225. # let a few buildsets complete
  226. self.sendCompletionMessage(29, results=3)
  227. self.sendCompletionMessage(201, results=22)
  228. self.sendCompletionMessage(9, results=3)
  229. self.sendCompletionMessage(200, results=11)
  230. self.clock.advance(0) # let the debounced function fire
  231. # both should have triggered with appropriate results, and the
  232. # subscription should be cancelled
  233. self.assertEqual(sched.master.mq.qrefs, [])
  234. @defer.inlineCallbacks
  235. def test_trigger_with_sourcestamp(self):
  236. # Test triggering a scheduler with a sourcestamp, and see that
  237. # sourcestamp handed to addBuildsetForSourceStampsWithDefaults.
  238. sched = self.makeScheduler(overrideBuildsetMethods=True)
  239. waited_for = False
  240. ss = {'repository': 'r3', 'codebase': 'cb3', 'revision': 'fixrev3',
  241. 'branch': 'default', 'project': 'p'}
  242. idsDeferred = sched.trigger(waited_for, sourcestamps=[ss])[0]
  243. yield idsDeferred
  244. self.assertEqual(self.addBuildsetCalls, [
  245. ('addBuildsetForSourceStampsWithDefaults', {
  246. 'builderNames': None,
  247. 'properties': {'scheduler': ('n', 'Scheduler')},
  248. 'reason': "The Triggerable scheduler named 'n' triggered "
  249. "this build",
  250. 'sourcestamps': [{
  251. 'branch': 'default',
  252. 'codebase': 'cb3',
  253. 'project': 'p',
  254. 'repository': 'r3',
  255. 'revision': 'fixrev3'},
  256. ],
  257. 'waited_for': False}),
  258. ])
  259. @defer.inlineCallbacks
  260. def test_trigger_without_sourcestamps(self):
  261. # Test triggering *without* sourcestamps, and see that nothing is passed
  262. # to addBuildsetForSourceStampsWithDefaults
  263. waited_for = True
  264. sched = self.makeScheduler(overrideBuildsetMethods=True)
  265. idsDeferred = sched.trigger(waited_for, sourcestamps=[])[0]
  266. yield idsDeferred
  267. self.assertEqual(self.addBuildsetCalls, [
  268. ('addBuildsetForSourceStampsWithDefaults', {
  269. 'builderNames': None,
  270. 'properties': {'scheduler': ('n', 'Scheduler')},
  271. 'reason': "The Triggerable scheduler named 'n' triggered "
  272. "this build",
  273. 'sourcestamps': [],
  274. 'waited_for': True}),
  275. ])
  276. @defer.inlineCallbacks
  277. def test_startService_stopService(self):
  278. sched = self.makeScheduler()
  279. yield sched.startService()
  280. yield sched.stopService()