Mercurial > repos > melissacline > ucsc_xena_platform
comparison python-daemon-2.0.5/test/test_runner.py @ 33:7ceb967147c3
start xena with no gui
add library files
| author | jingchunzhu <jingchunzhu@gmail.com> |
|---|---|
| date | Wed, 22 Jul 2015 13:24:44 -0700 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 32:63b1ba1e3424 | 33:7ceb967147c3 |
|---|---|
| 1 # -*- coding: utf-8 -*- | |
| 2 # | |
| 3 # test/test_runner.py | |
| 4 # Part of ‘python-daemon’, an implementation of PEP 3143. | |
| 5 # | |
| 6 # Copyright © 2009–2015 Ben Finney <ben+python@benfinney.id.au> | |
| 7 # | |
| 8 # This is free software: you may copy, modify, and/or distribute this work | |
| 9 # under the terms of the Apache License, version 2.0 as published by the | |
| 10 # Apache Software Foundation. | |
| 11 # No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details. | |
| 12 | |
| 13 """ Unit test for ‘runner’ module. | |
| 14 """ | |
| 15 | |
| 16 from __future__ import (absolute_import, unicode_literals) | |
| 17 | |
| 18 try: | |
| 19 # Python 3 standard library. | |
| 20 import builtins | |
| 21 except ImportError: | |
| 22 # Python 2 standard library. | |
| 23 import __builtin__ as builtins | |
| 24 import os | |
| 25 import os.path | |
| 26 import sys | |
| 27 import tempfile | |
| 28 import errno | |
| 29 import signal | |
| 30 import functools | |
| 31 | |
| 32 import lockfile | |
| 33 import mock | |
| 34 import testtools | |
| 35 | |
| 36 from . import scaffold | |
| 37 from .scaffold import (basestring, unicode) | |
| 38 from .test_pidfile import ( | |
| 39 FakeFileDescriptorStringIO, | |
| 40 setup_pidfile_fixtures, | |
| 41 make_pidlockfile_scenarios, | |
| 42 apply_lockfile_method_mocks, | |
| 43 ) | |
| 44 from .test_daemon import ( | |
| 45 setup_streams_fixtures, | |
| 46 ) | |
| 47 | |
| 48 import daemon.daemon | |
| 49 import daemon.runner | |
| 50 import daemon.pidfile | |
| 51 | |
| 52 | |
| 53 class ModuleExceptions_TestCase(scaffold.Exception_TestCase): | |
| 54 """ Test cases for module exception classes. """ | |
| 55 | |
| 56 scenarios = scaffold.make_exception_scenarios([ | |
| 57 ('daemon.runner.DaemonRunnerError', dict( | |
| 58 exc_type = daemon.runner.DaemonRunnerError, | |
| 59 min_args = 1, | |
| 60 types = [Exception], | |
| 61 )), | |
| 62 ('daemon.runner.DaemonRunnerInvalidActionError', dict( | |
| 63 exc_type = daemon.runner.DaemonRunnerInvalidActionError, | |
| 64 min_args = 1, | |
| 65 types = [daemon.runner.DaemonRunnerError, ValueError], | |
| 66 )), | |
| 67 ('daemon.runner.DaemonRunnerStartFailureError', dict( | |
| 68 exc_type = daemon.runner.DaemonRunnerStartFailureError, | |
| 69 min_args = 1, | |
| 70 types = [daemon.runner.DaemonRunnerError, RuntimeError], | |
| 71 )), | |
| 72 ('daemon.runner.DaemonRunnerStopFailureError', dict( | |
| 73 exc_type = daemon.runner.DaemonRunnerStopFailureError, | |
| 74 min_args = 1, | |
| 75 types = [daemon.runner.DaemonRunnerError, RuntimeError], | |
| 76 )), | |
| 77 ]) | |
| 78 | |
| 79 | |
| 80 def make_runner_scenarios(): | |
| 81 """ Make a collection of scenarios for testing `DaemonRunner` instances. | |
| 82 | |
| 83 :return: A collection of scenarios for tests involving | |
| 84 `DaemonRunner` instances. | |
| 85 | |
| 86 The collection is a mapping from scenario name to a dictionary of | |
| 87 scenario attributes. | |
| 88 | |
| 89 """ | |
| 90 | |
| 91 pidlockfile_scenarios = make_pidlockfile_scenarios() | |
| 92 | |
| 93 scenarios = { | |
| 94 'simple': { | |
| 95 'pidlockfile_scenario_name': 'simple', | |
| 96 }, | |
| 97 'pidfile-locked': { | |
| 98 'pidlockfile_scenario_name': 'exist-other-pid-locked', | |
| 99 }, | |
| 100 } | |
| 101 | |
| 102 for scenario in scenarios.values(): | |
| 103 if 'pidlockfile_scenario_name' in scenario: | |
| 104 pidlockfile_scenario = pidlockfile_scenarios.pop( | |
| 105 scenario['pidlockfile_scenario_name']) | |
| 106 scenario['pid'] = pidlockfile_scenario['pid'] | |
| 107 scenario['pidfile_path'] = pidlockfile_scenario['pidfile_path'] | |
| 108 scenario['pidfile_timeout'] = 23 | |
| 109 scenario['pidlockfile_scenario'] = pidlockfile_scenario | |
| 110 | |
| 111 return scenarios | |
| 112 | |
| 113 | |
| 114 def set_runner_scenario(testcase, scenario_name): | |
| 115 """ Set the DaemonRunner test scenario for the test case. | |
| 116 | |
| 117 :param testcase: The `TestCase` instance to decorate. | |
| 118 :param scenario_name: The name of the scenario to use. | |
| 119 | |
| 120 Set the `DaemonRunner` test scenario name and decorate the | |
| 121 `testcase` with the corresponding scenario fixtures. | |
| 122 | |
| 123 """ | |
| 124 scenarios = testcase.runner_scenarios | |
| 125 testcase.scenario = scenarios[scenario_name] | |
| 126 apply_lockfile_method_mocks( | |
| 127 testcase.mock_runner_lockfile, | |
| 128 testcase, | |
| 129 testcase.scenario['pidlockfile_scenario']) | |
| 130 | |
| 131 | |
| 132 def setup_runner_fixtures(testcase): | |
| 133 """ Set up common fixtures for `DaemonRunner` test cases. | |
| 134 | |
| 135 :param testcase: A `TestCase` instance to decorate. | |
| 136 | |
| 137 Decorate the `testcase` with attributes to be fixtures for tests | |
| 138 involving `DaemonRunner` instances. | |
| 139 | |
| 140 """ | |
| 141 setup_pidfile_fixtures(testcase) | |
| 142 setup_streams_fixtures(testcase) | |
| 143 | |
| 144 testcase.runner_scenarios = make_runner_scenarios() | |
| 145 | |
| 146 patcher_stderr = mock.patch.object( | |
| 147 sys, "stderr", | |
| 148 new=FakeFileDescriptorStringIO()) | |
| 149 testcase.fake_stderr = patcher_stderr.start() | |
| 150 testcase.addCleanup(patcher_stderr.stop) | |
| 151 | |
| 152 simple_scenario = testcase.runner_scenarios['simple'] | |
| 153 | |
| 154 testcase.mock_runner_lockfile = mock.MagicMock( | |
| 155 spec=daemon.pidfile.TimeoutPIDLockFile) | |
| 156 apply_lockfile_method_mocks( | |
| 157 testcase.mock_runner_lockfile, | |
| 158 testcase, | |
| 159 simple_scenario['pidlockfile_scenario']) | |
| 160 testcase.mock_runner_lockfile.path = simple_scenario['pidfile_path'] | |
| 161 | |
| 162 patcher_lockfile_class = mock.patch.object( | |
| 163 daemon.pidfile, "TimeoutPIDLockFile", | |
| 164 return_value=testcase.mock_runner_lockfile) | |
| 165 patcher_lockfile_class.start() | |
| 166 testcase.addCleanup(patcher_lockfile_class.stop) | |
| 167 | |
| 168 class TestApp(object): | |
| 169 | |
| 170 def __init__(self): | |
| 171 self.stdin_path = testcase.stream_file_paths['stdin'] | |
| 172 self.stdout_path = testcase.stream_file_paths['stdout'] | |
| 173 self.stderr_path = testcase.stream_file_paths['stderr'] | |
| 174 self.pidfile_path = simple_scenario['pidfile_path'] | |
| 175 self.pidfile_timeout = simple_scenario['pidfile_timeout'] | |
| 176 | |
| 177 run = mock.MagicMock(name="TestApp.run") | |
| 178 | |
| 179 testcase.TestApp = TestApp | |
| 180 | |
| 181 patcher_runner_daemoncontext = mock.patch.object( | |
| 182 daemon.runner, "DaemonContext", autospec=True) | |
| 183 patcher_runner_daemoncontext.start() | |
| 184 testcase.addCleanup(patcher_runner_daemoncontext.stop) | |
| 185 | |
| 186 testcase.test_app = testcase.TestApp() | |
| 187 | |
| 188 testcase.test_program_name = "bazprog" | |
| 189 testcase.test_program_path = os.path.join( | |
| 190 "/foo/bar", testcase.test_program_name) | |
| 191 testcase.valid_argv_params = { | |
| 192 'start': [testcase.test_program_path, 'start'], | |
| 193 'stop': [testcase.test_program_path, 'stop'], | |
| 194 'restart': [testcase.test_program_path, 'restart'], | |
| 195 } | |
| 196 | |
| 197 def fake_open(filename, mode=None, buffering=None): | |
| 198 if filename in testcase.stream_files_by_path: | |
| 199 result = testcase.stream_files_by_path[filename] | |
| 200 else: | |
| 201 result = FakeFileDescriptorStringIO() | |
| 202 result.mode = mode | |
| 203 result.buffering = buffering | |
| 204 return result | |
| 205 | |
| 206 mock_open = mock.mock_open() | |
| 207 mock_open.side_effect = fake_open | |
| 208 | |
| 209 func_patcher_builtin_open = mock.patch.object( | |
| 210 builtins, "open", | |
| 211 new=mock_open) | |
| 212 func_patcher_builtin_open.start() | |
| 213 testcase.addCleanup(func_patcher_builtin_open.stop) | |
| 214 | |
| 215 func_patcher_os_kill = mock.patch.object(os, "kill") | |
| 216 func_patcher_os_kill.start() | |
| 217 testcase.addCleanup(func_patcher_os_kill.stop) | |
| 218 | |
| 219 patcher_sys_argv = mock.patch.object( | |
| 220 sys, "argv", | |
| 221 new=testcase.valid_argv_params['start']) | |
| 222 patcher_sys_argv.start() | |
| 223 testcase.addCleanup(patcher_sys_argv.stop) | |
| 224 | |
| 225 testcase.test_instance = daemon.runner.DaemonRunner(testcase.test_app) | |
| 226 | |
| 227 testcase.scenario = NotImplemented | |
| 228 | |
| 229 | |
| 230 class DaemonRunner_BaseTestCase(scaffold.TestCase): | |
| 231 """ Base class for DaemonRunner test case classes. """ | |
| 232 | |
| 233 def setUp(self): | |
| 234 """ Set up test fixtures. """ | |
| 235 super(DaemonRunner_BaseTestCase, self).setUp() | |
| 236 | |
| 237 setup_runner_fixtures(self) | |
| 238 set_runner_scenario(self, 'simple') | |
| 239 | |
| 240 | |
| 241 class DaemonRunner_TestCase(DaemonRunner_BaseTestCase): | |
| 242 """ Test cases for DaemonRunner class. """ | |
| 243 | |
| 244 def setUp(self): | |
| 245 """ Set up test fixtures. """ | |
| 246 super(DaemonRunner_TestCase, self).setUp() | |
| 247 | |
| 248 func_patcher_parse_args = mock.patch.object( | |
| 249 daemon.runner.DaemonRunner, "parse_args") | |
| 250 func_patcher_parse_args.start() | |
| 251 self.addCleanup(func_patcher_parse_args.stop) | |
| 252 | |
| 253 # Create a new instance now with our custom patches. | |
| 254 self.test_instance = daemon.runner.DaemonRunner(self.test_app) | |
| 255 | |
| 256 def test_instantiate(self): | |
| 257 """ New instance of DaemonRunner should be created. """ | |
| 258 self.assertIsInstance(self.test_instance, daemon.runner.DaemonRunner) | |
| 259 | |
| 260 def test_parses_commandline_args(self): | |
| 261 """ Should parse commandline arguments. """ | |
| 262 self.test_instance.parse_args.assert_called_with() | |
| 263 | |
| 264 def test_has_specified_app(self): | |
| 265 """ Should have specified application object. """ | |
| 266 self.assertIs(self.test_app, self.test_instance.app) | |
| 267 | |
| 268 def test_sets_pidfile_none_when_pidfile_path_is_none(self): | |
| 269 """ Should set ‘pidfile’ to ‘None’ when ‘pidfile_path’ is ‘None’. """ | |
| 270 pidfile_path = None | |
| 271 self.test_app.pidfile_path = pidfile_path | |
| 272 expected_pidfile = None | |
| 273 instance = daemon.runner.DaemonRunner(self.test_app) | |
| 274 self.assertIs(expected_pidfile, instance.pidfile) | |
| 275 | |
| 276 def test_error_when_pidfile_path_not_string(self): | |
| 277 """ Should raise ValueError when PID file path not a string. """ | |
| 278 pidfile_path = object() | |
| 279 self.test_app.pidfile_path = pidfile_path | |
| 280 expected_error = ValueError | |
| 281 self.assertRaises( | |
| 282 expected_error, | |
| 283 daemon.runner.DaemonRunner, self.test_app) | |
| 284 | |
| 285 def test_error_when_pidfile_path_not_absolute(self): | |
| 286 """ Should raise ValueError when PID file path not absolute. """ | |
| 287 pidfile_path = "foo/bar.pid" | |
| 288 self.test_app.pidfile_path = pidfile_path | |
| 289 expected_error = ValueError | |
| 290 self.assertRaises( | |
| 291 expected_error, | |
| 292 daemon.runner.DaemonRunner, self.test_app) | |
| 293 | |
| 294 def test_creates_lock_with_specified_parameters(self): | |
| 295 """ Should create a TimeoutPIDLockFile with specified params. """ | |
| 296 pidfile_path = self.scenario['pidfile_path'] | |
| 297 pidfile_timeout = self.scenario['pidfile_timeout'] | |
| 298 daemon.pidfile.TimeoutPIDLockFile.assert_called_with( | |
| 299 pidfile_path, pidfile_timeout) | |
| 300 | |
| 301 def test_has_created_pidfile(self): | |
| 302 """ Should have new PID lock file as `pidfile` attribute. """ | |
| 303 expected_pidfile = self.mock_runner_lockfile | |
| 304 instance = self.test_instance | |
| 305 self.assertIs( | |
| 306 expected_pidfile, instance.pidfile) | |
| 307 | |
| 308 def test_daemon_context_has_created_pidfile(self): | |
| 309 """ DaemonContext component should have new PID lock file. """ | |
| 310 expected_pidfile = self.mock_runner_lockfile | |
| 311 daemon_context = self.test_instance.daemon_context | |
| 312 self.assertIs( | |
| 313 expected_pidfile, daemon_context.pidfile) | |
| 314 | |
| 315 def test_daemon_context_has_specified_stdin_stream(self): | |
| 316 """ DaemonContext component should have specified stdin file. """ | |
| 317 test_app = self.test_app | |
| 318 expected_file = self.stream_files_by_name['stdin'] | |
| 319 daemon_context = self.test_instance.daemon_context | |
| 320 self.assertEqual(expected_file, daemon_context.stdin) | |
| 321 | |
| 322 def test_daemon_context_has_stdin_in_read_mode(self): | |
| 323 """ DaemonContext component should open stdin file for read. """ | |
| 324 expected_mode = 'rt' | |
| 325 daemon_context = self.test_instance.daemon_context | |
| 326 self.assertIn(expected_mode, daemon_context.stdin.mode) | |
| 327 | |
| 328 def test_daemon_context_has_specified_stdout_stream(self): | |
| 329 """ DaemonContext component should have specified stdout file. """ | |
| 330 test_app = self.test_app | |
| 331 expected_file = self.stream_files_by_name['stdout'] | |
| 332 daemon_context = self.test_instance.daemon_context | |
| 333 self.assertEqual(expected_file, daemon_context.stdout) | |
| 334 | |
| 335 def test_daemon_context_has_stdout_in_append_mode(self): | |
| 336 """ DaemonContext component should open stdout file for append. """ | |
| 337 expected_mode = 'w+t' | |
| 338 daemon_context = self.test_instance.daemon_context | |
| 339 self.assertIn(expected_mode, daemon_context.stdout.mode) | |
| 340 | |
| 341 def test_daemon_context_has_specified_stderr_stream(self): | |
| 342 """ DaemonContext component should have specified stderr file. """ | |
| 343 test_app = self.test_app | |
| 344 expected_file = self.stream_files_by_name['stderr'] | |
| 345 daemon_context = self.test_instance.daemon_context | |
| 346 self.assertEqual(expected_file, daemon_context.stderr) | |
| 347 | |
| 348 def test_daemon_context_has_stderr_in_append_mode(self): | |
| 349 """ DaemonContext component should open stderr file for append. """ | |
| 350 expected_mode = 'w+t' | |
| 351 daemon_context = self.test_instance.daemon_context | |
| 352 self.assertIn(expected_mode, daemon_context.stderr.mode) | |
| 353 | |
| 354 def test_daemon_context_has_stderr_with_no_buffering(self): | |
| 355 """ DaemonContext component should open stderr file unbuffered. """ | |
| 356 expected_buffering = 0 | |
| 357 daemon_context = self.test_instance.daemon_context | |
| 358 self.assertEqual( | |
| 359 expected_buffering, daemon_context.stderr.buffering) | |
| 360 | |
| 361 | |
| 362 class DaemonRunner_usage_exit_TestCase(DaemonRunner_BaseTestCase): | |
| 363 """ Test cases for DaemonRunner.usage_exit method. """ | |
| 364 | |
| 365 def test_raises_system_exit(self): | |
| 366 """ Should raise SystemExit exception. """ | |
| 367 instance = self.test_instance | |
| 368 argv = [self.test_program_path] | |
| 369 self.assertRaises( | |
| 370 SystemExit, | |
| 371 instance._usage_exit, argv) | |
| 372 | |
| 373 def test_message_follows_conventional_format(self): | |
| 374 """ Should emit a conventional usage message. """ | |
| 375 instance = self.test_instance | |
| 376 argv = [self.test_program_path] | |
| 377 expected_stderr_output = """\ | |
| 378 usage: {progname} ... | |
| 379 """.format( | |
| 380 progname=self.test_program_name) | |
| 381 self.assertRaises( | |
| 382 SystemExit, | |
| 383 instance._usage_exit, argv) | |
| 384 self.assertOutputCheckerMatch( | |
| 385 expected_stderr_output, self.fake_stderr.getvalue()) | |
| 386 | |
| 387 | |
| 388 class DaemonRunner_parse_args_TestCase(DaemonRunner_BaseTestCase): | |
| 389 """ Test cases for DaemonRunner.parse_args method. """ | |
| 390 | |
| 391 def setUp(self): | |
| 392 """ Set up test fixtures. """ | |
| 393 super(DaemonRunner_parse_args_TestCase, self).setUp() | |
| 394 | |
| 395 func_patcher_usage_exit = mock.patch.object( | |
| 396 daemon.runner.DaemonRunner, "_usage_exit", | |
| 397 side_effect=NotImplementedError) | |
| 398 func_patcher_usage_exit.start() | |
| 399 self.addCleanup(func_patcher_usage_exit.stop) | |
| 400 | |
| 401 def test_emits_usage_message_if_insufficient_args(self): | |
| 402 """ Should emit a usage message and exit if too few arguments. """ | |
| 403 instance = self.test_instance | |
| 404 argv = [self.test_program_path] | |
| 405 exc = self.assertRaises( | |
| 406 NotImplementedError, | |
| 407 instance.parse_args, argv) | |
| 408 daemon.runner.DaemonRunner._usage_exit.assert_called_with(argv) | |
| 409 | |
| 410 def test_emits_usage_message_if_unknown_action_arg(self): | |
| 411 """ Should emit a usage message and exit if unknown action. """ | |
| 412 instance = self.test_instance | |
| 413 progname = self.test_program_name | |
| 414 argv = [self.test_program_path, 'bogus'] | |
| 415 exc = self.assertRaises( | |
| 416 NotImplementedError, | |
| 417 instance.parse_args, argv) | |
| 418 daemon.runner.DaemonRunner._usage_exit.assert_called_with(argv) | |
| 419 | |
| 420 def test_should_parse_system_argv_by_default(self): | |
| 421 """ Should parse sys.argv by default. """ | |
| 422 instance = self.test_instance | |
| 423 expected_action = 'start' | |
| 424 argv = self.valid_argv_params['start'] | |
| 425 with mock.patch.object(sys, "argv", new=argv): | |
| 426 instance.parse_args() | |
| 427 self.assertEqual(expected_action, instance.action) | |
| 428 | |
| 429 def test_sets_action_from_first_argument(self): | |
| 430 """ Should set action from first commandline argument. """ | |
| 431 instance = self.test_instance | |
| 432 for name, argv in self.valid_argv_params.items(): | |
| 433 expected_action = name | |
| 434 instance.parse_args(argv) | |
| 435 self.assertEqual(expected_action, instance.action) | |
| 436 | |
| 437 | |
| 438 try: | |
| 439 ProcessLookupError | |
| 440 except NameError: | |
| 441 # Python 2 uses OSError. | |
| 442 ProcessLookupError = functools.partial(OSError, errno.ESRCH) | |
| 443 | |
| 444 class DaemonRunner_do_action_TestCase(DaemonRunner_BaseTestCase): | |
| 445 """ Test cases for DaemonRunner.do_action method. """ | |
| 446 | |
| 447 def test_raises_error_if_unknown_action(self): | |
| 448 """ Should emit a usage message and exit if action is unknown. """ | |
| 449 instance = self.test_instance | |
| 450 instance.action = 'bogus' | |
| 451 expected_error = daemon.runner.DaemonRunnerInvalidActionError | |
| 452 self.assertRaises( | |
| 453 expected_error, | |
| 454 instance.do_action) | |
| 455 | |
| 456 | |
| 457 class DaemonRunner_do_action_start_TestCase(DaemonRunner_BaseTestCase): | |
| 458 """ Test cases for DaemonRunner.do_action method, action 'start'. """ | |
| 459 | |
| 460 def setUp(self): | |
| 461 """ Set up test fixtures. """ | |
| 462 super(DaemonRunner_do_action_start_TestCase, self).setUp() | |
| 463 | |
| 464 self.test_instance.action = 'start' | |
| 465 | |
| 466 def test_raises_error_if_pidfile_locked(self): | |
| 467 """ Should raise error if PID file is locked. """ | |
| 468 | |
| 469 instance = self.test_instance | |
| 470 instance.daemon_context.open.side_effect = lockfile.AlreadyLocked | |
| 471 pidfile_path = self.scenario['pidfile_path'] | |
| 472 expected_error = daemon.runner.DaemonRunnerStartFailureError | |
| 473 expected_message_content = pidfile_path | |
| 474 exc = self.assertRaises( | |
| 475 expected_error, | |
| 476 instance.do_action) | |
| 477 self.assertIn(expected_message_content, unicode(exc)) | |
| 478 | |
| 479 def test_breaks_lock_if_no_such_process(self): | |
| 480 """ Should request breaking lock if PID file process is not running. """ | |
| 481 set_runner_scenario(self, 'pidfile-locked') | |
| 482 instance = self.test_instance | |
| 483 self.mock_runner_lockfile.read_pid.return_value = ( | |
| 484 self.scenario['pidlockfile_scenario']['pidfile_pid']) | |
| 485 pidfile_path = self.scenario['pidfile_path'] | |
| 486 test_pid = self.scenario['pidlockfile_scenario']['pidfile_pid'] | |
| 487 expected_signal = signal.SIG_DFL | |
| 488 test_error = ProcessLookupError("Not running") | |
| 489 os.kill.side_effect = test_error | |
| 490 instance.do_action() | |
| 491 os.kill.assert_called_with(test_pid, expected_signal) | |
| 492 self.mock_runner_lockfile.break_lock.assert_called_with() | |
| 493 | |
| 494 def test_requests_daemon_context_open(self): | |
| 495 """ Should request the daemon context to open. """ | |
| 496 instance = self.test_instance | |
| 497 instance.do_action() | |
| 498 instance.daemon_context.open.assert_called_with() | |
| 499 | |
| 500 def test_emits_start_message_to_stderr(self): | |
| 501 """ Should emit start message to stderr. """ | |
| 502 instance = self.test_instance | |
| 503 expected_stderr = """\ | |
| 504 started with pid {pid:d} | |
| 505 """.format( | |
| 506 pid=self.scenario['pid']) | |
| 507 instance.do_action() | |
| 508 self.assertOutputCheckerMatch( | |
| 509 expected_stderr, self.fake_stderr.getvalue()) | |
| 510 | |
| 511 def test_requests_app_run(self): | |
| 512 """ Should request the application to run. """ | |
| 513 instance = self.test_instance | |
| 514 instance.do_action() | |
| 515 self.test_app.run.assert_called_with() | |
| 516 | |
| 517 | |
| 518 class DaemonRunner_do_action_stop_TestCase(DaemonRunner_BaseTestCase): | |
| 519 """ Test cases for DaemonRunner.do_action method, action 'stop'. """ | |
| 520 | |
| 521 def setUp(self): | |
| 522 """ Set up test fixtures. """ | |
| 523 super(DaemonRunner_do_action_stop_TestCase, self).setUp() | |
| 524 | |
| 525 set_runner_scenario(self, 'pidfile-locked') | |
| 526 | |
| 527 self.test_instance.action = 'stop' | |
| 528 | |
| 529 self.mock_runner_lockfile.is_locked.return_value = True | |
| 530 self.mock_runner_lockfile.i_am_locking.return_value = False | |
| 531 self.mock_runner_lockfile.read_pid.return_value = ( | |
| 532 self.scenario['pidlockfile_scenario']['pidfile_pid']) | |
| 533 | |
| 534 def test_raises_error_if_pidfile_not_locked(self): | |
| 535 """ Should raise error if PID file is not locked. """ | |
| 536 set_runner_scenario(self, 'simple') | |
| 537 instance = self.test_instance | |
| 538 self.mock_runner_lockfile.is_locked.return_value = False | |
| 539 self.mock_runner_lockfile.i_am_locking.return_value = False | |
| 540 self.mock_runner_lockfile.read_pid.return_value = ( | |
| 541 self.scenario['pidlockfile_scenario']['pidfile_pid']) | |
| 542 pidfile_path = self.scenario['pidfile_path'] | |
| 543 expected_error = daemon.runner.DaemonRunnerStopFailureError | |
| 544 expected_message_content = pidfile_path | |
| 545 exc = self.assertRaises( | |
| 546 expected_error, | |
| 547 instance.do_action) | |
| 548 self.assertIn(expected_message_content, unicode(exc)) | |
| 549 | |
| 550 def test_breaks_lock_if_pidfile_stale(self): | |
| 551 """ Should break lock if PID file is stale. """ | |
| 552 instance = self.test_instance | |
| 553 pidfile_path = self.scenario['pidfile_path'] | |
| 554 test_pid = self.scenario['pidlockfile_scenario']['pidfile_pid'] | |
| 555 expected_signal = signal.SIG_DFL | |
| 556 test_error = OSError(errno.ESRCH, "Not running") | |
| 557 os.kill.side_effect = test_error | |
| 558 instance.do_action() | |
| 559 self.mock_runner_lockfile.break_lock.assert_called_with() | |
| 560 | |
| 561 def test_sends_terminate_signal_to_process_from_pidfile(self): | |
| 562 """ Should send SIGTERM to the daemon process. """ | |
| 563 instance = self.test_instance | |
| 564 test_pid = self.scenario['pidlockfile_scenario']['pidfile_pid'] | |
| 565 expected_signal = signal.SIGTERM | |
| 566 instance.do_action() | |
| 567 os.kill.assert_called_with(test_pid, expected_signal) | |
| 568 | |
| 569 def test_raises_error_if_cannot_send_signal_to_process(self): | |
| 570 """ Should raise error if cannot send signal to daemon process. """ | |
| 571 instance = self.test_instance | |
| 572 test_pid = self.scenario['pidlockfile_scenario']['pidfile_pid'] | |
| 573 pidfile_path = self.scenario['pidfile_path'] | |
| 574 test_error = OSError(errno.EPERM, "Nice try") | |
| 575 os.kill.side_effect = test_error | |
| 576 expected_error = daemon.runner.DaemonRunnerStopFailureError | |
| 577 expected_message_content = unicode(test_pid) | |
| 578 exc = self.assertRaises( | |
| 579 expected_error, | |
| 580 instance.do_action) | |
| 581 self.assertIn(expected_message_content, unicode(exc)) | |
| 582 | |
| 583 | |
| 584 @mock.patch.object(daemon.runner.DaemonRunner, "_start") | |
| 585 @mock.patch.object(daemon.runner.DaemonRunner, "_stop") | |
| 586 class DaemonRunner_do_action_restart_TestCase(DaemonRunner_BaseTestCase): | |
| 587 """ Test cases for DaemonRunner.do_action method, action 'restart'. """ | |
| 588 | |
| 589 def setUp(self): | |
| 590 """ Set up test fixtures. """ | |
| 591 super(DaemonRunner_do_action_restart_TestCase, self).setUp() | |
| 592 | |
| 593 set_runner_scenario(self, 'pidfile-locked') | |
| 594 | |
| 595 self.test_instance.action = 'restart' | |
| 596 | |
| 597 def test_requests_stop_then_start( | |
| 598 self, | |
| 599 mock_func_daemonrunner_start, mock_func_daemonrunner_stop): | |
| 600 """ Should request stop, then start. """ | |
| 601 instance = self.test_instance | |
| 602 instance.do_action() | |
| 603 mock_func_daemonrunner_start.assert_called_with() | |
| 604 mock_func_daemonrunner_stop.assert_called_with() | |
| 605 | |
| 606 | |
| 607 @mock.patch.object(sys, "stderr") | |
| 608 class emit_message_TestCase(scaffold.TestCase): | |
| 609 """ Test cases for ‘emit_message’ function. """ | |
| 610 | |
| 611 def test_writes_specified_message_to_stream(self, mock_stderr): | |
| 612 """ Should write specified message to stream. """ | |
| 613 test_message = self.getUniqueString() | |
| 614 expected_content = "{message}\n".format(message=test_message) | |
| 615 daemon.runner.emit_message(test_message, stream=mock_stderr) | |
| 616 mock_stderr.write.assert_called_with(expected_content) | |
| 617 | |
| 618 def test_writes_to_specified_stream(self, mock_stderr): | |
| 619 """ Should write message to specified stream. """ | |
| 620 test_message = self.getUniqueString() | |
| 621 mock_stream = mock.MagicMock() | |
| 622 daemon.runner.emit_message(test_message, stream=mock_stream) | |
| 623 mock_stream.write.assert_called_with(mock.ANY) | |
| 624 | |
| 625 def test_writes_to_stderr_by_default(self, mock_stderr): | |
| 626 """ Should write message to ‘sys.stderr’ by default. """ | |
| 627 test_message = self.getUniqueString() | |
| 628 daemon.runner.emit_message(test_message) | |
| 629 mock_stderr.write.assert_called_with(mock.ANY) | |
| 630 | |
| 631 | |
| 632 class is_pidfile_stale_TestCase(scaffold.TestCase): | |
| 633 """ Test cases for ‘is_pidfile_stale’ function. """ | |
| 634 | |
| 635 def setUp(self): | |
| 636 """ Set up test fixtures. """ | |
| 637 super(is_pidfile_stale_TestCase, self).setUp() | |
| 638 | |
| 639 func_patcher_os_kill = mock.patch.object(os, "kill") | |
| 640 func_patcher_os_kill.start() | |
| 641 self.addCleanup(func_patcher_os_kill.stop) | |
| 642 os.kill.return_value = None | |
| 643 | |
| 644 self.test_pid = self.getUniqueInteger() | |
| 645 self.test_pidfile = mock.MagicMock(daemon.pidfile.TimeoutPIDLockFile) | |
| 646 self.test_pidfile.read_pid.return_value = self.test_pid | |
| 647 | |
| 648 def test_returns_false_if_no_pid_in_file(self): | |
| 649 """ Should return False if the pidfile contains no PID. """ | |
| 650 self.test_pidfile.read_pid.return_value = None | |
| 651 expected_result = False | |
| 652 result = daemon.runner.is_pidfile_stale(self.test_pidfile) | |
| 653 self.assertEqual(expected_result, result) | |
| 654 | |
| 655 def test_returns_false_if_process_exists(self): | |
| 656 """ Should return False if the process with its PID exists. """ | |
| 657 expected_result = False | |
| 658 result = daemon.runner.is_pidfile_stale(self.test_pidfile) | |
| 659 self.assertEqual(expected_result, result) | |
| 660 | |
| 661 def test_returns_true_if_process_does_not_exist(self): | |
| 662 """ Should return True if the process does not exist. """ | |
| 663 test_error = ProcessLookupError("No such process") | |
| 664 del os.kill.return_value | |
| 665 os.kill.side_effect = test_error | |
| 666 expected_result = True | |
| 667 result = daemon.runner.is_pidfile_stale(self.test_pidfile) | |
| 668 self.assertEqual(expected_result, result) | |
| 669 | |
| 670 | |
| 671 # Local variables: | |
| 672 # coding: utf-8 | |
| 673 # mode: python | |
| 674 # End: | |
| 675 # vim: fileencoding=utf-8 filetype=python : |
