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 :