PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/worker/buildbot_worker/test/unit/test_scripts_runner.py

https://gitlab.com/murder187ss/buildbot
Python | 411 lines | 388 code | 8 blank | 15 comment | 8 complexity | a7902b1fab6a13a4c59d9c9b3329add3 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. import mock
  16. import os
  17. import sys
  18. from buildbot_worker.scripts import runner
  19. from buildbot_worker.test.util import misc
  20. from twisted.python import log
  21. from twisted.python import usage
  22. from twisted.trial import unittest
  23. class OptionsMixin(object):
  24. def assertOptions(self, opts, exp):
  25. got = dict([(k, opts[k]) for k in exp])
  26. if got != exp:
  27. msg = []
  28. for k in exp:
  29. if opts[k] != exp[k]:
  30. msg.append(" %s: expected %r, got %r" %
  31. (k, exp[k], opts[k]))
  32. self.fail("did not get expected options\n" + ("\n".join(msg)))
  33. class BaseDirTestsMixin(object):
  34. """
  35. Common tests for Options classes with 'basedir' parameter
  36. """
  37. GETCWD_PATH = "test-dir"
  38. ABSPATH_PREFIX = "test-prefix-"
  39. MY_BASEDIR = "my-basedir"
  40. # the options class to instantiate for test cases
  41. options_class = None
  42. def setUp(self):
  43. self.patch(os, "getcwd", lambda: self.GETCWD_PATH)
  44. self.patch(os.path, "abspath", lambda path: self.ABSPATH_PREFIX + path)
  45. def parse(self, *args):
  46. assert self.options_class is not None
  47. opts = self.options_class()
  48. opts.parseOptions(args)
  49. return opts
  50. def test_defaults(self):
  51. opts = self.parse()
  52. self.assertEqual(opts["basedir"],
  53. self.ABSPATH_PREFIX + self.GETCWD_PATH,
  54. "unexpected basedir path")
  55. def test_basedir_arg(self):
  56. opts = self.parse(self.MY_BASEDIR)
  57. self.assertEqual(opts["basedir"],
  58. self.ABSPATH_PREFIX + self.MY_BASEDIR,
  59. "unexpected basedir path")
  60. def test_too_many_args(self):
  61. self.assertRaisesRegexp(usage.UsageError,
  62. "I wasn't expecting so many arguments",
  63. self.parse, "arg1", "arg2")
  64. class TestMakerBase(BaseDirTestsMixin, unittest.TestCase):
  65. """
  66. Test buildbot_worker.scripts.runner.MakerBase class.
  67. """
  68. options_class = runner.MakerBase
  69. class TestStopOptions(BaseDirTestsMixin, unittest.TestCase):
  70. """
  71. Test buildbot_worker.scripts.runner.StopOptions class.
  72. """
  73. options_class = runner.StopOptions
  74. def test_synopsis(self):
  75. opts = runner.StopOptions()
  76. self.assertIn('buildbot-worker stop', opts.getSynopsis())
  77. class TestStartOptions(OptionsMixin, BaseDirTestsMixin, unittest.TestCase):
  78. """
  79. Test buildbot_worker.scripts.runner.StartOptions class.
  80. """
  81. options_class = runner.StartOptions
  82. def test_synopsis(self):
  83. opts = runner.StartOptions()
  84. self.assertIn('buildbot-worker start', opts.getSynopsis())
  85. def test_all_args(self):
  86. opts = self.parse("--quiet", "--nodaemon", self.MY_BASEDIR)
  87. self.assertOptions(opts,
  88. dict(quiet=True, nodaemon=True,
  89. basedir=self.ABSPATH_PREFIX + self.MY_BASEDIR))
  90. class TestRestartOptions(OptionsMixin, BaseDirTestsMixin, unittest.TestCase):
  91. """
  92. Test buildbot_worker.scripts.runner.RestartOptions class.
  93. """
  94. options_class = runner.RestartOptions
  95. def test_synopsis(self):
  96. opts = runner.RestartOptions()
  97. self.assertIn('buildbot-worker restart', opts.getSynopsis())
  98. def test_all_args(self):
  99. opts = self.parse("--quiet", "--nodaemon", self.MY_BASEDIR)
  100. self.assertOptions(opts,
  101. dict(quiet=True, nodaemon=True,
  102. basedir=self.ABSPATH_PREFIX + self.MY_BASEDIR))
  103. class TestCreateWorkerOptions(OptionsMixin, unittest.TestCase):
  104. """
  105. Test buildbot_worker.scripts.runner.CreateWorkerOptions class.
  106. """
  107. req_args = ["bdir", "mstr:5678", "name", "pswd"]
  108. def parse(self, *args):
  109. opts = runner.CreateWorkerOptions()
  110. opts.parseOptions(args)
  111. return opts
  112. def test_defaults(self):
  113. self.assertRaisesRegexp(usage.UsageError,
  114. "incorrect number of arguments",
  115. self.parse)
  116. def test_synopsis(self):
  117. opts = runner.CreateWorkerOptions()
  118. self.assertIn('buildbot-worker create-worker', opts.getSynopsis())
  119. def test_min_args(self):
  120. # patch runner.MakerBase.postOptions() so that 'basedir'
  121. # argument will not be converted to absolute path
  122. self.patch(runner.MakerBase, "postOptions", mock.Mock())
  123. self.assertOptions(self.parse(*self.req_args),
  124. dict(basedir="bdir", host="mstr", port=5678,
  125. name="name", passwd="pswd"))
  126. def test_all_args(self):
  127. # patch runner.MakerBase.postOptions() so that 'basedir'
  128. # argument will not be converted to absolute path
  129. self.patch(runner.MakerBase, "postOptions", mock.Mock())
  130. opts = self.parse("--force", "--relocatable", "--no-logrotate",
  131. "--keepalive=4", "--usepty=0", "--umask=022",
  132. "--maxdelay=3", "--numcpus=4", "--log-size=2", "--log-count=1",
  133. "--allow-shutdown=file", *self.req_args)
  134. self.assertOptions(opts,
  135. {"force": True,
  136. "relocatable": True,
  137. "no-logrotate": True,
  138. "usepty": 0,
  139. "umask": "022",
  140. "maxdelay": 3,
  141. "numcpus": "4",
  142. "log-size": 2,
  143. "log-count": "1",
  144. "allow-shutdown": "file",
  145. "basedir": "bdir",
  146. "host": "mstr",
  147. "port": 5678,
  148. "name": "name",
  149. "passwd": "pswd"})
  150. def test_master_url(self):
  151. self.assertRaisesRegexp(usage.UsageError,
  152. "<master> is not a URL - do not use URL",
  153. self.parse, "a", "http://b.c", "d", "e")
  154. def test_inv_keepalive(self):
  155. self.assertRaisesRegexp(usage.UsageError,
  156. "keepalive parameter needs to be an number",
  157. self.parse, "--keepalive=X", *self.req_args)
  158. def test_inv_usepty(self):
  159. self.assertRaisesRegexp(usage.UsageError,
  160. "usepty parameter needs to be an number",
  161. self.parse, "--usepty=X", *self.req_args)
  162. def test_inv_maxdelay(self):
  163. self.assertRaisesRegexp(usage.UsageError,
  164. "maxdelay parameter needs to be an number",
  165. self.parse, "--maxdelay=X", *self.req_args)
  166. def test_inv_log_size(self):
  167. self.assertRaisesRegexp(usage.UsageError,
  168. "log-size parameter needs to be an number",
  169. self.parse, "--log-size=X", *self.req_args)
  170. def test_inv_log_count(self):
  171. self.assertRaisesRegexp(usage.UsageError,
  172. "log-count parameter needs to be an number or None",
  173. self.parse, "--log-count=X", *self.req_args)
  174. def test_inv_numcpus(self):
  175. self.assertRaisesRegexp(usage.UsageError,
  176. "numcpus parameter needs to be an number or None",
  177. self.parse, "--numcpus=X", *self.req_args)
  178. def test_inv_umask(self):
  179. self.assertRaisesRegexp(usage.UsageError,
  180. "umask parameter needs to be an number or None",
  181. self.parse, "--umask=X", *self.req_args)
  182. def test_inv_allow_shutdown(self):
  183. self.assertRaisesRegexp(usage.UsageError,
  184. "allow-shutdown needs to be one of 'signal' or 'file'",
  185. self.parse, "--allow-shutdown=X", *self.req_args)
  186. def test_too_few_args(self):
  187. self.assertRaisesRegexp(usage.UsageError,
  188. "incorrect number of arguments",
  189. self.parse, "arg1", "arg2")
  190. def test_too_many_args(self):
  191. self.assertRaisesRegexp(usage.UsageError,
  192. "incorrect number of arguments",
  193. self.parse, "extra_arg", *self.req_args)
  194. def test_validateMasterArgument_no_port(self):
  195. """
  196. test calling CreateWorkerOptions.validateMasterArgument()
  197. on <master> argument without port specified.
  198. """
  199. opts = runner.CreateWorkerOptions()
  200. self.assertEqual(opts.validateMasterArgument("mstrhost"),
  201. ("mstrhost", 9989),
  202. "incorrect master host and/or port")
  203. def test_validateMasterArgument_empty_master(self):
  204. """
  205. test calling CreateWorkerOptions.validateMasterArgument()
  206. on <master> without host part specified.
  207. """
  208. opts = runner.CreateWorkerOptions()
  209. self.assertRaisesRegexp(usage.UsageError,
  210. "invalid <master> argument ':1234'",
  211. opts.validateMasterArgument, ":1234")
  212. def test_validateMasterArgument_inv_port(self):
  213. """
  214. test calling CreateWorkerOptions.validateMasterArgument()
  215. on <master> without with unparsable port part
  216. """
  217. opts = runner.CreateWorkerOptions()
  218. self.assertRaisesRegexp(usage.UsageError,
  219. "invalid master port 'apple', "
  220. "needs to be an number",
  221. opts.validateMasterArgument, "host:apple")
  222. def test_validateMasterArgument_ok(self):
  223. """
  224. test calling CreateWorkerOptions.validateMasterArgument()
  225. on <master> without host and port parts specified.
  226. """
  227. opts = runner.CreateWorkerOptions()
  228. self.assertEqual(opts.validateMasterArgument("mstrhost:4321"),
  229. ("mstrhost", 4321),
  230. "incorrect master host and/or port")
  231. class TestOptions(misc.StdoutAssertionsMixin, unittest.TestCase):
  232. """
  233. Test buildbot_worker.scripts.runner.Options class.
  234. """
  235. def setUp(self):
  236. self.setUpStdoutAssertions()
  237. def parse(self, *args):
  238. opts = runner.Options()
  239. opts.parseOptions(args)
  240. return opts
  241. def test_defaults(self):
  242. self.assertRaisesRegexp(usage.UsageError,
  243. "must specify a command",
  244. self.parse)
  245. def test_version(self):
  246. exception = self.assertRaises(SystemExit, self.parse, '--version')
  247. self.assertEqual(exception.code, 0, "unexpected exit code")
  248. self.assertInStdout('worker version:')
  249. def test_verbose(self):
  250. self.patch(log, 'startLogging', mock.Mock())
  251. self.assertRaises(usage.UsageError, self.parse, "--verbose")
  252. log.startLogging.assert_called_once_with(sys.stderr)
  253. # used by TestRun.test_run_good to patch in a callback
  254. functionPlaceholder = None
  255. class TestRun(misc.StdoutAssertionsMixin, unittest.TestCase):
  256. """
  257. Test buildbot_worker.scripts.runner.run()
  258. """
  259. def setUp(self):
  260. self.setUpStdoutAssertions()
  261. class TestSubCommand(usage.Options):
  262. subcommandFunction = __name__ + ".functionPlaceholder"
  263. optFlags = [["test-opt", None, None]]
  264. class TestOptions(usage.Options):
  265. """
  266. Option class that emulates usage error. The 'suboptions' flag
  267. enables emulation of usage error in a sub-option.
  268. """
  269. optFlags = [["suboptions", None, None]]
  270. def postOptions(self):
  271. if self["suboptions"]:
  272. self.subOptions = "SubOptionUsage"
  273. raise usage.UsageError("usage-error-message")
  274. def __str__(self):
  275. return "GeneralUsage"
  276. def test_run_good(self):
  277. """
  278. Test successful invocation of worker command.
  279. """
  280. self.patch(sys, "argv", ["command", 'test', '--test-opt'])
  281. # patch runner module to use our test subcommand class
  282. self.patch(runner.Options, 'subCommands',
  283. [['test', None, self.TestSubCommand, None]])
  284. # trace calls to subcommand function
  285. subcommand_func = mock.Mock(return_value=42)
  286. self.patch(sys.modules[__name__],
  287. "functionPlaceholder",
  288. subcommand_func)
  289. # check that subcommand function called with correct arguments
  290. # and that it's return value is used as exit code
  291. exception = self.assertRaises(SystemExit, runner.run)
  292. subcommand_func.assert_called_once_with({'test-opt': 1})
  293. self.assertEqual(exception.code, 42, "unexpected exit code")
  294. def test_run_bad_noargs(self):
  295. """
  296. Test handling of invalid command line arguments.
  297. """
  298. self.patch(sys, "argv", ["command"])
  299. # patch runner module to use test Options class
  300. self.patch(runner, "Options", self.TestOptions)
  301. exception = self.assertRaises(SystemExit, runner.run)
  302. self.assertEqual(exception.code, 1, "unexpected exit code")
  303. self.assertStdoutEqual("command: usage-error-message\n\n"
  304. "GeneralUsage\n",
  305. "unexpected error message on stdout")
  306. def test_run_bad_suboption(self):
  307. """
  308. Test handling of invalid command line arguments in a suboption.
  309. """
  310. self.patch(sys, "argv", ["command", "--suboptions"])
  311. # patch runner module to use test Options class
  312. self.patch(runner, "Options", self.TestOptions)
  313. exception = self.assertRaises(SystemExit, runner.run)
  314. self.assertEqual(exception.code, 1, "unexpected exit code")
  315. # check that we get error message for a sub-option
  316. self.assertStdoutEqual("command: usage-error-message\n\n"
  317. "SubOptionUsage\n",
  318. "unexpected error message on stdout")