PageRenderTime 44ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/master/buildbot/test/unit/test_plugins.py

https://gitlab.com/murder187ss/buildbot
Python | 400 lines | 258 code | 82 blank | 60 comment | 23 complexity | 4c3d26675827c693bd566b00899d1b85 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. """
  16. Unit tests for the plugin framework
  17. """
  18. import re
  19. import mock
  20. from twisted.trial import unittest
  21. from zope.interface import implements
  22. import buildbot.plugins.db
  23. from buildbot.errors import PluginDBError
  24. from buildbot.interfaces import IPlugin
  25. from buildbot.test.util.warnings import assertNotProducesWarnings
  26. from buildbot.test.util.warnings import assertProducesWarning
  27. from buildbot.worker_transition import DeprecatedWorkerAPIWarning
  28. from buildbot.worker_transition import DeprecatedWorkerNameWarning
  29. # buildbot.plugins.db needs to be imported for patching, however just 'db' is
  30. # much shorter for using in tests
  31. db = buildbot.plugins.db
  32. class FakeEntry(object):
  33. """
  34. An entry suitable for unit tests
  35. """
  36. def __init__(self, name, project_name, version, fail_require, value):
  37. self._name = name
  38. self._dist = mock.Mock(spec_set=['project_name', 'version'])
  39. self._dist.project_name = project_name
  40. self._dist.version = version
  41. self._fail_require = fail_require
  42. self._value = value
  43. @property
  44. def name(self):
  45. "entry name"
  46. return self._name
  47. @property
  48. def dist(self):
  49. "dist thingie"
  50. return self._dist
  51. def require(self):
  52. """
  53. handle external dependencies
  54. """
  55. if self._fail_require:
  56. raise RuntimeError('Fail require as requested')
  57. def load(self):
  58. """
  59. handle loading
  60. """
  61. return self._value
  62. class ITestInterface(IPlugin):
  63. """
  64. test interface
  65. """
  66. def hello(name):
  67. "Greets by :param:`name`"
  68. class ClassWithInterface(object):
  69. """
  70. a class to implement a simple interface
  71. """
  72. implements(ITestInterface)
  73. def __init__(self, name=None):
  74. self._name = name
  75. def hello(self, name=None):
  76. 'implement the required method'
  77. return name or self._name
  78. class ClassWithNoInterface(object):
  79. """
  80. just a class
  81. """
  82. # NOTE: buildbot.plugins.db prepends the group with common namespace --
  83. # 'buildbot.'
  84. _FAKE_ENTRIES = {
  85. 'buildbot.interface': [
  86. FakeEntry('good', 'non-existant', 'irrelevant', False,
  87. ClassWithInterface),
  88. FakeEntry('deep.path', 'non-existant', 'irrelevant', False,
  89. ClassWithInterface)
  90. ],
  91. 'buildbot.interface_failed': [
  92. FakeEntry('good', 'non-existant', 'irrelevant', True,
  93. ClassWithInterface)
  94. ],
  95. 'buildbot.no_interface': [
  96. FakeEntry('good', 'non-existant', 'irrelevant', False,
  97. ClassWithNoInterface)
  98. ],
  99. 'buildbot.no_interface_again': [
  100. FakeEntry('good', 'non-existant', 'irrelevant', False,
  101. ClassWithNoInterface)
  102. ],
  103. 'buildbot.no_interface_failed': [
  104. FakeEntry('good', 'non-existant', 'irrelevant', True,
  105. ClassWithNoInterface)
  106. ],
  107. 'buildbot.duplicates': [
  108. FakeEntry('good', 'non-existant', 'first', False,
  109. ClassWithNoInterface),
  110. FakeEntry('good', 'non-existant', 'second', False,
  111. ClassWithNoInterface)
  112. ]
  113. }
  114. def provide_fake_entries(group):
  115. """
  116. give a set of fake entries for known groups
  117. """
  118. return _FAKE_ENTRIES.get(group, [])
  119. @mock.patch('buildbot.plugins.db.iter_entry_points', provide_fake_entries)
  120. class TestBuildbotPlugins(unittest.TestCase):
  121. def setUp(self):
  122. buildbot.plugins.db._DB = buildbot.plugins.db._PluginDB()
  123. def test_check_group_registration(self):
  124. with mock.patch.object(buildbot.plugins.db, '_DB', db._PluginDB()):
  125. # The groups will be prepended with namespace, so info() will
  126. # return a dictionary with right keys, but no data
  127. groups = set(_FAKE_ENTRIES.keys())
  128. for group in groups:
  129. db.get_plugins(group)
  130. registered = set(db.info().keys())
  131. self.assertEqual(registered, groups)
  132. self.assertEqual(registered, set(db.namespaces()))
  133. def test_interface_provided_simple(self):
  134. # Basic check before the actual test
  135. self.assertTrue(ITestInterface.implementedBy(ClassWithInterface))
  136. plugins = db.get_plugins('interface', interface=ITestInterface)
  137. self.assertTrue('good' in plugins.names)
  138. result_get = plugins.get('good')
  139. result_getattr = plugins.good
  140. self.assertFalse(result_get is None)
  141. self.assertTrue(result_get is result_getattr)
  142. # Make sure we actually got our class
  143. greeter = result_get('yes')
  144. self.assertEqual('yes', greeter.hello())
  145. self.assertEqual('no', greeter.hello('no'))
  146. def test_missing_plugin(self):
  147. plugins = db.get_plugins('interface', interface=ITestInterface)
  148. self.assertRaises(AttributeError, getattr, plugins, 'bad')
  149. self.assertRaises(PluginDBError, plugins.get, 'bad')
  150. self.assertRaises(PluginDBError, plugins.get, 'good.extra')
  151. def test_interface_provided_deep(self):
  152. # Basic check before the actual test
  153. self.assertTrue(ITestInterface.implementedBy(ClassWithInterface))
  154. plugins = db.get_plugins('interface', interface=ITestInterface)
  155. self.assertTrue('deep.path' in plugins.names)
  156. self.assertTrue('deep.path' in plugins)
  157. self.assertFalse('even.deeper.path' in plugins)
  158. result_get = plugins.get('deep.path')
  159. result_getattr = plugins.deep.path
  160. self.assertFalse(result_get is None)
  161. self.assertTrue(result_get is result_getattr)
  162. # Make sure we actually got our class
  163. greeter = result_get('yes')
  164. self.assertEqual('yes', greeter.hello())
  165. self.assertEqual('no', greeter.hello('no'))
  166. def test_interface_provided_deps_failed(self):
  167. plugins = db.get_plugins('interface_failed', interface=ITestInterface,
  168. check_extras=True)
  169. self.assertRaises(PluginDBError, plugins.get, 'good')
  170. def test_required_interface_not_provided(self):
  171. plugins = db.get_plugins('no_interface_again',
  172. interface=ITestInterface)
  173. self.assertTrue(plugins._interface is ITestInterface)
  174. self.assertRaises(PluginDBError, plugins.get, 'good')
  175. def test_no_interface_provided(self):
  176. plugins = db.get_plugins('no_interface')
  177. self.assertFalse(plugins.get('good') is None)
  178. def test_no_interface_provided_deps_failed(self):
  179. plugins = db.get_plugins('no_interface_failed', check_extras=True)
  180. self.assertRaises(PluginDBError, plugins.get, 'good')
  181. def test_failure_on_dups(self):
  182. self.assertRaises(PluginDBError, db.get_plugins, 'duplicates',
  183. load_now=True)
  184. def test_get_info_on_a_known_plugin(self):
  185. plugins = db.get_plugins('interface')
  186. self.assertEqual(('non-existant', 'irrelevant'), plugins.info('good'))
  187. def test_failure_on_unknown_plugin_info(self):
  188. plugins = db.get_plugins('interface')
  189. self.assertRaises(PluginDBError, plugins.info, 'bad')
  190. def test_failure_on_unknown_plugin_get(self):
  191. plugins = db.get_plugins('interface')
  192. self.assertRaises(PluginDBError, plugins.get, 'bad')
  193. class SimpleFakeEntry(FakeEntry):
  194. def __init__(self, name, value):
  195. FakeEntry.__init__(self, name, 'non-existant', 'irrelevant', False,
  196. value)
  197. _WORKER_FAKE_ENTRIES = {
  198. 'buildbot.worker': [
  199. SimpleFakeEntry('Worker', ClassWithInterface),
  200. SimpleFakeEntry('EC2LatentWorker', ClassWithInterface),
  201. SimpleFakeEntry('LibVirtWorker', ClassWithInterface),
  202. SimpleFakeEntry('OpenStackLatentWorker', ClassWithInterface),
  203. SimpleFakeEntry('newthirdparty', ClassWithInterface),
  204. SimpleFakeEntry('deep.newthirdparty', ClassWithInterface),
  205. ],
  206. 'buildbot.buildslave': [
  207. SimpleFakeEntry('thirdparty', ClassWithInterface),
  208. SimpleFakeEntry('deep.thirdparty', ClassWithInterface),
  209. ],
  210. 'buildbot.util': [
  211. SimpleFakeEntry('WorkerLock', ClassWithInterface),
  212. SimpleFakeEntry('enforceChosenWorker', ClassWithInterface),
  213. SimpleFakeEntry('WorkerChoiceParameter', ClassWithInterface),
  214. ],
  215. }
  216. def provide_worker_fake_entries(group):
  217. """
  218. give a set of fake entries for known groups
  219. """
  220. return _WORKER_FAKE_ENTRIES.get(group, [])
  221. class TestWorkerPluginsTransition(unittest.TestCase):
  222. def setUp(self):
  223. buildbot.plugins.db._DB = buildbot.plugins.db._PluginDB()
  224. with mock.patch('buildbot.plugins.db.iter_entry_points',
  225. provide_worker_fake_entries):
  226. self.worker_ns = db.get_plugins('worker')
  227. self.buildslave_ns = db.get_plugins('buildslave')
  228. self.util_ns = db.get_plugins('util')
  229. def test_new_api(self):
  230. with assertNotProducesWarnings(DeprecatedWorkerAPIWarning):
  231. self.assertTrue(self.worker_ns.Worker is ClassWithInterface)
  232. def test_old_api_access_produces_warning(self):
  233. with assertProducesWarning(
  234. DeprecatedWorkerNameWarning,
  235. message_pattern=r"'buildbot\.plugins\.buildslave' plugins "
  236. "namespace is deprecated"):
  237. # Old API, with warning
  238. self.assertTrue(
  239. self.buildslave_ns.BuildSlave is ClassWithInterface)
  240. def test_new_api_through_old_namespace(self):
  241. # Access of newly named workers through old entry point is an error.
  242. with assertProducesWarning(DeprecatedWorkerNameWarning,
  243. message_pattern="namespace is deprecated"):
  244. self.assertRaises(AttributeError, lambda: self.buildslave_ns.Worker)
  245. def test_old_api_through_new_namespace(self):
  246. # Access of old-named workers through new API is an error.
  247. with assertNotProducesWarnings(DeprecatedWorkerAPIWarning):
  248. self.assertRaises(AttributeError,
  249. lambda: self.worker_ns.BuildSlave)
  250. def test_old_api_thirdparty(self):
  251. with assertProducesWarning(
  252. DeprecatedWorkerNameWarning,
  253. message_pattern=r"'buildbot\.plugins\.buildslave' plugins "
  254. "namespace is deprecated"):
  255. # Third party plugins that use old API should work through old API.
  256. self.assertTrue(
  257. self.buildslave_ns.thirdparty is ClassWithInterface)
  258. with assertNotProducesWarnings(DeprecatedWorkerAPIWarning):
  259. # Third party plugins that use old API should work through new API.
  260. self.assertTrue(
  261. self.worker_ns.thirdparty is ClassWithInterface)
  262. def test_old_api_thirdparty_deep(self):
  263. with assertProducesWarning(
  264. DeprecatedWorkerNameWarning,
  265. message_pattern=r"'buildbot\.plugins\.buildslave' plugins "
  266. "namespace is deprecated"):
  267. self.assertTrue(
  268. self.buildslave_ns.deep.thirdparty is ClassWithInterface)
  269. with assertNotProducesWarnings(DeprecatedWorkerAPIWarning):
  270. self.assertTrue(
  271. self.worker_ns.deep.thirdparty is ClassWithInterface)
  272. def test_new_api_thirdparty(self):
  273. # Third party plugins that use new API should work only through
  274. # new API.
  275. with assertProducesWarning(DeprecatedWorkerNameWarning,
  276. message_pattern="namespace is deprecated"):
  277. self.assertRaises(AttributeError,
  278. lambda: self.buildslave_ns.newthirdparty)
  279. with assertNotProducesWarnings(DeprecatedWorkerAPIWarning):
  280. self.assertTrue(
  281. self.worker_ns.newthirdparty is ClassWithInterface)
  282. def test_new_api_thirdparty_deep(self):
  283. # TODO: Why it's not AttributeError (as in tests above), but
  284. # PluginDBError?
  285. with assertProducesWarning(DeprecatedWorkerNameWarning,
  286. message_pattern="namespace is deprecated"):
  287. self.assertRaises(PluginDBError,
  288. lambda: self.buildslave_ns.deep.newthirdparty)
  289. with assertNotProducesWarnings(DeprecatedWorkerAPIWarning):
  290. self.assertTrue(
  291. self.worker_ns.deep.newthirdparty is ClassWithInterface)
  292. def test_util_SlaveLock_import(self):
  293. with assertProducesWarning(
  294. DeprecatedWorkerNameWarning,
  295. message_pattern=re.escape(
  296. "'buildbot.util.SlaveLock' is deprecated, "
  297. "use 'buildbot.util.WorkerLock' instead")):
  298. deprecated = self.util_ns.SlaveLock
  299. with assertNotProducesWarnings(DeprecatedWorkerAPIWarning):
  300. self.assertIdentical(deprecated, ClassWithInterface)
  301. def test_util_enforceChosenSlave_import(self):
  302. with assertProducesWarning(
  303. DeprecatedWorkerNameWarning,
  304. message_pattern=re.escape(
  305. "'buildbot.util.enforceChosenSlave' is deprecated, "
  306. "use 'buildbot.util.enforceChosenWorker' instead")):
  307. deprecated = self.util_ns.enforceChosenSlave
  308. with assertNotProducesWarnings(DeprecatedWorkerAPIWarning):
  309. self.assertIdentical(deprecated, ClassWithInterface)
  310. def test_util_BuildslaveChoiceParameter_import(self):
  311. with assertProducesWarning(
  312. DeprecatedWorkerNameWarning,
  313. message_pattern=re.escape(
  314. "'buildbot.util.BuildslaveChoiceParameter' is deprecated, "
  315. "use 'buildbot.util.WorkerChoiceParameter' instead")):
  316. deprecated = self.util_ns.BuildslaveChoiceParameter
  317. with assertNotProducesWarnings(DeprecatedWorkerAPIWarning):
  318. self.assertIdentical(deprecated, ClassWithInterface)